@gajae-code/coding-agent 0.2.4 → 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 +27 -0
- package/README.md +1 -1
- package/dist/types/async/job-manager.d.ts +145 -2
- package/dist/types/commands/harness.d.ts +37 -0
- package/dist/types/config/settings-schema.d.ts +13 -3
- package/dist/types/config/settings.d.ts +3 -1
- package/dist/types/deep-interview/render-middleware.d.ts +5 -0
- package/dist/types/discovery/helpers.d.ts +1 -0
- package/dist/types/exec/bash-executor.d.ts +8 -1
- 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/restricted-role-agent-bash.d.ts +2 -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/acp/acp-client-bridge.d.ts +1 -1
- package/dist/types/modes/components/hook-selector.d.ts +1 -0
- package/dist/types/modes/components/skill-hud/render.d.ts +1 -1
- package/dist/types/modes/interactive-mode.d.ts +2 -0
- package/dist/types/modes/theme/defaults/index.d.ts +45 -9477
- package/dist/types/modes/theme/theme.d.ts +1 -5
- 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 +8 -0
- package/dist/types/session/streaming-output.d.ts +11 -0
- package/dist/types/skill-state/active-state.d.ts +3 -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 +56 -3
- package/dist/types/tools/bash-allowed-prefixes.d.ts +5 -0
- package/dist/types/tools/bash.d.ts +24 -0
- package/dist/types/tools/cron.d.ts +110 -0
- package/dist/types/tools/index.d.ts +4 -0
- package/dist/types/tools/monitor.d.ts +54 -0
- package/dist/types/tools/subagent.d.ts +11 -1
- package/dist/types/web/search/index.d.ts +1 -0
- package/dist/types/web/search/provider.d.ts +11 -4
- package/dist/types/web/search/providers/duckduckgo.d.ts +57 -0
- package/dist/types/web/search/types.d.ts +1 -1
- package/package.json +7 -7
- package/src/async/job-manager.ts +522 -6
- package/src/cli/agents-cli.ts +3 -0
- 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 +15 -2
- package/src/config/settings.ts +49 -7
- package/src/deep-interview/render-middleware.ts +366 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +9 -2
- package/src/defaults/gjc/skills/ralplan/SKILL.md +8 -4
- package/src/defaults/gjc/skills/team/SKILL.md +47 -21
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +78 -11
- package/src/discovery/helpers.ts +5 -0
- package/src/eval/js/shared/rewrite-imports.ts +1 -2
- package/src/exec/bash-executor.ts +20 -9
- 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 +27 -10
- package/src/gjc-runtime/restricted-role-agent-bash.ts +5 -0
- 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 +25 -42
- package/src/internal-urls/docs-index.generated.ts +6 -4
- package/src/lsp/render.ts +1 -1
- package/src/modes/acp/acp-agent.ts +1 -1
- package/src/modes/acp/acp-client-bridge.ts +1 -1
- package/src/modes/components/agent-dashboard.ts +1 -1
- package/src/modes/components/assistant-message.ts +5 -1
- package/src/modes/components/diff.ts +2 -2
- package/src/modes/components/hook-selector.ts +72 -2
- package/src/modes/components/skill-hud/render.ts +7 -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 +19 -3
- package/src/modes/controllers/selector-controller.ts +3 -2
- package/src/modes/interactive-mode.ts +21 -2
- package/src/modes/theme/defaults/index.ts +0 -196
- package/src/modes/theme/theme.ts +35 -35
- package/src/modes/types.ts +2 -0
- package/src/prompts/agents/architect.md +5 -1
- package/src/prompts/agents/critic.md +5 -1
- package/src/prompts/agents/executor.md +13 -0
- package/src/prompts/agents/frontmatter.md +1 -0
- package/src/prompts/agents/planner.md +5 -1
- package/src/prompts/tools/bash.md +9 -0
- package/src/prompts/tools/cron.md +25 -0
- package/src/prompts/tools/monitor.md +30 -0
- package/src/prompts/tools/subagent.md +33 -3
- package/src/runtime-mcp/oauth-flow.ts +4 -2
- package/src/sdk.ts +7 -0
- package/src/session/agent-session.ts +247 -38
- package/src/session/session-manager.ts +13 -1
- package/src/session/streaming-output.ts +21 -0
- package/src/skill-state/active-state.ts +222 -78
- 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/agents.ts +1 -0
- package/src/task/executor.ts +51 -8
- package/src/task/index.ts +120 -8
- package/src/task/render.ts +6 -3
- package/src/task/types.ts +57 -3
- package/src/tools/ask.ts +28 -7
- package/src/tools/bash-allowed-prefixes.ts +169 -0
- package/src/tools/bash.ts +190 -29
- package/src/tools/browser/tab-worker.ts +1 -1
- package/src/tools/cron.ts +665 -0
- package/src/tools/index.ts +20 -2
- package/src/tools/monitor.ts +136 -0
- package/src/tools/subagent.ts +255 -64
- package/src/vim/engine.ts +3 -3
- package/src/web/search/index.ts +31 -18
- package/src/web/search/provider.ts +57 -12
- package/src/web/search/providers/duckduckgo.ts +279 -0
- package/src/web/search/types.ts +2 -0
- package/src/modes/theme/dark.json +0 -95
- package/src/modes/theme/defaults/alabaster.json +0 -93
- package/src/modes/theme/defaults/amethyst.json +0 -96
- package/src/modes/theme/defaults/anthracite.json +0 -93
- package/src/modes/theme/defaults/basalt.json +0 -91
- package/src/modes/theme/defaults/birch.json +0 -95
- package/src/modes/theme/defaults/dark-abyss.json +0 -91
- package/src/modes/theme/defaults/dark-arctic.json +0 -104
- package/src/modes/theme/defaults/dark-aurora.json +0 -95
- package/src/modes/theme/defaults/dark-catppuccin.json +0 -107
- package/src/modes/theme/defaults/dark-cavern.json +0 -91
- package/src/modes/theme/defaults/dark-copper.json +0 -95
- package/src/modes/theme/defaults/dark-cosmos.json +0 -90
- package/src/modes/theme/defaults/dark-cyberpunk.json +0 -102
- package/src/modes/theme/defaults/dark-dracula.json +0 -98
- package/src/modes/theme/defaults/dark-eclipse.json +0 -91
- package/src/modes/theme/defaults/dark-ember.json +0 -95
- package/src/modes/theme/defaults/dark-equinox.json +0 -90
- package/src/modes/theme/defaults/dark-forest.json +0 -96
- package/src/modes/theme/defaults/dark-github.json +0 -105
- package/src/modes/theme/defaults/dark-gruvbox.json +0 -112
- package/src/modes/theme/defaults/dark-lavender.json +0 -95
- package/src/modes/theme/defaults/dark-lunar.json +0 -89
- package/src/modes/theme/defaults/dark-midnight.json +0 -95
- package/src/modes/theme/defaults/dark-monochrome.json +0 -94
- package/src/modes/theme/defaults/dark-monokai.json +0 -98
- package/src/modes/theme/defaults/dark-nebula.json +0 -90
- package/src/modes/theme/defaults/dark-nord.json +0 -97
- package/src/modes/theme/defaults/dark-ocean.json +0 -101
- package/src/modes/theme/defaults/dark-one.json +0 -100
- package/src/modes/theme/defaults/dark-poimandres.json +0 -141
- package/src/modes/theme/defaults/dark-rainforest.json +0 -91
- package/src/modes/theme/defaults/dark-reef.json +0 -91
- package/src/modes/theme/defaults/dark-retro.json +0 -92
- package/src/modes/theme/defaults/dark-rose-pine.json +0 -96
- package/src/modes/theme/defaults/dark-sakura.json +0 -95
- package/src/modes/theme/defaults/dark-slate.json +0 -95
- package/src/modes/theme/defaults/dark-solarized.json +0 -97
- package/src/modes/theme/defaults/dark-solstice.json +0 -90
- package/src/modes/theme/defaults/dark-starfall.json +0 -91
- package/src/modes/theme/defaults/dark-sunset.json +0 -99
- package/src/modes/theme/defaults/dark-swamp.json +0 -90
- package/src/modes/theme/defaults/dark-synthwave.json +0 -103
- package/src/modes/theme/defaults/dark-taiga.json +0 -91
- package/src/modes/theme/defaults/dark-terminal.json +0 -95
- package/src/modes/theme/defaults/dark-tokyo-night.json +0 -101
- package/src/modes/theme/defaults/dark-tundra.json +0 -91
- package/src/modes/theme/defaults/dark-twilight.json +0 -91
- package/src/modes/theme/defaults/dark-volcanic.json +0 -91
- package/src/modes/theme/defaults/graphite.json +0 -92
- package/src/modes/theme/defaults/light-arctic.json +0 -107
- package/src/modes/theme/defaults/light-aurora-day.json +0 -91
- package/src/modes/theme/defaults/light-canyon.json +0 -91
- package/src/modes/theme/defaults/light-catppuccin.json +0 -106
- package/src/modes/theme/defaults/light-cirrus.json +0 -90
- package/src/modes/theme/defaults/light-coral.json +0 -95
- package/src/modes/theme/defaults/light-cyberpunk.json +0 -96
- package/src/modes/theme/defaults/light-dawn.json +0 -90
- package/src/modes/theme/defaults/light-dunes.json +0 -91
- package/src/modes/theme/defaults/light-eucalyptus.json +0 -95
- package/src/modes/theme/defaults/light-forest.json +0 -100
- package/src/modes/theme/defaults/light-frost.json +0 -95
- package/src/modes/theme/defaults/light-github.json +0 -115
- package/src/modes/theme/defaults/light-glacier.json +0 -91
- package/src/modes/theme/defaults/light-gruvbox.json +0 -108
- package/src/modes/theme/defaults/light-haze.json +0 -90
- package/src/modes/theme/defaults/light-honeycomb.json +0 -95
- package/src/modes/theme/defaults/light-lagoon.json +0 -91
- package/src/modes/theme/defaults/light-lavender.json +0 -95
- package/src/modes/theme/defaults/light-meadow.json +0 -91
- package/src/modes/theme/defaults/light-mint.json +0 -95
- package/src/modes/theme/defaults/light-monochrome.json +0 -101
- package/src/modes/theme/defaults/light-ocean.json +0 -99
- package/src/modes/theme/defaults/light-one.json +0 -99
- package/src/modes/theme/defaults/light-opal.json +0 -91
- package/src/modes/theme/defaults/light-orchard.json +0 -91
- package/src/modes/theme/defaults/light-paper.json +0 -95
- package/src/modes/theme/defaults/light-poimandres.json +0 -141
- package/src/modes/theme/defaults/light-prism.json +0 -90
- package/src/modes/theme/defaults/light-retro.json +0 -98
- package/src/modes/theme/defaults/light-sand.json +0 -95
- package/src/modes/theme/defaults/light-savanna.json +0 -91
- package/src/modes/theme/defaults/light-solarized.json +0 -102
- package/src/modes/theme/defaults/light-soleil.json +0 -90
- package/src/modes/theme/defaults/light-sunset.json +0 -99
- package/src/modes/theme/defaults/light-synthwave.json +0 -98
- package/src/modes/theme/defaults/light-tokyo-night.json +0 -111
- package/src/modes/theme/defaults/light-wetland.json +0 -91
- package/src/modes/theme/defaults/light-zenith.json +0 -89
- package/src/modes/theme/defaults/limestone.json +0 -94
- package/src/modes/theme/defaults/mahogany.json +0 -97
- package/src/modes/theme/defaults/marble.json +0 -93
- package/src/modes/theme/defaults/obsidian.json +0 -91
- package/src/modes/theme/defaults/onyx.json +0 -91
- package/src/modes/theme/defaults/pearl.json +0 -93
- package/src/modes/theme/defaults/porcelain.json +0 -91
- package/src/modes/theme/defaults/quartz.json +0 -96
- package/src/modes/theme/defaults/sandstone.json +0 -95
- package/src/modes/theme/defaults/titanium.json +0 -90
- package/src/modes/theme/light.json +0 -93
package/src/tools/index.ts
CHANGED
|
@@ -31,6 +31,7 @@ import { BashTool } from "./bash";
|
|
|
31
31
|
import { BrowserTool } from "./browser";
|
|
32
32
|
import { CalculatorTool } from "./calculator";
|
|
33
33
|
import { type CheckpointState, CheckpointTool, RewindTool } from "./checkpoint";
|
|
34
|
+
import { CronCreateTool, CronDeleteTool, CronListTool } from "./cron";
|
|
34
35
|
import { DebugTool } from "./debug";
|
|
35
36
|
import { EvalTool } from "./eval";
|
|
36
37
|
import { FindTool } from "./find";
|
|
@@ -41,6 +42,7 @@ import { HindsightRetainTool } from "./hindsight-retain";
|
|
|
41
42
|
import { InspectImageTool } from "./inspect-image";
|
|
42
43
|
import { IrcTool } from "./irc";
|
|
43
44
|
import { JobTool } from "./job";
|
|
45
|
+
import { MonitorTool } from "./monitor";
|
|
44
46
|
import { wrapToolWithMetaNotice } from "./output-meta";
|
|
45
47
|
import { ReadTool } from "./read";
|
|
46
48
|
import { RecipeTool } from "./recipe";
|
|
@@ -69,6 +71,7 @@ export * from "./bash";
|
|
|
69
71
|
export * from "./browser";
|
|
70
72
|
export * from "./calculator";
|
|
71
73
|
export * from "./checkpoint";
|
|
74
|
+
export * from "./cron";
|
|
72
75
|
export * from "./debug";
|
|
73
76
|
export * from "./eval";
|
|
74
77
|
export * from "./find";
|
|
@@ -80,6 +83,7 @@ export * from "./image-gen";
|
|
|
80
83
|
export * from "./inspect-image";
|
|
81
84
|
export * from "./irc";
|
|
82
85
|
export * from "./job";
|
|
86
|
+
export * from "./monitor";
|
|
83
87
|
export * from "./read";
|
|
84
88
|
export * from "./recipe";
|
|
85
89
|
export * from "./render-mermaid";
|
|
@@ -164,6 +168,8 @@ export interface ToolSession {
|
|
|
164
168
|
getToolByName?: (name: string) => AgentTool | undefined;
|
|
165
169
|
/** Agent registry for IRC routing across live sessions. */
|
|
166
170
|
agentRegistry?: AgentRegistry;
|
|
171
|
+
/** Optional restricted bash command prefixes for read-only role agents. */
|
|
172
|
+
bashAllowedPrefixes?: string[];
|
|
167
173
|
/** Get artifacts directory for artifact:// URLs */
|
|
168
174
|
getArtifactsDir?: () => string | null;
|
|
169
175
|
/** Get the ArtifactManager backing this session (shared across parent + subagents). */
|
|
@@ -319,6 +325,10 @@ export const BUILTIN_TOOLS: Record<string, ToolFactory> = {
|
|
|
319
325
|
task: s => TaskTool.create(s),
|
|
320
326
|
subagent: s => new SubagentTool(s),
|
|
321
327
|
job: JobTool.createIf,
|
|
328
|
+
monitor: MonitorTool.createIf,
|
|
329
|
+
CronCreate: CronCreateTool.createIf,
|
|
330
|
+
CronList: CronListTool.createIf,
|
|
331
|
+
CronDelete: CronDeleteTool.createIf,
|
|
322
332
|
recipe: RecipeTool.createIf,
|
|
323
333
|
irc: IrcTool.createIf,
|
|
324
334
|
todo_write: s => new TodoWriteTool(s),
|
|
@@ -466,6 +476,11 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
|
|
466
476
|
const discoveryActive = effectiveDiscoveryMode !== "off";
|
|
467
477
|
|
|
468
478
|
const allTools: Record<string, ToolFactory> = { ...BUILTIN_TOOLS, ...HIDDEN_TOOLS };
|
|
479
|
+
const allToolFactoryEntries = Object.entries(allTools) as Array<[string, ToolFactory]>;
|
|
480
|
+
const allToolsByRequestName = new Map<string, [string, ToolFactory]>();
|
|
481
|
+
for (const [name, factory] of allToolFactoryEntries) {
|
|
482
|
+
allToolsByRequestName.set(name.toLowerCase(), [name, factory]);
|
|
483
|
+
}
|
|
469
484
|
const isToolAllowed = (name: string) => {
|
|
470
485
|
if (name === "goal") return goalEnabled && session.getGoalRuntime !== undefined;
|
|
471
486
|
if (goalStateToolNames.includes(name as (typeof GOAL_MODE_TOOL_NAMES)[number])) return goalEnabled;
|
|
@@ -509,10 +524,13 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
|
|
509
524
|
requestedTools.push("yield");
|
|
510
525
|
}
|
|
511
526
|
|
|
512
|
-
const filteredRequestedTools = requestedTools
|
|
527
|
+
const filteredRequestedTools = requestedTools
|
|
528
|
+
?.map(name => allToolsByRequestName.get(name))
|
|
529
|
+
.filter((entry): entry is [string, ToolFactory] => entry !== undefined)
|
|
530
|
+
.filter(([name]) => isToolAllowed(name));
|
|
513
531
|
const baseEntries =
|
|
514
532
|
filteredRequestedTools !== undefined
|
|
515
|
-
? filteredRequestedTools.filter(name => name !== "resolve")
|
|
533
|
+
? filteredRequestedTools.filter(([name]) => name !== "resolve")
|
|
516
534
|
: [
|
|
517
535
|
...Object.entries(BUILTIN_TOOLS)
|
|
518
536
|
.filter(([name]) => isToolAllowed(name))
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@gajae-code/agent-core";
|
|
2
|
+
import { logger, prompt } from "@gajae-code/utils";
|
|
3
|
+
import * as z from "zod/v4";
|
|
4
|
+
import { AsyncJobManager, isBackgroundJobSupportEnabled } from "../async";
|
|
5
|
+
import monitorDescription from "../prompts/tools/monitor.md" with { type: "text" };
|
|
6
|
+
import { BashTool } from "./bash";
|
|
7
|
+
import type { ToolSession } from "./index";
|
|
8
|
+
import { ToolError } from "./tool-errors";
|
|
9
|
+
|
|
10
|
+
const monitorKindEnum = z.enum(["log", "poll", "watch", "other"]);
|
|
11
|
+
|
|
12
|
+
const monitorSchema = z.object({
|
|
13
|
+
command: z
|
|
14
|
+
.string()
|
|
15
|
+
.describe(
|
|
16
|
+
"Shell command to run as a background monitor. Each stdout line is delivered as a separate task-notification event.",
|
|
17
|
+
),
|
|
18
|
+
kind: monitorKindEnum.describe(
|
|
19
|
+
"Category of monitor. 'log' tails a log file, 'poll' polls a status endpoint, 'watch' watches a directory, 'other' for arbitrary streams.",
|
|
20
|
+
),
|
|
21
|
+
description: z
|
|
22
|
+
.string()
|
|
23
|
+
.describe("Short human-readable description of what is being monitored. Appears in task listings."),
|
|
24
|
+
timeout: z
|
|
25
|
+
.number()
|
|
26
|
+
.min(1)
|
|
27
|
+
.optional()
|
|
28
|
+
.describe(
|
|
29
|
+
"Optional maximum wall-clock seconds the monitor may run before automatic shutdown. Omit for indefinite (subject to session lifetime).",
|
|
30
|
+
),
|
|
31
|
+
persistent: z
|
|
32
|
+
.boolean()
|
|
33
|
+
.optional()
|
|
34
|
+
.describe(
|
|
35
|
+
"Whether to keep the monitor running past the originating turn. Persistent monitors survive until session end or explicit kill via the background-task stop tool.",
|
|
36
|
+
),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export type MonitorParams = z.infer<typeof monitorSchema>;
|
|
40
|
+
|
|
41
|
+
export interface MonitorToolDetails {
|
|
42
|
+
taskId: string;
|
|
43
|
+
kind: z.infer<typeof monitorKindEnum>;
|
|
44
|
+
description: string;
|
|
45
|
+
command: string;
|
|
46
|
+
persistent: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const MONITOR_LABEL_MAX = 120;
|
|
50
|
+
|
|
51
|
+
function buildMonitorLabel(params: MonitorParams): string {
|
|
52
|
+
const base = `[monitor:${params.kind}] ${params.description}`;
|
|
53
|
+
if (base.length <= MONITOR_LABEL_MAX) return base;
|
|
54
|
+
return `${base.slice(0, MONITOR_LABEL_MAX - 3)}...`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export class MonitorTool implements AgentTool<typeof monitorSchema, MonitorToolDetails> {
|
|
58
|
+
readonly name = "monitor";
|
|
59
|
+
readonly label = "Monitor";
|
|
60
|
+
readonly summary = "Start a background monitor that streams stdout lines as task notifications";
|
|
61
|
+
readonly description: string;
|
|
62
|
+
readonly parameters = monitorSchema;
|
|
63
|
+
readonly strict = true;
|
|
64
|
+
readonly loadMode = "discoverable";
|
|
65
|
+
|
|
66
|
+
constructor(private readonly session: ToolSession) {
|
|
67
|
+
this.description = prompt.render(monitorDescription);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
static createIf(session: ToolSession): MonitorTool | null {
|
|
71
|
+
if (!isBackgroundJobSupportEnabled(session.settings)) return null;
|
|
72
|
+
return new MonitorTool(session);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async execute(
|
|
76
|
+
_toolCallId: string,
|
|
77
|
+
params: MonitorParams,
|
|
78
|
+
_signal?: AbortSignal,
|
|
79
|
+
_onUpdate?: AgentToolUpdateCallback<MonitorToolDetails>,
|
|
80
|
+
context?: AgentToolContext,
|
|
81
|
+
): Promise<AgentToolResult<MonitorToolDetails>> {
|
|
82
|
+
const manager = AsyncJobManager.instance();
|
|
83
|
+
if (!manager) {
|
|
84
|
+
throw new ToolError("Async execution is disabled; the monitor tool is unavailable in this session.");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const persistent = params.persistent ?? false;
|
|
88
|
+
const label = buildMonitorLabel(params);
|
|
89
|
+
const ownerId = this.session.getAgentId?.() ?? undefined;
|
|
90
|
+
const bash = new BashTool(this.session);
|
|
91
|
+
let deliveredFirstLine = false;
|
|
92
|
+
const monitorJob = await bash.startMonitorJob(
|
|
93
|
+
{ command: params.command, timeout: params.timeout },
|
|
94
|
+
{
|
|
95
|
+
ownerId,
|
|
96
|
+
label,
|
|
97
|
+
ctx: context,
|
|
98
|
+
onRawLine: (line, jobId) => {
|
|
99
|
+
if (!persistent && deliveredFirstLine) return;
|
|
100
|
+
deliveredFirstLine = true;
|
|
101
|
+
const content = `<task-notification>\nMonitor task ${jobId} (${params.kind}: ${params.description}) emitted:\n${line}\n</task-notification>`;
|
|
102
|
+
const details = { taskId: jobId, kind: params.kind, description: params.description };
|
|
103
|
+
const sendPromise = this.session.sendCustomMessage?.(
|
|
104
|
+
{ customType: "task-notification", content, display: false, attribution: "agent", details },
|
|
105
|
+
{ triggerTurn: true, deliverAs: "followUp" },
|
|
106
|
+
);
|
|
107
|
+
if (sendPromise) {
|
|
108
|
+
void sendPromise.catch(error => {
|
|
109
|
+
logger.warn("Monitor task-notification delivery failed", {
|
|
110
|
+
error: error instanceof Error ? error.message : String(error),
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
} else {
|
|
114
|
+
this.session.steer?.({ customType: "task-notification", content, details });
|
|
115
|
+
}
|
|
116
|
+
if (!persistent) {
|
|
117
|
+
manager.cancel(jobId, ownerId ? { ownerId } : undefined);
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
const startedText = `Monitor started · task ${monitorJob.jobId} · persistent: ${persistent}`;
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
content: [{ type: "text", text: startedText }],
|
|
127
|
+
details: {
|
|
128
|
+
taskId: monitorJob.jobId,
|
|
129
|
+
kind: params.kind,
|
|
130
|
+
description: params.description,
|
|
131
|
+
command: params.command,
|
|
132
|
+
persistent,
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
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
|
}
|