@evandu/super-pi 0.1.156 → 0.1.157
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/agents/super-pi-task/index.ts +25 -3
- package/dist/client/.super-pi-client-sources.json +1 -1
- package/dist/server/application/project-service.d.ts +20 -1
- package/dist/server/application/project-service.js +120 -7
- package/dist/server/types.d.ts +3 -0
- package/package.json +1 -1
|
@@ -1135,7 +1135,7 @@ export async function executeScheduledTask(
|
|
|
1135
1135
|
export const PROJECT_TOOL_NAME = "project";
|
|
1136
1136
|
export const PROJECT_ENDPOINT_ENV = "SUPER_PI_PROJECT_ENDPOINT";
|
|
1137
1137
|
|
|
1138
|
-
export const PROJECT_DESCRIPTION = `Manage a user-triggered project through accountable delegation, evidence and independent review. Read the latest state with get. The project leader uses add_task (title, objective, deliverables, acceptanceCriteria, dependencies, role=leader/executor, optional executorProfile/reviewerProfile with agentId, cwd, model, thinkingLevel, watchdogTimeoutSeconds), dispatch and request_review. A branch leader creates children with parentTaskId set to its own task ID and manages only its direct tasks. Dependencies must be siblings. Leader aggregate submission requires independently accepted children. Keep at most 3 leader levels and 8 branch leaders; small projects need only one executor. Workers submit output plus evidenceRefs. Only the assigned independent reviewer can review with verdict accepted or changes_requested, summary and evidenceRefs. update_task marks blocked/failed with blockedReason, updates executorProfile/reviewerProfile before child sessions spawn, or the leader resets ready for retry. report records kind=progress/blocked, progress, variance, nextStep, risks, decisionNeeded and evidenceRefs. escalate records reason and severity=low/medium/high for Boss decisions. complete requires all tasks independently accepted and a final summary with evidenceRefs. Project and task IDs come from get/add_task. Reads never invoke models. Dispatch is asynchronous and consumes budget; stop polling when capacity is full and wait for child completion reports. Do not use session_spawn, session_dispatch, task or workflow to bypass the project dispatch guard. Never claim success on an error.`;
|
|
1138
|
+
export const PROJECT_DESCRIPTION = `Manage a user-triggered project through accountable delegation, evidence and independent review. When the user describes a project in natural language, decompose it yourself into title, objective, deliverables, acceptanceCriteria and budget, then call create with those fields and the top-level tasks you derived. Create also accepts optional constraints, risk, budget with maxTokens/maxConcurrent, and default profiles defaultLeaderProfile/defaultExecutorProfile/defaultReviewerProfile. The tasks array carries that decomposition: each entry needs a title and an objective, and its dependencies name the titles of sibling tasks in the same array. The project starts on action="start" - call it only after you have registered the top-level tasks, because the leader session begins decomposing the work the moment it starts. You are the initiator: you may create the project, register top-level tasks and start it; you may not register child tasks, change the budget or cancel the scope. Only a root conversation may create a project. Top-level tasks can also be registered later with add_task. Read the latest state with get. The project leader uses add_task (title, objective, deliverables, acceptanceCriteria, dependencies, role=leader/executor, optional executorProfile/reviewerProfile with agentId, cwd, model, thinkingLevel, watchdogTimeoutSeconds), dispatch and request_review. A branch leader creates children with parentTaskId set to its own task ID and manages only its direct tasks. Dependencies must be siblings. Leader aggregate submission requires independently accepted children. Keep at most 3 leader levels and 8 branch leaders; small projects need only one executor. Workers submit output plus evidenceRefs. Only the assigned independent reviewer can review with verdict accepted or changes_requested, summary and evidenceRefs. update_task marks blocked/failed with blockedReason, updates executorProfile/reviewerProfile before child sessions spawn, or the leader resets ready for retry. report records kind=progress/blocked, progress, variance, nextStep, risks, decisionNeeded and evidenceRefs. escalate records reason and severity=low/medium/high for Boss decisions. complete requires all tasks independently accepted and a final summary with evidenceRefs. Project and task IDs come from create/get/add_task. Reads never invoke models. Dispatch is asynchronous and consumes budget; stop polling when capacity is full and wait for child completion reports. Do not use session_spawn, session_dispatch, task or workflow to bypass the project dispatch guard. Never claim success on an error.`;
|
|
1139
1139
|
|
|
1140
1140
|
export function readProjectConfig(env: Record<string, string | undefined>) {
|
|
1141
1141
|
return { endpoint: env[PROJECT_ENDPOINT_ENV] ?? "", conversationId: env[TASK_CONVERSATION_ENV] ?? "", workspaceId: env.SUPER_PI_ORCHESTRATOR_WORKSPACE ?? "", capability: env[TASK_CAPABILITY_ENV] };
|
|
@@ -305,15 +305,37 @@ const extension = (pi: ExtensionApi): void => {
|
|
|
305
305
|
pi.registerTool({
|
|
306
306
|
name: PROJECT_TOOL_NAME, label: "Project", description: PROJECT_DESCRIPTION,
|
|
307
307
|
parameters: Type.Object({
|
|
308
|
-
action: Type.Union(["get", "add_task", "dispatch", "submit", "request_review", "review", "update_task", "report", "escalate", "complete"].map((value) => Type.Literal(value))),
|
|
309
|
-
projectId: Type.String(), taskId: Type.Optional(Type.String()),
|
|
308
|
+
action: Type.Union(["create", "start", "get", "add_task", "dispatch", "submit", "request_review", "review", "update_task", "report", "escalate", "complete"].map((value) => Type.Literal(value))),
|
|
309
|
+
projectId: Type.Optional(Type.String()), taskId: Type.Optional(Type.String()),
|
|
310
310
|
parentTaskId: Type.Optional(Type.String()), role: Type.Optional(Type.Union([Type.Literal("leader"), Type.Literal("executor")])),
|
|
311
311
|
title: Type.Optional(Type.String()), objective: Type.Optional(Type.String()),
|
|
312
|
-
deliverables: Type.Optional(Type.Array(Type.String())), acceptanceCriteria: Type.Optional(Type.Array(Type.String())), dependencies: Type.Optional(Type.Array(Type.String())),
|
|
312
|
+
deliverables: Type.Optional(Type.Array(Type.String())), acceptanceCriteria: Type.Optional(Type.Array(Type.String())), constraints: Type.Optional(Type.Array(Type.String())), dependencies: Type.Optional(Type.Array(Type.String())),
|
|
313
|
+
risk: Type.Optional(Type.String()),
|
|
314
|
+
budget: Type.Optional(Type.Object({ maxTokens: Type.Optional(Type.Number()), maxConcurrent: Type.Optional(Type.Number()) })),
|
|
313
315
|
priority: Type.Optional(Type.String()), status: Type.Optional(Type.String()), blockedReason: Type.Optional(Type.String()),
|
|
316
|
+
tasks: Type.Optional(Type.Array(Type.Object({
|
|
317
|
+
title: Type.String(), objective: Type.String(),
|
|
318
|
+
deliverables: Type.Optional(Type.Array(Type.String())), acceptanceCriteria: Type.Optional(Type.Array(Type.String())),
|
|
319
|
+
dependencies: Type.Optional(Type.Array(Type.String())), priority: Type.Optional(Type.String()),
|
|
320
|
+
}))),
|
|
314
321
|
output: Type.Optional(Type.String()), evidenceRefs: Type.Optional(Type.Array(Type.String())), verdict: Type.Optional(Type.String()), summary: Type.Optional(Type.String()),
|
|
315
322
|
kind: Type.Optional(Type.String()), progress: Type.Optional(Type.String()), variance: Type.Optional(Type.String()), nextStep: Type.Optional(Type.String()),
|
|
316
323
|
risks: Type.Optional(Type.Array(Type.String())), decisionNeeded: Type.Optional(Type.String()), reason: Type.Optional(Type.String()), severity: Type.Optional(Type.String()),
|
|
324
|
+
defaultLeaderProfile: Type.Optional(Type.Object({
|
|
325
|
+
agentId: Type.Optional(Type.String()), cwd: Type.Optional(Type.String()),
|
|
326
|
+
model: Type.Optional(Type.Object({ provider: Type.String(), modelId: Type.String() })),
|
|
327
|
+
thinkingLevel: Type.Optional(Type.String()), watchdogTimeoutSeconds: Type.Optional(Type.Number()),
|
|
328
|
+
})),
|
|
329
|
+
defaultExecutorProfile: Type.Optional(Type.Object({
|
|
330
|
+
agentId: Type.Optional(Type.String()), cwd: Type.Optional(Type.String()),
|
|
331
|
+
model: Type.Optional(Type.Object({ provider: Type.String(), modelId: Type.String() })),
|
|
332
|
+
thinkingLevel: Type.Optional(Type.String()), watchdogTimeoutSeconds: Type.Optional(Type.Number()),
|
|
333
|
+
})),
|
|
334
|
+
defaultReviewerProfile: Type.Optional(Type.Object({
|
|
335
|
+
agentId: Type.Optional(Type.String()), cwd: Type.Optional(Type.String()),
|
|
336
|
+
model: Type.Optional(Type.Object({ provider: Type.String(), modelId: Type.String() })),
|
|
337
|
+
thinkingLevel: Type.Optional(Type.String()), watchdogTimeoutSeconds: Type.Optional(Type.Number()),
|
|
338
|
+
})),
|
|
317
339
|
executorProfile: Type.Optional(Type.Object({
|
|
318
340
|
agentId: Type.Optional(Type.String()), cwd: Type.Optional(Type.String()),
|
|
319
341
|
model: Type.Optional(Type.Object({ provider: Type.String(), modelId: Type.String() })),
|
|
@@ -39,5 +39,5 @@
|
|
|
39
39
|
"src/client/types.ts": "b4d24bfabf230e29b984cf698aeb579c19797139a2b358f1a4e61cafce02964d",
|
|
40
40
|
"src/client/vite-env.d.ts": "65996936fbb042915f7b74a200fcdde7e410f32a669b1ab9597cfaa4b0faddb5"
|
|
41
41
|
},
|
|
42
|
-
"recordedAt": "2026-11-
|
|
42
|
+
"recordedAt": "2026-11-04T06:54:50.867Z"
|
|
43
43
|
}
|
|
@@ -19,6 +19,7 @@ export type CreateProjectInput = {
|
|
|
19
19
|
defaultLeaderProfile?: unknown;
|
|
20
20
|
defaultExecutorProfile?: unknown;
|
|
21
21
|
defaultReviewerProfile?: unknown;
|
|
22
|
+
initiatorConversationId?: string;
|
|
22
23
|
};
|
|
23
24
|
export type AddTaskInput = Partial<ProjectTask> & {
|
|
24
25
|
title: string;
|
|
@@ -26,6 +27,15 @@ export type AddTaskInput = Partial<ProjectTask> & {
|
|
|
26
27
|
executorProfile?: unknown;
|
|
27
28
|
reviewerProfile?: unknown;
|
|
28
29
|
};
|
|
30
|
+
/** A top-level task an initiator hands over with create, before the service gives it an ID. */
|
|
31
|
+
export type TopLevelTaskPlan = {
|
|
32
|
+
title: string;
|
|
33
|
+
objective: string;
|
|
34
|
+
deliverables?: string[];
|
|
35
|
+
acceptanceCriteria?: string[];
|
|
36
|
+
priority?: ProjectTask["priority"];
|
|
37
|
+
dependencies: string[];
|
|
38
|
+
};
|
|
29
39
|
/** Persist explicit domain fields only; transport credentials never enter project records. */
|
|
30
40
|
export declare class ProjectService {
|
|
31
41
|
private readonly store;
|
|
@@ -40,6 +50,7 @@ export declare class ProjectService {
|
|
|
40
50
|
private change;
|
|
41
51
|
private live;
|
|
42
52
|
private leader;
|
|
53
|
+
private topLevelAuthor;
|
|
43
54
|
private manager;
|
|
44
55
|
private requiredComplete;
|
|
45
56
|
private task;
|
|
@@ -49,7 +60,7 @@ export declare class ProjectService {
|
|
|
49
60
|
list(workspaceId?: string): Project[];
|
|
50
61
|
get(id: string, workspaceId?: string): Project;
|
|
51
62
|
projectForSession(conversationId: string): Project | undefined;
|
|
52
|
-
create(input: CreateProjectInput): Promise<Project>;
|
|
63
|
+
create(input: CreateProjectInput, initiatorConversationId?: string): Promise<Project>;
|
|
53
64
|
private admission;
|
|
54
65
|
private context;
|
|
55
66
|
start(id: string, workspaceId: string, actor?: string): Promise<Project>;
|
|
@@ -77,5 +88,13 @@ export declare class ProjectService {
|
|
|
77
88
|
addEscalation(id: string, input: Pick<ProjectEscalation, "reason" | "severity" | "taskId">, workspaceId?: string, actor?: string): Promise<ProjectEscalation>;
|
|
78
89
|
resolveEscalation(id: string, escalationId: string, decision: string, status: "resolved" | "dismissed", workspaceId?: string): Promise<ProjectEscalation>;
|
|
79
90
|
complete(id: string, summary: string, evidenceRefs: unknown, workspaceId: string, actor?: string): Promise<Project>;
|
|
91
|
+
/**
|
|
92
|
+
* The tasks an initiator hands over with create. A dependency names the TITLE of another task in
|
|
93
|
+
* the same batch, because the batch carries no task IDs yet - the service assigns them while it
|
|
94
|
+
* registers the batch. The plan comes back in dependency order, so the plain add_task rules
|
|
95
|
+
* (title, objective, priority, sibling dependencies) can be reused unchanged.
|
|
96
|
+
*/
|
|
97
|
+
private planTopLevelTasks;
|
|
98
|
+
private registerTopLevelTasks;
|
|
80
99
|
tool(body: Record<string, any>, workspaceId: string, actor: string): Promise<unknown>;
|
|
81
100
|
}
|
|
@@ -177,6 +177,15 @@ export class ProjectService {
|
|
|
177
177
|
throw new Error("项目已结束,不能继续修改或派发"); }
|
|
178
178
|
leader(p, actor) { if (actor && actor !== p.ownerConversationId)
|
|
179
179
|
throw new Error("仅项目负责人可以执行此操作"); }
|
|
180
|
+
topLevelAuthor(p, actor) {
|
|
181
|
+
if (!actor)
|
|
182
|
+
return;
|
|
183
|
+
if (actor === p.ownerConversationId)
|
|
184
|
+
return;
|
|
185
|
+
if (actor === p.initiatorConversationId)
|
|
186
|
+
return;
|
|
187
|
+
throw new Error("只有项目负责人或发起人可以登记顶层任务");
|
|
188
|
+
}
|
|
180
189
|
manager(p, t, actor) {
|
|
181
190
|
const manager = t.parentTaskId ? this.task(p, t.parentTaskId).ownerConversationId : p.ownerConversationId;
|
|
182
191
|
if (actor && actor !== manager)
|
|
@@ -194,7 +203,7 @@ export class ProjectService {
|
|
|
194
203
|
return t;
|
|
195
204
|
}
|
|
196
205
|
member(p, actor) {
|
|
197
|
-
if (actor && actor !== p.ownerConversationId && !p.tasks.some((t) => t.ownerConversationId === actor || t.reviewerConversationId === actor))
|
|
206
|
+
if (actor && actor !== p.ownerConversationId && actor !== p.initiatorConversationId && !p.tasks.some((t) => t.ownerConversationId === actor || t.reviewerConversationId === actor))
|
|
198
207
|
throw new Error("调用会话不属于项目执行团队");
|
|
199
208
|
}
|
|
200
209
|
reconcile(p) {
|
|
@@ -250,7 +259,7 @@ export class ProjectService {
|
|
|
250
259
|
}
|
|
251
260
|
return undefined;
|
|
252
261
|
}
|
|
253
|
-
async create(input) {
|
|
262
|
+
async create(input, initiatorConversationId) {
|
|
254
263
|
const workspaceId = this.workspace(input.workspaceId);
|
|
255
264
|
if (input.ownerConversationId !== undefined)
|
|
256
265
|
throw new Error("项目负责人由启动操作创建独立会话");
|
|
@@ -258,12 +267,16 @@ export class ProjectService {
|
|
|
258
267
|
throw new Error("项目风险等级不合法");
|
|
259
268
|
if (input.budget !== undefined && (!input.budget || typeof input.budget !== "object" || Array.isArray(input.budget)))
|
|
260
269
|
throw new Error("资源预算不合法");
|
|
261
|
-
for (const [key, value] of Object.entries(input.budget ?? {}))
|
|
270
|
+
for (const [key, value] of Object.entries(input.budget ?? {})) {
|
|
271
|
+
if (value === undefined)
|
|
272
|
+
continue;
|
|
262
273
|
if (!["maxTokens", "maxConcurrent"].includes(key) || !Number.isSafeInteger(value) || value <= 0)
|
|
263
274
|
throw new Error("资源预算必须是正整数");
|
|
275
|
+
}
|
|
264
276
|
const defaultLeaderProfile = profile(input.defaultLeaderProfile, "默认 Leader 配置");
|
|
265
277
|
const defaultExecutorProfile = profile(input.defaultExecutorProfile, "默认执行配置");
|
|
266
278
|
const defaultReviewerProfile = profile(input.defaultReviewerProfile, "默认复核配置");
|
|
279
|
+
const initiator = (initiatorConversationId ?? (typeof input.initiatorConversationId === "string" ? input.initiatorConversationId : undefined))?.trim() || undefined;
|
|
267
280
|
const now = stamp();
|
|
268
281
|
const p = { id: `project_${randomUUID()}`, workspaceId, title: text(input.title, "项目标题"), objective: text(input.objective, "项目目标"),
|
|
269
282
|
deliverables: strings(input.deliverables, "交付物"), acceptanceCriteria: strings(input.acceptanceCriteria, "验收标准"), constraints: strings(input.constraints, "约束"),
|
|
@@ -271,8 +284,14 @@ export class ProjectService {
|
|
|
271
284
|
...(defaultLeaderProfile ? { defaultLeaderProfile } : {}),
|
|
272
285
|
...(defaultExecutorProfile ? { defaultExecutorProfile } : {}),
|
|
273
286
|
...(defaultReviewerProfile ? { defaultReviewerProfile } : {}),
|
|
287
|
+
...(initiator ? { initiatorConversationId: initiator } : {}),
|
|
274
288
|
tasks: [], reports: [], escalations: [], events: [], createdAt: now, updatedAt: now };
|
|
275
|
-
|
|
289
|
+
if (initiator) {
|
|
290
|
+
this.event(p, "created", "Agent 触发项目", undefined, initiator);
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
this.event(p, "created", "Boss 触发项目");
|
|
294
|
+
}
|
|
276
295
|
const saved = await this.store.setProject(p);
|
|
277
296
|
return this.projectView(saved);
|
|
278
297
|
}
|
|
@@ -303,7 +322,7 @@ export class ProjectService {
|
|
|
303
322
|
}
|
|
304
323
|
async start(id, workspaceId, actor) {
|
|
305
324
|
return this.change(id, workspaceId, async (p) => {
|
|
306
|
-
this.
|
|
325
|
+
this.topLevelAuthor(p, actor);
|
|
307
326
|
this.admission(p, false);
|
|
308
327
|
if (!this.orchestrator)
|
|
309
328
|
throw new Error("会话编排服务未就绪");
|
|
@@ -334,7 +353,9 @@ export class ProjectService {
|
|
|
334
353
|
return this.change(id, workspaceId, (p) => {
|
|
335
354
|
this.live(p);
|
|
336
355
|
const parentTaskId = optional(input.parentTaskId, "父任务 id");
|
|
337
|
-
|
|
356
|
+
// A top-level task is accountable to whoever owns the project: the leader once it runs, and
|
|
357
|
+
// before that the initiator who registered it - a task nobody answers for cannot be closed.
|
|
358
|
+
let manager = p.ownerConversationId ?? p.initiatorConversationId;
|
|
338
359
|
if (parentTaskId) {
|
|
339
360
|
const parent = this.task(p, parentTaskId);
|
|
340
361
|
if (parent.role !== "leader" || parent.status !== "in_progress" || !parent.ownerConversationId)
|
|
@@ -352,7 +373,7 @@ export class ProjectService {
|
|
|
352
373
|
throw new Error("Leader 最多 3 层,请改为执行任务");
|
|
353
374
|
}
|
|
354
375
|
else
|
|
355
|
-
this.
|
|
376
|
+
this.topLevelAuthor(p, actor);
|
|
356
377
|
if (input.role !== undefined && input.role !== "leader" && input.role !== "executor")
|
|
357
378
|
throw new Error("任务角色必须是 leader 或 executor");
|
|
358
379
|
const dependencies = strings(input.dependencies, "任务依赖");
|
|
@@ -723,12 +744,104 @@ export class ProjectService {
|
|
|
723
744
|
return p;
|
|
724
745
|
});
|
|
725
746
|
}
|
|
747
|
+
/**
|
|
748
|
+
* The tasks an initiator hands over with create. A dependency names the TITLE of another task in
|
|
749
|
+
* the same batch, because the batch carries no task IDs yet - the service assigns them while it
|
|
750
|
+
* registers the batch. The plan comes back in dependency order, so the plain add_task rules
|
|
751
|
+
* (title, objective, priority, sibling dependencies) can be reused unchanged.
|
|
752
|
+
*/
|
|
753
|
+
planTopLevelTasks(batch) {
|
|
754
|
+
if (batch === undefined || batch === null)
|
|
755
|
+
return [];
|
|
756
|
+
if (!Array.isArray(batch))
|
|
757
|
+
throw new Error("tasks 必须是任务数组");
|
|
758
|
+
if (batch.length > 100)
|
|
759
|
+
throw new Error("项目最多 100 个任务,请收敛分工范围");
|
|
760
|
+
const items = batch.map((raw) => {
|
|
761
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
762
|
+
throw new Error("tasks 的每一项必须是任务对象");
|
|
763
|
+
const record = raw;
|
|
764
|
+
const priority = record.priority;
|
|
765
|
+
if (priority !== undefined && !["low", "medium", "high", "urgent"].includes(String(priority)))
|
|
766
|
+
throw new Error("任务优先级不合法");
|
|
767
|
+
return {
|
|
768
|
+
title: text(record.title, "任务标题"),
|
|
769
|
+
objective: text(record.objective, "任务目标"),
|
|
770
|
+
deliverables: record.deliverables === undefined ? undefined : strings(record.deliverables, "任务交付物"),
|
|
771
|
+
acceptanceCriteria: record.acceptanceCriteria === undefined ? undefined : strings(record.acceptanceCriteria, "任务验收标准"),
|
|
772
|
+
priority: priority,
|
|
773
|
+
dependencies: record.dependencies === undefined ? [] : strings(record.dependencies, "任务依赖"),
|
|
774
|
+
};
|
|
775
|
+
});
|
|
776
|
+
const index = new Map();
|
|
777
|
+
items.forEach((item, at) => {
|
|
778
|
+
if (index.has(item.title))
|
|
779
|
+
throw new Error(`任务标题重复:${item.title}`);
|
|
780
|
+
index.set(item.title, at);
|
|
781
|
+
});
|
|
782
|
+
const edges = items.map((item) => item.dependencies.map((dependency) => {
|
|
783
|
+
const at = index.get(dependency);
|
|
784
|
+
if (at === undefined)
|
|
785
|
+
throw new Error(`任务依赖不存在:${dependency}`);
|
|
786
|
+
return at;
|
|
787
|
+
}));
|
|
788
|
+
const ordered = [];
|
|
789
|
+
const done = new Set();
|
|
790
|
+
while (ordered.length < items.length) {
|
|
791
|
+
let progressed = false;
|
|
792
|
+
for (let at = 0; at < items.length; at += 1) {
|
|
793
|
+
if (done.has(at))
|
|
794
|
+
continue;
|
|
795
|
+
if (!edges[at].every((dependency) => done.has(dependency)))
|
|
796
|
+
continue;
|
|
797
|
+
ordered.push(items[at]);
|
|
798
|
+
done.add(at);
|
|
799
|
+
progressed = true;
|
|
800
|
+
}
|
|
801
|
+
if (!progressed)
|
|
802
|
+
throw new Error("任务依赖存在循环,无法登记顶层任务");
|
|
803
|
+
}
|
|
804
|
+
return ordered;
|
|
805
|
+
}
|
|
806
|
+
async registerTopLevelTasks(projectId, plan, workspaceId, actor) {
|
|
807
|
+
const ids = new Map();
|
|
808
|
+
for (const item of plan) {
|
|
809
|
+
const dependencies = item.dependencies.map((dependency) => ids.get(dependency));
|
|
810
|
+
const task = await this.addTask(projectId, {
|
|
811
|
+
title: item.title, objective: item.objective,
|
|
812
|
+
...(item.deliverables ? { deliverables: item.deliverables } : {}),
|
|
813
|
+
...(item.acceptanceCriteria ? { acceptanceCriteria: item.acceptanceCriteria } : {}),
|
|
814
|
+
...(item.priority !== undefined ? { priority: item.priority } : {}),
|
|
815
|
+
dependencies,
|
|
816
|
+
}, workspaceId, actor);
|
|
817
|
+
ids.set(item.title, task.id);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
726
820
|
async tool(body, workspaceId, actor) {
|
|
821
|
+
if (body.action === "create") {
|
|
822
|
+
// ONLY A ROOT CONVERSATION MAY OPEN A PROJECT. Every dispatched session - the leader, its
|
|
823
|
+
// reviewers, the workers - holds the same tool, and a project opened from one of them would
|
|
824
|
+
// carry its own budget and concurrency guards while sitting outside the guard that dispatched it.
|
|
825
|
+
const conversation = this.store.conversation(actor);
|
|
826
|
+
if (!conversation)
|
|
827
|
+
throw new Error("找不到这个会话。");
|
|
828
|
+
if (conversation.parentConversationId)
|
|
829
|
+
throw new Error("只有主对话可以创建项目:派发出来的会话不能另起项目,否则会绕过项目的预算与并发护栏。");
|
|
830
|
+
const running = this.store.projects().filter((p) => p.initiatorConversationId === actor && !closed(p));
|
|
831
|
+
if (running.length >= 3)
|
|
832
|
+
throw new Error("你同时进行的项目超过上限(3 个),先完成或停止一个再新建。");
|
|
833
|
+
const { tasks: batch, ...input } = body;
|
|
834
|
+
const plan = this.planTopLevelTasks(batch);
|
|
835
|
+
const project = await this.create({ ...input, workspaceId }, actor);
|
|
836
|
+
await this.registerTopLevelTasks(project.id, plan, workspaceId, actor);
|
|
837
|
+
return this.get(project.id, workspaceId);
|
|
838
|
+
}
|
|
727
839
|
const id = text(body.projectId, "projectId");
|
|
728
840
|
const p = this.get(id, workspaceId);
|
|
729
841
|
this.member(p, actor);
|
|
730
842
|
switch (body.action) {
|
|
731
843
|
case "get": return p;
|
|
844
|
+
case "start": return this.start(id, workspaceId, actor);
|
|
732
845
|
case "add_task": return this.addTask(id, body, workspaceId, actor);
|
|
733
846
|
case "dispatch": return this.dispatchTask(id, text(body.taskId, "taskId"), workspaceId, actor);
|
|
734
847
|
case "submit": return this.submit(id, text(body.taskId, "taskId"), body, workspaceId, actor);
|
package/dist/server/types.d.ts
CHANGED
|
@@ -996,6 +996,9 @@ export type Project = {
|
|
|
996
996
|
risk: ProjectRisk;
|
|
997
997
|
status: ProjectStatus;
|
|
998
998
|
ownerConversationId?: string;
|
|
999
|
+
/** The session that created the project - the Boss's own conversation, which may register the
|
|
1000
|
+
* top-level tasks and is the one the project answers to. */
|
|
1001
|
+
initiatorConversationId?: string;
|
|
999
1002
|
budget?: {
|
|
1000
1003
|
maxTokens?: number;
|
|
1001
1004
|
maxConcurrent?: number;
|