@evandu/super-pi 0.1.146 → 0.1.148
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 +223 -2
- package/agents/super-pi-task/index.ts +50 -0
- package/dist/.super-pi-check-inputs.json +2 -2
- package/dist/client/.super-pi-client-sources.json +8 -8
- package/dist/client/assets/{CodeMirrorEditor-CEwBCVM6.js → CodeMirrorEditor-DQCjEnxG.js} +2 -2
- package/dist/client/assets/{dist-DC1IocrS.js → dist-1Innu17E.js} +1 -1
- package/dist/client/assets/{dist-CSCUT1PQ.js → dist-1lynFOc5.js} +1 -1
- package/dist/client/assets/{dist-Do52MRwm.js → dist-B1qLZVMK.js} +1 -1
- package/dist/client/assets/{dist-CAOcjKAQ.js → dist-B4pz_xdd.js} +1 -1
- package/dist/client/assets/{dist-B0RXZeXQ.js → dist-B6KwDpeI.js} +1 -1
- package/dist/client/assets/{dist-DFMtP3wG.js → dist-BJVI1Unv.js} +1 -1
- package/dist/client/assets/{dist-oLq6HXLG.js → dist-BWvzD8Pe.js} +1 -1
- package/dist/client/assets/{dist-9F1wL-T5.js → dist-BayWssd0.js} +1 -1
- package/dist/client/assets/{dist-DBUqwiWG.js → dist-Bk_pkBVH.js} +1 -1
- package/dist/client/assets/{dist-CbVf2Cjn.js → dist-BpeClAv0.js} +1 -1
- package/dist/client/assets/{dist-DftD4ibj.js → dist-BsmJg5PX.js} +1 -1
- package/dist/client/assets/{dist-CUB_Chdh.js → dist-Bx7s4rni.js} +1 -1
- package/dist/client/assets/{dist-Bh8Q7Y_n.js → dist-CDBKM9tL.js} +1 -1
- package/dist/client/assets/{dist-BoyICDlL.js → dist-CGzXCzCG.js} +1 -1
- package/dist/client/assets/{dist-BCNaDgNW.js → dist-CRxCwn-Z.js} +1 -1
- package/dist/client/assets/{dist-76ArZ5M4.js → dist-CTDgUnBG.js} +1 -1
- package/dist/client/assets/{dist-BXBSIuTD.js → dist-D1VfMWmf.js} +1 -1
- package/dist/client/assets/{dist-DN0GlnnZ.js → dist-DTZLNEci.js} +1 -1
- package/dist/client/assets/{dist-JvQIIdqB.js → dist-DuUi29km.js} +1 -1
- package/dist/client/assets/{dist-CHt8PX8R.js → dist-F3S0t1oM.js} +1 -1
- package/dist/client/assets/{dist-BuR5Iagd.js → dist-QZ-FAVJV.js} +1 -1
- package/dist/client/assets/{dist-Bx6ZWYUW.js → dist-u0nBg0fZ.js} +1 -1
- package/dist/client/assets/index-BMpqviwU.css +1 -0
- package/dist/client/assets/{index-E7yaLa2C.js → index-Dzyp_7sf.js} +43 -43
- package/dist/client/index.html +2 -2
- package/dist/server/active-memory.d.ts +17 -0
- package/dist/server/active-memory.js +39 -0
- package/dist/server/agents.d.ts +1 -1
- package/dist/server/agents.js +1 -1
- package/dist/server/application/conversation-service.js +6 -0
- package/dist/server/application/run-service.d.ts +16 -0
- package/dist/server/application/run-service.js +175 -26
- package/dist/server/application/session-orchestrator-service.d.ts +103 -1
- package/dist/server/application/session-orchestrator-service.js +308 -8
- package/dist/server/application/session-watchdog.d.ts +159 -0
- package/dist/server/application/session-watchdog.js +244 -0
- package/dist/server/http-server.js +136 -76
- package/dist/server/index.js +3 -0
- package/dist/server/knowledge-vectors.d.ts +15 -0
- package/dist/server/knowledge-vectors.js +118 -37
- package/dist/server/knowledge.d.ts +7 -8
- package/dist/server/knowledge.js +133 -55
- package/dist/server/length-continuation.d.ts +50 -0
- package/dist/server/length-continuation.js +55 -0
- package/dist/server/pi-session-events.js +5 -1
- package/dist/server/pi-session-pool.d.ts +2 -0
- package/dist/server/pi-session-pool.js +16 -0
- package/dist/server/runtime.js +5 -4
- package/dist/server/session-tree-scope.d.ts +125 -0
- package/dist/server/session-tree-scope.js +337 -0
- package/dist/server/store.d.ts +13 -0
- package/dist/server/store.js +37 -4
- package/dist/server/task-tool.d.ts +4 -1
- package/dist/server/task-tool.js +25 -1
- package/dist/server/turn-failure.d.ts +26 -0
- package/dist/server/turn-failure.js +51 -0
- package/dist/server/types.d.ts +15 -1
- package/package.json +20 -3
- package/dist/client/assets/index-D-OxlUxN.css +0 -1
package/dist/server/store.js
CHANGED
|
@@ -153,7 +153,7 @@ function reconcileConversations(state) {
|
|
|
153
153
|
// was sent) and the server died before any settled state was recorded. A conversation that was in
|
|
154
154
|
// either of those two states when the process went away has a half-open turn whose outcome nobody
|
|
155
155
|
// can determine from the file alone - exactly the situation this sweep exists to remove.
|
|
156
|
-
if (!conversation || (conversation.status !== "running" && conversation.status !== "waiting"))
|
|
156
|
+
if (!conversation || (conversation.status !== "running" && conversation.status !== "waiting" && conversation.status !== "awaiting_background"))
|
|
157
157
|
continue;
|
|
158
158
|
conversation.status = "failed";
|
|
159
159
|
conversation.error = ORPHANED_CONVERSATION_ERROR;
|
|
@@ -722,6 +722,10 @@ export class Store {
|
|
|
722
722
|
const workspace = this.state.workspaces.find((item) => item.id === workspaceId);
|
|
723
723
|
return workspace ? { ...workspace } : undefined;
|
|
724
724
|
}
|
|
725
|
+
/** Read every workspace as detached shallow copies. */
|
|
726
|
+
workspaces() {
|
|
727
|
+
return this.state.workspaces.map((item) => ({ ...item }));
|
|
728
|
+
}
|
|
725
729
|
/** Read the app-wide default without cloning conversations or transcripts. */
|
|
726
730
|
defaultModel() {
|
|
727
731
|
const model = this.state.defaultModel;
|
|
@@ -1234,7 +1238,7 @@ export class Store {
|
|
|
1234
1238
|
const conversation = this.requireConversation(conversationId);
|
|
1235
1239
|
if (conversation.archivedAt)
|
|
1236
1240
|
return;
|
|
1237
|
-
if (conversation.status === "running")
|
|
1241
|
+
if (conversation.status === "running" || conversation.status === "awaiting_background")
|
|
1238
1242
|
throw new Error("运行中的会话无法归档,请先停止运行");
|
|
1239
1243
|
conversation.archivedAt = now();
|
|
1240
1244
|
await this.persistMetadata(conversationId);
|
|
@@ -1370,7 +1374,6 @@ export class Store {
|
|
|
1370
1374
|
* reader sees who spoke.
|
|
1371
1375
|
*/
|
|
1372
1376
|
async addAgentMessage(conversationId, content, options) {
|
|
1373
|
-
console.error("STORE addAgentMessage conv=" + conversationId + " content=" + content.slice(0, 60));
|
|
1374
1377
|
const conversation = this.requireConversation(conversationId);
|
|
1375
1378
|
if (conversation.archivedAt)
|
|
1376
1379
|
throw new Error("无法向已归档的会话下发指令,请先恢复该会话");
|
|
@@ -1422,6 +1425,10 @@ export class Store {
|
|
|
1422
1425
|
// the new turn starts: leaving it up would show "was interrupted, marked failed" above a conversation
|
|
1423
1426
|
// that is visibly working again.
|
|
1424
1427
|
delete conversation.error;
|
|
1428
|
+
// THE SAME IS TRUE OF THE TRUNCATION WARNING. It describes the answer that was cut short, and a
|
|
1429
|
+
// new turn is the reader answering that situation - so the notice belongs to the turn that just
|
|
1430
|
+
// ended, not to the conversation from now on.
|
|
1431
|
+
delete conversation.warning;
|
|
1425
1432
|
// AN INCREMENTAL WRITE, NOT A FULL ONE. This line was the last full-state write on the hot path
|
|
1426
1433
|
// of every message, and it was in front of the broadcast that tells the browser a turn has
|
|
1427
1434
|
// started - so the size of the WHOLE state was the delay between clicking send and the stream
|
|
@@ -1460,7 +1467,6 @@ export class Store {
|
|
|
1460
1467
|
* state it also enters `waitingMessages` and `runConversationTurns` will pick it up next.
|
|
1461
1468
|
*/
|
|
1462
1469
|
async addToolMessage(conversationId, content, options) {
|
|
1463
|
-
console.error("STORE addToolMessage conv=" + conversationId + " content=" + content.slice(0, 60));
|
|
1464
1470
|
const conversation = this.requireConversation(conversationId);
|
|
1465
1471
|
if (conversation.archivedAt)
|
|
1466
1472
|
throw new Error("无法向已归档的会话追加后台回传,请先恢复该会话");
|
|
@@ -1912,6 +1918,25 @@ export class Store {
|
|
|
1912
1918
|
conversation.updatedAt = now();
|
|
1913
1919
|
await this.persistMetadata(conversationId);
|
|
1914
1920
|
}
|
|
1921
|
+
/**
|
|
1922
|
+
* Records WHY THIS CONVERSATION'S LAST ANSWER IS INCOMPLETE, in a way the reader can see.
|
|
1923
|
+
*
|
|
1924
|
+
* `Conversation.warning` is a different fact from `Conversation.error`: the run did not break, the
|
|
1925
|
+
* answer was cut short by the model's output budget. The reader's next move is different too - a
|
|
1926
|
+
* failed turn asks them to retry, a truncated one asks them to let the model continue - so the two
|
|
1927
|
+
* are kept apart rather than sharing one field and one colour.
|
|
1928
|
+
*
|
|
1929
|
+
* Set to `undefined` to clear, which the next `addUserMessage` does anyway.
|
|
1930
|
+
*/
|
|
1931
|
+
async setWarningReason(conversationId, reason) {
|
|
1932
|
+
const conversation = this.requireConversation(conversationId);
|
|
1933
|
+
if (reason === undefined)
|
|
1934
|
+
delete conversation.warning;
|
|
1935
|
+
else
|
|
1936
|
+
conversation.warning = reason;
|
|
1937
|
+
conversation.updatedAt = now();
|
|
1938
|
+
await this.persistMetadata(conversationId);
|
|
1939
|
+
}
|
|
1915
1940
|
/** The plan a revert would carry out, without carrying it out. The server reads this to stop children first. */
|
|
1916
1941
|
planRevert(conversationId, messageId, withdraw = false) { return planRevert(this.state, conversationId, messageId, withdraw); }
|
|
1917
1942
|
/**
|
|
@@ -2392,6 +2417,14 @@ export class Store {
|
|
|
2392
2417
|
Object.assign(existing, summary);
|
|
2393
2418
|
else
|
|
2394
2419
|
this.state.backgroundTasks.unshift(summary);
|
|
2420
|
+
if (summary.conversationId && summary.status === "running") {
|
|
2421
|
+
const conv = this.state.conversations.find((c) => c.id === summary.conversationId);
|
|
2422
|
+
if (conv && conv.status !== "running" && conv.status !== "waiting") {
|
|
2423
|
+
conv.status = "awaiting_background";
|
|
2424
|
+
conv.updatedAt = now();
|
|
2425
|
+
await this.persistMetadata(conv.id);
|
|
2426
|
+
}
|
|
2427
|
+
}
|
|
2395
2428
|
await this.persistCollections("backgroundTasks");
|
|
2396
2429
|
}
|
|
2397
2430
|
async removeBackgroundTask(id) {
|
|
@@ -257,11 +257,14 @@ export declare const SESSION_SPAWN_TOOL_NAME = "session_spawn";
|
|
|
257
257
|
export declare const SESSION_STATUS_TOOL_NAME = "session_status";
|
|
258
258
|
export declare const SESSION_DISPATCH_TOOL_NAME = "session_dispatch";
|
|
259
259
|
export declare const SESSION_STOP_TOOL_NAME = "session_stop";
|
|
260
|
+
export declare const SESSION_REPORT_TOOL_NAME = "session_report";
|
|
260
261
|
/** Model-facing description for session_spawn. */
|
|
261
|
-
export declare const SESSION_SPAWN_DESCRIPTION = "Create a new sibling conversation in the current workspace and immediately dispatch its initial task.\n\nDIFFERENCE FROM THE 'task' TOOL:\n- The 'task' tool delegates to a transient, one-shot subagent process (scout, critic, patcher) that runs once, returns its final text, and terminates. It has no conversation history and does not appear in the workspace sidebar.\n- 'session_spawn' creates a real, persistent sibling CONVERSATION in Super Pi. It appears in the sidebar, retains multi-turn message history, can receive follow-up messages via session_dispatch, can be inspected via session_status, and can be stopped via session_stop. The user can also switch to it in the Web UI.\n\nWHEN TO USE THIS:\n- Long-running, multi-phase tracks of work that require ongoing context, multiple turns of dialogue, or independent user visibility.\n- Parallel work streams (e.g., Track A: backend refactor, Track B: frontend component update) where both need full conversational capabilities.\n- When the user asks you to act as an orchestrator/manager who delegates to multiple sessions.\n\nWHEN NOT TO USE THIS:\n- For one-off, focused delegation with a single answer: use 'task' (with scout/critic/patcher). It is faster, cheaper, and returns directly to your transcript.\n- For running shell commands or build watchers: use 'run_async'.\n- For a linear multi-step subagent pipeline: use 'workflow'.\n\nUsage notes:\n1. Spawning is ASYNCHRONOUS by default: it creates the conversation, enqueues the initial task, and returns the new conversationId immediately. The sibling session runs in the background.\n2. The initial task should provide clear context and goals since the new session starts with a fresh conversation history.\n3. Use session_status with the returned conversationId to check progress, or session_dispatch to send further messages.\n4. The new session reports completion to its DIRECT parent automatically. 'parentConversationId' optionally selects that parent; by default it is the calling session.\n5. The new session always belongs to the calling session's workspace. Pass 'cwd' as a workspace-relative subdirectory (for example, 'packages/a') when it should work below the workspace root; absolute paths and traversal are rejected.";
|
|
262
|
+
export declare const SESSION_SPAWN_DESCRIPTION = "Create a new sibling conversation in the current workspace and immediately dispatch its initial task.\n\nDIFFERENCE FROM THE 'task' TOOL:\n- The 'task' tool delegates to a transient, one-shot subagent process (scout, critic, patcher) that runs once, returns its final text, and terminates. It has no conversation history and does not appear in the workspace sidebar.\n- 'session_spawn' creates a real, persistent sibling CONVERSATION in Super Pi. It appears in the sidebar, retains multi-turn message history, can receive follow-up messages via session_dispatch, can be inspected via session_status, and can be stopped via session_stop. The user can also switch to it in the Web UI.\n\nWHEN TO USE THIS:\n- Long-running, multi-phase tracks of work that require ongoing context, multiple turns of dialogue, or independent user visibility.\n- Parallel work streams (e.g., Track A: backend refactor, Track B: frontend component update) where both need full conversational capabilities.\n- When the user asks you to act as an orchestrator/manager who delegates to multiple sessions.\n\nWHEN NOT TO USE THIS:\n- For one-off, focused delegation with a single answer: use 'task' (with scout/critic/patcher). It is faster, cheaper, and returns directly to your transcript.\n- For running shell commands or build watchers: use 'run_async'.\n- For a linear multi-step subagent pipeline: use 'workflow'.\n\nUsage notes:\n1. Spawning is ASYNCHRONOUS by default: it creates the conversation, enqueues the initial task, and returns the new conversationId immediately. The sibling session runs in the background.\n2. The initial task should provide clear context and goals since the new session starts with a fresh conversation history.\n3. Use session_status with the returned conversationId to check progress, or session_dispatch to send further messages.\n4. The new session reports completion to its DIRECT parent automatically. 'parentConversationId' optionally selects that parent; by default it is the calling session.\n5. The new session always belongs to the calling session's workspace. Pass 'cwd' as a workspace-relative subdirectory (for example, 'packages/a') when it should work below the workspace root; absolute paths and traversal are rejected.\n6. Optionally pass 'model' to choose which model the NEW session runs on: {\"provider\":\"...\",\"modelId\":\"...\"} - the same \"provider/modelId\" pair the model picker shows, and BOTH halves are required. Leave 'model' out and the new session starts on the workspace default model.\n7. Optionally pass 'watchdogTimeoutSeconds' to declare how long YOU are willing to wait for a report before you want to be reminded: a positive number of seconds. If the new session stays silent that long, you receive a '[Watchdog \u63D0\u9192]' message naming it. A report from the child (session_report) restarts the clock; the watchdog never stops the child. Leave it out to run without a watchdog.";
|
|
262
263
|
/** Model-facing description for session_status. */
|
|
263
264
|
export declare const SESSION_STATUS_DESCRIPTION = "Inspect the progress, run state, and latest activity of sibling conversations created by the orchestrator.\n\nWHEN TO USE THIS:\n- To check whether a spawned sibling conversation is still running, has finished (idle), is stopped, or encountered an error.\n- To read the latest assistant progress snippet without bloating your context with the session's entire raw message transcript.\n- To list all sibling conversations under your coordination when conversationId is omitted.\n\nUsage notes:\n1. When 'conversationId' is provided: returns a compact summary for that specific session, including its run status ('idle', 'running', 'waiting', 'stopped', 'failed'), messageCount, turnCount, lastAssistantSnippet, lastUserSnippet, activeTool, and error (if any).\n2. When 'conversationId' is omitted: returns a summary list of sibling conversations whose direct parent is 'parentConversationId' (the calling session by default).\n3. By default it never returns full transcripts; concise summaries keep your context window lean. Set 'includeOutput' with a conversationId to read the final output from its result file, capped to the last 64Ki characters with an explicit truncation notice.";
|
|
264
265
|
/** Model-facing description for session_dispatch. */
|
|
265
266
|
export declare const SESSION_DISPATCH_DESCRIPTION = "Send a message, follow-up instructions, or steering guidance to an existing sibling conversation.\n\nWHEN TO USE THIS:\n- To follow up with a sibling session after reading its progress with session_status.\n- To give feedback, provide additional requirements, or answer a question from a sibling session.\n- To kick off a new turn on an idle or stopped sibling session.\n\nUsage notes:\n1. 'conversationId' is required and must identify an existing sibling conversation.\n2. 'content' is the text instruction sent to that session, just as if typed by a user in that session's chat.\n3. Dispatch is ASYNCHRONOUS by default: it enqueues the message into the session's queue and returns immediately.\n4. If the sibling session is currently busy running a turn, the dispatched message is placed in its waiting queue and processed when the active turn completes.";
|
|
266
267
|
/** Model-facing description for session_stop. */
|
|
267
268
|
export declare const SESSION_STOP_DESCRIPTION = "Stop execution in a sibling conversation.\n\nWHEN TO USE THIS:\n- When a sibling session has gone down the wrong path, is stuck in an unwanted loop, or when the overall goal has changed and remaining work should be aborted.\n- To clear pending queued messages before they start running.\n\nUsage notes:\n1. 'mode' controls what to stop:\n - 'all' (default): stops the currently running turn and cancels all pending queued messages in the session.\n - 'active': only interrupts the active running turn; leaves any pending queued messages untouched.\n - 'queued': only cancels pending queued messages; lets the currently running turn complete naturally.\n2. Use 'messageId' if you want to cancel or interrupt one specific queued message instead of all queued messages.";
|
|
269
|
+
/** Model-facing description for session_report. */
|
|
270
|
+
export declare const SESSION_REPORT_DESCRIPTION = "Report progress from THIS session to the parent session that spawned it.\n\nWHEN TO USE THIS:\n- You are a spawned sibling session, and the parent is waiting to hear from you: a milestone is done, a roadblock appeared, or the task is finished.\n- The parent set a watchdog (session_spawn's 'watchdogTimeoutSeconds'): a report is what restarts that clock, and silence is what triggers the reminder.\n- The task is over and the parent needs the outcome NOW rather than after the automatic end-of-turn result.\n\nWHEN NOT TO USE THIS:\n- This session has no parent (the user opened it directly): there is nobody to report to, and the server refuses the call.\n- You want to ASK the parent something or send it new instructions: that is session_dispatch, and only the parent can send it. A report is one-way - the parent receives it as a message.\n\nUsage notes:\n1. 'content' is required: it is the text the parent receives. Write it for the parent - what is done, what is left, what you need.\n2. 'status' says what the report MEANS:\n - 'in_progress' (default): an interim update. The task continues, and the parent's watchdog clock restarts.\n - 'completed': the task is finished and this is your final word.\n - 'failed': the task could not be finished, and this says why.\n3. A 'completed' or 'failed' report tells the server the work is over, so the parent is NOT also sent the automatic end-of-turn result. Send it once, at the end, with the final answer in 'content'.\n4. Reporting does NOT end your turn. If the task is not done, keep working afterwards - the parent is waiting on the work, not on the report.\n5. The report reaches your DIRECT parent only. A sibling, a cousin, and the user's own session are all out of reach, and the server refuses those.";
|
package/dist/server/task-tool.js
CHANGED
|
@@ -362,6 +362,7 @@ export const SESSION_SPAWN_TOOL_NAME = "session_spawn";
|
|
|
362
362
|
export const SESSION_STATUS_TOOL_NAME = "session_status";
|
|
363
363
|
export const SESSION_DISPATCH_TOOL_NAME = "session_dispatch";
|
|
364
364
|
export const SESSION_STOP_TOOL_NAME = "session_stop";
|
|
365
|
+
export const SESSION_REPORT_TOOL_NAME = "session_report";
|
|
365
366
|
/** Model-facing description for session_spawn. */
|
|
366
367
|
export const SESSION_SPAWN_DESCRIPTION = `Create a new sibling conversation in the current workspace and immediately dispatch its initial task.
|
|
367
368
|
|
|
@@ -384,7 +385,9 @@ Usage notes:
|
|
|
384
385
|
2. The initial task should provide clear context and goals since the new session starts with a fresh conversation history.
|
|
385
386
|
3. Use session_status with the returned conversationId to check progress, or session_dispatch to send further messages.
|
|
386
387
|
4. The new session reports completion to its DIRECT parent automatically. 'parentConversationId' optionally selects that parent; by default it is the calling session.
|
|
387
|
-
5. The new session always belongs to the calling session's workspace. Pass 'cwd' as a workspace-relative subdirectory (for example, 'packages/a') when it should work below the workspace root; absolute paths and traversal are rejected
|
|
388
|
+
5. The new session always belongs to the calling session's workspace. Pass 'cwd' as a workspace-relative subdirectory (for example, 'packages/a') when it should work below the workspace root; absolute paths and traversal are rejected.
|
|
389
|
+
6. Optionally pass 'model' to choose which model the NEW session runs on: {"provider":"...","modelId":"..."} - the same "provider/modelId" pair the model picker shows, and BOTH halves are required. Leave 'model' out and the new session starts on the workspace default model.
|
|
390
|
+
7. Optionally pass 'watchdogTimeoutSeconds' to declare how long YOU are willing to wait for a report before you want to be reminded: a positive number of seconds. If the new session stays silent that long, you receive a '[Watchdog 提醒]' message naming it. A report from the child (session_report) restarts the clock; the watchdog never stops the child. Leave it out to run without a watchdog.`;
|
|
388
391
|
/** Model-facing description for session_status. */
|
|
389
392
|
export const SESSION_STATUS_DESCRIPTION = `Inspect the progress, run state, and latest activity of sibling conversations created by the orchestrator.
|
|
390
393
|
|
|
@@ -423,3 +426,24 @@ Usage notes:
|
|
|
423
426
|
- 'active': only interrupts the active running turn; leaves any pending queued messages untouched.
|
|
424
427
|
- 'queued': only cancels pending queued messages; lets the currently running turn complete naturally.
|
|
425
428
|
2. Use 'messageId' if you want to cancel or interrupt one specific queued message instead of all queued messages.`;
|
|
429
|
+
/** Model-facing description for session_report. */
|
|
430
|
+
export const SESSION_REPORT_DESCRIPTION = `Report progress from THIS session to the parent session that spawned it.
|
|
431
|
+
|
|
432
|
+
WHEN TO USE THIS:
|
|
433
|
+
- You are a spawned sibling session, and the parent is waiting to hear from you: a milestone is done, a roadblock appeared, or the task is finished.
|
|
434
|
+
- The parent set a watchdog (session_spawn's 'watchdogTimeoutSeconds'): a report is what restarts that clock, and silence is what triggers the reminder.
|
|
435
|
+
- The task is over and the parent needs the outcome NOW rather than after the automatic end-of-turn result.
|
|
436
|
+
|
|
437
|
+
WHEN NOT TO USE THIS:
|
|
438
|
+
- This session has no parent (the user opened it directly): there is nobody to report to, and the server refuses the call.
|
|
439
|
+
- You want to ASK the parent something or send it new instructions: that is session_dispatch, and only the parent can send it. A report is one-way - the parent receives it as a message.
|
|
440
|
+
|
|
441
|
+
Usage notes:
|
|
442
|
+
1. 'content' is required: it is the text the parent receives. Write it for the parent - what is done, what is left, what you need.
|
|
443
|
+
2. 'status' says what the report MEANS:
|
|
444
|
+
- 'in_progress' (default): an interim update. The task continues, and the parent's watchdog clock restarts.
|
|
445
|
+
- 'completed': the task is finished and this is your final word.
|
|
446
|
+
- 'failed': the task could not be finished, and this says why.
|
|
447
|
+
3. A 'completed' or 'failed' report tells the server the work is over, so the parent is NOT also sent the automatic end-of-turn result. Send it once, at the end, with the final answer in 'content'.
|
|
448
|
+
4. Reporting does NOT end your turn. If the task is not done, keep working afterwards - the parent is waiting on the work, not on the report.
|
|
449
|
+
5. The report reaches your DIRECT parent only. A sibling, a cousin, and the user's own session are all out of reach, and the server refuses those.`;
|
|
@@ -19,4 +19,30 @@
|
|
|
19
19
|
* needs no server, socket or Pi process.
|
|
20
20
|
*/
|
|
21
21
|
export declare function formatTurnFailure(error: unknown): string;
|
|
22
|
+
/**
|
|
23
|
+
* THE MODEL FILLED ITS OUTPUT BUDGET AND STOPPED MID-ANSWER.
|
|
24
|
+
*
|
|
25
|
+
* A length stop is NOT a crash. The provider answered, streamed real text, and stopped because it
|
|
26
|
+
* ran out of `max_tokens` - which is what happens every time an answer is longer than one output
|
|
27
|
+
* budget. Treating it as a failure is what made an orchestrated child session die: the run service
|
|
28
|
+
* stamped the conversation `failed`, the orchestrator reported a failure to the parent, and a task
|
|
29
|
+
* that was one sentence from done was thrown away.
|
|
30
|
+
*
|
|
31
|
+
* It carries a CLASS so the layers above can tell it apart from a provider error by identity rather
|
|
32
|
+
* than by matching a Chinese substring - the message is written for a reader, and a reader-facing
|
|
33
|
+
* sentence is not a reliable machine contract.
|
|
34
|
+
*/
|
|
35
|
+
export declare class LengthExceededError extends Error {
|
|
36
|
+
readonly stopReason: "length";
|
|
37
|
+
constructor(message?: string);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Whether this failure is a length stop rather than a real failure.
|
|
41
|
+
*
|
|
42
|
+
* The class is the answer; the message match is the BACKSTOP for a throw site that never got to
|
|
43
|
+
* construct the class - an error that crossed a process boundary, or one that was rebuilt from its
|
|
44
|
+
* text somewhere along the way. Both are checked because a length stop that loses its class on the
|
|
45
|
+
* way up is exactly the case that used to kill a child session.
|
|
46
|
+
*/
|
|
47
|
+
export declare function isLengthExceededError(error: unknown): boolean;
|
|
22
48
|
export declare function controllerAborted(error: unknown): boolean;
|
|
@@ -82,11 +82,62 @@ export function formatTurnFailure(error) {
|
|
|
82
82
|
if (lower.includes("不支持图片输入")) {
|
|
83
83
|
return raw;
|
|
84
84
|
}
|
|
85
|
+
// LENGTH LIMIT - the model stopped because it filled the output budget, NOT because anything
|
|
86
|
+
// broke. The answer is incomplete, but everything that was said is still on screen, so the
|
|
87
|
+
// sentence has to offer the way out (say "continue") instead of calling it a failure.
|
|
88
|
+
//
|
|
89
|
+
// THE SENTENCE IS BUILT, NOT TAKEN FROM THE ERROR. The error's own message says the fact
|
|
90
|
+
// ("模型输出达到本轮长度上限,回答未完成") and stops there - which is a statement, not an action, and
|
|
91
|
+
// a reader who is told a thing stopped has no idea what to do next. What they need is the way
|
|
92
|
+
// out, so the sentence always carries it. Pi's own words are kept only when they add something
|
|
93
|
+
// the sentence does not already say.
|
|
94
|
+
if (isLengthExceededError(error)) {
|
|
95
|
+
const detail = raw && !/长度上限/.test(raw) ? `(${raw})` : "";
|
|
96
|
+
return `模型单次输出达到长度上限,回答没有写完${detail}。可以点「继续写完」或者自己发一句「继续」,模型会从被截断的地方接着写。`;
|
|
97
|
+
}
|
|
85
98
|
// GENERIC - the literal message is more useful than a paraphrase, but it has to be the LAST
|
|
86
99
|
// branch and the message has to be non-empty (a thrown `new Error()` is empty and renders
|
|
87
100
|
// nothing, which is exactly the silence this whole change exists to break).
|
|
88
101
|
return raw || "Pi 未能完成本轮回答,已标记为失败。可以重发任务继续。";
|
|
89
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* THE MODEL FILLED ITS OUTPUT BUDGET AND STOPPED MID-ANSWER.
|
|
105
|
+
*
|
|
106
|
+
* A length stop is NOT a crash. The provider answered, streamed real text, and stopped because it
|
|
107
|
+
* ran out of `max_tokens` - which is what happens every time an answer is longer than one output
|
|
108
|
+
* budget. Treating it as a failure is what made an orchestrated child session die: the run service
|
|
109
|
+
* stamped the conversation `failed`, the orchestrator reported a failure to the parent, and a task
|
|
110
|
+
* that was one sentence from done was thrown away.
|
|
111
|
+
*
|
|
112
|
+
* It carries a CLASS so the layers above can tell it apart from a provider error by identity rather
|
|
113
|
+
* than by matching a Chinese substring - the message is written for a reader, and a reader-facing
|
|
114
|
+
* sentence is not a reliable machine contract.
|
|
115
|
+
*/
|
|
116
|
+
export class LengthExceededError extends Error {
|
|
117
|
+
stopReason = "length";
|
|
118
|
+
constructor(message) {
|
|
119
|
+
super(message || "模型输出达到本轮长度上限,回答未完成。");
|
|
120
|
+
this.name = "LengthExceededError";
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Whether this failure is a length stop rather than a real failure.
|
|
125
|
+
*
|
|
126
|
+
* The class is the answer; the message match is the BACKSTOP for a throw site that never got to
|
|
127
|
+
* construct the class - an error that crossed a process boundary, or one that was rebuilt from its
|
|
128
|
+
* text somewhere along the way. Both are checked because a length stop that loses its class on the
|
|
129
|
+
* way up is exactly the case that used to kill a child session.
|
|
130
|
+
*/
|
|
131
|
+
export function isLengthExceededError(error) {
|
|
132
|
+
if (!error)
|
|
133
|
+
return false;
|
|
134
|
+
if (error instanceof LengthExceededError)
|
|
135
|
+
return true;
|
|
136
|
+
if (error?.stopReason === "length")
|
|
137
|
+
return true;
|
|
138
|
+
const msg = error instanceof Error ? error.message : typeof error === "string" ? error : "";
|
|
139
|
+
return msg.includes("模型输出达到本轮长度上限") || msg.includes("达到本轮长度上限") || msg.includes("单次输出达到长度上限");
|
|
140
|
+
}
|
|
90
141
|
export function controllerAborted(error) {
|
|
91
142
|
if (!error)
|
|
92
143
|
return false;
|
package/dist/server/types.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ import type { ScheduledTask } from "./scheduled-tasks.js";
|
|
|
18
18
|
* `failed` is what any unhandled throw lands (with `Conversation.error` carrying the translated reason);
|
|
19
19
|
* `completed` is the clean path.
|
|
20
20
|
*/
|
|
21
|
-
export type RunStatus = "idle" | "waiting" | "running" | "completed" | "stopped" | "failed";
|
|
21
|
+
export type RunStatus = "idle" | "waiting" | "running" | "awaiting_background" | "completed" | "stopped" | "failed";
|
|
22
22
|
export type MessageRole = "user" | "assistant" | "tool" | "system";
|
|
23
23
|
/**
|
|
24
24
|
* One message in `state.json`.
|
|
@@ -494,6 +494,20 @@ export type Conversation = {
|
|
|
494
494
|
* would answer "what happened" with something the reader has to go and decode.
|
|
495
495
|
*/
|
|
496
496
|
error?: string;
|
|
497
|
+
/**
|
|
498
|
+
* WHY THIS CONVERSATION'S LAST ANSWER IS INCOMPLETE - in words a person reads.
|
|
499
|
+
*
|
|
500
|
+
* A WARNING, not a failure. The model filled its single-response output budget and stopped
|
|
501
|
+
* mid-answer; everything it said is on screen, and the conversation is still usable. It is a
|
|
502
|
+
* separate field from `error` because the two mean different things to the reader: `error` says
|
|
503
|
+
* "this run broke, here is why", this says "this answer was cut short, and here is what to do
|
|
504
|
+
* about it". Collapsing them would paint a recoverable truncation as a crash - which is exactly
|
|
505
|
+
* what made an orchestrated child session die.
|
|
506
|
+
*
|
|
507
|
+
* Cleared by the next turn, the same way `error` is: it describes the turn that just ended and
|
|
508
|
+
* must never outlive it.
|
|
509
|
+
*/
|
|
510
|
+
warning?: string;
|
|
497
511
|
/**
|
|
498
512
|
* WHY THIS CONVERSATION IS NOT ON THE MODEL THE USER SET FOR IT - in words a person reads.
|
|
499
513
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evandu/super-pi",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.148",
|
|
4
4
|
"description": "Web control plane and companion for Pi",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
"scripts/inline-thinking.test.mjs",
|
|
52
52
|
"scripts/inline-thinking-close-tag.test.mjs",
|
|
53
53
|
"scripts/pi-session.test.mjs",
|
|
54
|
+
"scripts/length-continuation.test.mjs",
|
|
54
55
|
"scripts/pi-session-isolation.test.mjs",
|
|
55
56
|
"scripts/installed-layout.test.mjs",
|
|
56
57
|
"scripts/pi-model-switch.test.mjs",
|
|
@@ -192,6 +193,7 @@
|
|
|
192
193
|
"scripts/document-write-overwrite.test.mjs",
|
|
193
194
|
"scripts/feishu.test.mjs",
|
|
194
195
|
"scripts/scheduled-tasks.test.mjs",
|
|
196
|
+
"scripts/scheduled-tasks-timezone.test.mjs",
|
|
195
197
|
"scripts/scheduled-task-model-clear.test.mjs",
|
|
196
198
|
"scripts/scheduled-task-scope.test.mjs",
|
|
197
199
|
"scripts/scheduled-task-tool.test.mjs",
|
|
@@ -203,8 +205,19 @@
|
|
|
203
205
|
"scripts/spawn-workspace-boundary.test.mjs",
|
|
204
206
|
"scripts/cwd-agent-spawn.test.mjs",
|
|
205
207
|
"scripts/capabilities-prompt.test.mjs",
|
|
208
|
+
"scripts/version-fallback.test.mjs",
|
|
209
|
+
"scripts/model-image-input-session.test.mjs",
|
|
206
210
|
"scripts/conversation-usage.test.mjs",
|
|
207
|
-
"scripts/orchestrator-observability.test.mjs"
|
|
211
|
+
"scripts/orchestrator-observability.test.mjs",
|
|
212
|
+
"scripts/session-tab-preview.test.mjs",
|
|
213
|
+
"scripts/knowledge-retrieval-quality.test.mjs",
|
|
214
|
+
"scripts/rail-ui-contract.test.mjs",
|
|
215
|
+
"scripts/session-tree-scope.test.mjs",
|
|
216
|
+
"scripts/session-spawn-model.test.mjs",
|
|
217
|
+
"scripts/background-result-steer.test.mjs",
|
|
218
|
+
"scripts/session-report-watchdog.test.mjs",
|
|
219
|
+
"scripts/session-report-tool.test.mjs",
|
|
220
|
+
"scripts/awaiting-background-status.test.mjs"
|
|
208
221
|
],
|
|
209
222
|
"check:browser": [
|
|
210
223
|
"scripts/interrupt-browser.test.mjs",
|
|
@@ -243,6 +256,7 @@
|
|
|
243
256
|
"scripts/archive-restore-race-browser.test.mjs",
|
|
244
257
|
"scripts/tool-call-blocks-browser.test.mjs",
|
|
245
258
|
"scripts/model-notice-browser.test.mjs",
|
|
259
|
+
"scripts/length-warning-browser.test.mjs",
|
|
246
260
|
"scripts/dead-default-agent-browser.test.mjs",
|
|
247
261
|
"scripts/assistant-working-indicator-browser.test.mjs",
|
|
248
262
|
"scripts/thinking-first-delta-once-browser.test.mjs",
|
|
@@ -285,7 +299,10 @@
|
|
|
285
299
|
"scripts/orchestrated-order-role-browser.test.mjs",
|
|
286
300
|
"scripts/result-file-link-browser.test.mjs",
|
|
287
301
|
"scripts/git-panel-confirm-browser.test.mjs",
|
|
288
|
-
"scripts/session-origin-badge-browser.test.mjs"
|
|
302
|
+
"scripts/session-origin-badge-browser.test.mjs",
|
|
303
|
+
"scripts/rail-ui-layout-browser.test.mjs",
|
|
304
|
+
"scripts/session-landing-browser.test.mjs",
|
|
305
|
+
"scripts/responsive-rail-drawer-browser.test.mjs"
|
|
289
306
|
],
|
|
290
307
|
"dependencies": {
|
|
291
308
|
"@codemirror/commands": "^6.11.1",
|