@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/task/executor.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type { AgentEvent, AgentIdentity, AgentTelemetryConfig, ThinkingLevel } f
|
|
|
9
9
|
import { recordHandoff, resolveTelemetry } from "@gajae-code/agent-core";
|
|
10
10
|
import { type JsonSchemaValidationIssue, validateJsonSchemaValue } from "@gajae-code/ai/utils/schema";
|
|
11
11
|
import { logger, prompt, untilAborted } from "@gajae-code/utils";
|
|
12
|
+
import { AsyncJobManager } from "../async";
|
|
12
13
|
import { ModelRegistry } from "../config/model-registry";
|
|
13
14
|
import { resolveModelOverrideWithAuthFallback } from "../config/model-resolver";
|
|
14
15
|
import type { PromptTemplate } from "../config/prompt-templates";
|
|
@@ -112,6 +113,9 @@ export interface ExecutorOptions {
|
|
|
112
113
|
index: number;
|
|
113
114
|
id: string;
|
|
114
115
|
modelOverride?: string | string[];
|
|
116
|
+
runMode?: "initial" | "resume" | "message";
|
|
117
|
+
resumeMessage?: string;
|
|
118
|
+
subagentId?: string;
|
|
115
119
|
/**
|
|
116
120
|
* Active model selector of the parent session, used as an auth-aware fallback
|
|
117
121
|
* if the resolved subagent model has no working credentials. See #985.
|
|
@@ -550,8 +554,8 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
550
554
|
}
|
|
551
555
|
|
|
552
556
|
// Set up artifact paths and write input file upfront if artifacts dir provided
|
|
553
|
-
let subtaskSessionFile: string | undefined;
|
|
554
|
-
if (options.artifactsDir) {
|
|
557
|
+
let subtaskSessionFile: string | undefined = options.sessionFile ?? undefined;
|
|
558
|
+
if (!subtaskSessionFile && options.artifactsDir) {
|
|
555
559
|
subtaskSessionFile = path.join(options.artifactsDir, `${id}.jsonl`);
|
|
556
560
|
}
|
|
557
561
|
|
|
@@ -617,6 +621,8 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
617
621
|
let activeSession: AgentSession | null = null;
|
|
618
622
|
let unsubscribe: (() => void) | null = null;
|
|
619
623
|
let yieldCalled = false;
|
|
624
|
+
let pauseRequested = false;
|
|
625
|
+
let paused = false;
|
|
620
626
|
|
|
621
627
|
// Accumulate usage incrementally from message_end events (no memory for streaming events)
|
|
622
628
|
const accumulatedUsage = {
|
|
@@ -1006,6 +1012,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1006
1012
|
}
|
|
1007
1013
|
}
|
|
1008
1014
|
}
|
|
1015
|
+
paused = (event as { stopReason?: string }).stopReason === "paused";
|
|
1009
1016
|
flushProgress = true;
|
|
1010
1017
|
break;
|
|
1011
1018
|
}
|
|
@@ -1186,16 +1193,34 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1186
1193
|
parentTaskPrefix: id,
|
|
1187
1194
|
agentId: id,
|
|
1188
1195
|
agentDisplayName: agent.name,
|
|
1196
|
+
bashAllowedPrefixes: agent.bashAllowedPrefixes,
|
|
1189
1197
|
enableLsp: lspEnabled,
|
|
1190
1198
|
skipPythonPreflight,
|
|
1191
1199
|
enableMCP,
|
|
1192
1200
|
localProtocolOptions: options.localProtocolOptions,
|
|
1193
1201
|
telemetry: subagentTelemetry,
|
|
1194
1202
|
forkContextSeed: options.forkContextSeed,
|
|
1203
|
+
shouldPause: () => pauseRequested,
|
|
1195
1204
|
}),
|
|
1196
1205
|
);
|
|
1197
1206
|
|
|
1198
1207
|
activeSession = session;
|
|
1208
|
+
const liveSubagentId = options.subagentId ?? id;
|
|
1209
|
+
const manager = AsyncJobManager.instance();
|
|
1210
|
+
if (manager) {
|
|
1211
|
+
manager.registerLiveHandle(liveSubagentId, {
|
|
1212
|
+
requestPause: () => {
|
|
1213
|
+
pauseRequested = true;
|
|
1214
|
+
},
|
|
1215
|
+
injectMessage: async (content, deliverAs) => {
|
|
1216
|
+
if (deliverAs === "nextTurn") {
|
|
1217
|
+
await session.prompt(content, { attribution: "agent" });
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
await session.sendUserMessage(content, { deliverAs });
|
|
1221
|
+
},
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1199
1224
|
|
|
1200
1225
|
// Emit lifecycle start event
|
|
1201
1226
|
if (options.eventBus) {
|
|
@@ -1305,6 +1330,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1305
1330
|
progress.retryState = {
|
|
1306
1331
|
attempt: event.attempt,
|
|
1307
1332
|
maxAttempts: event.maxAttempts,
|
|
1333
|
+
unbounded: event.unbounded,
|
|
1308
1334
|
delayMs: event.delayMs,
|
|
1309
1335
|
errorMessage: event.errorMessage,
|
|
1310
1336
|
startedAtMs: Date.now(),
|
|
@@ -1353,13 +1379,24 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1353
1379
|
);
|
|
1354
1380
|
}
|
|
1355
1381
|
}
|
|
1356
|
-
|
|
1357
|
-
|
|
1382
|
+
const runMode = options.runMode ?? "initial";
|
|
1383
|
+
if (runMode === "message") {
|
|
1384
|
+
await awaitAbortable(session.prompt(options.resumeMessage ?? "", { attribution: "agent" }));
|
|
1385
|
+
await awaitAbortable(session.waitForIdle());
|
|
1386
|
+
} else if (runMode === "resume") {
|
|
1387
|
+
await awaitAbortable(
|
|
1388
|
+
session.prompt("Continue from the paused subagent session state.", { attribution: "agent" }),
|
|
1389
|
+
);
|
|
1390
|
+
await awaitAbortable(session.waitForIdle());
|
|
1391
|
+
} else {
|
|
1392
|
+
await awaitAbortable(session.prompt(task, { attribution: "agent" }));
|
|
1393
|
+
await awaitAbortable(session.waitForIdle());
|
|
1394
|
+
}
|
|
1358
1395
|
|
|
1359
1396
|
const reminderToolChoice = buildNamedToolChoice("yield", session.model);
|
|
1360
1397
|
|
|
1361
1398
|
let retryCount = 0;
|
|
1362
|
-
while (!yieldCalled && retryCount < MAX_YIELD_RETRIES && !abortSignal.aborted) {
|
|
1399
|
+
while (!paused && !yieldCalled && retryCount < MAX_YIELD_RETRIES && !abortSignal.aborted) {
|
|
1363
1400
|
// Skip reminders when the model returned a terminal error (e.g.
|
|
1364
1401
|
// rate-limit cap hit, auth failure). Re-prompting would just
|
|
1365
1402
|
// hit the same wall, multiplying the failure noise without
|
|
@@ -1389,7 +1426,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1389
1426
|
}
|
|
1390
1427
|
|
|
1391
1428
|
await awaitAbortable(session.waitForIdle());
|
|
1392
|
-
if (!yieldCalled && !abortSignal.aborted) {
|
|
1429
|
+
if (!paused && !yieldCalled && !abortSignal.aborted) {
|
|
1393
1430
|
exitCode = 0;
|
|
1394
1431
|
}
|
|
1395
1432
|
|
|
@@ -1405,6 +1442,10 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1405
1442
|
exitCode = 1;
|
|
1406
1443
|
error ??= lastAssistant.errorMessage || "Subagent failed";
|
|
1407
1444
|
}
|
|
1445
|
+
if (paused) {
|
|
1446
|
+
exitCode = 0;
|
|
1447
|
+
error = undefined;
|
|
1448
|
+
}
|
|
1408
1449
|
}
|
|
1409
1450
|
} catch (err) {
|
|
1410
1451
|
exitCode = 1;
|
|
@@ -1420,6 +1461,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1420
1461
|
if (exitCode === 0) exitCode = 1;
|
|
1421
1462
|
}
|
|
1422
1463
|
sessionAbortController.abort();
|
|
1464
|
+
AsyncJobManager.instance()?.removeLiveHandle(options.subagentId ?? id);
|
|
1423
1465
|
if (unsubscribe) {
|
|
1424
1466
|
try {
|
|
1425
1467
|
unsubscribe();
|
|
@@ -1526,7 +1568,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1526
1568
|
? yieldAbortReason
|
|
1527
1569
|
: (done.abortReason ?? (signal?.aborted ? resolveSignalAbortReason() : resolveAbortReasonText()))
|
|
1528
1570
|
: undefined;
|
|
1529
|
-
progress.status = wasAborted ? "aborted" : exitCode === 0 ? "completed" : "failed";
|
|
1571
|
+
progress.status = paused ? "paused" : wasAborted ? "aborted" : exitCode === 0 ? "completed" : "failed";
|
|
1530
1572
|
scheduleProgress(true);
|
|
1531
1573
|
|
|
1532
1574
|
// Emit lifecycle end event after finalization so yield status is reflected
|
|
@@ -1536,7 +1578,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1536
1578
|
agent: agent.name,
|
|
1537
1579
|
agentSource: agent.source,
|
|
1538
1580
|
description: options.description,
|
|
1539
|
-
status: progress.status as "completed" | "failed" | "aborted",
|
|
1581
|
+
status: progress.status as "completed" | "failed" | "aborted" | "paused",
|
|
1540
1582
|
sessionFile: subtaskSessionFile,
|
|
1541
1583
|
index,
|
|
1542
1584
|
});
|
|
@@ -1563,6 +1605,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1563
1605
|
error: exitCode !== 0 && stderr ? stderr : undefined,
|
|
1564
1606
|
aborted: wasAborted,
|
|
1565
1607
|
abortReason: finalAbortReason,
|
|
1608
|
+
paused,
|
|
1566
1609
|
usage: hasUsage ? accumulatedUsage : undefined,
|
|
1567
1610
|
outputPath,
|
|
1568
1611
|
extractedToolData: progress.extractedToolData,
|
package/src/task/index.ts
CHANGED
|
@@ -65,6 +65,18 @@ import {
|
|
|
65
65
|
type WorktreeBaseline,
|
|
66
66
|
} from "./worktree";
|
|
67
67
|
|
|
68
|
+
interface TaskResumeDescriptor {
|
|
69
|
+
toolCallId: string;
|
|
70
|
+
params: TaskParams;
|
|
71
|
+
task: TaskItem & { id: string };
|
|
72
|
+
sessionFile: string | null;
|
|
73
|
+
forkContextSeed?: ForkContextSeed;
|
|
74
|
+
agentSource: AgentDefinition["source"];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function isTaskResumeDescriptor(value: unknown): value is TaskResumeDescriptor {
|
|
78
|
+
return typeof value === "object" && value !== null && "task" in value && "params" in value;
|
|
79
|
+
}
|
|
68
80
|
function renderSubagentUserPrompt(assignment: string, simpleMode: TaskSimpleMode): string {
|
|
69
81
|
return prompt.render(subagentUserPromptTemplate, {
|
|
70
82
|
assignment: assignment.trim(),
|
|
@@ -416,6 +428,50 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
416
428
|
};
|
|
417
429
|
|
|
418
430
|
const maxConcurrency = this.session.settings.get("task.maxConcurrency");
|
|
431
|
+
if (typeof manager.setResumeRunner === "function") {
|
|
432
|
+
manager.setResumeRunner((_subagentId, message, resumeDescriptor) => {
|
|
433
|
+
const descriptor = isTaskResumeDescriptor(resumeDescriptor?.data) ? resumeDescriptor.data : undefined;
|
|
434
|
+
if (!descriptor) return undefined;
|
|
435
|
+
const forkSeeds = descriptor.forkContextSeed
|
|
436
|
+
? new Map([[descriptor.task.id, descriptor.forkContextSeed]])
|
|
437
|
+
: undefined;
|
|
438
|
+
return manager.register(
|
|
439
|
+
"task",
|
|
440
|
+
descriptor.task.id,
|
|
441
|
+
async ({ signal: runSignal }) => {
|
|
442
|
+
const result = await this.#executeSync(
|
|
443
|
+
descriptor.toolCallId,
|
|
444
|
+
{ ...descriptor.params, tasks: [descriptor.task] },
|
|
445
|
+
runSignal,
|
|
446
|
+
undefined,
|
|
447
|
+
[descriptor.task.id],
|
|
448
|
+
forkSeeds,
|
|
449
|
+
{
|
|
450
|
+
runMode: message ? "message" : "resume",
|
|
451
|
+
resumeMessage: message,
|
|
452
|
+
sessionFiles: new Map([[descriptor.task.id, descriptor.sessionFile]]),
|
|
453
|
+
},
|
|
454
|
+
);
|
|
455
|
+
const finalText = result.content.find(part => part.type === "text")?.text ?? "(no output)";
|
|
456
|
+
const singleResult = result.details?.results[0];
|
|
457
|
+
return singleResult?.paused ? { kind: "paused" } : finalText;
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
id: `${descriptor.task.id}-resume-${Snowflake.next()}`,
|
|
461
|
+
ownerId: this.session.getAgentId?.() ?? undefined,
|
|
462
|
+
metadata: {
|
|
463
|
+
subagent: {
|
|
464
|
+
id: descriptor.task.id,
|
|
465
|
+
agent: descriptor.params.agent,
|
|
466
|
+
agentSource: descriptor.agentSource,
|
|
467
|
+
description: descriptor.task.description,
|
|
468
|
+
assignment: descriptor.task.assignment.trim(),
|
|
469
|
+
},
|
|
470
|
+
},
|
|
471
|
+
},
|
|
472
|
+
);
|
|
473
|
+
});
|
|
474
|
+
}
|
|
419
475
|
const semaphore = new Semaphore(maxConcurrency);
|
|
420
476
|
const buildForkContextSeedForTask = async (task: TaskItem): Promise<ForkContextSeed | undefined> => {
|
|
421
477
|
if (task.inheritContext !== true) return undefined;
|
|
@@ -431,6 +487,8 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
431
487
|
});
|
|
432
488
|
};
|
|
433
489
|
const frozenForkSeeds = new Map<string, ForkContextSeed>();
|
|
490
|
+
const parentSessionFileForBatch = this.session.getSessionFile();
|
|
491
|
+
const batchArtifactsDir = parentSessionFileForBatch ? parentSessionFileForBatch.slice(0, -6) : null;
|
|
434
492
|
|
|
435
493
|
for (let i = 0; i < taskItems.length; i++) {
|
|
436
494
|
const taskItem = taskItems[i];
|
|
@@ -449,6 +507,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
449
507
|
const singleParams: TaskParams = { ...params, tasks: [taskItem] };
|
|
450
508
|
const label = uniqueId;
|
|
451
509
|
try {
|
|
510
|
+
const subtaskSessionFile = batchArtifactsDir ? path.join(batchArtifactsDir, `${uniqueId}.jsonl`) : null;
|
|
452
511
|
const jobId = manager.register(
|
|
453
512
|
"task",
|
|
454
513
|
label,
|
|
@@ -478,15 +537,20 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
478
537
|
undefined,
|
|
479
538
|
[uniqueId],
|
|
480
539
|
frozenForkSeeds,
|
|
540
|
+
{
|
|
541
|
+
sessionFiles: new Map([[uniqueId, subtaskSessionFile]]),
|
|
542
|
+
},
|
|
481
543
|
);
|
|
482
544
|
const finalText = result.content.find(part => part.type === "text")?.text ?? "(no output)";
|
|
483
545
|
const singleResult = result.details?.results[0];
|
|
484
546
|
if (progress) {
|
|
485
|
-
progress.status = singleResult?.
|
|
486
|
-
? "
|
|
487
|
-
:
|
|
488
|
-
? "
|
|
489
|
-
:
|
|
547
|
+
progress.status = singleResult?.paused
|
|
548
|
+
? "paused"
|
|
549
|
+
: singleResult?.aborted
|
|
550
|
+
? "aborted"
|
|
551
|
+
: (singleResult?.exitCode ?? 0) === 0
|
|
552
|
+
? "completed"
|
|
553
|
+
: "failed";
|
|
490
554
|
progress.durationMs = singleResult?.durationMs ?? Math.max(0, Date.now() - startedAt);
|
|
491
555
|
progress.tokens = singleResult?.tokens ?? 0;
|
|
492
556
|
progress.contextTokens = singleResult?.contextTokens;
|
|
@@ -517,6 +581,9 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
517
581
|
`Background task batch complete: ${completedJobs}/${taskItems.length} finished.`,
|
|
518
582
|
);
|
|
519
583
|
}
|
|
584
|
+
if (singleResult?.paused) {
|
|
585
|
+
return { kind: "paused" };
|
|
586
|
+
}
|
|
520
587
|
return finalText;
|
|
521
588
|
} catch (error) {
|
|
522
589
|
if (progress) {
|
|
@@ -568,6 +635,31 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
568
635
|
},
|
|
569
636
|
);
|
|
570
637
|
startedJobs.push({ jobId, taskId: taskItem.id });
|
|
638
|
+
if (typeof manager.registerResumeDescriptor === "function") {
|
|
639
|
+
manager.registerResumeDescriptor({
|
|
640
|
+
subagentId: uniqueId,
|
|
641
|
+
ownerId: this.session.getAgentId?.() ?? undefined,
|
|
642
|
+
data: {
|
|
643
|
+
toolCallId: _toolCallId,
|
|
644
|
+
params,
|
|
645
|
+
task: { ...taskItem, id: uniqueId },
|
|
646
|
+
sessionFile: subtaskSessionFile,
|
|
647
|
+
forkContextSeed: frozenForkSeed,
|
|
648
|
+
agentSource: fallbackAgentSource,
|
|
649
|
+
} satisfies TaskResumeDescriptor,
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
if (typeof manager.registerSubagentRecord === "function") {
|
|
653
|
+
manager.registerSubagentRecord({
|
|
654
|
+
subagentId: uniqueId,
|
|
655
|
+
ownerId: this.session.getAgentId?.() ?? undefined,
|
|
656
|
+
currentJobId: jobId,
|
|
657
|
+
historicalJobIds: [],
|
|
658
|
+
status: manager.getJob(jobId)?.status ?? "running",
|
|
659
|
+
sessionFile: subtaskSessionFile,
|
|
660
|
+
resumable: !!batchArtifactsDir,
|
|
661
|
+
});
|
|
662
|
+
}
|
|
571
663
|
} catch (error) {
|
|
572
664
|
const message = error instanceof Error ? error.message : String(error);
|
|
573
665
|
failedSchedules.push(`${taskItem.id}: ${message}`);
|
|
@@ -637,6 +729,11 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
637
729
|
onUpdate?: AgentToolUpdateCallback<TaskToolDetails>,
|
|
638
730
|
preAllocatedIds?: string[],
|
|
639
731
|
prebuiltForkContextSeeds?: ReadonlyMap<string, ForkContextSeed>,
|
|
732
|
+
executionOverrides?: {
|
|
733
|
+
runMode?: "initial" | "resume" | "message";
|
|
734
|
+
resumeMessage?: string;
|
|
735
|
+
sessionFiles?: ReadonlyMap<string, string | null>;
|
|
736
|
+
},
|
|
640
737
|
): Promise<AgentToolResult<TaskToolDetails>> {
|
|
641
738
|
const startTime = Date.now();
|
|
642
739
|
const { agents, projectAgentsDir } = await discoverAgents(this.session.cwd);
|
|
@@ -996,8 +1093,17 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
996
1093
|
});
|
|
997
1094
|
};
|
|
998
1095
|
|
|
999
|
-
const runTask = async (
|
|
1096
|
+
const runTask = async (
|
|
1097
|
+
task: (typeof tasksWithUniqueIds)[number],
|
|
1098
|
+
index: number,
|
|
1099
|
+
overrides?: {
|
|
1100
|
+
runMode?: "initial" | "resume" | "message";
|
|
1101
|
+
resumeMessage?: string;
|
|
1102
|
+
sessionFile?: string | null;
|
|
1103
|
+
},
|
|
1104
|
+
) => {
|
|
1000
1105
|
const forkContextSeed = prebuiltForkContextSeeds?.get(task.id) ?? (await buildForkContextSeed(task));
|
|
1106
|
+
const taskSessionFile = overrides?.sessionFile ?? executionOverrides?.sessionFiles?.get(task.id) ?? null;
|
|
1001
1107
|
if (!isIsolated) {
|
|
1002
1108
|
return runSubprocess({
|
|
1003
1109
|
cwd: this.session.cwd,
|
|
@@ -1008,12 +1114,15 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1008
1114
|
description: task.description,
|
|
1009
1115
|
index,
|
|
1010
1116
|
id: task.id,
|
|
1117
|
+
runMode: overrides?.runMode ?? executionOverrides?.runMode,
|
|
1118
|
+
resumeMessage: overrides?.resumeMessage ?? executionOverrides?.resumeMessage,
|
|
1119
|
+
subagentId: task.id,
|
|
1011
1120
|
taskDepth,
|
|
1012
1121
|
modelOverride,
|
|
1013
1122
|
parentActiveModelPattern,
|
|
1014
1123
|
thinkingLevel: thinkingLevelOverride,
|
|
1015
1124
|
outputSchema: effectiveOutputSchema,
|
|
1016
|
-
sessionFile,
|
|
1125
|
+
sessionFile: taskSessionFile,
|
|
1017
1126
|
persistArtifacts: !!artifactsDir,
|
|
1018
1127
|
artifactsDir: effectiveArtifactsDir,
|
|
1019
1128
|
contextFile: contextFilePath,
|
|
@@ -1063,12 +1172,15 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1063
1172
|
description: task.description,
|
|
1064
1173
|
index,
|
|
1065
1174
|
id: task.id,
|
|
1175
|
+
runMode: overrides?.runMode ?? executionOverrides?.runMode,
|
|
1176
|
+
resumeMessage: overrides?.resumeMessage ?? executionOverrides?.resumeMessage,
|
|
1177
|
+
subagentId: task.id,
|
|
1066
1178
|
taskDepth,
|
|
1067
1179
|
modelOverride,
|
|
1068
1180
|
parentActiveModelPattern,
|
|
1069
1181
|
thinkingLevel: thinkingLevelOverride,
|
|
1070
1182
|
outputSchema: effectiveOutputSchema,
|
|
1071
|
-
sessionFile,
|
|
1183
|
+
sessionFile: taskSessionFile,
|
|
1072
1184
|
persistArtifacts: !!artifactsDir,
|
|
1073
1185
|
artifactsDir: effectiveArtifactsDir,
|
|
1074
1186
|
contextFile: contextFilePath,
|
package/src/task/render.ts
CHANGED
|
@@ -47,6 +47,8 @@ function getStatusIcon(status: AgentProgress["status"], theme: Theme, spinnerFra
|
|
|
47
47
|
return formatStatusIcon("error", theme);
|
|
48
48
|
case "aborted":
|
|
49
49
|
return formatStatusIcon("aborted", theme);
|
|
50
|
+
case "paused":
|
|
51
|
+
return formatStatusIcon("pending", theme);
|
|
50
52
|
}
|
|
51
53
|
}
|
|
52
54
|
|
|
@@ -611,9 +613,10 @@ function renderAgentProgress(
|
|
|
611
613
|
if (progress.retryState && progress.status === "running") {
|
|
612
614
|
const remainingMs = Math.max(0, progress.retryState.startedAtMs + progress.retryState.delayMs - Date.now());
|
|
613
615
|
const waitLabel = remainingMs > 0 ? `in ${formatDuration(remainingMs)}` : "now";
|
|
614
|
-
const
|
|
615
|
-
`
|
|
616
|
-
|
|
616
|
+
const attemptLabel = progress.retryState.unbounded
|
|
617
|
+
? `attempt ${progress.retryState.attempt}`
|
|
618
|
+
: `${progress.retryState.attempt}/${progress.retryState.maxAttempts}`;
|
|
619
|
+
const summary = `retrying ${attemptLabel} ${waitLabel}: ${truncateToWidth(replaceTabs(progress.retryState.errorMessage), 60)}`;
|
|
617
620
|
lines.push(`${continuePrefix}${theme.tree.hook} ${theme.fg("warning", summary)}`);
|
|
618
621
|
} else if (progress.retryFailure && progress.status !== "running") {
|
|
619
622
|
const summary = `auto-retry gave up after ${progress.retryFailure.attempt} attempt${
|
package/src/task/types.ts
CHANGED
|
@@ -53,7 +53,7 @@ export interface SubagentLifecyclePayload {
|
|
|
53
53
|
agent: string;
|
|
54
54
|
agentSource: AgentSource;
|
|
55
55
|
description?: string;
|
|
56
|
-
status: "started" | "completed" | "failed" | "aborted";
|
|
56
|
+
status: "started" | "completed" | "failed" | "aborted" | "paused";
|
|
57
57
|
sessionFile?: string;
|
|
58
58
|
index: number;
|
|
59
59
|
}
|
|
@@ -181,6 +181,7 @@ export interface AgentDefinition {
|
|
|
181
181
|
autoloadSkills?: string[];
|
|
182
182
|
hide?: boolean;
|
|
183
183
|
forkContext?: ForkContextPolicy;
|
|
184
|
+
bashAllowedPrefixes?: string[];
|
|
184
185
|
source: AgentSource;
|
|
185
186
|
filePath?: string;
|
|
186
187
|
}
|
|
@@ -191,7 +192,7 @@ export interface AgentProgress {
|
|
|
191
192
|
id: string;
|
|
192
193
|
agent: string;
|
|
193
194
|
agentSource: AgentSource;
|
|
194
|
-
status: "pending" | "running" | "completed" | "failed" | "aborted";
|
|
195
|
+
status: "pending" | "running" | "completed" | "failed" | "aborted" | "paused";
|
|
195
196
|
task: string;
|
|
196
197
|
assignment?: string;
|
|
197
198
|
description?: string;
|
|
@@ -229,6 +230,7 @@ export interface AgentProgress {
|
|
|
229
230
|
retryState?: {
|
|
230
231
|
attempt: number;
|
|
231
232
|
maxAttempts: number;
|
|
233
|
+
unbounded?: boolean;
|
|
232
234
|
delayMs: number;
|
|
233
235
|
errorMessage: string;
|
|
234
236
|
startedAtMs: number;
|
|
@@ -278,6 +280,7 @@ export interface SingleResult {
|
|
|
278
280
|
error?: string;
|
|
279
281
|
aborted?: boolean;
|
|
280
282
|
abortReason?: string;
|
|
283
|
+
paused?: boolean;
|
|
281
284
|
/** Aggregated usage from the subprocess, accumulated incrementally from message_end events. */
|
|
282
285
|
usage?: Usage;
|
|
283
286
|
/** Output path for the task result */
|
|
@@ -314,8 +317,59 @@ export interface TaskToolDetails {
|
|
|
314
317
|
outputPaths?: string[];
|
|
315
318
|
progress?: AgentProgress[];
|
|
316
319
|
async?: {
|
|
317
|
-
state: "running" | "completed" | "failed";
|
|
320
|
+
state: "running" | "paused" | "queued" | "completed" | "failed";
|
|
318
321
|
jobId: string;
|
|
319
322
|
type: "task";
|
|
320
323
|
};
|
|
321
324
|
}
|
|
325
|
+
/**
|
|
326
|
+
* Persisted per-turn / per-subagent token record (Phase 0 instrumentation).
|
|
327
|
+
*
|
|
328
|
+
* Additive: this does not alter any existing task result shape. It is the
|
|
329
|
+
* durable, model-independent unit the deterministic orchestration-token
|
|
330
|
+
* benchmark (`@gajae-code/orchestration-token-benchmark`) consumes to measure
|
|
331
|
+
* token efficiency without any live-model calls.
|
|
332
|
+
*/
|
|
333
|
+
export interface TaskTokenLog {
|
|
334
|
+
/** Subagent id, or "root" for the orchestrator's own turn. */
|
|
335
|
+
subagentId: string;
|
|
336
|
+
/** Agent name for attribution, when known. */
|
|
337
|
+
agent?: string;
|
|
338
|
+
/** 1-based turn index within the subagent's session. */
|
|
339
|
+
turn: number;
|
|
340
|
+
/** ISO-8601 timestamp the turn completed. */
|
|
341
|
+
at: string;
|
|
342
|
+
/** Cost-bearing input tokens (excludes cache reads), mirrors `Usage.input`. */
|
|
343
|
+
input: number;
|
|
344
|
+
/** Total output tokens for the turn, mirrors `Usage.output`. */
|
|
345
|
+
output: number;
|
|
346
|
+
/** Tokens read from the prompt cache, mirrors `Usage.cacheRead`. */
|
|
347
|
+
cacheRead: number;
|
|
348
|
+
/** Tokens written to the prompt cache, mirrors `Usage.cacheWrite`. */
|
|
349
|
+
cacheWrite: number;
|
|
350
|
+
/** input + output + cacheRead + cacheWrite. */
|
|
351
|
+
totalTokens: number;
|
|
352
|
+
/** Latest per-turn context-window occupancy, when known. */
|
|
353
|
+
contextTokens?: number;
|
|
354
|
+
/** Estimated USD cost for the turn, when known. */
|
|
355
|
+
cost?: number;
|
|
356
|
+
/** Model id used for the turn, when known. */
|
|
357
|
+
model?: string;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Deterministic aggregate token metrics computed from a set of `TaskTokenLog`
|
|
362
|
+
* entries. The cache-hit-rate field is the primary prompt-cache signal called
|
|
363
|
+
* out by the prefix-stability invariant (see the approved plan).
|
|
364
|
+
*/
|
|
365
|
+
export interface TaskTokenMetrics {
|
|
366
|
+
/** Number of token-log entries aggregated. */
|
|
367
|
+
turns: number;
|
|
368
|
+
inputTokens: number;
|
|
369
|
+
outputTokens: number;
|
|
370
|
+
cacheReadTokens: number;
|
|
371
|
+
cacheWriteTokens: number;
|
|
372
|
+
totalTokens: number;
|
|
373
|
+
/** cacheRead / (input + cacheRead); 0 when there is no input-class traffic. */
|
|
374
|
+
cacheHitRate: number;
|
|
375
|
+
}
|
package/src/tools/ask.ts
CHANGED
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
} from "@gajae-code/tui";
|
|
29
29
|
import { prompt, untilAborted } from "@gajae-code/utils";
|
|
30
30
|
import * as z from "zod/v4";
|
|
31
|
+
import { formatDeepInterviewSelectorPrompt, renderDeepInterviewAskQuestion } from "../deep-interview/render-middleware";
|
|
31
32
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
32
33
|
import { getMarkdownTheme, type Theme, theme } from "../modes/theme/theme";
|
|
33
34
|
import askDescription from "../prompts/tools/ask.md" with { type: "text" };
|
|
@@ -84,6 +85,7 @@ export interface AskToolDetails {
|
|
|
84
85
|
|
|
85
86
|
const OTHER_OPTION = "Other (type your own)";
|
|
86
87
|
const RECOMMENDED_SUFFIX = " (Recommended)";
|
|
88
|
+
const DEEP_INTERVIEW_SELECTOR_SCROLL_TITLE_ROWS = 12;
|
|
87
89
|
|
|
88
90
|
function getDoneOptionLabel(): string {
|
|
89
91
|
return `${theme.status.success} Done selecting`;
|
|
@@ -138,6 +140,7 @@ interface AskSingleQuestionOptions {
|
|
|
138
140
|
signal?: AbortSignal;
|
|
139
141
|
initialSelection?: Pick<SelectionResult, "selectedOptions" | "customInput">;
|
|
140
142
|
navigation?: NavigationControls;
|
|
143
|
+
scrollTitleRows?: number;
|
|
141
144
|
}
|
|
142
145
|
|
|
143
146
|
interface UIContext {
|
|
@@ -150,6 +153,7 @@ interface UIContext {
|
|
|
150
153
|
signal?: AbortSignal;
|
|
151
154
|
outline?: boolean;
|
|
152
155
|
wrapFocused?: boolean;
|
|
156
|
+
scrollTitleRows?: number;
|
|
153
157
|
onTimeout?: () => void;
|
|
154
158
|
onLeft?: () => void;
|
|
155
159
|
onRight?: () => void;
|
|
@@ -171,7 +175,7 @@ async function askSingleQuestion(
|
|
|
171
175
|
multi: boolean,
|
|
172
176
|
options: AskSingleQuestionOptions = {},
|
|
173
177
|
): Promise<SelectionResult> {
|
|
174
|
-
const { recommended, timeout, signal, initialSelection, navigation } = options;
|
|
178
|
+
const { recommended, timeout, signal, initialSelection, navigation, scrollTitleRows } = options;
|
|
175
179
|
const doneLabel = getDoneOptionLabel();
|
|
176
180
|
let selectedOptions = [...(initialSelection?.selectedOptions ?? [])];
|
|
177
181
|
let customInput = initialSelection?.customInput;
|
|
@@ -187,15 +191,17 @@ async function askSingleQuestion(
|
|
|
187
191
|
timeoutTriggered = true;
|
|
188
192
|
};
|
|
189
193
|
let navigationAction: "back" | "forward" | undefined;
|
|
190
|
-
const
|
|
194
|
+
const baseHelpText = navigation
|
|
191
195
|
? "up/down navigate enter select ←/→ question esc cancel"
|
|
192
196
|
: "up/down navigate enter select esc cancel";
|
|
197
|
+
const helpText = scrollTitleRows === undefined ? baseHelpText : `${baseHelpText} PgUp/PgDn scroll question`;
|
|
193
198
|
const dialogOptions = {
|
|
194
199
|
initialIndex,
|
|
195
200
|
timeout,
|
|
196
201
|
signal,
|
|
197
202
|
outline: true,
|
|
198
203
|
wrapFocused: true,
|
|
204
|
+
scrollTitleRows,
|
|
199
205
|
onTimeout,
|
|
200
206
|
helpText,
|
|
201
207
|
onLeft: navigation?.allowBack
|
|
@@ -454,9 +460,11 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
454
460
|
) => {
|
|
455
461
|
const optionLabels = q.options.map(o => o.label);
|
|
456
462
|
try {
|
|
463
|
+
const deepInterviewPrompt = formatDeepInterviewSelectorPrompt(q.question);
|
|
464
|
+
const displayQuestion = deepInterviewPrompt ?? q.question;
|
|
457
465
|
const { selectedOptions, customInput, navigation, cancelled, timedOut } = await askSingleQuestion(
|
|
458
466
|
ui,
|
|
459
|
-
|
|
467
|
+
displayQuestion,
|
|
460
468
|
optionLabels,
|
|
461
469
|
q.multi ?? false,
|
|
462
470
|
{
|
|
@@ -465,6 +473,7 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
465
473
|
signal,
|
|
466
474
|
initialSelection: options?.previous,
|
|
467
475
|
navigation: options?.navigation,
|
|
476
|
+
scrollTitleRows: deepInterviewPrompt === null ? undefined : DEEP_INTERVIEW_SELECTOR_SCROLL_TITLE_ROWS,
|
|
468
477
|
},
|
|
469
478
|
);
|
|
470
479
|
return { optionLabels, selectedOptions, customInput, navigation, cancelled, timedOut };
|
|
@@ -659,7 +668,10 @@ export const askToolRenderer = {
|
|
|
659
668
|
container.addChild(
|
|
660
669
|
new Text(` ${uiTheme.fg("dim", qBranch)} ${uiTheme.fg("dim", `[${q.id}]`)}${metaStr}`, 0, 0),
|
|
661
670
|
);
|
|
662
|
-
container.addChild(
|
|
671
|
+
container.addChild(
|
|
672
|
+
renderDeepInterviewAskQuestion(q.question, uiTheme) ??
|
|
673
|
+
new Markdown(q.question, 3, 0, mdTheme, accentStyle),
|
|
674
|
+
);
|
|
663
675
|
|
|
664
676
|
const qOptions = q.options;
|
|
665
677
|
if (qOptions?.length) {
|
|
@@ -688,7 +700,10 @@ export const askToolRenderer = {
|
|
|
688
700
|
if (args.multi) meta.push("multi");
|
|
689
701
|
if (args.options?.length) meta.push(`options:${args.options.length}`);
|
|
690
702
|
container.addChild(new Text(`${label}${formatMeta(meta, uiTheme)}`, 0, 0));
|
|
691
|
-
container.addChild(
|
|
703
|
+
container.addChild(
|
|
704
|
+
renderDeepInterviewAskQuestion(args.question, uiTheme) ??
|
|
705
|
+
new Markdown(args.question, 1, 0, mdTheme, accentStyle),
|
|
706
|
+
);
|
|
692
707
|
|
|
693
708
|
const options = args.options;
|
|
694
709
|
if (options?.length) {
|
|
@@ -752,7 +767,10 @@ export const askToolRenderer = {
|
|
|
752
767
|
container.addChild(
|
|
753
768
|
new Text(` ${uiTheme.fg("dim", branch)} ${statusIcon} ${uiTheme.fg("dim", `[${r.id}]`)}`, 0, 0),
|
|
754
769
|
);
|
|
755
|
-
container.addChild(
|
|
770
|
+
container.addChild(
|
|
771
|
+
renderDeepInterviewAskQuestion(r.question, uiTheme) ??
|
|
772
|
+
new Markdown(r.question, 3, 0, mdTheme, accentStyle),
|
|
773
|
+
);
|
|
756
774
|
|
|
757
775
|
const answerLines: string[] = [];
|
|
758
776
|
for (let j = 0; j < r.selectedOptions.length; j++) {
|
|
@@ -795,7 +813,10 @@ export const askToolRenderer = {
|
|
|
795
813
|
const header = renderStatusLine({ icon: hasSelection ? "success" : "warning", title: "Ask" }, uiTheme);
|
|
796
814
|
const container = new Container();
|
|
797
815
|
container.addChild(new Text(header, 0, 0));
|
|
798
|
-
container.addChild(
|
|
816
|
+
container.addChild(
|
|
817
|
+
renderDeepInterviewAskQuestion(details.question, uiTheme) ??
|
|
818
|
+
new Markdown(details.question, 1, 0, mdTheme, accentStyle),
|
|
819
|
+
);
|
|
799
820
|
|
|
800
821
|
const answerLines: string[] = [];
|
|
801
822
|
if (details.selectedOptions && details.selectedOptions.length > 0) {
|