@evandu/super-pi 0.1.149 → 0.1.151
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 +105 -1
- package/agents/super-pi-task/index.ts +46 -0
- package/dist/.super-pi-check-inputs.json +2 -2
- package/dist/client/.super-pi-client-sources.json +3 -3
- package/dist/client/assets/{CodeMirrorEditor-DQCjEnxG.js → CodeMirrorEditor-BhTJvXpU.js} +2 -2
- package/dist/client/assets/{dist-QZ-FAVJV.js → dist-0vbzT0fs.js} +1 -1
- package/dist/client/assets/{dist-CRxCwn-Z.js → dist-49TFFUQg.js} +1 -1
- package/dist/client/assets/{dist-B4pz_xdd.js → dist-80xX2Yd8.js} +1 -1
- package/dist/client/assets/{dist-F3S0t1oM.js → dist-B0gI-KRY.js} +1 -1
- package/dist/client/assets/{dist-CDBKM9tL.js → dist-B3Dy7iF_.js} +1 -1
- package/dist/client/assets/{dist-BpeClAv0.js → dist-B5nvkwVU.js} +1 -1
- package/dist/client/assets/{dist-CGzXCzCG.js → dist-BFAjSsad.js} +1 -1
- package/dist/client/assets/{dist-CTDgUnBG.js → dist-BOqRI6sA.js} +1 -1
- package/dist/client/assets/{dist-u0nBg0fZ.js → dist-BstoGC94.js} +1 -1
- package/dist/client/assets/{dist-Bk_pkBVH.js → dist-BuDFattZ.js} +1 -1
- package/dist/client/assets/{dist-B1qLZVMK.js → dist-Bz1OET8s.js} +1 -1
- package/dist/client/assets/{dist-BWvzD8Pe.js → dist-CMiCGuFr.js} +1 -1
- package/dist/client/assets/{dist-DTZLNEci.js → dist-Ce7WvAkm.js} +1 -1
- package/dist/client/assets/{dist-DuUi29km.js → dist-CkatEAPx.js} +1 -1
- package/dist/client/assets/{dist-BJVI1Unv.js → dist-Cwuref9o.js} +1 -1
- package/dist/client/assets/{dist-Bx7s4rni.js → dist-CzCFepaD.js} +1 -1
- package/dist/client/assets/{dist-1Innu17E.js → dist-DDdxjRBp.js} +1 -1
- package/dist/client/assets/{dist-D1VfMWmf.js → dist-DJdFGvR0.js} +1 -1
- package/dist/client/assets/{dist-1lynFOc5.js → dist-Dq7hLZoB.js} +1 -1
- package/dist/client/assets/{dist-BsmJg5PX.js → dist-SMKcq1ns.js} +1 -1
- package/dist/client/assets/{dist-BayWssd0.js → dist-TwlTH8F1.js} +1 -1
- package/dist/client/assets/{dist-B6KwDpeI.js → dist-aOpTOMHI.js} +1 -1
- package/dist/client/assets/{index-Dzyp_7sf.js → index-Csr3-JTY.js} +5 -5
- package/dist/client/index.html +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 +36 -0
- package/dist/server/application/session-orchestrator-service.js +89 -0
- package/dist/server/http-server.js +65 -0
- package/dist/server/runtime.js +3 -3
- package/dist/server/task-tool.d.ts +3 -0
- package/dist/server/task-tool.js +19 -0
- package/package.json +2 -1
package/dist/client/index.html
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<link rel="alternate icon" href="/favicon.ico" />
|
|
9
9
|
<link rel="apple-touch-icon" href="/favicon.svg" />
|
|
10
10
|
<title>Super Pi</title>
|
|
11
|
-
<script type="module" crossorigin src="/assets/index-
|
|
11
|
+
<script type="module" crossorigin src="/assets/index-Csr3-JTY.js"></script>
|
|
12
12
|
<link rel="stylesheet" crossorigin href="/assets/index-BMpqviwU.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body>
|
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", "session_report", "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", "session_stop_subtree", "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", "session_report",
|
|
43
|
+
"session_spawn", "session_status", "session_dispatch", "session_stop", "session_report", "session_stop_subtree",
|
|
44
44
|
"knowledge_search", "knowledge_import",
|
|
45
45
|
"document_read", "document_write", "web_search", "web_fetch", "feishu_doc_read", "feishu_doc_write",
|
|
46
46
|
];
|
|
@@ -230,6 +230,42 @@ export declare class SessionOrchestratorService {
|
|
|
230
230
|
stopped: boolean;
|
|
231
231
|
mode: string;
|
|
232
232
|
}>;
|
|
233
|
+
/**
|
|
234
|
+
* THE SESSIONS UNDER ONE SESSION, DEEPEST LAST.
|
|
235
|
+
*
|
|
236
|
+
* The tree edge lives on the conversation itself (`parentConversationId`), so "who is under this
|
|
237
|
+
* one?" is a walk over the workspace's conversations rather than a separate structure that could
|
|
238
|
+
* disagree with it. The order is breadth-first, which is depth order - and the caller stops them
|
|
239
|
+
* in reverse, so the deepest work dies before the session that owns it.
|
|
240
|
+
*/
|
|
241
|
+
private descendantsOf;
|
|
242
|
+
/**
|
|
243
|
+
* STOPS A SESSION AND EVERYTHING UNDER IT.
|
|
244
|
+
*
|
|
245
|
+
* THIS IS NOT THE SAME THING AS STOPPING A SESSION, and it is deliberately a separate operation
|
|
246
|
+
* rather than a flag on the other one. Stopping a session stops THAT session: its turn, its queued
|
|
247
|
+
* messages, its watchdog. Nothing else. That behaviour is right for the ordinary case - a child
|
|
248
|
+
* that went down the wrong path should not take its own children down with it, because those
|
|
249
|
+
* children may be doing work the parent still wants.
|
|
250
|
+
*
|
|
251
|
+
* But there is a case where the ordinary case leaves a mess. A supervisor whose own supervisor has
|
|
252
|
+
* stopped is an ORPHAN, and the sessions it started are orphans too: they keep burning tokens,
|
|
253
|
+
* keep writing files, and report their results to a session that is no longer running - nobody is
|
|
254
|
+
* left to read them. The parent sees a child that has gone wrong, stops it, and the grandchildren
|
|
255
|
+
* keep running with no parent to stop them.
|
|
256
|
+
*
|
|
257
|
+
* So this operation takes the whole tree: the root, its children, its grandchildren, deepest
|
|
258
|
+
* first. The descendants are stopped AS A CONSEQUENCE of the root being stopped - the caller is
|
|
259
|
+
* still only managing its OWN direct child, and the cross-level reach the scope rules forbid is
|
|
260
|
+
* not what happens here: a grandchild cannot be stopped on its own, and it cannot be spared
|
|
261
|
+
* either, because it cannot outlive the session that owns it.
|
|
262
|
+
*/
|
|
263
|
+
stopSubtree(conversationId: string, options?: StopSessionOptions): Promise<{
|
|
264
|
+
stopped: boolean;
|
|
265
|
+
mode: string;
|
|
266
|
+
stoppedIds: string[];
|
|
267
|
+
descendantCount: number;
|
|
268
|
+
}>;
|
|
233
269
|
/**
|
|
234
270
|
* Resumes execution in a conversation.
|
|
235
271
|
* If prompt is provided, sends it as a new turn.
|
|
@@ -695,6 +695,95 @@ export class SessionOrchestratorService {
|
|
|
695
695
|
await this.watchdog.cancel(conversationId);
|
|
696
696
|
return { stopped: true, mode };
|
|
697
697
|
}
|
|
698
|
+
/**
|
|
699
|
+
* THE SESSIONS UNDER ONE SESSION, DEEPEST LAST.
|
|
700
|
+
*
|
|
701
|
+
* The tree edge lives on the conversation itself (`parentConversationId`), so "who is under this
|
|
702
|
+
* one?" is a walk over the workspace's conversations rather than a separate structure that could
|
|
703
|
+
* disagree with it. The order is breadth-first, which is depth order - and the caller stops them
|
|
704
|
+
* in reverse, so the deepest work dies before the session that owns it.
|
|
705
|
+
*/
|
|
706
|
+
descendantsOf(rootConversationId) {
|
|
707
|
+
const root = this.store.conversation(rootConversationId);
|
|
708
|
+
if (!root)
|
|
709
|
+
return [];
|
|
710
|
+
const childrenByParent = new Map();
|
|
711
|
+
for (const conversation of this.store.conversationsForWorkspace(root.workspaceId)) {
|
|
712
|
+
const parentId = conversation.parentConversationId;
|
|
713
|
+
if (!parentId)
|
|
714
|
+
continue;
|
|
715
|
+
const siblings = childrenByParent.get(parentId);
|
|
716
|
+
if (siblings)
|
|
717
|
+
siblings.push(conversation.id);
|
|
718
|
+
else
|
|
719
|
+
childrenByParent.set(parentId, [conversation.id]);
|
|
720
|
+
}
|
|
721
|
+
const ordered = [];
|
|
722
|
+
const seen = new Set([rootConversationId]);
|
|
723
|
+
const queue = [rootConversationId];
|
|
724
|
+
while (queue.length) {
|
|
725
|
+
const current = queue.shift();
|
|
726
|
+
for (const child of childrenByParent.get(current) ?? []) {
|
|
727
|
+
// A cycle in the stored edges must not turn this into an endless walk.
|
|
728
|
+
if (seen.has(child))
|
|
729
|
+
continue;
|
|
730
|
+
seen.add(child);
|
|
731
|
+
ordered.push(child);
|
|
732
|
+
queue.push(child);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
return ordered;
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* STOPS A SESSION AND EVERYTHING UNDER IT.
|
|
739
|
+
*
|
|
740
|
+
* THIS IS NOT THE SAME THING AS STOPPING A SESSION, and it is deliberately a separate operation
|
|
741
|
+
* rather than a flag on the other one. Stopping a session stops THAT session: its turn, its queued
|
|
742
|
+
* messages, its watchdog. Nothing else. That behaviour is right for the ordinary case - a child
|
|
743
|
+
* that went down the wrong path should not take its own children down with it, because those
|
|
744
|
+
* children may be doing work the parent still wants.
|
|
745
|
+
*
|
|
746
|
+
* But there is a case where the ordinary case leaves a mess. A supervisor whose own supervisor has
|
|
747
|
+
* stopped is an ORPHAN, and the sessions it started are orphans too: they keep burning tokens,
|
|
748
|
+
* keep writing files, and report their results to a session that is no longer running - nobody is
|
|
749
|
+
* left to read them. The parent sees a child that has gone wrong, stops it, and the grandchildren
|
|
750
|
+
* keep running with no parent to stop them.
|
|
751
|
+
*
|
|
752
|
+
* So this operation takes the whole tree: the root, its children, its grandchildren, deepest
|
|
753
|
+
* first. The descendants are stopped AS A CONSEQUENCE of the root being stopped - the caller is
|
|
754
|
+
* still only managing its OWN direct child, and the cross-level reach the scope rules forbid is
|
|
755
|
+
* not what happens here: a grandchild cannot be stopped on its own, and it cannot be spared
|
|
756
|
+
* either, because it cannot outlive the session that owns it.
|
|
757
|
+
*/
|
|
758
|
+
async stopSubtree(conversationId, options) {
|
|
759
|
+
const root = this.store.conversation(conversationId);
|
|
760
|
+
if (!root) {
|
|
761
|
+
throw new Error(`找不到会话:${conversationId}`);
|
|
762
|
+
}
|
|
763
|
+
const descendants = this.descendantsOf(conversationId);
|
|
764
|
+
const stoppedIds = [];
|
|
765
|
+
// DEEPEST FIRST. A session that is stopped while its children still run is exactly the zombie
|
|
766
|
+
// this operation exists to prevent, so the leaves go before their parents.
|
|
767
|
+
for (const descendantId of [...descendants].reverse()) {
|
|
768
|
+
try {
|
|
769
|
+
await this.stop(descendantId, options);
|
|
770
|
+
stoppedIds.push(descendantId);
|
|
771
|
+
}
|
|
772
|
+
catch (error) {
|
|
773
|
+
// ONE SESSION THAT WILL NOT STOP MUST NOT LEAVE THE REST RUNNING. The point of the cascade
|
|
774
|
+
// is that nothing survives, so a failure on one session is recorded and the sweep goes on.
|
|
775
|
+
console.warn(`Super Pi: stopping descendant ${descendantId} failed:`, error instanceof Error ? error.message : String(error));
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
const result = await this.stop(conversationId, options);
|
|
779
|
+
stoppedIds.push(conversationId);
|
|
780
|
+
return {
|
|
781
|
+
stopped: result.stopped,
|
|
782
|
+
mode: result.mode,
|
|
783
|
+
stoppedIds,
|
|
784
|
+
descendantCount: descendants.length,
|
|
785
|
+
};
|
|
786
|
+
}
|
|
698
787
|
/**
|
|
699
788
|
* Resumes execution in a conversation.
|
|
700
789
|
* If prompt is provided, sends it as a new turn.
|
|
@@ -1614,6 +1614,21 @@ export function createHttpServer(options) {
|
|
|
1614
1614
|
json(200, { ok: true, ...result });
|
|
1615
1615
|
return;
|
|
1616
1616
|
}
|
|
1617
|
+
if (action === "stop_subtree") {
|
|
1618
|
+
if (!body.conversationId)
|
|
1619
|
+
throw new Error("stop_subtree requires conversationId");
|
|
1620
|
+
// THE ROOT OF THE TREE IS STILL THE CALLER'S DIRECT CHILD. The scope rule does not move:
|
|
1621
|
+
// the descendants go down with their owner, they are not reachable on their own, and a
|
|
1622
|
+
// caller still cannot stop somebody else's branch.
|
|
1623
|
+
const scope = verifyTargetScope(body.conversationId, "停止");
|
|
1624
|
+
if (!scope.ok) {
|
|
1625
|
+
json(scope.status, { error: scope.error });
|
|
1626
|
+
return;
|
|
1627
|
+
}
|
|
1628
|
+
const result = await service.stopSubtree(body.conversationId, { mode: body.mode, messageId: body.messageId });
|
|
1629
|
+
json(200, { ok: true, ...result });
|
|
1630
|
+
return;
|
|
1631
|
+
}
|
|
1617
1632
|
if (action === "resume") {
|
|
1618
1633
|
if (!body.conversationId)
|
|
1619
1634
|
throw new Error("resume requires conversationId");
|
|
@@ -1691,6 +1706,56 @@ export function createHttpServer(options) {
|
|
|
1691
1706
|
}
|
|
1692
1707
|
return;
|
|
1693
1708
|
}
|
|
1709
|
+
// THE PERSON'S OWN STOP.
|
|
1710
|
+
//
|
|
1711
|
+
// The endpoint above is the AGENT's path, and it authenticates with the session's capability,
|
|
1712
|
+
// because a dispatch spends model quota on somebody's behalf and the capability is what says
|
|
1713
|
+
// "this really is that session". A person clicking "stop" in their own browser is not that:
|
|
1714
|
+
// stopping REDUCES work, it grants nothing, and a browser has no capability to present - so
|
|
1715
|
+
// this path is refused by the address guard instead, the same guard every other user-facing
|
|
1716
|
+
// write in this server carries.
|
|
1717
|
+
//
|
|
1718
|
+
// WHY IT IS A SEPARATE PATH AND NOT A FLAG ON THE AGENT'S ENDPOINT. The two have different
|
|
1719
|
+
// authorities: one proves a session is speaking, the other proves a person is. Mixing them
|
|
1720
|
+
// would mean either the browser gets a dispatch credential it must not have, or the agent
|
|
1721
|
+
// loses the proof that it is the agent.
|
|
1722
|
+
if (url.pathname === "/api/orchestrator/stop") {
|
|
1723
|
+
if (request.method !== "POST" || !isLocalOrPrivateAddress(remote)) {
|
|
1724
|
+
response.writeHead(403, { "content-type": "text/plain; charset=utf-8" }).end("Stopping a session requires a loopback or private network POST");
|
|
1725
|
+
return;
|
|
1726
|
+
}
|
|
1727
|
+
const asked = await readRequestText(request, 64 * 1024);
|
|
1728
|
+
if (asked.tooLarge) {
|
|
1729
|
+
response.writeHead(413, { "content-type": "application/json; charset=utf-8" }).end(JSON.stringify({ error: "请求体过大", code: "too_large" }));
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1732
|
+
const json = (status, body) => {
|
|
1733
|
+
response.writeHead(status, { "content-type": "application/json; charset=utf-8" }).end(JSON.stringify(body));
|
|
1734
|
+
};
|
|
1735
|
+
try {
|
|
1736
|
+
const service = context.sessionOrchestratorService;
|
|
1737
|
+
if (!service) {
|
|
1738
|
+
json(503, { error: "编排服务未就绪" });
|
|
1739
|
+
return;
|
|
1740
|
+
}
|
|
1741
|
+
const body = JSON.parse(asked.text);
|
|
1742
|
+
const conversationId = body.conversationId?.trim();
|
|
1743
|
+
if (!conversationId) {
|
|
1744
|
+
json(400, { error: "缺少 conversationId" });
|
|
1745
|
+
return;
|
|
1746
|
+
}
|
|
1747
|
+
// THE TREE GOES WITH THE ROOT WHEN THE PERSON ASKS FOR THE TREE. Stopping one session and
|
|
1748
|
+
// stopping its whole branch are two different intentions, and the person says which.
|
|
1749
|
+
const result = body.subtree
|
|
1750
|
+
? await service.stopSubtree(conversationId, { mode: body.mode, messageId: body.messageId })
|
|
1751
|
+
: await service.stop(conversationId, { mode: body.mode, messageId: body.messageId });
|
|
1752
|
+
json(200, { ok: true, ...result });
|
|
1753
|
+
}
|
|
1754
|
+
catch (error) {
|
|
1755
|
+
json(400, { error: error instanceof Error ? error.message : "stop failed" });
|
|
1756
|
+
}
|
|
1757
|
+
return;
|
|
1758
|
+
}
|
|
1694
1759
|
if (url.pathname === "/api/conversation/usage") {
|
|
1695
1760
|
const json = (status, payload) => {
|
|
1696
1761
|
response.writeHead(status, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" }).end(JSON.stringify(payload));
|
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, SESSION_REPORT_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, SESSION_STOP_SUBTREE_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, SESSION_REPORT_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, SESSION_STOP_SUBTREE_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, 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] : [])] });
|
|
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, SESSION_STOP_SUBTREE_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)
|
|
@@ -258,6 +258,7 @@ 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
260
|
export declare const SESSION_REPORT_TOOL_NAME = "session_report";
|
|
261
|
+
export declare const SESSION_STOP_SUBTREE_TOOL_NAME = "session_stop_subtree";
|
|
261
262
|
/** Model-facing description for session_spawn. */
|
|
262
263
|
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.";
|
|
263
264
|
/** Model-facing description for session_status. */
|
|
@@ -268,3 +269,5 @@ export declare const SESSION_DISPATCH_DESCRIPTION = "Send a message, follow-up i
|
|
|
268
269
|
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
270
|
/** Model-facing description for session_report. */
|
|
270
271
|
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.";
|
|
272
|
+
/** Model-facing description for session_stop_subtree. */
|
|
273
|
+
export declare const SESSION_STOP_SUBTREE_DESCRIPTION = "Stop a sibling session AND every session underneath it.\n\nWHEN TO USE THIS:\n- The session has children of its own and you are stopping it: those children would otherwise keep running with a dead parent - zombies that burn tokens, keep writing files, and report results nobody is left to read.\n- The whole branch should go: stopping only the top leaves the work underneath it alive.\n- A child went wrong and everything it started has to go down with it.\n\nWHEN NOT TO USE THIS:\n- The session has no children: session_stop does the same thing to one session and says less.\n- The session is only misbehaving at the top and its children are doing work you still want: stop it alone with session_stop.\n- You want to cancel one queued message: session_stop's 'messageId'.\n\nUsage notes:\n1. 'conversationId' must be your own DIRECT child, exactly as for session_stop. You are not reaching across levels to manage anybody.\n2. Everything under that session goes down with it - children, grandchildren, the whole tree - deepest first, so nothing is left running under a session that is already gone.\n3. The descendants are stopped AS A CONSEQUENCE of stopping the root. They cannot be spared, because a session cannot outlive the one that owns it.\n4. The answer says how many sessions were stopped in total, including the one you named.";
|
package/dist/server/task-tool.js
CHANGED
|
@@ -363,6 +363,7 @@ 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
365
|
export const SESSION_REPORT_TOOL_NAME = "session_report";
|
|
366
|
+
export const SESSION_STOP_SUBTREE_TOOL_NAME = "session_stop_subtree";
|
|
366
367
|
/** Model-facing description for session_spawn. */
|
|
367
368
|
export const SESSION_SPAWN_DESCRIPTION = `Create a new sibling conversation in the current workspace and immediately dispatch its initial task.
|
|
368
369
|
|
|
@@ -447,3 +448,21 @@ Usage notes:
|
|
|
447
448
|
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
449
|
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
450
|
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.`;
|
|
451
|
+
/** Model-facing description for session_stop_subtree. */
|
|
452
|
+
export const SESSION_STOP_SUBTREE_DESCRIPTION = `Stop a sibling session AND every session underneath it.
|
|
453
|
+
|
|
454
|
+
WHEN TO USE THIS:
|
|
455
|
+
- The session has children of its own and you are stopping it: those children would otherwise keep running with a dead parent - zombies that burn tokens, keep writing files, and report results nobody is left to read.
|
|
456
|
+
- The whole branch should go: stopping only the top leaves the work underneath it alive.
|
|
457
|
+
- A child went wrong and everything it started has to go down with it.
|
|
458
|
+
|
|
459
|
+
WHEN NOT TO USE THIS:
|
|
460
|
+
- The session has no children: session_stop does the same thing to one session and says less.
|
|
461
|
+
- The session is only misbehaving at the top and its children are doing work you still want: stop it alone with session_stop.
|
|
462
|
+
- You want to cancel one queued message: session_stop's 'messageId'.
|
|
463
|
+
|
|
464
|
+
Usage notes:
|
|
465
|
+
1. 'conversationId' must be your own DIRECT child, exactly as for session_stop. You are not reaching across levels to manage anybody.
|
|
466
|
+
2. Everything under that session goes down with it - children, grandchildren, the whole tree - deepest first, so nothing is left running under a session that is already gone.
|
|
467
|
+
3. The descendants are stopped AS A CONSEQUENCE of stopping the root. They cannot be spared, because a session cannot outlive the one that owns it.
|
|
468
|
+
4. The answer says how many sessions were stopped in total, including the one you named.`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evandu/super-pi",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.151",
|
|
4
4
|
"description": "Web control plane and companion for Pi",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -217,6 +217,7 @@
|
|
|
217
217
|
"scripts/background-result-steer.test.mjs",
|
|
218
218
|
"scripts/session-report-watchdog.test.mjs",
|
|
219
219
|
"scripts/session-report-tool.test.mjs",
|
|
220
|
+
"scripts/session-stop-subtree.test.mjs",
|
|
220
221
|
"scripts/awaiting-background-status.test.mjs"
|
|
221
222
|
],
|
|
222
223
|
"check:browser": [
|