@co0ontty/wand 4.94.0 → 4.95.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. package/README.md +4 -1
  2. package/dist/ai-text-runner.d.ts +4 -0
  3. package/dist/ai-text-runner.js +44 -4
  4. package/dist/build-info.json +3 -3
  5. package/dist/cli-api.d.ts +1 -0
  6. package/dist/cli-api.js +7 -2
  7. package/dist/cli.js +47 -0
  8. package/dist/conversation-execution-preference.d.ts +7 -0
  9. package/dist/conversation-execution-preference.js +26 -0
  10. package/dist/conversation-organize-contract.d.ts +70 -0
  11. package/dist/conversation-organize-contract.js +1 -0
  12. package/dist/conversation-organize-service.d.ts +46 -0
  13. package/dist/conversation-organize-service.js +505 -0
  14. package/dist/conversation-organize.d.ts +41 -0
  15. package/dist/conversation-organize.js +97 -0
  16. package/dist/conversation-service.d.ts +2 -1
  17. package/dist/conversation-service.js +10 -4
  18. package/dist/core-turn-tracker.d.ts +5 -1
  19. package/dist/core-turn-tracker.js +17 -1
  20. package/dist/decision-expert-service.d.ts +2 -0
  21. package/dist/decision-expert-service.js +17 -1
  22. package/dist/decision-skill.d.ts +0 -1
  23. package/dist/decision-skill.js +5 -64
  24. package/dist/deployment-lock.js +4 -1
  25. package/dist/employee-im-config.d.ts +17 -0
  26. package/dist/employee-im-config.js +85 -0
  27. package/dist/employee-im-lifecycle.d.ts +35 -0
  28. package/dist/employee-im-lifecycle.js +168 -0
  29. package/dist/employee-im-types.d.ts +80 -0
  30. package/dist/employee-im-types.js +32 -0
  31. package/dist/employee-im.d.ts +35 -0
  32. package/dist/employee-im.js +157 -0
  33. package/dist/employee-knowledge.js +3 -2
  34. package/dist/employee-text.d.ts +1 -0
  35. package/dist/employee-text.js +28 -13
  36. package/dist/git-quick-commit.js +21 -3
  37. package/dist/managed-skill.d.ts +6 -0
  38. package/dist/managed-skill.js +99 -0
  39. package/dist/model-auto-assign.js +1 -1
  40. package/dist/process-manager.js +1 -1
  41. package/dist/prompt-optimizer.js +1 -1
  42. package/dist/scheduled-task-activity.d.ts +16 -0
  43. package/dist/scheduled-task-activity.js +42 -0
  44. package/dist/scheduled-task-agenda.d.ts +20 -0
  45. package/dist/scheduled-task-agenda.js +82 -0
  46. package/dist/scheduled-task-calendar.d.ts +37 -0
  47. package/dist/scheduled-task-calendar.js +131 -0
  48. package/dist/scheduled-task-read-range.d.ts +7 -0
  49. package/dist/scheduled-task-read-range.js +35 -0
  50. package/dist/scheduled-task-service.d.ts +6 -1
  51. package/dist/scheduled-task-service.js +61 -1
  52. package/dist/scheduled-task-state.d.ts +1 -1
  53. package/dist/scheduled-task-types.d.ts +38 -0
  54. package/dist/server-asset-routes.js +3 -2
  55. package/dist/server-conversation-organize-routes.d.ts +4 -0
  56. package/dist/server-conversation-organize-routes.js +75 -0
  57. package/dist/server-conversation-routes.js +2 -1
  58. package/dist/server-employee-im-routes.d.ts +9 -0
  59. package/dist/server-employee-im-routes.js +40 -0
  60. package/dist/server-employee-routes.js +1 -1
  61. package/dist/server-scheduled-task-routes.js +24 -0
  62. package/dist/server-session-lifecycle-routes.js +8 -3
  63. package/dist/server-session-routes.js +18 -0
  64. package/dist/server-session-worktree-routes.js +3 -3
  65. package/dist/server-workspace-routes.d.ts +3 -0
  66. package/dist/server-workspace-routes.js +14 -1
  67. package/dist/server.js +39 -5
  68. package/dist/session-ai-context.d.ts +11 -1
  69. package/dist/session-ai-context.js +21 -2
  70. package/dist/session-metadata-service.js +1 -1
  71. package/dist/session-registry.js +6 -0
  72. package/dist/session-request.js +3 -0
  73. package/dist/session-topic.js +1 -1
  74. package/dist/silicon-employee-draft.js +1 -1
  75. package/dist/speech-polisher-service.d.ts +2 -0
  76. package/dist/speech-polisher-service.js +28 -6
  77. package/dist/storage/conversation-organization-repository.d.ts +13 -0
  78. package/dist/storage/conversation-organization-repository.js +33 -0
  79. package/dist/storage/migrations.js +10 -0
  80. package/dist/storage/secrets-repository.d.ts +7 -0
  81. package/dist/storage/secrets-repository.js +28 -1
  82. package/dist/storage/tasks-repository.js +4 -0
  83. package/dist/storage-schedules.d.ts +8 -1
  84. package/dist/storage-schedules.js +24 -0
  85. package/dist/storage-schema.js +9 -0
  86. package/dist/storage.d.ts +6 -1
  87. package/dist/storage.js +13 -0
  88. package/dist/structured-session-manager.d.ts +1 -0
  89. package/dist/structured-session-manager.js +18 -4
  90. package/dist/system-ai-call.d.ts +49 -0
  91. package/dist/system-ai-call.js +28 -0
  92. package/dist/system-ai-session-recorder.d.ts +30 -0
  93. package/dist/system-ai-session-recorder.js +239 -0
  94. package/dist/system-cli.d.ts +6 -0
  95. package/dist/system-cli.js +93 -0
  96. package/dist/system-skill.d.ts +5 -0
  97. package/dist/system-skill.js +19 -0
  98. package/dist/task-organize-contract.js +2 -2
  99. package/dist/task-title-organize-service.d.ts +1 -1
  100. package/dist/task-title-organize-service.js +9 -5
  101. package/dist/task-title-organize.js +13 -6
  102. package/dist/task-title-service.js +1 -1
  103. package/dist/task-title.js +1 -1
  104. package/dist/user-memory.js +1 -1
  105. package/dist/wand-task-sync.js +2 -1
  106. package/dist/web-ui/content/ai-teams.js +12 -12
  107. package/dist/web-ui/content/module-manifest.json +1 -1
  108. package/dist/web-ui/content/modules/{0051bd3d55f10ab7/library-bridge-DTMQIHVJ.js → 09842858f9d34a4e/library-bridge.js} +2 -2
  109. package/dist/web-ui/content/modules/1021ae40c5761e3f/sha256.js +1 -0
  110. package/dist/web-ui/content/modules/134f6120c0acaf57/host.js +1 -0
  111. package/dist/web-ui/content/modules/1d518ae6ed66fe5a/chunk.js +3 -0
  112. package/dist/web-ui/content/modules/1f93b5bd70381bb6/runtime.js +539 -0
  113. package/dist/web-ui/content/modules/26c68d4decdd18b4/markdown.js +1 -0
  114. package/dist/web-ui/content/modules/2ed8bafd5b4fecc5/workspace-window.js +1 -0
  115. package/dist/web-ui/content/modules/31ffc2f05f87652c/host.js +1 -0
  116. package/dist/web-ui/content/modules/35aaa85b9d0937af/chunk.js +20 -0
  117. package/dist/web-ui/content/modules/3664d0620f124292/chunk.js +3 -0
  118. package/dist/web-ui/content/modules/3cea480770cb184c/chunk.js +1 -0
  119. package/dist/web-ui/content/modules/3d12adf54647c1e2/host-bridge.js +1 -0
  120. package/dist/web-ui/content/modules/459cca2482a9a4ba/host.js +18 -0
  121. package/dist/web-ui/content/modules/474c9647130ef3e9/task-board-host.js +2 -0
  122. package/dist/web-ui/content/modules/4f74d2517f29134c/chunk.js +15 -0
  123. package/dist/web-ui/content/modules/52a218d74002a011/host.js +30 -0
  124. package/dist/web-ui/content/modules/54f2b10659ef8475/employee-card.js +1 -0
  125. package/dist/web-ui/content/modules/703fb1c670f7b199/chunk.js +1 -0
  126. package/dist/web-ui/content/modules/75511da23f051718/host.js +1 -0
  127. package/dist/web-ui/content/modules/{0051bd3d55f10ab7/host-2IVIZ2YC.js → 799b19da948bec8d/host.js} +3 -2
  128. package/dist/web-ui/content/modules/{0051bd3d55f10ab7/host-XNIR3NTJ.js → 7ef73b6c20d2504f/host.js} +3 -2
  129. package/dist/web-ui/content/modules/8f8f7a0a8874d29a/host.js +1 -0
  130. package/dist/web-ui/content/modules/90f2cd73cdf3a2cd/chat-render.js +25 -0
  131. package/dist/web-ui/content/modules/9510efce6caaa9ec/chunk.js +1 -0
  132. package/dist/web-ui/content/modules/9a0e65188739957f/chunk.js +67 -0
  133. package/dist/web-ui/content/modules/9eae1033f47e4526/host.js +1 -0
  134. package/dist/web-ui/content/modules/a2c5b246e57fa165/panel.js +1 -0
  135. package/dist/web-ui/content/modules/a50ce28f45bfeb2e/host.js +1 -0
  136. package/dist/web-ui/content/modules/adfc53789d5bedb2/chunk.js +1 -0
  137. package/dist/web-ui/content/modules/bcecb0cc14b7d2bd/chunk.js +1 -0
  138. package/dist/web-ui/content/modules/c4706f47c23f33e4/host.js +1 -0
  139. package/dist/web-ui/content/modules/{0051bd3d55f10ab7/host-Q524TMSR.js → c7de873095c061ab/host.js} +4 -3
  140. package/dist/web-ui/content/modules/d789f84a3f8765ae/page.js +6 -0
  141. package/dist/web-ui/content/modules/db4e2dbc7aed6353/chunk.js +1 -0
  142. package/dist/web-ui/content/modules/e4fd9906521b4aa3/chunk.js +1 -0
  143. package/dist/web-ui/content/modules/eb95c3ee0d42827a/chunk.js +6 -0
  144. package/dist/web-ui/content/modules/{0051bd3d55f10ab7/host-PHHNGVNL.js → ec68d93a95c1d5d6/host.js} +2 -2
  145. package/dist/web-ui/content/modules/f33ed89d1ccd3a2d/chunk.js +1 -0
  146. package/dist/web-ui/content/modules/f6502dab121f9d91/chunk.js +1 -0
  147. package/dist/web-ui/content/modules/fac83e61c0a8c8a7/host.js +1 -0
  148. package/dist/web-ui/content/scripts.js +1 -1
  149. package/dist/web-ui/embedded-assets.d.ts +200 -27
  150. package/dist/web-ui/embedded-assets.js +5 -4
  151. package/dist/web-ui/react/workspaces/sidebar-manage.d.ts +5 -0
  152. package/dist/web-ui/react/workspaces/sidebar-manage.js +27 -3
  153. package/dist/web-ui/running-activity.d.ts +22 -9
  154. package/dist/web-ui/running-activity.js +58 -42
  155. package/dist/web-ui/running-current-action.d.ts +12 -0
  156. package/dist/web-ui/running-current-action.js +74 -0
  157. package/dist/web-ui/scripts.d.ts +4 -0
  158. package/dist/web-ui/scripts.js +17 -8
  159. package/package.json +4 -2
  160. package/scripts/recover-web-update.mjs +26 -19
  161. package/scripts/recover-web-update.sh +9 -6
  162. package/scripts/update-node.sh +3 -1
  163. package/skills/wand-system/SKILL.md +69 -0
  164. package/skills/wand-system/references/operations.md +100 -0
  165. package/skills/wand-system/scripts/wand.mjs +34 -0
  166. package/dist/web-ui/content/modules/0051bd3d55f10ab7/chat-render-RY2I2CF3.js +0 -24
  167. package/dist/web-ui/content/modules/0051bd3d55f10ab7/chunk-2F6J2MKX.js +0 -1
  168. package/dist/web-ui/content/modules/0051bd3d55f10ab7/chunk-6C6A2RHG.js +0 -1
  169. package/dist/web-ui/content/modules/0051bd3d55f10ab7/chunk-7AFM245W.js +0 -1
  170. package/dist/web-ui/content/modules/0051bd3d55f10ab7/chunk-ECWYA3U5.js +0 -1
  171. package/dist/web-ui/content/modules/0051bd3d55f10ab7/chunk-MHX2QXIS.js +0 -111
  172. package/dist/web-ui/content/modules/0051bd3d55f10ab7/chunk-RAAPETNH.js +0 -1
  173. package/dist/web-ui/content/modules/0051bd3d55f10ab7/chunk-RXKBHYUR.js +0 -38
  174. package/dist/web-ui/content/modules/0051bd3d55f10ab7/chunk-T35D57FW.js +0 -1
  175. package/dist/web-ui/content/modules/0051bd3d55f10ab7/chunk-U4EDP4TI.js +0 -1
  176. package/dist/web-ui/content/modules/0051bd3d55f10ab7/chunk-WHXQR7JT.js +0 -20
  177. package/dist/web-ui/content/modules/0051bd3d55f10ab7/chunk-YOXXNXOX.js +0 -2
  178. package/dist/web-ui/content/modules/0051bd3d55f10ab7/chunk-YQJ6XYV7.js +0 -1
  179. package/dist/web-ui/content/modules/0051bd3d55f10ab7/employee-card-NV34TZIH.js +0 -1
  180. package/dist/web-ui/content/modules/0051bd3d55f10ab7/host-2AQAIUNO.js +0 -1
  181. package/dist/web-ui/content/modules/0051bd3d55f10ab7/host-AZXNFCRW.js +0 -1
  182. package/dist/web-ui/content/modules/0051bd3d55f10ab7/host-EPEQX22R.js +0 -1
  183. package/dist/web-ui/content/modules/0051bd3d55f10ab7/host-FZYIMOLC.js +0 -1
  184. package/dist/web-ui/content/modules/0051bd3d55f10ab7/host-G6GL4SNL.js +0 -1
  185. package/dist/web-ui/content/modules/0051bd3d55f10ab7/host-PEILC5NU.js +0 -1
  186. package/dist/web-ui/content/modules/0051bd3d55f10ab7/host-RO5MB6RB.js +0 -69
  187. package/dist/web-ui/content/modules/0051bd3d55f10ab7/host-WOQRK6NG.js +0 -1
  188. package/dist/web-ui/content/modules/0051bd3d55f10ab7/host-XRVOIEP6.js +0 -1
  189. package/dist/web-ui/content/modules/0051bd3d55f10ab7/host-ZDKOQEMS.js +0 -1
  190. package/dist/web-ui/content/modules/0051bd3d55f10ab7/host-bridge-PMQZRG5G.js +0 -1
  191. package/dist/web-ui/content/modules/0051bd3d55f10ab7/page-PY4JUL4W.js +0 -3
  192. package/dist/web-ui/content/modules/0051bd3d55f10ab7/panel-BBQFC55Q.js +0 -1
  193. package/dist/web-ui/content/modules/0051bd3d55f10ab7/runtime.js +0 -974
  194. package/dist/web-ui/content/modules/0051bd3d55f10ab7/sha256-ANXV72UC.js +0 -1
  195. package/dist/web-ui/content/modules/0051bd3d55f10ab7/task-board-host-PXZSWBBG.js +0 -2
  196. package/dist/web-ui/content/modules/0051bd3d55f10ab7/workspace-agent-picker-DJSECSY6.js +0 -1
  197. package/dist/web-ui/content/modules/0051bd3d55f10ab7/workspace-window-MNMKSOBG.js +0 -1
@@ -0,0 +1,239 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { CONVERSATION_OWNER, employeeConversationId } from "./conversation-types.js";
3
+ import { conversationSessionPreview } from "./conversation-session-preview.js";
4
+ import { defaultStructuredRunner } from "./structured-provider-common.js";
5
+ import { providerCliCommand } from "./provider-catalog.js";
6
+ import { buildStructuredOutputPayload } from "./structured-session-manager-replay.js";
7
+ import { AiCallTraceError, SYSTEM_AI_CALL_PREFIX, SYSTEM_AI_PURPOSES, SYSTEM_AI_READ_ONLY, isSystemAiCall } from "./system-ai-call.js";
8
+ const INPUT_LIMIT = 24_000;
9
+ const OUTPUT_LIMIT = 32_000;
10
+ const MESSAGE_LIMIT = 256;
11
+ const recorders = new WeakMap();
12
+ /** Only copies business text. Never logs credential commands, runtime config, or provider error bodies. */
13
+ export function redactSystemAiText(value) {
14
+ let text = value;
15
+ for (const [key, secret] of Object.entries(process.env)) {
16
+ if (/(?:KEY|TOKEN|PASSWORD|SECRET|CONNECTION_CODE)/i.test(key) && secret && secret.length >= 6)
17
+ text = text.split(secret).join("[已脱敏]");
18
+ }
19
+ text = text.replace(/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g, "[私钥已脱敏]")
20
+ .replace(/\bBearer\s+[A-Za-z0-9._~+\/-]+=*/gi, "Bearer [已脱敏]")
21
+ .replace(/\b(?:sk-|ghp_|github_pat_|xox[baprs]-)[A-Za-z0-9_-]{10,}/g, "[已脱敏]")
22
+ .replace(/((?:["']?(?:api[_ -]?key|access[_ -]?token|connection[_ -]?code|password|secret|密钥|密码|口令|连接码)["']?)\s*[:=:]\s*)("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|[^\s"',;\n}]+)/gi, (_all, key, value) => key + (value.startsWith('"') ? '"[已脱敏]"' : value.startsWith("'") ? "'[已脱敏]'" : "[已脱敏]"));
23
+ return text;
24
+ }
25
+ function logText(value, limit) {
26
+ const text = redactSystemAiText(value);
27
+ return text.length > limit ? text.slice(0, limit) + `\n(日志已限长,原始 ${text.length} 字符)` : text;
28
+ }
29
+ function resultText(value) {
30
+ if (typeof value === "string")
31
+ return value;
32
+ try {
33
+ return JSON.stringify(value, null, 2) ?? "(无返回值)";
34
+ }
35
+ catch {
36
+ return "(结果不可序列化)";
37
+ }
38
+ }
39
+ function failureCode(error) {
40
+ const value = error && typeof error === "object" && "code" in error ? String(error.code) : error instanceof Error ? error.name : "UNKNOWN";
41
+ return /^[A-Za-z0-9_-]{1,64}$/.test(value) ? value : "AI_CALL_FAILED";
42
+ }
43
+ function turn(text, notice = false) {
44
+ const now = new Date().toISOString();
45
+ return { role: "assistant", content: [{ type: "text", text }], notice, createdAt: now, completedAt: now };
46
+ }
47
+ export class SystemAiSessionRecorder {
48
+ storage;
49
+ closed = false;
50
+ active = new Set();
51
+ logger;
52
+ emit;
53
+ notifyConversation;
54
+ constructor(storage) {
55
+ this.storage = storage;
56
+ storage.onClose(() => { this.closed = true; });
57
+ }
58
+ attach(options) {
59
+ this.logger = options.logger ?? this.logger;
60
+ this.emit = options.emit ?? this.emit;
61
+ this.notifyConversation = options.notifyConversation ?? this.notifyConversation;
62
+ }
63
+ /** A server-owned one-shot has no detached runner to adopt after a restart. Never replay it. */
64
+ recoverInterrupted() {
65
+ for (const slim of this.storage.loadSessionsSlim()) {
66
+ if (!isSystemAiCall(slim) || slim.status !== "running" || this.active.has(slim.id))
67
+ continue;
68
+ const session = this.storage.getSession(slim.id);
69
+ if (!session)
70
+ continue;
71
+ const now = new Date().toISOString();
72
+ this.persist({ ...session, status: "failed", exitCode: 1, endedAt: now,
73
+ messages: [...(session.messages ?? []), turn("服务重启,本次系统调用结果未知;未自动重试。")],
74
+ ...(session.structuredState ? { structuredState: { ...session.structuredState, inFlight: false,
75
+ activeRequestId: null, turnStartedAt: null, lastActivityAt: null, lastError: "服务重启,系统调用结果未知。" } } : {}),
76
+ }, false);
77
+ }
78
+ }
79
+ forEmployee(employee, sourceSessionId) {
80
+ const stored = this.storage.getSiliconEmployee(employee.id);
81
+ if (!stored || stored.systemKey !== employee.systemKey)
82
+ throw new AiCallTraceError();
83
+ // Freeze attribution, not the employee's private knowledge or current UI display name.
84
+ const actor = { id: employee.id, name: employee.name, avatar: employee.avatar, prompt: employee.prompt };
85
+ return { start: (purpose, request, cwd) => this.start(actor, purpose, request, cwd, sourceSessionId) };
86
+ }
87
+ remove(sessionId) {
88
+ const current = this.storage.getSessionSlim(sessionId);
89
+ if (!current || !isSystemAiCall(current))
90
+ return;
91
+ if (current.status === "running")
92
+ throw Object.assign(new Error("系统调用仍在执行,完成后才能删除日志。"), { statusCode: 409 });
93
+ this.storage.deleteSession(sessionId);
94
+ this.logger?.deleteSession(sessionId);
95
+ }
96
+ appendOutcome(sessionId, label, value) {
97
+ if (this.closed)
98
+ return;
99
+ const current = this.storage.getSession(sessionId);
100
+ if (!current || !isSystemAiCall(current))
101
+ return;
102
+ this.persist({ ...current, messages: [...(current.messages ?? []), turn(`## ${label}\n\n${logText(resultText(value), OUTPUT_LIMIT)}`)] }, false);
103
+ }
104
+ persist(snapshot, create) {
105
+ if (this.closed)
106
+ return;
107
+ try {
108
+ // Deletion is authoritative: late completion must never recreate a removed record or DM.
109
+ if (!create && !this.storage.getSessionSlim(snapshot.id))
110
+ return;
111
+ const conversationId = employeeConversationId(snapshot.employeeId);
112
+ if (create && this.storage.conversationListState(conversationId).deleting)
113
+ throw new AiCallTraceError();
114
+ this.storage.saveSession(snapshot);
115
+ if (!this.storage.conversationListState(conversationId).deleting) {
116
+ const instance = this.storage.getConversation(conversationId) ?? {
117
+ id: conversationId, owner: CONVERSATION_OWNER, kind: "dm", peerEmployeeId: snapshot.employeeId,
118
+ name: snapshot.employeeName, nameSource: "auto", sourceTemplateId: null, team: null,
119
+ memberVersion: 1, joinedVersions: {}, sessionId: null, communicationSessionId: null,
120
+ createdAt: snapshot.startedAt, updatedAt: snapshot.endedAt ?? snapshot.startedAt,
121
+ };
122
+ // Canonical link stores no second transcript and never pretends the system sent a user DM.
123
+ this.storage.linkEmployeeSession(instance, snapshot);
124
+ this.notifyConversation?.({ conversationId, sessionId: snapshot.id, preview: conversationSessionPreview(snapshot) });
125
+ }
126
+ this.logger?.saveMessages(snapshot.id, snapshot.messages ?? []);
127
+ this.logger?.saveMetadata(snapshot.id, { kind: "system-ai-call", employeeId: snapshot.employeeId,
128
+ operation: snapshot.title, provider: snapshot.provider, model: snapshot.selectedModel,
129
+ status: snapshot.status, startedAt: snapshot.startedAt, endedAt: snapshot.endedAt });
130
+ this.emit?.({ type: create ? "started" : "output",
131
+ sessionId: snapshot.id, data: { ...buildStructuredOutputPayload(snapshot),
132
+ automationId: snapshot.automationId, sessionSource: snapshot.sessionSource,
133
+ status: snapshot.status, exitCode: snapshot.exitCode, endedAt: snapshot.endedAt, } });
134
+ }
135
+ catch (error) {
136
+ if (error instanceof AiCallTraceError)
137
+ throw error;
138
+ throw new AiCallTraceError();
139
+ }
140
+ }
141
+ start(actor, purpose, request, cwd, sourceSessionId) {
142
+ const id = randomUUID(), now = new Date().toISOString();
143
+ const label = SYSTEM_AI_PURPOSES[purpose];
144
+ const system = actor.prompt && request.system.startsWith(actor.prompt) ? request.system.slice(actor.prompt.length).trim() : request.system;
145
+ const sensitiveInput = purpose.startsWith("git-") || purpose === "user-memory";
146
+ const input = sensitiveInput
147
+ ? `该输入可能包含源码差异或私人习惯语料,仅保存范围校验信息。\n字符数:${request.prompt.length}\nSHA256:${createHash("sha256").update(request.prompt).digest("hex")}`
148
+ : logText(request.prompt, INPUT_LIMIT);
149
+ let snapshot = {
150
+ id, sessionKind: "structured", sessionSource: "automation", automationId: `${SYSTEM_AI_CALL_PREFIX}${purpose}:${id}`,
151
+ employeeId: actor.id, employeeName: actor.name, employeeAvatar: actor.avatar,
152
+ command: "system-ai", cwd, mode: "default", status: "running", exitCode: null,
153
+ startedAt: now, endedAt: null, output: "", archived: false, archivedAt: null, claudeSessionId: null, worktreeEnabled: false,
154
+ title: `系统调用 · ${label}`, description: `${actor.name} · ${label} · 只读调用记录`,
155
+ completionRevision: 0, viewedCompletionRevision: 0,
156
+ messages: [{ role: "user", author: { id: "system", name: "系统" }, createdAt: now,
157
+ content: [{ type: "text", text: `# ${label}\n\n${SYSTEM_AI_READ_ONLY}\n\n${sourceSessionId ? `关联会话:${sourceSessionId}\n\n` : ""}## 调用规则\n\n${logText(system, INPUT_LIMIT)}\n\n## 输入\n\n${input}` }] }],
158
+ };
159
+ this.active.add(id);
160
+ try {
161
+ this.persist(snapshot, true);
162
+ }
163
+ catch (error) {
164
+ this.active.delete(id);
165
+ throw error;
166
+ }
167
+ let finished = false, omitted = 0, attempts = 0, usageAvailable = false;
168
+ const append = (text, force = false) => {
169
+ if (finished)
170
+ return;
171
+ const messages = snapshot.messages ?? [];
172
+ if (messages.length >= MESSAGE_LIMIT && !force) {
173
+ omitted++;
174
+ const last = messages.at(-1);
175
+ const notice = turn(`候选明细达到日志上限,另外 ${omitted} 条已折叠;最终结果仍保留。`, true);
176
+ snapshot = { ...snapshot, messages: last?.notice ? [...messages.slice(0, -1), notice] : [...messages, notice] };
177
+ }
178
+ else
179
+ snapshot = { ...snapshot, messages: [...messages, turn(logText(text, OUTPUT_LIMIT))] };
180
+ this.persist(snapshot, false);
181
+ };
182
+ const finish = (status, message) => {
183
+ if (finished)
184
+ return;
185
+ if (message)
186
+ append(message, true);
187
+ finished = true;
188
+ this.active.delete(id);
189
+ snapshot = { ...snapshot, status, endedAt: new Date().toISOString(), exitCode: status === "exited" ? 0 : 1,
190
+ ...(snapshot.structuredState ? { structuredState: { ...snapshot.structuredState, inFlight: false,
191
+ activeRequestId: null, turnStartedAt: null, lastActivityAt: null, lastError: status === "exited" ? null : message ?? "系统调用失败。" } } : {}),
192
+ };
193
+ this.persist(snapshot, false);
194
+ };
195
+ const candidateName = (candidate) => `${candidate.provider} / ${candidate.engine === "sdk" ? "Wand Agent SDK" : "CLI"} / ${candidate.model ?? "默认模型"}`;
196
+ return {
197
+ sessionId: id,
198
+ candidateStarted: candidate => {
199
+ attempts++;
200
+ const runner = defaultStructuredRunner(candidate.provider);
201
+ snapshot = { ...snapshot, provider: candidate.provider, runner, selectedModel: candidate.model ? logText(candidate.model, 256) : null,
202
+ thinkingEffort: candidate.thinkingEffort ?? "off", command: providerCliCommand(candidate.provider),
203
+ structuredState: { provider: candidate.provider, runner, model: candidate.model ? logText(candidate.model, 256) : undefined,
204
+ engine: candidate.engine === "sdk" ? "core" : "cli", inFlight: true, activeRequestId: id,
205
+ turnStartedAt: now, lastActivityAt: new Date().toISOString(), lastError: null } };
206
+ append(`候选 ${attempts}:开始尝试 ${candidateName(candidate)}。`);
207
+ },
208
+ localStarted: model => {
209
+ attempts++;
210
+ snapshot = { ...snapshot, provider: undefined, runner: undefined, structuredState: undefined,
211
+ selectedModel: logText(model, 256), command: "local-decision" };
212
+ append(`候选 ${attempts}:开始本地决策 worker(${model}),不是 CLI 或 Wand Agent SDK。`);
213
+ },
214
+ candidateSkipped: (candidate, reason) => append(`未执行候选 ${candidateName(candidate)}:${reason}`),
215
+ candidateFailed: (candidate, error, phase) => append(`候选 ${candidateName(candidate)} ${phase === "availability" ? "准备" : phase === "result" ? "结果校验" : "调用"}失败(${failureCode(error)})。未保存提供商错误正文。`),
216
+ candidateSucceeded: (raw, parsed) => {
217
+ append(`## 模型输出(已通过调用方校验)\n\n${raw}`, true);
218
+ if (typeof parsed !== "string" || parsed !== raw)
219
+ append(`## 解析后的业务结果\n\n${resultText(parsed)}`, true);
220
+ },
221
+ usageReported: usage => {
222
+ usageAvailable = true;
223
+ snapshot = { ...snapshot, selectedModel: logText(usage.model, 256) };
224
+ append(`SDK 实际模型:${usage.model};适配器统计输入 ${usage.inputTokens} / 输出 ${usage.outputTokens} tokens(不是账单审计)。`);
225
+ },
226
+ applicationResult: value => append(`## 实际业务执行结果\n\n${resultText(value)}`, true),
227
+ complete: () => finish("exited", `${attempts ? `调用完成;尝试了 ${attempts} 个候选。` : "系统操作已结束;本记录未直接请求模型。"}具体业务是否应用以单独记录的实际业务结果为准。${attempts && !usageAvailable ? "未采集到实际叶子/上游用量,不估算费用。" : ""}`),
228
+ fail: (error, cancelled = false) => finish(cancelled ? "stopped" : "failed", `${cancelled ? "调用已取消,结果不再应用" : "调用失败"}(${failureCode(error)});未自动扩大模型/费用范围。`),
229
+ };
230
+ }
231
+ }
232
+ export function systemAiSessions(storage) {
233
+ let recorder = recorders.get(storage);
234
+ if (!recorder) {
235
+ recorder = new SystemAiSessionRecorder(storage);
236
+ recorders.set(storage, recorder);
237
+ }
238
+ return recorder;
239
+ }
@@ -0,0 +1,6 @@
1
+ import type { WandCliApi } from "./cli-api.js";
2
+ type Api = Pick<WandCliApi, "get" | "post" | "patch">;
3
+ type Operation = (api: Api) => Promise<unknown>;
4
+ /** Parse before opening config/authentication; never retries mutations or accepts arbitrary API paths. */
5
+ export declare function parseSystemCliCommand(command: string, args: string[], input?: AsyncIterable<string | Buffer>): Promise<Operation>;
6
+ export {};
@@ -0,0 +1,93 @@
1
+ const MAX_INPUT_BYTES = 1024 * 1024;
2
+ async function jsonInput(input) {
3
+ const chunks = [];
4
+ let size = 0;
5
+ for await (const chunk of input) {
6
+ const bytes = Buffer.from(chunk);
7
+ size += bytes.length;
8
+ if (size > MAX_INPUT_BYTES)
9
+ throw new Error("系统操作 JSON 超过 1 MiB。");
10
+ chunks.push(bytes);
11
+ }
12
+ const value = JSON.parse(Buffer.concat(chunks).toString("utf8"));
13
+ if (!value || typeof value !== "object" || Array.isArray(value))
14
+ throw new Error("需要 JSON 对象。");
15
+ return value;
16
+ }
17
+ function fields(body, allowed) {
18
+ for (const key of Object.keys(body))
19
+ if (!allowed.includes(key))
20
+ throw new Error(`不支持字段:${key}`);
21
+ }
22
+ function requiredText(value, field) {
23
+ if (typeof value !== "string" || !value.trim())
24
+ throw new Error(`缺少 ${field}。`);
25
+ return value;
26
+ }
27
+ /** Parse before opening config/authentication; never retries mutations or accepts arbitrary API paths. */
28
+ export async function parseSystemCliCommand(command, args, input = process.stdin) {
29
+ const reads = {
30
+ "employee:list": "/api/silicon-employees",
31
+ "workspace:list": "/api/workspaces",
32
+ "schedule:list": "/api/scheduled-tasks",
33
+ };
34
+ if (reads[command]) {
35
+ const flag = command === "employee:list" ? "--include-archived" : command === "schedule:list" ? "--include-cancelled" : null;
36
+ if (args.length && !(flag && args.length === 1 && args[0] === flag))
37
+ throw new Error(`用法:wand ${command}${flag ? ` [${flag}]` : ""}`);
38
+ const query = args.length ? (command === "employee:list" ? "?includeArchived=1" : "?includeCancelled=1") : "";
39
+ return api => api.get(reads[command] + query);
40
+ }
41
+ if (command === "schedule:read") {
42
+ if (args.length !== 1 || !args[0] || args[0].startsWith("-"))
43
+ throw new Error("用法:wand schedule:read <id>");
44
+ return api => api.get(`/api/scheduled-tasks/${encodeURIComponent(args[0])}`);
45
+ }
46
+ const withId = command === "schedule:update" || command === "session:resume";
47
+ const id = withId ? args[0] : undefined;
48
+ if ((withId && (!id || id.startsWith("-"))) || args.length !== (withId ? 2 : 1) || args.at(-1) !== "--stdin") {
49
+ throw new Error(`用法:wand ${command}${withId ? " <id>" : ""} --stdin`);
50
+ }
51
+ const body = await jsonInput(input);
52
+ if (command === "session:create") {
53
+ fields(body, ["employeeId", "workspaceId", "cwd", "prompt"]);
54
+ requiredText(body.employeeId, "employeeId(先查询员工列表)");
55
+ // Employee's configured provider, model, role and permissions remain authoritative.
56
+ return api => api.post("/api/structured-sessions", { ...body, respondImmediately: true });
57
+ }
58
+ if (command === "schedule:create") {
59
+ fields(body, ["requestId", "employeeId", "workspaceId", "title", "prompt", "schedule"]);
60
+ requiredText(body.requestId, "requestId(保存后复用,不要重建)");
61
+ requiredText(body.employeeId, "employeeId");
62
+ requiredText(body.prompt, "prompt");
63
+ return api => api.post("/api/scheduled-tasks", body);
64
+ }
65
+ if (command === "schedule:update") {
66
+ fields(body, ["revision", "employeeId", "workspaceId", "title", "prompt", "schedule", "enabled", "cancelled"]);
67
+ if (!Number.isInteger(body.revision) || Number(body.revision) < 1)
68
+ throw new Error("需要当前 revision。");
69
+ return api => api.patch(`/api/scheduled-tasks/${encodeURIComponent(id)}`, body);
70
+ }
71
+ if (command === "session:resume") {
72
+ fields(body, ["input"]);
73
+ requiredText(body.input, "input(检查证据及明确的续接要求)");
74
+ return async (api) => {
75
+ const route = `/api/sessions/${encodeURIComponent(id)}`;
76
+ const snapshot = await api.get(route);
77
+ if (snapshot.id !== id)
78
+ throw new Error("会话 ID 不匹配,未续接。");
79
+ if (snapshot.structuredState?.inFlight || snapshot.ptyCommandRunning || snapshot.ptyBusy
80
+ || snapshot.permissionBlocked || snapshot.pendingEscalation || snapshot.queuedMessages?.length) {
81
+ throw new Error("目标会话仍在执行、等待审批或有排队消息;未打断或重复唤起,请核对。");
82
+ }
83
+ if (snapshot.sessionKind !== "structured") {
84
+ throw new Error("此入口仅续接 Wand 结构化会话;PTY 请通过原终端恢复,不能冒充已恢复。");
85
+ }
86
+ if (snapshot.archived)
87
+ await api.post(`${route}/unarchive`, {});
88
+ // Managed structured sessions resume their native history through the existing input owner.
89
+ return api.post(`${route}/input`, { input: body.input, respondImmediately: true });
90
+ };
91
+ }
92
+ throw new Error("未知系统操作。");
93
+ }
@@ -0,0 +1,5 @@
1
+ import { type SessionProvider } from "./provider-catalog.js";
2
+ export declare function installSystemSkill(configPath: string, providers?: readonly SessionProvider[], home?: string): Promise<{
3
+ installed: string[];
4
+ providers: readonly SessionProvider[];
5
+ }>;
@@ -0,0 +1,19 @@
1
+ import { existsSync } from "node:fs";
2
+ import { readFile } from "node:fs/promises";
3
+ import { homedir } from "node:os";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { installManagedSkill } from "./managed-skill.js";
7
+ import { SESSION_PROVIDERS } from "./provider-catalog.js";
8
+ export async function installSystemSkill(configPath, providers = SESSION_PROVIDERS, home = homedir()) {
9
+ const source = fileURLToPath(new URL("../skills/wand-system/", import.meta.url));
10
+ const files = Object.fromEntries(await Promise.all(["SKILL.md", "references/operations.md", "scripts/wand.mjs"].map(async (file) => [file, await readFile(path.join(source, file), "utf8")])));
11
+ const js = fileURLToPath(new URL("./cli.js", import.meta.url));
12
+ const cli = existsSync(js) ? js : js.replace(/\.js$/, ".ts");
13
+ // Only local executable/config paths, never an admin password or capability token.
14
+ files[".runtime.json"] = JSON.stringify({
15
+ node: process.execPath, cli, config: path.resolve(configPath),
16
+ ...(cli.endsWith(".ts") ? { loader: import.meta.resolve("tsx") } : {}),
17
+ });
18
+ return installManagedSkill(path.dirname(configPath), "wand-system", files, providers, home);
19
+ }
@@ -1,4 +1,4 @@
1
1
  export const TASK_ORGANIZE_OPTIONS = [
2
- { value: "auto", label: "自动任务归类", hint: "仅系统自动标题,跳过用户自定标题" },
3
- { value: "all", label: "所有任务归类", hint: "包含用户自定标题的任务" },
2
+ { value: "auto", label: "默认范围会话", hint: "未分组及系统自动任务中的工作会话;不挪动人工命名任务中的会话" },
3
+ { value: "all", label: "所有工作会话", hint: "包含人工任务中的会话;仍跳过归档、私聊、自动化执行和系统日志" },
4
4
  ];
@@ -4,7 +4,7 @@ import { type TitleJudge } from "./task-title-organize.js";
4
4
  import type { WandStorage } from "./storage.js";
5
5
  import type { OrganizeByTitleResult } from "./task-organize-contract.js";
6
6
  export type { OrganizeByTitleResult } from "./task-organize-contract.js";
7
- export declare function titleJudgeForStorage(storage: WandStorage, options: AutoTaskTitleOptions): TitleJudge | undefined;
7
+ export declare function titleJudgeForStorage(storage: WandStorage, options: AutoTaskTitleOptions, onCallSessionCreated?: (id: string) => void): TitleJudge | undefined;
8
8
  export declare function systemTitleJudge(ai: QuickCommitAiOptions, cwd: string): TitleJudge;
9
9
  /**
10
10
  * Classify the selected tasks by title and move in-scope sessions to the task
@@ -1,10 +1,11 @@
1
1
  import { callConfiguredAiText } from "./ai-text-runner.js";
2
2
  import { getErrorMessage } from "./error-utils.js";
3
+ import { isSystemAiCall } from "./system-ai-call.js";
3
4
  import { resolveSystemAiContext } from "./session-ai-context.js";
4
5
  import { parentTaskIdFrom } from "./task-agent-parsing.js";
5
6
  import { planTitleOrganization, parseTitleJudgment, titleJudgmentPrompt, } from "./task-title-organize.js";
6
7
  import { isGlobalWorkspace, normalizeProjectCwd, projectCwdForSession } from "./workspace-binding.js";
7
- export function titleJudgeForStorage(storage, options) {
8
+ export function titleJudgeForStorage(storage, options, onCallSessionCreated) {
8
9
  const config = options.config;
9
10
  if (!config)
10
11
  return undefined;
@@ -16,16 +17,15 @@ export function titleJudgeForStorage(storage, options) {
16
17
  command: provider === "qoder" ? "qodercli" : (provider || "pi"),
17
18
  selectedModel: null,
18
19
  thinkingEffort: "off",
19
- }, config, storage.getSystemSiliconEmployee(), options.free);
20
- if (ai.employeeChannelOnly && !ai.cliCandidates?.length)
21
- return undefined;
22
- return systemTitleJudge(ai, options.cwd || config.defaultCwd || process.cwd());
20
+ }, config, storage.getSystemSiliconEmployee(), options.free, { storage });
21
+ return systemTitleJudge({ ...ai, onCallSessionCreated }, options.cwd || config.defaultCwd || process.cwd());
23
22
  }
24
23
  export function systemTitleJudge(ai, cwd) {
25
24
  return async (input) => {
26
25
  const request = titleJudgmentPrompt(input);
27
26
  return callConfiguredAiText(request, cwd || process.cwd(), "", {
28
27
  ...ai,
28
+ purpose: "task-organize",
29
29
  thinkingEffort: "off",
30
30
  }, parseTitleJudgment);
31
31
  };
@@ -94,6 +94,8 @@ export async function organizeSelectedByTitle(storage, input, options = {}) {
94
94
  const sessions = new Map();
95
95
  for (const task of tasks) {
96
96
  for (const session of storage.listSessionsByWorkspaceTaskSlim(task.id)) {
97
+ if (isSystemAiCall(session))
98
+ continue;
97
99
  sessions.set(session.id, sessionRecord(session, teamIds.has(session.id)));
98
100
  }
99
101
  }
@@ -105,6 +107,8 @@ export async function organizeSelectedByTitle(storage, input, options = {}) {
105
107
  refused.push({ id, kind: "session", reason: "未找到该会话" });
106
108
  continue;
107
109
  }
110
+ if (isSystemAiCall(session))
111
+ continue;
108
112
  // Explicit session selection must not pull user-named tasks into auto mode.
109
113
  if ((input.mode ?? "auto") === "auto" && session.workspaceTaskId
110
114
  && storage.getWandTaskByWorkspaceTaskId(session.workspaceTaskId)?.titleSource !== "auto")
@@ -14,8 +14,11 @@ const PLACEHOLDER_TITLES = new Set([
14
14
  "pi",
15
15
  ]);
16
16
  export function titleIdentity(title) {
17
+ // Symbols and inner punctuation can carry identity (C++ / C#, 1.0 / 10).
18
+ // Only ignore spacing, case and terminal sentence marks; other differences need judgment.
17
19
  return title.normalize("NFKC")
18
- .replace(/[\p{P}\p{S}]+/gu, " ")
20
+ .trim()
21
+ .replace(/[。.!!??]+$/u, "")
19
22
  .replace(/\s+/g, "")
20
23
  .toLowerCase();
21
24
  }
@@ -24,9 +27,11 @@ export function isClassifiableTitle(title) {
24
27
  if (trimmed.length < 2 || trimmed === "未命名任务" || trimmed === "新任务")
25
28
  return false;
26
29
  const identity = titleIdentity(trimmed);
27
- if (!identity || PLACEHOLDER_TITLES.has(identity))
30
+ // Placeholder recognition retains old formatting tolerance without using this lossy key for grouping.
31
+ const placeholderIdentity = identity.replace(/[\p{P}\p{S}]+/gu, "");
32
+ if (!placeholderIdentity || PLACEHOLDER_TITLES.has(placeholderIdentity))
28
33
  return false;
29
- if (/^(?:claude|codex|opencode|grok|qoder|pi|终端)\d+$/i.test(identity))
34
+ if (/^(?:claude|codex|opencode|grok|qoder|pi|终端)\d+$/i.test(placeholderIdentity))
30
35
  return false;
31
36
  return true;
32
37
  }
@@ -263,7 +268,8 @@ export async function planTitleOrganization(tasks, sessions, judge) {
263
268
  const task = byId.get(id);
264
269
  if (!task)
265
270
  return [];
266
- const hasPeer = targets.some((peer) => peer.id !== task.id && peer.workspaceId === task.workspaceId && peer.status === "doing");
271
+ const hasPeer = targets.some((peer) => peer.id !== task.id && peer.workspaceId === task.workspaceId
272
+ && sameDirectory(peer.directoryKey, task.directoryKey) && peer.status === "doing");
267
273
  return hasPeer ? [task] : [];
268
274
  });
269
275
  const judgeNeeded = judgeSessions.length > 0 || judgeTasks.length > 0;
@@ -274,7 +280,8 @@ export async function planTitleOrganization(tasks, sessions, judge) {
274
280
  let judgment;
275
281
  try {
276
282
  judgment = await judgeTitleOrganization({
277
- tasks: targets.filter((task) => judgeTasks.some((item) => item.workspaceId === task.workspaceId) || judgeSessions.some((session) => sameDirectory(session.directoryKey, task.directoryKey)))
283
+ tasks: targets.filter((task) => judgeTasks.some((item) => item.workspaceId === task.workspaceId && sameDirectory(item.directoryKey, task.directoryKey))
284
+ || judgeSessions.some((session) => sameDirectory(session.directoryKey, task.directoryKey)))
278
285
  .map((task) => ({ id: task.id, titles: classifiableTitles(task.titles) })),
279
286
  sessions: judgeSessions.map((session) => ({ id: session.id, title: session.title.trim() })),
280
287
  }, judge);
@@ -307,7 +314,7 @@ export async function planTitleOrganization(tasks, sessions, judge) {
307
314
  if (!parentWorkspaceTaskId || parentWorkspaceTaskId === task.id)
308
315
  continue;
309
316
  const parent = byId.get(parentWorkspaceTaskId);
310
- if (!parent || parent.workspaceId !== task.workspaceId || parent.status !== "doing") {
317
+ if (!parent || parent.workspaceId !== task.workspaceId || !sameDirectory(parent.directoryKey, task.directoryKey) || parent.status !== "doing") {
311
318
  if (parentWorkspaceTaskId)
312
319
  refused.push({ id: task.id, kind: "task", reason: "不能归入该任务" });
313
320
  continue;
@@ -68,7 +68,7 @@ export class TaskTitleService {
68
68
  const provider = config?.defaultProvider ?? "claude";
69
69
  const ai = config ? resolveSystemAiContext({ provider, structuredState: undefined, runner: undefined,
70
70
  command: provider === "qoder" ? "qodercli" : provider, selectedModel: null,
71
- thinkingEffort: config.defaultThinkingEffort }, config, this.storage.getSystemSiliconEmployee(), free) : {};
71
+ thinkingEffort: config.defaultThinkingEffort }, config, this.storage.getSystemSiliconEmployee(), free, { storage: this.storage }) : {};
72
72
  const cwd = (current.workspaceId ? this.storage.getWorkspace(current.workspaceId)?.cwd : undefined) || this.options.cwd
73
73
  || config?.defaultCwd || process.cwd();
74
74
  const title = await generateTitle(input.source, cwd, config?.language ?? "", { ...ai, signal: this.controller.signal });
@@ -78,7 +78,7 @@ export async function generateWandTaskTitle(description, cwd, language = "", ai
78
78
  ].join("\n"),
79
79
  prompt: `任务描述:\n${input}`,
80
80
  };
81
- return callConfiguredAiText(request, cwd || process.cwd(), language, ai, (raw) => {
81
+ return callConfiguredAiText(request, cwd || process.cwd(), language, { ...ai, purpose: "task-title" }, (raw) => {
82
82
  const stripped = stripGeneratedTitle(raw);
83
83
  // 校验在候选链内:格式不可用也要继续备用候选,不能先把非空文本当成功。
84
84
  // 先判可信度再裁剪:截断过的长句看起来就像标题,会绕过长度检查。
@@ -160,7 +160,7 @@ export class UserMemoryService {
160
160
  }, config.defaultCwd, config.language ?? "", resolveSystemAiContext({
161
161
  provider: config.defaultProvider ?? "claude", command: config.defaultProvider ?? "claude",
162
162
  selectedModel: null, thinkingEffort: config.defaultThinkingEffort,
163
- }, config, storage.getSystemSiliconEmployee(), this.deps.free), (raw) => parseUserMemoryPreferences(raw, events));
163
+ }, config, storage.getSystemSiliconEmployee(), this.deps.free, { storage, purpose: "user-memory" }), (raw) => parseUserMemoryPreferences(raw, events));
164
164
  // Test seam and production share the exact same validator.
165
165
  const validated = parseUserMemoryPreferences(JSON.stringify({ preferences }), events);
166
166
  const referenced = new Set(validated.flatMap((entry) => entry.evidenceIds));
@@ -1,4 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
+ import { isSystemAiCall } from "./system-ai-call.js";
2
3
  import { isSessionProvider } from "./session-provider.js";
3
4
  import { provisionalTaskTitleFromDescription } from "./task-title.js";
4
5
  /** 未命名任务的占位名;服务端新建时写入,自动命名成功后被真实标题覆盖。 */
@@ -43,7 +44,7 @@ export function isAutoNameableBoardTask(card) {
43
44
  function taskNamingSessions(storage, card) {
44
45
  const sessions = new Map();
45
46
  const add = (session) => {
46
- if (session && !sessions.has(session.id))
47
+ if (session && !isSystemAiCall(session) && !sessions.has(session.id))
47
48
  sessions.set(session.id, session);
48
49
  };
49
50
  const needsMessageFallback = [];