@evandu/super-pi 0.1.164 → 0.1.165
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/agents/super-pi-task/core.ts +1 -1
- package/dist/client/.super-pi-client-sources.json +3 -3
- package/dist/client/assets/{CodeMirrorEditor-B7hhyG33.js → CodeMirrorEditor-B3-4Qpd1.js} +2 -2
- package/dist/client/assets/{dist-BmeBkDNB.js → dist-2Ax3GOhT.js} +1 -1
- package/dist/client/assets/{dist-CF9zuubZ.js → dist-BD4w6y25.js} +1 -1
- package/dist/client/assets/{dist-qqDS87aN.js → dist-BIqjsoet.js} +1 -1
- package/dist/client/assets/{dist-DaUyp_NN.js → dist-BWsed8P8.js} +1 -1
- package/dist/client/assets/{dist-ByEzPyEi.js → dist-Bh9jVMEA.js} +1 -1
- package/dist/client/assets/{dist-BhouAfNI.js → dist-BhiW2HYK.js} +1 -1
- package/dist/client/assets/{dist-D607J8oC.js → dist-C2mhG_5m.js} +1 -1
- package/dist/client/assets/{dist-BjlmPyRl.js → dist-CL9_Ff9R.js} +1 -1
- package/dist/client/assets/{dist-BDy5Nw4_.js → dist-CN6rdrJA.js} +1 -1
- package/dist/client/assets/{dist-CGO5Bn1x.js → dist-Cn4Q7-eG.js} +1 -1
- package/dist/client/assets/{dist-CdpuBRoy.js → dist-D45Y5G5v.js} +1 -1
- package/dist/client/assets/{dist-BLhPsVFt.js → dist-DF05sEDO.js} +1 -1
- package/dist/client/assets/{dist-BBfDBMAk.js → dist-DGOgW2hA.js} +1 -1
- package/dist/client/assets/{dist-C4ql8ak8.js → dist-DQnuHIqA.js} +1 -1
- package/dist/client/assets/{dist-D6nsZZd3.js → dist-DRgOr5sP.js} +1 -1
- package/dist/client/assets/{dist-Foaz_oK2.js → dist-Di8I1Dsi.js} +1 -1
- package/dist/client/assets/{dist-B5xG-jTK.js → dist-Do72hsvz.js} +1 -1
- package/dist/client/assets/{dist-DJ4sWDSy.js → dist-DtNwDyK1.js} +1 -1
- package/dist/client/assets/{dist-CrJV_eES.js → dist-QTqWnNGE.js} +1 -1
- package/dist/client/assets/{dist-7Nz91m3a.js → dist-hUAwe2Kn.js} +1 -1
- package/dist/client/assets/{dist-CxZb6ATc.js → dist-rFG_iRW3.js} +1 -1
- package/dist/client/assets/{dist-CsBiEr54.js → dist-wW-2h0X5.js} +1 -1
- package/dist/client/assets/{index-1hS3wVqF.js → index-BUTxkA3D.js} +4 -4
- package/dist/client/index.html +1 -1
- package/dist/server/application/project-service.d.ts +95 -4
- package/dist/server/application/project-service.js +316 -70
- package/dist/server/http-server.js +7 -0
- package/dist/server/types.d.ts +4 -0
- package/package.json +3 -1
|
@@ -4,13 +4,22 @@ const stamp = () => new Date().toISOString();
|
|
|
4
4
|
* WHAT THE PROJECT LEADER IS TOLD WHEN IT STARTS.
|
|
5
5
|
*
|
|
6
6
|
* The two prompts are kept apart on purpose. The standard one is the shape the project flow has
|
|
7
|
-
* always had - decompose, delegate, review
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* always had - decompose, delegate, review. The lightweight one is a different set of
|
|
8
|
+
* instructions about a different shape: one session, no decomposition, no reviewer. Writing one
|
|
9
|
+
* prompt with holes in it would mean every sentence had to carry a condition, and the model would
|
|
10
|
+
* have to work out which shape it was in.
|
|
11
|
+
*
|
|
12
|
+
* THE ESCALATION RULE IS SAID ONCE, IN ONE VOICE. The standard leader used to be told two
|
|
13
|
+
* opposite things about the same event - "your subordinates' escalations come to you first, you
|
|
14
|
+
* decide whether to handle them yourself or report them up" AND "you must not resolve escalations
|
|
15
|
+
* yourself" - and a model reading both has no way to pick, so it hesitates between them. The
|
|
16
|
+
* rule is now one sentence with three parts: handle it at your own level, escalate only what
|
|
17
|
+
* really needs the Boss, and never decide for the Boss or drop an escalation. The lightweight
|
|
18
|
+
* leader has no subordinates at all, so it is told the second half only - it must not decide
|
|
19
|
+
* alone either - and is never told about subordinates it does not have.
|
|
11
20
|
*/
|
|
12
|
-
const STANDARD_LEADER_PROMPT = "你是直接向 Boss 负责的项目 Leader,兼任秘书。先调用 project(action=get, projectId) 了解最新状态。小项目只拆最少必要任务,大项目按交付边界分工。用 add_task 登记任务(明确目标、交付物、验收标准、依赖);add_task / update_task 时可以带 executorProfile 与 reviewerProfile(支持指定 agentId、cwd、model、thinkingLevel、watchdogTimeoutSeconds),按任务性质将执行与复核委派给最合适的员工,不指定则继承项目默认;需要继续拆解的工作设 role=leader,叶子执行任务设 role=executor,子 Leader 用 parentTaskId 登记自己的子任务,dispatch 启动执行子会话;不要用 session_spawn/session_dispatch 或 task/workflow 绕过项目资源限制。收到交付后用 request_review 启动独立复核。子会话结束会自动回报给你,届时继续派发 ready 任务、复核待验收任务或按 review.summary 返工。不要轮询等待,不要反复无进展重试,并发满时结束当前回复等待回报。用 report
|
|
13
|
-
const LIGHTWEIGHT_LEADER_PROMPT = "你是直接向 Boss 负责的项目 Leader,这是一个轻量项目:这个项目由你亲自完成,不要拆子任务、不要派发给别人、也没有独立复核。先调用 project(action=get, projectId) 了解最新状态。用 add_task 登记任务(明确目标、交付物、验收标准、依赖),任务一律登记为执行任务:role=leader 或带 parentTaskId 都会被拒绝。dispatch 会把任务交回给你自己,你亲自做完;做完后调用 project(action=submit, projectId, taskId, output, evidenceRefs) 提交交付,提交即完成,不会有人回来复核,证据必须附上。不要用 session_spawn/session_dispatch 或 task/workflow 绕过项目资源限制。不要轮询等待,不要反复无进展重试。用 report 登记关键进展,用 escalate 将重大偏差、权限或预算问题交给 Boss,不得自行解决升级事项。所有任务交付后,用 complete 提交带证据的最终交付总结。上下文有限,明确假设,主动寻找反证。只在本次触发范围内工作。";
|
|
21
|
+
export const STANDARD_LEADER_PROMPT = "你是直接向 Boss 负责的项目 Leader,兼任秘书。先调用 project(action=get, projectId) 了解最新状态。小项目只拆最少必要任务,大项目按交付边界分工。用 add_task 登记任务(明确目标、交付物、验收标准、依赖);add_task / update_task 时可以带 executorProfile 与 reviewerProfile(支持指定 agentId、cwd、model、thinkingLevel、watchdogTimeoutSeconds),按任务性质将执行与复核委派给最合适的员工,不指定则继承项目默认;需要继续拆解的工作设 role=leader,叶子执行任务设 role=executor,子 Leader 用 parentTaskId 登记自己的子任务,dispatch 启动执行子会话;不要用 session_spawn/session_dispatch 或 task/workflow 绕过项目资源限制。收到交付后用 request_review 启动独立复核。子会话结束会自动回报给你,届时继续派发 ready 任务、复核待验收任务或按 review.summary 返工。不要轮询等待,不要反复无进展重试,并发满时结束当前回复等待回报。用 report 登记关键进展。你的下属的升级先到你这里:你在自己这一层处理,能自己解决的自己解决;只有确实需要 Boss 决策的(重大偏差、权限或预算问题)才用 escalate 交给 Boss。你不能替 Boss 做决定,也不要把没处理的升级压下去不理。所有任务验收后,用 complete 提交带证据的最终交付总结。上下文有限,明确假设,主动寻找反证。只在本次触发范围内工作。";
|
|
22
|
+
export const LIGHTWEIGHT_LEADER_PROMPT = "你是直接向 Boss 负责的项目 Leader,这是一个轻量项目:这个项目由你亲自完成,不要拆子任务、不要派发给别人、也没有独立复核。先调用 project(action=get, projectId) 了解最新状态。用 add_task 登记任务(明确目标、交付物、验收标准、依赖),任务一律登记为执行任务:role=leader 或带 parentTaskId 都会被拒绝。dispatch 会把任务交回给你自己,你亲自做完;做完后调用 project(action=submit, projectId, taskId, output, evidenceRefs) 提交交付,提交即完成,不会有人回来复核,证据必须附上。不要用 session_spawn/session_dispatch 或 task/workflow 绕过项目资源限制。不要轮询等待,不要反复无进展重试。用 report 登记关键进展,用 escalate 将重大偏差、权限或预算问题交给 Boss,不得自行解决升级事项。所有任务交付后,用 complete 提交带证据的最终交付总结。上下文有限,明确假设,主动寻找反证。只在本次触发范围内工作。";
|
|
14
23
|
const text = (value, label) => {
|
|
15
24
|
if (typeof value !== "string" || !value.trim())
|
|
16
25
|
throw new Error(`${label} 不能为空`);
|
|
@@ -122,6 +131,16 @@ const CACHE_WRITE_WEIGHT = 1.25;
|
|
|
122
131
|
const tokens = (usage) => usage ? Math.round(usage.input + usage.output + usage.cacheWrite * CACHE_WRITE_WEIGHT + usage.cacheRead * CACHE_READ_WEIGHT) : 0;
|
|
123
132
|
/** The budget a project starts with, and the floor the one-click raise falls back to. */
|
|
124
133
|
const DEFAULT_MAX_TOKENS = 100_000;
|
|
134
|
+
/**
|
|
135
|
+
* HOW MANY PROJECTS ONE LIST CALL MAY CARRY.
|
|
136
|
+
*
|
|
137
|
+
* The list goes into an agent's context, and a workspace that has been alive for a year has
|
|
138
|
+
* hundreds of finished projects. Twenty is enough to answer "how are my projects doing" - the
|
|
139
|
+
* running ones and the ones that just finished are all in there - and small enough that the
|
|
140
|
+
* answer does not arrive wrapped in a year of history. The count that was cut is NOT hidden:
|
|
141
|
+
* the list carries the real total next to the truncated one.
|
|
142
|
+
*/
|
|
143
|
+
const PROJECT_LIST_LIMIT = 20;
|
|
125
144
|
/**
|
|
126
145
|
* HOW MANY EVENTS THE REPORT'S TIMELINE PRINTS.
|
|
127
146
|
*
|
|
@@ -150,7 +169,7 @@ const EVENT_TEXT = {
|
|
|
150
169
|
created: "创建项目", started: "启动执行", task_created: "登记任务", dispatched: "派发执行",
|
|
151
170
|
submitted: "提交交付", reviewed: "复核", review_requested: "启动复核", task_updated: "任务更新", status: "状态变更",
|
|
152
171
|
budget: "预算变更", report: "进展汇报", escalated: "例外升级", decision: "决定",
|
|
153
|
-
completed: "项目完成", ready: "依赖就绪",
|
|
172
|
+
completed: "项目完成", ready: "依赖就绪", unreachable: "转述对象不可达",
|
|
154
173
|
};
|
|
155
174
|
const ESCALATION_STATUS_TEXT = { open: "未决", resolved: "已解决", dismissed: "已忽略" };
|
|
156
175
|
const SEVERITY_TEXT = { low: "低", medium: "中", high: "高" };
|
|
@@ -202,13 +221,24 @@ export class ProjectService {
|
|
|
202
221
|
delete t.effectiveReviewerProfile;
|
|
203
222
|
return t;
|
|
204
223
|
}
|
|
205
|
-
|
|
224
|
+
filterEscalations(escalations, actor) {
|
|
225
|
+
return escalations.filter((e) => {
|
|
226
|
+
const audience = e.audience ?? "boss";
|
|
227
|
+
if (audience === "boss")
|
|
228
|
+
return true;
|
|
229
|
+
if (!actor)
|
|
230
|
+
return false;
|
|
231
|
+
return actor === e.targetConversationId || actor === e.requestedByConversationId;
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
projectView(p, actor) {
|
|
206
235
|
const resources = this.resources(p);
|
|
207
236
|
return {
|
|
208
237
|
...p,
|
|
209
238
|
resources,
|
|
210
239
|
suggestedMaxTokens: this.suggestedMaxTokens(p, resources.tokens),
|
|
211
240
|
tasks: p.tasks.map((t) => ProjectService.taskView(t, p)),
|
|
241
|
+
escalations: this.filterEscalations(p.escalations ?? [], actor),
|
|
212
242
|
};
|
|
213
243
|
}
|
|
214
244
|
/**
|
|
@@ -241,7 +271,7 @@ export class ProjectService {
|
|
|
241
271
|
p.updatedAt = stamp();
|
|
242
272
|
(p.events ??= []).push({ id: randomUUID(), kind, summary, taskId, actorConversationId, createdAt: p.updatedAt });
|
|
243
273
|
}
|
|
244
|
-
async change(id, workspaceId, work) {
|
|
274
|
+
async change(id, workspaceId, work, actor) {
|
|
245
275
|
this.workspace(workspaceId);
|
|
246
276
|
const previous = this.queues.get(id) ?? Promise.resolve();
|
|
247
277
|
const pending = previous.catch(() => { }).then(async () => {
|
|
@@ -253,7 +283,7 @@ export class ProjectService {
|
|
|
253
283
|
}
|
|
254
284
|
await this.store.setProject(p);
|
|
255
285
|
if (result && typeof result === "object" && "tasks" in result && Array.isArray(result.tasks)) {
|
|
256
|
-
return structuredClone(this.projectView(result));
|
|
286
|
+
return structuredClone(this.projectView(result, actor));
|
|
257
287
|
}
|
|
258
288
|
return structuredClone(result);
|
|
259
289
|
});
|
|
@@ -268,6 +298,45 @@ export class ProjectService {
|
|
|
268
298
|
}
|
|
269
299
|
live(p) { if (closed(p))
|
|
270
300
|
throw new Error("项目已结束,不能继续修改或派发"); }
|
|
301
|
+
/**
|
|
302
|
+
* WAKING THE OTHER SIDE MUST NOT TAKE THE REPORT DOWN WITH IT.
|
|
303
|
+
*
|
|
304
|
+
* The report, the escalation or the decision is already written to the store by the time the
|
|
305
|
+
* other side is woken, so a dispatch that fails - a session that was archived, stopped or never
|
|
306
|
+
* existed - must NOT fail the request. It would: the caller would be told the whole thing
|
|
307
|
+
* failed although it succeeded, it would try again, and the same report would be written twice.
|
|
308
|
+
*
|
|
309
|
+
* So the wake is wrapped, the failure is written down, and the record is returned as the
|
|
310
|
+
* success it is.
|
|
311
|
+
*
|
|
312
|
+
* AND THE FAILURE IS NOT SILENT. A report nobody was told about is a report nobody will ever
|
|
313
|
+
* handle, so the project's own timeline gets an event saying exactly that, and the log line
|
|
314
|
+
* names the project and the escalation so the case can be found again later.
|
|
315
|
+
*/
|
|
316
|
+
async wake(target, prompt, what, projectId, workspaceId, actor) {
|
|
317
|
+
if (!this.orchestrator)
|
|
318
|
+
return;
|
|
319
|
+
try {
|
|
320
|
+
await this.orchestrator.dispatch(target, prompt, { wait: false });
|
|
321
|
+
}
|
|
322
|
+
catch (error) {
|
|
323
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
324
|
+
console.warn(`Super Pi: 项目 ${projectId} 的${what}已保存,但叫不醒会话 ${target}:${message}`);
|
|
325
|
+
await this.noteUnreachable(projectId, workspaceId, `${what}已保存,但转述对象会话不可达(${target}):${message}`, actor);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
/** THE TIMELINE SAYS THAT NOBODY COULD BE TOLD. Written after the fact, so it never loses the
|
|
329
|
+
* record that was already saved. */
|
|
330
|
+
async noteUnreachable(projectId, workspaceId, summary, actor) {
|
|
331
|
+
if (workspaceId === undefined)
|
|
332
|
+
return;
|
|
333
|
+
try {
|
|
334
|
+
await this.change(projectId, workspaceId, (p) => { this.event(p, "unreachable", summary, undefined, actor); });
|
|
335
|
+
}
|
|
336
|
+
catch (error) {
|
|
337
|
+
console.warn(`Super Pi: 项目 ${projectId} 记录「转述对象不可达」事件失败:${error instanceof Error ? error.message : String(error)}`);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
271
340
|
leader(p, actor) { if (actor && actor !== p.ownerConversationId)
|
|
272
341
|
throw new Error("仅项目负责人可以执行此操作"); }
|
|
273
342
|
topLevelAuthor(p, actor) {
|
|
@@ -320,6 +389,78 @@ export class ProjectService {
|
|
|
320
389
|
if (actor && actor !== p.ownerConversationId && actor !== p.initiatorConversationId && !p.tasks.some((t) => t.ownerConversationId === actor || t.reviewerConversationId === actor))
|
|
321
390
|
throw new Error("调用会话不属于项目执行团队");
|
|
322
391
|
}
|
|
392
|
+
isMember(p, actor) {
|
|
393
|
+
if (!actor)
|
|
394
|
+
return false;
|
|
395
|
+
if (actor === p.initiatorConversationId)
|
|
396
|
+
return true;
|
|
397
|
+
if (actor === p.ownerConversationId)
|
|
398
|
+
return true;
|
|
399
|
+
if (p.tasks?.some((t) => t.ownerConversationId === actor || t.reviewerConversationId === actor))
|
|
400
|
+
return true;
|
|
401
|
+
if (this.projectForSession(actor)?.id === p.id)
|
|
402
|
+
return true;
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
directSupervisor(p, actor, taskId) {
|
|
406
|
+
if (!actor || actor === p.ownerConversationId || actor === p.initiatorConversationId)
|
|
407
|
+
return undefined;
|
|
408
|
+
const session = this.store.conversation(actor);
|
|
409
|
+
const parentId = session?.parentConversationId;
|
|
410
|
+
if (parentId && this.isMember(p, parentId))
|
|
411
|
+
return parentId;
|
|
412
|
+
const t = taskId
|
|
413
|
+
? p.tasks.find((item) => item.id === taskId)
|
|
414
|
+
: p.tasks.find((item) => item.ownerConversationId === actor || item.reviewerConversationId === actor);
|
|
415
|
+
const manager = t ? this.manager(p, t) : undefined;
|
|
416
|
+
if (manager && this.isMember(p, manager))
|
|
417
|
+
return manager;
|
|
418
|
+
return undefined;
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* THE PROJECT A SESSION BELONGS TO RIGHT NOW.
|
|
422
|
+
*
|
|
423
|
+
* `projectForSession` answers a different question - "which project is this session part of" -
|
|
424
|
+
* and it counts the finished projects too, because the history of a project has to stay visible
|
|
425
|
+
* to the people who worked on it. This method answers the narrower question the QUESTION path
|
|
426
|
+
* needs: "is this session inside a project that is still running".
|
|
427
|
+
*
|
|
428
|
+
* WHY THAT DISTINCTION MATTERS. A conversation that took part in a project which has since been
|
|
429
|
+
* completed is not a project member any more - it is a conversation that happens to have a
|
|
430
|
+
* finished project in its history. The old lookup mixed the two, so asking the Boss a question
|
|
431
|
+
* from such a conversation was refused with "a project member may not ask the Boss directly",
|
|
432
|
+
* which is not what was true: the project was over, and nobody was being bypassed. The finished
|
|
433
|
+
* project is still found and still visible in the history; it just does not block the question.
|
|
434
|
+
*
|
|
435
|
+
* A session that is in a running project AND in a finished one belongs to the running one - it
|
|
436
|
+
* is the one that is still in charge of it.
|
|
437
|
+
*/
|
|
438
|
+
liveProjectForSession(conversationId) {
|
|
439
|
+
let c = this.store.conversation(conversationId);
|
|
440
|
+
const seen = new Set();
|
|
441
|
+
while (c && !seen.has(c.id)) {
|
|
442
|
+
seen.add(c.id);
|
|
443
|
+
const projects = this.store.projects().filter((p) => p.workspaceId === c.workspaceId
|
|
444
|
+
&& (p.ownerConversationId === c.id || p.tasks.some((t) => t.ownerConversationId === c.id || t.reviewerConversationId === c.id)));
|
|
445
|
+
const live = projects.find((p) => !closed(p));
|
|
446
|
+
if (live)
|
|
447
|
+
return live;
|
|
448
|
+
c = c.parentConversationId ? this.store.conversation(c.parentConversationId) : undefined;
|
|
449
|
+
}
|
|
450
|
+
return undefined;
|
|
451
|
+
}
|
|
452
|
+
canAskQuestion(conversationId) {
|
|
453
|
+
const project = this.liveProjectForSession(conversationId);
|
|
454
|
+
if (!project)
|
|
455
|
+
return { ok: true };
|
|
456
|
+
if (conversationId === project.ownerConversationId || conversationId === project.initiatorConversationId) {
|
|
457
|
+
return { ok: true };
|
|
458
|
+
}
|
|
459
|
+
return {
|
|
460
|
+
ok: false,
|
|
461
|
+
reason: "项目成员不能直接向 Boss 提问:请通过直接上级上报,由负责人代为转述",
|
|
462
|
+
};
|
|
463
|
+
}
|
|
323
464
|
reconcile(p) {
|
|
324
465
|
if (closed(p))
|
|
325
466
|
return;
|
|
@@ -329,9 +470,15 @@ export class ProjectService {
|
|
|
329
470
|
t.updatedAt = stamp();
|
|
330
471
|
this.event(p, "ready", `前置交付已验收:${t.title}`, t.id);
|
|
331
472
|
}
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
|
|
473
|
+
const isLeader = (convId) => !convId || convId === p.ownerConversationId || convId === p.initiatorConversationId;
|
|
474
|
+
const openBoss = p.escalations.filter((e) => e.status === "open" && (e.audience === "boss" || !e.audience));
|
|
475
|
+
const hasLeaderBlockedTask = p.tasks.some((t) => t.status === "blocked" && isLeader(t.blockedReportedByConversationId));
|
|
476
|
+
const lastLeaderProjectReport = [...p.reports].reverse().find((r) => !r.taskId && isLeader(r.authorConversationId));
|
|
477
|
+
const leaderProjectBlocked = lastLeaderProjectReport?.kind === "blocked";
|
|
478
|
+
p.status = openBoss.some((e) => e.severity === "high") ? "awaiting_approval"
|
|
479
|
+
: openBoss.length || hasLeaderBlockedTask || leaderProjectBlocked ? "blocked"
|
|
480
|
+
: p.tasks.some((t) => t.status === "awaiting_review") ? "awaiting_review"
|
|
481
|
+
: p.tasks.length ? "active" : "planning";
|
|
335
482
|
}
|
|
336
483
|
resources(p) {
|
|
337
484
|
const state = this.store.snapshot();
|
|
@@ -385,7 +532,7 @@ export class ProjectService {
|
|
|
385
532
|
const scope = this.workspace(workspaceId);
|
|
386
533
|
return this.store.projects().filter((p) => p.workspaceId === scope).map((p) => this.projectView(p));
|
|
387
534
|
}
|
|
388
|
-
headline(p) {
|
|
535
|
+
headline(p, actor) {
|
|
389
536
|
const total = p.tasks.length;
|
|
390
537
|
const completed = p.tasks.filter((t) => t.status === "completed").length;
|
|
391
538
|
if (p.status === "completed") {
|
|
@@ -395,9 +542,10 @@ export class ProjectService {
|
|
|
395
542
|
return "项目已取消";
|
|
396
543
|
if (p.status === "failed")
|
|
397
544
|
return "项目失败";
|
|
398
|
-
const
|
|
545
|
+
const visibleEscalations = this.filterEscalations(p.escalations ?? [], actor);
|
|
546
|
+
const openEscalation = visibleEscalations.find((e) => e.status === "open");
|
|
399
547
|
if (openEscalation) {
|
|
400
|
-
return p.status === "awaiting_approval"
|
|
548
|
+
return (openEscalation.audience === "boss" || !openEscalation.audience) && p.status === "awaiting_approval"
|
|
401
549
|
? `等待审批:${openEscalation.reason}`
|
|
402
550
|
: `项目已阻塞:${openEscalation.reason}`;
|
|
403
551
|
}
|
|
@@ -424,26 +572,16 @@ export class ProjectService {
|
|
|
424
572
|
return "项目规划中,尚未拆解任务";
|
|
425
573
|
}
|
|
426
574
|
isActorRelated(p, actor) {
|
|
427
|
-
|
|
428
|
-
return false;
|
|
429
|
-
if (actor === p.initiatorConversationId)
|
|
430
|
-
return true;
|
|
431
|
-
if (actor === p.ownerConversationId)
|
|
432
|
-
return true;
|
|
433
|
-
if (p.tasks?.some((t) => t.ownerConversationId === actor || t.reviewerConversationId === actor))
|
|
434
|
-
return true;
|
|
435
|
-
if (this.projectForSession(actor)?.id === p.id)
|
|
436
|
-
return true;
|
|
437
|
-
return false;
|
|
575
|
+
return this.isMember(p, actor);
|
|
438
576
|
}
|
|
439
|
-
projectSummary(p) {
|
|
577
|
+
projectSummary(p, actor) {
|
|
440
578
|
const resources = this.resources(p);
|
|
441
579
|
return {
|
|
442
580
|
id: p.id,
|
|
443
581
|
name: p.title,
|
|
444
582
|
status: p.status,
|
|
445
583
|
mode: p.mode ?? "standard",
|
|
446
|
-
headline: this.headline(p),
|
|
584
|
+
headline: this.headline(p, actor),
|
|
447
585
|
tasks: {
|
|
448
586
|
total: p.tasks.length,
|
|
449
587
|
completed: p.tasks.filter((t) => t.status === "completed").length,
|
|
@@ -455,15 +593,32 @@ export class ProjectService {
|
|
|
455
593
|
updatedAt: p.updatedAt,
|
|
456
594
|
};
|
|
457
595
|
}
|
|
458
|
-
|
|
596
|
+
/**
|
|
597
|
+
* THE PROJECTS AN ACTOR IS PART OF, AND HOW MANY OF THEM THERE ARE.
|
|
598
|
+
*
|
|
599
|
+
* The list is capped, because the list is what goes into an agent's context: a workspace that
|
|
600
|
+
* has been alive for a year has hundreds of finished projects, and handing all of them over
|
|
601
|
+
* answers the question "how are my projects doing" with a wall of history. The cap is on the
|
|
602
|
+
* RECENT ones - the same order the list has always had, newest update first - so what the
|
|
603
|
+
* agent reads first is what actually changed.
|
|
604
|
+
*
|
|
605
|
+
* AND THE COUNT IS TOLD, NOT IMPLIED. `total` is how many projects the session is really
|
|
606
|
+
* related to, counted BEFORE the cut, and `returned` is how many came back. An agent that sees
|
|
607
|
+
* total 57 and returned 20 knows the workspace has more history than it was shown, and says so
|
|
608
|
+
* instead of telling the Boss the workspace only has 20 projects. A silently truncated list
|
|
609
|
+
* would be a lie the agent could not see through.
|
|
610
|
+
*/
|
|
611
|
+
listForActor(workspaceId, actor, limit = PROJECT_LIST_LIMIT) {
|
|
459
612
|
const scope = this.workspace(workspaceId);
|
|
460
|
-
|
|
613
|
+
const related = this.store.projects()
|
|
461
614
|
.filter((p) => p.workspaceId === scope && this.isActorRelated(p, actor))
|
|
462
|
-
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))
|
|
463
|
-
|
|
615
|
+
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
616
|
+
const total = related.length;
|
|
617
|
+
const projects = related.slice(0, limit).map((p) => this.projectSummary(p, actor));
|
|
618
|
+
return { projects, total, returned: projects.length };
|
|
464
619
|
}
|
|
465
|
-
get(id, workspaceId) {
|
|
466
|
-
return this.projectView(this.raw(id, workspaceId));
|
|
620
|
+
get(id, workspaceId, actor) {
|
|
621
|
+
return this.projectView(this.raw(id, workspaceId), actor);
|
|
467
622
|
}
|
|
468
623
|
/**
|
|
469
624
|
* THE PROJECT AS A REPORT A HUMAN CAN READ.
|
|
@@ -634,7 +789,7 @@ export class ProjectService {
|
|
|
634
789
|
}
|
|
635
790
|
admission(p, checkConcurrency = true, coordinator = false) {
|
|
636
791
|
this.live(p);
|
|
637
|
-
if (p.escalations.some((e) => e.status === "open" && e.severity === "high"))
|
|
792
|
+
if (p.escalations.some((e) => e.status === "open" && e.severity === "high" && (e.audience === "boss" || !e.audience)))
|
|
638
793
|
throw new Error("等待 Boss 处理升级决策后才能继续派发");
|
|
639
794
|
const r = this.resources(p);
|
|
640
795
|
if (p.budget?.maxTokens && r.tokens >= p.budget.maxTokens)
|
|
@@ -810,15 +965,16 @@ export class ProjectService {
|
|
|
810
965
|
t.status = "in_progress";
|
|
811
966
|
t.updatedAt = stamp();
|
|
812
967
|
delete t.blockedReason;
|
|
968
|
+
delete t.blockedReportedByConversationId;
|
|
813
969
|
this.event(p, "dispatched", `派发执行:${t.title}`, t.id, actor);
|
|
814
970
|
this.reconcile(p);
|
|
815
971
|
await this.store.setProject(p);
|
|
816
|
-
const delegation = t.role === "leader" ? `你是分支 Leader,负责这项任务的拆解、委派、集成交付。用 project add_task 时必须带 parentTaskId="${t.id}";可按必要性指定 role="leader" 或 "executor";add_task / update_task 时可以带 executorProfile 与 reviewerProfile(支持指定 agentId、cwd、model、thinkingLevel、watchdogTimeoutSeconds),按任务性质将执行与复核委派给最合适的员工,不指定则继承项目默认。用 project dispatch / request_review 管理直属子任务,子会话结束自动回报给你,不要轮询;并发满时结束本轮等待回报。所有必需子任务独立验收后才能 submit 聚合交付,上级还会独立复核你的集成结果。最多 3 层 Leader、8 个分支 Leader
|
|
972
|
+
const delegation = t.role === "leader" ? `你是分支 Leader,负责这项任务的拆解、委派、集成交付。用 project add_task 时必须带 parentTaskId="${t.id}";可按必要性指定 role="leader" 或 "executor";add_task / update_task 时可以带 executorProfile 与 reviewerProfile(支持指定 agentId、cwd、model、thinkingLevel、watchdogTimeoutSeconds),按任务性质将执行与复核委派给最合适的员工,不指定则继承项目默认。用 project dispatch / request_review 管理直属子任务,子会话结束自动回报给你,不要轮询;并发满时结束本轮等待回报。所有必需子任务独立验收后才能 submit 聚合交付,上级还会独立复核你的集成结果。最多 3 层 Leader、8 个分支 Leader;小任务不要继续拆层。你的下属的升级先到你这里;你决定是自己解决还是再向上级转述。` : "你是执行者,不得自行拆解子任务。";
|
|
817
973
|
/* THE LIGHTWEIGHT TASK SAYS WHAT IS DIFFERENT, because the difference is the point: there is
|
|
818
974
|
no reviewer coming back, so the evidence that the reviewer would have demanded is the only
|
|
819
975
|
thing that stands behind the delivery. */
|
|
820
976
|
const modeNote = lightweight(p) ? "本项目是轻量项目:你就是唯一的执行会话,不会有独立复核人回来,完成后调用 project(action=submit, …) 提交即完成,交付证据必须附上。" : "";
|
|
821
|
-
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你不是唯一执行者,遵守分工边界。${modeNote}完成后必须调用 project(action=submit, projectId="${p.id}", taskId="${t.id}", output="交付总结", evidenceRefs=["产物路径或验证记录"]),不能自行标记已完成。复核退回时修复后重新 submit。遇到阻塞用 report 或 escalate,保留证据、假设和未解决问题。不要自行创建其他执行会话或 task/workflow。`, { wait: false });
|
|
977
|
+
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你不是唯一执行者,遵守分工边界。${modeNote}完成后必须调用 project(action=submit, projectId="${p.id}", taskId="${t.id}", output="交付总结", evidenceRefs=["产物路径或验证记录"]),不能自行标记已完成。复核退回时修复后重新 submit。遇到阻塞用 report(阻塞) 或 escalate 上报给你的直接上级,不要直接找 Boss,保留证据、假设和未解决问题。不要自行创建其他执行会话或 task/workflow。`, { wait: false });
|
|
822
978
|
return ProjectService.taskView(t, p);
|
|
823
979
|
});
|
|
824
980
|
}
|
|
@@ -938,6 +1094,12 @@ export class ProjectService {
|
|
|
938
1094
|
if (patch.status === "ready" && actor && actor !== manager)
|
|
939
1095
|
throw new Error("仅直属 Leader 可以重新派发任务");
|
|
940
1096
|
t.status = patch.status;
|
|
1097
|
+
if (t.status === "blocked") {
|
|
1098
|
+
t.blockedReportedByConversationId = actor;
|
|
1099
|
+
}
|
|
1100
|
+
else {
|
|
1101
|
+
delete t.blockedReportedByConversationId;
|
|
1102
|
+
}
|
|
941
1103
|
}
|
|
942
1104
|
if (patch.executorProfile !== undefined) {
|
|
943
1105
|
if (t.ownerConversationId)
|
|
@@ -1137,7 +1299,10 @@ export class ProjectService {
|
|
|
1137
1299
|
return { ...raised, project };
|
|
1138
1300
|
}
|
|
1139
1301
|
async addReport(id, input, workspaceId, actor) {
|
|
1140
|
-
|
|
1302
|
+
let supervisorToWake;
|
|
1303
|
+
let taskForWake;
|
|
1304
|
+
let memberBlockedReport = false;
|
|
1305
|
+
const report = await this.change(id, workspaceId, (p) => {
|
|
1141
1306
|
this.live(p);
|
|
1142
1307
|
this.member(p, actor);
|
|
1143
1308
|
if (!["progress", "blocked", "delivery", "decision_request", "review"].includes(input.kind))
|
|
@@ -1151,31 +1316,85 @@ export class ProjectService {
|
|
|
1151
1316
|
progress: text(input.progress, "汇报进展"), variance: optional(input.variance, "偏差"), nextStep: optional(input.nextStep, "下一步"), risks: strings(input.risks, "风险"),
|
|
1152
1317
|
decisionNeeded: optional(input.decisionNeeded, "所需决策"), evidenceRefs: strings(input.evidenceRefs, "证据"), createdAt: stamp() };
|
|
1153
1318
|
p.reports.push(report);
|
|
1154
|
-
if (report.kind === "blocked"
|
|
1155
|
-
t
|
|
1156
|
-
|
|
1157
|
-
|
|
1319
|
+
if (report.kind === "blocked") {
|
|
1320
|
+
if (t) {
|
|
1321
|
+
t.status = "blocked";
|
|
1322
|
+
t.blockedReason = report.progress;
|
|
1323
|
+
t.blockedReportedByConversationId = actor;
|
|
1324
|
+
t.updatedAt = stamp();
|
|
1325
|
+
}
|
|
1326
|
+
if (actor && actor !== p.ownerConversationId && actor !== p.initiatorConversationId) {
|
|
1327
|
+
memberBlockedReport = true;
|
|
1328
|
+
supervisorToWake = this.directSupervisor(p, actor, t?.id);
|
|
1329
|
+
taskForWake = t;
|
|
1330
|
+
}
|
|
1158
1331
|
}
|
|
1159
1332
|
this.event(p, "report", report.progress, t?.id, actor);
|
|
1160
1333
|
this.reconcile(p);
|
|
1161
1334
|
return report;
|
|
1162
|
-
});
|
|
1335
|
+
}, actor);
|
|
1336
|
+
if (supervisorToWake) {
|
|
1337
|
+
const taskInfo = taskForWake ? `任务 ${taskForWake.id}(${taskForWake.title})` : "所属任务";
|
|
1338
|
+
await this.wake(supervisorToWake, `下级成员(会话:${actor})汇报阻塞:${taskInfo}\n原因:${report.progress}\n请评估阻塞并跟进解决,必要时向 Boss 转述或升级。`, "阻塞上报", id, workspaceId, actor);
|
|
1339
|
+
}
|
|
1340
|
+
else if (memberBlockedReport) {
|
|
1341
|
+
/* A MEMBER REPORTED A BLOCK AND NO SUPERVISOR COULD BE FOUND. The report is saved either
|
|
1342
|
+
* way, but nobody has been told, so the case is written down instead of vanishing. */
|
|
1343
|
+
const summary = "阻塞上报已保存,但找不到直接上级会话,上报没有被转述";
|
|
1344
|
+
console.warn(`Super Pi: 项目 ${id} 的${summary}`);
|
|
1345
|
+
await this.noteUnreachable(id, workspaceId, summary, actor);
|
|
1346
|
+
}
|
|
1347
|
+
return report;
|
|
1163
1348
|
}
|
|
1164
1349
|
async addEscalation(id, input, workspaceId, actor) {
|
|
1165
|
-
|
|
1350
|
+
let supervisorToWake;
|
|
1351
|
+
let wakeMemberRole;
|
|
1352
|
+
const result = await this.change(id, workspaceId, (p) => {
|
|
1166
1353
|
this.live(p);
|
|
1167
1354
|
this.member(p, actor);
|
|
1168
1355
|
if (!["low", "medium", "high"].includes(input.severity))
|
|
1169
1356
|
throw new Error("升级风险等级不合法");
|
|
1170
|
-
const t = input.taskId
|
|
1171
|
-
|
|
1357
|
+
const t = input.taskId
|
|
1358
|
+
? this.task(p, text(input.taskId, "任务 id"))
|
|
1359
|
+
: p.tasks.find((task) => task.ownerConversationId === actor || task.reviewerConversationId === actor);
|
|
1360
|
+
const isBossAudience = !actor || actor === p.ownerConversationId || actor === p.initiatorConversationId;
|
|
1361
|
+
const audience = isBossAudience ? "boss" : "supervisor";
|
|
1362
|
+
let targetConversationId;
|
|
1363
|
+
if (!isBossAudience) {
|
|
1364
|
+
targetConversationId = this.directSupervisor(p, actor, t?.id);
|
|
1365
|
+
if (!targetConversationId) {
|
|
1366
|
+
throw new Error("无法确定直接上级会话:上级不在项目执行团队内且无法定位所属任务 Leader,升级已拒绝。");
|
|
1367
|
+
}
|
|
1368
|
+
supervisorToWake = targetConversationId;
|
|
1369
|
+
wakeMemberRole = p.tasks.some((task) => task.role === "leader" && task.ownerConversationId === actor)
|
|
1370
|
+
? "分支 Leader"
|
|
1371
|
+
: p.tasks.some((task) => task.reviewerConversationId === actor)
|
|
1372
|
+
? "独立复核"
|
|
1373
|
+
: "执行者";
|
|
1374
|
+
}
|
|
1375
|
+
const e = {
|
|
1376
|
+
id: randomUUID(),
|
|
1377
|
+
projectId: id,
|
|
1378
|
+
taskId: t?.id,
|
|
1379
|
+
reason: text(input.reason, "升级原因"),
|
|
1380
|
+
severity: input.severity,
|
|
1381
|
+
status: "open",
|
|
1382
|
+
requestedByConversationId: actor,
|
|
1383
|
+
audience,
|
|
1384
|
+
...(targetConversationId ? { targetConversationId } : {}),
|
|
1385
|
+
createdAt: stamp(),
|
|
1386
|
+
};
|
|
1172
1387
|
p.escalations.push(e);
|
|
1173
1388
|
this.event(p, "escalated", e.reason, t?.id, actor);
|
|
1174
1389
|
this.reconcile(p);
|
|
1175
1390
|
return e;
|
|
1176
|
-
});
|
|
1391
|
+
}, actor);
|
|
1392
|
+
if (supervisorToWake) {
|
|
1393
|
+
await this.wake(supervisorToWake, `下级成员(会话:${actor},身份:${wakeMemberRole ?? "执行者"})发起例外升级(ID:${result.id}):\n原因:${result.reason}\n严重级别:${result.severity}\n请及时处理该例外(调用 project action="resolve_escalation" 记录处理意见),或按需向上级转述升级。`, `例外升级(ID:${result.id})`, id, workspaceId, actor);
|
|
1394
|
+
}
|
|
1395
|
+
return result;
|
|
1177
1396
|
}
|
|
1178
|
-
async resolveEscalation(id, escalationId, decision, status, workspaceId) {
|
|
1397
|
+
async resolveEscalation(id, escalationId, decision, status, workspaceId, actor) {
|
|
1179
1398
|
const result = await this.change(id, workspaceId, (p) => {
|
|
1180
1399
|
this.live(p);
|
|
1181
1400
|
if (status !== "resolved" && status !== "dismissed")
|
|
@@ -1185,33 +1404,54 @@ export class ProjectService {
|
|
|
1185
1404
|
throw new Error("升级事项不存在");
|
|
1186
1405
|
if (e.status !== "open")
|
|
1187
1406
|
throw new Error("升级事项已处理");
|
|
1407
|
+
const audience = e.audience ?? "boss";
|
|
1408
|
+
if (audience === "boss") {
|
|
1409
|
+
if (actor)
|
|
1410
|
+
throw new Error("此例外需由 Boss 决策,团队成员无权处理");
|
|
1411
|
+
}
|
|
1412
|
+
else if (audience === "supervisor") {
|
|
1413
|
+
if (!actor)
|
|
1414
|
+
throw new Error("该例外属于直接上级处理范围,Boss 不能直接处理 supervisor 级别的例外");
|
|
1415
|
+
if (actor !== e.targetConversationId)
|
|
1416
|
+
throw new Error("只有指定的直接上级可以处理此例外");
|
|
1417
|
+
e.resolvedByConversationId = actor;
|
|
1418
|
+
}
|
|
1188
1419
|
e.status = status;
|
|
1189
1420
|
e.decision = text(decision, "处理意见");
|
|
1190
1421
|
e.resolvedAt = stamp();
|
|
1191
|
-
|
|
1422
|
+
const decider = audience === "boss" ? "Boss" : "直接上级";
|
|
1423
|
+
this.event(p, "decision", `${decider}决策:${e.decision}`, e.taskId, actor);
|
|
1192
1424
|
this.reconcile(p);
|
|
1193
1425
|
return e;
|
|
1194
|
-
});
|
|
1195
|
-
const p = this.
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
let directLeaderId;
|
|
1200
|
-
if (targetTask) {
|
|
1201
|
-
if (targetTask.role === "leader" && targetTask.ownerConversationId) {
|
|
1202
|
-
directLeaderId = targetTask.ownerConversationId;
|
|
1426
|
+
}, actor);
|
|
1427
|
+
const p = this.raw(id, workspaceId);
|
|
1428
|
+
if (result.audience === "supervisor") {
|
|
1429
|
+
if (result.requestedByConversationId) {
|
|
1430
|
+
await this.wake(result.requestedByConversationId, `直接上级已处理例外(ID:${result.id}):${result.reason}\n处理意见:${result.decision}\n请按处理意见继续工作。`, `例外处理结果(ID:${result.id})`, id, workspaceId, actor);
|
|
1203
1431
|
}
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1432
|
+
}
|
|
1433
|
+
else {
|
|
1434
|
+
const targetTask = result.taskId
|
|
1435
|
+
? p.tasks.find((t) => t.id === result.taskId)
|
|
1436
|
+
: p.tasks.find((t) => t.ownerConversationId === result.requestedByConversationId || t.reviewerConversationId === result.requestedByConversationId);
|
|
1437
|
+
let directLeaderId;
|
|
1438
|
+
if (targetTask) {
|
|
1439
|
+
if (targetTask.role === "leader" && targetTask.ownerConversationId) {
|
|
1440
|
+
directLeaderId = targetTask.ownerConversationId;
|
|
1441
|
+
}
|
|
1442
|
+
else if (targetTask.parentTaskId) {
|
|
1443
|
+
const parent = p.tasks.find((t) => t.id === targetTask.parentTaskId);
|
|
1444
|
+
directLeaderId = parent?.ownerConversationId ?? targetTask.accountableConversationId;
|
|
1445
|
+
}
|
|
1446
|
+
else {
|
|
1447
|
+
directLeaderId = targetTask.accountableConversationId ?? p.ownerConversationId;
|
|
1448
|
+
}
|
|
1207
1449
|
}
|
|
1208
|
-
|
|
1209
|
-
|
|
1450
|
+
const recipientId = directLeaderId || p.ownerConversationId;
|
|
1451
|
+
if (recipientId) {
|
|
1452
|
+
await this.wake(recipientId, `Boss 已处理项目 ${p.id} 的例外:${result.reason}\n决策:${result.decision}\n先读取 project 最新状态,按决策继续范围内的工作。`, `例外处理结果(ID:${result.id})`, id, workspaceId, actor);
|
|
1210
1453
|
}
|
|
1211
1454
|
}
|
|
1212
|
-
const recipientId = directLeaderId || p.ownerConversationId;
|
|
1213
|
-
if (recipientId && this.orchestrator)
|
|
1214
|
-
await this.orchestrator.dispatch(recipientId, `Boss 已处理项目 ${p.id} 的例外:${result.reason}\n决策:${result.decision}\n先读取 project 最新状态,按决策继续范围内的工作。`, { wait: false });
|
|
1215
1455
|
return result;
|
|
1216
1456
|
}
|
|
1217
1457
|
async complete(id, summary, evidenceRefs, workspaceId, actor) {
|
|
@@ -1337,13 +1577,16 @@ export class ProjectService {
|
|
|
1337
1577
|
return this.get(project.id, workspaceId);
|
|
1338
1578
|
}
|
|
1339
1579
|
if (body.action === "list") {
|
|
1580
|
+
// THE LIST IS CAPPED, AND IT SAYS SO. `total` and `returned` travel with it, so an agent
|
|
1581
|
+
// that sees a short list can tell the difference between "the workspace has three projects"
|
|
1582
|
+
// and "the workspace has fifty and I was shown twenty".
|
|
1340
1583
|
return this.listForActor(workspaceId, actor);
|
|
1341
1584
|
}
|
|
1342
1585
|
const id = text(body.projectId, "projectId");
|
|
1343
|
-
const
|
|
1344
|
-
this.member(
|
|
1586
|
+
const rawProject = this.raw(id, workspaceId);
|
|
1587
|
+
this.member(rawProject, actor);
|
|
1345
1588
|
switch (body.action) {
|
|
1346
|
-
case "get": return
|
|
1589
|
+
case "get": return this.get(id, workspaceId, actor);
|
|
1347
1590
|
case "export": return this.exportReport(id, workspaceId);
|
|
1348
1591
|
case "start": return this.start(id, workspaceId, actor);
|
|
1349
1592
|
case "add_task": return this.addTask(id, body, workspaceId, actor);
|
|
@@ -1354,6 +1597,9 @@ export class ProjectService {
|
|
|
1354
1597
|
case "update_task": return this.updateTask(id, text(body.taskId, "taskId"), { ...body, workspaceId }, actor);
|
|
1355
1598
|
case "report": return this.addReport(id, body, workspaceId, actor);
|
|
1356
1599
|
case "escalate": return this.addEscalation(id, body, workspaceId, actor);
|
|
1600
|
+
case "resolve_escalation":
|
|
1601
|
+
case "resolveEscalation":
|
|
1602
|
+
return this.resolveEscalation(id, text(body.escalationId, "escalationId"), text(body.decision, "decision"), body.status ?? "resolved", workspaceId, actor);
|
|
1357
1603
|
case "complete": return this.complete(id, body.summary, body.evidenceRefs, workspaceId, actor);
|
|
1358
1604
|
case "cancel": return this.cancel(id, body, workspaceId, actor);
|
|
1359
1605
|
default: throw new Error("未知的项目工具操作");
|
|
@@ -2099,6 +2099,13 @@ export function createHttpServer(options) {
|
|
|
2099
2099
|
response.writeHead(403, { "content-type": "application/json" }).end(JSON.stringify({ error: verdict.message }));
|
|
2100
2100
|
return;
|
|
2101
2101
|
}
|
|
2102
|
+
if (context.projectService) {
|
|
2103
|
+
const check = context.projectService.canAskQuestion(body.conversationId);
|
|
2104
|
+
if (!check.ok) {
|
|
2105
|
+
response.writeHead(403, { "content-type": "application/json; charset=utf-8" }).end(JSON.stringify({ error: check.reason }));
|
|
2106
|
+
return;
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2102
2109
|
const result = await runService.askQuestion(body.conversationId);
|
|
2103
2110
|
response.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify(result));
|
|
2104
2111
|
}
|
package/dist/server/types.d.ts
CHANGED
|
@@ -949,6 +949,7 @@ export type ProjectTask = {
|
|
|
949
949
|
priority: "low" | "medium" | "high" | "urgent";
|
|
950
950
|
dueAt?: string;
|
|
951
951
|
blockedReason?: string;
|
|
952
|
+
blockedReportedByConversationId?: string;
|
|
952
953
|
output?: string;
|
|
953
954
|
evidenceRefs?: string[];
|
|
954
955
|
/** The acceptance of this task. `lightweight: true` means the executor accepted its own
|
|
@@ -989,6 +990,9 @@ export type ProjectEscalation = {
|
|
|
989
990
|
decision?: string;
|
|
990
991
|
createdAt: string;
|
|
991
992
|
resolvedAt?: string;
|
|
993
|
+
audience?: "supervisor" | "boss";
|
|
994
|
+
targetConversationId?: string;
|
|
995
|
+
resolvedByConversationId?: string;
|
|
992
996
|
};
|
|
993
997
|
export type ProjectEvent = {
|
|
994
998
|
id: string;
|