@evandu/super-pi 0.1.147 → 0.1.149
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 +164 -2
- package/agents/super-pi-task/index.ts +46 -0
- package/dist/client/.super-pi-client-sources.json +1 -1
- package/dist/server/agents.d.ts +1 -1
- package/dist/server/agents.js +1 -1
- package/dist/server/application/session-orchestrator-service.d.ts +13 -2
- package/dist/server/application/session-orchestrator-service.js +50 -12
- package/dist/server/runtime.js +3 -3
- package/dist/server/task-tool.d.ts +4 -1
- package/dist/server/task-tool.js +24 -1
- package/package.json +2 -1
|
@@ -110,7 +110,7 @@ description: 用脚本清洗、转换、统计 Excel/CSV 数据
|
|
|
110
110
|
mode: primary
|
|
111
111
|
---
|
|
112
112
|
(正文从这里开始,写它的职责和工作方式。)保存后下一条消息即可使用;修改已有的自定义 Agent 就是编辑同一个目录里对应的 .md 文件。
|
|
113
|
-
- 用户要多个 agent 分工协作:单个子任务用 task 工具委托,有先后依赖的流程用 workflow 工具编排;同一流程要按明确结论行反复执行时用 loop 节点,要对 primary 自己列出的一批同构对象逐项执行时用 foreach 节点,两者可以任意嵌套。需要拆分多条独立并行工作线、多会话持续跟踪管理、或用户要求创建多个会话并行时,用 session_spawn 创建兄弟会话,并用 session_status / session_dispatch / session_stop
|
|
113
|
+
- 用户要多个 agent 分工协作:单个子任务用 task 工具委托,有先后依赖的流程用 workflow 工具编排;同一流程要按明确结论行反复执行时用 loop 节点,要对 primary 自己列出的一批同构对象逐项执行时用 foreach 节点,两者可以任意嵌套。需要拆分多条独立并行工作线、多会话持续跟踪管理、或用户要求创建多个会话并行时,用 session_spawn 创建兄弟会话,并用 session_status / session_dispatch / session_stop 监督管理。父会话可在 session_spawn 时用 watchdogTimeoutSeconds 声明「多久没收到汇报就提醒我一次」;被创建出来的子会话用 session_report 主动向自己的父会话汇报(status 用 in_progress 表示进行中、completed 表示完成、failed 表示失败)。`;
|
|
114
114
|
|
|
115
115
|
/** The parameters, OpenCode's `tool/todo.ts:6-8` minus its schema wrapper. */
|
|
116
116
|
export type TodoParams = { todos: TodoEntry[] };
|
|
@@ -1140,6 +1140,7 @@ export const SESSION_SPAWN_TOOL_NAME = "session_spawn";
|
|
|
1140
1140
|
export const SESSION_STATUS_TOOL_NAME = "session_status";
|
|
1141
1141
|
export const SESSION_DISPATCH_TOOL_NAME = "session_dispatch";
|
|
1142
1142
|
export const SESSION_STOP_TOOL_NAME = "session_stop";
|
|
1143
|
+
export const SESSION_REPORT_TOOL_NAME = "session_report";
|
|
1143
1144
|
|
|
1144
1145
|
/** Model-facing description for session_spawn. */
|
|
1145
1146
|
export const SESSION_SPAWN_DESCRIPTION = `Create a new sibling conversation in the current workspace and immediately dispatch its initial task.
|
|
@@ -1164,7 +1165,8 @@ Usage notes:
|
|
|
1164
1165
|
3. Use session_status with the returned conversationId to check progress, or session_dispatch to send further messages.
|
|
1165
1166
|
4. The new session reports completion to its DIRECT parent automatically. 'parentConversationId' optionally selects that parent; by default it is the calling session.
|
|
1166
1167
|
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.
|
|
1167
|
-
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
|
|
1168
|
+
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.
|
|
1169
|
+
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.`;
|
|
1168
1170
|
|
|
1169
1171
|
/** Model-facing description for session_status. */
|
|
1170
1172
|
export const SESSION_STATUS_DESCRIPTION = `Inspect the progress, run state, and latest activity of sibling conversations created by the orchestrator.
|
|
@@ -1207,6 +1209,28 @@ Usage notes:
|
|
|
1207
1209
|
- 'queued': only cancels pending queued messages; lets the currently running turn complete naturally.
|
|
1208
1210
|
2. Use 'messageId' if you want to cancel or interrupt one specific queued message instead of all queued messages.`;
|
|
1209
1211
|
|
|
1212
|
+
/** Model-facing description for session_report. */
|
|
1213
|
+
export const SESSION_REPORT_DESCRIPTION = `Report progress from THIS session to the parent session that spawned it.
|
|
1214
|
+
|
|
1215
|
+
WHEN TO USE THIS:
|
|
1216
|
+
- 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.
|
|
1217
|
+
- The parent set a watchdog (session_spawn's 'watchdogTimeoutSeconds'): a report is what restarts that clock, and silence is what triggers the reminder.
|
|
1218
|
+
- The task is over and the parent needs the outcome NOW rather than after the automatic end-of-turn result.
|
|
1219
|
+
|
|
1220
|
+
WHEN NOT TO USE THIS:
|
|
1221
|
+
- This session has no parent (the user opened it directly): there is nobody to report to, and the server refuses the call.
|
|
1222
|
+
- 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.
|
|
1223
|
+
|
|
1224
|
+
Usage notes:
|
|
1225
|
+
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.
|
|
1226
|
+
2. 'status' says what the report MEANS:
|
|
1227
|
+
- 'in_progress' (default): an interim update. The task continues, and the parent's watchdog clock restarts.
|
|
1228
|
+
- 'completed': the task is finished and this is your final word.
|
|
1229
|
+
- 'failed': the task could not be finished, and this says why.
|
|
1230
|
+
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'.
|
|
1231
|
+
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.
|
|
1232
|
+
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.`;
|
|
1233
|
+
|
|
1210
1234
|
/** Compact summary returned for an orchestrated session. */
|
|
1211
1235
|
export type OrchestratorSessionSummary = {
|
|
1212
1236
|
conversationId: string;
|
|
@@ -1271,6 +1295,13 @@ export type SessionSpawnParams = {
|
|
|
1271
1295
|
wait?: boolean;
|
|
1272
1296
|
/** Optional subdirectory (relative to the resolved workspace root) the new session's cwd should be rooted in. */
|
|
1273
1297
|
cwd?: string;
|
|
1298
|
+
/**
|
|
1299
|
+
* How long the PARENT is willing to wait for a report before it wants to be reminded, in seconds.
|
|
1300
|
+
*
|
|
1301
|
+
* The parent declares it once, at spawn time, and the watchdog never stops the child - it only
|
|
1302
|
+
* tells the parent that the child has gone quiet. A report from the child restarts the clock.
|
|
1303
|
+
*/
|
|
1304
|
+
watchdogTimeoutSeconds?: number;
|
|
1274
1305
|
};
|
|
1275
1306
|
|
|
1276
1307
|
export type SessionStatusParams = {
|
|
@@ -1291,6 +1322,18 @@ export type SessionStopParams = {
|
|
|
1291
1322
|
messageId?: string;
|
|
1292
1323
|
};
|
|
1293
1324
|
|
|
1325
|
+
/**
|
|
1326
|
+
* What this session is allowed to tell its parent.
|
|
1327
|
+
*
|
|
1328
|
+
* `status` is the model's word for what the report MEANS, and the server turns it into two
|
|
1329
|
+
* different things: an interim report restarts the watchdog clock, a terminal one cancels it and
|
|
1330
|
+
* silences the automatic end-of-turn result. The three values are the server's own vocabulary.
|
|
1331
|
+
*/
|
|
1332
|
+
export type SessionReportParams = {
|
|
1333
|
+
content: string;
|
|
1334
|
+
status?: "in_progress" | "completed" | "failed";
|
|
1335
|
+
};
|
|
1336
|
+
|
|
1294
1337
|
/**
|
|
1295
1338
|
* THE FORMAT THE MODEL IS TOLD TO USE. The client hands its model picker a single
|
|
1296
1339
|
* "provider/modelId" string, so this is the shape the model already knows how to read.
|
|
@@ -1303,6 +1346,34 @@ const SPAWN_MODEL_MISSING_PROVIDER_REFUSAL = `session_spawn's \`model\` paramete
|
|
|
1303
1346
|
|
|
1304
1347
|
const SPAWN_MODEL_MISSING_MODEL_ID_REFUSAL = `session_spawn's \`model\` parameter is missing \`modelId\`. It needs BOTH \`provider\` and \`modelId\` - ${SPAWN_MODEL_FORMAT}. Provide both, or leave \`model\` out entirely to use the workspace default model.`;
|
|
1305
1348
|
|
|
1349
|
+
/**
|
|
1350
|
+
* The silence watchdog the parent declares, or the reason it cannot be used.
|
|
1351
|
+
*
|
|
1352
|
+
* A NUMBER OF SECONDS, AND NOTHING ELSE. The server clamps whatever arrives into the range it
|
|
1353
|
+
* accepts, so a nonsense value would arrive as a real watchdog with a duration nobody chose - a
|
|
1354
|
+
* parent that asked for "about five minutes" and got three seconds of tolerance would be reminded
|
|
1355
|
+
* before the child had said anything. So a value that is not a positive number is refused HERE,
|
|
1356
|
+
* where the model is told exactly what to write instead.
|
|
1357
|
+
*
|
|
1358
|
+
* An ABSENT value is not a case here: it means "no watchdog", which is what a spawn without one has
|
|
1359
|
+
* always meant, and the body then carries no watchdog field at all.
|
|
1360
|
+
*/
|
|
1361
|
+
const SPAWN_WATCHDOG_REFUSAL = `session_spawn's \`watchdogTimeoutSeconds\` parameter must be a positive number of seconds - how long the parent is willing to wait for a report before it wants to be reminded. Provide a number such as 300, or leave the parameter out to run without a watchdog.`;
|
|
1362
|
+
|
|
1363
|
+
function readSpawnWatchdog(raw: unknown): { seconds?: number; error?: string } {
|
|
1364
|
+
if (raw === undefined || raw === null) return {};
|
|
1365
|
+
// A NUMBER, OR A STRING THAT IS ONE. Anything else - a boolean, an object, an array - is not a
|
|
1366
|
+
// duration, and `Number(true)` being 1 is exactly the kind of accident a duration must not have.
|
|
1367
|
+
const value = typeof raw === "number"
|
|
1368
|
+
? raw
|
|
1369
|
+
: (typeof raw === "string" && raw.trim() ? Number(raw) : Number.NaN);
|
|
1370
|
+
if (!Number.isFinite(value) || value <= 0) return { error: SPAWN_WATCHDOG_REFUSAL };
|
|
1371
|
+
// THE SERVER'S OWN FLOOR. A watchdog shorter than a second is not a watchdog, and the server
|
|
1372
|
+
// clamps it up to one second - so the tool does the same, and a parent that asked for "half a
|
|
1373
|
+
// second" gets a second rather than a watchdog that never fires.
|
|
1374
|
+
return { seconds: Math.max(1, Math.floor(value)) };
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1306
1377
|
/**
|
|
1307
1378
|
* The model selection the model asked for, or the reason it cannot be used.
|
|
1308
1379
|
*
|
|
@@ -1359,6 +1430,13 @@ export async function executeSessionSpawn(
|
|
|
1359
1430
|
if (selection.error) {
|
|
1360
1431
|
return text(selection.error, true);
|
|
1361
1432
|
}
|
|
1433
|
+
// THE SAME TREATMENT FOR THE WATCHDOG. A silence timeout that is not a number would reach the
|
|
1434
|
+
// server as a duration nobody chose, and the server would clamp it into a real watchdog anyway -
|
|
1435
|
+
// so the mistake is refused here, where the model can fix it in one call.
|
|
1436
|
+
const watchdog = readSpawnWatchdog(params.watchdogTimeoutSeconds);
|
|
1437
|
+
if (watchdog.error) {
|
|
1438
|
+
return text(watchdog.error, true);
|
|
1439
|
+
}
|
|
1362
1440
|
|
|
1363
1441
|
const send = deps.fetch ?? (globalFetch as unknown as TaskFetch);
|
|
1364
1442
|
try {
|
|
@@ -1381,6 +1459,9 @@ export async function executeSessionSpawn(
|
|
|
1381
1459
|
thinkingLevel: params.thinkingLevel?.trim() || undefined,
|
|
1382
1460
|
wait: Boolean(params.wait),
|
|
1383
1461
|
cwd: params.cwd?.trim() || undefined,
|
|
1462
|
+
// ONLY WHEN A DURATION WAS GIVEN. An absent watchdog leaves the field out entirely, and the
|
|
1463
|
+
// child then runs unwatched - which is what a spawn without one has always meant.
|
|
1464
|
+
...(watchdog.seconds ? { watchdogTimeoutSeconds: watchdog.seconds } : {}),
|
|
1384
1465
|
callerConversationId: config.conversationId || undefined,
|
|
1385
1466
|
parentConversationId: params.parentConversationId?.trim() || config.conversationId || undefined,
|
|
1386
1467
|
capability: config.capability,
|
|
@@ -1616,3 +1697,84 @@ export async function executeSessionStop(
|
|
|
1616
1697
|
return text(`session_stop network error: ${error instanceof Error ? error.message : String(error)}`, true);
|
|
1617
1698
|
}
|
|
1618
1699
|
}
|
|
1700
|
+
|
|
1701
|
+
/**
|
|
1702
|
+
* Execute session_report: THIS session tells its direct parent what is going on.
|
|
1703
|
+
*
|
|
1704
|
+
* WHO CALLS IT. The child. The parent has no use for it - the parent's half of this conversation is
|
|
1705
|
+
* `session_dispatch`, `session_status` and `session_stop`. The child has no other way to speak at
|
|
1706
|
+
* all: without this tool, everything a child knows stays in the child's transcript until the turn
|
|
1707
|
+
* ends, and a parent waiting on a long task has nothing to read in the meantime.
|
|
1708
|
+
*
|
|
1709
|
+
* WHY THE CALLER ID AND NOT A TARGET. The body carries THIS session's id and no target at all. The
|
|
1710
|
+
* server reads the parent off the conversation's own tree edge, so the report can only ever reach
|
|
1711
|
+
* the session that spawned this one: a sibling, a cousin, and the user's own session are out of
|
|
1712
|
+
* reach by construction rather than by a rule that could be forgotten. The server owns that rule and
|
|
1713
|
+
* refuses what does not belong to the tree.
|
|
1714
|
+
*
|
|
1715
|
+
* WHY A REPORT DOES NOT END THE TURN. The parent asked to hear from this child WHILE it worked; a
|
|
1716
|
+
* report that ended the child's turn would turn every interim update into a completion. So the
|
|
1717
|
+
* report is a message, and the child's turn continues.
|
|
1718
|
+
*/
|
|
1719
|
+
export async function executeSessionReport(
|
|
1720
|
+
params: SessionReportParams,
|
|
1721
|
+
config: OrchestratorToolConfig,
|
|
1722
|
+
deps: { signal?: AbortSignal; fetch?: TaskFetch } = {},
|
|
1723
|
+
): Promise<TaskToolResult> {
|
|
1724
|
+
const text = (body: string, isError?: boolean): TaskToolResult => ({
|
|
1725
|
+
content: [{ type: "text", text: body }],
|
|
1726
|
+
details: undefined,
|
|
1727
|
+
...(isError ? { isError: true } : {}),
|
|
1728
|
+
});
|
|
1729
|
+
|
|
1730
|
+
if (!config.endpoint) {
|
|
1731
|
+
return text("The session_report tool is not available in this session: SUPER_PI_ORCHESTRATOR_ENDPOINT is not set.", true);
|
|
1732
|
+
}
|
|
1733
|
+
if (!config.conversationId) {
|
|
1734
|
+
return text("The session_report tool is not available in this session: SUPER_PI_TASK_CONVERSATION is not set.", true);
|
|
1735
|
+
}
|
|
1736
|
+
if (!params.content || !params.content.trim()) {
|
|
1737
|
+
return text("session_report requires a non-empty `content` message.", true);
|
|
1738
|
+
}
|
|
1739
|
+
// THE STATUS IS THE MODEL'S OWN WORD, AND THE THREE VALUES ARE THE SERVER'S VOCABULARY. An
|
|
1740
|
+
// unknown value would reach the server as an interim report - the least consequential of the
|
|
1741
|
+
// three - so it is normalized here rather than travelling as something the server never saw.
|
|
1742
|
+
const status = params.status === "completed" || params.status === "failed" ? params.status : "in_progress";
|
|
1743
|
+
|
|
1744
|
+
const send = deps.fetch ?? (globalFetch as unknown as TaskFetch);
|
|
1745
|
+
try {
|
|
1746
|
+
const response = await send(config.endpoint, {
|
|
1747
|
+
method: "POST",
|
|
1748
|
+
headers: {
|
|
1749
|
+
"content-type": "application/json",
|
|
1750
|
+
...(config.token ? { authorization: `Bearer ${config.token}` } : {}),
|
|
1751
|
+
},
|
|
1752
|
+
body: JSON.stringify({
|
|
1753
|
+
action: "report",
|
|
1754
|
+
content: params.content.trim(),
|
|
1755
|
+
status,
|
|
1756
|
+
// THE CALLER IS THIS SESSION. There is no target field: the parent is the server's to
|
|
1757
|
+
// decide, and it decides from the tree edge.
|
|
1758
|
+
callerConversationId: config.conversationId,
|
|
1759
|
+
capability: config.capability,
|
|
1760
|
+
}),
|
|
1761
|
+
...(deps.signal ? { signal: deps.signal } : {}),
|
|
1762
|
+
});
|
|
1763
|
+
|
|
1764
|
+
const bodyText = await response.text();
|
|
1765
|
+
let payload: { ok?: boolean; delivered?: boolean; parentConversationId?: string; error?: string };
|
|
1766
|
+
try {
|
|
1767
|
+
payload = JSON.parse(bodyText);
|
|
1768
|
+
} catch {
|
|
1769
|
+
return text(`The orchestrator endpoint returned HTTP ${response.status}: ${bodyText}`, true);
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
if (!response.ok || !payload.ok || !payload.delivered) {
|
|
1773
|
+
return text(`Failed to report to the parent session: ${payload.error ?? `HTTP ${response.status}`}`, true);
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
return text(`Report delivered to parent session ${payload.parentConversationId ?? "(unknown)"}.`);
|
|
1777
|
+
} catch (error) {
|
|
1778
|
+
return text(`session_report network error: ${error instanceof Error ? error.message : String(error)}`, true);
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
@@ -41,19 +41,23 @@ import {
|
|
|
41
41
|
SESSION_STATUS_TOOL_NAME,
|
|
42
42
|
SESSION_DISPATCH_TOOL_NAME,
|
|
43
43
|
SESSION_STOP_TOOL_NAME,
|
|
44
|
+
SESSION_REPORT_TOOL_NAME,
|
|
44
45
|
SESSION_SPAWN_DESCRIPTION,
|
|
45
46
|
SESSION_STATUS_DESCRIPTION,
|
|
46
47
|
SESSION_DISPATCH_DESCRIPTION,
|
|
47
48
|
SESSION_STOP_DESCRIPTION,
|
|
49
|
+
SESSION_REPORT_DESCRIPTION,
|
|
48
50
|
executeSessionSpawn,
|
|
49
51
|
executeSessionStatus,
|
|
50
52
|
executeSessionDispatch,
|
|
51
53
|
executeSessionStop,
|
|
54
|
+
executeSessionReport,
|
|
52
55
|
readOrchestratorConfig,
|
|
53
56
|
type SessionSpawnParams,
|
|
54
57
|
type SessionStatusParams,
|
|
55
58
|
type SessionDispatchParams,
|
|
56
59
|
type SessionStopParams,
|
|
60
|
+
type SessionReportParams,
|
|
57
61
|
} from "./core.ts";
|
|
58
62
|
|
|
59
63
|
/**
|
|
@@ -471,6 +475,9 @@ const extension = (pi: ExtensionApi): void => {
|
|
|
471
475
|
cwd: Type.Optional(Type.String({
|
|
472
476
|
description: "Optional workspace-root-relative subdirectory (for example, packages/a). Absolute paths, '..' traversal, UNC paths, and escaping symlinks are rejected; omit it to use the workspace root.",
|
|
473
477
|
})),
|
|
478
|
+
watchdogTimeoutSeconds: Type.Optional(Type.Number({
|
|
479
|
+
description: "How long YOU are willing to wait for a report from the new session before you want to be reminded, in seconds (for example 300 for five minutes). A report from the child restarts the clock; the watchdog never stops the child. Omit it to run without a watchdog.",
|
|
480
|
+
})),
|
|
474
481
|
});
|
|
475
482
|
|
|
476
483
|
const SessionStatusParameters = Type.Object({
|
|
@@ -513,6 +520,27 @@ const extension = (pi: ExtensionApi): void => {
|
|
|
513
520
|
})),
|
|
514
521
|
});
|
|
515
522
|
|
|
523
|
+
/**
|
|
524
|
+
* The `session_report` parameter schema.
|
|
525
|
+
*
|
|
526
|
+
* `content` is required, because a report with nothing in it is a report that says nothing - and
|
|
527
|
+
* the parent reads it as if it had been told something. `status` is a closed union of the three
|
|
528
|
+
* words the server understands, so a model cannot invent a fourth one that arrives as an
|
|
529
|
+
* unintended interim update.
|
|
530
|
+
*/
|
|
531
|
+
const SessionReportParameters = Type.Object({
|
|
532
|
+
content: Type.String({
|
|
533
|
+
description: "The text the parent receives. Write it for the parent: what is done, what is left, what you need.",
|
|
534
|
+
}),
|
|
535
|
+
status: Type.Optional(Type.Union([
|
|
536
|
+
Type.Literal("in_progress", { description: "An interim update; the task continues and the parent's watchdog clock restarts (default)" }),
|
|
537
|
+
Type.Literal("completed", { description: "The task is finished and this is the final report" }),
|
|
538
|
+
Type.Literal("failed", { description: "The task could not be finished; say why" }),
|
|
539
|
+
], {
|
|
540
|
+
description: "What this report means: 'in_progress' (default), 'completed', or 'failed'.",
|
|
541
|
+
})),
|
|
542
|
+
});
|
|
543
|
+
|
|
516
544
|
if (orchestratorConfig.endpoint) {
|
|
517
545
|
pi.registerTool({
|
|
518
546
|
name: SESSION_SPAWN_TOOL_NAME,
|
|
@@ -553,6 +581,24 @@ const extension = (pi: ExtensionApi): void => {
|
|
|
553
581
|
execute: async (_toolCallId, params, signal) =>
|
|
554
582
|
await executeSessionStop(params as SessionStopParams, orchestratorConfig, { signal: signal ?? undefined }),
|
|
555
583
|
});
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* `session_report`, registered AFTER the four tools that act ON OTHER SESSIONS.
|
|
587
|
+
*
|
|
588
|
+
* The order is the reading order, and this one is the odd one out: the other four are a
|
|
589
|
+
* PARENT's tools - they reach out and manage somebody else's session - while this one is the
|
|
590
|
+
* CHILD's own voice. A model reading the list top to bottom meets the manager's instruments
|
|
591
|
+
* first and the worker's mouth last, which is the order the relationship actually has.
|
|
592
|
+
*/
|
|
593
|
+
pi.registerTool({
|
|
594
|
+
name: SESSION_REPORT_TOOL_NAME,
|
|
595
|
+
label: "Report to Parent",
|
|
596
|
+
description: SESSION_REPORT_DESCRIPTION,
|
|
597
|
+
promptSnippet: "report progress or a blocker to the parent session that spawned this one",
|
|
598
|
+
parameters: SessionReportParameters,
|
|
599
|
+
execute: async (_toolCallId, params, signal) =>
|
|
600
|
+
await executeSessionReport(params as SessionReportParams, orchestratorConfig, { signal: signal ?? undefined }),
|
|
601
|
+
});
|
|
556
602
|
}
|
|
557
603
|
};
|
|
558
604
|
|
|
@@ -35,5 +35,5 @@
|
|
|
35
35
|
"src/client/types.ts": "7c06189b32890009d28a371a64d9f29161c14a349a5b399d51d1314d38fa27ac",
|
|
36
36
|
"src/client/vite-env.d.ts": "65996936fbb042915f7b74a200fcdde7e410f32a669b1ab9597cfaa4b0faddb5"
|
|
37
37
|
},
|
|
38
|
-
"recordedAt": "2026-11-
|
|
38
|
+
"recordedAt": "2026-11-01T14:53:57.934Z"
|
|
39
39
|
}
|
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", "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"];
|
|
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", "session_report", "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
|
@@ -40,7 +40,7 @@ export const PI_TOOL_NAMES = [
|
|
|
40
40
|
export const SUPER_PI_TOOL_NAMES = [
|
|
41
41
|
"task", "todowrite", "workflow", "run_async", "scheduled_task", "question", "tool_register",
|
|
42
42
|
"memory", "skill_manage", "session_search",
|
|
43
|
-
"session_spawn", "session_status", "session_dispatch", "session_stop",
|
|
43
|
+
"session_spawn", "session_status", "session_dispatch", "session_stop", "session_report",
|
|
44
44
|
"knowledge_search", "knowledge_import",
|
|
45
45
|
"document_read", "document_write", "web_search", "web_fetch", "feishu_doc_read", "feishu_doc_write",
|
|
46
46
|
];
|
|
@@ -110,16 +110,27 @@ export declare class SessionOrchestratorService {
|
|
|
110
110
|
*/
|
|
111
111
|
private readonly lengthContinuations;
|
|
112
112
|
/**
|
|
113
|
-
* THE CHILDREN THAT HAVE ALREADY REPORTED THEIR OWN RESULT TO THEIR PARENT.
|
|
113
|
+
* THE CHILDREN THAT HAVE ALREADY REPORTED THEIR OWN RESULT TO THEIR PARENT, AND WHEN.
|
|
114
114
|
*
|
|
115
115
|
* A child can report explicitly - the `report` action - and that report is the child's own
|
|
116
116
|
* statement of what it did. When the child's turn then ends, the automatic completion report
|
|
117
117
|
* would deliver the same thing a second time, so the automatic path steps aside for a child that
|
|
118
|
-
* is in this
|
|
118
|
+
* is in this map.
|
|
119
119
|
*
|
|
120
120
|
* The entry is added only by a TERMINAL report (`completed` / `failed`), because an interim
|
|
121
121
|
* update is not the child's final word: the task is still running, and the parent will want to
|
|
122
122
|
* hear how it ended.
|
|
123
|
+
*
|
|
124
|
+
* WHY THE MAP HOLDS A TIMESTAMP AND NOT A FLAG. A flag says "this child reported", and that is
|
|
125
|
+
* not the same question as "this child reported ABOUT THE TURN THAT JUST ENDED". A flag stays
|
|
126
|
+
* true for the rest of the child's life, so it silences every automatic report the child ever
|
|
127
|
+
* produces again: the parent gives the same child a second task, the child finishes, and the
|
|
128
|
+
* result is swallowed because of a report the parent received about the FIRST task. The parent
|
|
129
|
+
* waits for a result that was written and never arrives, and nothing in the transcript says why.
|
|
130
|
+
*
|
|
131
|
+
* The timestamp answers the real question: the report was made INSIDE this turn, so it speaks for
|
|
132
|
+
* this turn. A report made before the turn started speaks for an earlier one, and this turn's
|
|
133
|
+
* result is still its own.
|
|
123
134
|
*/
|
|
124
135
|
private readonly reportedChildren;
|
|
125
136
|
constructor(store: Store, conversationService: ConversationService, runService: RunService, ledger: SessionLedger, broadcaster?: EventBroadcaster | undefined, watchdog?: SessionWatchdog);
|
|
@@ -39,18 +39,29 @@ export class SessionOrchestratorService {
|
|
|
39
39
|
*/
|
|
40
40
|
lengthContinuations = new Map();
|
|
41
41
|
/**
|
|
42
|
-
* THE CHILDREN THAT HAVE ALREADY REPORTED THEIR OWN RESULT TO THEIR PARENT.
|
|
42
|
+
* THE CHILDREN THAT HAVE ALREADY REPORTED THEIR OWN RESULT TO THEIR PARENT, AND WHEN.
|
|
43
43
|
*
|
|
44
44
|
* A child can report explicitly - the `report` action - and that report is the child's own
|
|
45
45
|
* statement of what it did. When the child's turn then ends, the automatic completion report
|
|
46
46
|
* would deliver the same thing a second time, so the automatic path steps aside for a child that
|
|
47
|
-
* is in this
|
|
47
|
+
* is in this map.
|
|
48
48
|
*
|
|
49
49
|
* The entry is added only by a TERMINAL report (`completed` / `failed`), because an interim
|
|
50
50
|
* update is not the child's final word: the task is still running, and the parent will want to
|
|
51
51
|
* hear how it ended.
|
|
52
|
+
*
|
|
53
|
+
* WHY THE MAP HOLDS A TIMESTAMP AND NOT A FLAG. A flag says "this child reported", and that is
|
|
54
|
+
* not the same question as "this child reported ABOUT THE TURN THAT JUST ENDED". A flag stays
|
|
55
|
+
* true for the rest of the child's life, so it silences every automatic report the child ever
|
|
56
|
+
* produces again: the parent gives the same child a second task, the child finishes, and the
|
|
57
|
+
* result is swallowed because of a report the parent received about the FIRST task. The parent
|
|
58
|
+
* waits for a result that was written and never arrives, and nothing in the transcript says why.
|
|
59
|
+
*
|
|
60
|
+
* The timestamp answers the real question: the report was made INSIDE this turn, so it speaks for
|
|
61
|
+
* this turn. A report made before the turn started speaks for an earlier one, and this turn's
|
|
62
|
+
* result is still its own.
|
|
52
63
|
*/
|
|
53
|
-
reportedChildren = new
|
|
64
|
+
reportedChildren = new Map();
|
|
54
65
|
constructor(store, conversationService, runService, ledger, broadcaster, watchdog) {
|
|
55
66
|
this.store = store;
|
|
56
67
|
this.conversationService = conversationService;
|
|
@@ -180,6 +191,11 @@ export class SessionOrchestratorService {
|
|
|
180
191
|
// writes the words under the reader's own name - so the child's transcript showed an
|
|
181
192
|
// instruction the reader never typed, wearing the reader's avatar, and a reader who looked at
|
|
182
193
|
// the child session saw their own name on somebody else's order.
|
|
194
|
+
//
|
|
195
|
+
// THE TURN'S START IS RECORDED HERE, because the question "did this child already report THIS
|
|
196
|
+
// turn?" is answered by comparing the report's time against it. A report made inside the turn
|
|
197
|
+
// speaks for the turn; a report made before it speaks for an earlier one.
|
|
198
|
+
const turnStartedAt = Date.now();
|
|
183
199
|
const runPromise = this.runService.enqueueAndRunAsAgent(conversationId, content);
|
|
184
200
|
const completion = (async () => {
|
|
185
201
|
let failure;
|
|
@@ -229,14 +245,23 @@ export class SessionOrchestratorService {
|
|
|
229
245
|
const result = await this.persistSessionResult(conversationId, failure);
|
|
230
246
|
if (result) {
|
|
231
247
|
const current = this.store.conversation(conversationId);
|
|
232
|
-
//
|
|
233
|
-
//
|
|
234
|
-
//
|
|
235
|
-
|
|
236
|
-
//
|
|
237
|
-
//
|
|
238
|
-
|
|
239
|
-
|
|
248
|
+
// THE REPORT GOES OUT ONLY WHEN THE CHILD'S TURN ENDED. The statuses that mean "the turn is
|
|
249
|
+
// over" are `completed` and `failed`; `running` and `awaiting_background` mean work is still
|
|
250
|
+
// in flight and its result is not final, and `stopped` means somebody pressed stop, so the
|
|
251
|
+
// half-finished answer on screen is not a result anybody stated.
|
|
252
|
+
//
|
|
253
|
+
// This is what separates the two channels. The automatic report says "the task ended, here
|
|
254
|
+
// is what it produced"; the explicit report says "the child is telling its parent something
|
|
255
|
+
// now", and can be called at any point in the task's life.
|
|
256
|
+
const turnEnded = current?.status === "completed" || current?.status === "failed";
|
|
257
|
+
// A CHILD THAT REPORTED ITSELF IS NOT REPORTED TWICE - BUT ONLY FOR THE TURN IT REPORTED.
|
|
258
|
+
// The explicit terminal report is the child's own statement of what it did, so the
|
|
259
|
+
// automatic completion steps aside for it - when the report was made inside THIS turn.
|
|
260
|
+
// A report from an earlier turn says nothing about this one's result, and silencing this
|
|
261
|
+
// one because of that would swallow a result the parent is still waiting for.
|
|
262
|
+
const reportedAt = this.reportedChildren.get(conversationId);
|
|
263
|
+
const alreadyReported = reportedAt !== undefined && reportedAt >= turnStartedAt;
|
|
264
|
+
if (turnEnded && !alreadyReported) {
|
|
240
265
|
await this.runService.enqueueSessionResult({
|
|
241
266
|
conversationId,
|
|
242
267
|
parentConversationId: current?.parentConversationId,
|
|
@@ -255,6 +280,16 @@ export class SessionOrchestratorService {
|
|
|
255
280
|
// turn an already-finished child turn into a second failure.
|
|
256
281
|
console.warn(`Super Pi: session result callback failed for ${conversationId}:`, error instanceof Error ? error.message : String(error));
|
|
257
282
|
}
|
|
283
|
+
finally {
|
|
284
|
+
// THE RECORD IS SPENT BY THE TURN IT SPOKE FOR. Whatever it was about, that turn is over.
|
|
285
|
+
//
|
|
286
|
+
// A record left behind would be harmless - the timestamp comparison already keeps it from
|
|
287
|
+
// silencing a later turn - but it would keep saying "this child reported" to every future
|
|
288
|
+
// reader of this code, and the next thing that consults it would have to re-derive the
|
|
289
|
+
// timestamp rule. Dropping it here keeps one rule in one place: a report belongs to the
|
|
290
|
+
// turn that was running when it was made.
|
|
291
|
+
this.reportedChildren.delete(conversationId);
|
|
292
|
+
}
|
|
258
293
|
if (failure !== undefined) {
|
|
259
294
|
console.warn(`Super Pi: dispatch failed for conversation ${conversationId}:`, failure instanceof Error ? failure.message : String(failure));
|
|
260
295
|
this.ledger.update(conversationId, { status: "failed" });
|
|
@@ -386,7 +421,10 @@ export class SessionOrchestratorService {
|
|
|
386
421
|
// does not silence the automatic report: the child is still working, and the parent will want
|
|
387
422
|
// to hear how the task ended.
|
|
388
423
|
if (status === "completed" || status === "failed") {
|
|
389
|
-
|
|
424
|
+
// THE TIME OF THE REPORT IS THE PART THAT MATTERS. It is what tells the completion path
|
|
425
|
+
// whether this report spoke for the turn that is ending or for an earlier one, and it is
|
|
426
|
+
// stamped here - where the report is made - rather than where it is looked up later.
|
|
427
|
+
this.reportedChildren.set(childConversationId, Date.now());
|
|
390
428
|
await this.watchdog.cancel(childConversationId);
|
|
391
429
|
}
|
|
392
430
|
else {
|
package/dist/server/runtime.js
CHANGED
|
@@ -24,7 +24,7 @@ import { LengthExceededError } from "./turn-failure.js";
|
|
|
24
24
|
// paths recognise a delivery identically. Re-exported because they were part of this module's surface.
|
|
25
25
|
export { describeDeliveredUserMessage, describePiQueue } from "./pi-session-events.js";
|
|
26
26
|
import { createPiSession, createSdkCatalog, piSdkAvailable, resolveTranscriptFile, superPiExtensionPaths } from "./pi-session.js";
|
|
27
|
-
import { TASK_CONVERSATION_ENV, PARENT_CONVERSATION_ENV, TASK_DEPTH_ENV, TASK_ENDPOINT_ENV, TASK_EXEC_ENDPOINT_ENV, TASK_ROSTER_ENV, TASK_TOOL_NAME, TODO_ENDPOINT_ENV, TODO_TOOL_NAME, SCHEDULED_TASK_ENDPOINT_ENV, SCHEDULED_TASK_TOOL_NAME, WORKFLOW_ENDPOINT_ENV, WORKFLOW_TOOL_NAME, RUN_ASYNC_TOOL_NAME, ORCHESTRATOR_ENDPOINT_ENV, ORCHESTRATOR_WORKSPACE_ENV, SESSION_SPAWN_TOOL_NAME, SESSION_STATUS_TOOL_NAME, SESSION_DISPATCH_TOOL_NAME, SESSION_STOP_TOOL_NAME, rosterPayload, } from "./task-tool.js";
|
|
27
|
+
import { TASK_CONVERSATION_ENV, PARENT_CONVERSATION_ENV, TASK_DEPTH_ENV, TASK_ENDPOINT_ENV, TASK_EXEC_ENDPOINT_ENV, TASK_ROSTER_ENV, TASK_TOOL_NAME, TODO_ENDPOINT_ENV, TODO_TOOL_NAME, SCHEDULED_TASK_ENDPOINT_ENV, SCHEDULED_TASK_TOOL_NAME, WORKFLOW_ENDPOINT_ENV, WORKFLOW_TOOL_NAME, RUN_ASYNC_TOOL_NAME, ORCHESTRATOR_ENDPOINT_ENV, ORCHESTRATOR_WORKSPACE_ENV, SESSION_SPAWN_TOOL_NAME, SESSION_STATUS_TOOL_NAME, SESSION_DISPATCH_TOOL_NAME, SESSION_STOP_TOOL_NAME, SESSION_REPORT_TOOL_NAME, rosterPayload, } from "./task-tool.js";
|
|
28
28
|
import { BLOCKING_UI_METHODS, EXTENSION_UI_METHODS, } from "./types.js";
|
|
29
29
|
import { loadConversationImages } from "./upload.js";
|
|
30
30
|
import { describeToolCall, describeToolCallArgs, describeToolCallInProgress, extractPartialResultText, extractToolResultText, readQuestionCall } from "./tool-calls.js";
|
|
@@ -1260,7 +1260,7 @@ export class PiRpcRuntime {
|
|
|
1260
1260
|
if (process.env.SUPER_PI_FEISHU_ENDPOINT)
|
|
1261
1261
|
names.push(FEISHU_READ_TOOL_NAME, FEISHU_WRITE_TOOL_NAME);
|
|
1262
1262
|
if (this.options.orchestratorEndpoint)
|
|
1263
|
-
names.push(SESSION_SPAWN_TOOL_NAME, SESSION_STATUS_TOOL_NAME, SESSION_DISPATCH_TOOL_NAME, SESSION_STOP_TOOL_NAME);
|
|
1263
|
+
names.push(SESSION_SPAWN_TOOL_NAME, SESSION_STATUS_TOOL_NAME, SESSION_DISPATCH_TOOL_NAME, SESSION_STOP_TOOL_NAME, SESSION_REPORT_TOOL_NAME);
|
|
1264
1264
|
return names;
|
|
1265
1265
|
}
|
|
1266
1266
|
/**
|
|
@@ -1479,7 +1479,7 @@ export class PiRpcRuntime {
|
|
|
1479
1479
|
// allowlist and naming a tool Pi has not registered would change the spawn line for every other
|
|
1480
1480
|
// mode. The prompt asks for a bare JSON array and nothing else; if the model spends a turn on a
|
|
1481
1481
|
// tool call anyway the reranker times out and falls back to the lexical order.
|
|
1482
|
-
const agentArgs = memoryReview ? ["--no-extensions", "--no-skills", "--no-mcp", "--no-context-files", "--no-prompt-templates", "--tools", "memory,skill_manage,session_search"] : knowledgeRerank ? ["--no-extensions", "--no-skills", "--no-mcp", "--no-context-files", "--no-prompt-templates"] : await agentSpawnArgs(this.agents.promptDir, agent, { extraAllowedTools: [...(extensionArgs.length ? [TASK_TOOL_NAME, TODO_TOOL_NAME, WORKFLOW_TOOL_NAME, RUN_ASYNC_TOOL_NAME, SCHEDULED_TASK_TOOL_NAME, "question", "tool_register", ...(this.options.orchestratorEndpoint ? [SESSION_SPAWN_TOOL_NAME, SESSION_STATUS_TOOL_NAME, SESSION_DISPATCH_TOOL_NAME, SESSION_STOP_TOOL_NAME] : [])] : []), ...(memoryEnabled ? ["memory", "skill_manage", "session_search"] : []), ...(knowledgeEnabled ? [KNOWLEDGE_TOOL_NAME, KNOWLEDGE_IMPORT_TOOL_NAME] : []), ...(documentsEnabled ? [DOCUMENT_READ_TOOL_NAME, DOCUMENT_WRITE_TOOL_NAME] : []), ...(webEnabled ? [WEB_SEARCH_TOOL_NAME, WEB_FETCH_TOOL_NAME] : []), ...(feishuEnabled ? [FEISHU_READ_TOOL_NAME, FEISHU_WRITE_TOOL_NAME] : [])] });
|
|
1482
|
+
const agentArgs = memoryReview ? ["--no-extensions", "--no-skills", "--no-mcp", "--no-context-files", "--no-prompt-templates", "--tools", "memory,skill_manage,session_search"] : knowledgeRerank ? ["--no-extensions", "--no-skills", "--no-mcp", "--no-context-files", "--no-prompt-templates"] : await agentSpawnArgs(this.agents.promptDir, agent, { extraAllowedTools: [...(extensionArgs.length ? [TASK_TOOL_NAME, TODO_TOOL_NAME, WORKFLOW_TOOL_NAME, RUN_ASYNC_TOOL_NAME, SCHEDULED_TASK_TOOL_NAME, "question", "tool_register", ...(this.options.orchestratorEndpoint ? [SESSION_SPAWN_TOOL_NAME, SESSION_STATUS_TOOL_NAME, SESSION_DISPATCH_TOOL_NAME, SESSION_STOP_TOOL_NAME, SESSION_REPORT_TOOL_NAME] : [])] : []), ...(memoryEnabled ? ["memory", "skill_manage", "session_search"] : []), ...(knowledgeEnabled ? [KNOWLEDGE_TOOL_NAME, KNOWLEDGE_IMPORT_TOOL_NAME] : []), ...(documentsEnabled ? [DOCUMENT_READ_TOOL_NAME, DOCUMENT_WRITE_TOOL_NAME] : []), ...(webEnabled ? [WEB_SEARCH_TOOL_NAME, WEB_FETCH_TOOL_NAME] : []), ...(feishuEnabled ? [FEISHU_READ_TOOL_NAME, FEISHU_WRITE_TOOL_NAME] : [])] });
|
|
1483
1483
|
// Refused here, with the reason, rather than handed to the spawn: an ENOENT from `spawn` reads as
|
|
1484
1484
|
// "the app is broken" and hides the one fact that matters, which is where it looked for Pi.
|
|
1485
1485
|
if (!this.pi.found)
|
|
@@ -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.\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.";
|
|
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
|
|
|
@@ -385,7 +386,8 @@ Usage notes:
|
|
|
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
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.
|
|
388
|
-
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
|
|
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.`;
|
|
389
391
|
/** Model-facing description for session_status. */
|
|
390
392
|
export const SESSION_STATUS_DESCRIPTION = `Inspect the progress, run state, and latest activity of sibling conversations created by the orchestrator.
|
|
391
393
|
|
|
@@ -424,3 +426,24 @@ Usage notes:
|
|
|
424
426
|
- 'active': only interrupts the active running turn; leaves any pending queued messages untouched.
|
|
425
427
|
- 'queued': only cancels pending queued messages; lets the currently running turn complete naturally.
|
|
426
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.`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evandu/super-pi",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.149",
|
|
4
4
|
"description": "Web control plane and companion for Pi",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -216,6 +216,7 @@
|
|
|
216
216
|
"scripts/session-spawn-model.test.mjs",
|
|
217
217
|
"scripts/background-result-steer.test.mjs",
|
|
218
218
|
"scripts/session-report-watchdog.test.mjs",
|
|
219
|
+
"scripts/session-report-tool.test.mjs",
|
|
219
220
|
"scripts/awaiting-background-status.test.mjs"
|
|
220
221
|
],
|
|
221
222
|
"check:browser": [
|