@evandu/super-pi 0.1.144 → 0.1.146
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 +15 -2
- package/agents/super-pi-task/index.ts +3 -0
- package/dist/.super-pi-check-inputs.json +2 -2
- package/dist/client/.super-pi-client-sources.json +9 -9
- package/dist/client/assets/{CodeMirrorEditor-DfQHX1Hy.js → CodeMirrorEditor-CEwBCVM6.js} +2 -2
- package/dist/client/assets/{dist-Cu3B0JQX.js → dist-76ArZ5M4.js} +1 -1
- package/dist/client/assets/{dist-BEbEv2KM.js → dist-9F1wL-T5.js} +1 -1
- package/dist/client/assets/{dist-BRjyQs_L.js → dist-B0RXZeXQ.js} +1 -1
- package/dist/client/assets/{dist-SzZEAk24.js → dist-BCNaDgNW.js} +1 -1
- package/dist/client/assets/{dist-qAUj_2Ue.js → dist-BXBSIuTD.js} +1 -1
- package/dist/client/assets/{dist-BGBinEie.js → dist-Bh8Q7Y_n.js} +1 -1
- package/dist/client/assets/{dist-DKkMk3XX.js → dist-BoyICDlL.js} +1 -1
- package/dist/client/assets/{dist-ISoxXlmW.js → dist-BuR5Iagd.js} +1 -1
- package/dist/client/assets/{dist-LRNzDUVb.js → dist-Bx6ZWYUW.js} +1 -1
- package/dist/client/assets/{dist-DOvzruAQ.js → dist-CAOcjKAQ.js} +1 -1
- package/dist/client/assets/{dist-CDtFD-PW.js → dist-CHt8PX8R.js} +1 -1
- package/dist/client/assets/{dist-BuPimjY-.js → dist-CSCUT1PQ.js} +1 -1
- package/dist/client/assets/{dist-BsMikSTM.js → dist-CUB_Chdh.js} +1 -1
- package/dist/client/assets/{dist-KQJCVwxy.js → dist-CbVf2Cjn.js} +1 -1
- package/dist/client/assets/{dist-Dnru8_uX.js → dist-DBUqwiWG.js} +1 -1
- package/dist/client/assets/{dist-B8oH6dsc.js → dist-DC1IocrS.js} +1 -1
- package/dist/client/assets/{dist-D7aAMRTb.js → dist-DFMtP3wG.js} +1 -1
- package/dist/client/assets/{dist-Cw_KCDH9.js → dist-DN0GlnnZ.js} +1 -1
- package/dist/client/assets/{dist-BCLhQXYY.js → dist-DftD4ibj.js} +1 -1
- package/dist/client/assets/{dist-CgQ7zaWY.js → dist-Do52MRwm.js} +1 -1
- package/dist/client/assets/{dist-BS-FBUKW.js → dist-JvQIIdqB.js} +1 -1
- package/dist/client/assets/{dist-C5BQsmB7.js → dist-oLq6HXLG.js} +1 -1
- package/dist/client/assets/{index-Xaui0uUI.css → index-D-OxlUxN.css} +1 -1
- package/dist/client/assets/index-E7yaLa2C.js +102 -0
- package/dist/client/index.html +2 -2
- package/dist/server/agents.d.ts +1 -1
- package/dist/server/agents.js +3 -1
- package/dist/server/application/conversation-service.d.ts +1 -1
- package/dist/server/application/conversation-service.js +6 -4
- package/dist/server/application/run-service.d.ts +16 -2
- package/dist/server/application/run-service.js +121 -14
- package/dist/server/application/scheduled-task-service.d.ts +14 -1
- package/dist/server/application/scheduled-task-service.js +27 -2
- package/dist/server/application/session-orchestrator-service.d.ts +11 -2
- package/dist/server/application/session-orchestrator-service.js +103 -27
- package/dist/server/command-handler.js +1 -1
- package/dist/server/conversation-usage.d.ts +19 -0
- package/dist/server/conversation-usage.js +40 -0
- package/dist/server/file-diff.d.ts +3 -0
- package/dist/server/file-diff.js +4 -4
- package/dist/server/file-index.d.ts +4 -2
- package/dist/server/file-index.js +14 -9
- package/dist/server/file-name-index.d.ts +5 -2
- package/dist/server/file-name-index.js +22 -5
- package/dist/server/git.d.ts +3 -3
- package/dist/server/git.js +10 -9
- package/dist/server/http-server.js +195 -33
- package/dist/server/model-context-window.d.ts +46 -0
- package/dist/server/model-context-window.js +121 -0
- package/dist/server/pi-session.d.ts +6 -0
- package/dist/server/pi-session.js +15 -1
- package/dist/server/runtime.d.ts +3 -0
- package/dist/server/runtime.js +21 -6
- package/dist/server/session-cwd.d.ts +17 -0
- package/dist/server/session-cwd.js +43 -0
- package/dist/server/session-ledger.d.ts +7 -0
- package/dist/server/store.d.ts +22 -0
- package/dist/server/store.js +52 -0
- package/dist/server/task-tool.d.ts +2 -1
- package/dist/server/task-tool.js +3 -1
- package/dist/server/terminal.d.ts +1 -0
- package/dist/server/terminal.js +5 -3
- package/dist/server/turn-failure.js +16 -1
- package/dist/server/types.d.ts +18 -0
- package/dist/server/version.d.ts +25 -2
- package/dist/server/version.js +98 -26
- package/dist/server/ws-server.d.ts +1 -1
- package/dist/server/ws-server.js +7 -5
- package/package.json +20 -3
- package/dist/client/assets/index-BLWr70ej.js +0 -100
package/dist/client/index.html
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
<link rel="alternate icon" href="/favicon.ico" />
|
|
9
9
|
<link rel="apple-touch-icon" href="/favicon.svg" />
|
|
10
10
|
<title>Super Pi</title>
|
|
11
|
-
<script type="module" crossorigin src="/assets/index-
|
|
12
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
11
|
+
<script type="module" crossorigin src="/assets/index-E7yaLa2C.js"></script>
|
|
12
|
+
<link rel="stylesheet" crossorigin href="/assets/index-D-OxlUxN.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body>
|
|
15
15
|
<div id="root"></div>
|
package/dist/server/agents.d.ts
CHANGED
|
@@ -38,7 +38,7 @@ type Frontmatter = Record<string, unknown>;
|
|
|
38
38
|
*/
|
|
39
39
|
export declare const PI_TOOL_NAMES: readonly ["read", "bash", "powershell", "edit", "write", "grep", "find", "ls", "codemode", "tool_search", "list_mcp_resources", "list_mcp_resource_templates", "read_mcp_resource"];
|
|
40
40
|
/** Tools shipped by this package. Authors must be able to explicitly deny host extras. */
|
|
41
|
-
export declare const SUPER_PI_TOOL_NAMES: readonly ["task", "todowrite", "workflow", "run_async", "scheduled_task", "question", "tool_register", "memory", "skill_manage", "session_search", "knowledge_search", "knowledge_import", "document_read", "document_write", "web_search", "web_fetch", "feishu_doc_read", "feishu_doc_write"];
|
|
41
|
+
export declare const SUPER_PI_TOOL_NAMES: readonly ["task", "todowrite", "workflow", "run_async", "scheduled_task", "question", "tool_register", "memory", "skill_manage", "session_search", "session_spawn", "session_status", "session_dispatch", "session_stop", "knowledge_search", "knowledge_import", "document_read", "document_write", "web_search", "web_fetch", "feishu_doc_read", "feishu_doc_write"];
|
|
42
42
|
/**
|
|
43
43
|
* Whether a name can be a real Pi tool. A name is real if it is one of `PI_TOOL_NAMES`, an MCP
|
|
44
44
|
* server tool, or a PATTERN.
|
package/dist/server/agents.js
CHANGED
|
@@ -39,7 +39,9 @@ export const PI_TOOL_NAMES = [
|
|
|
39
39
|
/** Tools shipped by this package. Authors must be able to explicitly deny host extras. */
|
|
40
40
|
export const SUPER_PI_TOOL_NAMES = [
|
|
41
41
|
"task", "todowrite", "workflow", "run_async", "scheduled_task", "question", "tool_register",
|
|
42
|
-
"memory", "skill_manage", "session_search",
|
|
42
|
+
"memory", "skill_manage", "session_search",
|
|
43
|
+
"session_spawn", "session_status", "session_dispatch", "session_stop",
|
|
44
|
+
"knowledge_search", "knowledge_import",
|
|
43
45
|
"document_read", "document_write", "web_search", "web_fetch", "feishu_doc_read", "feishu_doc_write",
|
|
44
46
|
];
|
|
45
47
|
const KNOWN_TOOL_NAMES = [...PI_TOOL_NAMES, ...SUPER_PI_TOOL_NAMES];
|
|
@@ -48,7 +48,7 @@ export declare class ConversationService {
|
|
|
48
48
|
createConversation(workspaceId: string, parentage?: {
|
|
49
49
|
parentConversationId?: string;
|
|
50
50
|
origin?: "user" | "orchestrator";
|
|
51
|
-
}): Promise<string>;
|
|
51
|
+
}, cwd?: string): Promise<string>;
|
|
52
52
|
/**
|
|
53
53
|
* Name a conversation from what was said in it. Returns the title; the CALLER applies it, because the
|
|
54
54
|
* rename is a second command with its own optimistic path and its own failure toast, and a service that
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { assertModelAllowed } from "../model-filter.js";
|
|
2
2
|
import { newConversationId } from "../store.js";
|
|
3
|
+
import { normalizeSessionCwd, resolveSessionCwd } from "../session-cwd.js";
|
|
3
4
|
function isMissingPrimaryAgent(error, agentId) {
|
|
4
5
|
const message = error instanceof Error ? error.message : String(error);
|
|
5
6
|
const id = JSON.stringify(agentId);
|
|
@@ -50,7 +51,7 @@ export class ConversationService {
|
|
|
50
51
|
* button and every other caller create a top-level conversation by omission, which is the same
|
|
51
52
|
* "absence means top level" rule the pre-tree state.json files read under.
|
|
52
53
|
*/
|
|
53
|
-
async createConversation(workspaceId, parentage) {
|
|
54
|
+
async createConversation(workspaceId, parentage, cwd) {
|
|
54
55
|
const workspace = this.store.workspace(workspaceId);
|
|
55
56
|
if (!workspace)
|
|
56
57
|
throw new Error("找不到这个工作区。");
|
|
@@ -126,7 +127,8 @@ export class ConversationService {
|
|
|
126
127
|
}
|
|
127
128
|
// `null` is intentional here: Store uses it to remove a workspace value that it
|
|
128
129
|
// would otherwise have copied before applying the resolved configuration.
|
|
129
|
-
const
|
|
130
|
+
const sessionCwd = await normalizeSessionCwd(workspace.path, cwd);
|
|
131
|
+
const defaults = { ...resolved, agentId: resolved.agentId ?? null, model: resolved.model ?? null, thinkingLevel: resolved.thinkingLevel ?? null, ...(sessionCwd ? { cwd: sessionCwd } : {}), ...(modelNotice ? { modelNotice } : {}), ...(agentNotice ? { agentNotice } : {}), ...(parentage ? { parentConversationId: parentage.parentConversationId, origin: parentage.origin } : {}) };
|
|
130
132
|
const created = await this.store.createConversation(workspaceId, defaults);
|
|
131
133
|
this.broadcaster.broadcastState();
|
|
132
134
|
return created.id;
|
|
@@ -422,7 +424,7 @@ export class ConversationService {
|
|
|
422
424
|
}
|
|
423
425
|
const outcome = await this.runtime.revert({
|
|
424
426
|
conversation,
|
|
425
|
-
cwd: workspace.path,
|
|
427
|
+
cwd: await resolveSessionCwd(workspace.path, conversation.cwd),
|
|
426
428
|
boundaryEntryId: plan.boundaryEntryId,
|
|
427
429
|
beforeBoundary: withdraw && Boolean(conversation.messages.find(item => item.id === messageId)?.piEntryId),
|
|
428
430
|
});
|
|
@@ -480,7 +482,7 @@ export class ConversationService {
|
|
|
480
482
|
}
|
|
481
483
|
try {
|
|
482
484
|
const forkedId = newConversationId();
|
|
483
|
-
const outcome = await this.runtime.fork({ conversation, cwd: workspace.path, forkedConversationId: forkedId });
|
|
485
|
+
const outcome = await this.runtime.fork({ conversation, cwd: await resolveSessionCwd(workspace.path, conversation.cwd), forkedConversationId: forkedId });
|
|
484
486
|
const forked = await this.store.forkConversation(conversationId, forkedId);
|
|
485
487
|
console.log(`Super Pi: forked conversation ${conversation.id} into ${forked.id}: ` +
|
|
486
488
|
`${forked.messages.length} message(s) copied in state.json` +
|
|
@@ -236,10 +236,10 @@ export declare class RunService {
|
|
|
236
236
|
/**
|
|
237
237
|
* Resolves conversation and workspace working directory for subagent dispatch.
|
|
238
238
|
*/
|
|
239
|
-
dispatchContext(conversationId: string): {
|
|
239
|
+
dispatchContext(conversationId: string): Promise<{
|
|
240
240
|
conversation: Conversation;
|
|
241
241
|
cwd: string;
|
|
242
|
-
}
|
|
242
|
+
}>;
|
|
243
243
|
/**
|
|
244
244
|
* Checks whether a conversation has an active turn running.
|
|
245
245
|
*/
|
|
@@ -384,6 +384,20 @@ export declare class RunService {
|
|
|
384
384
|
/**
|
|
385
385
|
* Enqueues or starts execution for a user message.
|
|
386
386
|
*/
|
|
387
|
+
/**
|
|
388
|
+
* STARTS A TURN FROM A SENDER THAT IS NOT THE READER.
|
|
389
|
+
*
|
|
390
|
+
* `enqueueAndRun` below is the person's path: it writes the words under the reader's own name, in
|
|
391
|
+
* the reader's bubble, so the reader can see that they said it. The orchestrator's path is the
|
|
392
|
+
* same machinery with a different speaker - a primary agent handing a task to a child session -
|
|
393
|
+
* and writing THAT under the reader's name was the bug the reader reported: the child's
|
|
394
|
+
* transcript showed an instruction they never typed, wearing their avatar.
|
|
395
|
+
*
|
|
396
|
+
* So the write goes through `addAgentMessage` instead, which records the words as the system's,
|
|
397
|
+
* and the turn runs on them exactly as before. `composePrompt` is what tells the model who is
|
|
398
|
+
* speaking, so the model is addressed in the same words.
|
|
399
|
+
*/
|
|
400
|
+
enqueueAndRunAsAgent(conversationId: string, content: string): Promise<void>;
|
|
387
401
|
enqueueAndRun(conversationId: string, content: string, images?: MessageImage[], attachments?: import("../types.js").MessageAttachments, clientId?: string): Promise<void>;
|
|
388
402
|
/**
|
|
389
403
|
* Steers a queued message into the in-flight turn.
|
|
@@ -4,11 +4,12 @@ import { newSubagentRunId } from "../subagents.js";
|
|
|
4
4
|
import { readAgents } from "../agents.js";
|
|
5
5
|
import { verifyCapability } from "../capability.js";
|
|
6
6
|
import { capText, validateWorkflowPlan, workflowSummary } from "../workflow.js";
|
|
7
|
-
import { ensureNameIndex } from "../file-name-index.js";
|
|
7
|
+
import { ensureNameIndex, recordWorkspacePath } from "../file-name-index.js";
|
|
8
8
|
import { formatTurnFailure } from "../turn-failure.js";
|
|
9
9
|
import { describeThrownWithStack } from "../failure-text.js";
|
|
10
10
|
import { toWorkspaceRelative } from "../files.js";
|
|
11
11
|
import { refreshWorkspaceIndex } from "../file-index.js";
|
|
12
|
+
import { resolveSessionCwd } from "../session-cwd.js";
|
|
12
13
|
import { execFile } from "node:child_process";
|
|
13
14
|
import { promisify } from "node:util";
|
|
14
15
|
import { randomUUID } from "node:crypto";
|
|
@@ -56,10 +57,32 @@ async function expandAtReferences(store, workspace, content) {
|
|
|
56
57
|
* The envelope and the prose are NOT redundant: the envelope is what the reader sees in the transcript
|
|
57
58
|
* column, what they cancel or reorder; the prose is what leaves the server for Pi.
|
|
58
59
|
*/
|
|
60
|
+
/**
|
|
61
|
+
* THE TEXT THE MODEL IS HANDED WHEN A 后台回传 BECOMES ITS TURN.
|
|
62
|
+
*
|
|
63
|
+
* Pi's SDK has ONE entry point for a new turn - `session.prompt(text)` - and every text sent
|
|
64
|
+
* through it is a USER MESSAGE in Pi's transcript. There is no "the agent said this" channel to
|
|
65
|
+
* use. So a delivered result has to travel as prose, and the only question left is what that prose
|
|
66
|
+
* SAYS.
|
|
67
|
+
*
|
|
68
|
+
* IT USED TO SAY `[后台任务「...」已完成]`, which reads as a thing the PERSON said - a bracketed
|
|
69
|
+
* label, then the body, with nothing saying who is speaking. The model therefore answered it as a
|
|
70
|
+
* user would be answered ("已收到:[后台任务「...」]"), and the reader saw their own result coming
|
|
71
|
+
* back in the user's voice, inside the user's own turn. The delivery had become a quotation.
|
|
72
|
+
*
|
|
73
|
+
* So the prose now says WHO IS SPEAKING and that this is information, not an instruction:
|
|
74
|
+
* 「子会话「...」已完成,结果如下」. The model is told what happened and what it received, and it
|
|
75
|
+
* answers about THAT - which is what a primary agent should do with a child's result.
|
|
76
|
+
*/
|
|
59
77
|
function composePrompt(message) {
|
|
60
78
|
if (message.role !== "tool")
|
|
61
79
|
return message.content;
|
|
62
80
|
const content = message.content;
|
|
81
|
+
// A DISPATCHED ORDER IS NOT A DELIVERED RESULT. The two both travel as a `tool` message, and the
|
|
82
|
+
// envelope tells them apart: a result carries `<task ...>`, an order does not.
|
|
83
|
+
if (!/<task id="[^"]+" state="[^"]+"/.test(content)) {
|
|
84
|
+
return `上级智能体下发的任务:\n\n${content}`;
|
|
85
|
+
}
|
|
63
86
|
const summaryMatch = /<summary>([\s\S]*?)<\/summary>/.exec(content);
|
|
64
87
|
const summary = summaryMatch ? summaryMatch[1].trim() : "后台任务";
|
|
65
88
|
const stateMatch = /<task id="[^"]+" state="([^"]+)"/.exec(content);
|
|
@@ -74,7 +97,10 @@ function composePrompt(message) {
|
|
|
74
97
|
const bodyMatch = /<task_(?:result|error)>\n?([\s\S]*)\n?<\/(?:task_result|task_error)>/.exec(content);
|
|
75
98
|
const body = bodyMatch ? bodyMatch[1] : content;
|
|
76
99
|
const stateLabel = state === "completed" ? "已完成" : state === "error" ? "失败" : state;
|
|
77
|
-
|
|
100
|
+
// THE SPEAKER IS NAMED, AND IT IS NOT THE READER. A line that starts 「[」 looks like a note the
|
|
101
|
+
// person pasted in; a line that starts 「子会话」 or 「后台任务」 says the SYSTEM is reporting.
|
|
102
|
+
const speaker = /kind="session"/.test(content) ? "子会话" : "后台任务";
|
|
103
|
+
return `${speaker}「${summary}」${stateLabel},结果如下:\n\n${body}`;
|
|
78
104
|
}
|
|
79
105
|
/**
|
|
80
106
|
* Service managing execution of conversation turns, message queueing,
|
|
@@ -563,9 +589,9 @@ export class RunService {
|
|
|
563
589
|
/**
|
|
564
590
|
* Resolves conversation and workspace working directory for subagent dispatch.
|
|
565
591
|
*/
|
|
566
|
-
dispatchContext(conversationId) {
|
|
592
|
+
async dispatchContext(conversationId) {
|
|
567
593
|
const { conversation, workspace } = this.turnContext(conversationId);
|
|
568
|
-
return { conversation, cwd: workspace.path };
|
|
594
|
+
return { conversation, cwd: await resolveSessionCwd(workspace.path, conversation.cwd) };
|
|
569
595
|
}
|
|
570
596
|
/**
|
|
571
597
|
* Checks whether a conversation has an active turn running.
|
|
@@ -1205,13 +1231,15 @@ export class RunService {
|
|
|
1205
1231
|
// does not exist.
|
|
1206
1232
|
const relative = toWorkspaceRelative(workspace.path, path);
|
|
1207
1233
|
tracker.record(workspace.id, relative, { root: workspace.path, conversationId: conversation.id });
|
|
1208
|
-
refreshWorkspaceIndex(this.fileIndexes, workspace.id, relative);
|
|
1234
|
+
refreshWorkspaceIndex(this.fileIndexes, workspace.id, relative, workspace.path);
|
|
1209
1235
|
// The name index learns about the write as well, so the file the agent just created is
|
|
1210
1236
|
// findable by name straight away rather than after the next background scan. It goes through
|
|
1211
1237
|
// the SAME `ensureNameIndex` the HTTP layer uses, so there is one index per workspace rather
|
|
1212
1238
|
// than one per layer.
|
|
1213
|
-
if (this.nameIndexes)
|
|
1214
|
-
ensureNameIndex(this.nameIndexes, workspace.id, workspace.path)
|
|
1239
|
+
if (this.nameIndexes) {
|
|
1240
|
+
ensureNameIndex(this.nameIndexes, workspace.id, workspace.path);
|
|
1241
|
+
recordWorkspacePath(this.nameIndexes, workspace.id, workspace.path, relative);
|
|
1242
|
+
}
|
|
1215
1243
|
if (toolCallId)
|
|
1216
1244
|
fileWritePaths.set(toolCallId, relative);
|
|
1217
1245
|
this.broadcastFileChanges(workspace.id);
|
|
@@ -1294,9 +1322,11 @@ export class RunService {
|
|
|
1294
1322
|
const writtenPath = call.id ? fileWritePaths.get(call.id) : undefined;
|
|
1295
1323
|
if (writtenPath) {
|
|
1296
1324
|
this.fileChanges?.confirm(workspace.id, writtenPath, workspace.path);
|
|
1297
|
-
refreshWorkspaceIndex(this.fileIndexes, workspace.id, writtenPath);
|
|
1298
|
-
if (this.nameIndexes)
|
|
1299
|
-
ensureNameIndex(this.nameIndexes, workspace.id, workspace.path)
|
|
1325
|
+
refreshWorkspaceIndex(this.fileIndexes, workspace.id, writtenPath, workspace.path);
|
|
1326
|
+
if (this.nameIndexes) {
|
|
1327
|
+
ensureNameIndex(this.nameIndexes, workspace.id, workspace.path);
|
|
1328
|
+
recordWorkspacePath(this.nameIndexes, workspace.id, workspace.path, writtenPath);
|
|
1329
|
+
}
|
|
1300
1330
|
if (call.id)
|
|
1301
1331
|
fileWritePaths.delete(call.id);
|
|
1302
1332
|
this.broadcastFileChanges(workspace.id);
|
|
@@ -1377,7 +1407,7 @@ export class RunService {
|
|
|
1377
1407
|
conversation,
|
|
1378
1408
|
prompt,
|
|
1379
1409
|
signal: controller.signal,
|
|
1380
|
-
cwd: workspace.path,
|
|
1410
|
+
cwd: await resolveSessionCwd(workspace.path, conversation.cwd),
|
|
1381
1411
|
host: {
|
|
1382
1412
|
notify: (request) => { if (acceptsEvent("ui.notify"))
|
|
1383
1413
|
uiHost.notify(request); },
|
|
@@ -1394,6 +1424,10 @@ export class RunService {
|
|
|
1394
1424
|
if (acceptsEvent("transcript.file", true))
|
|
1395
1425
|
await this.store.setConversationPiSessionFile(conversation.id, path);
|
|
1396
1426
|
},
|
|
1427
|
+
onUsage: async (readout) => {
|
|
1428
|
+
if (acceptsEvent("usage", true))
|
|
1429
|
+
await this.store.recordConversationUsage(conversation.id, readout);
|
|
1430
|
+
},
|
|
1397
1431
|
onToolCall,
|
|
1398
1432
|
onFileWrite,
|
|
1399
1433
|
onToolPreparing,
|
|
@@ -1608,6 +1642,79 @@ export class RunService {
|
|
|
1608
1642
|
/**
|
|
1609
1643
|
* Enqueues or starts execution for a user message.
|
|
1610
1644
|
*/
|
|
1645
|
+
/**
|
|
1646
|
+
* STARTS A TURN FROM A SENDER THAT IS NOT THE READER.
|
|
1647
|
+
*
|
|
1648
|
+
* `enqueueAndRun` below is the person's path: it writes the words under the reader's own name, in
|
|
1649
|
+
* the reader's bubble, so the reader can see that they said it. The orchestrator's path is the
|
|
1650
|
+
* same machinery with a different speaker - a primary agent handing a task to a child session -
|
|
1651
|
+
* and writing THAT under the reader's name was the bug the reader reported: the child's
|
|
1652
|
+
* transcript showed an instruction they never typed, wearing their avatar.
|
|
1653
|
+
*
|
|
1654
|
+
* So the write goes through `addAgentMessage` instead, which records the words as the system's,
|
|
1655
|
+
* and the turn runs on them exactly as before. `composePrompt` is what tells the model who is
|
|
1656
|
+
* speaking, so the model is addressed in the same words.
|
|
1657
|
+
*/
|
|
1658
|
+
async enqueueAndRunAsAgent(conversationId, content) {
|
|
1659
|
+
this.assertHistoryAvailable(conversationId);
|
|
1660
|
+
this.memory?.cancel(conversationId);
|
|
1661
|
+
const trimmed = content.trim();
|
|
1662
|
+
if (!trimmed)
|
|
1663
|
+
return;
|
|
1664
|
+
const { conversation, workspace } = this.turnContext(conversationId);
|
|
1665
|
+
const active = this.activeRuns.get(conversation.id);
|
|
1666
|
+
if (active?.signal.aborted)
|
|
1667
|
+
await this.awaitRunSlotReleased(conversation.id, false);
|
|
1668
|
+
this.assertHistoryAvailable(conversationId);
|
|
1669
|
+
const busy = this.activeRuns.has(conversation.id) || this.interruptTargets.has(conversation.id);
|
|
1670
|
+
const reservedController = busy ? undefined : new AbortController();
|
|
1671
|
+
if (reservedController) {
|
|
1672
|
+
this.cancelledQuestionConversations.delete(conversation.id);
|
|
1673
|
+
this.claimRunSlot(conversation.id, reservedController);
|
|
1674
|
+
}
|
|
1675
|
+
let message;
|
|
1676
|
+
try {
|
|
1677
|
+
message = await this.store.addAgentMessage(conversationId, trimmed, { ...(busy ? { delivery: "waiting" } : {}) });
|
|
1678
|
+
}
|
|
1679
|
+
catch (error) {
|
|
1680
|
+
if (reservedController)
|
|
1681
|
+
this.releaseRunSlot(conversation.id, reservedController);
|
|
1682
|
+
throw error;
|
|
1683
|
+
}
|
|
1684
|
+
this.broadcaster.broadcastState();
|
|
1685
|
+
if (!reservedController) {
|
|
1686
|
+
const waiting = this.store.listWaitingMessages(conversationId).find((item) => item.id === message.id && item.delivery === "waiting");
|
|
1687
|
+
if (!waiting)
|
|
1688
|
+
return;
|
|
1689
|
+
if (!this.activeRuns.has(conversationId) && !this.interruptTargets.has(conversationId))
|
|
1690
|
+
await this.processWaitingMessage(conversationId, message.id);
|
|
1691
|
+
else
|
|
1692
|
+
await this.submitToPiQueue(conversationId, message);
|
|
1693
|
+
return;
|
|
1694
|
+
}
|
|
1695
|
+
const controller = reservedController;
|
|
1696
|
+
try {
|
|
1697
|
+
if (controller.signal.aborted) {
|
|
1698
|
+
await this.store.setStatus(conversation.id, "stopped");
|
|
1699
|
+
return;
|
|
1700
|
+
}
|
|
1701
|
+
await this.runUntilDrained(conversation, workspace, composePrompt(message), controller, message.id);
|
|
1702
|
+
}
|
|
1703
|
+
catch (error) {
|
|
1704
|
+
await this.store.setStatus(conversation.id, controller.signal.aborted ? "stopped" : "failed");
|
|
1705
|
+
if (controller.signal.aborted)
|
|
1706
|
+
return;
|
|
1707
|
+
await this.store.setFailureReason(conversationId, formatTurnFailure(error));
|
|
1708
|
+
console.error("Super Pi agent-dispatched turn failed:", describeThrownWithStack(error));
|
|
1709
|
+
throw error;
|
|
1710
|
+
}
|
|
1711
|
+
finally {
|
|
1712
|
+
this.releaseRunSlot(conversation.id, controller);
|
|
1713
|
+
if (!this.activeRuns.has(conversation.id))
|
|
1714
|
+
this.extensionUiService.cancelPendingUi(conversation.id, "expired");
|
|
1715
|
+
this.broadcaster.broadcastState();
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1611
1718
|
async enqueueAndRun(conversationId, content, images, attachments, clientId) {
|
|
1612
1719
|
this.assertHistoryAvailable(conversationId);
|
|
1613
1720
|
/**
|
|
@@ -2004,7 +2111,7 @@ export class RunService {
|
|
|
2004
2111
|
const task = params.task.trim();
|
|
2005
2112
|
if (!task)
|
|
2006
2113
|
throw new Error("子 agent 的任务不能为空");
|
|
2007
|
-
const { conversation, cwd } = this.dispatchContext(params.conversationId);
|
|
2114
|
+
const { conversation, cwd } = await this.dispatchContext(params.conversationId);
|
|
2008
2115
|
void this.trackDetached(this.subagents.dispatch({
|
|
2009
2116
|
conversationId: conversation.id,
|
|
2010
2117
|
agentId: params.agentId,
|
|
@@ -2039,7 +2146,7 @@ export class RunService {
|
|
|
2039
2146
|
if (body.depth !== undefined)
|
|
2040
2147
|
checkSubagentDepth(body.depth);
|
|
2041
2148
|
const signal = this.activeRuns.get(conversationId)?.signal;
|
|
2042
|
-
const { conversation, cwd } = this.dispatchContext(conversationId);
|
|
2149
|
+
const { conversation, cwd } = await this.dispatchContext(conversationId);
|
|
2043
2150
|
const parent = body.parentRunId ? this.store.snapshot().subagentRuns.find((run) => run.id === body.parentRunId) : undefined;
|
|
2044
2151
|
if (body.parentRunId && (!parent || parent.conversationId !== conversation.id)) {
|
|
2045
2152
|
throw new Error("Invalid parentRunId: dispatch ancestry is not recorded for this conversation");
|
|
@@ -2173,7 +2280,7 @@ export class RunService {
|
|
|
2173
2280
|
const capability = this.verifyPrimaryCapability(conversationId, body.capability);
|
|
2174
2281
|
if (!capability.ok)
|
|
2175
2282
|
throw new Error(capability.message);
|
|
2176
|
-
const { conversation, cwd } = this.dispatchContext(conversationId);
|
|
2283
|
+
const { conversation, cwd } = await this.dispatchContext(conversationId);
|
|
2177
2284
|
const signal = this.activeRuns.get(conversationId)?.signal;
|
|
2178
2285
|
// Validated against the roster THIS CALL reads from disk, for the same freshness reason the task tool
|
|
2179
2286
|
// builds its roster per turn: a sub-agent added while the server is running is dispatchable on the next
|
|
@@ -98,7 +98,20 @@ export declare class ScheduledTaskService {
|
|
|
98
98
|
update(id: string, input: ScheduledTaskInput): Promise<ScheduledTask>;
|
|
99
99
|
/** Turns a task on or off. */
|
|
100
100
|
setEnabled(id: string, enabled: boolean): Promise<ScheduledTask>;
|
|
101
|
-
/**
|
|
101
|
+
/**
|
|
102
|
+
* Removes a task. Its conversation is ARCHIVED, not deleted.
|
|
103
|
+
*
|
|
104
|
+
* THE ANSWERS ARE WORTH KEEPING, so the session is not thrown away - and archiving is the keeping
|
|
105
|
+
* that can be undone: the session moves into the sidebar's 已归档 group and can be restored from
|
|
106
|
+
* there. The old behaviour was to leave it in the LIVE list, and that was the defect: a 「定时:xxx」
|
|
107
|
+
* session with no task behind it is an orphan the reader cannot explain (there is no delete-session
|
|
108
|
+
* control at all) and cannot get rid of.
|
|
109
|
+
*
|
|
110
|
+
* THE DELETE ITSELF IS NEVER REFUSED. The task is removed first and the archive is best-effort: a
|
|
111
|
+
* conversation that is still running cannot be archived, and a reader who deletes a task must not be
|
|
112
|
+
* told "no" because a session happens to be busy. The session simply stays where it is, and the
|
|
113
|
+
* reason is in the server log rather than in the reader's face.
|
|
114
|
+
*/
|
|
102
115
|
remove(id: string): Promise<void>;
|
|
103
116
|
/**
|
|
104
117
|
* Runs a task NOW, once.
|
|
@@ -186,10 +186,35 @@ export class ScheduledTaskService {
|
|
|
186
186
|
return task;
|
|
187
187
|
return this.update(id, { enabled });
|
|
188
188
|
}
|
|
189
|
-
/**
|
|
189
|
+
/**
|
|
190
|
+
* Removes a task. Its conversation is ARCHIVED, not deleted.
|
|
191
|
+
*
|
|
192
|
+
* THE ANSWERS ARE WORTH KEEPING, so the session is not thrown away - and archiving is the keeping
|
|
193
|
+
* that can be undone: the session moves into the sidebar's 已归档 group and can be restored from
|
|
194
|
+
* there. The old behaviour was to leave it in the LIVE list, and that was the defect: a 「定时:xxx」
|
|
195
|
+
* session with no task behind it is an orphan the reader cannot explain (there is no delete-session
|
|
196
|
+
* control at all) and cannot get rid of.
|
|
197
|
+
*
|
|
198
|
+
* THE DELETE ITSELF IS NEVER REFUSED. The task is removed first and the archive is best-effort: a
|
|
199
|
+
* conversation that is still running cannot be archived, and a reader who deletes a task must not be
|
|
200
|
+
* told "no" because a session happens to be busy. The session simply stays where it is, and the
|
|
201
|
+
* reason is in the server log rather than in the reader's face.
|
|
202
|
+
*/
|
|
190
203
|
async remove(id) {
|
|
191
|
-
this.require(id);
|
|
204
|
+
const task = this.require(id);
|
|
192
205
|
await this.store.removeScheduledTask(id);
|
|
206
|
+
const conversationId = task.conversationId;
|
|
207
|
+
if (conversationId) {
|
|
208
|
+
const conversation = this.store.conversation(conversationId);
|
|
209
|
+
if (conversation && !conversation.archivedAt) {
|
|
210
|
+
try {
|
|
211
|
+
await this.conversationService.archiveConversation(conversationId);
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
console.warn(`Super Pi: the deleted task's conversation ${conversationId} could not be archived:`, error instanceof Error ? error.message : String(error));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
193
218
|
this.broadcaster.broadcastState();
|
|
194
219
|
}
|
|
195
220
|
/**
|
|
@@ -11,8 +11,13 @@ export interface SpawnSessionOptions {
|
|
|
11
11
|
task: string;
|
|
12
12
|
/** Parent conversation initiating the orchestration, if any. */
|
|
13
13
|
parentConversationId?: string;
|
|
14
|
-
/**
|
|
15
|
-
workspaceId
|
|
14
|
+
/** Explicit target workspace. A parent conversation records only the session-tree relationship. */
|
|
15
|
+
workspaceId: string;
|
|
16
|
+
/**
|
|
17
|
+
* Optional workspace-root-relative subdirectory for the new session. Invalid or out-of-bounds
|
|
18
|
+
* values are rejected before the conversation is created.
|
|
19
|
+
*/
|
|
20
|
+
cwd?: string;
|
|
16
21
|
/** Optional custom title for the new session. */
|
|
17
22
|
title?: string;
|
|
18
23
|
/** Optional agent override for the new session. */
|
|
@@ -98,6 +103,10 @@ export declare class SessionOrchestratorService {
|
|
|
98
103
|
}): Promise<SessionSummary>;
|
|
99
104
|
/**
|
|
100
105
|
* Lists summaries of all sessions, optionally filtered by parent conversation.
|
|
106
|
+
*
|
|
107
|
+
* Persisted store conversations are the authoritative source for session tree
|
|
108
|
+
* parent-child relationships across server restarts, while SessionLedger
|
|
109
|
+
* enriches live in-memory task, progress, and activeTool state.
|
|
101
110
|
*/
|
|
102
111
|
list(parentConversationId?: string): Promise<SessionSummary[]>;
|
|
103
112
|
/**
|