@evandu/super-pi 0.1.139 → 0.1.140
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 +418 -1
- package/agents/super-pi-task/index.ts +115 -0
- package/dist/.super-pi-check-inputs.json +2 -2
- package/dist/client/.super-pi-client-sources.json +6 -5
- package/dist/client/assets/{CodeMirrorEditor-H7SHGhdS.js → CodeMirrorEditor-DvZeOLny.js} +2 -2
- package/dist/client/assets/{dist-CukPKrEn.js → dist-3BuyVG3H.js} +1 -1
- package/dist/client/assets/{dist-cP2vnTP_.js → dist-BCUfH4nP.js} +1 -1
- package/dist/client/assets/{dist-C-MUXK_t.js → dist-BIIMtU9W.js} +1 -1
- package/dist/client/assets/{dist-BfUeRJKr.js → dist-C-QmRwbd.js} +1 -1
- package/dist/client/assets/{dist-CZ9Krsno.js → dist-CTkzLBdb.js} +1 -1
- package/dist/client/assets/{dist-BxTGjU84.js → dist-C_VuSpx8.js} +1 -1
- package/dist/client/assets/{dist-NlopeJ5R.js → dist-CaUsvSz1.js} +1 -1
- package/dist/client/assets/{dist-zUJqdbfo.js → dist-Cb1gxS57.js} +1 -1
- package/dist/client/assets/{dist-C8NllalC.js → dist-CqDSeoUk.js} +1 -1
- package/dist/client/assets/{dist-DLKVFZhO.js → dist-D7yfnKNC.js} +1 -1
- package/dist/client/assets/{dist-CUNctZ5v.js → dist-DA8yW4lK.js} +1 -1
- package/dist/client/assets/{dist-Jw-GQ-re.js → dist-DAcS4Kj8.js} +1 -1
- package/dist/client/assets/{dist-C_ftUQZN.js → dist-DN0M4DAX.js} +1 -1
- package/dist/client/assets/{dist-2XXLO-IZ.js → dist-DRgWZqFH.js} +1 -1
- package/dist/client/assets/{dist-POoQ4j29.js → dist-DdfW8Gjv.js} +1 -1
- package/dist/client/assets/{dist-BEM6tttY.js → dist-DlQ8PMvv.js} +1 -1
- package/dist/client/assets/{dist-CSdzvQnf.js → dist-Dvca0LeR.js} +1 -1
- package/dist/client/assets/{dist-DJ8SwQl_.js → dist-HJdP1yxN.js} +1 -1
- package/dist/client/assets/{dist-CuMnZOGE.js → dist-gBAr9yXF.js} +1 -1
- package/dist/client/assets/{dist-BokzcFLE.js → dist-gyzurUDR.js} +1 -1
- package/dist/client/assets/{dist-BoucVzKA.js → dist-qG53hT1b.js} +1 -1
- package/dist/client/assets/{dist-D3Ta0y4e.js → dist-r4Kq1ABA.js} +1 -1
- package/dist/client/assets/{index-B32VZVYx.js → index-CNbh1fL2.js} +9 -9
- package/dist/client/assets/index-DCSFD4VV.css +1 -0
- package/dist/client/index.html +2 -2
- package/dist/server/application/session-orchestrator-service.d.ts +113 -0
- package/dist/server/application/session-orchestrator-service.js +234 -0
- package/dist/server/http-server.js +135 -0
- package/dist/server/index.js +20 -2
- package/dist/server/runtime.d.ts +1 -0
- package/dist/server/runtime.js +6 -2
- package/dist/server/server-context.d.ts +2 -0
- package/dist/server/session-ledger.d.ts +60 -0
- package/dist/server/session-ledger.js +76 -0
- package/dist/server/subagent-runs.d.ts +31 -1
- package/dist/server/subagent-runs.js +30 -13
- package/dist/server/task-tool.d.ts +18 -0
- package/dist/server/task-tool.js +85 -0
- package/dist/server/types.d.ts +23 -0
- package/dist/server/ws-server.d.ts +52 -1
- package/dist/server/ws-server.js +151 -35
- package/package.json +3 -2
- package/dist/client/assets/index-Bdym8BPo.css +0 -1
|
@@ -106,7 +106,7 @@ description: 用脚本清洗、转换、统计 Excel/CSV 数据
|
|
|
106
106
|
mode: primary
|
|
107
107
|
---
|
|
108
108
|
(正文从这里开始,写它的职责和工作方式。)保存后下一条消息即可使用;修改已有的自定义 Agent 就是编辑同一个目录里对应的 .md 文件。
|
|
109
|
-
- 用户要多个 agent 分工协作:单个子任务用 task 工具委托,有先后依赖的流程用 workflow
|
|
109
|
+
- 用户要多个 agent 分工协作:单个子任务用 task 工具委托,有先后依赖的流程用 workflow 工具编排;需要拆分多条独立并行工作线、多会话持续跟踪管理、或用户要求创建多个会话并行时,用 session_spawn 创建兄弟会话,并用 session_status / session_dispatch / session_stop 监督管理。`;
|
|
110
110
|
|
|
111
111
|
/** The parameters, OpenCode's `tool/todo.ts:6-8` minus its schema wrapper. */
|
|
112
112
|
export type TodoParams = { todos: TodoEntry[] };
|
|
@@ -1099,3 +1099,420 @@ export async function executeScheduledTask(
|
|
|
1099
1099
|
if (action === "delete") return text(`已删除定时任务「${task.name ?? task.id}」。`);
|
|
1100
1100
|
return text(`已保存定时任务「${task.name ?? task.id}」:${payload.describe ?? "按计划"},下次触发 ${next}。用户可以在右侧栏「任务」tab 里看到它、修改提示词与模型、暂停它、立刻手动触发一次,或者删除它。`);
|
|
1101
1101
|
}
|
|
1102
|
+
|
|
1103
|
+
// ---------------------------------------------------------------------------------------------
|
|
1104
|
+
// The `session_*` orchestrator tools: manage sibling conversations in the current workspace.
|
|
1105
|
+
//
|
|
1106
|
+
// WHY THESE TOOLS EXIST. A primary agent can already delegate a single transient job to a subagent
|
|
1107
|
+
// via `task` (a one-shot Pi process), or pipeline 2-8 steps via `workflow`. What was missing was the
|
|
1108
|
+
// ability to spin up INDEPENDENT, PERSISTENT sibling conversations in Super Pi — sessions that appear
|
|
1109
|
+
// in the workspace sidebar, carry their own full message history, run concurrently in the background,
|
|
1110
|
+
// and can be conversed with over multiple turns via `session_dispatch`, monitored via `session_status`,
|
|
1111
|
+
// or cancelled via `session_stop`.
|
|
1112
|
+
//
|
|
1113
|
+
// WHY THEY DIFFER FROM `task`. `task` runs an ephemeral child process: it has no conversation history,
|
|
1114
|
+
// cannot be conversed with across turns, does not appear in the sidebar, and dies upon returning its
|
|
1115
|
+
// final text. Sibling conversations are first-class Super Pi conversations: they retain their own
|
|
1116
|
+
// transcripts, can be opened and viewed by the user in the UI, and can execute complex multi-phase
|
|
1117
|
+
// work in parallel with the orchestrator.
|
|
1118
|
+
// ---------------------------------------------------------------------------------------------
|
|
1119
|
+
|
|
1120
|
+
/**
|
|
1121
|
+
* The orchestrator extension's half of the environment contract.
|
|
1122
|
+
* `src/server/task-tool.ts` is the other end; `scripts/task-tool.test.mjs` asserts the two copies agree.
|
|
1123
|
+
*/
|
|
1124
|
+
export const ORCHESTRATOR_ENDPOINT_ENV = "SUPER_PI_ORCHESTRATOR_ENDPOINT";
|
|
1125
|
+
|
|
1126
|
+
/** Tool names registered with Pi. */
|
|
1127
|
+
export const SESSION_SPAWN_TOOL_NAME = "session_spawn";
|
|
1128
|
+
export const SESSION_STATUS_TOOL_NAME = "session_status";
|
|
1129
|
+
export const SESSION_DISPATCH_TOOL_NAME = "session_dispatch";
|
|
1130
|
+
export const SESSION_STOP_TOOL_NAME = "session_stop";
|
|
1131
|
+
|
|
1132
|
+
/** Model-facing description for session_spawn. */
|
|
1133
|
+
export const SESSION_SPAWN_DESCRIPTION = `Create a new sibling conversation in the current workspace and immediately dispatch its initial task.
|
|
1134
|
+
|
|
1135
|
+
DIFFERENCE FROM THE 'task' TOOL:
|
|
1136
|
+
- 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.
|
|
1137
|
+
- '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.
|
|
1138
|
+
|
|
1139
|
+
WHEN TO USE THIS:
|
|
1140
|
+
- Long-running, multi-phase tracks of work that require ongoing context, multiple turns of dialogue, or independent user visibility.
|
|
1141
|
+
- Parallel work streams (e.g., Track A: backend refactor, Track B: frontend component update) where both need full conversational capabilities.
|
|
1142
|
+
- When the user asks you to act as an orchestrator/manager who delegates to multiple sessions.
|
|
1143
|
+
|
|
1144
|
+
WHEN NOT TO USE THIS:
|
|
1145
|
+
- 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.
|
|
1146
|
+
- For running shell commands or build watchers: use 'run_async'.
|
|
1147
|
+
- For a linear multi-step subagent pipeline: use 'workflow'.
|
|
1148
|
+
|
|
1149
|
+
Usage notes:
|
|
1150
|
+
1. 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.
|
|
1151
|
+
2. The initial task should provide clear context and goals since the new session starts with a fresh conversation history.
|
|
1152
|
+
3. Use session_status with the returned conversationId to check progress, or session_dispatch to send further messages.`;
|
|
1153
|
+
|
|
1154
|
+
/** Model-facing description for session_status. */
|
|
1155
|
+
export const SESSION_STATUS_DESCRIPTION = `Inspect the progress, run state, and latest activity of sibling conversations created by the orchestrator.
|
|
1156
|
+
|
|
1157
|
+
WHEN TO USE THIS:
|
|
1158
|
+
- To check whether a spawned sibling conversation is still running, has finished (idle), is stopped, or encountered an error.
|
|
1159
|
+
- To read the latest assistant progress snippet without bloating your context with the session's entire raw message transcript.
|
|
1160
|
+
- To list all sibling conversations under your coordination when conversationId is omitted.
|
|
1161
|
+
|
|
1162
|
+
Usage notes:
|
|
1163
|
+
1. 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).
|
|
1164
|
+
2. When 'conversationId' is omitted: returns a summary list of ALL sibling conversations spawned by this orchestrator session.
|
|
1165
|
+
3. This tool never returns full transcripts; it returns concise summaries and snippets to keep your context window lean.`;
|
|
1166
|
+
|
|
1167
|
+
/** Model-facing description for session_dispatch. */
|
|
1168
|
+
export const SESSION_DISPATCH_DESCRIPTION = `Send a message, follow-up instructions, or steering guidance to an existing sibling conversation.
|
|
1169
|
+
|
|
1170
|
+
WHEN TO USE THIS:
|
|
1171
|
+
- To follow up with a sibling session after reading its progress with session_status.
|
|
1172
|
+
- To give feedback, provide additional requirements, or answer a question from a sibling session.
|
|
1173
|
+
- To kick off a new turn on an idle or stopped sibling session.
|
|
1174
|
+
|
|
1175
|
+
Usage notes:
|
|
1176
|
+
1. 'conversationId' is required and must identify an existing sibling conversation.
|
|
1177
|
+
2. 'content' is the text instruction sent to that session, just as if typed by a user in that session's chat.
|
|
1178
|
+
3. Dispatch is ASYNCHRONOUS by default: it enqueues the message into the session's queue and returns immediately.
|
|
1179
|
+
4. 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.`;
|
|
1180
|
+
|
|
1181
|
+
/** Model-facing description for session_stop. */
|
|
1182
|
+
export const SESSION_STOP_DESCRIPTION = `Stop execution in a sibling conversation.
|
|
1183
|
+
|
|
1184
|
+
WHEN TO USE THIS:
|
|
1185
|
+
- 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.
|
|
1186
|
+
- To clear pending queued messages before they start running.
|
|
1187
|
+
|
|
1188
|
+
Usage notes:
|
|
1189
|
+
1. 'mode' controls what to stop:
|
|
1190
|
+
- 'all' (default): stops the currently running turn and cancels all pending queued messages in the session.
|
|
1191
|
+
- 'active': only interrupts the active running turn; leaves any pending queued messages untouched.
|
|
1192
|
+
- 'queued': only cancels pending queued messages; lets the currently running turn complete naturally.
|
|
1193
|
+
2. Use 'messageId' if you want to cancel or interrupt one specific queued message instead of all queued messages.`;
|
|
1194
|
+
|
|
1195
|
+
/** Compact summary returned for an orchestrated session. */
|
|
1196
|
+
export type OrchestratorSessionSummary = {
|
|
1197
|
+
conversationId: string;
|
|
1198
|
+
workspaceId: string;
|
|
1199
|
+
title: string;
|
|
1200
|
+
status: string;
|
|
1201
|
+
origin: "user" | "orchestrator";
|
|
1202
|
+
parentConversationId?: string;
|
|
1203
|
+
task?: string;
|
|
1204
|
+
messageCount: number;
|
|
1205
|
+
turnCount: number;
|
|
1206
|
+
lastAssistantSnippet?: string;
|
|
1207
|
+
lastUserSnippet?: string;
|
|
1208
|
+
hasPendingQuestion: boolean;
|
|
1209
|
+
activeTool?: { label: string; detail?: string };
|
|
1210
|
+
error?: string;
|
|
1211
|
+
updatedAt: string;
|
|
1212
|
+
createdAt?: number;
|
|
1213
|
+
lastProgress?: string;
|
|
1214
|
+
};
|
|
1215
|
+
|
|
1216
|
+
export type OrchestratorToolConfig = {
|
|
1217
|
+
endpoint: string;
|
|
1218
|
+
conversationId: string;
|
|
1219
|
+
token?: string;
|
|
1220
|
+
capability?: string;
|
|
1221
|
+
};
|
|
1222
|
+
|
|
1223
|
+
/** Read orchestrator configuration from extension environment. */
|
|
1224
|
+
export function readOrchestratorConfig(env: Record<string, string | undefined>): OrchestratorToolConfig {
|
|
1225
|
+
const token = env.SUPER_PI_ACCESS_TOKEN;
|
|
1226
|
+
const capability = env[TASK_CAPABILITY_ENV];
|
|
1227
|
+
return {
|
|
1228
|
+
endpoint: env[ORCHESTRATOR_ENDPOINT_ENV] ?? "",
|
|
1229
|
+
conversationId: env[TASK_CONVERSATION_ENV] ?? "",
|
|
1230
|
+
...(token ? { token } : {}),
|
|
1231
|
+
...(capability ? { capability } : {}),
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
export type SessionSpawnParams = {
|
|
1236
|
+
task: string;
|
|
1237
|
+
title?: string;
|
|
1238
|
+
agentId?: string;
|
|
1239
|
+
thinkingLevel?: string;
|
|
1240
|
+
wait?: boolean;
|
|
1241
|
+
};
|
|
1242
|
+
|
|
1243
|
+
export type SessionStatusParams = {
|
|
1244
|
+
conversationId?: string;
|
|
1245
|
+
};
|
|
1246
|
+
|
|
1247
|
+
export type SessionDispatchParams = {
|
|
1248
|
+
conversationId: string;
|
|
1249
|
+
content: string;
|
|
1250
|
+
wait?: boolean;
|
|
1251
|
+
};
|
|
1252
|
+
|
|
1253
|
+
export type SessionStopParams = {
|
|
1254
|
+
conversationId: string;
|
|
1255
|
+
mode?: "active" | "queued" | "all";
|
|
1256
|
+
messageId?: string;
|
|
1257
|
+
};
|
|
1258
|
+
|
|
1259
|
+
/** Execute session_spawn: creates a new sibling conversation and queues initial prompt. */
|
|
1260
|
+
export async function executeSessionSpawn(
|
|
1261
|
+
params: SessionSpawnParams,
|
|
1262
|
+
config: OrchestratorToolConfig,
|
|
1263
|
+
deps: { signal?: AbortSignal; fetch?: TaskFetch } = {},
|
|
1264
|
+
): Promise<TaskToolResult> {
|
|
1265
|
+
const text = (body: string, isError?: boolean): TaskToolResult => ({
|
|
1266
|
+
content: [{ type: "text", text: body }],
|
|
1267
|
+
details: undefined,
|
|
1268
|
+
...(isError ? { isError: true } : {}),
|
|
1269
|
+
});
|
|
1270
|
+
|
|
1271
|
+
if (!config.endpoint) {
|
|
1272
|
+
return text("The session_spawn tool is not available in this session: SUPER_PI_ORCHESTRATOR_ENDPOINT is not set.", true);
|
|
1273
|
+
}
|
|
1274
|
+
if (!params.task || !params.task.trim()) {
|
|
1275
|
+
return text("session_spawn requires a non-empty `task` parameter describing what the sibling session should do.", true);
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
const send = deps.fetch ?? (globalFetch as unknown as TaskFetch);
|
|
1279
|
+
try {
|
|
1280
|
+
const response = await send(config.endpoint, {
|
|
1281
|
+
method: "POST",
|
|
1282
|
+
headers: {
|
|
1283
|
+
"content-type": "application/json",
|
|
1284
|
+
...(config.token ? { authorization: `Bearer ${config.token}` } : {}),
|
|
1285
|
+
},
|
|
1286
|
+
body: JSON.stringify({
|
|
1287
|
+
action: "spawn",
|
|
1288
|
+
task: params.task.trim(),
|
|
1289
|
+
title: params.title?.trim() || undefined,
|
|
1290
|
+
agentId: params.agentId?.trim() || undefined,
|
|
1291
|
+
thinkingLevel: params.thinkingLevel?.trim() || undefined,
|
|
1292
|
+
wait: Boolean(params.wait),
|
|
1293
|
+
parentConversationId: config.conversationId || undefined,
|
|
1294
|
+
capability: config.capability,
|
|
1295
|
+
}),
|
|
1296
|
+
...(deps.signal ? { signal: deps.signal } : {}),
|
|
1297
|
+
});
|
|
1298
|
+
|
|
1299
|
+
const bodyText = await response.text();
|
|
1300
|
+
let payload: { ok?: boolean; conversationId?: string; error?: string };
|
|
1301
|
+
try {
|
|
1302
|
+
payload = JSON.parse(bodyText);
|
|
1303
|
+
} catch {
|
|
1304
|
+
return text(`The orchestrator endpoint returned HTTP ${response.status}: ${bodyText}`, true);
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
if (!response.ok || !payload.ok || !payload.conversationId) {
|
|
1308
|
+
return text(`Failed to spawn sibling session: ${payload.error ?? `HTTP ${response.status}`}`, true);
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
const convId = payload.conversationId;
|
|
1312
|
+
return text(
|
|
1313
|
+
`<session id="${convId}" state="running">\n` +
|
|
1314
|
+
`<title>${params.title?.trim() || `任务:${params.task.trim().slice(0, 30)}`}</title>\n` +
|
|
1315
|
+
`<task>${params.task.trim()}</task>\n` +
|
|
1316
|
+
`<session_result>Sibling session created and initial task enqueued successfully. Use session_status with conversationId="${convId}" to monitor progress, or session_dispatch to send follow-up instructions.</session_result>\n` +
|
|
1317
|
+
`</session>`
|
|
1318
|
+
);
|
|
1319
|
+
} catch (error) {
|
|
1320
|
+
return text(`session_spawn network error: ${error instanceof Error ? error.message : String(error)}`, true);
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
/** Execute session_status: queries status of a single session or lists all sibling sessions. */
|
|
1325
|
+
export async function executeSessionStatus(
|
|
1326
|
+
params: SessionStatusParams,
|
|
1327
|
+
config: OrchestratorToolConfig,
|
|
1328
|
+
deps: { signal?: AbortSignal; fetch?: TaskFetch } = {},
|
|
1329
|
+
): Promise<TaskToolResult> {
|
|
1330
|
+
const text = (body: string, isError?: boolean): TaskToolResult => ({
|
|
1331
|
+
content: [{ type: "text", text: body }],
|
|
1332
|
+
details: undefined,
|
|
1333
|
+
...(isError ? { isError: true } : {}),
|
|
1334
|
+
});
|
|
1335
|
+
|
|
1336
|
+
if (!config.endpoint) {
|
|
1337
|
+
return text("The session_status tool is not available in this session: SUPER_PI_ORCHESTRATOR_ENDPOINT is not set.", true);
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
const send = deps.fetch ?? (globalFetch as unknown as TaskFetch);
|
|
1341
|
+
const targetId = params.conversationId?.trim();
|
|
1342
|
+
|
|
1343
|
+
try {
|
|
1344
|
+
const response = await send(config.endpoint, {
|
|
1345
|
+
method: "POST",
|
|
1346
|
+
headers: {
|
|
1347
|
+
"content-type": "application/json",
|
|
1348
|
+
...(config.token ? { authorization: `Bearer ${config.token}` } : {}),
|
|
1349
|
+
},
|
|
1350
|
+
body: JSON.stringify({
|
|
1351
|
+
action: targetId ? "status" : "list",
|
|
1352
|
+
conversationId: targetId || undefined,
|
|
1353
|
+
parentConversationId: config.conversationId || undefined,
|
|
1354
|
+
capability: config.capability,
|
|
1355
|
+
}),
|
|
1356
|
+
...(deps.signal ? { signal: deps.signal } : {}),
|
|
1357
|
+
});
|
|
1358
|
+
|
|
1359
|
+
const bodyText = await response.text();
|
|
1360
|
+
let payload: { ok?: boolean; summary?: OrchestratorSessionSummary; sessions?: OrchestratorSessionSummary[]; error?: string };
|
|
1361
|
+
try {
|
|
1362
|
+
payload = JSON.parse(bodyText);
|
|
1363
|
+
} catch {
|
|
1364
|
+
return text(`The orchestrator endpoint returned HTTP ${response.status}: ${bodyText}`, true);
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
if (!response.ok || !payload.ok) {
|
|
1368
|
+
return text(`Failed to query session status: ${payload.error ?? `HTTP ${response.status}`}`, true);
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
if (targetId && payload.summary) {
|
|
1372
|
+
const s = payload.summary;
|
|
1373
|
+
const lines = [
|
|
1374
|
+
`Session Status for ${s.conversationId}:`,
|
|
1375
|
+
`- Title: ${s.title}`,
|
|
1376
|
+
`- Status: ${s.status}`,
|
|
1377
|
+
`- Turns: ${s.turnCount} (Messages: ${s.messageCount})`,
|
|
1378
|
+
`- Active Tool: ${s.activeTool ? `${s.activeTool.label}${s.activeTool.detail ? ` (${s.activeTool.detail})` : ""}` : "none"}`,
|
|
1379
|
+
`- Pending Question: ${s.hasPendingQuestion ? "yes" : "no"}`,
|
|
1380
|
+
...(s.error ? [`- Error: ${s.error}`] : []),
|
|
1381
|
+
...(s.lastAssistantSnippet ? [`- Latest Assistant Progress: "${s.lastAssistantSnippet}"`] : []),
|
|
1382
|
+
...(s.lastUserSnippet ? [`- Latest User/Prompt Snippet: "${s.lastUserSnippet}"`] : []),
|
|
1383
|
+
];
|
|
1384
|
+
return text(lines.join("\n"));
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
if (payload.sessions) {
|
|
1388
|
+
if (!payload.sessions.length) {
|
|
1389
|
+
return text("No sibling sessions found for this parent conversation.");
|
|
1390
|
+
}
|
|
1391
|
+
const listSummary = payload.sessions.map((s, idx) => {
|
|
1392
|
+
const snippet = s.lastAssistantSnippet ?? s.lastProgress ?? s.task ?? "No output yet";
|
|
1393
|
+
return `${idx + 1}. [${s.conversationId}] "${s.title}" — Status: ${s.status}, Turns: ${s.turnCount}, Latest: "${snippet}"`;
|
|
1394
|
+
}).join("\n");
|
|
1395
|
+
return text(`Sibling sessions (${payload.sessions.length}):\n${listSummary}`);
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
return text(bodyText);
|
|
1399
|
+
} catch (error) {
|
|
1400
|
+
return text(`session_status network error: ${error instanceof Error ? error.message : String(error)}`, true);
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
/** Execute session_dispatch: sends a follow-up message to an existing sibling conversation. */
|
|
1405
|
+
export async function executeSessionDispatch(
|
|
1406
|
+
params: SessionDispatchParams,
|
|
1407
|
+
config: OrchestratorToolConfig,
|
|
1408
|
+
deps: { signal?: AbortSignal; fetch?: TaskFetch } = {},
|
|
1409
|
+
): Promise<TaskToolResult> {
|
|
1410
|
+
const text = (body: string, isError?: boolean): TaskToolResult => ({
|
|
1411
|
+
content: [{ type: "text", text: body }],
|
|
1412
|
+
details: undefined,
|
|
1413
|
+
...(isError ? { isError: true } : {}),
|
|
1414
|
+
});
|
|
1415
|
+
|
|
1416
|
+
if (!config.endpoint) {
|
|
1417
|
+
return text("The session_dispatch tool is not available in this session: SUPER_PI_ORCHESTRATOR_ENDPOINT is not set.", true);
|
|
1418
|
+
}
|
|
1419
|
+
if (!params.conversationId?.trim()) {
|
|
1420
|
+
return text("session_dispatch requires a valid `conversationId`.", true);
|
|
1421
|
+
}
|
|
1422
|
+
if (!params.content?.trim()) {
|
|
1423
|
+
return text("session_dispatch requires a non-empty `content` message.", true);
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
const send = deps.fetch ?? (globalFetch as unknown as TaskFetch);
|
|
1427
|
+
try {
|
|
1428
|
+
const response = await send(config.endpoint, {
|
|
1429
|
+
method: "POST",
|
|
1430
|
+
headers: {
|
|
1431
|
+
"content-type": "application/json",
|
|
1432
|
+
...(config.token ? { authorization: `Bearer ${config.token}` } : {}),
|
|
1433
|
+
},
|
|
1434
|
+
body: JSON.stringify({
|
|
1435
|
+
action: "dispatch",
|
|
1436
|
+
conversationId: params.conversationId.trim(),
|
|
1437
|
+
content: params.content.trim(),
|
|
1438
|
+
wait: Boolean(params.wait),
|
|
1439
|
+
parentConversationId: config.conversationId || undefined,
|
|
1440
|
+
capability: config.capability,
|
|
1441
|
+
}),
|
|
1442
|
+
...(deps.signal ? { signal: deps.signal } : {}),
|
|
1443
|
+
});
|
|
1444
|
+
|
|
1445
|
+
const bodyText = await response.text();
|
|
1446
|
+
let payload: { ok?: boolean; conversationId?: string; error?: string };
|
|
1447
|
+
try {
|
|
1448
|
+
payload = JSON.parse(bodyText);
|
|
1449
|
+
} catch {
|
|
1450
|
+
return text(`The orchestrator endpoint returned HTTP ${response.status}: ${bodyText}`, true);
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
if (!response.ok || !payload.ok) {
|
|
1454
|
+
return text(`Failed to dispatch message to session ${params.conversationId}: ${payload.error ?? `HTTP ${response.status}`}`, true);
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
return text(`Message successfully dispatched to session ${params.conversationId}. The turn has been enqueued.`);
|
|
1458
|
+
} catch (error) {
|
|
1459
|
+
return text(`session_dispatch network error: ${error instanceof Error ? error.message : String(error)}`, true);
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
/** Execute session_stop: stops execution in a sibling conversation. */
|
|
1464
|
+
export async function executeSessionStop(
|
|
1465
|
+
params: SessionStopParams,
|
|
1466
|
+
config: OrchestratorToolConfig,
|
|
1467
|
+
deps: { signal?: AbortSignal; fetch?: TaskFetch } = {},
|
|
1468
|
+
): Promise<TaskToolResult> {
|
|
1469
|
+
const text = (body: string, isError?: boolean): TaskToolResult => ({
|
|
1470
|
+
content: [{ type: "text", text: body }],
|
|
1471
|
+
details: undefined,
|
|
1472
|
+
...(isError ? { isError: true } : {}),
|
|
1473
|
+
});
|
|
1474
|
+
|
|
1475
|
+
if (!config.endpoint) {
|
|
1476
|
+
return text("The session_stop tool is not available in this session: SUPER_PI_ORCHESTRATOR_ENDPOINT is not set.", true);
|
|
1477
|
+
}
|
|
1478
|
+
if (!params.conversationId?.trim()) {
|
|
1479
|
+
return text("session_stop requires a valid `conversationId`.", true);
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
const send = deps.fetch ?? (globalFetch as unknown as TaskFetch);
|
|
1483
|
+
try {
|
|
1484
|
+
const response = await send(config.endpoint, {
|
|
1485
|
+
method: "POST",
|
|
1486
|
+
headers: {
|
|
1487
|
+
"content-type": "application/json",
|
|
1488
|
+
...(config.token ? { authorization: `Bearer ${config.token}` } : {}),
|
|
1489
|
+
},
|
|
1490
|
+
body: JSON.stringify({
|
|
1491
|
+
action: "stop",
|
|
1492
|
+
conversationId: params.conversationId.trim(),
|
|
1493
|
+
mode: params.mode ?? "all",
|
|
1494
|
+
messageId: params.messageId?.trim() || undefined,
|
|
1495
|
+
parentConversationId: config.conversationId || undefined,
|
|
1496
|
+
capability: config.capability,
|
|
1497
|
+
}),
|
|
1498
|
+
...(deps.signal ? { signal: deps.signal } : {}),
|
|
1499
|
+
});
|
|
1500
|
+
|
|
1501
|
+
const bodyText = await response.text();
|
|
1502
|
+
let payload: { ok?: boolean; stopped?: boolean; mode?: string; error?: string };
|
|
1503
|
+
try {
|
|
1504
|
+
payload = JSON.parse(bodyText);
|
|
1505
|
+
} catch {
|
|
1506
|
+
return text(`The orchestrator endpoint returned HTTP ${response.status}: ${bodyText}`, true);
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
if (!response.ok || !payload.ok) {
|
|
1510
|
+
return text(`Failed to stop session ${params.conversationId}: ${payload.error ?? `HTTP ${response.status}`}`, true);
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
return text(`Session ${params.conversationId} stopped (mode: ${payload.mode ?? params.mode ?? "all"}).`);
|
|
1514
|
+
} catch (error) {
|
|
1515
|
+
return text(`session_stop network error: ${error instanceof Error ? error.message : String(error)}`, true);
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
|
|
@@ -36,6 +36,25 @@ import {
|
|
|
36
36
|
readScheduledTaskConfig,
|
|
37
37
|
type ScheduledTaskParams,
|
|
38
38
|
} from "./core.ts";
|
|
39
|
+
import {
|
|
40
|
+
SESSION_SPAWN_TOOL_NAME,
|
|
41
|
+
SESSION_STATUS_TOOL_NAME,
|
|
42
|
+
SESSION_DISPATCH_TOOL_NAME,
|
|
43
|
+
SESSION_STOP_TOOL_NAME,
|
|
44
|
+
SESSION_SPAWN_DESCRIPTION,
|
|
45
|
+
SESSION_STATUS_DESCRIPTION,
|
|
46
|
+
SESSION_DISPATCH_DESCRIPTION,
|
|
47
|
+
SESSION_STOP_DESCRIPTION,
|
|
48
|
+
executeSessionSpawn,
|
|
49
|
+
executeSessionStatus,
|
|
50
|
+
executeSessionDispatch,
|
|
51
|
+
executeSessionStop,
|
|
52
|
+
readOrchestratorConfig,
|
|
53
|
+
type SessionSpawnParams,
|
|
54
|
+
type SessionStatusParams,
|
|
55
|
+
type SessionDispatchParams,
|
|
56
|
+
type SessionStopParams,
|
|
57
|
+
} from "./core.ts";
|
|
39
58
|
|
|
40
59
|
/**
|
|
41
60
|
* The Pi adapter for the `task` and `todowrite` tools. Everything with a decision in it is in
|
|
@@ -360,6 +379,102 @@ const extension = (pi: ExtensionApi): void => {
|
|
|
360
379
|
execute: async (_toolCallId, params, signal) =>
|
|
361
380
|
await executeScheduledTask(params as ScheduledTaskParams, scheduledTaskConfig, { signal: signal ?? undefined }),
|
|
362
381
|
});
|
|
382
|
+
|
|
383
|
+
const orchestratorConfig = readOrchestratorConfig(process.env);
|
|
384
|
+
|
|
385
|
+
const SessionSpawnParameters = Type.Object({
|
|
386
|
+
task: Type.String({
|
|
387
|
+
description: "The high-level task or instructions to be performed by the new sibling session. This is sent as the initial prompt.",
|
|
388
|
+
}),
|
|
389
|
+
title: Type.Optional(Type.String({
|
|
390
|
+
description: "An optional title for the new sibling conversation in the sidebar. If omitted, derived from task.",
|
|
391
|
+
})),
|
|
392
|
+
agentId: Type.Optional(Type.String({
|
|
393
|
+
description: "Optional agent definition to assign to the new session (e.g. 'coder', 'planner'). Defaults to the workspace default.",
|
|
394
|
+
})),
|
|
395
|
+
thinkingLevel: Type.Optional(Type.String({
|
|
396
|
+
description: "Optional thinking level override ('off', 'low', 'medium', 'high'). Defaults to workspace setting.",
|
|
397
|
+
})),
|
|
398
|
+
wait: Type.Optional(Type.Boolean({
|
|
399
|
+
description: "Whether to await turn completion before returning (default: false, asynchronous).",
|
|
400
|
+
})),
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
const SessionStatusParameters = Type.Object({
|
|
404
|
+
conversationId: Type.Optional(Type.String({
|
|
405
|
+
description: "The ID of the sibling conversation to inspect. Omit to list all sibling conversations created under this session.",
|
|
406
|
+
})),
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
const SessionDispatchParameters = Type.Object({
|
|
410
|
+
conversationId: Type.String({
|
|
411
|
+
description: "The ID of the target sibling conversation to send a message to.",
|
|
412
|
+
}),
|
|
413
|
+
content: Type.String({
|
|
414
|
+
description: "The text message or instructions to send to the sibling conversation.",
|
|
415
|
+
}),
|
|
416
|
+
wait: Type.Optional(Type.Boolean({
|
|
417
|
+
description: "Whether to await turn completion before returning (default: false, asynchronous).",
|
|
418
|
+
})),
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
const SessionStopParameters = Type.Object({
|
|
422
|
+
conversationId: Type.String({
|
|
423
|
+
description: "The conversation ID of the sibling session to stop.",
|
|
424
|
+
}),
|
|
425
|
+
mode: Type.Optional(Type.Union([
|
|
426
|
+
Type.Literal("active", { description: "Interrupt current running turn only" }),
|
|
427
|
+
Type.Literal("queued", { description: "Cancel pending queued messages only" }),
|
|
428
|
+
Type.Literal("all", { description: "Stop active turn and cancel all queued messages (default)" }),
|
|
429
|
+
], {
|
|
430
|
+
description: "Stop mode: 'active', 'queued', or 'all' (default).",
|
|
431
|
+
})),
|
|
432
|
+
messageId: Type.Optional(Type.String({
|
|
433
|
+
description: "Optional specific queued message ID to cancel or interrupt.",
|
|
434
|
+
})),
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
if (orchestratorConfig.endpoint) {
|
|
438
|
+
pi.registerTool({
|
|
439
|
+
name: SESSION_SPAWN_TOOL_NAME,
|
|
440
|
+
label: "Spawn Sibling Session",
|
|
441
|
+
description: SESSION_SPAWN_DESCRIPTION,
|
|
442
|
+
promptSnippet: "spawn a persistent sibling conversation for an independent work stream",
|
|
443
|
+
parameters: SessionSpawnParameters,
|
|
444
|
+
execute: async (_toolCallId, params, signal) =>
|
|
445
|
+
await executeSessionSpawn(params as SessionSpawnParams, orchestratorConfig, { signal: signal ?? undefined }),
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
pi.registerTool({
|
|
449
|
+
name: SESSION_STATUS_TOOL_NAME,
|
|
450
|
+
label: "Session Status",
|
|
451
|
+
description: SESSION_STATUS_DESCRIPTION,
|
|
452
|
+
promptSnippet: "check progress, turn count and activity of sibling sessions",
|
|
453
|
+
parameters: SessionStatusParameters,
|
|
454
|
+
execute: async (_toolCallId, params, signal) =>
|
|
455
|
+
await executeSessionStatus(params as SessionStatusParams, orchestratorConfig, { signal: signal ?? undefined }),
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
pi.registerTool({
|
|
459
|
+
name: SESSION_DISPATCH_TOOL_NAME,
|
|
460
|
+
label: "Dispatch to Session",
|
|
461
|
+
description: SESSION_DISPATCH_DESCRIPTION,
|
|
462
|
+
promptSnippet: "send a message or follow-up prompt to an existing sibling session",
|
|
463
|
+
parameters: SessionDispatchParameters,
|
|
464
|
+
execute: async (_toolCallId, params, signal) =>
|
|
465
|
+
await executeSessionDispatch(params as SessionDispatchParams, orchestratorConfig, { signal: signal ?? undefined }),
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
pi.registerTool({
|
|
469
|
+
name: SESSION_STOP_TOOL_NAME,
|
|
470
|
+
label: "Stop Session",
|
|
471
|
+
description: SESSION_STOP_DESCRIPTION,
|
|
472
|
+
promptSnippet: "interrupt an active turn or cancel queued messages in a sibling session",
|
|
473
|
+
parameters: SessionStopParameters,
|
|
474
|
+
execute: async (_toolCallId, params, signal) =>
|
|
475
|
+
await executeSessionStop(params as SessionStopParams, orchestratorConfig, { signal: signal ?? undefined }),
|
|
476
|
+
});
|
|
477
|
+
}
|
|
363
478
|
};
|
|
364
479
|
|
|
365
480
|
export default extension;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"hash": "
|
|
2
|
+
"hash": "f452eac806eb9b524339a3014bad75b0e38512de1a657b0e20833bed50947ad7",
|
|
3
3
|
"files": {
|
|
4
|
-
"src/client/App.tsx": "
|
|
4
|
+
"src/client/App.tsx": "625097a2298f4413e83c6cc620e2855b245aa28cb0d5c2b47c225b54b6f40825",
|
|
5
5
|
"src/client/CodeMirrorEditor.tsx": "b6b6479effa1482eda710f04a26f3af843515526e129ca5bc70692cca9074b09",
|
|
6
6
|
"src/client/ErrorBoundary.tsx": "d16376f9f6c7cc1fab8590c65a66438127642f7d6bb8023cec53d8a6cc9f3964",
|
|
7
7
|
"src/client/FeishuPanel.tsx": "52f116eba265bfde683338f5893107ad5559fd2f7d4a9e0df935507a46862a3b",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"src/client/MarkdownView.tsx": "4f04a8f3c9381946e5d2f957ac4fb74a6b16272e90375fb4dc6bd3a43d7c744d",
|
|
14
14
|
"src/client/MemoryPanel.tsx": "2cc1f4ee709f6d56ede8b0ceced228ea4c935cfd1cc803275558dbbd5f5244fb",
|
|
15
15
|
"src/client/ScheduledTasksPanel.tsx": "eb05526241ba9072acd4ee7c08425e89a2054493ec7017f7041f01879971c224",
|
|
16
|
+
"src/client/SessionOverviewPanel.tsx": "fe654d49989c660ef67bc160d67e644d4f5bfffb2d04027ab15da64130033d39",
|
|
16
17
|
"src/client/SlashCommandMenu.tsx": "69cd0a9d5e35f41f703e91a845e5bd750c952d2957be03b5528e156e60bf6ecb",
|
|
17
18
|
"src/client/Tabs.tsx": "53323a198c0948887cb0cd018284ecd5c9b36025364484a91426f8e1e2198000",
|
|
18
19
|
"src/client/TerminalPanel.tsx": "6a8dbb0e0806ccfef6fd67ae16f580177dca9a7a8943fd5b801da555e424ad72",
|
|
@@ -26,13 +27,13 @@
|
|
|
26
27
|
"src/client/markdown.ts": "1499893ea5ce29d49f26c193cc0e6001ffa8a31d039ccee343ba407cd2cdbf1b",
|
|
27
28
|
"src/client/pending-operations.ts": "8a5f9e9711c0f875c8ed194196c218162de5270298dad4091351d41294c859d7",
|
|
28
29
|
"src/client/slash-commands.ts": "b792018fb67bd9e6f777397b0720b8b3aa565a4d8f7a76f94fec097ddf749480",
|
|
29
|
-
"src/client/styles.css": "
|
|
30
|
+
"src/client/styles.css": "0b8bdb92f90ada2997e1a941c8556f634c95ede144e8714c1d22a123948f9aa4",
|
|
30
31
|
"src/client/terminal-emulator.ts": "ffcc4f5fbddaf25cebcc9e03dbe5d7d0ad2c21586f5d169e57c04c82e2308b3b",
|
|
31
32
|
"src/client/theme.tsx": "5892479a802f41e7d45adeb5170efbf555b240f8581528bcde7bf2d99c635010",
|
|
32
33
|
"src/client/tool-sharing.ts": "679c97f6d40be466964f2f1076aa7ea55f4a585c3deff8214675070220921059",
|
|
33
34
|
"src/client/tools.ts": "58fe41f0edbd490e3fab7f12f258b1dbc2163b653553b03ac61caf1543618e50",
|
|
34
|
-
"src/client/types.ts": "
|
|
35
|
+
"src/client/types.ts": "cc8405d6bf1a9933b16c9e29692bfec746ee0a0b3fe27bab4bbe6f16f25c5131",
|
|
35
36
|
"src/client/vite-env.d.ts": "65996936fbb042915f7b74a200fcdde7e410f32a669b1ab9597cfaa4b0faddb5"
|
|
36
37
|
},
|
|
37
|
-
"recordedAt": "2026-10-
|
|
38
|
+
"recordedAt": "2026-10-29T10:32:39.121Z"
|
|
38
39
|
}
|