@gajae-code/coding-agent 0.2.5 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/dist/types/async/job-manager.d.ts +84 -2
- package/dist/types/commands/harness.d.ts +37 -0
- package/dist/types/config/settings-schema.d.ts +6 -0
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/deep-interview/render-middleware.d.ts +5 -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/state-graph.d.ts +4 -0
- package/dist/types/gjc-runtime/state-migrations.d.ts +24 -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-validation.d.ts +6 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +137 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +81 -7
- 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 +30 -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 +2 -29
- package/dist/types/modes/components/hook-selector.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -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 +8 -0
- 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 +24 -0
- package/dist/types/task/executor.d.ts +3 -0
- package/dist/types/task/types.d.ts +55 -3
- package/dist/types/tools/subagent.d.ts +11 -1
- package/package.json +7 -7
- package/src/async/job-manager.ts +298 -6
- 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/harness.ts +592 -0
- package/src/commands/team.ts +36 -39
- package/src/config/settings-schema.ts +7 -0
- package/src/config/settings.ts +5 -0
- package/src/deep-interview/render-middleware.ts +366 -0
- package/src/defaults/gjc/skills/team/SKILL.md +47 -21
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +78 -11
- 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/deep-interview-runtime.ts +40 -21
- package/src/gjc-runtime/goal-mode-request.ts +11 -3
- package/src/gjc-runtime/ralplan-runtime.ts +25 -10
- package/src/gjc-runtime/state-graph.ts +86 -0
- package/src/gjc-runtime/state-migrations.ts +132 -0
- package/src/gjc-runtime/state-renderer.ts +345 -0
- package/src/gjc-runtime/state-runtime.ts +733 -21
- package/src/gjc-runtime/state-validation.ts +49 -0
- package/src/gjc-runtime/state-writer.ts +718 -0
- package/src/gjc-runtime/team-runtime.ts +1083 -89
- package/src/gjc-runtime/ultragoal-runtime.ts +348 -19
- package/src/gjc-runtime/workflow-manifest.generated.json +1497 -0
- package/src/gjc-runtime/workflow-manifest.ts +425 -0
- package/src/harness-control-plane/classifier.ts +128 -0
- package/src/harness-control-plane/control-endpoint.ts +137 -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 +553 -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 +97 -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 +24 -41
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/modes/components/assistant-message.ts +5 -1
- package/src/modes/components/hook-selector.ts +72 -2
- package/src/modes/controllers/event-controller.ts +71 -6
- package/src/modes/controllers/extension-ui-controller.ts +6 -0
- package/src/modes/controllers/input-controller.ts +9 -1
- package/src/modes/controllers/selector-controller.ts +2 -1
- package/src/modes/interactive-mode.ts +1 -0
- package/src/modes/types.ts +1 -0
- package/src/prompts/agents/executor.md +13 -0
- package/src/prompts/tools/subagent.md +33 -3
- package/src/sdk.ts +4 -0
- package/src/session/agent-session.ts +231 -33
- 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 +91 -13
- package/src/skill-state/initial-phase.ts +2 -0
- package/src/skill-state/workflow-state-contract.ts +26 -0
- package/src/task/executor.ts +50 -8
- package/src/task/index.ts +120 -8
- package/src/task/render.ts +6 -3
- package/src/task/types.ts +56 -3
- package/src/tools/ask.ts +28 -7
- package/src/tools/subagent.ts +255 -64
package/src/tools/subagent.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@gajae-code/agent-core";
|
|
2
2
|
import { prompt } from "@gajae-code/utils";
|
|
3
3
|
import * as z from "zod/v4";
|
|
4
|
-
import { type AsyncJob, AsyncJobManager } from "../async";
|
|
4
|
+
import { type AsyncJob, AsyncJobManager, type SubagentRecord } from "../async";
|
|
5
5
|
import subagentDescription from "../prompts/tools/subagent.md" with { type: "text" };
|
|
6
6
|
import type { AgentSource } from "../task/types";
|
|
7
7
|
import { Ellipsis, truncateToWidth } from "../tui";
|
|
@@ -16,14 +16,26 @@ const MAX_LIST_LIMIT = 50;
|
|
|
16
16
|
const TEXT_PREVIEW_WIDTH = 12_000;
|
|
17
17
|
|
|
18
18
|
const subagentSchema = z.object({
|
|
19
|
-
action: z
|
|
19
|
+
action: z
|
|
20
|
+
.enum(["list", "inspect", "await", "cancel", "pause", "resume", "steer"])
|
|
21
|
+
.describe("subagent control action"),
|
|
20
22
|
ids: z.array(z.string()).optional().describe("subagent ids or backing job ids"),
|
|
23
|
+
message: z.string().optional().describe("message to deliver when resuming or steering a subagent"),
|
|
24
|
+
pause: z.boolean().optional().describe("pause after steering a currently running subagent"),
|
|
21
25
|
timeout_ms: z.number().min(0).max(MAX_AWAIT_TIMEOUT_MS).optional().describe("await timeout in milliseconds"),
|
|
22
26
|
limit: z.number().min(1).max(MAX_LIST_LIMIT).optional().describe("maximum subagents to return"),
|
|
23
27
|
});
|
|
24
28
|
|
|
25
29
|
type SubagentParams = z.infer<typeof subagentSchema>;
|
|
26
|
-
type SubagentStatus =
|
|
30
|
+
type SubagentStatus =
|
|
31
|
+
| "running"
|
|
32
|
+
| "paused"
|
|
33
|
+
| "queued"
|
|
34
|
+
| "completed"
|
|
35
|
+
| "failed"
|
|
36
|
+
| "cancelled"
|
|
37
|
+
| "not_found"
|
|
38
|
+
| "already_completed";
|
|
27
39
|
|
|
28
40
|
export interface SubagentSnapshot {
|
|
29
41
|
id: string;
|
|
@@ -77,17 +89,17 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
77
89
|
const limit = Math.min(MAX_LIST_LIMIT, Math.max(1, Math.floor(params.limit ?? DEFAULT_LIST_LIMIT)));
|
|
78
90
|
|
|
79
91
|
if (params.action === "list") {
|
|
80
|
-
const
|
|
81
|
-
return this.#
|
|
92
|
+
const records = this.#listSubagentRecords(manager, ownerFilter, limit);
|
|
93
|
+
return this.#buildRecordResult(manager, records, { title: "Subagents" });
|
|
82
94
|
}
|
|
83
95
|
|
|
84
96
|
if (params.action === "inspect") {
|
|
85
|
-
const
|
|
86
|
-
? this.#
|
|
87
|
-
: manager
|
|
88
|
-
return this.#
|
|
97
|
+
const records = params.ids?.length
|
|
98
|
+
? this.#visibleRecordsByIds(manager, params.ids, ownerFilter)
|
|
99
|
+
: this.#runningRecords(manager, ownerFilter);
|
|
100
|
+
return this.#buildRecordResult(manager, records, {
|
|
89
101
|
title: "Subagent inspection",
|
|
90
|
-
notFoundIds: this.#
|
|
102
|
+
notFoundIds: this.#notFoundRecordIds(manager, params.ids ?? [], ownerFilter),
|
|
91
103
|
});
|
|
92
104
|
}
|
|
93
105
|
|
|
@@ -98,46 +110,146 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
98
110
|
}
|
|
99
111
|
const snapshots: SubagentSnapshot[] = [];
|
|
100
112
|
for (const id of ids) {
|
|
101
|
-
const
|
|
102
|
-
if (!
|
|
113
|
+
const record = this.#findVisibleRecord(manager, id, ownerFilter);
|
|
114
|
+
if (!record) {
|
|
103
115
|
snapshots.push(this.#missingSnapshot(id, "not_found", "No visible detached subagent matches this id."));
|
|
104
116
|
continue;
|
|
105
117
|
}
|
|
106
|
-
|
|
107
|
-
|
|
118
|
+
const cancelled = manager.cancelSubagent(record.subagentId, ownerFilter);
|
|
119
|
+
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));
|
|
122
|
+
}
|
|
123
|
+
return this.#buildSnapshotResult(snapshots, "Subagent cancellation");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (params.action === "pause") {
|
|
127
|
+
const ids = params.ids ?? [];
|
|
128
|
+
if (ids.length === 0) {
|
|
129
|
+
throw new ToolError("`pause` requires at least one subagent id.");
|
|
130
|
+
}
|
|
131
|
+
const snapshots: SubagentSnapshot[] = [];
|
|
132
|
+
for (const id of ids) {
|
|
133
|
+
const record = this.#findVisibleRecord(manager, id, ownerFilter);
|
|
134
|
+
if (!record) {
|
|
135
|
+
snapshots.push(this.#missingSnapshot(id, "not_found", "No visible detached subagent matches this id."));
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
const result = manager.pauseSubagent(record.subagentId, ownerFilter);
|
|
139
|
+
if (!result.ok && result.reason === "not_found") {
|
|
140
|
+
snapshots.push(this.#missingSnapshot(id, "not_found", "No visible detached subagent matches this id."));
|
|
108
141
|
continue;
|
|
109
142
|
}
|
|
110
|
-
|
|
111
|
-
|
|
143
|
+
snapshots.push(
|
|
144
|
+
this.#recordSnapshot(manager, manager.getSubagentRecord(record.subagentId, ownerFilter) ?? record),
|
|
145
|
+
);
|
|
112
146
|
}
|
|
113
|
-
return this.#buildSnapshotResult(snapshots, "Subagent
|
|
147
|
+
return this.#buildSnapshotResult(snapshots, "Subagent pause");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
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
|
+
}
|
|
174
|
+
const result = manager.resumeSubagent(record.subagentId, ownerFilter, params.message);
|
|
175
|
+
if (!result.ok && result.reason === "context_unavailable") throw new ToolError("context unavailable");
|
|
176
|
+
if (!result.ok && result.reason === "not_found") {
|
|
177
|
+
snapshots.push(this.#missingSnapshot(id, "not_found", "No visible detached subagent matches this id."));
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
snapshots.push(
|
|
181
|
+
this.#recordSnapshot(manager, manager.getSubagentRecord(record.subagentId, ownerFilter) ?? record),
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
return this.#buildSnapshotResult(snapshots, "Subagent resume");
|
|
114
185
|
}
|
|
115
186
|
|
|
116
|
-
|
|
187
|
+
if (params.action === "steer") {
|
|
188
|
+
const ids = params.ids ?? [];
|
|
189
|
+
const message = params.message;
|
|
190
|
+
if (ids.length === 0) {
|
|
191
|
+
throw new ToolError("`steer` requires at least one subagent id.");
|
|
192
|
+
}
|
|
193
|
+
if (message === undefined || message.trim() === "") {
|
|
194
|
+
throw new ToolError("`steer` requires a non-empty message.");
|
|
195
|
+
}
|
|
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
|
+
}
|
|
203
|
+
if (!record.sessionFile) throw new ToolError(`Subagent ${record.subagentId} has no session file.`);
|
|
204
|
+
if (record.status === "running") {
|
|
205
|
+
const handle = manager.getLiveHandle(record.subagentId);
|
|
206
|
+
if (!handle) throw new ToolError(`Subagent ${record.subagentId} has no live handle.`);
|
|
207
|
+
await handle.injectMessage(message, "steer");
|
|
208
|
+
if (params.pause === true) manager.pauseSubagent(record.subagentId, ownerFilter);
|
|
209
|
+
} else {
|
|
210
|
+
const result = manager.resumeSubagent(record.subagentId, ownerFilter, message);
|
|
211
|
+
if (!result.ok && result.reason === "context_unavailable") throw new ToolError("context unavailable");
|
|
212
|
+
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;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
snapshots.push(
|
|
220
|
+
this.#recordSnapshot(manager, manager.getSubagentRecord(record.subagentId, ownerFilter) ?? record),
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
return this.#buildSnapshotResult(snapshots, "Subagent steer");
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return this.#awaitSubagents(manager, params, ownerFilter, signal, onUpdate);
|
|
117
227
|
}
|
|
118
228
|
|
|
119
229
|
async #awaitSubagents(
|
|
120
230
|
manager: AsyncJobManager,
|
|
121
231
|
params: SubagentParams,
|
|
122
|
-
ownerId: string | undefined,
|
|
123
232
|
ownerFilter: { ownerId: string } | undefined,
|
|
124
233
|
signal: AbortSignal | undefined,
|
|
125
234
|
onUpdate: AgentToolUpdateCallback<SubagentToolDetails> | undefined,
|
|
126
235
|
): Promise<AgentToolResult<SubagentToolDetails>> {
|
|
127
|
-
const
|
|
128
|
-
? this.#
|
|
129
|
-
: manager
|
|
130
|
-
const notFoundIds = this.#
|
|
131
|
-
if (
|
|
236
|
+
const records = params.ids?.length
|
|
237
|
+
? this.#visibleRecordsByIds(manager, params.ids, ownerFilter)
|
|
238
|
+
: this.#runningRecords(manager, ownerFilter);
|
|
239
|
+
const notFoundIds = this.#notFoundRecordIds(manager, params.ids ?? [], ownerFilter);
|
|
240
|
+
if (records.length === 0) {
|
|
132
241
|
const missing = notFoundIds.map(id =>
|
|
133
242
|
this.#missingSnapshot(id, "not_found", "No visible detached subagent matches this id."),
|
|
134
243
|
);
|
|
135
244
|
return this.#buildSnapshotResult(missing, "Subagent await");
|
|
136
245
|
}
|
|
137
246
|
|
|
138
|
-
const runningJobs =
|
|
247
|
+
const runningJobs = records
|
|
248
|
+
.filter(record => record.status === "running" && record.currentJobId)
|
|
249
|
+
.map(record => manager.getJob(record.currentJobId!))
|
|
250
|
+
.filter((job): job is AsyncJob => job !== undefined);
|
|
139
251
|
if (runningJobs.length === 0) {
|
|
140
|
-
return this.#
|
|
252
|
+
return this.#buildRecordResult(manager, records, { title: "Subagent await", notFoundIds });
|
|
141
253
|
}
|
|
142
254
|
|
|
143
255
|
const timeoutMs = Math.min(
|
|
@@ -148,10 +260,10 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
148
260
|
manager.watchJobs(watchedJobIds);
|
|
149
261
|
const progressTimer = onUpdate
|
|
150
262
|
? setInterval(() => {
|
|
151
|
-
onUpdate(this.#progressResult(manager,
|
|
263
|
+
onUpdate(this.#progressResult(manager, records));
|
|
152
264
|
}, 500)
|
|
153
265
|
: undefined;
|
|
154
|
-
onUpdate?.(this.#progressResult(manager,
|
|
266
|
+
onUpdate?.(this.#progressResult(manager, records));
|
|
155
267
|
|
|
156
268
|
let timedOut = false;
|
|
157
269
|
try {
|
|
@@ -176,70 +288,124 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
176
288
|
if (progressTimer) clearInterval(progressTimer);
|
|
177
289
|
}
|
|
178
290
|
|
|
179
|
-
return this.#
|
|
291
|
+
return this.#buildRecordResult(manager, records, { title: "Subagent await", notFoundIds, timedOut });
|
|
180
292
|
}
|
|
181
293
|
|
|
182
|
-
#
|
|
294
|
+
#mergedRecords(
|
|
183
295
|
manager: AsyncJobManager,
|
|
184
296
|
ownerFilter: { ownerId: string } | undefined,
|
|
185
297
|
limit: number,
|
|
186
|
-
):
|
|
187
|
-
const
|
|
188
|
-
const
|
|
189
|
-
const jobs = [...
|
|
190
|
-
|
|
298
|
+
): SubagentRecord[] {
|
|
299
|
+
const merged = [...manager.getSubagentRecords(ownerFilter)];
|
|
300
|
+
const known = new Set(merged.map(record => record.subagentId));
|
|
301
|
+
const jobs = [...manager.getRunningJobs(ownerFilter), ...manager.getRecentJobs(limit, ownerFilter)].filter(
|
|
302
|
+
isSubagentJob,
|
|
303
|
+
);
|
|
304
|
+
for (const job of jobs) {
|
|
305
|
+
const subagentId = job.metadata?.subagent?.id ?? job.id;
|
|
306
|
+
if (known.has(subagentId)) continue;
|
|
307
|
+
known.add(subagentId);
|
|
308
|
+
merged.push(this.#jobToRecord(job));
|
|
309
|
+
}
|
|
310
|
+
merged.sort((a, b) => {
|
|
311
|
+
const aJob = a.currentJobId ? manager.getJob(a.currentJobId) : undefined;
|
|
312
|
+
const bJob = b.currentJobId ? manager.getJob(b.currentJobId) : undefined;
|
|
313
|
+
return (bJob?.startTime ?? 0) - (aJob?.startTime ?? 0);
|
|
314
|
+
});
|
|
315
|
+
return merged.slice(0, limit);
|
|
191
316
|
}
|
|
192
317
|
|
|
193
|
-
#
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
return this.#dedupeJobs(jobs);
|
|
318
|
+
#listSubagentRecords(
|
|
319
|
+
manager: AsyncJobManager,
|
|
320
|
+
ownerFilter: { ownerId: string } | undefined,
|
|
321
|
+
limit: number,
|
|
322
|
+
): SubagentRecord[] {
|
|
323
|
+
return this.#mergedRecords(manager, ownerFilter, limit);
|
|
200
324
|
}
|
|
201
325
|
|
|
202
|
-
#
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
326
|
+
#runningRecords(manager: AsyncJobManager, ownerFilter: { ownerId: string } | undefined): SubagentRecord[] {
|
|
327
|
+
return this.#mergedRecords(manager, ownerFilter, MAX_LIST_LIMIT).filter(record => record.status === "running");
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/** Synthesize a record from a subagent job that has no registered SubagentRecord (backward compat). */
|
|
331
|
+
#jobToRecord(job: AsyncJob): SubagentRecord {
|
|
332
|
+
return {
|
|
333
|
+
subagentId: job.metadata?.subagent?.id ?? job.id,
|
|
334
|
+
ownerId: job.ownerId,
|
|
335
|
+
currentJobId: job.id,
|
|
336
|
+
historicalJobIds: [],
|
|
337
|
+
status: job.status,
|
|
338
|
+
sessionFile: null,
|
|
339
|
+
resumable: false,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
#findSubagentJob(manager: AsyncJobManager, id: string, ownerId: string | undefined): AsyncJob | undefined {
|
|
344
|
+
const direct = manager.getJob(id);
|
|
206
345
|
if (direct && isSubagentJob(direct) && (!ownerId || direct.ownerId === ownerId)) return direct;
|
|
207
346
|
return manager
|
|
208
347
|
.getAllJobs(ownerId ? { ownerId } : undefined)
|
|
209
|
-
.find(job => isSubagentJob(job) && job.metadata?.subagent?.id ===
|
|
348
|
+
.find(job => isSubagentJob(job) && job.metadata?.subagent?.id === id);
|
|
210
349
|
}
|
|
211
350
|
|
|
212
|
-
#
|
|
213
|
-
|
|
351
|
+
#visibleRecordsByIds(
|
|
352
|
+
manager: AsyncJobManager,
|
|
353
|
+
ids: string[],
|
|
354
|
+
ownerFilter: { ownerId: string } | undefined,
|
|
355
|
+
): SubagentRecord[] {
|
|
356
|
+
const records: SubagentRecord[] = [];
|
|
357
|
+
const seen = new Set<string>();
|
|
358
|
+
for (const id of ids) {
|
|
359
|
+
const record = this.#findVisibleRecord(manager, id, ownerFilter);
|
|
360
|
+
if (!record || seen.has(record.subagentId)) continue;
|
|
361
|
+
seen.add(record.subagentId);
|
|
362
|
+
records.push(record);
|
|
363
|
+
}
|
|
364
|
+
return records;
|
|
214
365
|
}
|
|
215
366
|
|
|
216
|
-
#
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
367
|
+
#findVisibleRecord(
|
|
368
|
+
manager: AsyncJobManager,
|
|
369
|
+
id: string,
|
|
370
|
+
ownerFilter: { ownerId: string } | undefined,
|
|
371
|
+
): SubagentRecord | undefined {
|
|
372
|
+
const trimmedId = id.trim();
|
|
373
|
+
if (!trimmedId) return undefined;
|
|
374
|
+
const direct = manager.getSubagentRecord(trimmedId, ownerFilter);
|
|
375
|
+
if (direct) return direct;
|
|
376
|
+
const byJobId = manager.getSubagentRecords(ownerFilter).find(record => record.currentJobId === trimmedId);
|
|
377
|
+
if (byJobId) return byJobId;
|
|
378
|
+
const job = this.#findSubagentJob(manager, trimmedId, ownerFilter?.ownerId);
|
|
379
|
+
return job ? this.#jobToRecord(job) : undefined;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
#notFoundRecordIds(manager: AsyncJobManager, ids: string[], ownerFilter: { ownerId: string } | undefined): string[] {
|
|
383
|
+
return ids.filter(id => !this.#findVisibleRecord(manager, id, ownerFilter));
|
|
223
384
|
}
|
|
224
385
|
|
|
225
|
-
#progressResult(manager: AsyncJobManager,
|
|
386
|
+
#progressResult(manager: AsyncJobManager, records: SubagentRecord[]): AgentToolResult<SubagentToolDetails> {
|
|
226
387
|
return {
|
|
227
388
|
content: [{ type: "text", text: "" }],
|
|
228
|
-
details: { subagents: this.#
|
|
389
|
+
details: { subagents: this.#recordSnapshots(manager, records) },
|
|
229
390
|
};
|
|
230
391
|
}
|
|
231
392
|
|
|
232
|
-
#
|
|
393
|
+
#buildRecordResult(
|
|
233
394
|
manager: AsyncJobManager,
|
|
234
|
-
|
|
395
|
+
records: SubagentRecord[],
|
|
235
396
|
options: { title: string; notFoundIds?: string[]; timedOut?: boolean },
|
|
236
397
|
): AgentToolResult<SubagentToolDetails> {
|
|
237
|
-
const snapshots = this.#
|
|
398
|
+
const snapshots = this.#recordSnapshots(manager, records, options.timedOut);
|
|
238
399
|
for (const id of options.notFoundIds ?? []) {
|
|
239
400
|
snapshots.push(this.#missingSnapshot(id, "not_found", "No visible detached subagent matches this id."));
|
|
240
401
|
}
|
|
241
402
|
manager.acknowledgeDeliveries(
|
|
242
|
-
snapshots
|
|
403
|
+
snapshots
|
|
404
|
+
.filter(
|
|
405
|
+
s =>
|
|
406
|
+
s.status !== "running" && s.status !== "paused" && s.status !== "queued" && s.status !== "not_found",
|
|
407
|
+
)
|
|
408
|
+
.map(s => s.jobId),
|
|
243
409
|
);
|
|
244
410
|
return this.#buildSnapshotResult(snapshots, options.title);
|
|
245
411
|
}
|
|
@@ -263,8 +429,29 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
263
429
|
};
|
|
264
430
|
}
|
|
265
431
|
|
|
266
|
-
#
|
|
267
|
-
return
|
|
432
|
+
#recordSnapshots(manager: AsyncJobManager, records: SubagentRecord[], timedOut = false): SubagentSnapshot[] {
|
|
433
|
+
return records.map(record => this.#recordSnapshot(manager, record, timedOut));
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
#recordSnapshot(manager: AsyncJobManager, record: SubagentRecord, timedOut = false): SubagentSnapshot {
|
|
437
|
+
const job = record.currentJobId ? manager.getJob(record.currentJobId) : undefined;
|
|
438
|
+
if (job) {
|
|
439
|
+
return {
|
|
440
|
+
...this.#snapshot(job, timedOut),
|
|
441
|
+
id: record.subagentId,
|
|
442
|
+
jobId: record.currentJobId ?? job.id,
|
|
443
|
+
status: record.status,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
return {
|
|
447
|
+
id: record.subagentId,
|
|
448
|
+
jobId: record.currentJobId ?? record.subagentId,
|
|
449
|
+
status: record.status,
|
|
450
|
+
label: "subagent",
|
|
451
|
+
agent: "unknown",
|
|
452
|
+
agentSource: "bundled",
|
|
453
|
+
durationMs: 0,
|
|
454
|
+
};
|
|
268
455
|
}
|
|
269
456
|
|
|
270
457
|
#snapshot(job: AsyncJob, timedOut = false): SubagentSnapshot {
|
|
@@ -303,6 +490,10 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
303
490
|
}
|
|
304
491
|
}
|
|
305
492
|
|
|
493
|
+
function isTerminalStatus(status: SubagentStatus): boolean {
|
|
494
|
+
return status === "completed" || status === "failed" || status === "cancelled";
|
|
495
|
+
}
|
|
496
|
+
|
|
306
497
|
function isSubagentJob(job: AsyncJob): boolean {
|
|
307
498
|
return job.type === "task" && job.metadata?.subagent !== undefined;
|
|
308
499
|
}
|