@hheei/pi-subagents 0.6.0 → 0.8.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/LICENSES/Apache-2.0.txt +202 -0
- package/NOTICE +10 -0
- package/README.md +14 -12
- package/dist/child-control.d.ts +0 -8
- package/dist/child-control.js +66 -3
- package/dist/commands.js +1 -1
- package/dist/controller-ipc.d.ts +38 -0
- package/dist/controller-ipc.js +292 -0
- package/dist/controller-main.d.ts +1 -0
- package/dist/controller-main.js +26 -0
- package/dist/controller-role.d.ts +40 -0
- package/dist/controller-role.js +37 -0
- package/dist/controller-socket.d.ts +6 -0
- package/dist/controller-socket.js +43 -0
- package/dist/controller.d.ts +25 -0
- package/dist/controller.js +217 -0
- package/dist/delegation.d.ts +73 -0
- package/dist/delegation.js +71 -0
- package/dist/domain.d.ts +20 -18
- package/dist/domain.js +6 -1
- package/dist/extension.js +43 -26
- package/dist/host-adapter.js +1 -1
- package/dist/launch-spec.d.ts +1 -3
- package/dist/launch-spec.js +39 -15
- package/dist/lease.d.ts +0 -1
- package/dist/lease.js +8 -11
- package/dist/mailbox.js +110 -34
- package/dist/manager.d.ts +52 -6
- package/dist/manager.js +585 -48
- package/dist/mcp/app.html +948 -0
- package/dist/mcp/delegate.d.ts +30 -0
- package/dist/mcp/delegate.js +44 -0
- package/dist/mcp/views.d.ts +5 -0
- package/dist/mcp/views.js +12 -0
- package/dist/mcp-server.d.ts +6 -0
- package/dist/mcp-server.js +287 -0
- package/dist/mcp-stdio.d.ts +2 -0
- package/dist/mcp-stdio.js +111 -0
- package/dist/process-lock.d.ts +1 -1
- package/dist/process-lock.js +4 -2
- package/dist/protocol.d.ts +5 -5
- package/dist/protocol.js +3 -4
- package/dist/receipts.d.ts +80 -0
- package/dist/receipts.js +326 -0
- package/dist/runtime.d.ts +5 -26
- package/dist/runtime.js +15 -57
- package/dist/session-bootstrap.js +2 -6
- package/dist/state-db.d.ts +11 -4
- package/dist/state-db.js +102 -24
- package/dist/state.d.ts +1 -0
- package/dist/state.js +29 -3
- package/dist/tool-activation.d.ts +2 -2
- package/dist/tool-activation.js +5 -5
- package/dist/tool-presentation.d.ts +2 -6
- package/dist/tool-presentation.js +75 -122
- package/dist/tools.d.ts +0 -3
- package/dist/tools.js +104 -132
- package/package.json +12 -5
- package/dist/json-lines.d.ts +0 -14
- package/dist/json-lines.js +0 -95
package/dist/state-db.js
CHANGED
|
@@ -2,11 +2,17 @@ import { chmodSync, mkdirSync } from "node:fs";
|
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { DatabaseSync } from "node:sqlite";
|
|
4
4
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
5
|
-
/** The one durable file for child records,
|
|
5
|
+
/** The one durable file for child records, the mailbox, and delegation receipts. */
|
|
6
6
|
export function defaultStatePath() {
|
|
7
7
|
return join(getAgentDir(), "pi-subagents", "state.sqlite");
|
|
8
8
|
}
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Bumped only when an existing ledger needs work. A ledger written by a newer version is refused
|
|
11
|
+
* instead of being rewritten: this file is runtime state, but silently discarding another build's
|
|
12
|
+
* records is worse than failing to start.
|
|
13
|
+
*/
|
|
14
|
+
const SCHEMA_VERSION = 2;
|
|
15
|
+
const TABLES = `
|
|
10
16
|
CREATE TABLE IF NOT EXISTS children (
|
|
11
17
|
parent_session_id TEXT NOT NULL,
|
|
12
18
|
subagent_id TEXT NOT NULL,
|
|
@@ -33,27 +39,91 @@ CREATE TABLE IF NOT EXISTS commands (
|
|
|
33
39
|
error TEXT,
|
|
34
40
|
created_at INTEGER NOT NULL
|
|
35
41
|
);
|
|
36
|
-
CREATE
|
|
37
|
-
ON commands (parent_session_id, subagent_id, direction, status, created_at);
|
|
38
|
-
CREATE TABLE IF NOT EXISTS tokens (
|
|
42
|
+
CREATE TABLE IF NOT EXISTS delegations (
|
|
39
43
|
parent_session_id TEXT NOT NULL,
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
delegation_id TEXT NOT NULL,
|
|
45
|
+
child_id TEXT,
|
|
46
|
+
delegation_revision INTEGER,
|
|
47
|
+
task TEXT NOT NULL,
|
|
48
|
+
input_checkpoint TEXT,
|
|
49
|
+
run_boundary INTEGER,
|
|
50
|
+
control_id TEXT,
|
|
51
|
+
status TEXT NOT NULL,
|
|
52
|
+
delivery TEXT NOT NULL,
|
|
53
|
+
result_text TEXT,
|
|
54
|
+
safe_to_retry INTEGER,
|
|
55
|
+
side_effects TEXT,
|
|
56
|
+
created_at INTEGER NOT NULL,
|
|
57
|
+
updated_at INTEGER NOT NULL,
|
|
58
|
+
PRIMARY KEY (parent_session_id, delegation_id)
|
|
43
59
|
);
|
|
44
60
|
`;
|
|
61
|
+
/**
|
|
62
|
+
* Indexes are created after the column repair below, because a ledger that predates
|
|
63
|
+
* `delegation_revision` cannot index it yet.
|
|
64
|
+
*/
|
|
65
|
+
const INDEXES = `
|
|
66
|
+
CREATE INDEX IF NOT EXISTS commands_pending
|
|
67
|
+
ON commands (parent_session_id, subagent_id, direction, status, created_at);
|
|
68
|
+
CREATE INDEX IF NOT EXISTS delegations_child
|
|
69
|
+
ON delegations (parent_session_id, child_id, status);
|
|
70
|
+
CREATE UNIQUE INDEX IF NOT EXISTS delegations_revision
|
|
71
|
+
ON delegations (parent_session_id, child_id, delegation_revision);
|
|
72
|
+
`;
|
|
73
|
+
/**
|
|
74
|
+
* Columns added to a table that already exists. `CREATE TABLE IF NOT EXISTS` leaves an older
|
|
75
|
+
* table untouched, so an 11-column `delegations` would keep failing the indexes above.
|
|
76
|
+
*/
|
|
77
|
+
const ADDED_COLUMNS = [
|
|
78
|
+
["delegations", "delegation_revision", "INTEGER"],
|
|
79
|
+
["delegations", "control_id", "TEXT"],
|
|
80
|
+
["delegations", "safe_to_retry", "INTEGER"],
|
|
81
|
+
["delegations", "side_effects", "TEXT"],
|
|
82
|
+
];
|
|
45
83
|
const connections = new Map();
|
|
84
|
+
function schemaVersion(db) {
|
|
85
|
+
return Number(db.prepare("PRAGMA user_version").get()?.user_version ?? 0);
|
|
86
|
+
}
|
|
87
|
+
/** Brings any older ledger to {@link SCHEMA_VERSION} in place, keeping every row. */
|
|
88
|
+
function migrate(db) {
|
|
89
|
+
const version = schemaVersion(db);
|
|
90
|
+
if (version > SCHEMA_VERSION) {
|
|
91
|
+
throw new Error(`the subagent ledger was written by a newer version (schema ${version}); this version understands schema ${SCHEMA_VERSION}`);
|
|
92
|
+
}
|
|
93
|
+
if (version === SCHEMA_VERSION)
|
|
94
|
+
return;
|
|
95
|
+
withWrite(db, () => {
|
|
96
|
+
if (schemaVersion(db) === SCHEMA_VERSION)
|
|
97
|
+
return;
|
|
98
|
+
db.exec(TABLES);
|
|
99
|
+
for (const [table, column, type] of ADDED_COLUMNS) {
|
|
100
|
+
const columns = db.prepare(`PRAGMA table_info(${table})`).all();
|
|
101
|
+
if (columns.some((existing) => existing.name === column))
|
|
102
|
+
continue;
|
|
103
|
+
db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${type}`);
|
|
104
|
+
}
|
|
105
|
+
// The bridge token had no reader once the mailbox replaced the socket handshake.
|
|
106
|
+
db.exec("DROP TABLE IF EXISTS tokens");
|
|
107
|
+
db.exec(INDEXES);
|
|
108
|
+
db.exec(`PRAGMA user_version = ${SCHEMA_VERSION}`);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
46
111
|
export function openStateDb(filePath) {
|
|
47
112
|
const cached = connections.get(filePath);
|
|
48
113
|
if (cached !== undefined)
|
|
49
114
|
return cached;
|
|
50
115
|
mkdirSync(dirname(filePath), { recursive: true, mode: 0o700 });
|
|
51
116
|
const db = new DatabaseSync(filePath);
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
117
|
+
try {
|
|
118
|
+
db.exec("PRAGMA busy_timeout = 5000");
|
|
119
|
+
db.exec("PRAGMA journal_mode = WAL");
|
|
120
|
+
migrate(db);
|
|
121
|
+
chmodSync(filePath, 0o600);
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
db.close();
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
57
127
|
connections.set(filePath, db);
|
|
58
128
|
return db;
|
|
59
129
|
}
|
|
@@ -194,16 +264,24 @@ export function finishCommand(db, id, outcome) {
|
|
|
194
264
|
}
|
|
195
265
|
db.prepare("UPDATE commands SET status = 'failed', error_code = ?, error = ? WHERE id = ?").run(outcome.errorCode, outcome.error, id);
|
|
196
266
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
db.prepare(`INSERT INTO tokens (parent_session_id, runtime_identity, token) VALUES (?, ?, ?)
|
|
205
|
-
ON CONFLICT (parent_session_id, runtime_identity) DO UPDATE SET token = excluded.token`).run(parentSessionId, runtimeIdentity, token);
|
|
267
|
+
/**
|
|
268
|
+
* A settled command whose waiter died with its process has no reader left. Only a parent that is
|
|
269
|
+
* taking over the mailbox may run this: a live waiter deletes its own row when it reads it.
|
|
270
|
+
*/
|
|
271
|
+
export function deleteSettledCommands(db, parentSessionId) {
|
|
272
|
+
db.prepare(`DELETE FROM commands
|
|
273
|
+
WHERE parent_session_id = ? AND status IN ('done', 'failed')`).run(parentSessionId);
|
|
206
274
|
}
|
|
207
|
-
|
|
208
|
-
|
|
275
|
+
/**
|
|
276
|
+
* Keeps only the newest `keep` queued events for one child. Events are advisory snapshots, so a
|
|
277
|
+
* parent that stopped reading must not leave an unbounded backlog behind.
|
|
278
|
+
*/
|
|
279
|
+
export function trimEventBacklog(db, parentSessionId, subagentId, keep) {
|
|
280
|
+
db.prepare(`DELETE FROM commands WHERE id IN (
|
|
281
|
+
SELECT id FROM commands
|
|
282
|
+
WHERE parent_session_id = ? AND subagent_id = ? AND direction = 'to_parent'
|
|
283
|
+
AND operation = 'event' AND status = 'pending'
|
|
284
|
+
ORDER BY created_at DESC
|
|
285
|
+
LIMIT -1 OFFSET ?
|
|
286
|
+
)`).run(parentSessionId, subagentId, keep);
|
|
209
287
|
}
|
package/dist/state.d.ts
CHANGED
package/dist/state.js
CHANGED
|
@@ -32,6 +32,14 @@ function text(message) {
|
|
|
32
32
|
.trim();
|
|
33
33
|
return value === "" ? undefined : value;
|
|
34
34
|
}
|
|
35
|
+
/** Activity lines stay short. The full answer lives on the receipt, not in the dashboard summary. */
|
|
36
|
+
const ACTIVITY_SUMMARY_LIMIT = 240;
|
|
37
|
+
function activitySummary(value) {
|
|
38
|
+
const flat = value.replace(/\s+/g, " ").trim();
|
|
39
|
+
if (flat.length <= ACTIVITY_SUMMARY_LIMIT)
|
|
40
|
+
return flat;
|
|
41
|
+
return `${flat.slice(0, ACTIVITY_SUMMARY_LIMIT - 1)}…`;
|
|
42
|
+
}
|
|
35
43
|
function completed(message) {
|
|
36
44
|
return message.stopReason !== "pending" && message.stopReason !== "deferred";
|
|
37
45
|
}
|
|
@@ -83,7 +91,7 @@ export function summarizeCurrentBranch(entries) {
|
|
|
83
91
|
continue;
|
|
84
92
|
const value = text(item.message);
|
|
85
93
|
if (value !== undefined)
|
|
86
|
-
return value;
|
|
94
|
+
return activitySummary(value);
|
|
87
95
|
}
|
|
88
96
|
return undefined;
|
|
89
97
|
}
|
|
@@ -123,12 +131,26 @@ function lastCompletedAssistant(entries) {
|
|
|
123
131
|
}
|
|
124
132
|
return last;
|
|
125
133
|
}
|
|
134
|
+
function lastAssistantContextTokens(message) {
|
|
135
|
+
if (message === undefined)
|
|
136
|
+
return undefined;
|
|
137
|
+
const usage = record(message.usage);
|
|
138
|
+
if (usage === undefined)
|
|
139
|
+
return undefined;
|
|
140
|
+
const input = finite(usage.input) ?? 0;
|
|
141
|
+
const output = finite(usage.output) ?? 0;
|
|
142
|
+
const cacheRead = finite(usage.cacheRead) ?? 0;
|
|
143
|
+
const cacheWrite = finite(usage.cacheWrite) ?? 0;
|
|
144
|
+
const total = input + output + cacheRead + cacheWrite;
|
|
145
|
+
return total > 0 ? total : undefined;
|
|
146
|
+
}
|
|
126
147
|
/** What the parent projects from a session. The transcript itself never crosses the bridge. */
|
|
127
148
|
export function transcriptFacts(entries, expectedInput) {
|
|
128
149
|
const last = lastCompletedAssistant(entries);
|
|
129
150
|
const summary = summarizeCurrentBranch(entries);
|
|
130
151
|
const stopReason = typeof last?.stopReason === "string" ? last.stopReason : undefined;
|
|
131
152
|
const interrupted = last === undefined ? undefined : diagnostic(last);
|
|
153
|
+
const contextTokens = lastAssistantContextTokens(last);
|
|
132
154
|
const containsInput = expectedInput !== undefined &&
|
|
133
155
|
entries.some((entry) => {
|
|
134
156
|
const value = record(entry);
|
|
@@ -140,6 +162,7 @@ export function transcriptFacts(entries, expectedInput) {
|
|
|
140
162
|
...(summary === undefined ? {} : { summary }),
|
|
141
163
|
...(stopReason === undefined ? {} : { stopReason }),
|
|
142
164
|
...(interrupted === undefined ? {} : { interrupted }),
|
|
165
|
+
...(contextTokens === undefined ? {} : { contextTokens }),
|
|
143
166
|
containsInput,
|
|
144
167
|
};
|
|
145
168
|
}
|
|
@@ -166,9 +189,10 @@ export function parseTranscriptFacts(value) {
|
|
|
166
189
|
costUsd === undefined) {
|
|
167
190
|
return undefined;
|
|
168
191
|
}
|
|
169
|
-
const summary = typeof value.summary === "string" ? value.summary : undefined;
|
|
192
|
+
const summary = typeof value.summary === "string" ? activitySummary(value.summary) : undefined;
|
|
170
193
|
const stopReason = typeof value.stopReason === "string" ? value.stopReason : undefined;
|
|
171
194
|
const interrupted = typeof value.interrupted === "string" ? value.interrupted : undefined;
|
|
195
|
+
const contextTokens = usageNumber(value.contextTokens);
|
|
172
196
|
return {
|
|
173
197
|
usage: {
|
|
174
198
|
inputTokens,
|
|
@@ -181,6 +205,7 @@ export function parseTranscriptFacts(value) {
|
|
|
181
205
|
...(summary === undefined ? {} : { summary }),
|
|
182
206
|
...(stopReason === undefined ? {} : { stopReason }),
|
|
183
207
|
...(interrupted === undefined ? {} : { interrupted }),
|
|
208
|
+
...(contextTokens === undefined ? {} : { contextTokens }),
|
|
184
209
|
containsInput: value.containsInput,
|
|
185
210
|
};
|
|
186
211
|
}
|
|
@@ -280,7 +305,8 @@ export function createStateProjector(initialState = "running") {
|
|
|
280
305
|
const messages = Array.isArray(value.messages) ? value.messages : [];
|
|
281
306
|
const message = record(messages.length > 0 ? messages[messages.length - 1] : value.message);
|
|
282
307
|
if (message?.role === "assistant") {
|
|
283
|
-
|
|
308
|
+
const spoken = text(message);
|
|
309
|
+
summary = spoken === undefined ? summary : activitySummary(spoken);
|
|
284
310
|
if (value.type === "agent_end" && message.stopReason === "error") {
|
|
285
311
|
state = "error";
|
|
286
312
|
interrupted = diagnostic(message);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { SubagentManager } from "./manager.js";
|
|
3
3
|
/** Interactive agent tools managed by dynamic activation. */
|
|
4
|
-
export declare const INTERACTIVE_TOOL_NAMES: readonly ["
|
|
4
|
+
export declare const INTERACTIVE_TOOL_NAMES: readonly ["agents_delegate", "agents_get", "agents_stop", "agents_dashboard"];
|
|
5
5
|
export type InteractiveToolName = (typeof INTERACTIVE_TOOL_NAMES)[number];
|
|
6
6
|
export declare const SUBAGENT_OPTIONAL_TOOL_KEY = "subagents";
|
|
7
|
-
export declare const SUBAGENT_OPTIONAL_TOOL_BULLET = "subagents (query: \"agent\"):
|
|
7
|
+
export declare const SUBAGENT_OPTIONAL_TOOL_BULLET = "subagents (query: \"agent\"): Agent orchestration and delegation (agents_delegate, agents_get, agents_stop, agents_dashboard).";
|
|
8
8
|
export interface RegisterInteractiveToolActivationOptions {
|
|
9
9
|
readonly pi: ExtensionAPI;
|
|
10
10
|
readonly manager: SubagentManager;
|
package/dist/tool-activation.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { setOptionalToolBullet } from "@hheei/pi-ext-core";
|
|
2
2
|
/** Interactive agent tools managed by dynamic activation. */
|
|
3
3
|
export const INTERACTIVE_TOOL_NAMES = [
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
4
|
+
"agents_delegate",
|
|
5
|
+
"agents_get",
|
|
6
|
+
"agents_stop",
|
|
7
|
+
"agents_dashboard",
|
|
8
8
|
];
|
|
9
9
|
export const SUBAGENT_OPTIONAL_TOOL_KEY = "subagents";
|
|
10
|
-
export const SUBAGENT_OPTIONAL_TOOL_BULLET = 'subagents (query: "agent"):
|
|
10
|
+
export const SUBAGENT_OPTIONAL_TOOL_BULLET = 'subagents (query: "agent"): Agent orchestration and delegation (agents_delegate, agents_get, agents_stop, agents_dashboard).';
|
|
11
11
|
export function registerInteractiveToolActivation(options) {
|
|
12
12
|
const { pi } = options;
|
|
13
13
|
const unbindBeforeAgent = pi.on("before_agent_start", (event) => {
|
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
import type { ExtensionAPI, ToolRenderers } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
export interface AgentSpawnDetails {
|
|
3
|
-
readonly id: string;
|
|
4
|
-
readonly path: string;
|
|
5
|
-
}
|
|
6
2
|
/** One renderer set per tool, named exactly after its tool so nested calls resolve too. */
|
|
7
3
|
export declare function parentToolRenderers(pi: ExtensionAPI): {
|
|
8
|
-
readonly
|
|
9
|
-
readonly send: ToolRenderers;
|
|
4
|
+
readonly delegate: ToolRenderers;
|
|
10
5
|
readonly get: ToolRenderers;
|
|
11
6
|
readonly stop: ToolRenderers;
|
|
7
|
+
readonly dashboard: ToolRenderers;
|
|
12
8
|
};
|
|
13
9
|
/** The child's blocker report names the reason in the header and keeps the full message readable. */
|
|
14
10
|
export declare function contactParentRenderers(pi: ExtensionAPI): ToolRenderers;
|
|
@@ -14,150 +14,103 @@ function stringField(source, key) {
|
|
|
14
14
|
const value = source?.[key];
|
|
15
15
|
return typeof value === "string" && value.trim() !== "" ? value : undefined;
|
|
16
16
|
}
|
|
17
|
-
function
|
|
18
|
-
const
|
|
19
|
-
return typeof
|
|
17
|
+
function delegationDetails(result) {
|
|
18
|
+
const details = result?.details;
|
|
19
|
+
return isRecord(details) && typeof details.id === "string"
|
|
20
|
+
? details
|
|
21
|
+
: undefined;
|
|
20
22
|
}
|
|
21
|
-
|
|
23
|
+
/** Readable delegation state; the result body is copied, never restated as state. */
|
|
24
|
+
function delegationLines(view) {
|
|
25
|
+
const lines = [`status: ${view.status}`, `revision: ${view.revision}`];
|
|
26
|
+
if (view.progress !== undefined) {
|
|
27
|
+
lines.push(`runtime: ${view.progress.runtimeState}`);
|
|
28
|
+
if (view.progress.activeTool !== undefined)
|
|
29
|
+
lines.push(`activeTool: ${view.progress.activeTool}`);
|
|
30
|
+
if (view.progress.summary !== undefined)
|
|
31
|
+
lines.push(`summary: ${view.progress.summary}`);
|
|
32
|
+
}
|
|
33
|
+
lines.push(`hasOutput: ${String(view.hasOutput)}`);
|
|
34
|
+
if (view.error !== undefined)
|
|
35
|
+
lines.push(`error: ${view.error.reason}`);
|
|
36
|
+
return lines;
|
|
37
|
+
}
|
|
38
|
+
function delegationRow(view) {
|
|
39
|
+
const parts = [view.status];
|
|
40
|
+
if (view.agent !== undefined)
|
|
41
|
+
parts.push(view.agent);
|
|
42
|
+
if (view.progress !== undefined) {
|
|
43
|
+
parts.push(view.progress.activeTool ?? view.progress.runtimeState);
|
|
44
|
+
}
|
|
45
|
+
parts.push(`rev ${view.revision}`);
|
|
46
|
+
return parts.join(" · ");
|
|
47
|
+
}
|
|
48
|
+
function delegatePresentation(args, result) {
|
|
22
49
|
const record = isRecord(args) ? args : undefined;
|
|
23
|
-
const agent = stringField(record, "agent")
|
|
24
|
-
const
|
|
50
|
+
const agent = stringField(record, "agent");
|
|
51
|
+
const id = stringField(record, "id");
|
|
25
52
|
const task = stringField(record, "task");
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
...(id === undefined ? [] : [`id: ${id}`]),
|
|
32
|
-
...(path === undefined ? [] : [`path: ${path}`]),
|
|
33
|
-
];
|
|
34
|
-
const summaryParts = [agent];
|
|
35
|
-
if (title !== undefined && title.trim() !== "") {
|
|
36
|
-
summaryParts.push(title.trim());
|
|
53
|
+
const view = delegationDetails(result);
|
|
54
|
+
const resultText = view?.resultText;
|
|
55
|
+
const summaryParts = [view?.agent ?? agent ?? "agent"];
|
|
56
|
+
if (view !== undefined) {
|
|
57
|
+
summaryParts.push(view.id, `rev ${view.revision}`);
|
|
37
58
|
}
|
|
38
|
-
if (id !== undefined
|
|
39
|
-
summaryParts.push(id
|
|
59
|
+
else if (id !== undefined) {
|
|
60
|
+
summaryParts.push(id);
|
|
40
61
|
}
|
|
62
|
+
const output = resultText ?? (view === undefined ? undefined : delegationLines(view).join("\n"));
|
|
41
63
|
return {
|
|
42
64
|
summary: summaryParts.join(" · "),
|
|
43
65
|
...(task === undefined ? {} : { request: task }),
|
|
66
|
+
...(output === undefined ? {} : { output }),
|
|
44
67
|
...(task === undefined
|
|
45
68
|
? {}
|
|
46
|
-
: {
|
|
47
|
-
copy: {
|
|
48
|
-
primary: task,
|
|
49
|
-
full: resultText === undefined ? task : `${task}\n---\n${resultText}`,
|
|
50
|
-
},
|
|
51
|
-
}),
|
|
52
|
-
...(rows.length === 0 ? {} : { output: rows.join("\n") }),
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
function sendPresentation(args, result) {
|
|
56
|
-
const record = isRecord(args) ? args : undefined;
|
|
57
|
-
const id = stringField(record, "id") ?? "agent";
|
|
58
|
-
const mode = stringField(record, "mode") ?? "steer";
|
|
59
|
-
const message = stringField(record, "message");
|
|
60
|
-
const confirmation = result === undefined ? undefined : agentResultText(result);
|
|
61
|
-
const copy = message === undefined
|
|
62
|
-
? undefined
|
|
63
|
-
: {
|
|
64
|
-
primary: message,
|
|
65
|
-
...(confirmation === undefined ? {} : { full: `${message}\n${confirmation}` }),
|
|
66
|
-
};
|
|
67
|
-
return {
|
|
68
|
-
summary: `${id} · ${mode}`,
|
|
69
|
-
...(message === undefined ? {} : { request: message }),
|
|
70
|
-
...(confirmation === undefined ? {} : { output: confirmation }),
|
|
71
|
-
...(copy === undefined ? {} : { copy }),
|
|
69
|
+
: { copy: { primary: task, full: output === undefined ? task : `${task}\n---\n${output}` } }),
|
|
72
70
|
};
|
|
73
71
|
}
|
|
74
|
-
const PROGRESS_TEXT_KEYS = ["agent", "path", "presentation", "freshness", "activeTool"];
|
|
75
|
-
const PROGRESS_NUMBER_KEYS = ["turns", "tokens"];
|
|
76
|
-
/** Readable selected fields only; never restates the task or a terminal answer. */
|
|
77
|
-
function detailLines(details) {
|
|
78
|
-
const lines = [];
|
|
79
|
-
const status = stringField(details, "status");
|
|
80
|
-
if (status !== undefined)
|
|
81
|
-
lines.push(`status: ${status}`);
|
|
82
|
-
const progress = isRecord(details.progress) ? details.progress : undefined;
|
|
83
|
-
if (progress !== undefined) {
|
|
84
|
-
for (const key of PROGRESS_TEXT_KEYS) {
|
|
85
|
-
const value = stringField(progress, key);
|
|
86
|
-
if (value !== undefined)
|
|
87
|
-
lines.push(`${key}: ${value}`);
|
|
88
|
-
}
|
|
89
|
-
for (const key of PROGRESS_NUMBER_KEYS) {
|
|
90
|
-
const value = numberField(progress, key);
|
|
91
|
-
if (value !== undefined)
|
|
92
|
-
lines.push(`${key}: ${value}`);
|
|
93
|
-
}
|
|
94
|
-
const summary = stringField(progress, "summary");
|
|
95
|
-
if (summary !== undefined)
|
|
96
|
-
lines.push(`summary: ${summary}`);
|
|
97
|
-
}
|
|
98
|
-
const diagnostic = stringField(details, "diagnostic");
|
|
99
|
-
if (diagnostic !== undefined)
|
|
100
|
-
lines.push(`diagnostic: ${diagnostic}`);
|
|
101
|
-
return lines;
|
|
102
|
-
}
|
|
103
|
-
function getCollapsedRow(details) {
|
|
104
|
-
const parts = [];
|
|
105
|
-
const status = stringField(details, "status");
|
|
106
|
-
if (status !== undefined)
|
|
107
|
-
parts.push(status);
|
|
108
|
-
const progress = isRecord(details.progress) ? details.progress : undefined;
|
|
109
|
-
if (progress !== undefined) {
|
|
110
|
-
const agent = stringField(progress, "agent");
|
|
111
|
-
if (agent !== undefined)
|
|
112
|
-
parts.push(agent);
|
|
113
|
-
const turns = numberField(progress, "turns");
|
|
114
|
-
if (turns !== undefined)
|
|
115
|
-
parts.push(`${turns} turn${turns === 1 ? "" : "s"}`);
|
|
116
|
-
const tokens = numberField(progress, "tokens");
|
|
117
|
-
if (tokens !== undefined) {
|
|
118
|
-
const tokStr = tokens >= 1000 ? `${(tokens / 1000).toFixed(1).replace(/\.0$/u, "")}k` : String(tokens);
|
|
119
|
-
parts.push(`${tokStr} tok`);
|
|
120
|
-
}
|
|
121
|
-
const activeTool = stringField(progress, "activeTool");
|
|
122
|
-
if (activeTool !== undefined) {
|
|
123
|
-
parts.push(activeTool);
|
|
124
|
-
}
|
|
125
|
-
else {
|
|
126
|
-
const summary = stringField(progress, "summary");
|
|
127
|
-
if (summary !== undefined)
|
|
128
|
-
parts.push(summary.replace(/\s+/gu, " ").trim());
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
const diagnostic = stringField(details, "diagnostic");
|
|
132
|
-
if (diagnostic !== undefined) {
|
|
133
|
-
parts.push(`diagnostic: ${diagnostic.replace(/\s+/gu, " ").trim()}`);
|
|
134
|
-
}
|
|
135
|
-
return parts.length > 0 ? parts.join(" · ") : undefined;
|
|
136
|
-
}
|
|
137
72
|
function getPresentation(args, result) {
|
|
138
73
|
const record = isRecord(args) ? args : undefined;
|
|
139
74
|
const id = stringField(record, "id") ?? "agent";
|
|
140
|
-
const
|
|
141
|
-
const
|
|
142
|
-
const lines =
|
|
143
|
-
const
|
|
144
|
-
const resultText = result === undefined ? undefined : agentResultText(result);
|
|
75
|
+
const revision = record?.revision;
|
|
76
|
+
const view = delegationDetails(result);
|
|
77
|
+
const lines = view === undefined ? [] : delegationLines(view);
|
|
78
|
+
const resultText = view?.resultText;
|
|
145
79
|
return {
|
|
146
|
-
summary: id,
|
|
147
|
-
request:
|
|
80
|
+
summary: typeof revision === "number" ? `${id} · rev ${revision}` : id,
|
|
81
|
+
request: record?.includeOutput === true ? "with output" : "without output",
|
|
148
82
|
...(lines.length === 0 ? {} : { output: lines.join("\n") }),
|
|
149
|
-
...(
|
|
83
|
+
...(view === undefined ? {} : { collapsedRows: [delegationRow(view)] }),
|
|
150
84
|
...(resultText === undefined ? {} : { copy: { primary: resultText } }),
|
|
151
85
|
};
|
|
152
86
|
}
|
|
153
87
|
function stopPresentation(args, result) {
|
|
154
88
|
const record = isRecord(args) ? args : undefined;
|
|
155
89
|
const id = stringField(record, "id") ?? "agent";
|
|
90
|
+
const view = delegationDetails(result);
|
|
156
91
|
const confirmation = result === undefined ? undefined : agentResultText(result);
|
|
92
|
+
const output = confirmation ?? (view === undefined ? undefined : `status: ${view.status}`);
|
|
157
93
|
return {
|
|
158
94
|
summary: id,
|
|
159
|
-
...(
|
|
160
|
-
...(
|
|
95
|
+
...(output === undefined ? {} : { output }),
|
|
96
|
+
...(output === undefined ? {} : { copy: { primary: output } }),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function dashboardPresentation(_args, result) {
|
|
100
|
+
const details = result?.details;
|
|
101
|
+
const view = isRecord(details) && Array.isArray(details.children)
|
|
102
|
+
? details
|
|
103
|
+
: undefined;
|
|
104
|
+
if (view === undefined)
|
|
105
|
+
return { summary: "agents" };
|
|
106
|
+
const rows = view.children.map((child) => {
|
|
107
|
+
const agent = child.agent === undefined ? "" : ` (${child.agent})`;
|
|
108
|
+
const summary = child.summary === undefined ? "" : ` — ${child.summary}`;
|
|
109
|
+
return `- ${child.id}${agent}: ${child.runtimeState} / ${child.status} · rev ${child.revision}${summary}`;
|
|
110
|
+
});
|
|
111
|
+
return {
|
|
112
|
+
summary: `${view.total} agents · running ${view.running} · blocked ${view.blocked} · completed ${view.completed} · failed ${view.failed}`,
|
|
113
|
+
...(rows.length === 0 ? {} : { output: rows.join("\n") }),
|
|
161
114
|
};
|
|
162
115
|
}
|
|
163
116
|
function contactPresentation(args, result) {
|
|
@@ -181,10 +134,10 @@ function contactPresentation(args, result) {
|
|
|
181
134
|
/** One renderer set per tool, named exactly after its tool so nested calls resolve too. */
|
|
182
135
|
export function parentToolRenderers(pi) {
|
|
183
136
|
return {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
137
|
+
delegate: ownedRenderers(pi, "agents_delegate", delegatePresentation),
|
|
138
|
+
get: ownedRenderers(pi, "agents_get", getPresentation),
|
|
139
|
+
stop: ownedRenderers(pi, "agents_stop", stopPresentation),
|
|
140
|
+
dashboard: ownedRenderers(pi, "agents_dashboard", dashboardPresentation),
|
|
188
141
|
};
|
|
189
142
|
}
|
|
190
143
|
/** The child's blocker report names the reason in the header and keeps the full message readable. */
|
package/dist/tools.d.ts
CHANGED
|
@@ -3,8 +3,6 @@ import type { DiscoveredAgent } from "./agent-resolver.js";
|
|
|
3
3
|
import { type ChildIdentity } from "./domain.js";
|
|
4
4
|
import type { SubagentManager } from "./manager.js";
|
|
5
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
6
|
export declare function registerParentTools(pi: ExtensionAPI, manager: SubagentManager, agents: readonly DiscoveredAgent[]): void;
|
|
9
7
|
/** Sends a child-initiated request over the bridge and resolves with the parent's answer. */
|
|
10
8
|
export type ChildReport = (operation: string, payload: unknown, signal?: AbortSignal) => Promise<unknown>;
|
|
@@ -14,4 +12,3 @@ export interface RegisterChildToolsOptions {
|
|
|
14
12
|
readonly isBound?: (sessionId: string) => boolean;
|
|
15
13
|
}
|
|
16
14
|
export declare function registerChildTools(pi: ExtensionAPI, identity: ChildIdentity, options: RegisterChildToolsOptions): void;
|
|
17
|
-
export {};
|