@gajae-code/coding-agent 0.3.0 → 0.3.1
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/CHANGELOG.md +18 -0
- package/dist/types/async/job-manager.d.ts +7 -0
- package/dist/types/cli/args.d.ts +1 -1
- package/dist/types/commands/deep-interview.d.ts +3 -0
- package/dist/types/config/keybindings.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +4 -4
- package/dist/types/debug/crash-diagnostics.d.ts +45 -0
- package/dist/types/debug/runtime-gauges.d.ts +6 -0
- package/dist/types/deep-interview/render-middleware.d.ts +1 -0
- package/dist/types/eval/py/executor.d.ts +2 -0
- package/dist/types/eval/py/kernel.d.ts +2 -0
- package/dist/types/exec/bash-executor.d.ts +10 -0
- package/dist/types/gjc-runtime/cli-write-receipt.d.ts +24 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +1 -0
- package/dist/types/gjc-runtime/state-migrations.d.ts +9 -0
- package/dist/types/gjc-runtime/state-schema.d.ts +317 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +10 -0
- package/dist/types/gjc-runtime/workflow-command-ref.d.ts +43 -0
- package/dist/types/harness-control-plane/control-endpoint.d.ts +3 -2
- package/dist/types/hooks/skill-state.d.ts +21 -0
- package/dist/types/internal-urls/agent-protocol.d.ts +2 -2
- package/dist/types/internal-urls/artifact-protocol.d.ts +2 -2
- package/dist/types/internal-urls/registry-helpers.d.ts +8 -7
- package/dist/types/internal-urls/types.d.ts +4 -0
- package/dist/types/lsp/index.d.ts +10 -10
- package/dist/types/modes/bridge/auth.d.ts +12 -0
- package/dist/types/modes/bridge/bridge-client-bridge.d.ts +9 -0
- package/dist/types/modes/bridge/bridge-mode.d.ts +44 -0
- package/dist/types/modes/bridge/bridge-ui-context.d.ts +88 -0
- package/dist/types/modes/bridge/event-stream.d.ts +8 -0
- package/dist/types/modes/components/custom-editor.d.ts +6 -0
- package/dist/types/modes/components/jobs-overlay-model.d.ts +31 -0
- package/dist/types/modes/components/jobs-overlay.d.ts +30 -0
- package/dist/types/modes/components/status-line/types.d.ts +2 -0
- package/dist/types/modes/components/status-line.d.ts +2 -0
- package/dist/types/modes/controllers/input-controller.d.ts +1 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +8 -0
- package/dist/types/modes/index.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/jobs-observer.d.ts +57 -0
- package/dist/types/modes/rpc/host-tools.d.ts +1 -16
- package/dist/types/modes/rpc/host-uris.d.ts +1 -38
- package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +20 -0
- package/dist/types/modes/shared/agent-wire/command-validation.d.ts +2 -0
- package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +24 -0
- package/dist/types/modes/shared/agent-wire/handshake.d.ts +46 -0
- package/dist/types/modes/shared/agent-wire/host-tool-bridge.d.ts +16 -0
- package/dist/types/modes/shared/agent-wire/host-uri-bridge.d.ts +17 -0
- package/dist/types/modes/shared/agent-wire/protocol.d.ts +44 -0
- package/dist/types/modes/shared/agent-wire/responses.d.ts +4 -0
- package/dist/types/modes/shared/agent-wire/scopes.d.ts +18 -0
- package/dist/types/modes/shared/agent-wire/ui-request-broker.d.ts +42 -0
- package/dist/types/modes/shared/agent-wire/ui-result.d.ts +27 -0
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +11 -1
- package/dist/types/skill-state/workflow-state-contract.d.ts +1 -2
- package/dist/types/skill-state/workflow-state-version.d.ts +3 -0
- package/dist/types/task/id.d.ts +7 -0
- package/dist/types/task/index.d.ts +5 -0
- package/dist/types/task/receipt.d.ts +85 -0
- package/dist/types/task/spawn-gate.d.ts +38 -0
- package/dist/types/task/types.d.ts +143 -11
- package/dist/types/tools/cron.d.ts +6 -0
- package/dist/types/tools/index.d.ts +2 -0
- package/dist/types/tools/path-utils.d.ts +1 -0
- package/dist/types/tools/subagent.d.ts +15 -0
- package/package.json +7 -7
- package/scripts/build-binary.ts +7 -0
- package/src/async/job-manager.ts +36 -0
- package/src/cli/args.ts +9 -2
- package/src/commands/deep-interview.ts +1 -0
- package/src/commands/harness.ts +289 -19
- package/src/commands/launch.ts +2 -2
- package/src/commands/state.ts +2 -1
- package/src/commands/team.ts +22 -4
- package/src/config/keybindings.ts +6 -0
- package/src/config/settings-schema.ts +6 -3
- package/src/dap/client.ts +17 -3
- package/src/debug/crash-diagnostics.ts +223 -0
- package/src/debug/runtime-gauges.ts +20 -0
- package/src/deep-interview/render-middleware.ts +6 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
- package/src/defaults/gjc/skills/ralplan/SKILL.md +31 -2
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +28 -2
- package/src/eval/py/executor.ts +21 -1
- package/src/eval/py/kernel.ts +15 -0
- package/src/exec/bash-executor.ts +41 -0
- package/src/gjc-runtime/cli-write-receipt.ts +31 -0
- package/src/gjc-runtime/deep-interview-runtime.ts +69 -32
- package/src/gjc-runtime/ralplan-runtime.ts +213 -36
- package/src/gjc-runtime/state-migrations.ts +54 -7
- package/src/gjc-runtime/state-runtime.ts +461 -64
- package/src/gjc-runtime/state-schema.ts +192 -0
- package/src/gjc-runtime/state-writer.ts +32 -1
- package/src/gjc-runtime/team-runtime.ts +177 -105
- package/src/gjc-runtime/ultragoal-runtime.ts +114 -26
- package/src/gjc-runtime/workflow-command-ref.ts +239 -0
- package/src/gjc-runtime/workflow-manifest.generated.json +108 -4
- package/src/gjc-runtime/workflow-manifest.ts +3 -1
- package/src/harness-control-plane/control-endpoint.ts +19 -8
- package/src/harness-control-plane/owner.ts +57 -10
- package/src/harness-control-plane/state-machine.ts +2 -1
- package/src/hooks/skill-state.ts +176 -26
- package/src/internal-urls/agent-protocol.ts +68 -21
- package/src/internal-urls/artifact-protocol.ts +12 -17
- package/src/internal-urls/docs-index.generated.ts +3 -2
- package/src/internal-urls/registry-helpers.ts +19 -16
- package/src/internal-urls/types.ts +4 -0
- package/src/lsp/client.ts +18 -2
- package/src/main.ts +21 -5
- package/src/modes/bridge/auth.ts +41 -0
- package/src/modes/bridge/bridge-client-bridge.ts +47 -0
- package/src/modes/bridge/bridge-mode.ts +520 -0
- package/src/modes/bridge/bridge-ui-context.ts +200 -0
- package/src/modes/bridge/event-stream.ts +70 -0
- package/src/modes/components/custom-editor.ts +101 -0
- package/src/modes/components/hook-selector.ts +61 -18
- package/src/modes/components/jobs-overlay-model.ts +109 -0
- package/src/modes/components/jobs-overlay.ts +172 -0
- package/src/modes/components/status-line/presets.ts +7 -5
- package/src/modes/components/status-line/segments.ts +25 -0
- package/src/modes/components/status-line/types.ts +2 -0
- package/src/modes/components/status-line.ts +9 -1
- package/src/modes/controllers/extension-ui-controller.ts +39 -3
- package/src/modes/controllers/input-controller.ts +97 -9
- package/src/modes/controllers/selector-controller.ts +29 -0
- package/src/modes/index.ts +1 -0
- package/src/modes/interactive-mode.ts +27 -0
- package/src/modes/jobs-observer.ts +204 -0
- package/src/modes/rpc/host-tools.ts +1 -186
- package/src/modes/rpc/host-uris.ts +1 -235
- package/src/modes/rpc/rpc-client.ts +25 -10
- package/src/modes/rpc/rpc-mode.ts +12 -381
- package/src/modes/shared/agent-wire/command-dispatch.ts +341 -0
- package/src/modes/shared/agent-wire/command-validation.ts +131 -0
- package/src/modes/shared/agent-wire/event-envelope.ts +108 -0
- package/src/modes/shared/agent-wire/handshake.ts +117 -0
- package/src/modes/shared/agent-wire/host-tool-bridge.ts +194 -0
- package/src/modes/shared/agent-wire/host-uri-bridge.ts +236 -0
- package/src/modes/shared/agent-wire/protocol.ts +96 -0
- package/src/modes/shared/agent-wire/responses.ts +17 -0
- package/src/modes/shared/agent-wire/scopes.ts +89 -0
- package/src/modes/shared/agent-wire/ui-request-broker.ts +150 -0
- package/src/modes/shared/agent-wire/ui-result.ts +48 -0
- package/src/modes/types.ts +1 -0
- package/src/prompts/tools/subagent.md +12 -7
- package/src/prompts/tools/task-summary.md +3 -9
- package/src/prompts/tools/task.md +5 -1
- package/src/sdk.ts +4 -0
- package/src/session/agent-session.ts +214 -38
- package/src/skill-state/deep-interview-mutation-guard.ts +23 -4
- package/src/skill-state/workflow-state-contract.ts +7 -4
- package/src/skill-state/workflow-state-version.ts +3 -0
- package/src/slash-commands/builtin-registry.ts +8 -0
- package/src/task/executor.ts +29 -5
- package/src/task/id.ts +33 -0
- package/src/task/index.ts +257 -67
- package/src/task/output-manager.ts +5 -4
- package/src/task/receipt.ts +297 -0
- package/src/task/render.ts +48 -131
- package/src/task/spawn-gate.ts +132 -0
- package/src/task/types.ts +48 -7
- package/src/tools/ask.ts +73 -33
- package/src/tools/ast-edit.ts +1 -0
- package/src/tools/ast-grep.ts +1 -0
- package/src/tools/bash.ts +1 -1
- package/src/tools/cron.ts +48 -0
- package/src/tools/find.ts +4 -1
- package/src/tools/index.ts +2 -0
- package/src/tools/path-utils.ts +3 -2
- package/src/tools/read.ts +1 -0
- package/src/tools/search.ts +1 -0
- package/src/tools/skill.ts +6 -1
- package/src/tools/subagent.ts +237 -84
package/src/tools/subagent.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
1
2
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@gajae-code/agent-core";
|
|
2
3
|
import { prompt } from "@gajae-code/utils";
|
|
3
4
|
import * as z from "zod/v4";
|
|
@@ -13,17 +14,26 @@ const DEFAULT_AWAIT_TIMEOUT_MS = 30_000;
|
|
|
13
14
|
const MAX_AWAIT_TIMEOUT_MS = 60 * 60 * 1000;
|
|
14
15
|
const DEFAULT_LIST_LIMIT = 10;
|
|
15
16
|
const MAX_LIST_LIMIT = 50;
|
|
16
|
-
const
|
|
17
|
+
const RECEIPT_PREVIEW_WIDTH = 280;
|
|
18
|
+
const PREVIEW_WIDTH = 2_000;
|
|
19
|
+
const FULL_PREVIEW_WIDTH = 12_000;
|
|
17
20
|
|
|
18
21
|
const subagentSchema = z.object({
|
|
19
22
|
action: z
|
|
20
23
|
.enum(["list", "inspect", "await", "cancel", "pause", "resume", "steer"])
|
|
21
24
|
.describe("subagent control action"),
|
|
22
25
|
ids: z.array(z.string()).optional().describe("subagent ids or backing job ids"),
|
|
26
|
+
id: z.string().optional().describe("single subagent id or backing job id for resume/steer"),
|
|
23
27
|
message: z.string().optional().describe("message to deliver when resuming or steering a subagent"),
|
|
24
28
|
pause: z.boolean().optional().describe("pause after steering a currently running subagent"),
|
|
25
29
|
timeout_ms: z.number().min(0).max(MAX_AWAIT_TIMEOUT_MS).optional().describe("await timeout in milliseconds"),
|
|
26
30
|
limit: z.number().min(1).max(MAX_LIST_LIMIT).optional().describe("maximum subagents to return"),
|
|
31
|
+
verbosity: z
|
|
32
|
+
.enum(["receipt", "preview", "full"])
|
|
33
|
+
.optional()
|
|
34
|
+
.describe(
|
|
35
|
+
"output verbosity: receipt (default, <=280-char receipt preview), preview (<=2000 chars), or full (<=12000 chars; requires explicit ids)",
|
|
36
|
+
),
|
|
27
37
|
});
|
|
28
38
|
|
|
29
39
|
type SubagentParams = z.infer<typeof subagentSchema>;
|
|
@@ -49,6 +59,9 @@ export interface SubagentSnapshot {
|
|
|
49
59
|
durationMs: number;
|
|
50
60
|
resultText?: string;
|
|
51
61
|
errorText?: string;
|
|
62
|
+
resultPreview?: string;
|
|
63
|
+
outputRef?: string;
|
|
64
|
+
truncated?: boolean;
|
|
52
65
|
guidance?: string;
|
|
53
66
|
}
|
|
54
67
|
|
|
@@ -87,19 +100,26 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
87
100
|
const ownerId = this.session.getAgentId?.() ?? undefined;
|
|
88
101
|
const ownerFilter = ownerId ? { ownerId } : undefined;
|
|
89
102
|
const limit = Math.min(MAX_LIST_LIMIT, Math.max(1, Math.floor(params.limit ?? DEFAULT_LIST_LIMIT)));
|
|
103
|
+
const verbosity = params.verbosity ?? "receipt";
|
|
104
|
+
if (verbosity === "full" && (params.action === "list" || !params.ids?.length)) {
|
|
105
|
+
throw new ToolError(
|
|
106
|
+
"`verbosity=full` cannot be used with `list` and requires explicit `ids` so broad inspection cannot inline retained subagent output.",
|
|
107
|
+
);
|
|
108
|
+
}
|
|
90
109
|
|
|
91
110
|
if (params.action === "list") {
|
|
92
111
|
const records = this.#listSubagentRecords(manager, ownerFilter, limit);
|
|
93
|
-
return this.#buildRecordResult(manager, records, { title: "Subagents" });
|
|
112
|
+
return await this.#buildRecordResult(manager, records, { title: "Subagents", verbosity });
|
|
94
113
|
}
|
|
95
114
|
|
|
96
115
|
if (params.action === "inspect") {
|
|
97
116
|
const records = params.ids?.length
|
|
98
117
|
? this.#visibleRecordsByIds(manager, params.ids, ownerFilter)
|
|
99
118
|
: this.#runningRecords(manager, ownerFilter);
|
|
100
|
-
return this.#buildRecordResult(manager, records, {
|
|
119
|
+
return await this.#buildRecordResult(manager, records, {
|
|
101
120
|
title: "Subagent inspection",
|
|
102
121
|
notFoundIds: this.#notFoundRecordIds(manager, params.ids ?? [], ownerFilter),
|
|
122
|
+
verbosity,
|
|
103
123
|
});
|
|
104
124
|
}
|
|
105
125
|
|
|
@@ -108,19 +128,26 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
108
128
|
if (ids.length === 0) {
|
|
109
129
|
throw new ToolError("`cancel` requires at least one subagent id.");
|
|
110
130
|
}
|
|
111
|
-
const
|
|
131
|
+
const records: SubagentRecord[] = [];
|
|
132
|
+
const missing: SubagentSnapshot[] = [];
|
|
112
133
|
for (const id of ids) {
|
|
113
134
|
const record = this.#findVisibleRecord(manager, id, ownerFilter);
|
|
114
135
|
if (!record) {
|
|
115
|
-
|
|
136
|
+
missing.push(this.#missingSnapshot(id, "not_found", "No visible detached subagent matches this id."));
|
|
116
137
|
continue;
|
|
117
138
|
}
|
|
118
139
|
const cancelled = manager.cancelSubagent(record.subagentId, ownerFilter);
|
|
119
140
|
if (!cancelled && record.currentJobId) manager.cancel(record.currentJobId, ownerFilter);
|
|
120
|
-
|
|
121
|
-
snapshots.push(this.#recordSnapshot(manager, updated));
|
|
141
|
+
records.push(this.#findVisibleRecord(manager, id, ownerFilter) ?? record);
|
|
122
142
|
}
|
|
123
|
-
|
|
143
|
+
const verifiedOutputIds = await this.#verifiedOutputIds(records);
|
|
144
|
+
return this.#buildSnapshotResult(
|
|
145
|
+
[
|
|
146
|
+
...records.map(record => this.#recordSnapshot(manager, record, false, verbosity, verifiedOutputIds)),
|
|
147
|
+
...missing,
|
|
148
|
+
],
|
|
149
|
+
"Subagent cancellation",
|
|
150
|
+
);
|
|
124
151
|
}
|
|
125
152
|
|
|
126
153
|
if (params.action === "pause") {
|
|
@@ -128,78 +155,86 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
128
155
|
if (ids.length === 0) {
|
|
129
156
|
throw new ToolError("`pause` requires at least one subagent id.");
|
|
130
157
|
}
|
|
131
|
-
const
|
|
158
|
+
const records: SubagentRecord[] = [];
|
|
159
|
+
const missing: SubagentSnapshot[] = [];
|
|
132
160
|
for (const id of ids) {
|
|
133
161
|
const record = this.#findVisibleRecord(manager, id, ownerFilter);
|
|
134
162
|
if (!record) {
|
|
135
|
-
|
|
163
|
+
missing.push(this.#missingSnapshot(id, "not_found", "No visible detached subagent matches this id."));
|
|
136
164
|
continue;
|
|
137
165
|
}
|
|
138
166
|
const result = manager.pauseSubagent(record.subagentId, ownerFilter);
|
|
139
167
|
if (!result.ok && result.reason === "not_found") {
|
|
140
|
-
|
|
168
|
+
missing.push(this.#missingSnapshot(id, "not_found", "No visible detached subagent matches this id."));
|
|
141
169
|
continue;
|
|
142
170
|
}
|
|
143
|
-
|
|
144
|
-
this.#recordSnapshot(manager, manager.getSubagentRecord(record.subagentId, ownerFilter) ?? record),
|
|
145
|
-
);
|
|
171
|
+
records.push(manager.getSubagentRecord(record.subagentId, ownerFilter) ?? record);
|
|
146
172
|
}
|
|
147
|
-
|
|
173
|
+
const verifiedOutputIds = await this.#verifiedOutputIds(records);
|
|
174
|
+
return this.#buildSnapshotResult(
|
|
175
|
+
[
|
|
176
|
+
...records.map(record => this.#recordSnapshot(manager, record, false, verbosity, verifiedOutputIds)),
|
|
177
|
+
...missing,
|
|
178
|
+
],
|
|
179
|
+
"Subagent pause",
|
|
180
|
+
);
|
|
148
181
|
}
|
|
149
182
|
|
|
150
183
|
if (params.action === "resume") {
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
-
if (params.message === undefined && isTerminalStatus(record.status)) {
|
|
167
|
-
snapshots.push({
|
|
168
|
-
...this.#recordSnapshot(manager, record),
|
|
169
|
-
guidance:
|
|
170
|
-
"This subagent is terminal. Provide `message` to start a follow-up resume run from its saved context.",
|
|
171
|
-
});
|
|
172
|
-
continue;
|
|
173
|
-
}
|
|
184
|
+
const id = this.#singleTargetId(params, "resume");
|
|
185
|
+
const records: SubagentRecord[] = [];
|
|
186
|
+
const missing: SubagentSnapshot[] = [];
|
|
187
|
+
const terminalGuidanceIds = new Set<string>();
|
|
188
|
+
const record = this.#findVisibleRecord(manager, id, ownerFilter);
|
|
189
|
+
const verifiedOutputIds = await this.#verifiedOutputIds(record ? [record] : []);
|
|
190
|
+
if (!record) {
|
|
191
|
+
missing.push(this.#missingSnapshot(id, "not_found", "No visible detached subagent matches this id."));
|
|
192
|
+
} else if (record.status === "running") {
|
|
193
|
+
records.push(record);
|
|
194
|
+
} else if (params.message === undefined && isTerminalStatus(record.status)) {
|
|
195
|
+
records.push(record);
|
|
196
|
+
terminalGuidanceIds.add(record.subagentId);
|
|
197
|
+
} else {
|
|
174
198
|
const result = manager.resumeSubagent(record.subagentId, ownerFilter, params.message);
|
|
175
199
|
if (!result.ok && result.reason === "context_unavailable") throw new ToolError("context unavailable");
|
|
176
200
|
if (!result.ok && result.reason === "not_found") {
|
|
177
|
-
|
|
178
|
-
|
|
201
|
+
missing.push(this.#missingSnapshot(id, "not_found", "No visible detached subagent matches this id."));
|
|
202
|
+
} else {
|
|
203
|
+
records.push(manager.getSubagentRecord(record.subagentId, ownerFilter) ?? record);
|
|
179
204
|
}
|
|
180
|
-
snapshots.push(
|
|
181
|
-
this.#recordSnapshot(manager, manager.getSubagentRecord(record.subagentId, ownerFilter) ?? record),
|
|
182
|
-
);
|
|
183
205
|
}
|
|
184
|
-
|
|
206
|
+
|
|
207
|
+
return this.#buildSnapshotResult(
|
|
208
|
+
[
|
|
209
|
+
...records.map(record => {
|
|
210
|
+
const snapshot = this.#recordSnapshot(manager, record, false, verbosity, verifiedOutputIds);
|
|
211
|
+
return terminalGuidanceIds.has(record.subagentId)
|
|
212
|
+
? {
|
|
213
|
+
...snapshot,
|
|
214
|
+
guidance:
|
|
215
|
+
"This subagent is terminal. Provide `message` to start a follow-up resume run from its saved context.",
|
|
216
|
+
}
|
|
217
|
+
: snapshot;
|
|
218
|
+
}),
|
|
219
|
+
...missing,
|
|
220
|
+
],
|
|
221
|
+
"Subagent resume",
|
|
222
|
+
);
|
|
185
223
|
}
|
|
186
224
|
|
|
187
225
|
if (params.action === "steer") {
|
|
188
|
-
const
|
|
226
|
+
const id = this.#singleTargetId(params, "steer");
|
|
189
227
|
const message = params.message;
|
|
190
|
-
if (ids.length === 0) {
|
|
191
|
-
throw new ToolError("`steer` requires at least one subagent id.");
|
|
192
|
-
}
|
|
193
228
|
if (message === undefined || message.trim() === "") {
|
|
194
229
|
throw new ToolError("`steer` requires a non-empty message.");
|
|
195
230
|
}
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
231
|
+
const records: SubagentRecord[] = [];
|
|
232
|
+
const missing: SubagentSnapshot[] = [];
|
|
233
|
+
const record = this.#findVisibleRecord(manager, id, ownerFilter);
|
|
234
|
+
const verifiedOutputIds = await this.#verifiedOutputIds(record ? [record] : []);
|
|
235
|
+
if (!record) {
|
|
236
|
+
missing.push(this.#missingSnapshot(id, "not_found", "No visible detached subagent matches this id."));
|
|
237
|
+
} else {
|
|
203
238
|
if (!record.sessionFile) throw new ToolError(`Subagent ${record.subagentId} has no session file.`);
|
|
204
239
|
if (record.status === "running") {
|
|
205
240
|
const handle = manager.getLiveHandle(record.subagentId);
|
|
@@ -210,22 +245,45 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
210
245
|
const result = manager.resumeSubagent(record.subagentId, ownerFilter, message);
|
|
211
246
|
if (!result.ok && result.reason === "context_unavailable") throw new ToolError("context unavailable");
|
|
212
247
|
if (!result.ok && result.reason === "not_found") {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
);
|
|
216
|
-
continue;
|
|
248
|
+
missing.push(this.#missingSnapshot(id, "not_found", "No visible detached subagent matches this id."));
|
|
249
|
+
} else {
|
|
250
|
+
records.push(manager.getSubagentRecord(record.subagentId, ownerFilter) ?? record);
|
|
217
251
|
}
|
|
218
252
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
);
|
|
253
|
+
if (record.status === "running")
|
|
254
|
+
records.push(manager.getSubagentRecord(record.subagentId, ownerFilter) ?? record);
|
|
222
255
|
}
|
|
223
|
-
return this.#buildSnapshotResult(
|
|
256
|
+
return this.#buildSnapshotResult(
|
|
257
|
+
[
|
|
258
|
+
...records.map(record => this.#recordSnapshot(manager, record, false, verbosity, verifiedOutputIds)),
|
|
259
|
+
...missing,
|
|
260
|
+
],
|
|
261
|
+
"Subagent steer",
|
|
262
|
+
);
|
|
224
263
|
}
|
|
225
264
|
|
|
226
265
|
return this.#awaitSubagents(manager, params, ownerFilter, signal, onUpdate);
|
|
227
266
|
}
|
|
228
267
|
|
|
268
|
+
#singleTargetId(params: SubagentParams, action: "resume" | "steer"): string {
|
|
269
|
+
const id = params.id?.trim();
|
|
270
|
+
const ids = (params.ids ?? []).map(value => value.trim()).filter(value => value.length > 0);
|
|
271
|
+
if (id && ids.length > 0) {
|
|
272
|
+
if (ids.length === 1 && ids[0] === id) return id;
|
|
273
|
+
throw new ToolError(
|
|
274
|
+
`\`${action}\` accepts exactly one target; provide \`id\` or a single-item \`ids\`, not both.`,
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
if (id) return id;
|
|
278
|
+
if (ids.length === 1) return ids[0]!;
|
|
279
|
+
if (ids.length > 1) {
|
|
280
|
+
throw new ToolError(
|
|
281
|
+
`\`${action}\` accepts exactly one target because \`message\` is delivered to one subagent.`,
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
throw new ToolError(`\`${action}\` requires a single subagent id via \`id\`.`);
|
|
285
|
+
}
|
|
286
|
+
|
|
229
287
|
async #awaitSubagents(
|
|
230
288
|
manager: AsyncJobManager,
|
|
231
289
|
params: SubagentParams,
|
|
@@ -249,7 +307,11 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
249
307
|
.map(record => manager.getJob(record.currentJobId!))
|
|
250
308
|
.filter((job): job is AsyncJob => job !== undefined);
|
|
251
309
|
if (runningJobs.length === 0) {
|
|
252
|
-
return this.#buildRecordResult(manager, records, {
|
|
310
|
+
return await this.#buildRecordResult(manager, records, {
|
|
311
|
+
title: "Subagent await",
|
|
312
|
+
notFoundIds,
|
|
313
|
+
verbosity: params.verbosity ?? "receipt",
|
|
314
|
+
});
|
|
253
315
|
}
|
|
254
316
|
|
|
255
317
|
const timeoutMs = Math.min(
|
|
@@ -288,7 +350,12 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
288
350
|
if (progressTimer) clearInterval(progressTimer);
|
|
289
351
|
}
|
|
290
352
|
|
|
291
|
-
return this.#buildRecordResult(manager, records, {
|
|
353
|
+
return await this.#buildRecordResult(manager, records, {
|
|
354
|
+
title: "Subagent await",
|
|
355
|
+
notFoundIds,
|
|
356
|
+
timedOut,
|
|
357
|
+
verbosity: params.verbosity ?? "receipt",
|
|
358
|
+
});
|
|
292
359
|
}
|
|
293
360
|
|
|
294
361
|
#mergedRecords(
|
|
@@ -386,16 +453,23 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
386
453
|
#progressResult(manager: AsyncJobManager, records: SubagentRecord[]): AgentToolResult<SubagentToolDetails> {
|
|
387
454
|
return {
|
|
388
455
|
content: [{ type: "text", text: "" }],
|
|
389
|
-
details: { subagents: this.#recordSnapshots(manager, records) },
|
|
456
|
+
details: { subagents: this.#recordSnapshots(manager, records, false, "receipt", new Set()) },
|
|
390
457
|
};
|
|
391
458
|
}
|
|
392
459
|
|
|
393
|
-
#buildRecordResult(
|
|
460
|
+
async #buildRecordResult(
|
|
394
461
|
manager: AsyncJobManager,
|
|
395
462
|
records: SubagentRecord[],
|
|
396
|
-
options: { title: string; notFoundIds?: string[]; timedOut?: boolean },
|
|
397
|
-
): AgentToolResult<SubagentToolDetails
|
|
398
|
-
const
|
|
463
|
+
options: { title: string; notFoundIds?: string[]; timedOut?: boolean; verbosity?: SubagentParams["verbosity"] },
|
|
464
|
+
): Promise<AgentToolResult<SubagentToolDetails>> {
|
|
465
|
+
const verifiedOutputIds = await this.#verifiedOutputIds(records);
|
|
466
|
+
const snapshots = this.#recordSnapshots(
|
|
467
|
+
manager,
|
|
468
|
+
records,
|
|
469
|
+
options.timedOut,
|
|
470
|
+
options.verbosity ?? "receipt",
|
|
471
|
+
verifiedOutputIds,
|
|
472
|
+
);
|
|
399
473
|
for (const id of options.notFoundIds ?? []) {
|
|
400
474
|
snapshots.push(this.#missingSnapshot(id, "not_found", "No visible detached subagent matches this id."));
|
|
401
475
|
}
|
|
@@ -417,9 +491,13 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
417
491
|
if (snapshot.jobId !== snapshot.id) lines.push(`Job: ${snapshot.jobId}`);
|
|
418
492
|
if (snapshot.agent) lines.push(`Agent: ${snapshot.agent} (${snapshot.agentSource})`);
|
|
419
493
|
if (snapshot.description) lines.push(`Description: ${snapshot.description}`);
|
|
494
|
+
if (snapshot.outputRef) lines.push(`Output: ${snapshot.outputRef}`);
|
|
420
495
|
if (snapshot.assignment) lines.push("Assignment:", "```", snapshot.assignment, "```");
|
|
421
|
-
if (snapshot.
|
|
422
|
-
|
|
496
|
+
if (snapshot.resultPreview) {
|
|
497
|
+
lines.push(snapshot.errorText ? "Error preview:" : "Result preview:", "```", snapshot.resultPreview, "```");
|
|
498
|
+
if (snapshot.truncated)
|
|
499
|
+
lines.push("Preview truncated; use the output ref or explicit ids with `verbosity=full` for more.");
|
|
500
|
+
}
|
|
423
501
|
if (snapshot.guidance) lines.push(`Guidance: ${snapshot.guidance}`);
|
|
424
502
|
lines.push("");
|
|
425
503
|
}
|
|
@@ -429,15 +507,27 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
429
507
|
};
|
|
430
508
|
}
|
|
431
509
|
|
|
432
|
-
#recordSnapshots(
|
|
433
|
-
|
|
510
|
+
#recordSnapshots(
|
|
511
|
+
manager: AsyncJobManager,
|
|
512
|
+
records: SubagentRecord[],
|
|
513
|
+
timedOut = false,
|
|
514
|
+
verbosity: SubagentParams["verbosity"] = "receipt",
|
|
515
|
+
verifiedOutputIds: ReadonlySet<string>,
|
|
516
|
+
): SubagentSnapshot[] {
|
|
517
|
+
return records.map(record => this.#recordSnapshot(manager, record, timedOut, verbosity, verifiedOutputIds));
|
|
434
518
|
}
|
|
435
519
|
|
|
436
|
-
#recordSnapshot(
|
|
520
|
+
#recordSnapshot(
|
|
521
|
+
manager: AsyncJobManager,
|
|
522
|
+
record: SubagentRecord,
|
|
523
|
+
timedOut = false,
|
|
524
|
+
verbosity: SubagentParams["verbosity"] = "receipt",
|
|
525
|
+
verifiedOutputIds: ReadonlySet<string>,
|
|
526
|
+
): SubagentSnapshot {
|
|
437
527
|
const job = record.currentJobId ? manager.getJob(record.currentJobId) : undefined;
|
|
438
528
|
if (job) {
|
|
439
529
|
return {
|
|
440
|
-
...this.#snapshot(job, timedOut),
|
|
530
|
+
...this.#snapshot(job, timedOut, verbosity, verifiedOutputIds, record),
|
|
441
531
|
id: record.subagentId,
|
|
442
532
|
jobId: record.currentJobId ?? job.id,
|
|
443
533
|
status: record.status,
|
|
@@ -451,31 +541,77 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
451
541
|
agent: "unknown",
|
|
452
542
|
agentSource: "bundled",
|
|
453
543
|
durationMs: 0,
|
|
544
|
+
...(verifiedOutputIds.has(record.subagentId) ? { outputRef: `agent://${record.subagentId}` } : {}),
|
|
454
545
|
};
|
|
455
546
|
}
|
|
456
547
|
|
|
457
|
-
#snapshot(
|
|
548
|
+
#snapshot(
|
|
549
|
+
job: AsyncJob,
|
|
550
|
+
timedOut = false,
|
|
551
|
+
verbosity: SubagentParams["verbosity"] = "receipt",
|
|
552
|
+
verifiedOutputIds: ReadonlySet<string>,
|
|
553
|
+
record?: SubagentRecord,
|
|
554
|
+
): SubagentSnapshot {
|
|
458
555
|
const subagent = job.metadata?.subagent;
|
|
459
556
|
const runningTimeoutGuidance =
|
|
460
557
|
timedOut && job.status === "running"
|
|
461
558
|
? "Still running after the await timeout; timeout only bounded this wait and is not a failure. Inspect progress, continue independent work, and never cancel just because an await timed out; cancel only if the subagent has actually failed, gone off-track, or become unrecoverably wrong."
|
|
462
559
|
: undefined;
|
|
560
|
+
const output = previewJobOutput(job, verbosity);
|
|
561
|
+
const outputRef = record && verifiedOutputIds.has(record.subagentId) ? `agent://${record.subagentId}` : undefined;
|
|
463
562
|
return {
|
|
464
563
|
id: subagent?.id ?? job.id,
|
|
465
564
|
jobId: job.id,
|
|
466
565
|
status: job.status,
|
|
467
|
-
label: sanitizeText(job.label),
|
|
566
|
+
label: sanitizeText(job.label, RECEIPT_PREVIEW_WIDTH),
|
|
468
567
|
agent: subagent?.agent ?? "unknown",
|
|
469
568
|
agentSource: subagent?.agentSource ?? "bundled",
|
|
470
569
|
durationMs: Math.max(0, Date.now() - job.startTime),
|
|
471
|
-
...(subagent?.description ? { description: sanitizeText(subagent.description) } : {}),
|
|
472
|
-
...(
|
|
473
|
-
|
|
474
|
-
|
|
570
|
+
...(subagent?.description ? { description: sanitizeText(subagent.description, RECEIPT_PREVIEW_WIDTH) } : {}),
|
|
571
|
+
...(verbosity === "full" && subagent?.assignment
|
|
572
|
+
? { assignment: sanitizeText(subagent.assignment, FULL_PREVIEW_WIDTH) }
|
|
573
|
+
: {}),
|
|
574
|
+
...(output
|
|
575
|
+
? {
|
|
576
|
+
...(output.type === "error" ? { errorText: output.preview } : { resultText: output.preview }),
|
|
577
|
+
resultPreview: output.preview,
|
|
578
|
+
truncated: output.truncated,
|
|
579
|
+
}
|
|
580
|
+
: {}),
|
|
581
|
+
...(outputRef ? { outputRef } : {}),
|
|
475
582
|
...(runningTimeoutGuidance ? { guidance: runningTimeoutGuidance } : {}),
|
|
476
583
|
};
|
|
477
584
|
}
|
|
478
585
|
|
|
586
|
+
async #verifiedOutputIds(records: SubagentRecord[]): Promise<Set<string>> {
|
|
587
|
+
const ids = new Set(records.map(record => record.subagentId));
|
|
588
|
+
const dirs = this.#artifactDirsForRecords(records);
|
|
589
|
+
const verified = new Set<string>();
|
|
590
|
+
await Promise.all(
|
|
591
|
+
[...ids].map(async id => {
|
|
592
|
+
for (const dir of dirs) {
|
|
593
|
+
if (await Bun.file(path.join(dir, `${id}.md.meta.json`)).exists()) {
|
|
594
|
+
verified.add(id);
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}),
|
|
599
|
+
);
|
|
600
|
+
return verified;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
#artifactDirsForRecords(records: SubagentRecord[]): string[] {
|
|
604
|
+
const dirs: string[] = [];
|
|
605
|
+
for (const record of records) {
|
|
606
|
+
if (!record.sessionFile) continue;
|
|
607
|
+
const dir = path.dirname(record.sessionFile);
|
|
608
|
+
if (!dirs.includes(dir)) dirs.push(dir);
|
|
609
|
+
}
|
|
610
|
+
const sessionDir = this.session.getArtifactsDir?.();
|
|
611
|
+
if (sessionDir && !dirs.includes(sessionDir)) dirs.push(sessionDir);
|
|
612
|
+
return dirs;
|
|
613
|
+
}
|
|
614
|
+
|
|
479
615
|
#missingSnapshot(id: string, status: "not_found", guidance: string): SubagentSnapshot {
|
|
480
616
|
return {
|
|
481
617
|
id,
|
|
@@ -498,6 +634,23 @@ function isSubagentJob(job: AsyncJob): boolean {
|
|
|
498
634
|
return job.type === "task" && job.metadata?.subagent !== undefined;
|
|
499
635
|
}
|
|
500
636
|
|
|
501
|
-
function sanitizeText(text: string): string {
|
|
502
|
-
return truncateToWidth(replaceTabs(text),
|
|
637
|
+
function sanitizeText(text: string, width: number): string {
|
|
638
|
+
return truncateToWidth(replaceTabs(text), width, Ellipsis.Unicode);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function previewJobOutput(
|
|
642
|
+
job: AsyncJob,
|
|
643
|
+
verbosity: SubagentParams["verbosity"] = "receipt",
|
|
644
|
+
): { type: "result" | "error"; preview: string; truncated: boolean } | undefined {
|
|
645
|
+
const source = job.errorText
|
|
646
|
+
? { type: "error" as const, text: job.errorText }
|
|
647
|
+
: job.resultText
|
|
648
|
+
? { type: "result" as const, text: job.resultText }
|
|
649
|
+
: undefined;
|
|
650
|
+
if (!source) return undefined;
|
|
651
|
+
const width =
|
|
652
|
+
verbosity === "full" ? FULL_PREVIEW_WIDTH : verbosity === "preview" ? PREVIEW_WIDTH : RECEIPT_PREVIEW_WIDTH;
|
|
653
|
+
const normalized = replaceTabs(source.text);
|
|
654
|
+
const preview = truncateToWidth(normalized, width, Ellipsis.Unicode);
|
|
655
|
+
return { type: source.type, preview, truncated: preview !== normalized };
|
|
503
656
|
}
|