@evandu/super-pi 0.1.153 → 0.1.154

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 (44) hide show
  1. package/agents/super-pi-task/core.ts +19 -0
  2. package/agents/super-pi-task/index.ts +17 -0
  3. package/dist/client/.super-pi-client-sources.json +6 -4
  4. package/dist/client/assets/{CodeMirrorEditor-DmRMgD_e.js → CodeMirrorEditor-B7Xs6jNi.js} +2 -2
  5. package/dist/client/assets/{dist-tpn03yX2.js → dist-BAKEacQv.js} +1 -1
  6. package/dist/client/assets/{dist-BTfxEdeF.js → dist-BLt409fN.js} +1 -1
  7. package/dist/client/assets/{dist-BzOPeLJM.js → dist-BdzEUJSL.js} +1 -1
  8. package/dist/client/assets/{dist-mY7dpaip.js → dist-Bl5sUV3i.js} +1 -1
  9. package/dist/client/assets/{dist-BQvKL2T4.js → dist-C1W6MyDX.js} +1 -1
  10. package/dist/client/assets/{dist-h0HvU8ex.js → dist-C8NpSGxO.js} +1 -1
  11. package/dist/client/assets/{dist-Cl_nos6D.js → dist-CAfwLExI.js} +1 -1
  12. package/dist/client/assets/{dist-BWxApOy6.js → dist-CTHQg4Rx.js} +1 -1
  13. package/dist/client/assets/{dist-QCoWWp1z.js → dist-CV2n3nfk.js} +1 -1
  14. package/dist/client/assets/{dist-1ehoscoU.js → dist-CVChVTw-.js} +1 -1
  15. package/dist/client/assets/{dist-BIMTBXpD.js → dist-CpbI--iL.js} +1 -1
  16. package/dist/client/assets/{dist-BaagZpIV.js → dist-CsTjYHwC.js} +1 -1
  17. package/dist/client/assets/{dist-DM15RCDF.js → dist-D-xVK9kg.js} +1 -1
  18. package/dist/client/assets/{dist-BAy39a02.js → dist-D0WeiUzu.js} +1 -1
  19. package/dist/client/assets/{dist-DE8_FyRQ.js → dist-D1WPlyWb.js} +1 -1
  20. package/dist/client/assets/{dist-CMNmeOX_.js → dist-DCKPOd40.js} +1 -1
  21. package/dist/client/assets/{dist-DpK_uu3H.js → dist-DQ4Nk969.js} +1 -1
  22. package/dist/client/assets/{dist-C5OCcu2D.js → dist-D_1md4dq.js} +1 -1
  23. package/dist/client/assets/{dist-YiNVn-GQ.js → dist-Dq0L1Jhh.js} +1 -1
  24. package/dist/client/assets/{dist-SHt_aVda.js → dist-DwQ2g3lZ.js} +1 -1
  25. package/dist/client/assets/{dist-BPU0RZX2.js → dist-TyClJ5X5.js} +1 -1
  26. package/dist/client/assets/{dist-BjZSq94T.js → dist-npMrgpJc.js} +1 -1
  27. package/dist/client/assets/{index-Ce896Hh9.js → index-DX_jpcni.js} +10 -10
  28. package/dist/client/assets/{index-DCLaWQin.css → index-srMSVLBy.css} +1 -1
  29. package/dist/client/index.html +2 -2
  30. package/dist/server/agents.d.ts +1 -1
  31. package/dist/server/agents.js +1 -1
  32. package/dist/server/application/project-service.d.ts +70 -0
  33. package/dist/server/application/project-service.js +573 -0
  34. package/dist/server/http-server.js +189 -2
  35. package/dist/server/index.js +3 -0
  36. package/dist/server/persistence/app-state-repository.d.ts +2 -2
  37. package/dist/server/runtime.js +5 -2
  38. package/dist/server/server-context.d.ts +1 -0
  39. package/dist/server/store.d.ts +5 -1
  40. package/dist/server/store.js +29 -3
  41. package/dist/server/task-tool.d.ts +2 -0
  42. package/dist/server/task-tool.js +2 -0
  43. package/dist/server/types.d.ts +103 -0
  44. package/package.json +3 -1
@@ -0,0 +1,573 @@
1
+ import { randomUUID } from "node:crypto";
2
+ const stamp = () => new Date().toISOString();
3
+ const text = (value, label) => {
4
+ if (typeof value !== "string" || !value.trim())
5
+ throw new Error(`${label} 不能为空`);
6
+ if (value.length > 32_000)
7
+ throw new Error(`${label} 过长`);
8
+ return value.trim();
9
+ };
10
+ const optional = (value, label) => value === undefined ? undefined : text(value, label);
11
+ const strings = (value, label) => {
12
+ if (value === undefined)
13
+ return [];
14
+ if (!Array.isArray(value) || value.length > 100)
15
+ throw new Error(`${label} 必须是最多 100 项的字符串数组`);
16
+ return [...new Set(value.map((item) => text(item, label)))];
17
+ };
18
+ const closed = (p) => ["completed", "cancelled", "failed"].includes(p.status);
19
+ const busy = (status) => ["running", "waiting", "awaiting_background"].includes(status ?? "");
20
+ const dependenciesComplete = (p, t) => t.dependencies.every((id) => p.tasks.find((candidate) => candidate.id === id)?.status === "completed");
21
+ const tokens = (usage) => usage ? usage.input + usage.output + usage.cacheRead + usage.cacheWrite : 0;
22
+ /** Persist explicit domain fields only; transport credentials never enter project records. */
23
+ export class ProjectService {
24
+ store;
25
+ orchestrator;
26
+ queues = new Map();
27
+ constructor(store, orchestrator) {
28
+ this.store = store;
29
+ this.orchestrator = orchestrator;
30
+ }
31
+ workspace(workspaceId) {
32
+ const value = text(workspaceId, "workspaceId");
33
+ if (!this.store.workspace(value))
34
+ throw new Error("工作区不存在");
35
+ return value;
36
+ }
37
+ event(p, kind, summary, taskId, actorConversationId) {
38
+ p.updatedAt = stamp();
39
+ (p.events ??= []).push({ id: randomUUID(), kind, summary, taskId, actorConversationId, createdAt: p.updatedAt });
40
+ }
41
+ async change(id, workspaceId, work) {
42
+ this.workspace(workspaceId);
43
+ const previous = this.queues.get(id) ?? Promise.resolve();
44
+ const pending = previous.catch(() => { }).then(async () => {
45
+ const p = this.get(id, workspaceId);
46
+ delete p.resources;
47
+ const result = await work(p);
48
+ await this.store.setProject(p);
49
+ return structuredClone(result);
50
+ });
51
+ this.queues.set(id, pending);
52
+ try {
53
+ return await pending;
54
+ }
55
+ finally {
56
+ if (this.queues.get(id) === pending)
57
+ this.queues.delete(id);
58
+ }
59
+ }
60
+ live(p) { if (closed(p))
61
+ throw new Error("项目已结束,不能继续修改或派发"); }
62
+ leader(p, actor) { if (actor && actor !== p.ownerConversationId)
63
+ throw new Error("仅项目负责人可以执行此操作"); }
64
+ manager(p, t, actor) {
65
+ const manager = t.parentTaskId ? this.task(p, t.parentTaskId).ownerConversationId : p.ownerConversationId;
66
+ if (actor && actor !== manager)
67
+ throw new Error("仅直属 Leader 可以管理此任务");
68
+ return manager;
69
+ }
70
+ requiredComplete(p, parentTaskId) {
71
+ const required = p.tasks.filter((t) => t.parentTaskId === parentTaskId && (t.status !== "cancelled" || !t.blockedReason));
72
+ return required.length > 0 && required.every((t) => t.status === "completed" && t.review?.verdict === "accepted" && t.review.reviewerConversationId === t.reviewerConversationId && Boolean(t.reviewerConversationId) && t.reviewerConversationId !== t.ownerConversationId);
73
+ }
74
+ task(p, taskId) {
75
+ const t = p.tasks.find((item) => item.id === taskId);
76
+ if (!t)
77
+ throw new Error("任务不存在");
78
+ return t;
79
+ }
80
+ member(p, actor) {
81
+ if (actor && actor !== p.ownerConversationId && !p.tasks.some((t) => t.ownerConversationId === actor || t.reviewerConversationId === actor))
82
+ throw new Error("调用会话不属于项目执行团队");
83
+ }
84
+ reconcile(p) {
85
+ if (closed(p))
86
+ return;
87
+ for (const t of p.tasks)
88
+ if (t.status === "pending" && dependenciesComplete(p, t)) {
89
+ t.status = "ready";
90
+ t.updatedAt = stamp();
91
+ this.event(p, "ready", `前置交付已验收:${t.title}`, t.id);
92
+ }
93
+ const open = p.escalations.filter((e) => e.status === "open");
94
+ p.status = open.some((e) => e.severity === "high") ? "awaiting_approval" : open.length || p.tasks.some((t) => t.status === "blocked") ? "blocked"
95
+ : p.tasks.some((t) => t.status === "awaiting_review") ? "awaiting_review" : p.tasks.length ? "active" : "planning";
96
+ }
97
+ resources(p) {
98
+ const state = this.store.snapshot();
99
+ const members = new Set([p.ownerConversationId, ...p.tasks.flatMap((t) => [t.ownerConversationId, t.reviewerConversationId])].filter(Boolean));
100
+ for (let changed = true; changed;) {
101
+ changed = false;
102
+ for (const c of state.conversations)
103
+ if (c.workspaceId === p.workspaceId && c.parentConversationId && members.has(c.parentConversationId) && !members.has(c.id)) {
104
+ members.add(c.id);
105
+ changed = true;
106
+ }
107
+ }
108
+ const coordinatorIds = new Set([p.ownerConversationId, ...p.tasks.filter((t) => t.role === "leader").map((t) => t.ownerConversationId)].filter(Boolean));
109
+ const sessions = state.conversations.filter((c) => c.workspaceId === p.workspaceId && members.has(c.id)).map((c) => ({
110
+ id: c.id, title: c.title, status: c.status, tokens: tokens(c.usage), contextPercent: c.contextUsage?.percent,
111
+ role: c.id === p.ownerConversationId ? "项目负责人" : coordinatorIds.has(c.id) ? "分支 Leader" : p.tasks.some((t) => t.reviewerConversationId === c.id) ? "独立复核" : "执行者",
112
+ }));
113
+ const runs = state.subagentRuns.filter((r) => members.has(r.conversationId));
114
+ return { tokens: sessions.reduce((sum, s) => sum + s.tokens, 0) + runs.reduce((sum, r) => sum + tokens(r.usage), 0),
115
+ usageKnown: sessions.length > 0 && sessions.every((s) => Boolean(this.store.conversation(s.id)?.usage)) && runs.every((r) => Boolean(r.usage)),
116
+ activeWorkers: sessions.filter((s) => !coordinatorIds.has(s.id) && busy(s.status)).length + runs.filter((r) => r.status === "running").length, sessions };
117
+ }
118
+ list(workspaceId) {
119
+ const scope = this.workspace(workspaceId);
120
+ return this.store.projects().filter((p) => p.workspaceId === scope).map((p) => ({ ...p, resources: this.resources(p) }));
121
+ }
122
+ get(id, workspaceId) {
123
+ const scope = this.workspace(workspaceId);
124
+ const p = this.store.project(id);
125
+ if (!p || p.workspaceId !== scope)
126
+ throw new Error("项目不存在或不属于当前工作区");
127
+ return { ...p, resources: this.resources(p) };
128
+ }
129
+ projectForSession(conversationId) {
130
+ let c = this.store.conversation(conversationId);
131
+ const seen = new Set();
132
+ while (c && !seen.has(c.id)) {
133
+ seen.add(c.id);
134
+ const p = this.store.projects().find((p) => p.workspaceId === c.workspaceId && (p.ownerConversationId === c.id || p.tasks.some((t) => t.ownerConversationId === c.id || t.reviewerConversationId === c.id)));
135
+ if (p)
136
+ return p;
137
+ c = c.parentConversationId ? this.store.conversation(c.parentConversationId) : undefined;
138
+ }
139
+ return undefined;
140
+ }
141
+ async create(input) {
142
+ const workspaceId = this.workspace(input.workspaceId);
143
+ if (input.ownerConversationId !== undefined)
144
+ throw new Error("项目负责人由启动操作创建独立会话");
145
+ if (input.risk !== undefined && !["low", "medium", "high"].includes(input.risk))
146
+ throw new Error("项目风险等级不合法");
147
+ if (input.budget !== undefined && (!input.budget || typeof input.budget !== "object" || Array.isArray(input.budget)))
148
+ throw new Error("资源预算不合法");
149
+ for (const [key, value] of Object.entries(input.budget ?? {}))
150
+ if (!["maxTokens", "maxConcurrent"].includes(key) || !Number.isSafeInteger(value) || value <= 0)
151
+ throw new Error("资源预算必须是正整数");
152
+ const now = stamp();
153
+ const p = { id: `project_${randomUUID()}`, workspaceId, title: text(input.title, "项目标题"), objective: text(input.objective, "项目目标"),
154
+ deliverables: strings(input.deliverables, "交付物"), acceptanceCriteria: strings(input.acceptanceCriteria, "验收标准"), constraints: strings(input.constraints, "约束"),
155
+ risk: input.risk ?? "low", status: "planning", budget: { maxTokens: input.budget?.maxTokens ?? 100_000, maxConcurrent: input.budget?.maxConcurrent ?? 2 },
156
+ tasks: [], reports: [], escalations: [], events: [], createdAt: now, updatedAt: now };
157
+ this.event(p, "created", "Boss 触发项目");
158
+ return this.store.setProject(p);
159
+ }
160
+ admission(p, checkConcurrency = true, coordinator = false) {
161
+ this.live(p);
162
+ if (p.escalations.some((e) => e.status === "open" && e.severity === "high"))
163
+ throw new Error("等待 Boss 处理升级决策后才能继续派发");
164
+ const r = this.resources(p);
165
+ if (p.budget?.maxTokens && r.tokens >= p.budget.maxTokens)
166
+ throw new Error("已达到已报告 Token 预算,拒绝继续派发");
167
+ const reserved = p.tasks.filter((t) => {
168
+ if (t.status === "in_progress" && t.role === "leader")
169
+ return false;
170
+ const sessionId = t.status === "in_progress" ? t.ownerConversationId : t.status === "awaiting_review" ? t.reviewerConversationId : undefined;
171
+ const state = sessionId ? this.store.conversation(sessionId)?.status : undefined;
172
+ if (busy(state))
173
+ return true;
174
+ if (state !== "idle")
175
+ return false;
176
+ const last = p.events?.filter((e) => e.taskId === t.id && (e.kind === "dispatched" || e.kind === "review_requested")).at(-1);
177
+ return Boolean(last && Date.now() - Date.parse(last.createdAt) < 5000);
178
+ }).length;
179
+ if (checkConcurrency && !coordinator && p.budget?.maxConcurrent && Math.max(r.activeWorkers, reserved) >= p.budget.maxConcurrent)
180
+ throw new Error("项目执行并发已满,等待任务交付后重试");
181
+ }
182
+ context(p) {
183
+ return `项目 ${p.id}:${p.title}\n目标:${p.objective}\n交付物:${p.deliverables.join(";")}\n验收标准:${p.acceptanceCriteria.join(";")}\n约束:${p.constraints.join(";")}\n预算:${JSON.stringify(p.budget)}。预算是派发准入限制,运行中请求可能超额。`;
184
+ }
185
+ async start(id, workspaceId, actor) {
186
+ return this.change(id, workspaceId, async (p) => {
187
+ this.leader(p, actor);
188
+ this.admission(p, false);
189
+ if (!this.orchestrator)
190
+ throw new Error("会话编排服务未就绪");
191
+ if (p.ownerConversationId && busy(this.store.conversation(p.ownerConversationId)?.status))
192
+ throw new Error("项目负责人正在执行,请勿重复启动");
193
+ if (!p.ownerConversationId)
194
+ p.ownerConversationId = (await this.orchestrator.spawn({ workspaceId, title: `项目负责人 · ${p.title}`, task: p.objective, initialPrompt: "" })).conversationId;
195
+ this.event(p, "started", "项目负责人开始组织执行", undefined, actor);
196
+ p.status = "active";
197
+ await this.store.setProject(p);
198
+ await this.orchestrator.dispatch(p.ownerConversationId, `${this.context(p)}\n你是直接向 Boss 负责的项目 Leader,兼任秘书。先调用 project(action=get, projectId) 了解最新状态。小项目只拆最少必要任务,大项目按交付边界分工。用 add_task 登记任务(明确目标、交付物、验收标准、依赖);需要继续拆解的工作设 role=leader,叶子执行任务设 role=executor,子 Leader 用 parentTaskId 登记自己的子任务,dispatch 启动执行子会话;不要用 session_spawn/session_dispatch 或 task/workflow 绕过项目资源限制。收到交付后用 request_review 启动独立复核。子会话结束会自动回报给你,届时继续派发 ready 任务、复核待验收任务或按 review.summary 返工。不要轮询等待,不要反复无进展重试,并发满时结束当前回复等待回报。用 report 登记关键进展,用 escalate 将重大偏差、权限或预算问题交给 Boss,不得自行解决升级事项。所有任务验收后,用 complete 提交带证据的最终交付总结。上下文有限,明确假设,主动寻找反证。只在本次触发范围内工作。`, { wait: false });
199
+ return p;
200
+ });
201
+ }
202
+ async addTask(id, input, workspaceId, actor) {
203
+ return this.change(id, workspaceId, (p) => {
204
+ this.live(p);
205
+ const parentTaskId = optional(input.parentTaskId, "父任务 id");
206
+ let manager = p.ownerConversationId;
207
+ if (parentTaskId) {
208
+ const parent = this.task(p, parentTaskId);
209
+ if (parent.role !== "leader" || parent.status !== "in_progress" || !parent.ownerConversationId)
210
+ throw new Error("父任务必须是执行中的 Leader 任务");
211
+ if (actor && actor !== parent.ownerConversationId)
212
+ throw new Error("仅父任务 Leader 可以拆解子任务");
213
+ manager = parent.ownerConversationId;
214
+ let depth = 1;
215
+ let ancestor = parent;
216
+ while (ancestor?.parentTaskId) {
217
+ depth++;
218
+ ancestor = this.task(p, ancestor.parentTaskId);
219
+ }
220
+ if (depth >= 3 && input.role === "leader")
221
+ throw new Error("Leader 最多 3 层,请改为执行任务");
222
+ }
223
+ else
224
+ this.leader(p, actor);
225
+ if (input.role !== undefined && input.role !== "leader" && input.role !== "executor")
226
+ throw new Error("任务角色必须是 leader 或 executor");
227
+ const dependencies = strings(input.dependencies, "任务依赖");
228
+ for (const dependency of dependencies)
229
+ if (!p.tasks.some((t) => t.id === dependency))
230
+ throw new Error(`任务依赖不存在:${dependency}`);
231
+ for (const dependency of dependencies)
232
+ if (this.task(p, dependency).parentTaskId !== parentTaskId)
233
+ throw new Error("任务依赖只能指向同级任务,避免层级死锁");
234
+ if (input.status !== undefined && input.status !== "pending" && input.status !== "ready")
235
+ throw new Error("新任务必须从待派发或等待依赖开始");
236
+ if (input.ownerConversationId || input.reviewerConversationId)
237
+ throw new Error("执行和复核会话由派发操作创建");
238
+ if (input.priority !== undefined && !["low", "medium", "high", "urgent"].includes(input.priority))
239
+ throw new Error("任务优先级不合法");
240
+ if (p.tasks.length >= 100)
241
+ throw new Error("项目最多 100 个任务,请收敛分工范围");
242
+ const now = stamp();
243
+ const t = { id: `task_${randomUUID()}`, projectId: id, title: text(input.title, "任务标题"), objective: text(input.objective, "任务目标"),
244
+ status: dependencies.length ? "pending" : "ready", accountableConversationId: manager, parentTaskId, role: input.role ?? "executor", dependencies,
245
+ deliverables: strings(input.deliverables, "任务交付物"), acceptanceCriteria: strings(input.acceptanceCriteria, "任务验收标准"), priority: input.priority ?? "medium",
246
+ dueAt: optional(input.dueAt, "截止时间"), createdAt: now, updatedAt: now };
247
+ p.tasks.push(t);
248
+ this.reconcile(p);
249
+ this.event(p, "task_created", `登记任务:${t.title}`, t.id, actor);
250
+ return t;
251
+ });
252
+ }
253
+ async dispatchTask(id, taskId, workspaceId, actor) {
254
+ return this.change(id, workspaceId, async (p) => {
255
+ const t = this.task(p, taskId);
256
+ const manager = this.manager(p, t, actor);
257
+ if (!dependenciesComplete(p, t))
258
+ throw new Error("任务的前置依赖尚未全部完成,不能开始执行");
259
+ if (t.status !== "ready")
260
+ throw new Error("仅待派发任务可以启动");
261
+ this.admission(p, true, t.role === "leader");
262
+ if (t.role === "leader" && !t.ownerConversationId && p.tasks.filter((t) => t.role === "leader" && t.ownerConversationId).length >= 8)
263
+ throw new Error("分支 Leader 最多 8 个,请收敛组织层级");
264
+ if (!this.orchestrator || !p.ownerConversationId)
265
+ throw new Error("请先启动项目负责人");
266
+ if (t.parentTaskId && this.task(p, t.parentTaskId).status !== "in_progress")
267
+ throw new Error("父任务未处于执行中");
268
+ if (!t.ownerConversationId)
269
+ t.ownerConversationId = (await this.orchestrator.spawn({ workspaceId, parentConversationId: manager, title: `${t.role === "leader" ? "Leader" : "执行"} · ${t.title}`, task: t.objective, initialPrompt: "" })).conversationId;
270
+ if (busy(this.store.conversation(t.ownerConversationId)?.status))
271
+ throw new Error("执行会话尚未结束,请等待完成回报后派发");
272
+ t.status = "in_progress";
273
+ t.updatedAt = stamp();
274
+ delete t.blockedReason;
275
+ this.event(p, "dispatched", `派发执行:${t.title}`, t.id, actor);
276
+ this.reconcile(p);
277
+ await this.store.setProject(p);
278
+ const delegation = t.role === "leader" ? `你是分支 Leader,负责这项任务的拆解、委派、集成交付。用 project add_task 时必须带 parentTaskId="${t.id}";可按必要性指定 role="leader" 或 "executor"。用 project dispatch / request_review 管理直属子任务,子会话结束自动回报给你,不要轮询;并发满时结束本轮等待回报。所有必需子任务独立验收后才能 submit 聚合交付,上级还会独立复核你的集成结果。最多 3 层 Leader、8 个分支 Leader;小任务不要继续拆层。` : "你是执行者,不得自行拆解子任务。";
279
+ await this.orchestrator.dispatch(t.ownerConversationId, `${this.context(p)}\n${delegation}\n你负责执行任务 ${t.id}:${t.title}\n目标:${t.objective}\n交付物:${t.deliverables.join(";")}\n验收:${t.acceptanceCriteria.join(";")}\n上次复核意见与验证证据:${JSON.stringify(t.review ?? null)}\n已验收前置产物:${JSON.stringify(p.tasks.filter((d) => t.dependencies.includes(d.id)).map((d) => ({ title: d.title, output: d.output, evidenceRefs: d.evidenceRefs })))}\n你不是唯一执行者,遵守分工边界。完成后必须调用 project(action=submit, projectId="${p.id}", taskId="${t.id}", output="交付总结", evidenceRefs=["产物路径或验证记录"]),不能自行标记已完成。复核退回时修复后重新 submit。遇到阻塞用 report 或 escalate,保留证据、假设和未解决问题。不要自行创建其他执行会话或 task/workflow。`, { wait: false });
280
+ return t;
281
+ });
282
+ }
283
+ async submit(id, taskId, input, workspaceId, actor) {
284
+ return this.change(id, workspaceId, (p) => {
285
+ this.live(p);
286
+ const t = this.task(p, taskId);
287
+ if (actor && actor !== t.ownerConversationId)
288
+ throw new Error("仅任务执行者可以提交交付");
289
+ if (t.status !== "in_progress")
290
+ throw new Error("仅执行中的任务可以提交交付");
291
+ if (t.role === "leader" && !this.requiredComplete(p, t.id))
292
+ throw new Error("Leader 的必需子任务尚未全部独立验收");
293
+ t.output = text(input.output, "交付结果");
294
+ t.evidenceRefs = strings(input.evidenceRefs, "交付证据");
295
+ if (!t.evidenceRefs.length)
296
+ throw new Error("交付必须附上产物或验证证据");
297
+ t.status = "awaiting_review";
298
+ t.updatedAt = stamp();
299
+ delete t.review;
300
+ this.event(p, "submitted", `交付待独立复核:${t.title}`, t.id, actor);
301
+ this.reconcile(p);
302
+ return t;
303
+ });
304
+ }
305
+ async requestReview(id, taskId, workspaceId, actor) {
306
+ return this.change(id, workspaceId, async (p) => {
307
+ this.admission(p);
308
+ const t = this.task(p, taskId);
309
+ const manager = this.manager(p, t, actor);
310
+ if (t.status !== "awaiting_review")
311
+ throw new Error("任务尚未提交交付");
312
+ if (!this.orchestrator || !p.ownerConversationId)
313
+ throw new Error("请先启动项目负责人");
314
+ if (!t.reviewerConversationId)
315
+ t.reviewerConversationId = (await this.orchestrator.spawn({ workspaceId, parentConversationId: manager, title: `复核 · ${t.title}`, task: `独立复核 ${t.title}`, initialPrompt: "" })).conversationId;
316
+ if (busy(this.store.conversation(t.reviewerConversationId)?.status))
317
+ throw new Error("复核会话正在执行");
318
+ this.event(p, "review_requested", `启动独立复核:${t.title}`, t.id, actor);
319
+ await this.store.setProject(p);
320
+ await this.orchestrator.dispatch(t.reviewerConversationId, `${this.context(p)}\n你是独立复核人,只读检查,不修改执行者产物。任务 ${t.id}:${t.title}\n目标:${t.objective}\n验收标准:${t.acceptanceCriteria.join(";")}\n交付:${t.output}\n证据:${JSON.stringify(t.evidenceRefs)}\n主动寻找反例,核对真实产物、测试与证据,不能只复述作者结论。调用 project(action=review, projectId="${p.id}", taskId="${t.id}", verdict="accepted" 或 "changes_requested", summary="验收理由或具体返工项", evidenceRefs=["独立验证记录"])。完成后结束本轮,由上级安排后续。不创建额外会话。`, { wait: false });
321
+ return t;
322
+ });
323
+ }
324
+ async review(id, taskId, input, workspaceId, actor) {
325
+ return this.change(id, workspaceId, (p) => {
326
+ this.live(p);
327
+ const t = this.task(p, taskId);
328
+ if (!actor || actor !== t.reviewerConversationId || actor === t.ownerConversationId)
329
+ throw new Error("仅指定的独立复核人可以验收,禁止自审");
330
+ if (t.status !== "awaiting_review")
331
+ throw new Error("任务不处于待复核状态");
332
+ if (input.verdict !== "accepted" && input.verdict !== "changes_requested")
333
+ throw new Error("复核结论不合法");
334
+ const evidenceRefs = strings(input.evidenceRefs, "复核证据");
335
+ if (!evidenceRefs.length)
336
+ throw new Error("复核必须附独立验证证据");
337
+ t.review = { verdict: input.verdict, summary: text(input.summary, "复核意见"), evidenceRefs, reviewerConversationId: actor, createdAt: stamp() };
338
+ t.status = input.verdict === "accepted" ? "completed" : "ready";
339
+ t.updatedAt = stamp();
340
+ this.event(p, "reviewed", `${input.verdict === "accepted" ? "验收通过" : "退回返工"}:${t.title};${t.review.summary}`, t.id, actor);
341
+ this.reconcile(p);
342
+ return t;
343
+ });
344
+ }
345
+ async updateTask(id, taskId, patch, actor) {
346
+ const result = await this.change(id, patch.workspaceId, (p) => {
347
+ this.live(p);
348
+ const t = this.task(p, taskId);
349
+ this.member(p, actor);
350
+ const manager = this.manager(p, t);
351
+ if (actor && actor !== manager && actor !== t.ownerConversationId)
352
+ throw new Error("无权更新此任务");
353
+ if (patch.status !== undefined) {
354
+ if (patch.status === "in_progress" && !dependenciesComplete(p, t))
355
+ throw new Error("任务的前置依赖尚未全部完成,不能开始执行");
356
+ if (!["blocked", "failed", "cancelled", "ready"].includes(String(patch.status)))
357
+ throw new Error("请使用派发、交付或独立复核操作流转任务");
358
+ if (t.status === "completed" || t.status === "cancelled")
359
+ throw new Error("任务已结束");
360
+ if (patch.status === "ready" && !dependenciesComplete(p, t))
361
+ throw new Error("前置依赖尚未完成");
362
+ if (patch.status === "cancelled" && actor)
363
+ throw new Error("取消交付范围必须由 Boss 决策");
364
+ if (patch.status === "cancelled" && !patch.blockedReason)
365
+ throw new Error("取消任务必须记录 Boss 的范围变更理由");
366
+ if (patch.status === "ready" && actor && actor !== manager)
367
+ throw new Error("仅直属 Leader 可以重新派发任务");
368
+ t.status = patch.status;
369
+ }
370
+ if (patch.blockedReason !== undefined)
371
+ t.blockedReason = text(patch.blockedReason, "阻塞原因");
372
+ if (t.status === "blocked" && !t.blockedReason)
373
+ throw new Error("阻塞任务必须说明原因");
374
+ if (patch.dueAt !== undefined)
375
+ t.dueAt = text(patch.dueAt, "截止时间");
376
+ if (patch.output !== undefined)
377
+ t.output = text(patch.output, "任务产出");
378
+ if (patch.evidenceRefs !== undefined)
379
+ t.evidenceRefs = strings(patch.evidenceRefs, "交付证据");
380
+ if (patch.review !== undefined && !actor) {
381
+ const rev = patch.review;
382
+ if (rev === null) {
383
+ delete t.review;
384
+ }
385
+ else if (typeof rev === "object") {
386
+ t.review = {
387
+ verdict: rev.verdict === "accepted" ? "accepted" : "changes_requested",
388
+ summary: text(rev.summary, "复核意见"),
389
+ evidenceRefs: strings(rev.evidenceRefs ?? ["verify.md"], "复核证据"),
390
+ reviewerConversationId: typeof rev.reviewerConversationId === "string" ? rev.reviewerConversationId : (p.ownerConversationId ?? "boss"),
391
+ createdAt: stamp(),
392
+ };
393
+ }
394
+ }
395
+ if (patch.status === "cancelled") {
396
+ const descendants = new Set([t.id]);
397
+ for (let changed = true; changed;) {
398
+ changed = false;
399
+ for (const child of p.tasks)
400
+ if (child.parentTaskId && descendants.has(child.parentTaskId) && !descendants.has(child.id)) {
401
+ descendants.add(child.id);
402
+ changed = true;
403
+ }
404
+ }
405
+ for (const child of p.tasks)
406
+ if (child.id !== t.id && descendants.has(child.id) && child.status !== "completed") {
407
+ child.status = "cancelled";
408
+ child.blockedReason = t.blockedReason;
409
+ child.updatedAt = stamp();
410
+ this.event(p, "task_updated", `范围变更取消子任务:${child.title}`, child.id);
411
+ }
412
+ }
413
+ t.updatedAt = stamp();
414
+ this.event(p, "task_updated", `${t.title}:${t.status}`, t.id, actor);
415
+ this.reconcile(p);
416
+ return t;
417
+ });
418
+ if (patch.status === "cancelled") {
419
+ if (result.ownerConversationId)
420
+ await this.orchestrator?.stopSubtree?.(result.ownerConversationId);
421
+ if (result.reviewerConversationId)
422
+ await this.orchestrator?.stopSubtree?.(result.reviewerConversationId);
423
+ }
424
+ return result;
425
+ }
426
+ async updateStatus(id, status, workspaceId) {
427
+ const result = await this.change(id, workspaceId, (p) => {
428
+ this.live(p);
429
+ if (!["active", "cancelled", "failed"].includes(status))
430
+ throw new Error("项目完成必须提交最终交付,其他状态由任务和例外推导");
431
+ if (status === "active" && p.escalations.some((e) => e.status === "open"))
432
+ throw new Error("请先处理未解决的例外");
433
+ p.status = status;
434
+ if (closed(p))
435
+ p.closedAt = stamp();
436
+ this.event(p, "status", `项目状态:${status}`);
437
+ return p;
438
+ });
439
+ // Stop after releasing the write lock: an interrupted tool may itself be waiting for it.
440
+ if ((status === "cancelled" || status === "failed") && result.ownerConversationId)
441
+ await this.orchestrator?.stopSubtree?.(result.ownerConversationId);
442
+ return result;
443
+ }
444
+ async updateBudget(id, input, workspaceId) {
445
+ return this.change(id, workspaceId, (p) => {
446
+ this.live(p);
447
+ for (const key of ["maxTokens", "maxConcurrent"])
448
+ if (input[key] !== undefined) {
449
+ if (!Number.isSafeInteger(input[key]) || input[key] <= 0)
450
+ throw new Error("资源预算必须是正整数");
451
+ (p.budget ??= {})[key] = input[key];
452
+ }
453
+ this.event(p, "budget", `Boss 调整派发预算:${p.budget?.maxTokens} Token,并发 ${p.budget?.maxConcurrent}`);
454
+ return p;
455
+ });
456
+ }
457
+ async addReport(id, input, workspaceId, actor) {
458
+ return this.change(id, workspaceId, (p) => {
459
+ this.live(p);
460
+ this.member(p, actor);
461
+ if (!["progress", "blocked", "delivery", "decision_request", "review"].includes(input.kind))
462
+ throw new Error("汇报类型不合法");
463
+ const t = input.taskId ? this.task(p, text(input.taskId, "任务 id")) : undefined;
464
+ if (t && ["completed", "cancelled"].includes(t.status) && input.kind === "blocked")
465
+ throw new Error("已结束的任务不能标记阻塞");
466
+ if (actor && actor !== p.ownerConversationId && (!t || ![t.ownerConversationId, t.reviewerConversationId, this.manager(p, t)].includes(actor)))
467
+ throw new Error("仅可汇报本人负责的任务");
468
+ const report = { id: randomUUID(), projectId: id, taskId: t?.id, authorConversationId: actor, kind: input.kind,
469
+ progress: text(input.progress, "汇报进展"), variance: optional(input.variance, "偏差"), nextStep: optional(input.nextStep, "下一步"), risks: strings(input.risks, "风险"),
470
+ decisionNeeded: optional(input.decisionNeeded, "所需决策"), evidenceRefs: strings(input.evidenceRefs, "证据"), createdAt: stamp() };
471
+ p.reports.push(report);
472
+ if (report.kind === "blocked" && t) {
473
+ t.status = "blocked";
474
+ t.blockedReason = report.progress;
475
+ t.updatedAt = stamp();
476
+ }
477
+ this.event(p, "report", report.progress, t?.id, actor);
478
+ this.reconcile(p);
479
+ return report;
480
+ });
481
+ }
482
+ async addEscalation(id, input, workspaceId, actor) {
483
+ return this.change(id, workspaceId, (p) => {
484
+ this.live(p);
485
+ this.member(p, actor);
486
+ if (!["low", "medium", "high"].includes(input.severity))
487
+ throw new Error("升级风险等级不合法");
488
+ const t = input.taskId ? this.task(p, text(input.taskId, "任务 id")) : undefined;
489
+ const e = { id: randomUUID(), projectId: id, taskId: t?.id, reason: text(input.reason, "升级原因"), severity: input.severity, status: "open", requestedByConversationId: actor, createdAt: stamp() };
490
+ p.escalations.push(e);
491
+ this.event(p, "escalated", e.reason, t?.id, actor);
492
+ this.reconcile(p);
493
+ return e;
494
+ });
495
+ }
496
+ async resolveEscalation(id, escalationId, decision, status, workspaceId) {
497
+ const result = await this.change(id, workspaceId, (p) => {
498
+ this.live(p);
499
+ if (status !== "resolved" && status !== "dismissed")
500
+ throw new Error("升级处理状态不合法");
501
+ const e = p.escalations.find((e) => e.id === escalationId);
502
+ if (!e)
503
+ throw new Error("升级事项不存在");
504
+ if (e.status !== "open")
505
+ throw new Error("升级事项已处理");
506
+ e.status = status;
507
+ e.decision = text(decision, "处理意见");
508
+ e.resolvedAt = stamp();
509
+ this.event(p, "decision", `Boss 决策:${e.decision}`, e.taskId);
510
+ this.reconcile(p);
511
+ return e;
512
+ });
513
+ const p = this.get(id, workspaceId);
514
+ const targetTask = result.taskId
515
+ ? p.tasks.find((t) => t.id === result.taskId)
516
+ : p.tasks.find((t) => t.ownerConversationId === result.requestedByConversationId || t.reviewerConversationId === result.requestedByConversationId);
517
+ let directLeaderId;
518
+ if (targetTask) {
519
+ if (targetTask.role === "leader" && targetTask.ownerConversationId) {
520
+ directLeaderId = targetTask.ownerConversationId;
521
+ }
522
+ else if (targetTask.parentTaskId) {
523
+ const parent = p.tasks.find((t) => t.id === targetTask.parentTaskId);
524
+ directLeaderId = parent?.ownerConversationId ?? targetTask.accountableConversationId;
525
+ }
526
+ else {
527
+ directLeaderId = targetTask.accountableConversationId ?? p.ownerConversationId;
528
+ }
529
+ }
530
+ const recipientId = directLeaderId || p.ownerConversationId;
531
+ if (recipientId && this.orchestrator)
532
+ await this.orchestrator.dispatch(recipientId, `Boss 已处理项目 ${p.id} 的例外:${result.reason}\n决策:${result.decision}\n先读取 project 最新状态,按决策继续范围内的工作。`, { wait: false });
533
+ return result;
534
+ }
535
+ async complete(id, summary, evidenceRefs, workspaceId, actor) {
536
+ return this.change(id, workspaceId, (p) => {
537
+ this.live(p);
538
+ this.leader(p, actor);
539
+ const required = p.tasks.filter((t) => t.status !== "cancelled" || !t.blockedReason);
540
+ if (!required.length || required.some((t) => t.status !== "completed" || t.review?.verdict !== "accepted" || !t.review.reviewerConversationId || t.review.reviewerConversationId !== t.reviewerConversationId || t.review.reviewerConversationId === t.ownerConversationId))
541
+ throw new Error("所有任务必须经独立复核验收后才能完成项目");
542
+ if (p.escalations.some((e) => e.status === "open"))
543
+ throw new Error("尚有未处理例外");
544
+ const evidence = strings(evidenceRefs, "最终交付证据");
545
+ if (!evidence.length)
546
+ throw new Error("最终交付必须附证据");
547
+ const progress = text(summary, "最终交付总结");
548
+ p.reports.push({ id: randomUUID(), projectId: id, kind: "delivery", progress, evidenceRefs: evidence, authorConversationId: actor, createdAt: stamp() });
549
+ p.status = "completed";
550
+ p.closedAt = stamp();
551
+ this.event(p, "completed", progress, undefined, actor);
552
+ return p;
553
+ });
554
+ }
555
+ async tool(body, workspaceId, actor) {
556
+ const id = text(body.projectId, "projectId");
557
+ const p = this.get(id, workspaceId);
558
+ this.member(p, actor);
559
+ switch (body.action) {
560
+ case "get": return p;
561
+ case "add_task": return this.addTask(id, body, workspaceId, actor);
562
+ case "dispatch": return this.dispatchTask(id, text(body.taskId, "taskId"), workspaceId, actor);
563
+ case "submit": return this.submit(id, text(body.taskId, "taskId"), body, workspaceId, actor);
564
+ case "request_review": return this.requestReview(id, text(body.taskId, "taskId"), workspaceId, actor);
565
+ case "review": return this.review(id, text(body.taskId, "taskId"), body, workspaceId, actor);
566
+ case "update_task": return this.updateTask(id, text(body.taskId, "taskId"), { ...body, workspaceId }, actor);
567
+ case "report": return this.addReport(id, body, workspaceId, actor);
568
+ case "escalate": return this.addEscalation(id, body, workspaceId, actor);
569
+ case "complete": return this.complete(id, body.summary, body.evidenceRefs, workspaceId, actor);
570
+ default: throw new Error("未知的项目工具操作");
571
+ }
572
+ }
573
+ }