merge: bring local uncommitted work onto the cloud-agent branch

Merges the wip/local-snapshot-20260901 safety commit (94 uncommitted files:
in-flight Account/Mac/Tip work, clipboard-semantics improvements, and the
feature-preview recording tooling) into the newer cloud-agent line.

Conflict resolutions:
- docs/*: honour the branch's deliberate removal in eac44da; drop the now
  dangling docs/archive/README.md that only pointed at the deleted files.
- CHANGELOG: both sides describe separate [Unreleased] features; keep both.
- ClipboardSkillSemanticRanker: take the branch's reply routing, which retires
  ranking playfulReply/acceptInvitation/... as separate skills in favour of
  ordinary/formal/playful variants inside one reply action. Keep the local
  link/phone/CJK-density work, which is orthogonal and still referenced.
- OfficialSkillCatalogTests: keep the local "installed exactly once" assertion;
  it matches the test's own comment and does not hardcode the default set.
- ClipboardSkillSemanticRankerTests: keep both sides' disjoint test functions;
  retarget the two link/phone cases at the branch's row, which no longer
  surfaces the retired specialised reply skills.

Verified: xcodebuild build succeeds and the 77 ranker/catalog/analyzer tests
pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-01 12:34:23 -07:00
co-authored by Claude Opus 5
79 changed files with 3459 additions and 1178 deletions
@@ -715,6 +715,134 @@ final class ClipboardSkillSemanticRankerTests: XCTestCase {
XCTAssertNil(store.snapshot)
}
func testLinkInsideAMessageKeepsTheMessageSkills() {
let recommendations = ClipboardSkillSemanticRanker.recommended(
skills: AIClipboardSkillCatalog.catalog,
sourceText: "会议链接 https://zoom.us/j/123 周三下午三点开始,能来吗?",
analysis: analysis(
hasDate: true,
urls: [URL(string: "https://zoom.us/j/123")!],
question: detected(),
invitation: detected()
),
uiLanguage: .chinese,
limit: 5,
preferredLanguages: ["zh-Hans"]
).map(\.id)
XCTAssertEqual(
recommendations,
[
AIClipboardSkillCatalog.replyID,
AIClipboardSkillCatalog.extractEventsID,
AIClipboardSkillCatalog.openLinkID,
AIClipboardSkillCatalog.summarizeWebPageID
]
)
}
func testLabelledLinkPasteStaysExclusive() {
let recommendations = ClipboardSkillSemanticRanker.recommended(
skills: AIClipboardSkillCatalog.catalog,
sourceText: "详情见 https://example.com/article",
analysis: analysis(urls: [URL(string: "https://example.com/article")!]),
uiLanguage: .chinese,
limit: 5,
preferredLanguages: ["zh-Hans"]
).map(\.id)
XCTAssertEqual(
recommendations,
[
AIClipboardSkillCatalog.openLinkID,
AIClipboardSkillCatalog.summarizeWebPageID,
AIClipboardSkillCatalog.replyID
]
)
}
func testPhoneNumberInsideAMessageKeepsTheMessageSkills() {
let recommendations = ClipboardSkillSemanticRanker.recommended(
skills: AIClipboardSkillCatalog.catalog,
sourceText: "王经理说这个订单有问题,你直接打 400-666-8800 找售后处理一下。",
analysis: analysis(
phoneNumbers: [ClipboardTextLabel(sourceText: "400-666-8800")],
task: detected()
),
uiLanguage: .chinese,
limit: 5,
preferredLanguages: ["zh-Hans"]
).map(\.id)
XCTAssertEqual(
recommendations,
[
AIClipboardSkillCatalog.callPhoneID,
AIClipboardSkillCatalog.extractTodosID,
AIClipboardSkillCatalog.createContactID,
AIClipboardSkillCatalog.replyID
]
)
}
/// 180 Chinese characters carry about as much text as 400 Latin ones, so the
/// summary threshold must fire well below the raw 360-character mark.
func testChineseTextReachesTheSummaryThresholdBelowTheLatinCharacterCount() {
let text = String(
repeating: "这是一段需要归纳整理的中文长文内容。",
count: 10
)
let recommendations = ClipboardSkillSemanticRanker.recommended(
skills: AIClipboardSkillCatalog.catalog,
sourceText: text,
analysis: analysis(),
uiLanguage: .chinese,
limit: 5,
preferredLanguages: ["zh-Hans"]
).map(\.id)
XCTAssertLessThan(text.count, 360)
XCTAssertEqual(
recommendations,
[
AIClipboardSkillCatalog.summarizeID,
AIClipboardSkillCatalog.saveToNotesID,
AIClipboardSkillCatalog.replyID
]
)
}
func testHardWrappedChineseProseIsNotTreatedAsAList() {
let prose = [
"我们这次的目标是在下个季度之前完成整套流程的重构工作",
"同时还要保证现有的客户不会受到任何影响并保持稳定",
"最后需要在月底之前把完整的测试报告提交给管理层评审"
].joined(separator: "\n")
let proseRecommendations = ClipboardSkillSemanticRanker.recommended(
skills: AIClipboardSkillCatalog.catalog,
sourceText: prose,
analysis: analysis(),
uiLanguage: .chinese,
limit: 5,
preferredLanguages: ["zh-Hans"]
).map(\.id)
let listRecommendations = ClipboardSkillSemanticRanker.recommended(
skills: AIClipboardSkillCatalog.catalog,
sourceText: "牛奶\n鸡蛋\n面包",
analysis: analysis(),
uiLanguage: .chinese,
limit: 5,
preferredLanguages: ["zh-Hans"]
).map(\.id)
XCTAssertEqual(proseRecommendations, [AIClipboardSkillCatalog.replyID])
XCTAssertTrue(
listRecommendations.contains(AIClipboardSkillCatalog.organizeListID)
)
}
private func rank(
text: String,
analysis: ClipboardSemanticAnalysis