@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.
Files changed (175) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/types/async/job-manager.d.ts +7 -0
  3. package/dist/types/cli/args.d.ts +1 -1
  4. package/dist/types/commands/deep-interview.d.ts +3 -0
  5. package/dist/types/config/keybindings.d.ts +5 -0
  6. package/dist/types/config/settings-schema.d.ts +4 -4
  7. package/dist/types/debug/crash-diagnostics.d.ts +45 -0
  8. package/dist/types/debug/runtime-gauges.d.ts +6 -0
  9. package/dist/types/deep-interview/render-middleware.d.ts +1 -0
  10. package/dist/types/eval/py/executor.d.ts +2 -0
  11. package/dist/types/eval/py/kernel.d.ts +2 -0
  12. package/dist/types/exec/bash-executor.d.ts +10 -0
  13. package/dist/types/gjc-runtime/cli-write-receipt.d.ts +24 -0
  14. package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +1 -0
  15. package/dist/types/gjc-runtime/state-migrations.d.ts +9 -0
  16. package/dist/types/gjc-runtime/state-schema.d.ts +317 -0
  17. package/dist/types/gjc-runtime/state-writer.d.ts +10 -0
  18. package/dist/types/gjc-runtime/workflow-command-ref.d.ts +43 -0
  19. package/dist/types/harness-control-plane/control-endpoint.d.ts +3 -2
  20. package/dist/types/hooks/skill-state.d.ts +21 -0
  21. package/dist/types/internal-urls/agent-protocol.d.ts +2 -2
  22. package/dist/types/internal-urls/artifact-protocol.d.ts +2 -2
  23. package/dist/types/internal-urls/registry-helpers.d.ts +8 -7
  24. package/dist/types/internal-urls/types.d.ts +4 -0
  25. package/dist/types/lsp/index.d.ts +10 -10
  26. package/dist/types/modes/bridge/auth.d.ts +12 -0
  27. package/dist/types/modes/bridge/bridge-client-bridge.d.ts +9 -0
  28. package/dist/types/modes/bridge/bridge-mode.d.ts +44 -0
  29. package/dist/types/modes/bridge/bridge-ui-context.d.ts +88 -0
  30. package/dist/types/modes/bridge/event-stream.d.ts +8 -0
  31. package/dist/types/modes/components/custom-editor.d.ts +6 -0
  32. package/dist/types/modes/components/jobs-overlay-model.d.ts +31 -0
  33. package/dist/types/modes/components/jobs-overlay.d.ts +30 -0
  34. package/dist/types/modes/components/status-line/types.d.ts +2 -0
  35. package/dist/types/modes/components/status-line.d.ts +2 -0
  36. package/dist/types/modes/controllers/input-controller.d.ts +1 -0
  37. package/dist/types/modes/controllers/selector-controller.d.ts +8 -0
  38. package/dist/types/modes/index.d.ts +1 -0
  39. package/dist/types/modes/interactive-mode.d.ts +1 -0
  40. package/dist/types/modes/jobs-observer.d.ts +57 -0
  41. package/dist/types/modes/rpc/host-tools.d.ts +1 -16
  42. package/dist/types/modes/rpc/host-uris.d.ts +1 -38
  43. package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +20 -0
  44. package/dist/types/modes/shared/agent-wire/command-validation.d.ts +2 -0
  45. package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +24 -0
  46. package/dist/types/modes/shared/agent-wire/handshake.d.ts +46 -0
  47. package/dist/types/modes/shared/agent-wire/host-tool-bridge.d.ts +16 -0
  48. package/dist/types/modes/shared/agent-wire/host-uri-bridge.d.ts +17 -0
  49. package/dist/types/modes/shared/agent-wire/protocol.d.ts +44 -0
  50. package/dist/types/modes/shared/agent-wire/responses.d.ts +4 -0
  51. package/dist/types/modes/shared/agent-wire/scopes.d.ts +18 -0
  52. package/dist/types/modes/shared/agent-wire/ui-request-broker.d.ts +42 -0
  53. package/dist/types/modes/shared/agent-wire/ui-result.d.ts +27 -0
  54. package/dist/types/modes/types.d.ts +1 -0
  55. package/dist/types/sdk.d.ts +2 -0
  56. package/dist/types/session/agent-session.d.ts +11 -1
  57. package/dist/types/skill-state/workflow-state-contract.d.ts +1 -2
  58. package/dist/types/skill-state/workflow-state-version.d.ts +3 -0
  59. package/dist/types/task/id.d.ts +7 -0
  60. package/dist/types/task/index.d.ts +5 -0
  61. package/dist/types/task/receipt.d.ts +85 -0
  62. package/dist/types/task/spawn-gate.d.ts +38 -0
  63. package/dist/types/task/types.d.ts +143 -11
  64. package/dist/types/tools/cron.d.ts +6 -0
  65. package/dist/types/tools/index.d.ts +2 -0
  66. package/dist/types/tools/path-utils.d.ts +1 -0
  67. package/dist/types/tools/subagent.d.ts +15 -0
  68. package/package.json +7 -7
  69. package/scripts/build-binary.ts +7 -0
  70. package/src/async/job-manager.ts +36 -0
  71. package/src/cli/args.ts +9 -2
  72. package/src/commands/deep-interview.ts +1 -0
  73. package/src/commands/harness.ts +289 -19
  74. package/src/commands/launch.ts +2 -2
  75. package/src/commands/state.ts +2 -1
  76. package/src/commands/team.ts +22 -4
  77. package/src/config/keybindings.ts +6 -0
  78. package/src/config/settings-schema.ts +6 -3
  79. package/src/dap/client.ts +17 -3
  80. package/src/debug/crash-diagnostics.ts +223 -0
  81. package/src/debug/runtime-gauges.ts +20 -0
  82. package/src/deep-interview/render-middleware.ts +6 -0
  83. package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
  84. package/src/defaults/gjc/skills/ralplan/SKILL.md +31 -2
  85. package/src/defaults/gjc/skills/ultragoal/SKILL.md +28 -2
  86. package/src/eval/py/executor.ts +21 -1
  87. package/src/eval/py/kernel.ts +15 -0
  88. package/src/exec/bash-executor.ts +41 -0
  89. package/src/gjc-runtime/cli-write-receipt.ts +31 -0
  90. package/src/gjc-runtime/deep-interview-runtime.ts +69 -32
  91. package/src/gjc-runtime/ralplan-runtime.ts +213 -36
  92. package/src/gjc-runtime/state-migrations.ts +54 -7
  93. package/src/gjc-runtime/state-runtime.ts +461 -64
  94. package/src/gjc-runtime/state-schema.ts +192 -0
  95. package/src/gjc-runtime/state-writer.ts +32 -1
  96. package/src/gjc-runtime/team-runtime.ts +177 -105
  97. package/src/gjc-runtime/ultragoal-runtime.ts +114 -26
  98. package/src/gjc-runtime/workflow-command-ref.ts +239 -0
  99. package/src/gjc-runtime/workflow-manifest.generated.json +108 -4
  100. package/src/gjc-runtime/workflow-manifest.ts +3 -1
  101. package/src/harness-control-plane/control-endpoint.ts +19 -8
  102. package/src/harness-control-plane/owner.ts +57 -10
  103. package/src/harness-control-plane/state-machine.ts +2 -1
  104. package/src/hooks/skill-state.ts +176 -26
  105. package/src/internal-urls/agent-protocol.ts +68 -21
  106. package/src/internal-urls/artifact-protocol.ts +12 -17
  107. package/src/internal-urls/docs-index.generated.ts +3 -2
  108. package/src/internal-urls/registry-helpers.ts +19 -16
  109. package/src/internal-urls/types.ts +4 -0
  110. package/src/lsp/client.ts +18 -2
  111. package/src/main.ts +21 -5
  112. package/src/modes/bridge/auth.ts +41 -0
  113. package/src/modes/bridge/bridge-client-bridge.ts +47 -0
  114. package/src/modes/bridge/bridge-mode.ts +520 -0
  115. package/src/modes/bridge/bridge-ui-context.ts +200 -0
  116. package/src/modes/bridge/event-stream.ts +70 -0
  117. package/src/modes/components/custom-editor.ts +101 -0
  118. package/src/modes/components/hook-selector.ts +61 -18
  119. package/src/modes/components/jobs-overlay-model.ts +109 -0
  120. package/src/modes/components/jobs-overlay.ts +172 -0
  121. package/src/modes/components/status-line/presets.ts +7 -5
  122. package/src/modes/components/status-line/segments.ts +25 -0
  123. package/src/modes/components/status-line/types.ts +2 -0
  124. package/src/modes/components/status-line.ts +9 -1
  125. package/src/modes/controllers/extension-ui-controller.ts +39 -3
  126. package/src/modes/controllers/input-controller.ts +97 -9
  127. package/src/modes/controllers/selector-controller.ts +29 -0
  128. package/src/modes/index.ts +1 -0
  129. package/src/modes/interactive-mode.ts +27 -0
  130. package/src/modes/jobs-observer.ts +204 -0
  131. package/src/modes/rpc/host-tools.ts +1 -186
  132. package/src/modes/rpc/host-uris.ts +1 -235
  133. package/src/modes/rpc/rpc-client.ts +25 -10
  134. package/src/modes/rpc/rpc-mode.ts +12 -381
  135. package/src/modes/shared/agent-wire/command-dispatch.ts +341 -0
  136. package/src/modes/shared/agent-wire/command-validation.ts +131 -0
  137. package/src/modes/shared/agent-wire/event-envelope.ts +108 -0
  138. package/src/modes/shared/agent-wire/handshake.ts +117 -0
  139. package/src/modes/shared/agent-wire/host-tool-bridge.ts +194 -0
  140. package/src/modes/shared/agent-wire/host-uri-bridge.ts +236 -0
  141. package/src/modes/shared/agent-wire/protocol.ts +96 -0
  142. package/src/modes/shared/agent-wire/responses.ts +17 -0
  143. package/src/modes/shared/agent-wire/scopes.ts +89 -0
  144. package/src/modes/shared/agent-wire/ui-request-broker.ts +150 -0
  145. package/src/modes/shared/agent-wire/ui-result.ts +48 -0
  146. package/src/modes/types.ts +1 -0
  147. package/src/prompts/tools/subagent.md +12 -7
  148. package/src/prompts/tools/task-summary.md +3 -9
  149. package/src/prompts/tools/task.md +5 -1
  150. package/src/sdk.ts +4 -0
  151. package/src/session/agent-session.ts +214 -38
  152. package/src/skill-state/deep-interview-mutation-guard.ts +23 -4
  153. package/src/skill-state/workflow-state-contract.ts +7 -4
  154. package/src/skill-state/workflow-state-version.ts +3 -0
  155. package/src/slash-commands/builtin-registry.ts +8 -0
  156. package/src/task/executor.ts +29 -5
  157. package/src/task/id.ts +33 -0
  158. package/src/task/index.ts +257 -67
  159. package/src/task/output-manager.ts +5 -4
  160. package/src/task/receipt.ts +297 -0
  161. package/src/task/render.ts +48 -131
  162. package/src/task/spawn-gate.ts +132 -0
  163. package/src/task/types.ts +48 -7
  164. package/src/tools/ask.ts +73 -33
  165. package/src/tools/ast-edit.ts +1 -0
  166. package/src/tools/ast-grep.ts +1 -0
  167. package/src/tools/bash.ts +1 -1
  168. package/src/tools/cron.ts +48 -0
  169. package/src/tools/find.ts +4 -1
  170. package/src/tools/index.ts +2 -0
  171. package/src/tools/path-utils.ts +3 -2
  172. package/src/tools/read.ts +1 -0
  173. package/src/tools/search.ts +1 -0
  174. package/src/tools/skill.ts +6 -1
  175. package/src/tools/subagent.ts +237 -84
@@ -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 TEXT_PREVIEW_WIDTH = 12_000;
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 snapshots: SubagentSnapshot[] = [];
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
- snapshots.push(this.#missingSnapshot(id, "not_found", "No visible detached subagent matches this id."));
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
- const updated = this.#findVisibleRecord(manager, id, ownerFilter) ?? record;
121
- snapshots.push(this.#recordSnapshot(manager, updated));
141
+ records.push(this.#findVisibleRecord(manager, id, ownerFilter) ?? record);
122
142
  }
123
- return this.#buildSnapshotResult(snapshots, "Subagent cancellation");
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 snapshots: SubagentSnapshot[] = [];
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
- snapshots.push(this.#missingSnapshot(id, "not_found", "No visible detached subagent matches this id."));
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
- snapshots.push(this.#missingSnapshot(id, "not_found", "No visible detached subagent matches this id."));
168
+ missing.push(this.#missingSnapshot(id, "not_found", "No visible detached subagent matches this id."));
141
169
  continue;
142
170
  }
143
- snapshots.push(
144
- this.#recordSnapshot(manager, manager.getSubagentRecord(record.subagentId, ownerFilter) ?? record),
145
- );
171
+ records.push(manager.getSubagentRecord(record.subagentId, ownerFilter) ?? record);
146
172
  }
147
- return this.#buildSnapshotResult(snapshots, "Subagent pause");
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 ids = params.ids ?? [];
152
- if (ids.length === 0) {
153
- throw new ToolError("`resume` requires at least one subagent id.");
154
- }
155
- const snapshots: SubagentSnapshot[] = [];
156
- for (const id of ids) {
157
- const record = this.#findVisibleRecord(manager, id, ownerFilter);
158
- if (!record) {
159
- snapshots.push(this.#missingSnapshot(id, "not_found", "No visible detached subagent matches this id."));
160
- continue;
161
- }
162
- if (record.status === "running") {
163
- snapshots.push(this.#recordSnapshot(manager, record));
164
- continue;
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
- snapshots.push(this.#missingSnapshot(id, "not_found", "No visible detached subagent matches this id."));
178
- continue;
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
- return this.#buildSnapshotResult(snapshots, "Subagent resume");
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 ids = params.ids ?? [];
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 snapshots: SubagentSnapshot[] = [];
197
- for (const id of ids) {
198
- const record = this.#findVisibleRecord(manager, id, ownerFilter);
199
- if (!record) {
200
- snapshots.push(this.#missingSnapshot(id, "not_found", "No visible detached subagent matches this id."));
201
- continue;
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
- snapshots.push(
214
- this.#missingSnapshot(id, "not_found", "No visible detached subagent matches this id."),
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
- snapshots.push(
220
- this.#recordSnapshot(manager, manager.getSubagentRecord(record.subagentId, ownerFilter) ?? record),
221
- );
253
+ if (record.status === "running")
254
+ records.push(manager.getSubagentRecord(record.subagentId, ownerFilter) ?? record);
222
255
  }
223
- return this.#buildSnapshotResult(snapshots, "Subagent steer");
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, { title: "Subagent await", notFoundIds });
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, { title: "Subagent await", notFoundIds, timedOut });
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 snapshots = this.#recordSnapshots(manager, records, options.timedOut);
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.resultText) lines.push("Result:", "```", snapshot.resultText, "```");
422
- if (snapshot.errorText) lines.push("Error:", "```", snapshot.errorText, "```");
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(manager: AsyncJobManager, records: SubagentRecord[], timedOut = false): SubagentSnapshot[] {
433
- return records.map(record => this.#recordSnapshot(manager, record, timedOut));
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(manager: AsyncJobManager, record: SubagentRecord, timedOut = false): SubagentSnapshot {
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(job: AsyncJob, timedOut = false): SubagentSnapshot {
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
- ...(subagent?.assignment ? { assignment: sanitizeText(subagent.assignment) } : {}),
473
- ...(job.resultText ? { resultText: sanitizeText(job.resultText) } : {}),
474
- ...(job.errorText ? { errorText: sanitizeText(job.errorText) } : {}),
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), TEXT_PREVIEW_WIDTH, Ellipsis.Unicode);
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
  }