@gajae-code/coding-agent 0.2.5 → 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 +28 -0
- package/dist/types/async/job-manager.d.ts +91 -2
- package/dist/types/cli/args.d.ts +1 -1
- package/dist/types/commands/deep-interview.d.ts +3 -0
- package/dist/types/commands/harness.d.ts +37 -0
- package/dist/types/config/keybindings.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +10 -4
- package/dist/types/config/settings.d.ts +2 -0
- 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 +6 -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/extensibility/custom-tools/types.d.ts +1 -0
- package/dist/types/extensibility/extensions/types.d.ts +6 -0
- package/dist/types/extensibility/shared-events.d.ts +1 -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-graph.d.ts +4 -0
- package/dist/types/gjc-runtime/state-migrations.d.ts +33 -0
- package/dist/types/gjc-runtime/state-renderer.d.ts +65 -0
- package/dist/types/gjc-runtime/state-runtime.d.ts +2 -0
- package/dist/types/gjc-runtime/state-schema.d.ts +317 -0
- package/dist/types/gjc-runtime/state-validation.d.ts +6 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +147 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +81 -7
- package/dist/types/gjc-runtime/workflow-command-ref.d.ts +43 -0
- package/dist/types/gjc-runtime/workflow-manifest.d.ts +54 -0
- package/dist/types/harness-control-plane/classifier.d.ts +13 -0
- package/dist/types/harness-control-plane/control-endpoint.d.ts +31 -0
- package/dist/types/harness-control-plane/finalize.d.ts +47 -0
- package/dist/types/harness-control-plane/frame-mapper.d.ts +29 -0
- package/dist/types/harness-control-plane/operate.d.ts +35 -0
- package/dist/types/harness-control-plane/owner.d.ts +46 -0
- package/dist/types/harness-control-plane/preserve.d.ts +19 -0
- package/dist/types/harness-control-plane/receipts.d.ts +88 -0
- package/dist/types/harness-control-plane/rpc-adapter.d.ts +66 -0
- package/dist/types/harness-control-plane/seams.d.ts +21 -0
- package/dist/types/harness-control-plane/session-lease.d.ts +65 -0
- package/dist/types/harness-control-plane/state-machine.d.ts +19 -0
- package/dist/types/harness-control-plane/storage.d.ts +53 -0
- package/dist/types/harness-control-plane/types.d.ts +162 -0
- package/dist/types/hooks/skill-keywords.d.ts +2 -1
- package/dist/types/hooks/skill-state.d.ts +23 -29
- 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/hook-selector.d.ts +1 -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 +2 -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 +2 -0
- package/dist/types/sdk.d.ts +4 -0
- package/dist/types/session/agent-session.d.ts +19 -1
- package/dist/types/skill-state/active-state.d.ts +2 -0
- package/dist/types/skill-state/deep-interview-mutation-guard.d.ts +1 -1
- package/dist/types/skill-state/workflow-state-contract.d.ts +25 -2
- package/dist/types/skill-state/workflow-state-version.d.ts +3 -0
- package/dist/types/task/executor.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 +198 -14
- 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 +26 -1
- package/package.json +7 -7
- package/scripts/build-binary.ts +7 -0
- package/src/async/job-manager.ts +334 -6
- package/src/cli/args.ts +9 -2
- package/src/cli/auth-broker-cli.ts +1 -0
- package/src/cli/config-cli.ts +10 -2
- package/src/cli.ts +2 -0
- package/src/commands/deep-interview.ts +1 -0
- package/src/commands/harness.ts +862 -0
- package/src/commands/launch.ts +2 -2
- package/src/commands/state.ts +2 -1
- package/src/commands/team.ts +54 -39
- package/src/config/keybindings.ts +6 -0
- package/src/config/settings-schema.ts +13 -3
- package/src/config/settings.ts +5 -0
- 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 +372 -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/team/SKILL.md +47 -21
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +106 -13
- 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/extensibility/custom-tools/types.ts +1 -0
- package/src/extensibility/extensions/types.ts +6 -0
- package/src/extensibility/shared-events.ts +1 -0
- package/src/gjc-runtime/cli-write-receipt.ts +31 -0
- package/src/gjc-runtime/deep-interview-runtime.ts +98 -42
- package/src/gjc-runtime/goal-mode-request.ts +11 -3
- package/src/gjc-runtime/ralplan-runtime.ts +235 -43
- package/src/gjc-runtime/state-graph.ts +86 -0
- package/src/gjc-runtime/state-migrations.ts +179 -0
- package/src/gjc-runtime/state-renderer.ts +345 -0
- package/src/gjc-runtime/state-runtime.ts +1155 -46
- package/src/gjc-runtime/state-schema.ts +192 -0
- package/src/gjc-runtime/state-validation.ts +49 -0
- package/src/gjc-runtime/state-writer.ts +749 -0
- package/src/gjc-runtime/team-runtime.ts +1255 -189
- package/src/gjc-runtime/ultragoal-runtime.ts +460 -43
- package/src/gjc-runtime/workflow-command-ref.ts +239 -0
- package/src/gjc-runtime/workflow-manifest.generated.json +1601 -0
- package/src/gjc-runtime/workflow-manifest.ts +427 -0
- package/src/harness-control-plane/classifier.ts +128 -0
- package/src/harness-control-plane/control-endpoint.ts +148 -0
- package/src/harness-control-plane/finalize.ts +222 -0
- package/src/harness-control-plane/frame-mapper.ts +286 -0
- package/src/harness-control-plane/operate.ts +225 -0
- package/src/harness-control-plane/owner.ts +600 -0
- package/src/harness-control-plane/preserve.ts +102 -0
- package/src/harness-control-plane/receipts.ts +216 -0
- package/src/harness-control-plane/rpc-adapter.ts +276 -0
- package/src/harness-control-plane/seams.ts +39 -0
- package/src/harness-control-plane/session-lease.ts +388 -0
- package/src/harness-control-plane/state-machine.ts +98 -0
- package/src/harness-control-plane/storage.ts +257 -0
- package/src/harness-control-plane/types.ts +214 -0
- package/src/hooks/skill-keywords.ts +4 -2
- package/src/hooks/skill-state.ts +197 -64
- 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/assistant-message.ts +5 -1
- package/src/modes/components/custom-editor.ts +101 -0
- package/src/modes/components/hook-selector.ts +133 -20
- 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/event-controller.ts +71 -6
- package/src/modes/controllers/extension-ui-controller.ts +43 -1
- package/src/modes/controllers/input-controller.ts +105 -9
- package/src/modes/controllers/selector-controller.ts +31 -1
- package/src/modes/index.ts +1 -0
- package/src/modes/interactive-mode.ts +28 -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 +2 -0
- package/src/prompts/agents/executor.md +13 -0
- package/src/prompts/tools/subagent.md +39 -4
- package/src/prompts/tools/task-summary.md +3 -9
- package/src/prompts/tools/task.md +5 -1
- package/src/sdk.ts +8 -0
- package/src/session/agent-session.ts +445 -71
- package/src/session/session-manager.ts +13 -1
- package/src/skill-state/active-state.ts +58 -65
- package/src/skill-state/deep-interview-mutation-guard.ts +114 -17
- package/src/skill-state/initial-phase.ts +2 -0
- package/src/skill-state/workflow-state-contract.ts +33 -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 +79 -13
- package/src/task/id.ts +33 -0
- package/src/task/index.ts +376 -74
- package/src/task/output-manager.ts +5 -4
- package/src/task/receipt.ts +297 -0
- package/src/task/render.ts +54 -134
- package/src/task/spawn-gate.ts +132 -0
- package/src/task/types.ts +104 -10
- package/src/tools/ask.ts +88 -27
- 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 +423 -79
package/src/tools/subagent.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
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";
|
|
4
|
-
import { type AsyncJob, AsyncJobManager } from "../async";
|
|
5
|
+
import { type AsyncJob, AsyncJobManager, type SubagentRecord } from "../async";
|
|
5
6
|
import subagentDescription from "../prompts/tools/subagent.md" with { type: "text" };
|
|
6
7
|
import type { AgentSource } from "../task/types";
|
|
7
8
|
import { Ellipsis, truncateToWidth } from "../tui";
|
|
@@ -13,17 +14,38 @@ 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
|
-
action: z
|
|
22
|
+
action: z
|
|
23
|
+
.enum(["list", "inspect", "await", "cancel", "pause", "resume", "steer"])
|
|
24
|
+
.describe("subagent control action"),
|
|
20
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"),
|
|
27
|
+
message: z.string().optional().describe("message to deliver when resuming or steering a subagent"),
|
|
28
|
+
pause: z.boolean().optional().describe("pause after steering a currently running subagent"),
|
|
21
29
|
timeout_ms: z.number().min(0).max(MAX_AWAIT_TIMEOUT_MS).optional().describe("await timeout in milliseconds"),
|
|
22
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
|
+
),
|
|
23
37
|
});
|
|
24
38
|
|
|
25
39
|
type SubagentParams = z.infer<typeof subagentSchema>;
|
|
26
|
-
type SubagentStatus =
|
|
40
|
+
type SubagentStatus =
|
|
41
|
+
| "running"
|
|
42
|
+
| "paused"
|
|
43
|
+
| "queued"
|
|
44
|
+
| "completed"
|
|
45
|
+
| "failed"
|
|
46
|
+
| "cancelled"
|
|
47
|
+
| "not_found"
|
|
48
|
+
| "already_completed";
|
|
27
49
|
|
|
28
50
|
export interface SubagentSnapshot {
|
|
29
51
|
id: string;
|
|
@@ -37,6 +59,9 @@ export interface SubagentSnapshot {
|
|
|
37
59
|
durationMs: number;
|
|
38
60
|
resultText?: string;
|
|
39
61
|
errorText?: string;
|
|
62
|
+
resultPreview?: string;
|
|
63
|
+
outputRef?: string;
|
|
64
|
+
truncated?: boolean;
|
|
40
65
|
guidance?: string;
|
|
41
66
|
}
|
|
42
67
|
|
|
@@ -75,19 +100,26 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
75
100
|
const ownerId = this.session.getAgentId?.() ?? undefined;
|
|
76
101
|
const ownerFilter = ownerId ? { ownerId } : undefined;
|
|
77
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
|
+
}
|
|
78
109
|
|
|
79
110
|
if (params.action === "list") {
|
|
80
|
-
const
|
|
81
|
-
return this.#
|
|
111
|
+
const records = this.#listSubagentRecords(manager, ownerFilter, limit);
|
|
112
|
+
return await this.#buildRecordResult(manager, records, { title: "Subagents", verbosity });
|
|
82
113
|
}
|
|
83
114
|
|
|
84
115
|
if (params.action === "inspect") {
|
|
85
|
-
const
|
|
86
|
-
? this.#
|
|
87
|
-
: manager
|
|
88
|
-
return this.#
|
|
116
|
+
const records = params.ids?.length
|
|
117
|
+
? this.#visibleRecordsByIds(manager, params.ids, ownerFilter)
|
|
118
|
+
: this.#runningRecords(manager, ownerFilter);
|
|
119
|
+
return await this.#buildRecordResult(manager, records, {
|
|
89
120
|
title: "Subagent inspection",
|
|
90
|
-
notFoundIds: this.#
|
|
121
|
+
notFoundIds: this.#notFoundRecordIds(manager, params.ids ?? [], ownerFilter),
|
|
122
|
+
verbosity,
|
|
91
123
|
});
|
|
92
124
|
}
|
|
93
125
|
|
|
@@ -96,48 +128,190 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
96
128
|
if (ids.length === 0) {
|
|
97
129
|
throw new ToolError("`cancel` requires at least one subagent id.");
|
|
98
130
|
}
|
|
99
|
-
const
|
|
131
|
+
const records: SubagentRecord[] = [];
|
|
132
|
+
const missing: SubagentSnapshot[] = [];
|
|
133
|
+
for (const id of ids) {
|
|
134
|
+
const record = this.#findVisibleRecord(manager, id, ownerFilter);
|
|
135
|
+
if (!record) {
|
|
136
|
+
missing.push(this.#missingSnapshot(id, "not_found", "No visible detached subagent matches this id."));
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const cancelled = manager.cancelSubagent(record.subagentId, ownerFilter);
|
|
140
|
+
if (!cancelled && record.currentJobId) manager.cancel(record.currentJobId, ownerFilter);
|
|
141
|
+
records.push(this.#findVisibleRecord(manager, id, ownerFilter) ?? record);
|
|
142
|
+
}
|
|
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
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (params.action === "pause") {
|
|
154
|
+
const ids = params.ids ?? [];
|
|
155
|
+
if (ids.length === 0) {
|
|
156
|
+
throw new ToolError("`pause` requires at least one subagent id.");
|
|
157
|
+
}
|
|
158
|
+
const records: SubagentRecord[] = [];
|
|
159
|
+
const missing: SubagentSnapshot[] = [];
|
|
100
160
|
for (const id of ids) {
|
|
101
|
-
const
|
|
102
|
-
if (!
|
|
103
|
-
|
|
161
|
+
const record = this.#findVisibleRecord(manager, id, ownerFilter);
|
|
162
|
+
if (!record) {
|
|
163
|
+
missing.push(this.#missingSnapshot(id, "not_found", "No visible detached subagent matches this id."));
|
|
104
164
|
continue;
|
|
105
165
|
}
|
|
106
|
-
|
|
107
|
-
|
|
166
|
+
const result = manager.pauseSubagent(record.subagentId, ownerFilter);
|
|
167
|
+
if (!result.ok && result.reason === "not_found") {
|
|
168
|
+
missing.push(this.#missingSnapshot(id, "not_found", "No visible detached subagent matches this id."));
|
|
108
169
|
continue;
|
|
109
170
|
}
|
|
110
|
-
manager.
|
|
111
|
-
|
|
171
|
+
records.push(manager.getSubagentRecord(record.subagentId, ownerFilter) ?? record);
|
|
172
|
+
}
|
|
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
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (params.action === "resume") {
|
|
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 {
|
|
198
|
+
const result = manager.resumeSubagent(record.subagentId, ownerFilter, params.message);
|
|
199
|
+
if (!result.ok && result.reason === "context_unavailable") throw new ToolError("context unavailable");
|
|
200
|
+
if (!result.ok && result.reason === "not_found") {
|
|
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);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
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
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (params.action === "steer") {
|
|
226
|
+
const id = this.#singleTargetId(params, "steer");
|
|
227
|
+
const message = params.message;
|
|
228
|
+
if (message === undefined || message.trim() === "") {
|
|
229
|
+
throw new ToolError("`steer` requires a non-empty message.");
|
|
230
|
+
}
|
|
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 {
|
|
238
|
+
if (!record.sessionFile) throw new ToolError(`Subagent ${record.subagentId} has no session file.`);
|
|
239
|
+
if (record.status === "running") {
|
|
240
|
+
const handle = manager.getLiveHandle(record.subagentId);
|
|
241
|
+
if (!handle) throw new ToolError(`Subagent ${record.subagentId} has no live handle.`);
|
|
242
|
+
await handle.injectMessage(message, "steer");
|
|
243
|
+
if (params.pause === true) manager.pauseSubagent(record.subagentId, ownerFilter);
|
|
244
|
+
} else {
|
|
245
|
+
const result = manager.resumeSubagent(record.subagentId, ownerFilter, message);
|
|
246
|
+
if (!result.ok && result.reason === "context_unavailable") throw new ToolError("context unavailable");
|
|
247
|
+
if (!result.ok && result.reason === "not_found") {
|
|
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);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (record.status === "running")
|
|
254
|
+
records.push(manager.getSubagentRecord(record.subagentId, ownerFilter) ?? record);
|
|
112
255
|
}
|
|
113
|
-
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
|
+
);
|
|
114
263
|
}
|
|
115
264
|
|
|
116
|
-
return this.#awaitSubagents(manager, params,
|
|
265
|
+
return this.#awaitSubagents(manager, params, ownerFilter, signal, onUpdate);
|
|
266
|
+
}
|
|
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\`.`);
|
|
117
285
|
}
|
|
118
286
|
|
|
119
287
|
async #awaitSubagents(
|
|
120
288
|
manager: AsyncJobManager,
|
|
121
289
|
params: SubagentParams,
|
|
122
|
-
ownerId: string | undefined,
|
|
123
290
|
ownerFilter: { ownerId: string } | undefined,
|
|
124
291
|
signal: AbortSignal | undefined,
|
|
125
292
|
onUpdate: AgentToolUpdateCallback<SubagentToolDetails> | undefined,
|
|
126
293
|
): Promise<AgentToolResult<SubagentToolDetails>> {
|
|
127
|
-
const
|
|
128
|
-
? this.#
|
|
129
|
-
: manager
|
|
130
|
-
const notFoundIds = this.#
|
|
131
|
-
if (
|
|
294
|
+
const records = params.ids?.length
|
|
295
|
+
? this.#visibleRecordsByIds(manager, params.ids, ownerFilter)
|
|
296
|
+
: this.#runningRecords(manager, ownerFilter);
|
|
297
|
+
const notFoundIds = this.#notFoundRecordIds(manager, params.ids ?? [], ownerFilter);
|
|
298
|
+
if (records.length === 0) {
|
|
132
299
|
const missing = notFoundIds.map(id =>
|
|
133
300
|
this.#missingSnapshot(id, "not_found", "No visible detached subagent matches this id."),
|
|
134
301
|
);
|
|
135
302
|
return this.#buildSnapshotResult(missing, "Subagent await");
|
|
136
303
|
}
|
|
137
304
|
|
|
138
|
-
const runningJobs =
|
|
305
|
+
const runningJobs = records
|
|
306
|
+
.filter(record => record.status === "running" && record.currentJobId)
|
|
307
|
+
.map(record => manager.getJob(record.currentJobId!))
|
|
308
|
+
.filter((job): job is AsyncJob => job !== undefined);
|
|
139
309
|
if (runningJobs.length === 0) {
|
|
140
|
-
return this.#
|
|
310
|
+
return await this.#buildRecordResult(manager, records, {
|
|
311
|
+
title: "Subagent await",
|
|
312
|
+
notFoundIds,
|
|
313
|
+
verbosity: params.verbosity ?? "receipt",
|
|
314
|
+
});
|
|
141
315
|
}
|
|
142
316
|
|
|
143
317
|
const timeoutMs = Math.min(
|
|
@@ -148,10 +322,10 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
148
322
|
manager.watchJobs(watchedJobIds);
|
|
149
323
|
const progressTimer = onUpdate
|
|
150
324
|
? setInterval(() => {
|
|
151
|
-
onUpdate(this.#progressResult(manager,
|
|
325
|
+
onUpdate(this.#progressResult(manager, records));
|
|
152
326
|
}, 500)
|
|
153
327
|
: undefined;
|
|
154
|
-
onUpdate?.(this.#progressResult(manager,
|
|
328
|
+
onUpdate?.(this.#progressResult(manager, records));
|
|
155
329
|
|
|
156
330
|
let timedOut = false;
|
|
157
331
|
try {
|
|
@@ -176,70 +350,136 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
176
350
|
if (progressTimer) clearInterval(progressTimer);
|
|
177
351
|
}
|
|
178
352
|
|
|
179
|
-
return this.#
|
|
353
|
+
return await this.#buildRecordResult(manager, records, {
|
|
354
|
+
title: "Subagent await",
|
|
355
|
+
notFoundIds,
|
|
356
|
+
timedOut,
|
|
357
|
+
verbosity: params.verbosity ?? "receipt",
|
|
358
|
+
});
|
|
180
359
|
}
|
|
181
360
|
|
|
182
|
-
#
|
|
361
|
+
#mergedRecords(
|
|
183
362
|
manager: AsyncJobManager,
|
|
184
363
|
ownerFilter: { ownerId: string } | undefined,
|
|
185
364
|
limit: number,
|
|
186
|
-
):
|
|
187
|
-
const
|
|
188
|
-
const
|
|
189
|
-
const jobs = [...
|
|
190
|
-
|
|
365
|
+
): SubagentRecord[] {
|
|
366
|
+
const merged = [...manager.getSubagentRecords(ownerFilter)];
|
|
367
|
+
const known = new Set(merged.map(record => record.subagentId));
|
|
368
|
+
const jobs = [...manager.getRunningJobs(ownerFilter), ...manager.getRecentJobs(limit, ownerFilter)].filter(
|
|
369
|
+
isSubagentJob,
|
|
370
|
+
);
|
|
371
|
+
for (const job of jobs) {
|
|
372
|
+
const subagentId = job.metadata?.subagent?.id ?? job.id;
|
|
373
|
+
if (known.has(subagentId)) continue;
|
|
374
|
+
known.add(subagentId);
|
|
375
|
+
merged.push(this.#jobToRecord(job));
|
|
376
|
+
}
|
|
377
|
+
merged.sort((a, b) => {
|
|
378
|
+
const aJob = a.currentJobId ? manager.getJob(a.currentJobId) : undefined;
|
|
379
|
+
const bJob = b.currentJobId ? manager.getJob(b.currentJobId) : undefined;
|
|
380
|
+
return (bJob?.startTime ?? 0) - (aJob?.startTime ?? 0);
|
|
381
|
+
});
|
|
382
|
+
return merged.slice(0, limit);
|
|
191
383
|
}
|
|
192
384
|
|
|
193
|
-
#
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
return this.#dedupeJobs(jobs);
|
|
385
|
+
#listSubagentRecords(
|
|
386
|
+
manager: AsyncJobManager,
|
|
387
|
+
ownerFilter: { ownerId: string } | undefined,
|
|
388
|
+
limit: number,
|
|
389
|
+
): SubagentRecord[] {
|
|
390
|
+
return this.#mergedRecords(manager, ownerFilter, limit);
|
|
200
391
|
}
|
|
201
392
|
|
|
202
|
-
#
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
393
|
+
#runningRecords(manager: AsyncJobManager, ownerFilter: { ownerId: string } | undefined): SubagentRecord[] {
|
|
394
|
+
return this.#mergedRecords(manager, ownerFilter, MAX_LIST_LIMIT).filter(record => record.status === "running");
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/** Synthesize a record from a subagent job that has no registered SubagentRecord (backward compat). */
|
|
398
|
+
#jobToRecord(job: AsyncJob): SubagentRecord {
|
|
399
|
+
return {
|
|
400
|
+
subagentId: job.metadata?.subagent?.id ?? job.id,
|
|
401
|
+
ownerId: job.ownerId,
|
|
402
|
+
currentJobId: job.id,
|
|
403
|
+
historicalJobIds: [],
|
|
404
|
+
status: job.status,
|
|
405
|
+
sessionFile: null,
|
|
406
|
+
resumable: false,
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
#findSubagentJob(manager: AsyncJobManager, id: string, ownerId: string | undefined): AsyncJob | undefined {
|
|
411
|
+
const direct = manager.getJob(id);
|
|
206
412
|
if (direct && isSubagentJob(direct) && (!ownerId || direct.ownerId === ownerId)) return direct;
|
|
207
413
|
return manager
|
|
208
414
|
.getAllJobs(ownerId ? { ownerId } : undefined)
|
|
209
|
-
.find(job => isSubagentJob(job) && job.metadata?.subagent?.id ===
|
|
415
|
+
.find(job => isSubagentJob(job) && job.metadata?.subagent?.id === id);
|
|
210
416
|
}
|
|
211
417
|
|
|
212
|
-
#
|
|
213
|
-
|
|
418
|
+
#visibleRecordsByIds(
|
|
419
|
+
manager: AsyncJobManager,
|
|
420
|
+
ids: string[],
|
|
421
|
+
ownerFilter: { ownerId: string } | undefined,
|
|
422
|
+
): SubagentRecord[] {
|
|
423
|
+
const records: SubagentRecord[] = [];
|
|
424
|
+
const seen = new Set<string>();
|
|
425
|
+
for (const id of ids) {
|
|
426
|
+
const record = this.#findVisibleRecord(manager, id, ownerFilter);
|
|
427
|
+
if (!record || seen.has(record.subagentId)) continue;
|
|
428
|
+
seen.add(record.subagentId);
|
|
429
|
+
records.push(record);
|
|
430
|
+
}
|
|
431
|
+
return records;
|
|
214
432
|
}
|
|
215
433
|
|
|
216
|
-
#
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
434
|
+
#findVisibleRecord(
|
|
435
|
+
manager: AsyncJobManager,
|
|
436
|
+
id: string,
|
|
437
|
+
ownerFilter: { ownerId: string } | undefined,
|
|
438
|
+
): SubagentRecord | undefined {
|
|
439
|
+
const trimmedId = id.trim();
|
|
440
|
+
if (!trimmedId) return undefined;
|
|
441
|
+
const direct = manager.getSubagentRecord(trimmedId, ownerFilter);
|
|
442
|
+
if (direct) return direct;
|
|
443
|
+
const byJobId = manager.getSubagentRecords(ownerFilter).find(record => record.currentJobId === trimmedId);
|
|
444
|
+
if (byJobId) return byJobId;
|
|
445
|
+
const job = this.#findSubagentJob(manager, trimmedId, ownerFilter?.ownerId);
|
|
446
|
+
return job ? this.#jobToRecord(job) : undefined;
|
|
223
447
|
}
|
|
224
448
|
|
|
225
|
-
#
|
|
449
|
+
#notFoundRecordIds(manager: AsyncJobManager, ids: string[], ownerFilter: { ownerId: string } | undefined): string[] {
|
|
450
|
+
return ids.filter(id => !this.#findVisibleRecord(manager, id, ownerFilter));
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
#progressResult(manager: AsyncJobManager, records: SubagentRecord[]): AgentToolResult<SubagentToolDetails> {
|
|
226
454
|
return {
|
|
227
455
|
content: [{ type: "text", text: "" }],
|
|
228
|
-
details: { subagents: this.#
|
|
456
|
+
details: { subagents: this.#recordSnapshots(manager, records, false, "receipt", new Set()) },
|
|
229
457
|
};
|
|
230
458
|
}
|
|
231
459
|
|
|
232
|
-
#
|
|
460
|
+
async #buildRecordResult(
|
|
233
461
|
manager: AsyncJobManager,
|
|
234
|
-
|
|
235
|
-
options: { title: string; notFoundIds?: string[]; timedOut?: boolean },
|
|
236
|
-
): AgentToolResult<SubagentToolDetails
|
|
237
|
-
const
|
|
462
|
+
records: SubagentRecord[],
|
|
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
|
+
);
|
|
238
473
|
for (const id of options.notFoundIds ?? []) {
|
|
239
474
|
snapshots.push(this.#missingSnapshot(id, "not_found", "No visible detached subagent matches this id."));
|
|
240
475
|
}
|
|
241
476
|
manager.acknowledgeDeliveries(
|
|
242
|
-
snapshots
|
|
477
|
+
snapshots
|
|
478
|
+
.filter(
|
|
479
|
+
s =>
|
|
480
|
+
s.status !== "running" && s.status !== "paused" && s.status !== "queued" && s.status !== "not_found",
|
|
481
|
+
)
|
|
482
|
+
.map(s => s.jobId),
|
|
243
483
|
);
|
|
244
484
|
return this.#buildSnapshotResult(snapshots, options.title);
|
|
245
485
|
}
|
|
@@ -251,9 +491,13 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
251
491
|
if (snapshot.jobId !== snapshot.id) lines.push(`Job: ${snapshot.jobId}`);
|
|
252
492
|
if (snapshot.agent) lines.push(`Agent: ${snapshot.agent} (${snapshot.agentSource})`);
|
|
253
493
|
if (snapshot.description) lines.push(`Description: ${snapshot.description}`);
|
|
494
|
+
if (snapshot.outputRef) lines.push(`Output: ${snapshot.outputRef}`);
|
|
254
495
|
if (snapshot.assignment) lines.push("Assignment:", "```", snapshot.assignment, "```");
|
|
255
|
-
if (snapshot.
|
|
256
|
-
|
|
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
|
+
}
|
|
257
501
|
if (snapshot.guidance) lines.push(`Guidance: ${snapshot.guidance}`);
|
|
258
502
|
lines.push("");
|
|
259
503
|
}
|
|
@@ -263,32 +507,111 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
263
507
|
};
|
|
264
508
|
}
|
|
265
509
|
|
|
266
|
-
#
|
|
267
|
-
|
|
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));
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
#recordSnapshot(
|
|
521
|
+
manager: AsyncJobManager,
|
|
522
|
+
record: SubagentRecord,
|
|
523
|
+
timedOut = false,
|
|
524
|
+
verbosity: SubagentParams["verbosity"] = "receipt",
|
|
525
|
+
verifiedOutputIds: ReadonlySet<string>,
|
|
526
|
+
): SubagentSnapshot {
|
|
527
|
+
const job = record.currentJobId ? manager.getJob(record.currentJobId) : undefined;
|
|
528
|
+
if (job) {
|
|
529
|
+
return {
|
|
530
|
+
...this.#snapshot(job, timedOut, verbosity, verifiedOutputIds, record),
|
|
531
|
+
id: record.subagentId,
|
|
532
|
+
jobId: record.currentJobId ?? job.id,
|
|
533
|
+
status: record.status,
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
return {
|
|
537
|
+
id: record.subagentId,
|
|
538
|
+
jobId: record.currentJobId ?? record.subagentId,
|
|
539
|
+
status: record.status,
|
|
540
|
+
label: "subagent",
|
|
541
|
+
agent: "unknown",
|
|
542
|
+
agentSource: "bundled",
|
|
543
|
+
durationMs: 0,
|
|
544
|
+
...(verifiedOutputIds.has(record.subagentId) ? { outputRef: `agent://${record.subagentId}` } : {}),
|
|
545
|
+
};
|
|
268
546
|
}
|
|
269
547
|
|
|
270
|
-
#snapshot(
|
|
548
|
+
#snapshot(
|
|
549
|
+
job: AsyncJob,
|
|
550
|
+
timedOut = false,
|
|
551
|
+
verbosity: SubagentParams["verbosity"] = "receipt",
|
|
552
|
+
verifiedOutputIds: ReadonlySet<string>,
|
|
553
|
+
record?: SubagentRecord,
|
|
554
|
+
): SubagentSnapshot {
|
|
271
555
|
const subagent = job.metadata?.subagent;
|
|
272
556
|
const runningTimeoutGuidance =
|
|
273
557
|
timedOut && job.status === "running"
|
|
274
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."
|
|
275
559
|
: undefined;
|
|
560
|
+
const output = previewJobOutput(job, verbosity);
|
|
561
|
+
const outputRef = record && verifiedOutputIds.has(record.subagentId) ? `agent://${record.subagentId}` : undefined;
|
|
276
562
|
return {
|
|
277
563
|
id: subagent?.id ?? job.id,
|
|
278
564
|
jobId: job.id,
|
|
279
565
|
status: job.status,
|
|
280
|
-
label: sanitizeText(job.label),
|
|
566
|
+
label: sanitizeText(job.label, RECEIPT_PREVIEW_WIDTH),
|
|
281
567
|
agent: subagent?.agent ?? "unknown",
|
|
282
568
|
agentSource: subagent?.agentSource ?? "bundled",
|
|
283
569
|
durationMs: Math.max(0, Date.now() - job.startTime),
|
|
284
|
-
...(subagent?.description ? { description: sanitizeText(subagent.description) } : {}),
|
|
285
|
-
...(
|
|
286
|
-
|
|
287
|
-
|
|
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 } : {}),
|
|
288
582
|
...(runningTimeoutGuidance ? { guidance: runningTimeoutGuidance } : {}),
|
|
289
583
|
};
|
|
290
584
|
}
|
|
291
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
|
+
|
|
292
615
|
#missingSnapshot(id: string, status: "not_found", guidance: string): SubagentSnapshot {
|
|
293
616
|
return {
|
|
294
617
|
id,
|
|
@@ -303,10 +626,31 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
303
626
|
}
|
|
304
627
|
}
|
|
305
628
|
|
|
629
|
+
function isTerminalStatus(status: SubagentStatus): boolean {
|
|
630
|
+
return status === "completed" || status === "failed" || status === "cancelled";
|
|
631
|
+
}
|
|
632
|
+
|
|
306
633
|
function isSubagentJob(job: AsyncJob): boolean {
|
|
307
634
|
return job.type === "task" && job.metadata?.subagent !== undefined;
|
|
308
635
|
}
|
|
309
636
|
|
|
310
|
-
function sanitizeText(text: string): string {
|
|
311
|
-
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 };
|
|
312
656
|
}
|