@hheei/pi-subagents 0.2.3 → 0.3.0
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/README.md +32 -17
- package/dist/agent-resolver.d.ts +9 -0
- package/dist/agent-resolver.js +278 -51
- package/dist/bridge-client.d.ts +5 -0
- package/dist/bridge-client.js +6 -0
- package/dist/builtin-agents.js +50 -37
- package/dist/child-bridge.d.ts +0 -8
- package/dist/child-bridge.js +60 -43
- package/dist/child-control.d.ts +5 -3
- package/dist/child-control.js +176 -35
- package/dist/commands.d.ts +1 -4
- package/dist/commands.js +1 -52
- package/dist/domain.d.ts +12 -12
- package/dist/domain.js +2 -1
- package/dist/extension.d.ts +5 -3
- package/dist/extension.js +42 -44
- package/dist/host-adapter.js +1 -1
- package/dist/info.d.ts +9 -0
- package/dist/info.js +54 -0
- package/dist/launch-spec.d.ts +0 -1
- package/dist/launch-spec.js +38 -18
- package/dist/manager.d.ts +4 -2
- package/dist/manager.js +299 -114
- package/dist/registry.js +19 -5
- package/dist/rpc-events.d.ts +1 -1
- package/dist/rpc-events.js +0 -1
- package/dist/runtime.js +8 -0
- package/dist/session-bootstrap.d.ts +2 -1
- package/dist/session-bootstrap.js +22 -14
- package/dist/state.d.ts +13 -0
- package/dist/state.js +59 -16
- package/dist/status.d.ts +14 -0
- package/dist/status.js +36 -0
- package/dist/tool-activation.d.ts +3 -4
- package/dist/tool-activation.js +13 -215
- package/dist/tool-presentation.d.ts +17 -0
- package/dist/tool-presentation.js +160 -0
- package/dist/tools.d.ts +6 -1
- package/dist/tools.js +133 -180
- package/package.json +6 -6
- package/dist/session-fork.d.ts +0 -16
- package/dist/session-fork.js +0 -67
- package/dist/widget.d.ts +0 -12
- package/dist/widget.js +0 -149
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { agentResultText, createToolPresentationRenderers, isRecord, } from "@hheei/pi-ext-core";
|
|
2
|
+
function stringField(source, key) {
|
|
3
|
+
const value = source?.[key];
|
|
4
|
+
return typeof value === "string" && value.trim() !== "" ? value : undefined;
|
|
5
|
+
}
|
|
6
|
+
function numberField(source, key) {
|
|
7
|
+
const value = source[key];
|
|
8
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
9
|
+
}
|
|
10
|
+
function spawnPresentation(args, result) {
|
|
11
|
+
const record = isRecord(args) ? args : undefined;
|
|
12
|
+
const agent = stringField(record, "agent") ?? "agent";
|
|
13
|
+
const title = stringField(record, "title");
|
|
14
|
+
const task = stringField(record, "task");
|
|
15
|
+
const details = result !== undefined && isRecord(result.details) ? result.details : undefined;
|
|
16
|
+
const id = stringField(details, "id");
|
|
17
|
+
const path = stringField(details, "path");
|
|
18
|
+
const resultText = result === undefined ? undefined : agentResultText(result);
|
|
19
|
+
const rows = [
|
|
20
|
+
...(id === undefined ? [] : [`id: ${id}`]),
|
|
21
|
+
...(path === undefined ? [] : [`path: ${path}`]),
|
|
22
|
+
];
|
|
23
|
+
const summaryParts = [agent];
|
|
24
|
+
if (title !== undefined && title.trim() !== "") {
|
|
25
|
+
summaryParts.push(title.trim());
|
|
26
|
+
}
|
|
27
|
+
if (id !== undefined && id.trim() !== "") {
|
|
28
|
+
summaryParts.push(id.trim());
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
summary: summaryParts.join(" · "),
|
|
32
|
+
...(task === undefined ? {} : { request: task }),
|
|
33
|
+
...(task === undefined
|
|
34
|
+
? {}
|
|
35
|
+
: {
|
|
36
|
+
copy: {
|
|
37
|
+
primary: task,
|
|
38
|
+
full: resultText === undefined ? task : `${task}\n---\n${resultText}`,
|
|
39
|
+
},
|
|
40
|
+
}),
|
|
41
|
+
...(rows.length === 0 ? {} : { output: rows.join("\n") }),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function sendPresentation(args, result) {
|
|
45
|
+
const record = isRecord(args) ? args : undefined;
|
|
46
|
+
const id = stringField(record, "id") ?? "agent";
|
|
47
|
+
const mode = stringField(record, "mode") ?? "auto";
|
|
48
|
+
const message = stringField(record, "message");
|
|
49
|
+
const confirmation = result === undefined ? undefined : agentResultText(result);
|
|
50
|
+
const copy = message === undefined
|
|
51
|
+
? undefined
|
|
52
|
+
: {
|
|
53
|
+
primary: message,
|
|
54
|
+
...(confirmation === undefined ? {} : { full: `${message}\n${confirmation}` }),
|
|
55
|
+
};
|
|
56
|
+
return {
|
|
57
|
+
summary: `${id} · ${mode}`,
|
|
58
|
+
...(message === undefined ? {} : { request: message }),
|
|
59
|
+
...(confirmation === undefined ? {} : { output: confirmation }),
|
|
60
|
+
...(copy === undefined ? {} : { copy }),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const PROGRESS_TEXT_KEYS = ["agent", "path", "presentation", "freshness", "activeTool"];
|
|
64
|
+
const PROGRESS_NUMBER_KEYS = ["turns", "tokens"];
|
|
65
|
+
/** Readable selected fields only; never restates the task or a terminal answer. */
|
|
66
|
+
function detailLines(details) {
|
|
67
|
+
const lines = [];
|
|
68
|
+
const status = stringField(details, "status");
|
|
69
|
+
if (status !== undefined)
|
|
70
|
+
lines.push(`status: ${status}`);
|
|
71
|
+
const progress = isRecord(details.progress) ? details.progress : undefined;
|
|
72
|
+
if (progress !== undefined) {
|
|
73
|
+
for (const key of PROGRESS_TEXT_KEYS) {
|
|
74
|
+
const value = stringField(progress, key);
|
|
75
|
+
if (value !== undefined)
|
|
76
|
+
lines.push(`${key}: ${value}`);
|
|
77
|
+
}
|
|
78
|
+
for (const key of PROGRESS_NUMBER_KEYS) {
|
|
79
|
+
const value = numberField(progress, key);
|
|
80
|
+
if (value !== undefined)
|
|
81
|
+
lines.push(`${key}: ${value}`);
|
|
82
|
+
}
|
|
83
|
+
const summary = stringField(progress, "summary");
|
|
84
|
+
if (summary !== undefined)
|
|
85
|
+
lines.push(`summary: ${summary}`);
|
|
86
|
+
}
|
|
87
|
+
const diagnostic = stringField(details, "diagnostic");
|
|
88
|
+
if (diagnostic !== undefined)
|
|
89
|
+
lines.push(`diagnostic: ${diagnostic}`);
|
|
90
|
+
return lines;
|
|
91
|
+
}
|
|
92
|
+
function getCollapsedRow(details) {
|
|
93
|
+
const parts = [];
|
|
94
|
+
const status = stringField(details, "status");
|
|
95
|
+
if (status !== undefined)
|
|
96
|
+
parts.push(status);
|
|
97
|
+
const progress = isRecord(details.progress) ? details.progress : undefined;
|
|
98
|
+
if (progress !== undefined) {
|
|
99
|
+
const agent = stringField(progress, "agent");
|
|
100
|
+
if (agent !== undefined)
|
|
101
|
+
parts.push(agent);
|
|
102
|
+
const turns = numberField(progress, "turns");
|
|
103
|
+
if (turns !== undefined)
|
|
104
|
+
parts.push(`${turns} turn${turns === 1 ? "" : "s"}`);
|
|
105
|
+
const tokens = numberField(progress, "tokens");
|
|
106
|
+
if (tokens !== undefined) {
|
|
107
|
+
const tokStr = tokens >= 1000 ? `${(tokens / 1000).toFixed(1).replace(/\.0$/u, "")}k` : String(tokens);
|
|
108
|
+
parts.push(`${tokStr} tok`);
|
|
109
|
+
}
|
|
110
|
+
const activeTool = stringField(progress, "activeTool");
|
|
111
|
+
if (activeTool !== undefined) {
|
|
112
|
+
parts.push(activeTool);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
const summary = stringField(progress, "summary");
|
|
116
|
+
if (summary !== undefined)
|
|
117
|
+
parts.push(summary.replace(/\s+/gu, " ").trim());
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const diagnostic = stringField(details, "diagnostic");
|
|
121
|
+
if (diagnostic !== undefined) {
|
|
122
|
+
parts.push(`diagnostic: ${diagnostic.replace(/\s+/gu, " ").trim()}`);
|
|
123
|
+
}
|
|
124
|
+
return parts.length > 0 ? parts.join(" · ") : undefined;
|
|
125
|
+
}
|
|
126
|
+
function getPresentation(args, result) {
|
|
127
|
+
const record = isRecord(args) ? args : undefined;
|
|
128
|
+
const id = stringField(record, "id") ?? "agent";
|
|
129
|
+
const keys = stringField(record, "keys") ?? "status, progress";
|
|
130
|
+
const details = result !== undefined && isRecord(result.details) ? result.details : undefined;
|
|
131
|
+
const lines = details === undefined ? [] : detailLines(details);
|
|
132
|
+
const collapsedRow = details === undefined ? undefined : getCollapsedRow(details);
|
|
133
|
+
const resultText = result === undefined ? undefined : agentResultText(result);
|
|
134
|
+
return {
|
|
135
|
+
summary: id,
|
|
136
|
+
request: `keys: ${keys}`,
|
|
137
|
+
...(lines.length === 0 ? {} : { output: lines.join("\n") }),
|
|
138
|
+
...(collapsedRow === undefined ? {} : { collapsedRows: [collapsedRow] }),
|
|
139
|
+
...(resultText === undefined ? {} : { copy: { primary: resultText } }),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function stopPresentation(args, result) {
|
|
143
|
+
const record = isRecord(args) ? args : undefined;
|
|
144
|
+
const id = stringField(record, "id") ?? "agent";
|
|
145
|
+
const confirmation = result === undefined ? undefined : agentResultText(result);
|
|
146
|
+
return {
|
|
147
|
+
summary: id,
|
|
148
|
+
...(confirmation === undefined ? {} : { output: confirmation }),
|
|
149
|
+
...(confirmation === undefined ? {} : { copy: { primary: confirmation } }),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/** One renderer set per tool, named exactly after its tool so nested calls resolve too. */
|
|
153
|
+
export function parentToolRenderers(pi) {
|
|
154
|
+
return {
|
|
155
|
+
spawn: createToolPresentationRenderers(pi, "agent_spawn", spawnPresentation),
|
|
156
|
+
send: createToolPresentationRenderers(pi, "agent_send", sendPresentation),
|
|
157
|
+
get: createToolPresentationRenderers(pi, "agent_get", getPresentation),
|
|
158
|
+
stop: createToolPresentationRenderers(pi, "agent_stop", stopPresentation),
|
|
159
|
+
};
|
|
160
|
+
}
|
package/dist/tools.d.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { DiscoveredAgent } from "./agent-resolver.js";
|
|
2
3
|
import { type ChildIdentity } from "./domain.js";
|
|
3
4
|
import type { SubagentManager } from "./manager.js";
|
|
4
|
-
export declare function
|
|
5
|
+
export declare function formatAvailableAgents(agents: readonly DiscoveredAgent[]): string;
|
|
6
|
+
declare const GET_KEYS: readonly ["status", "progress", "diagnostic"];
|
|
7
|
+
export type AgentGetKey = (typeof GET_KEYS)[number];
|
|
8
|
+
export declare function registerParentTools(pi: ExtensionAPI, manager: SubagentManager, agents: readonly DiscoveredAgent[]): void;
|
|
5
9
|
/** Sends a child-initiated request over the bridge and resolves with the parent's answer. */
|
|
6
10
|
export type ChildReport = (operation: string, payload: unknown, signal?: AbortSignal) => Promise<unknown>;
|
|
7
11
|
export interface RegisterChildToolsOptions {
|
|
@@ -10,3 +14,4 @@ export interface RegisterChildToolsOptions {
|
|
|
10
14
|
readonly isBound?: (sessionId: string) => boolean;
|
|
11
15
|
}
|
|
12
16
|
export declare function registerChildTools(pi: ExtensionAPI, identity: ChildIdentity, options: RegisterChildToolsOptions): void;
|
|
17
|
+
export {};
|
package/dist/tools.js
CHANGED
|
@@ -1,26 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { textToolResult } from "@hheei/pi-ext-core";
|
|
2
2
|
import { Type } from "typebox";
|
|
3
|
-
import {
|
|
4
|
-
import { builtinAgents } from "./builtin-agents.js";
|
|
3
|
+
import { Value } from "typebox/value";
|
|
5
4
|
import { isOperationError } from "./domain.js";
|
|
5
|
+
import { parentToolRenderers } from "./tool-presentation.js";
|
|
6
6
|
const spawnSchema = Type.Object({
|
|
7
|
-
id: Type.Optional(Type.String({
|
|
8
|
-
description: "Optional subagent id (e.g. 'agent-1') to re-awaken an idle or completed subagent with a new task instead of creating a new one.",
|
|
9
|
-
})),
|
|
10
7
|
task: Type.String({ minLength: 1 }),
|
|
11
8
|
agent: Type.String({
|
|
12
9
|
minLength: 1,
|
|
13
|
-
description: "
|
|
10
|
+
description: "Agent name from <available_agents> in this tool description.",
|
|
14
11
|
}),
|
|
15
12
|
cwd: Type.Optional(Type.String()),
|
|
16
13
|
title: Type.Optional(Type.String({
|
|
17
14
|
maxLength: 60,
|
|
18
15
|
description: "Short session title for this child, e.g. 'OVITO properties editor'. It becomes the child's Pi session title, prefixed with a robot marker so delegated sessions stay recognizable; omit it — or leave it blank — to derive one from the agent name and child id.",
|
|
19
16
|
})),
|
|
20
|
-
|
|
21
|
-
description: "Optional session or subagent id to reference context from: 'parent' (parent session), or a subagent id like 'agent-1' (including subagents that ended in 'error'). Does not inject past conversation into the prompt; provides the referenced session JSONL path in instructions so the subagent can inspect prior context on demand via read or grep. Cannot be 'current'.",
|
|
22
|
-
})),
|
|
23
|
-
});
|
|
17
|
+
}, { additionalProperties: false });
|
|
24
18
|
const sendSchema = Type.Object({
|
|
25
19
|
id: Type.String({ minLength: 1, description: "Subagent id, e.g. agent-1" }),
|
|
26
20
|
message: Type.String({ minLength: 1 }),
|
|
@@ -29,241 +23,203 @@ const sendSchema = Type.Object({
|
|
|
29
23
|
const idSchema = Type.Object({
|
|
30
24
|
id: Type.String({ minLength: 1, description: "Subagent id, e.g. agent-1" }),
|
|
31
25
|
});
|
|
26
|
+
const getSchema = Type.Object({
|
|
27
|
+
id: Type.String({ minLength: 1, description: "Subagent id, e.g. agent-1" }),
|
|
28
|
+
keys: Type.Optional(Type.String({
|
|
29
|
+
description: "Comma-separated fields to return. Defaults to 'status,progress'. Allowed: status, progress, diagnostic.",
|
|
30
|
+
})),
|
|
31
|
+
});
|
|
32
32
|
const contactSchema = Type.Object({
|
|
33
33
|
message: Type.String({
|
|
34
34
|
minLength: 1,
|
|
35
35
|
description: "Explanation of what is blocking progress or the error encountered.",
|
|
36
36
|
}),
|
|
37
37
|
reason: Type.Optional(Type.Union([Type.Literal("blocked"), Type.Literal("error")], {
|
|
38
|
-
description: "Reason: 'blocked' if waiting for input/guidance (can be resumed via
|
|
38
|
+
description: "Reason: 'blocked' if waiting for input/guidance (can be resumed via agent_send), or 'error' if the turn failed (you may retry via agent_send or create a new child). Defaults to 'blocked'.",
|
|
39
39
|
})),
|
|
40
40
|
});
|
|
41
|
-
const emptySchema = Type.Object({});
|
|
42
|
-
function result(value) {
|
|
43
|
-
if (isOperationError(value))
|
|
44
|
-
throw new Error(JSON.stringify(value));
|
|
45
|
-
return textToolResult(typeof value === "string" ? value : JSON.stringify(value), value);
|
|
46
|
-
}
|
|
47
41
|
const SUBAGENTS_NAMESPACE = {
|
|
48
|
-
name: "
|
|
42
|
+
name: "agent",
|
|
43
|
+
aliases: ["agent", "agents", "subagent", "subagents"],
|
|
49
44
|
description: "Subagent orchestration and delegation tools",
|
|
50
45
|
};
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
46
|
+
export function formatAvailableAgents(agents) {
|
|
47
|
+
const available = agents
|
|
48
|
+
.filter((agent) => agent.enabled !== false && agent.frontmatter.hidden !== true)
|
|
49
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
50
|
+
.map((agent) => {
|
|
51
|
+
const description = agent.frontmatter.description;
|
|
52
|
+
return `- ${agent.name}: ${typeof description === "string" && description.trim() !== "" ? description.trim() : "No description provided."}`;
|
|
53
|
+
});
|
|
54
|
+
return [
|
|
55
|
+
"<available_agents>",
|
|
56
|
+
...(available.length > 0 ? available : ["None."]),
|
|
57
|
+
"</available_agents>",
|
|
58
|
+
].join("\n");
|
|
59
|
+
}
|
|
60
|
+
function getSpawnDescription(agents) {
|
|
61
|
+
return [
|
|
62
|
+
"Spawn an independent background subagent task to work on a goal asynchronously. Choose an agent from <available_agents> below.",
|
|
63
|
+
"Returns JSON { id, path }, where path is the absolute child session file path; the first write may still be pending.",
|
|
64
|
+
"Results are returned automatically to this parent session when the task finishes. After spawning, do independent work or end your turn; do not poll agent_get.",
|
|
65
|
+
"Related subagent tools: agent_send, agent_get, agent_stop.",
|
|
66
|
+
formatAvailableAgents(agents),
|
|
67
|
+
].join("\n");
|
|
68
|
+
}
|
|
69
|
+
const SEND_DESCRIPTION = "Send input, steer, or follow-up instructions to an existing subagent (e.g. 'agent-1'), resuming its session when it is idle, blocked, done, or errored. Results are returned automatically when the task finishes; do not poll agent_get. Related subagent tools: agent_spawn, agent_get, agent_stop.";
|
|
70
|
+
const GET_DESCRIPTION = "Inspect one subagent's status, progress, or diagnostic details by id (e.g. 'agent-1'). Use keys to choose fields; defaults to 'status,progress'. Results are returned automatically; use this for inspection, not completion polling. Related subagent tools: agent_spawn, agent_send, agent_stop.";
|
|
71
|
+
const STOP_DESCRIPTION = "Stop a subagent's runtime by id (e.g. 'agent-1'). The child keeps its session, so a later agent_send can resume it. Related subagent tools: agent_spawn, agent_send, agent_get.";
|
|
67
72
|
const CONTACT_DESCRIPTION = "Notify the parent session when you are BLOCKED and cannot continue without parent decision or intervention. Do NOT call this on success or task completion: simply output your final answer directly as normal text in your response, and the harness will automatically capture and report your result to the parent.";
|
|
68
73
|
const CONTACT_SNIPPET = "Report a blocker to the parent. Do NOT call on success: write normal text to finish.";
|
|
69
74
|
const CONTACT_GUIDELINES = [
|
|
70
75
|
"Use contact_parent ONLY if you are blocked or urgently require a parent decision midway.",
|
|
71
76
|
"Do NOT use contact_parent to deliver final findings or report success: output normal text instead.",
|
|
72
|
-
"After reporting a blocker, wait for a parent
|
|
77
|
+
"After reporting a blocker, wait for a parent agent_send instruction.",
|
|
73
78
|
];
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
const GET_KEYS = ["status", "progress", "diagnostic"];
|
|
80
|
+
/** Parses the comma-separated `keys` argument; empty means the default view. */
|
|
81
|
+
function parseAgentGetKeys(raw) {
|
|
82
|
+
if (raw === undefined)
|
|
83
|
+
return ["status", "progress"];
|
|
84
|
+
const keys = [];
|
|
85
|
+
for (const part of raw.split(",")) {
|
|
86
|
+
const key = part.trim();
|
|
87
|
+
if (key === "")
|
|
88
|
+
continue;
|
|
89
|
+
if (!GET_KEYS.includes(key)) {
|
|
90
|
+
throw new Error(`Unknown agent_get key "${key}". Allowed keys: ${GET_KEYS.join(", ")}.`);
|
|
91
|
+
}
|
|
92
|
+
if (!keys.includes(key))
|
|
93
|
+
keys.push(key);
|
|
80
94
|
}
|
|
81
|
-
return
|
|
95
|
+
return keys.length > 0 ? keys : ["status", "progress"];
|
|
82
96
|
}
|
|
83
|
-
function
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
97
|
+
function childAgentDetails(child, keys) {
|
|
98
|
+
const view = {};
|
|
99
|
+
if (keys.includes("status"))
|
|
100
|
+
view.status = child.state;
|
|
101
|
+
if (keys.includes("progress")) {
|
|
102
|
+
view.progress = {
|
|
103
|
+
agent: child.agent,
|
|
104
|
+
...(child.path === undefined ? {} : { path: child.path }),
|
|
105
|
+
presentation: child.presentation,
|
|
106
|
+
freshness: child.freshness,
|
|
107
|
+
...(child.activeTool === undefined ? {} : { activeTool: child.activeTool.slice(0, 200) }),
|
|
108
|
+
...(child.state !== "running" || child.summary === undefined
|
|
109
|
+
? {}
|
|
110
|
+
: { summary: child.summary.slice(0, 2000) }),
|
|
111
|
+
...(child.usage === undefined
|
|
112
|
+
? {}
|
|
113
|
+
: { turns: child.usage.turns, tokens: child.usage.inputTokens + child.usage.outputTokens }),
|
|
114
|
+
};
|
|
88
115
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (count === 0)
|
|
96
|
-
return undefined;
|
|
97
|
-
return count === 1 ? "1 child" : `${count} children`;
|
|
98
|
-
}
|
|
99
|
-
if (Array.isArray(details)) {
|
|
100
|
-
const count = details.length;
|
|
101
|
-
if (count === 0)
|
|
102
|
-
return undefined;
|
|
103
|
-
return count === 1 ? "1 child" : `${count} children`;
|
|
116
|
+
if (keys.includes("diagnostic")) {
|
|
117
|
+
const diagnostic = child.interrupted ??
|
|
118
|
+
(child.state === "blocked" || child.state === "error" ? child.summary : undefined) ??
|
|
119
|
+
(child.freshness === "last_known" ? "Runtime disconnected; state is last known" : undefined);
|
|
120
|
+
if (diagnostic !== undefined)
|
|
121
|
+
view.diagnostic = diagnostic.slice(0, 2000);
|
|
104
122
|
}
|
|
105
|
-
return
|
|
123
|
+
return view;
|
|
106
124
|
}
|
|
107
|
-
export function registerParentTools(pi, manager) {
|
|
108
|
-
|
|
109
|
-
const
|
|
125
|
+
export function registerParentTools(pi, manager, agents) {
|
|
126
|
+
const renderers = parentToolRenderers(pi);
|
|
127
|
+
const inspectedTasks = new Set();
|
|
128
|
+
function inspectionResult(id, text, details) {
|
|
129
|
+
const repeated = inspectedTasks.has(id);
|
|
130
|
+
inspectedTasks.add(id);
|
|
131
|
+
return textToolResult(repeated ? `${text}\nResults are returned automatically; do not poll agent_get.` : text, details);
|
|
132
|
+
}
|
|
110
133
|
const spawn = {
|
|
111
|
-
name: "
|
|
134
|
+
name: "agent_spawn",
|
|
112
135
|
namespace: SUBAGENTS_NAMESPACE,
|
|
113
136
|
label: "Spawn agent",
|
|
114
|
-
description:
|
|
115
|
-
promptSnippet: SPAWN_SNIPPET,
|
|
116
|
-
promptGuidelines: [...SPAWN_GUIDELINES],
|
|
137
|
+
description: getSpawnDescription(agents),
|
|
117
138
|
parameters: spawnSchema,
|
|
139
|
+
exposure: "deferred",
|
|
118
140
|
defaultActive: false,
|
|
119
141
|
annotations: {
|
|
120
142
|
openWorldHint: true,
|
|
121
143
|
},
|
|
122
144
|
async execute(_id, params) {
|
|
123
|
-
|
|
145
|
+
if (!Value.Check(spawnSchema, params))
|
|
146
|
+
throw new Error("Invalid agent_spawn parameters");
|
|
147
|
+
const spawned = await manager.spawn(params);
|
|
148
|
+
if (isOperationError(spawned))
|
|
149
|
+
throw new Error(spawned.reason);
|
|
150
|
+
const { id } = spawned.child;
|
|
151
|
+
const { path } = spawned;
|
|
152
|
+
const details = { id, path };
|
|
153
|
+
return textToolResult(JSON.stringify(details), details);
|
|
124
154
|
},
|
|
155
|
+
...renderers.spawn,
|
|
125
156
|
};
|
|
126
157
|
const send = {
|
|
127
|
-
name: "
|
|
158
|
+
name: "agent_send",
|
|
128
159
|
namespace: SUBAGENTS_NAMESPACE,
|
|
129
160
|
label: "Send to agent",
|
|
130
161
|
description: SEND_DESCRIPTION,
|
|
131
|
-
promptSnippet: SEND_SNIPPET,
|
|
132
|
-
promptGuidelines: [
|
|
133
|
-
"Prefer reusing an existing subagent in 'done' or 'blocked' states via send_agent for subsequent or functionally identical/related subtasks instead of spawning a new subagent each time. This preserves context and maximizes memory and prompt cache efficiency.",
|
|
134
|
-
"Do not wait for or ask workers to 'freeze': idle subagents are dormant and touch nothing. Dispatch reviewers or follow-ups immediately.",
|
|
135
|
-
"Do not poll get_agent or list_agents afterwards.",
|
|
136
|
-
"Child reports arrive as subagent-report messages that start your next turn.",
|
|
137
|
-
],
|
|
138
162
|
parameters: sendSchema,
|
|
163
|
+
exposure: "deferred",
|
|
139
164
|
defaultActive: false,
|
|
140
165
|
annotations: {
|
|
141
166
|
openWorldHint: true,
|
|
142
167
|
},
|
|
143
168
|
async execute(_id, params, signal) {
|
|
144
|
-
|
|
169
|
+
const sent = await manager.send(params.id, params.message, params.mode, signal);
|
|
170
|
+
if (isOperationError(sent))
|
|
171
|
+
throw new Error(sent.reason);
|
|
172
|
+
return textToolResult("Message sent.", undefined);
|
|
145
173
|
},
|
|
174
|
+
...renderers.send,
|
|
146
175
|
};
|
|
147
176
|
const get = {
|
|
148
|
-
name: "
|
|
177
|
+
name: "agent_get",
|
|
149
178
|
namespace: SUBAGENTS_NAMESPACE,
|
|
150
179
|
label: "Get agent",
|
|
151
180
|
description: GET_DESCRIPTION,
|
|
152
|
-
|
|
153
|
-
|
|
181
|
+
parameters: getSchema,
|
|
182
|
+
exposure: "deferred",
|
|
154
183
|
defaultActive: false,
|
|
155
184
|
annotations: {
|
|
156
185
|
readOnlyHint: true,
|
|
157
186
|
idempotentHint: true,
|
|
158
187
|
},
|
|
159
188
|
async execute(_id, params) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
label: "List agents",
|
|
167
|
-
description: LIST_DESCRIPTION,
|
|
168
|
-
promptSnippet: LIST_SNIPPET,
|
|
169
|
-
parameters: emptySchema,
|
|
170
|
-
annotations: {
|
|
171
|
-
readOnlyHint: true,
|
|
172
|
-
idempotentHint: true,
|
|
173
|
-
},
|
|
174
|
-
async execute(_id, _params, _signal, _onUpdate, ctx) {
|
|
175
|
-
const children = await manager.list();
|
|
176
|
-
const cwd = ctx !== undefined && typeof ctx === "object" && "cwd" in ctx && typeof ctx.cwd === "string"
|
|
177
|
-
? ctx.cwd
|
|
178
|
-
: process.cwd();
|
|
179
|
-
let allAgents;
|
|
180
|
-
try {
|
|
181
|
-
allAgents = await discoverAgents(cwd);
|
|
182
|
-
}
|
|
183
|
-
catch {
|
|
184
|
-
allAgents = builtinAgents();
|
|
185
|
-
}
|
|
186
|
-
const visible = allAgents.filter((agent) => agent.enabled !== false && agent.frontmatter.hidden !== true);
|
|
187
|
-
const interactiveAgents = visible
|
|
188
|
-
.filter((agent) => agent.frontmatter.interactive === true)
|
|
189
|
-
.map((agent) => ({
|
|
190
|
-
name: agent.name,
|
|
191
|
-
description: typeof agent.frontmatter.description === "string" &&
|
|
192
|
-
agent.frontmatter.description.trim() !== ""
|
|
193
|
-
? agent.frontmatter.description.trim()
|
|
194
|
-
: "No description provided.",
|
|
195
|
-
}))
|
|
196
|
-
.sort((a, b) => a.name.localeCompare(b.name));
|
|
197
|
-
const lines = [];
|
|
198
|
-
if (interactiveAgents.length > 0) {
|
|
199
|
-
lines.push("<interactive_agents>");
|
|
200
|
-
for (const agent of interactiveAgents) {
|
|
201
|
-
lines.push(`- ${agent.name}: ${agent.description}`);
|
|
202
|
-
}
|
|
203
|
-
lines.push("</interactive_agents>");
|
|
204
|
-
}
|
|
205
|
-
if (children.length > 0) {
|
|
206
|
-
if (lines.length > 0)
|
|
207
|
-
lines.push("");
|
|
208
|
-
lines.push("<running_agents>");
|
|
209
|
-
for (const child of children) {
|
|
210
|
-
const summaryPart = child.summary ? ` | summary: ${child.summary}` : "";
|
|
211
|
-
lines.push(`- id: ${child.id} | agent: ${child.agent} | state: ${child.state}${summaryPart}`);
|
|
212
|
-
}
|
|
213
|
-
lines.push("</running_agents>");
|
|
214
|
-
}
|
|
215
|
-
if (lines.length === 0) {
|
|
216
|
-
lines.push("No active or available interactive subagents.");
|
|
217
|
-
}
|
|
218
|
-
return textToolResult(lines.join("\n"), {
|
|
219
|
-
...(interactiveAgents.length > 0 ? { interactiveAgents } : {}),
|
|
220
|
-
runningAgents: children,
|
|
221
|
-
});
|
|
189
|
+
const keys = parseAgentGetKeys(params.keys);
|
|
190
|
+
const child = await manager.get(params.id);
|
|
191
|
+
if (isOperationError(child))
|
|
192
|
+
throw new Error(child.reason);
|
|
193
|
+
const view = childAgentDetails(child, keys);
|
|
194
|
+
return inspectionResult(params.id, JSON.stringify(view), view);
|
|
222
195
|
},
|
|
196
|
+
...renderers.get,
|
|
223
197
|
};
|
|
224
198
|
const stop = {
|
|
225
|
-
name: "
|
|
199
|
+
name: "agent_stop",
|
|
226
200
|
namespace: SUBAGENTS_NAMESPACE,
|
|
227
201
|
label: "Stop agent",
|
|
228
202
|
description: STOP_DESCRIPTION,
|
|
229
203
|
parameters: idSchema,
|
|
204
|
+
exposure: "deferred",
|
|
230
205
|
defaultActive: false,
|
|
231
206
|
annotations: {
|
|
232
207
|
destructiveHint: true,
|
|
233
208
|
},
|
|
234
209
|
async execute(_id, params) {
|
|
235
|
-
|
|
210
|
+
const stopped = await manager.stop(params.id);
|
|
211
|
+
if (isOperationError(stopped))
|
|
212
|
+
throw new Error(stopped.reason);
|
|
213
|
+
return textToolResult("Agent stopped.", undefined);
|
|
236
214
|
},
|
|
215
|
+
...renderers.stop,
|
|
237
216
|
};
|
|
238
|
-
pi.registerTool(
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
}));
|
|
243
|
-
pi.registerTool(tui.frame(send, {
|
|
244
|
-
summary: (args) => args.id,
|
|
245
|
-
headerLine: "truncate",
|
|
246
|
-
footer: childStateFooter,
|
|
247
|
-
}));
|
|
248
|
-
pi.registerTool(tui.frame(get, {
|
|
249
|
-
summary: (args) => args.id,
|
|
250
|
-
headerLine: "truncate",
|
|
251
|
-
footer: childStateFooter,
|
|
252
|
-
}));
|
|
253
|
-
pi.registerTool(tui.frame(list, {
|
|
254
|
-
summary: () => "owned children",
|
|
255
|
-
headerLine: "truncate",
|
|
256
|
-
footer: listFooter,
|
|
257
|
-
}));
|
|
258
|
-
pi.registerTool(tui.frame(stop, {
|
|
259
|
-
summary: (args) => args.id,
|
|
260
|
-
headerLine: "truncate",
|
|
261
|
-
footer: childStateFooter,
|
|
262
|
-
}));
|
|
217
|
+
pi.registerTool(spawn);
|
|
218
|
+
pi.registerTool(send);
|
|
219
|
+
pi.registerTool(get);
|
|
220
|
+
pi.registerTool(stop);
|
|
263
221
|
}
|
|
264
222
|
export function registerChildTools(pi, identity, options) {
|
|
265
|
-
registerToolTuiTrace(pi);
|
|
266
|
-
const tui = getToolTui(pi);
|
|
267
223
|
const tool = {
|
|
268
224
|
name: "contact_parent",
|
|
269
225
|
label: "Contact parent",
|
|
@@ -289,11 +245,8 @@ export function registerChildTools(pi, identity, options) {
|
|
|
289
245
|
await options.report("contact_parent", details, signal);
|
|
290
246
|
return textToolResult(reason === "error"
|
|
291
247
|
? "Error report delivered to the parent."
|
|
292
|
-
: "Blocker report queued for the parent.",
|
|
248
|
+
: "Blocker report queued for the parent.", undefined);
|
|
293
249
|
},
|
|
294
250
|
};
|
|
295
|
-
pi.registerTool(
|
|
296
|
-
summary: (args) => args.message,
|
|
297
|
-
headerLine: "truncate",
|
|
298
|
-
}));
|
|
251
|
+
pi.registerTool(tool);
|
|
299
252
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hheei/pi-subagents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Independent background Pi subagent sessions with durable identity and RPC control.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -35,16 +35,16 @@
|
|
|
35
35
|
"access": "public"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@hheei/pi-ext-core": "^0.
|
|
38
|
+
"@hheei/pi-ext-core": "^0.3.0"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@earendil-works/pi-coding-agent": ">=1.
|
|
42
|
-
"@earendil-works/pi-tui": ">=1.
|
|
41
|
+
"@earendil-works/pi-coding-agent": ">=1.2.0",
|
|
42
|
+
"@earendil-works/pi-tui": ">=1.2.0",
|
|
43
43
|
"typebox": "*"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@earendil-works/pi-coding-agent": "1.
|
|
47
|
-
"@earendil-works/pi-tui": "1.
|
|
46
|
+
"@earendil-works/pi-coding-agent": "1.2.0",
|
|
47
|
+
"@earendil-works/pi-tui": "1.2.0"
|
|
48
48
|
},
|
|
49
49
|
"scripts": {
|
|
50
50
|
"build": "node -e \"import { rmSync } from 'node:fs'; rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.build.json"
|
package/dist/session-fork.d.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { SubagentRegistry } from "./registry.js";
|
|
2
|
-
/** Reads all JSON entries from a session file on disk. */
|
|
3
|
-
export declare function readSessionJsonlEntries(sessionPath: string): Promise<unknown[]>;
|
|
4
|
-
export interface ResolveForkSessionReferenceOptions {
|
|
5
|
-
readonly forkFrom: string;
|
|
6
|
-
readonly parentSessionPath?: string | undefined;
|
|
7
|
-
readonly registry?: Pick<SubagentRegistry, "get"> | undefined;
|
|
8
|
-
readonly onWarning?: ((message: string) => void) | undefined;
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Resolves the source session JSONL file path for on-demand context inspection.
|
|
12
|
-
* Rejects 'current' to prevent ambiguous self-references.
|
|
13
|
-
*/
|
|
14
|
-
export declare function resolveForkSessionReference(options: ResolveForkSessionReferenceOptions): Promise<string>;
|
|
15
|
-
/** Formats instructions explaining where the referenced session file is and how to inspect it on demand. */
|
|
16
|
-
export declare function formatSessionReferencePrompt(sessionPath: string): string;
|