@co0ontty/wand 4.83.1 → 4.84.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.
- package/README.md +4 -0
- package/dist/agent-dispatch.js +5 -1
- package/dist/ai-team-availability.d.ts +1 -1
- package/dist/ai-team-prompts.js +13 -4
- package/dist/ai-team-runner.d.ts +80 -5
- package/dist/ai-team-runner.js +332 -55
- package/dist/ai-team-types.d.ts +92 -5
- package/dist/ai-team-types.js +9 -2
- package/dist/build-info.json +3 -3
- package/dist/cli-api.d.ts +1 -0
- package/dist/cli-api.js +3 -0
- package/dist/cli.js +18 -1
- package/dist/commit-task-archive.d.ts +3 -1
- package/dist/commit-task-archive.js +21 -10
- package/dist/config.d.ts +1 -1
- package/dist/config.js +73 -0
- package/dist/conversation-mentions.d.ts +16 -0
- package/dist/conversation-mentions.js +32 -0
- package/dist/conversation-service.d.ts +90 -0
- package/dist/conversation-service.js +759 -0
- package/dist/conversation-session-preview.d.ts +9 -0
- package/dist/conversation-session-preview.js +47 -0
- package/dist/conversation-task-preview.d.ts +5 -0
- package/dist/conversation-task-preview.js +8 -0
- package/dist/conversation-types.d.ts +80 -0
- package/dist/conversation-types.js +7 -0
- package/dist/core-extension-host.d.ts +27 -0
- package/dist/core-extension-host.js +138 -0
- package/dist/core-runner.d.ts +86 -0
- package/dist/core-runner.js +757 -0
- package/dist/core-status-cli.d.ts +9 -0
- package/dist/core-status-cli.js +280 -0
- package/dist/core-turn-tracker.d.ts +12 -0
- package/dist/core-turn-tracker.js +36 -0
- package/dist/decision-runner.d.ts +6 -0
- package/dist/decision-runner.js +3 -1
- package/dist/decision-tool.d.ts +2 -0
- package/dist/decision-tool.js +49 -22
- package/dist/env-utils.d.ts +7 -3
- package/dist/env-utils.js +30 -5
- package/dist/git-quick-commit.d.ts +5 -0
- package/dist/git-quick-commit.js +9 -3
- package/dist/git-utils.js +2 -1
- package/dist/harness-engine.d.ts +110 -0
- package/dist/harness-engine.js +331 -0
- package/dist/mission-diff.js +2 -1
- package/dist/model-auto-assign.d.ts +50 -0
- package/dist/model-auto-assign.js +163 -0
- package/dist/model-group-runner.d.ts +4 -0
- package/dist/model-group-runner.js +72 -0
- package/dist/model-groups.d.ts +38 -0
- package/dist/model-groups.js +138 -0
- package/dist/models.d.ts +12 -0
- package/dist/models.js +102 -8
- package/dist/npm-update-utils.js +2 -1
- package/dist/openrouter-free-cli.d.ts +25 -0
- package/dist/openrouter-free-cli.js +109 -0
- package/dist/openrouter-free-models.d.ts +85 -0
- package/dist/openrouter-free-models.js +458 -0
- package/dist/openrouter-free-selection.d.ts +5 -0
- package/dist/openrouter-free-selection.js +7 -0
- package/dist/path-repair.d.ts +10 -0
- package/dist/path-repair.js +50 -5
- package/dist/pi-auto-resources.d.ts +12 -0
- package/dist/pi-auto-resources.js +151 -0
- package/dist/pi-execution-types.d.ts +48 -0
- package/dist/pi-execution-types.js +1 -0
- package/dist/pi-execution.d.ts +9 -0
- package/dist/pi-execution.js +187 -0
- package/dist/pi-extension-resources.d.ts +13 -0
- package/dist/pi-extension-resources.js +71 -0
- package/dist/pi-openrouter-free-bridge.d.ts +2 -0
- package/dist/pi-openrouter-free-bridge.js +80 -0
- package/dist/pi-resource-bridge.d.ts +2 -0
- package/dist/pi-resource-bridge.js +73 -0
- package/dist/pi-resource-catalog.d.ts +18 -0
- package/dist/pi-resource-catalog.js +82 -0
- package/dist/pi-resource-recommendation.d.ts +29 -0
- package/dist/pi-resource-recommendation.js +169 -0
- package/dist/pi-resource-run.d.ts +8 -0
- package/dist/pi-resource-run.js +28 -0
- package/dist/pi-session-settings.d.ts +157 -0
- package/dist/pi-session-settings.js +166 -0
- package/dist/platform-action-catalog.d.ts +24 -0
- package/dist/platform-action-catalog.js +141 -0
- package/dist/platform-action-service.d.ts +37 -0
- package/dist/platform-action-service.js +138 -0
- package/dist/platform-action-types.d.ts +125 -0
- package/dist/platform-action-types.js +7 -0
- package/dist/process-manager.d.ts +21 -1
- package/dist/process-manager.js +168 -15
- package/dist/provider-catalog.js +1 -1
- package/dist/provider-cli-updater.js +29 -8
- package/dist/provider-history-scanner.d.ts +9 -0
- package/dist/provider-history-scanner.js +107 -1
- package/dist/retention.d.ts +19 -7
- package/dist/retention.js +171 -28
- package/dist/server-ai-team-routes.js +6 -2
- package/dist/server-conversation-routes.d.ts +5 -0
- package/dist/server-conversation-routes.js +91 -0
- package/dist/server-employee-routes.d.ts +2 -0
- package/dist/server-employee-routes.js +85 -11
- package/dist/server-pi-recommendation-routes.d.ts +13 -0
- package/dist/server-pi-recommendation-routes.js +67 -0
- package/dist/server-resume-routes.js +1 -1
- package/dist/server-session-routes.d.ts +4 -3
- package/dist/server-session-routes.js +182 -37
- package/dist/server-settings-routes.d.ts +5 -0
- package/dist/server-settings-routes.js +46 -0
- package/dist/server-task-routes.js +49 -4
- package/dist/server-team-dispatch-routes.d.ts +19 -0
- package/dist/server-team-dispatch-routes.js +71 -0
- package/dist/server-update-routes.d.ts +1 -1
- package/dist/server-update-routes.js +10 -5
- package/dist/server-workspace-routes.js +44 -24
- package/dist/server.d.ts +1 -0
- package/dist/server.js +148 -30
- package/dist/session-ai-context.d.ts +5 -1
- package/dist/session-ai-context.js +9 -4
- package/dist/session-registry.d.ts +4 -0
- package/dist/session-registry.js +47 -13
- package/dist/session-transport.js +8 -1
- package/dist/settings-web-access.d.ts +15 -0
- package/dist/settings-web-access.js +37 -0
- package/dist/silicon-employee-defaults.d.ts +29 -0
- package/dist/silicon-employee-defaults.js +41 -0
- package/dist/silicon-employee-dispatch.d.ts +5 -1
- package/dist/silicon-employee-dispatch.js +10 -4
- package/dist/silicon-employee-draft.d.ts +21 -4
- package/dist/silicon-employee-draft.js +103 -16
- package/dist/storage.d.ts +73 -2
- package/dist/storage.js +362 -26
- package/dist/structured-claude-protocol.js +1 -1
- package/dist/structured-client-protocol.js +11 -33
- package/dist/structured-content.d.ts +23 -0
- package/dist/structured-content.js +72 -0
- package/dist/structured-failure.d.ts +16 -0
- package/dist/structured-failure.js +36 -0
- package/dist/structured-pi-adapter.d.ts +8 -3
- package/dist/structured-pi-adapter.js +131 -30
- package/dist/structured-provider-common.d.ts +6 -0
- package/dist/structured-provider-common.js +23 -1
- package/dist/structured-runner.d.ts +45 -0
- package/dist/structured-session-manager.d.ts +78 -4
- package/dist/structured-session-manager.js +513 -129
- package/dist/structured-thinking.d.ts +15 -0
- package/dist/structured-thinking.js +46 -0
- package/dist/subagent-dispatch.d.ts +56 -0
- package/dist/subagent-dispatch.js +102 -0
- package/dist/system-employee.d.ts +4 -3
- package/dist/system-employee.js +7 -4
- package/dist/task-retention.d.ts +23 -0
- package/dist/task-retention.js +69 -0
- package/dist/task-types.d.ts +4 -0
- package/dist/task-types.js +3 -0
- package/dist/team-dispatch.d.ts +101 -0
- package/dist/team-dispatch.js +269 -0
- package/dist/tool-preview.js +7 -1
- package/dist/tui/commands.js +50 -1
- package/dist/tui/ipc-protocol.d.ts +1 -1
- package/dist/tui/ipc-server.d.ts +4 -0
- package/dist/tui/ipc-server.js +23 -0
- package/dist/turn-heartbeat.d.ts +35 -0
- package/dist/turn-heartbeat.js +64 -0
- package/dist/types.d.ts +147 -1
- package/dist/user-profile.d.ts +33 -0
- package/dist/user-profile.js +70 -0
- package/dist/web-ui/chat-message-meta.d.ts +11 -0
- package/dist/web-ui/chat-message-meta.js +47 -0
- package/dist/web-ui/content/ai-teams.js +13 -13
- package/dist/web-ui/content/scripts.js +795 -95
- package/dist/web-ui/content/styles.css +1 -1
- package/dist/web-ui/content/tailwind.css +1 -6808
- package/dist/web-ui/content/vendor/qrcode/qrcode.bundle.js +1 -1
- package/dist/web-ui/embedded-assets.d.ts +1 -1
- package/dist/web-ui/embedded-assets.js +5 -5
- package/dist/web-ui/index.d.ts +1 -1
- package/dist/web-ui/index.js +3 -3
- package/dist/web-ui/markdown.d.ts +2 -0
- package/dist/web-ui/markdown.js +111 -0
- package/dist/web-ui/page.d.ts +4 -0
- package/dist/web-ui/page.js +8 -0
- package/dist/web-ui/react/local-preview/controller.d.ts +30 -0
- package/dist/web-ui/react/local-preview/controller.js +159 -0
- package/dist/web-ui/running-activity.d.ts +63 -0
- package/dist/web-ui/running-activity.js +134 -0
- package/dist/web-ui/styles.js +2 -11
- package/package.json +7 -2
package/dist/ai-team-types.d.ts
CHANGED
|
@@ -18,6 +18,9 @@ export interface AiTeamMember {
|
|
|
18
18
|
id: string;
|
|
19
19
|
/** 通讯录身份链接;缺省是独立 CLI 成员,不按名字猜测。 */
|
|
20
20
|
employeeId?: string;
|
|
21
|
+
/** Group-only identity for unbound legacy preset members; never guessed from a name. */
|
|
22
|
+
legacyTemplateId?: string;
|
|
23
|
+
legacyMemberId?: string;
|
|
21
24
|
name: string;
|
|
22
25
|
/** 职责说明,原样写进提示词。 */
|
|
23
26
|
duty: string;
|
|
@@ -28,7 +31,7 @@ export interface AiTeamMember {
|
|
|
28
31
|
/** 职责标注;缺省视同 "any"。 */
|
|
29
32
|
role?: TeamMemberRole;
|
|
30
33
|
isLeader: boolean;
|
|
31
|
-
/**
|
|
34
|
+
/** 头像:空串按身份生成(渐变底 + 名字首字);"cat:<n>" 指定毛色;"data:image/…" 为用户上传的小图。 */
|
|
32
35
|
avatar?: string;
|
|
33
36
|
}
|
|
34
37
|
/**
|
|
@@ -45,7 +48,7 @@ export declare function memberAgents(member: {
|
|
|
45
48
|
*/
|
|
46
49
|
export declare function agentKey(agent: WandTaskAgent): string;
|
|
47
50
|
/** 启动失败分类(§3.4);ai-team-availability(T2)复用此联合类型。 */
|
|
48
|
-
export type CandidateFailureKind = "spawn-missing" | "host-disabled" | "model-unknown" | "startup-timeout" | "runtime-failure" | "format-error" | "user-stop";
|
|
51
|
+
export type CandidateFailureKind = "spawn-missing" | "input-rejected" | "host-disabled" | "model-unknown" | "startup-timeout" | "runtime-failure" | "format-error" | "user-stop";
|
|
49
52
|
/** 步骤实际派发用的候选与本步之前跳过的候选(§3.2,存 ai_team_steps.dispatch_info_json)。 */
|
|
50
53
|
export interface StepDispatchInfo {
|
|
51
54
|
usedCandidate: number;
|
|
@@ -77,14 +80,18 @@ export interface SiliconEmployee {
|
|
|
77
80
|
updatedAt: string;
|
|
78
81
|
}
|
|
79
82
|
/**
|
|
80
|
-
* AI
|
|
81
|
-
*
|
|
83
|
+
* AI 按自然语言期望起草(或结构化校验后)的员工配置:agent 是首选执行候选,
|
|
84
|
+
* candidates 是已校验的完整候选清单。其余手动字段(头像等)仍由表单和「高级配置」负责。
|
|
82
85
|
*/
|
|
83
86
|
export interface SiliconEmployeeDraft {
|
|
84
87
|
name: string;
|
|
85
88
|
duty: string;
|
|
86
89
|
prompt: string;
|
|
87
90
|
agent: WandTaskAgent;
|
|
91
|
+
/**
|
|
92
|
+
* IM-11 新增:已校验的执行候选,首项即 agent;旧客户端继续只读 agent 不受影响。
|
|
93
|
+
*/
|
|
94
|
+
candidates?: WandTaskAgent[];
|
|
88
95
|
}
|
|
89
96
|
export declare const AI_TEAM_AVATAR_MAX_CHARS = 60000;
|
|
90
97
|
export declare const SILICON_EMPLOYEE_AVATAR_MAX_CHARS = 60000;
|
|
@@ -114,6 +121,8 @@ export interface AiTeam {
|
|
|
114
121
|
/** 协作指令:分工、工作要求与注意事项,写进负责人和成员的提示词。 */
|
|
115
122
|
instructions: string;
|
|
116
123
|
members: AiTeamMember[];
|
|
124
|
+
/** Per-run @ coordinator may take a duty-specific work step in a separate stage session. */
|
|
125
|
+
allowLeaderWork?: boolean;
|
|
117
126
|
requirePlanApproval: boolean;
|
|
118
127
|
/** Leader 轮次 + 成员步骤合计的上限。 */
|
|
119
128
|
maxSteps: number;
|
|
@@ -127,7 +136,11 @@ export declare const AI_TEAM_TERMINAL_RUN_STATUSES: readonly AiTeamRunStatus[];
|
|
|
127
136
|
export interface AiTeamRun {
|
|
128
137
|
id: string;
|
|
129
138
|
teamId: string;
|
|
130
|
-
/**
|
|
139
|
+
/** 群实例运行全程冻结;预设旧 CLI 运行保留兼容刷新规则。 */
|
|
140
|
+
conversationId?: string;
|
|
141
|
+
memberVersion?: number;
|
|
142
|
+
roundNumber?: number;
|
|
143
|
+
/** 执行快照;展示层改名不写回这里。 */
|
|
131
144
|
team: AiTeam;
|
|
132
145
|
taskId: string;
|
|
133
146
|
objective: string;
|
|
@@ -210,11 +223,42 @@ export interface AiTeamStep {
|
|
|
210
223
|
export interface AiTeamRunSummary extends AiTeamRun {
|
|
211
224
|
taskTitle: string;
|
|
212
225
|
taskIdentifier: string;
|
|
226
|
+
/**
|
|
227
|
+
* 活动时间投影(派生、不入库)。本机服务端总是带;旧服务端或测试夹具可能缺省,
|
|
228
|
+
* 读取端按可选处理,不要在缺省时把 `updatedAt` 当活动证据(长静默期它不动)。
|
|
229
|
+
*/
|
|
230
|
+
activity?: AiTeamRunActivity;
|
|
213
231
|
}
|
|
214
232
|
/** detail() 里群聊回合的截尾条数(§4.4):面板只给最近这一段,完整会话走「打开群聊」。 */
|
|
215
233
|
export declare const AI_TEAM_DETAIL_CHAT_TURNS = 200;
|
|
216
234
|
/** live 卡片文本的尾部保留上限(§4.9),超出部分回报 omittedChars 给前端显示「已省略前面 N 字」。 */
|
|
217
235
|
export declare const AI_TEAM_LIVE_TEXT_MAX_CHARS = 2000;
|
|
236
|
+
/**
|
|
237
|
+
* 静默心跳阈值:run 处于 running 且这段时间内没有任何可观测活动,就重推一次 live 快照
|
|
238
|
+
* 和一次 `ai-team-run` 通知(与会话侧的静默口径同一数值)。阈值只决定**重推频率**,
|
|
239
|
+
* 不引入新事件类型,也不是轮询:计时器每个 run 至多一个,run 离开 running 即 cancel。
|
|
240
|
+
*/
|
|
241
|
+
export declare const AI_TEAM_SILENCE_HEARTBEAT_MS = 30000;
|
|
242
|
+
/**
|
|
243
|
+
* run 级活动时间的投影,读取时派生、**不入库**(`ai_team_runs` 没有对应列,也不加):
|
|
244
|
+
* 长任务静默期(模型在想、在跑一条长命令)界面上要能区分「还在跑」和「卡死了」。
|
|
245
|
+
*/
|
|
246
|
+
export interface AiTeamRunActivity {
|
|
247
|
+
/**
|
|
248
|
+
* 本轮开始 = `run.createdAt`。终态 run 也照原值返回,客户端可以继续显示「共运行 N 分钟」;
|
|
249
|
+
* 它不是心跳时间,不会随推送前进。
|
|
250
|
+
*/
|
|
251
|
+
startedAt: string;
|
|
252
|
+
/**
|
|
253
|
+
* 最近一次**可观测活动**的时刻:live 文本/状态/输出增长、步骤开始或结束、run 状态写入。
|
|
254
|
+
* 心跳不推进它 —— 只有真实活动才变,所以「startedAt 在走、lastActivityAt 不动」正是
|
|
255
|
+
* 「仍在跑、只是这一轮安静」的判据。终态 run 取最后一次活动的时刻(等于结束时的
|
|
256
|
+
* `run.updatedAt`),之后不再变化;缺证据时退回 `startedAt`,不会返回未来时间。
|
|
257
|
+
*/
|
|
258
|
+
lastActivityAt: string;
|
|
259
|
+
/** 服务端生成这份投影的时刻;客户端用它算「N 秒前」,不受两端时钟偏差影响。 */
|
|
260
|
+
observedAt: string;
|
|
261
|
+
}
|
|
218
262
|
/**
|
|
219
263
|
* 一个正在干活的运行中步骤的实时快照(§4.9):Web 走 ai-team-step-live 推送,
|
|
220
264
|
* 移动端轮询 GET /api/ai-team-runs/:id/live,两边共用这个形状。
|
|
@@ -238,18 +282,61 @@ export interface AiTeamLiveStep {
|
|
|
238
282
|
/** renderLiveStepText 的渲染结果,尾部保留、至多 AI_TEAM_LIVE_TEXT_MAX_CHARS 字。 */
|
|
239
283
|
text: string;
|
|
240
284
|
omittedChars: number;
|
|
285
|
+
/**
|
|
286
|
+
* 本步开始执行的时刻(= `AiTeamStep.startedAt`,步骤进入 running 时写入)。
|
|
287
|
+
* 客户端用它算「这步已运行 N 分钟」;理论上 running 步都有值,旧数据缺省时为 null,
|
|
288
|
+
* 客户端按「没有开始时间」处理,不要拿 updatedAt 代替。
|
|
289
|
+
*/
|
|
290
|
+
startedAt: string | null;
|
|
291
|
+
/**
|
|
292
|
+
* 这一步最近一次**可观测活动**的时刻:live 文本、思考/输出增量或活动状态发生变化才前进。
|
|
293
|
+
* 静默期(模型在想、在跑长命令、没有新输出)它保持不变,此时靠 `heartbeat` 快照证明还活着。
|
|
294
|
+
* 首次观察到该步时以 `startedAt` 为基线(不假造「刚刚有活动」),所以它不会早于 startedAt、
|
|
295
|
+
* 也不会晚于生成这次快照的时刻。步骤结束后该步不再出现在 live 列表里,本字段随之消失。
|
|
296
|
+
*/
|
|
297
|
+
lastActivityAt: string;
|
|
298
|
+
/** 本次快照的生成时刻(不是活动时刻):每次推送/轮询都会前进,别拿它判断「有没有新进展」。 */
|
|
241
299
|
updatedAt: string;
|
|
242
300
|
}
|
|
243
301
|
/** notifyLive 回调与 ai-team-step-live 通知的 payload(§4.9)。 */
|
|
244
302
|
export interface AiTeamLiveUpdate {
|
|
245
303
|
runId: string;
|
|
246
304
|
taskId: string;
|
|
305
|
+
/**
|
|
306
|
+
* 推送时的 run 状态;客户端据此区分「在跑」「等批准」「已结束」,不必再拉 detail。
|
|
307
|
+
* 本机服务端总是带;未升级的转发层或旧服务端可能缺省,读取端按可选处理。
|
|
308
|
+
*/
|
|
309
|
+
status?: AiTeamRunStatus;
|
|
310
|
+
/** run 级活动时间;与 steps 同级,避免为「仍在运行」指示再多打一次 detail。同上,可选项。 */
|
|
311
|
+
activity?: AiTeamRunActivity;
|
|
312
|
+
/**
|
|
313
|
+
* true = 静默超阈值的重推:内容与上一次可能逐字相同,但 `activity.observedAt` 是新的,
|
|
314
|
+
* 语义是「服务端此刻仍在跟踪这个 run」。真实内容变化产生的推送这里是 false,
|
|
315
|
+
* 缺省按 false 处理。run 进入终态后不会再有 heartbeat 推送。
|
|
316
|
+
*/
|
|
317
|
+
heartbeat?: boolean;
|
|
318
|
+
steps: AiTeamLiveStep[];
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* `GET /api/ai-team-runs/:id/live` 的回包(§4.9.1):轮询侧的 live 快照 + run 级活动时间。
|
|
322
|
+
* 轮询本身就是「服务端此刻的真实观察」,所以没有 heartbeat 标记,只有推送通道才有。
|
|
323
|
+
*/
|
|
324
|
+
export interface AiTeamLiveSnapshot {
|
|
325
|
+
runId: string;
|
|
326
|
+
/** 投影时的 run 状态;轮询端不必为「还在跑吗」再拉一次 detail。 */
|
|
327
|
+
status: AiTeamRunStatus;
|
|
328
|
+
activity: AiTeamRunActivity;
|
|
247
329
|
steps: AiTeamLiveStep[];
|
|
248
330
|
}
|
|
249
331
|
export interface AiTeamRunDetail {
|
|
250
332
|
run: AiTeamRun;
|
|
251
333
|
/** One run's result/files/current handoffs; absent on older servers, no side effects. */
|
|
252
334
|
delivery?: AiTeamDeliverySummary;
|
|
335
|
+
/**
|
|
336
|
+
* 活动时间投影(派生、不入库):长任务静默期靠它显示「已运行 N 分钟、M 秒前有活动」。
|
|
337
|
+
* 本机的 detail() 总是带;旧服务端缺省时读取端要退回 `run.updatedAt`,不能当作活动证据。
|
|
338
|
+
*/
|
|
339
|
+
activity?: AiTeamRunActivity;
|
|
253
340
|
/** 仅供展示的群名,读取当前任务标题,不以团队名代替。 */
|
|
254
341
|
chatTitle?: string;
|
|
255
342
|
/** 任务标题的版本;任务改名不改变 run.updatedAt。 */
|
package/dist/ai-team-types.js
CHANGED
|
@@ -23,7 +23,8 @@ export function memberAgents(member) {
|
|
|
23
23
|
* 按字面量比较:model:"default" 不与具体默认模型名做等价归一。
|
|
24
24
|
*/
|
|
25
25
|
export function agentKey(agent) {
|
|
26
|
-
|
|
26
|
+
const engine = agent.engine === "sdk" ? "|sdk" : "";
|
|
27
|
+
return `${agent.provider}|${agent.model}|${agent.thinkingEffort}|${agent.mode}|${agent.kind}${engine}`;
|
|
27
28
|
}
|
|
28
29
|
export const AI_TEAM_AVATAR_MAX_CHARS = 60_000;
|
|
29
30
|
export const SILICON_EMPLOYEE_AVATAR_MAX_CHARS = 60_000;
|
|
@@ -94,9 +95,15 @@ export const AI_TEAM_ACTIVE_RUN_STATUSES = ["running", "awaiting_approval", "wai
|
|
|
94
95
|
export const AI_TEAM_TERMINAL_RUN_STATUSES = ["done", "failed", "stopped"];
|
|
95
96
|
/** 群聊名从任务标题派生;不修改团队定义,也不回写历史消息。 */
|
|
96
97
|
export function aiTeamChatTitle(taskTitle) {
|
|
97
|
-
return
|
|
98
|
+
return taskTitle?.trim() || "群聊";
|
|
98
99
|
}
|
|
99
100
|
/** detail() 里群聊回合的截尾条数(§4.4):面板只给最近这一段,完整会话走「打开群聊」。 */
|
|
100
101
|
export const AI_TEAM_DETAIL_CHAT_TURNS = 200;
|
|
101
102
|
/** live 卡片文本的尾部保留上限(§4.9),超出部分回报 omittedChars 给前端显示「已省略前面 N 字」。 */
|
|
102
103
|
export const AI_TEAM_LIVE_TEXT_MAX_CHARS = 2000;
|
|
104
|
+
/**
|
|
105
|
+
* 静默心跳阈值:run 处于 running 且这段时间内没有任何可观测活动,就重推一次 live 快照
|
|
106
|
+
* 和一次 `ai-team-run` 通知(与会话侧的静默口径同一数值)。阈值只决定**重推频率**,
|
|
107
|
+
* 不引入新事件类型,也不是轮询:计时器每个 run 至多一个,run 离开 running 即 cancel。
|
|
108
|
+
*/
|
|
109
|
+
export const AI_TEAM_SILENCE_HEARTBEAT_MS = 30_000;
|
package/dist/build-info.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"commit": "
|
|
3
|
-
"builtAt": "2026-10-
|
|
4
|
-
"version": "4.
|
|
2
|
+
"commit": "a87dbb00803c14eb160778bbc2097270457b3b7a",
|
|
3
|
+
"builtAt": "2026-10-07T23:20:56.996Z",
|
|
4
|
+
"version": "4.84.0",
|
|
5
5
|
"channel": "stable"
|
|
6
6
|
}
|
package/dist/cli-api.d.ts
CHANGED
package/dist/cli-api.js
CHANGED
|
@@ -12,6 +12,9 @@ export class WandCliApi {
|
|
|
12
12
|
async post(pathname, body) {
|
|
13
13
|
return this.request("POST", pathname, body);
|
|
14
14
|
}
|
|
15
|
+
async del(pathname) {
|
|
16
|
+
return this.request("DELETE", pathname);
|
|
17
|
+
}
|
|
15
18
|
async login() {
|
|
16
19
|
const result = await this.rawRequest("POST", "/api/login", { password: this.config.password });
|
|
17
20
|
if (result.status < 200 || result.status >= 300)
|
package/dist/cli.js
CHANGED
|
@@ -175,10 +175,23 @@ async function main() {
|
|
|
175
175
|
case "mission:create":
|
|
176
176
|
case "mission:diff":
|
|
177
177
|
case "mission:review":
|
|
178
|
-
case "mission:review:send":
|
|
178
|
+
case "mission:review:send":
|
|
179
|
+
case "tasks:purge-archived": {
|
|
179
180
|
await runAgentCliCommand(command, args, configPath);
|
|
180
181
|
break;
|
|
181
182
|
}
|
|
183
|
+
case "core:status": {
|
|
184
|
+
const { checkCoreStatus } = await import("./core-status-cli.js");
|
|
185
|
+
await checkCoreStatus(configPath);
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
case "core:wait": {
|
|
189
|
+
const { waitForCoreTurnsComplete } = await import("./core-status-cli.js");
|
|
190
|
+
const timeoutArg = readFlagValue(args.slice(1), "--timeout");
|
|
191
|
+
const timeout = timeoutArg === undefined ? 0 : Number(timeoutArg);
|
|
192
|
+
await waitForCoreTurnsComplete(configPath, timeout);
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
182
195
|
case "decision:configure": {
|
|
183
196
|
const { isAbsolute, join } = await import("node:path");
|
|
184
197
|
const disabling = args.includes("--disable");
|
|
@@ -267,6 +280,7 @@ Agent runtime:
|
|
|
267
280
|
wand mission:diff <mission-id> <attempt-id>
|
|
268
281
|
wand mission:review <mission-id> <attempt-id> --file <path> --body <text> [--line N]
|
|
269
282
|
wand mission:review:send <mission-id> <attempt-id>
|
|
283
|
+
wand tasks:purge-archived Hard-delete every archived board task
|
|
270
284
|
wand decide --stdin | --status Use the session-bound local decision service
|
|
271
285
|
wand decision:configure --python <path> --model <dir> | --disable
|
|
272
286
|
wand decision:skills [--providers pi,claude,...] Install managed user-level skills
|
|
@@ -361,6 +375,7 @@ async function runAgentCliCommand(command, args, configPath) {
|
|
|
361
375
|
const attemptId = required(args[2], "wand mission:review:send <mission-id> <attempt-id>");
|
|
362
376
|
return output(await api.post(`/api/missions/${encodeURIComponent(missionId)}/attempts/${encodeURIComponent(attemptId)}/review/send`, {}));
|
|
363
377
|
}
|
|
378
|
+
case "tasks:purge-archived": return output(await api.del("/api/wand-tasks/archived"));
|
|
364
379
|
}
|
|
365
380
|
}
|
|
366
381
|
async function ensureRequiredFiles(configPath, opts = {}) {
|
|
@@ -461,6 +476,8 @@ async function registerInstance(handle, configPath, startedAtMs) {
|
|
|
461
476
|
const { buildSnapshotData } = await import("./tui/snapshot.js");
|
|
462
477
|
const ipc = startIpcServer({
|
|
463
478
|
socketPath: sockPath,
|
|
479
|
+
coreStatusProvider: () => handle.structuredSessions.getCoreTurnStatus(),
|
|
480
|
+
beginCoreDrain: () => handle.structuredSessions.beginCoreRestartDrain(),
|
|
464
481
|
snapshotProvider: () => buildSnapshotData({
|
|
465
482
|
version: handle.version,
|
|
466
483
|
url: pidInfo.url,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import type { WandStorage } from "./storage.js";
|
|
2
2
|
import type { SessionSnapshot } from "./types.js";
|
|
3
|
-
export declare function archiveCommitTasks(storage: WandStorage, session: SessionSnapshot, entryIds: readonly string[]
|
|
3
|
+
export declare function archiveCommitTasks(storage: WandStorage, session: SessionSnapshot, entryIds: readonly string[], options?: {
|
|
4
|
+
workspaceTaskId?: string | null;
|
|
5
|
+
}): string[];
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { projectCwdForSession, normalizeProjectCwd } from "./workspace-binding.js";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* Close cards linked to this commit. The session's own task (or the task the client
|
|
4
|
+
* opened this chat from) leaves the active list even while it is still in progress —
|
|
5
|
+
* that is the row the user just asked to archive. Other cards only close when they are
|
|
6
|
+
* already done: a shared iteration can contain unrelated in-progress work, and selecting
|
|
7
|
+
* its prompts must not sweep those cards off the list.
|
|
6
8
|
*/
|
|
7
9
|
function cardIdForSession(storage, sessionId) {
|
|
8
10
|
const binding = storage.getSessionWorkspace(sessionId);
|
|
@@ -11,21 +13,30 @@ function cardIdForSession(storage, sessionId) {
|
|
|
11
13
|
: null;
|
|
12
14
|
return card?.id ?? storage.getWandTaskIdForSession(sessionId);
|
|
13
15
|
}
|
|
14
|
-
export function archiveCommitTasks(storage, session, entryIds) {
|
|
16
|
+
export function archiveCommitTasks(storage, session, entryIds, options = {}) {
|
|
15
17
|
const directory = projectCwdForSession(session);
|
|
16
|
-
const related = new Set();
|
|
17
18
|
const currentTaskId = cardIdForSession(storage, session.id);
|
|
19
|
+
const openedTaskId = options.workspaceTaskId
|
|
20
|
+
? storage.getWandTaskByWorkspaceTaskId(options.workspaceTaskId)?.id ?? null
|
|
21
|
+
: null;
|
|
22
|
+
// These two are the task the user is looking at. Archive them even if still todo/doing.
|
|
23
|
+
const inProgressAllowed = new Set();
|
|
18
24
|
if (currentTaskId)
|
|
19
|
-
|
|
25
|
+
inProgressAllowed.add(currentTaskId);
|
|
26
|
+
if (openedTaskId)
|
|
27
|
+
inProgressAllowed.add(openedTaskId);
|
|
28
|
+
const related = new Set(inProgressAllowed);
|
|
20
29
|
for (const entry of storage.listIterationPromptsByIds(entryIds)) {
|
|
21
30
|
const taskId = entry.taskId || (entry.sessionId ? cardIdForSession(storage, entry.sessionId) : null);
|
|
22
31
|
if (taskId)
|
|
23
32
|
related.add(taskId);
|
|
24
33
|
}
|
|
25
|
-
const
|
|
34
|
+
const eligible = [];
|
|
26
35
|
for (const id of related) {
|
|
27
36
|
const task = storage.getWandTask(id);
|
|
28
|
-
if (task
|
|
37
|
+
if (!task || task.status === "archived")
|
|
38
|
+
continue;
|
|
39
|
+
if (task.status !== "done" && !inProgressAllowed.has(id))
|
|
29
40
|
continue;
|
|
30
41
|
// Project directory constrains selected history; standalone cards qualify via this session.
|
|
31
42
|
const workspace = task.workspaceId ? storage.getWorkspace(task.workspaceId) : null;
|
|
@@ -33,7 +44,7 @@ export function archiveCommitTasks(storage, session, entryIds) {
|
|
|
33
44
|
&& !!directory && normalizeProjectCwd(workspace.cwd) === directory;
|
|
34
45
|
if (!sameDirectory && !(id === currentTaskId && (!workspace || workspace.kind === "global")))
|
|
35
46
|
continue;
|
|
36
|
-
|
|
47
|
+
eligible.push(id);
|
|
37
48
|
}
|
|
38
|
-
return storage.
|
|
49
|
+
return storage.archiveWandTasks(eligible);
|
|
39
50
|
}
|
package/dist/config.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ import type { WandStorage } from "./storage.js";
|
|
|
7
7
|
* 升级路径:老 JSON 里仍存有这些字段时,首次启动会被搬到 DB(见 migrateLegacyPreferencesToDb),
|
|
8
8
|
* 然后下一次 saveConfig 写回 JSON 时它们会被剥离(见 stripPreferenceFields)。
|
|
9
9
|
*/
|
|
10
|
-
export declare const PREFERENCE_KEYS: readonly ["defaultProvider", "defaultSessionKind", "defaultTaskWorktree", "defaultMode", "defaultCwd", "defaultModel", "defaultCodexModel", "defaultOpenCodeModel", "defaultGrokModel", "defaultQoderModel", "defaultPiModel", "defaultGeminiModel", "commitCli", "commitModel", "systemAiCli", "systemAiModel", "defaultThinkingEffort", "language", "cardDefaults", "inheritEnv"];
|
|
10
|
+
export declare const PREFERENCE_KEYS: readonly ["defaultProvider", "defaultSessionKind", "defaultTaskWorktree", "defaultMode", "defaultCwd", "defaultModel", "defaultCodexModel", "defaultOpenCodeModel", "defaultGrokModel", "defaultQoderModel", "defaultPiModel", "defaultGeminiModel", "modelGroups", "commitCli", "commitModel", "systemAiCli", "systemAiModel", "defaultThinkingEffort", "language", "cardDefaults", "inheritEnv", "taskRetention", "userProfile"];
|
|
11
11
|
export type PreferenceKey = (typeof PREFERENCE_KEYS)[number];
|
|
12
12
|
/**
|
|
13
13
|
* 每个 provider 的默认模型偏好字段。新增 provider 时只改这一处:
|
package/dist/config.js
CHANGED
|
@@ -7,6 +7,9 @@ import process from "node:process";
|
|
|
7
7
|
import { isRunningAsRoot } from "./env-utils.js";
|
|
8
8
|
import { isSessionProvider, SESSION_PROVIDERS } from "./session-provider.js";
|
|
9
9
|
import { isThinkingEffort } from "./structured-provider-common.js";
|
|
10
|
+
import { normalizeModelGroups } from "./model-groups.js";
|
|
11
|
+
import { normalizeUserProfile, parseUserProfile } from "./user-profile.js";
|
|
12
|
+
import { defaultTaskRetention, normalizeTaskRetention, parseTaskRetention } from "./task-retention.js";
|
|
10
13
|
const DEFAULT_CONFIG_DIR = ".wand";
|
|
11
14
|
const DEFAULT_CONFIG_FILE = "config.json";
|
|
12
15
|
/**
|
|
@@ -29,6 +32,7 @@ export const PREFERENCE_KEYS = [
|
|
|
29
32
|
"defaultQoderModel",
|
|
30
33
|
"defaultPiModel",
|
|
31
34
|
"defaultGeminiModel",
|
|
35
|
+
"modelGroups",
|
|
32
36
|
"commitCli",
|
|
33
37
|
"commitModel",
|
|
34
38
|
"systemAiCli",
|
|
@@ -37,6 +41,8 @@ export const PREFERENCE_KEYS = [
|
|
|
37
41
|
"language",
|
|
38
42
|
"cardDefaults",
|
|
39
43
|
"inheritEnv",
|
|
44
|
+
"taskRetention",
|
|
45
|
+
"userProfile",
|
|
40
46
|
];
|
|
41
47
|
/**
|
|
42
48
|
* 每个 provider 的默认模型偏好字段。新增 provider 时只改这一处:
|
|
@@ -109,9 +115,11 @@ export const defaultConfig = () => ({
|
|
|
109
115
|
macos: defaultMacosDmgConfig(),
|
|
110
116
|
ios: defaultIosIpaConfig(),
|
|
111
117
|
render: defaultRenderConfig(),
|
|
118
|
+
harness: defaultHarnessConfig(),
|
|
112
119
|
structured: { processHost: "legacy" },
|
|
113
120
|
localDecision: { enabled: false, pythonPath: "", modelPath: "" },
|
|
114
121
|
cardDefaults: defaultCardExpandDefaults(),
|
|
122
|
+
taskRetention: defaultTaskRetention(),
|
|
115
123
|
defaultModel: "",
|
|
116
124
|
defaultCodexModel: "",
|
|
117
125
|
defaultOpenCodeModel: "",
|
|
@@ -119,6 +127,7 @@ export const defaultConfig = () => ({
|
|
|
119
127
|
defaultQoderModel: "",
|
|
120
128
|
defaultPiModel: "",
|
|
121
129
|
defaultGeminiModel: "",
|
|
130
|
+
modelGroups: [],
|
|
122
131
|
commitCli: "claude",
|
|
123
132
|
commitModel: "",
|
|
124
133
|
systemAiModel: "",
|
|
@@ -359,6 +368,9 @@ export function applyStoragePreferences(config, storage) {
|
|
|
359
368
|
if (typeof v === "string")
|
|
360
369
|
config[field] = v.trim();
|
|
361
370
|
}
|
|
371
|
+
if (storage.hasPreference(preferenceStorageKey("modelGroups"))) {
|
|
372
|
+
config.modelGroups = normalizeModelGroups(storage.getPreference(preferenceStorageKey("modelGroups"), []));
|
|
373
|
+
}
|
|
362
374
|
if (storage.hasPreference(preferenceStorageKey("commitCli"))) {
|
|
363
375
|
const v = storage.getPreference(preferenceStorageKey("commitCli"), defaults.commitCli ?? "claude");
|
|
364
376
|
if (v === "claude" || v === "codex" || v === "opencode")
|
|
@@ -397,6 +409,12 @@ export function applyStoragePreferences(config, storage) {
|
|
|
397
409
|
const v = storage.getPreference(preferenceStorageKey("inheritEnv"), defaults.inheritEnv ?? true);
|
|
398
410
|
config.inheritEnv = v === false ? false : true;
|
|
399
411
|
}
|
|
412
|
+
if (storage.hasPreference(preferenceStorageKey("taskRetention"))) {
|
|
413
|
+
config.taskRetention = normalizeTaskRetention(storage.getPreference(preferenceStorageKey("taskRetention"), defaults.taskRetention));
|
|
414
|
+
}
|
|
415
|
+
if (storage.hasPreference(preferenceStorageKey("userProfile"))) {
|
|
416
|
+
config.userProfile = normalizeUserProfile(storage.getPreference(preferenceStorageKey("userProfile"), null));
|
|
417
|
+
}
|
|
400
418
|
return config;
|
|
401
419
|
}
|
|
402
420
|
/** Write a single preference value to DB and (in-place) update the live config object. */
|
|
@@ -411,6 +429,12 @@ export function writePreferenceToStorage(config, storage, key, value) {
|
|
|
411
429
|
return;
|
|
412
430
|
}
|
|
413
431
|
switch (key) {
|
|
432
|
+
case "modelGroups": {
|
|
433
|
+
const groups = normalizeModelGroups(value);
|
|
434
|
+
storage.setPreference(dbKey, groups);
|
|
435
|
+
config.modelGroups = groups;
|
|
436
|
+
break;
|
|
437
|
+
}
|
|
414
438
|
case "defaultProvider": {
|
|
415
439
|
if (!isSessionProvider(value))
|
|
416
440
|
throw new Error(`无效 Provider: ${String(value)}`);
|
|
@@ -499,6 +523,18 @@ export function writePreferenceToStorage(config, storage, key, value) {
|
|
|
499
523
|
config.inheritEnv = v;
|
|
500
524
|
break;
|
|
501
525
|
}
|
|
526
|
+
case "taskRetention": {
|
|
527
|
+
const retention = parseTaskRetention(value);
|
|
528
|
+
storage.setPreference(dbKey, retention);
|
|
529
|
+
config.taskRetention = retention;
|
|
530
|
+
break;
|
|
531
|
+
}
|
|
532
|
+
case "userProfile": {
|
|
533
|
+
const profile = parseUserProfile(value);
|
|
534
|
+
storage.setPreference(dbKey, profile ?? null);
|
|
535
|
+
config.userProfile = profile;
|
|
536
|
+
break;
|
|
537
|
+
}
|
|
502
538
|
}
|
|
503
539
|
}
|
|
504
540
|
function defaultCardExpandDefaults() {
|
|
@@ -587,6 +623,39 @@ function normalizeRenderConfig(input) {
|
|
|
587
623
|
const binaryPath = typeof raw.binaryPath === "string" ? raw.binaryPath.trim() : "";
|
|
588
624
|
return { engine, ...(binaryPath ? { binaryPath } : {}) };
|
|
589
625
|
}
|
|
626
|
+
/**
|
|
627
|
+
* 默认 auto:Pi 侧有可用认证时,结构化 pi 会话走进程内 core,否则保持 CLI。
|
|
628
|
+
* 放在 config.json(部署项)而非 SQLite 偏好里:它决定「谁持有 agent loop」,属于部署决策。
|
|
629
|
+
*/
|
|
630
|
+
function defaultHarnessConfig() {
|
|
631
|
+
return { engine: "auto" };
|
|
632
|
+
}
|
|
633
|
+
function normalizeHarnessTokenLimit(value) {
|
|
634
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : undefined;
|
|
635
|
+
}
|
|
636
|
+
/** 旧 config.json 没有 harness 字段时回落到默认值,不能因为新字段让启动失败。 */
|
|
637
|
+
function normalizeHarnessConfig(input) {
|
|
638
|
+
if (!input || typeof input !== "object" || Array.isArray(input))
|
|
639
|
+
return undefined;
|
|
640
|
+
const raw = input;
|
|
641
|
+
const engine = raw.engine === "core" || raw.engine === "cli" ? raw.engine : "auto";
|
|
642
|
+
const compactionRaw = raw.compaction && typeof raw.compaction === "object" && !Array.isArray(raw.compaction)
|
|
643
|
+
? raw.compaction
|
|
644
|
+
: null;
|
|
645
|
+
const reserveTokens = normalizeHarnessTokenLimit(compactionRaw?.reserveTokens);
|
|
646
|
+
const keepRecentTokens = normalizeHarnessTokenLimit(compactionRaw?.keepRecentTokens);
|
|
647
|
+
const compaction = {
|
|
648
|
+
...(typeof compactionRaw?.enabled === "boolean" ? { enabled: compactionRaw.enabled } : {}),
|
|
649
|
+
...(reserveTokens !== undefined ? { reserveTokens } : {}),
|
|
650
|
+
...(keepRecentTokens !== undefined ? { keepRecentTokens } : {}),
|
|
651
|
+
};
|
|
652
|
+
const agentDir = typeof raw.agentDir === "string" ? raw.agentDir.trim() : "";
|
|
653
|
+
return {
|
|
654
|
+
engine,
|
|
655
|
+
...(agentDir ? { agentDir } : {}),
|
|
656
|
+
...(Object.keys(compaction).length > 0 ? { compaction } : {}),
|
|
657
|
+
};
|
|
658
|
+
}
|
|
590
659
|
function normalizeIosIpaConfig(input) {
|
|
591
660
|
if (!input || typeof input !== "object")
|
|
592
661
|
return undefined;
|
|
@@ -683,6 +752,7 @@ function mergeWithDefaults(input) {
|
|
|
683
752
|
}))
|
|
684
753
|
: defaults.commandPresets,
|
|
685
754
|
structuredChatPersona: normalizeStructuredChatPersona(input.structuredChatPersona),
|
|
755
|
+
userProfile: normalizeUserProfile(input.userProfile),
|
|
686
756
|
language: typeof input.language === "string" ? input.language.trim() : defaults.language,
|
|
687
757
|
appSecret: typeof input.appSecret === "string" && input.appSecret.length >= 32
|
|
688
758
|
? input.appSecret
|
|
@@ -691,6 +761,7 @@ function mergeWithDefaults(input) {
|
|
|
691
761
|
macos: normalizeMacosDmgConfig(input.macos) ?? defaults.macos,
|
|
692
762
|
ios: normalizeIosIpaConfig(input.ios) ?? defaults.ios,
|
|
693
763
|
render: normalizeRenderConfig(input.render) ?? defaults.render,
|
|
764
|
+
harness: normalizeHarnessConfig(input.harness) ?? defaults.harness,
|
|
694
765
|
structured: { processHost: input.structured?.processHost === "rust" ? "rust" : "legacy" },
|
|
695
766
|
localDecision: {
|
|
696
767
|
enabled: input.localDecision?.enabled === true,
|
|
@@ -698,10 +769,12 @@ function mergeWithDefaults(input) {
|
|
|
698
769
|
modelPath: typeof input.localDecision?.modelPath === "string" ? input.localDecision.modelPath.trim() : "",
|
|
699
770
|
},
|
|
700
771
|
cardDefaults: normalizeCardDefaults(input.cardDefaults),
|
|
772
|
+
taskRetention: normalizeTaskRetention(input.taskRetention),
|
|
701
773
|
defaultProvider: isSessionProvider(input.defaultProvider) ? input.defaultProvider : "claude",
|
|
702
774
|
defaultSessionKind: input.defaultSessionKind === "pty" ? "pty" : "structured",
|
|
703
775
|
defaultTaskWorktree: typeof input.defaultTaskWorktree === "boolean" ? input.defaultTaskWorktree : defaults.defaultTaskWorktree,
|
|
704
776
|
...providerModelOverrides(input, defaults),
|
|
777
|
+
modelGroups: normalizeModelGroups(input.modelGroups ?? []),
|
|
705
778
|
commitCli: input.commitCli === "codex" || input.commitCli === "opencode" ? input.commitCli : "claude",
|
|
706
779
|
commitModel: typeof input.commitModel === "string" ? input.commitModel.trim() : defaults.commitModel,
|
|
707
780
|
systemAiCli: isSessionProvider(input.systemAiCli) ? input.systemAiCli : undefined,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AiTeamMember } from "./ai-team-types.js";
|
|
2
|
+
type Member = Pick<AiTeamMember, "id" | "name">;
|
|
3
|
+
export type ConversationLeaderMention = {
|
|
4
|
+
memberId: string;
|
|
5
|
+
name: string;
|
|
6
|
+
body: string;
|
|
7
|
+
} | {
|
|
8
|
+
error: string;
|
|
9
|
+
};
|
|
10
|
+
/** Only a leading, explicit address selects a coordinator. Quoted/body mentions and emails are data. */
|
|
11
|
+
export declare function conversationLeaderMention(text: string, members: readonly Member[]): ConversationLeaderMention | null;
|
|
12
|
+
/** Shared upload-envelope handling: paths are never task titles or addressees. */
|
|
13
|
+
export declare function conversationMessageBody(text: string): string;
|
|
14
|
+
/** Duplicate display names remain addressable without guessing an employee identity. */
|
|
15
|
+
export declare function conversationMentionToken(member: Member, members: readonly Member[]): string;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** Only a leading, explicit address selects a coordinator. Quoted/body mentions and emails are data. */
|
|
2
|
+
export function conversationLeaderMention(text, members) {
|
|
3
|
+
// Upload envelopes precede the user's text on Web/Android; filenames are never mention targets.
|
|
4
|
+
const input = conversationMessageBody(text).trimStart();
|
|
5
|
+
if (!/^[@@]/u.test(input))
|
|
6
|
+
return null;
|
|
7
|
+
const addressed = input.slice(1);
|
|
8
|
+
const candidates = members.flatMap(member => [member.name.trim(), member.id]
|
|
9
|
+
.filter(Boolean).map(label => ({ member, label })))
|
|
10
|
+
.filter(({ label }) => addressed.startsWith(label) && /^(?:$|[\s,,::;;、!!??。])/u.test(addressed.slice(label.length)))
|
|
11
|
+
.sort((a, b) => b.label.length - a.label.length);
|
|
12
|
+
const match = candidates[0];
|
|
13
|
+
if (!match)
|
|
14
|
+
return { error: "没有找到被 @ 的群成员,请用「@姓名 消息」或从成员列表选择。" };
|
|
15
|
+
if (new Set(candidates.filter(c => c.label.length === match.label.length).map(c => c.member.id)).size !== 1) {
|
|
16
|
+
return { error: "被 @ 的名字对应多位群成员,请从成员列表选择,或用 @成员ID 指定。" };
|
|
17
|
+
}
|
|
18
|
+
const body = addressed.slice(match.label.length).replace(/^[\s,,::]+/u, "").trim();
|
|
19
|
+
if (!body)
|
|
20
|
+
return { error: "请在 @负责人 后写明本轮消息。" };
|
|
21
|
+
return { memberId: match.member.id, name: match.member.name, body };
|
|
22
|
+
}
|
|
23
|
+
/** Shared upload-envelope handling: paths are never task titles or addressees. */
|
|
24
|
+
export function conversationMessageBody(text) {
|
|
25
|
+
return text.replace(/^\s*\[附件已上传,请查看以下文件:\r?\n[\s\S]*?\r?\n\]\s*/u, "");
|
|
26
|
+
}
|
|
27
|
+
/** Duplicate display names remain addressable without guessing an employee identity. */
|
|
28
|
+
export function conversationMentionToken(member, members) {
|
|
29
|
+
const name = member.name.trim();
|
|
30
|
+
const unique = name && !/\r|\n/u.test(name) && members.filter(m => m.name.trim() === name || m.id === name).length === 1;
|
|
31
|
+
return `@${unique ? name : member.id}`;
|
|
32
|
+
}
|