@gajae-code/coding-agent 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/dist/types/async/job-manager.d.ts +7 -0
- package/dist/types/cli/args.d.ts +1 -1
- package/dist/types/commands/deep-interview.d.ts +3 -0
- package/dist/types/config/keybindings.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +4 -4
- package/dist/types/debug/crash-diagnostics.d.ts +45 -0
- package/dist/types/debug/runtime-gauges.d.ts +6 -0
- package/dist/types/deep-interview/render-middleware.d.ts +1 -0
- package/dist/types/eval/py/executor.d.ts +2 -0
- package/dist/types/eval/py/kernel.d.ts +2 -0
- package/dist/types/exec/bash-executor.d.ts +10 -0
- package/dist/types/gjc-runtime/cli-write-receipt.d.ts +24 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +1 -0
- package/dist/types/gjc-runtime/state-migrations.d.ts +9 -0
- package/dist/types/gjc-runtime/state-schema.d.ts +317 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +10 -0
- package/dist/types/gjc-runtime/workflow-command-ref.d.ts +43 -0
- package/dist/types/harness-control-plane/control-endpoint.d.ts +3 -2
- package/dist/types/hooks/skill-state.d.ts +21 -0
- package/dist/types/internal-urls/agent-protocol.d.ts +2 -2
- package/dist/types/internal-urls/artifact-protocol.d.ts +2 -2
- package/dist/types/internal-urls/registry-helpers.d.ts +8 -7
- package/dist/types/internal-urls/types.d.ts +4 -0
- package/dist/types/lsp/index.d.ts +10 -10
- package/dist/types/modes/bridge/auth.d.ts +12 -0
- package/dist/types/modes/bridge/bridge-client-bridge.d.ts +9 -0
- package/dist/types/modes/bridge/bridge-mode.d.ts +44 -0
- package/dist/types/modes/bridge/bridge-ui-context.d.ts +88 -0
- package/dist/types/modes/bridge/event-stream.d.ts +8 -0
- package/dist/types/modes/components/custom-editor.d.ts +6 -0
- package/dist/types/modes/components/jobs-overlay-model.d.ts +31 -0
- package/dist/types/modes/components/jobs-overlay.d.ts +30 -0
- package/dist/types/modes/components/status-line/types.d.ts +2 -0
- package/dist/types/modes/components/status-line.d.ts +2 -0
- package/dist/types/modes/controllers/input-controller.d.ts +1 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +8 -0
- package/dist/types/modes/index.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/jobs-observer.d.ts +57 -0
- package/dist/types/modes/rpc/host-tools.d.ts +1 -16
- package/dist/types/modes/rpc/host-uris.d.ts +1 -38
- package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +20 -0
- package/dist/types/modes/shared/agent-wire/command-validation.d.ts +2 -0
- package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +24 -0
- package/dist/types/modes/shared/agent-wire/handshake.d.ts +46 -0
- package/dist/types/modes/shared/agent-wire/host-tool-bridge.d.ts +16 -0
- package/dist/types/modes/shared/agent-wire/host-uri-bridge.d.ts +17 -0
- package/dist/types/modes/shared/agent-wire/protocol.d.ts +44 -0
- package/dist/types/modes/shared/agent-wire/responses.d.ts +4 -0
- package/dist/types/modes/shared/agent-wire/scopes.d.ts +18 -0
- package/dist/types/modes/shared/agent-wire/ui-request-broker.d.ts +42 -0
- package/dist/types/modes/shared/agent-wire/ui-result.d.ts +27 -0
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +11 -1
- package/dist/types/skill-state/workflow-state-contract.d.ts +1 -2
- package/dist/types/skill-state/workflow-state-version.d.ts +3 -0
- package/dist/types/task/id.d.ts +7 -0
- package/dist/types/task/index.d.ts +5 -0
- package/dist/types/task/receipt.d.ts +85 -0
- package/dist/types/task/spawn-gate.d.ts +38 -0
- package/dist/types/task/types.d.ts +143 -11
- package/dist/types/tools/cron.d.ts +6 -0
- package/dist/types/tools/index.d.ts +2 -0
- package/dist/types/tools/path-utils.d.ts +1 -0
- package/dist/types/tools/subagent.d.ts +15 -0
- package/package.json +7 -7
- package/scripts/build-binary.ts +7 -0
- package/src/async/job-manager.ts +36 -0
- package/src/cli/args.ts +9 -2
- package/src/commands/deep-interview.ts +1 -0
- package/src/commands/harness.ts +289 -19
- package/src/commands/launch.ts +2 -2
- package/src/commands/state.ts +2 -1
- package/src/commands/team.ts +22 -4
- package/src/config/keybindings.ts +6 -0
- package/src/config/settings-schema.ts +6 -3
- package/src/dap/client.ts +17 -3
- package/src/debug/crash-diagnostics.ts +223 -0
- package/src/debug/runtime-gauges.ts +20 -0
- package/src/deep-interview/render-middleware.ts +6 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
- package/src/defaults/gjc/skills/ralplan/SKILL.md +31 -2
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +28 -2
- package/src/eval/py/executor.ts +21 -1
- package/src/eval/py/kernel.ts +15 -0
- package/src/exec/bash-executor.ts +41 -0
- package/src/gjc-runtime/cli-write-receipt.ts +31 -0
- package/src/gjc-runtime/deep-interview-runtime.ts +69 -32
- package/src/gjc-runtime/ralplan-runtime.ts +213 -36
- package/src/gjc-runtime/state-migrations.ts +54 -7
- package/src/gjc-runtime/state-runtime.ts +461 -64
- package/src/gjc-runtime/state-schema.ts +192 -0
- package/src/gjc-runtime/state-writer.ts +32 -1
- package/src/gjc-runtime/team-runtime.ts +177 -105
- package/src/gjc-runtime/ultragoal-runtime.ts +114 -26
- package/src/gjc-runtime/workflow-command-ref.ts +239 -0
- package/src/gjc-runtime/workflow-manifest.generated.json +108 -4
- package/src/gjc-runtime/workflow-manifest.ts +3 -1
- package/src/harness-control-plane/control-endpoint.ts +19 -8
- package/src/harness-control-plane/owner.ts +57 -10
- package/src/harness-control-plane/state-machine.ts +2 -1
- package/src/hooks/skill-state.ts +176 -26
- package/src/internal-urls/agent-protocol.ts +68 -21
- package/src/internal-urls/artifact-protocol.ts +12 -17
- package/src/internal-urls/docs-index.generated.ts +3 -2
- package/src/internal-urls/registry-helpers.ts +19 -16
- package/src/internal-urls/types.ts +4 -0
- package/src/lsp/client.ts +18 -2
- package/src/main.ts +21 -5
- package/src/modes/bridge/auth.ts +41 -0
- package/src/modes/bridge/bridge-client-bridge.ts +47 -0
- package/src/modes/bridge/bridge-mode.ts +520 -0
- package/src/modes/bridge/bridge-ui-context.ts +200 -0
- package/src/modes/bridge/event-stream.ts +70 -0
- package/src/modes/components/custom-editor.ts +101 -0
- package/src/modes/components/hook-selector.ts +61 -18
- package/src/modes/components/jobs-overlay-model.ts +109 -0
- package/src/modes/components/jobs-overlay.ts +172 -0
- package/src/modes/components/status-line/presets.ts +7 -5
- package/src/modes/components/status-line/segments.ts +25 -0
- package/src/modes/components/status-line/types.ts +2 -0
- package/src/modes/components/status-line.ts +9 -1
- package/src/modes/controllers/extension-ui-controller.ts +39 -3
- package/src/modes/controllers/input-controller.ts +97 -9
- package/src/modes/controllers/selector-controller.ts +29 -0
- package/src/modes/index.ts +1 -0
- package/src/modes/interactive-mode.ts +27 -0
- package/src/modes/jobs-observer.ts +204 -0
- package/src/modes/rpc/host-tools.ts +1 -186
- package/src/modes/rpc/host-uris.ts +1 -235
- package/src/modes/rpc/rpc-client.ts +25 -10
- package/src/modes/rpc/rpc-mode.ts +12 -381
- package/src/modes/shared/agent-wire/command-dispatch.ts +341 -0
- package/src/modes/shared/agent-wire/command-validation.ts +131 -0
- package/src/modes/shared/agent-wire/event-envelope.ts +108 -0
- package/src/modes/shared/agent-wire/handshake.ts +117 -0
- package/src/modes/shared/agent-wire/host-tool-bridge.ts +194 -0
- package/src/modes/shared/agent-wire/host-uri-bridge.ts +236 -0
- package/src/modes/shared/agent-wire/protocol.ts +96 -0
- package/src/modes/shared/agent-wire/responses.ts +17 -0
- package/src/modes/shared/agent-wire/scopes.ts +89 -0
- package/src/modes/shared/agent-wire/ui-request-broker.ts +150 -0
- package/src/modes/shared/agent-wire/ui-result.ts +48 -0
- package/src/modes/types.ts +1 -0
- package/src/prompts/tools/subagent.md +12 -7
- package/src/prompts/tools/task-summary.md +3 -9
- package/src/prompts/tools/task.md +5 -1
- package/src/sdk.ts +4 -0
- package/src/session/agent-session.ts +214 -38
- package/src/skill-state/deep-interview-mutation-guard.ts +23 -4
- package/src/skill-state/workflow-state-contract.ts +7 -4
- package/src/skill-state/workflow-state-version.ts +3 -0
- package/src/slash-commands/builtin-registry.ts +8 -0
- package/src/task/executor.ts +29 -5
- package/src/task/id.ts +33 -0
- package/src/task/index.ts +257 -67
- package/src/task/output-manager.ts +5 -4
- package/src/task/receipt.ts +297 -0
- package/src/task/render.ts +48 -131
- package/src/task/spawn-gate.ts +132 -0
- package/src/task/types.ts +48 -7
- package/src/tools/ask.ts +73 -33
- package/src/tools/ast-edit.ts +1 -0
- package/src/tools/ast-grep.ts +1 -0
- package/src/tools/bash.ts +1 -1
- package/src/tools/cron.ts +48 -0
- package/src/tools/find.ts +4 -1
- package/src/tools/index.ts +2 -0
- package/src/tools/path-utils.ts +3 -2
- package/src/tools/read.ts +1 -0
- package/src/tools/search.ts +1 -0
- package/src/tools/skill.ts +6 -1
- package/src/tools/subagent.ts +237 -84
package/src/task/types.ts
CHANGED
|
@@ -2,12 +2,16 @@ import type { ThinkingLevel } from "@gajae-code/agent-core";
|
|
|
2
2
|
import type { Usage } from "@gajae-code/ai";
|
|
3
3
|
import { $env } from "@gajae-code/utils";
|
|
4
4
|
import * as z from "zod/v4";
|
|
5
|
+
import { isValidTaskId, TASK_ID_DESCRIPTION } from "./id";
|
|
6
|
+
import type { TaskResultReceipt } from "./receipt";
|
|
5
7
|
import { getTaskSimpleModeCapabilities, type TaskSimpleMode } from "./simple-mode";
|
|
8
|
+
import type { SpawnPlanReceipt } from "./spawn-gate";
|
|
6
9
|
import type { NestedRepoPatch } from "./worktree";
|
|
7
10
|
|
|
8
11
|
/** Source of an agent definition */
|
|
9
12
|
export type AgentSource = "bundled" | "user" | "project";
|
|
10
13
|
export type ForkContextPolicy = "forbidden" | "allowed";
|
|
14
|
+
export type ForkContextMode = "none" | "receipt" | "last-turn" | "bounded" | "full";
|
|
11
15
|
|
|
12
16
|
const parseNumber = (value: string | undefined, defaultValue: number): number => {
|
|
13
17
|
if (value) {
|
|
@@ -59,16 +63,27 @@ export interface SubagentLifecyclePayload {
|
|
|
59
63
|
}
|
|
60
64
|
|
|
61
65
|
const assignmentDescription = "per-task instructions; self-contained";
|
|
66
|
+
const spawnPlanSchema = z
|
|
67
|
+
.object({
|
|
68
|
+
whyParallel: z.string(),
|
|
69
|
+
whyNotLocal: z.string(),
|
|
70
|
+
independence: z.string(),
|
|
71
|
+
expectedReceiptShape: z.string(),
|
|
72
|
+
maxInlineTokens: z.number(),
|
|
73
|
+
})
|
|
74
|
+
.describe("justification required before spawning more than four tasks or reviewer-spawned explore tasks");
|
|
62
75
|
|
|
63
76
|
const createTaskItemSchema = (_contextEnabled: boolean) =>
|
|
64
77
|
z.object({
|
|
65
|
-
id: z.string().max(48).describe("
|
|
78
|
+
id: z.string().max(48).refine(isValidTaskId, TASK_ID_DESCRIPTION).describe("filesystem-safe task identifier"),
|
|
66
79
|
description: z.string().describe("ui label, not seen by subagent"),
|
|
67
80
|
assignment: z.string().describe(assignmentDescription),
|
|
68
81
|
inheritContext: z
|
|
69
|
-
.
|
|
82
|
+
.enum(["none", "receipt", "last-turn", "bounded", "full"])
|
|
70
83
|
.optional()
|
|
71
|
-
.describe(
|
|
84
|
+
.describe(
|
|
85
|
+
"fork-context mode: none/omitted copies no parent context; receipt copies a minimal receipt-sized snapshot; last-turn copies only the latest exchange; bounded copies the bounded default snapshot; full copies a larger sanitized snapshot up to the configured/model token cap",
|
|
86
|
+
),
|
|
72
87
|
});
|
|
73
88
|
|
|
74
89
|
/** Single task item for parallel execution (default shape with context enabled). */
|
|
@@ -77,11 +92,23 @@ export type TaskItem = z.infer<typeof taskItemSchema>;
|
|
|
77
92
|
|
|
78
93
|
const createTaskSchema = (options: { isolationEnabled: boolean; simpleMode: TaskSimpleMode }) => {
|
|
79
94
|
const { contextEnabled, customSchemaEnabled } = getTaskSimpleModeCapabilities(options.simpleMode);
|
|
80
|
-
|
|
95
|
+
let itemSchema = createTaskItemSchema(contextEnabled);
|
|
96
|
+
if (!contextEnabled) {
|
|
97
|
+
itemSchema = itemSchema.superRefine((item, ctx) => {
|
|
98
|
+
if (item.inheritContext !== undefined && item.inheritContext !== "none") {
|
|
99
|
+
ctx.addIssue({
|
|
100
|
+
code: "custom",
|
|
101
|
+
path: ["inheritContext"],
|
|
102
|
+
message: "Independent tasks cannot inherit parent context; omit inheritContext or set it to none.",
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
81
107
|
|
|
82
108
|
let schema = z.object({
|
|
83
109
|
agent: z.string().describe("agent type"),
|
|
84
110
|
tasks: z.array(itemSchema).describe("tasks to execute in parallel"),
|
|
111
|
+
spawnPlan: spawnPlanSchema.optional(),
|
|
85
112
|
});
|
|
86
113
|
if (contextEnabled) {
|
|
87
114
|
schema = schema.extend({
|
|
@@ -139,6 +166,7 @@ export interface TaskParams {
|
|
|
139
166
|
agent: string;
|
|
140
167
|
context?: string;
|
|
141
168
|
schema?: string;
|
|
169
|
+
spawnPlan?: SpawnPlanReceipt;
|
|
142
170
|
tasks: TaskItem[];
|
|
143
171
|
isolated?: boolean;
|
|
144
172
|
}
|
|
@@ -291,6 +319,8 @@ export interface SingleResult {
|
|
|
291
319
|
branchName?: string;
|
|
292
320
|
/** Nested repo patches to apply after parent merge */
|
|
293
321
|
nestedPatches?: NestedRepoPatch[];
|
|
322
|
+
/** Whether isolated execution produced a non-empty root or nested patch. */
|
|
323
|
+
producedChanges?: boolean;
|
|
294
324
|
/** Data extracted by registered subprocess tool handlers (keyed by tool name) */
|
|
295
325
|
extractedToolData?: Record<string, unknown[]>;
|
|
296
326
|
/**
|
|
@@ -304,17 +334,28 @@ export interface SingleResult {
|
|
|
304
334
|
errorMessage: string;
|
|
305
335
|
};
|
|
306
336
|
/** Output metadata for agent:// URL integration */
|
|
307
|
-
outputMeta?: { lineCount: number; charCount: number };
|
|
337
|
+
outputMeta?: { lineCount: number; charCount: number; byteSize?: number; sha256?: string };
|
|
338
|
+
/** Fork-context seed accounting for this subagent, when inherited parent context was cloned. */
|
|
339
|
+
forkContext?: { mode: ForkContextMode; clonedTokens: number };
|
|
308
340
|
}
|
|
309
341
|
|
|
310
342
|
/** Tool details for TUI rendering */
|
|
311
343
|
export interface TaskToolDetails {
|
|
312
344
|
projectAgentsDir: string | null;
|
|
313
|
-
results:
|
|
345
|
+
results: TaskResultReceipt[];
|
|
314
346
|
totalDurationMs: number;
|
|
315
347
|
/** Aggregated usage across all subagents. */
|
|
316
348
|
usage?: Usage;
|
|
317
|
-
|
|
349
|
+
/** Aggregate cloned tokens copied into fork-context seeds across subagents. */
|
|
350
|
+
forkContextClonedTokens?: number;
|
|
351
|
+
roiSummary?: {
|
|
352
|
+
childCount: number;
|
|
353
|
+
totalTokens: number;
|
|
354
|
+
totalCostTotal?: number;
|
|
355
|
+
totalClonedTokens?: number;
|
|
356
|
+
/** Advisory ids for terminal children that spent tokens without detectable output/review/changes. */
|
|
357
|
+
lowRoiChildIds: string[];
|
|
358
|
+
};
|
|
318
359
|
progress?: AgentProgress[];
|
|
319
360
|
async?: {
|
|
320
361
|
state: "running" | "paused" | "queued" | "completed" | "failed";
|
package/src/tools/ask.ts
CHANGED
|
@@ -28,7 +28,11 @@ 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 {
|
|
31
|
+
import {
|
|
32
|
+
formatDeepInterviewSelectorPrompt,
|
|
33
|
+
isDeepInterviewAskQuestion,
|
|
34
|
+
renderDeepInterviewAskQuestion,
|
|
35
|
+
} from "../deep-interview/render-middleware";
|
|
32
36
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
33
37
|
import { getMarkdownTheme, type Theme, theme } from "../modes/theme/theme";
|
|
34
38
|
import askDescription from "../prompts/tools/ask.md" with { type: "text" };
|
|
@@ -85,7 +89,7 @@ export interface AskToolDetails {
|
|
|
85
89
|
|
|
86
90
|
const OTHER_OPTION = "Other (type your own)";
|
|
87
91
|
const RECOMMENDED_SUFFIX = " (Recommended)";
|
|
88
|
-
const DEEP_INTERVIEW_SELECTOR_SCROLL_TITLE_ROWS =
|
|
92
|
+
const DEEP_INTERVIEW_SELECTOR_SCROLL_TITLE_ROWS = Number.MAX_SAFE_INTEGER;
|
|
89
93
|
|
|
90
94
|
function getDoneOptionLabel(): string {
|
|
91
95
|
return `${theme.status.success} Done selecting`;
|
|
@@ -117,6 +121,17 @@ function stripRecommendedSuffix(label: string): string {
|
|
|
117
121
|
return label.endsWith(RECOMMENDED_SUFFIX) ? label.slice(0, -RECOMMENDED_SUFFIX.length) : label;
|
|
118
122
|
}
|
|
119
123
|
|
|
124
|
+
function formatNumberedOptionLabel(label: string, index: number): string {
|
|
125
|
+
if (/^\s*\d+[.)]\s+/.test(label)) {
|
|
126
|
+
return label;
|
|
127
|
+
}
|
|
128
|
+
return `${index + 1}. ${label}`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function numberOptionLabels(labels: string[]): string[] {
|
|
132
|
+
return labels.map(formatNumberedOptionLabel);
|
|
133
|
+
}
|
|
134
|
+
|
|
120
135
|
// =============================================================================
|
|
121
136
|
// Question Selection Logic
|
|
122
137
|
// =============================================================================
|
|
@@ -141,6 +156,7 @@ interface AskSingleQuestionOptions {
|
|
|
141
156
|
initialSelection?: Pick<SelectionResult, "selectedOptions" | "customInput">;
|
|
142
157
|
navigation?: NavigationControls;
|
|
143
158
|
scrollTitleRows?: number;
|
|
159
|
+
otherOptionLabel?: string;
|
|
144
160
|
}
|
|
145
161
|
|
|
146
162
|
interface UIContext {
|
|
@@ -194,7 +210,8 @@ async function askSingleQuestion(
|
|
|
194
210
|
const baseHelpText = navigation
|
|
195
211
|
? "up/down navigate enter select ←/→ question esc cancel"
|
|
196
212
|
: "up/down navigate enter select esc cancel";
|
|
197
|
-
const helpText =
|
|
213
|
+
const helpText =
|
|
214
|
+
scrollTitleRows === undefined ? baseHelpText : `${baseHelpText} wheel/PgUp/PgDn scroll question`;
|
|
198
215
|
const dialogOptions = {
|
|
199
216
|
initialIndex,
|
|
200
217
|
timeout,
|
|
@@ -231,6 +248,7 @@ async function askSingleQuestion(
|
|
|
231
248
|
const input = signal ? await untilAborted(signal, showCustomInput) : await showCustomInput();
|
|
232
249
|
return { input };
|
|
233
250
|
};
|
|
251
|
+
const otherOptionLabel = options.otherOptionLabel ?? OTHER_OPTION;
|
|
234
252
|
|
|
235
253
|
const promptWithProgress = navigation?.progressText ? `${question} (${navigation.progressText})` : question;
|
|
236
254
|
if (multi) {
|
|
@@ -252,7 +270,7 @@ async function askSingleQuestion(
|
|
|
252
270
|
if (!navigation?.allowForward && selected.size > 0) {
|
|
253
271
|
opts.push(doneLabel);
|
|
254
272
|
}
|
|
255
|
-
opts.push(
|
|
273
|
+
opts.push(otherOptionLabel);
|
|
256
274
|
|
|
257
275
|
const prefix = selected.size > 0 ? `(${selected.size} selected) ` : "";
|
|
258
276
|
const {
|
|
@@ -273,7 +291,7 @@ async function askSingleQuestion(
|
|
|
273
291
|
}
|
|
274
292
|
if (choice === doneLabel) break;
|
|
275
293
|
|
|
276
|
-
if (choice ===
|
|
294
|
+
if (choice === otherOptionLabel) {
|
|
277
295
|
if (selectTimedOut) {
|
|
278
296
|
timedOut = true;
|
|
279
297
|
break;
|
|
@@ -315,7 +333,7 @@ async function askSingleQuestion(
|
|
|
315
333
|
selectedOptions = Array.from(selected);
|
|
316
334
|
} else {
|
|
317
335
|
const displayLabels = addRecommendedSuffix(optionLabels, recommended);
|
|
318
|
-
const optionsWithNavigation = [...displayLabels,
|
|
336
|
+
const optionsWithNavigation = [...displayLabels, otherOptionLabel];
|
|
319
337
|
|
|
320
338
|
let initialIndex = recommended;
|
|
321
339
|
const previouslySelected = selectedOptions[0];
|
|
@@ -344,7 +362,7 @@ async function askSingleQuestion(
|
|
|
344
362
|
if (!timedOut) {
|
|
345
363
|
return { selectedOptions, customInput, timedOut, cancelled: true };
|
|
346
364
|
}
|
|
347
|
-
} else if (choice ===
|
|
365
|
+
} else if (choice === otherOptionLabel) {
|
|
348
366
|
if (!selectTimedOut) {
|
|
349
367
|
const customResult = await promptForCustomInput();
|
|
350
368
|
if (customResult.input !== undefined) {
|
|
@@ -458,25 +476,46 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
458
476
|
q: AskParams["questions"][number],
|
|
459
477
|
options?: { previous?: QuestionResult; navigation?: NavigationControls },
|
|
460
478
|
) => {
|
|
461
|
-
const
|
|
479
|
+
const rawOptionLabels = q.options.map(o => o.label);
|
|
462
480
|
try {
|
|
463
481
|
const deepInterviewPrompt = formatDeepInterviewSelectorPrompt(q.question);
|
|
464
482
|
const displayQuestion = deepInterviewPrompt ?? q.question;
|
|
465
|
-
const
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
483
|
+
const shouldNumberOptions = isDeepInterviewAskQuestion(q.question);
|
|
484
|
+
const optionLabels = shouldNumberOptions ? numberOptionLabels(rawOptionLabels) : rawOptionLabels;
|
|
485
|
+
const initialSelection =
|
|
486
|
+
shouldNumberOptions && options?.previous
|
|
487
|
+
? {
|
|
488
|
+
...options.previous,
|
|
489
|
+
selectedOptions: options.previous.selectedOptions.map(selected => {
|
|
490
|
+
const rawIndex = rawOptionLabels.indexOf(selected);
|
|
491
|
+
return rawIndex >= 0 ? (optionLabels[rawIndex] ?? selected) : selected;
|
|
492
|
+
}),
|
|
493
|
+
}
|
|
494
|
+
: options?.previous;
|
|
495
|
+
const {
|
|
496
|
+
selectedOptions: displaySelectedOptions,
|
|
497
|
+
customInput,
|
|
498
|
+
navigation,
|
|
499
|
+
cancelled,
|
|
500
|
+
timedOut,
|
|
501
|
+
} = await askSingleQuestion(ui, displayQuestion, optionLabels, q.multi ?? false, {
|
|
502
|
+
recommended: q.recommended,
|
|
503
|
+
timeout: timeout ?? undefined,
|
|
504
|
+
signal,
|
|
505
|
+
initialSelection,
|
|
506
|
+
navigation: options?.navigation,
|
|
507
|
+
scrollTitleRows: deepInterviewPrompt === null ? undefined : DEEP_INTERVIEW_SELECTOR_SCROLL_TITLE_ROWS,
|
|
508
|
+
otherOptionLabel: shouldNumberOptions
|
|
509
|
+
? formatNumberedOptionLabel(OTHER_OPTION, optionLabels.length)
|
|
510
|
+
: undefined,
|
|
511
|
+
});
|
|
512
|
+
const selectedOptions = shouldNumberOptions
|
|
513
|
+
? displaySelectedOptions.map(selected => {
|
|
514
|
+
const displayIndex = optionLabels.indexOf(selected);
|
|
515
|
+
return displayIndex >= 0 ? (rawOptionLabels[displayIndex] ?? selected) : selected;
|
|
516
|
+
})
|
|
517
|
+
: displaySelectedOptions;
|
|
518
|
+
return { optionLabels: rawOptionLabels, selectedOptions, customInput, navigation, cancelled, timedOut };
|
|
480
519
|
} catch (error) {
|
|
481
520
|
if (error instanceof Error && error.name === "AbortError") {
|
|
482
521
|
throw new ToolAbortError("Ask input was cancelled");
|
|
@@ -668,17 +707,17 @@ export const askToolRenderer = {
|
|
|
668
707
|
container.addChild(
|
|
669
708
|
new Text(` ${uiTheme.fg("dim", qBranch)} ${uiTheme.fg("dim", `[${q.id}]`)}${metaStr}`, 0, 0),
|
|
670
709
|
);
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
new Markdown(q.question, 3, 0, mdTheme, accentStyle),
|
|
674
|
-
);
|
|
710
|
+
const deepInterviewQuestion = renderDeepInterviewAskQuestion(q.question, uiTheme);
|
|
711
|
+
container.addChild(deepInterviewQuestion ?? new Markdown(q.question, 3, 0, mdTheme, accentStyle));
|
|
675
712
|
|
|
676
713
|
const qOptions = q.options;
|
|
677
714
|
if (qOptions?.length) {
|
|
678
715
|
const entries = qOptions.map((opt, j) => {
|
|
679
716
|
const isLastOpt = j === qOptions.length - 1;
|
|
680
717
|
const optBranch = isLastOpt ? uiTheme.tree.last : uiTheme.tree.branch;
|
|
681
|
-
const
|
|
718
|
+
const shouldNumberOption = deepInterviewQuestion !== null || isDeepInterviewAskQuestion(q.question);
|
|
719
|
+
const displayLabel = shouldNumberOption ? formatNumberedOptionLabel(opt.label, j) : opt.label;
|
|
720
|
+
const optLabel = renderInlineMarkdown(displayLabel, mdTheme, t => uiTheme.fg("muted", t));
|
|
682
721
|
return {
|
|
683
722
|
prefix: ` ${uiTheme.fg("dim", continuation)} ${uiTheme.fg("dim", optBranch)} ${uiTheme.fg("dim", uiTheme.checkbox.unchecked)} `,
|
|
684
723
|
label: optLabel,
|
|
@@ -694,23 +733,24 @@ export const askToolRenderer = {
|
|
|
694
733
|
if (!args.question) {
|
|
695
734
|
return new Text(formatErrorMessage("No question provided", uiTheme), 0, 0);
|
|
696
735
|
}
|
|
736
|
+
const question = args.question;
|
|
697
737
|
|
|
698
738
|
const container = new Container();
|
|
699
739
|
const meta: string[] = [];
|
|
700
740
|
if (args.multi) meta.push("multi");
|
|
701
741
|
if (args.options?.length) meta.push(`options:${args.options.length}`);
|
|
702
742
|
container.addChild(new Text(`${label}${formatMeta(meta, uiTheme)}`, 0, 0));
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
new Markdown(args.question, 1, 0, mdTheme, accentStyle),
|
|
706
|
-
);
|
|
743
|
+
const deepInterviewQuestion = renderDeepInterviewAskQuestion(question, uiTheme);
|
|
744
|
+
container.addChild(deepInterviewQuestion ?? new Markdown(question, 1, 0, mdTheme, accentStyle));
|
|
707
745
|
|
|
708
746
|
const options = args.options;
|
|
709
747
|
if (options?.length) {
|
|
710
748
|
const entries = options.map((opt, i) => {
|
|
711
749
|
const isLast = i === options.length - 1;
|
|
712
750
|
const branch = isLast ? uiTheme.tree.last : uiTheme.tree.branch;
|
|
713
|
-
const
|
|
751
|
+
const shouldNumberOption = deepInterviewQuestion !== null || isDeepInterviewAskQuestion(question);
|
|
752
|
+
const displayLabel = shouldNumberOption ? formatNumberedOptionLabel(opt.label, i) : opt.label;
|
|
753
|
+
const optLabel = renderInlineMarkdown(displayLabel, mdTheme, t => uiTheme.fg("muted", t));
|
|
714
754
|
return {
|
|
715
755
|
prefix: ` ${uiTheme.fg("dim", branch)} ${uiTheme.fg("dim", uiTheme.checkbox.unchecked)} `,
|
|
716
756
|
label: optLabel,
|
package/src/tools/ast-edit.ts
CHANGED
|
@@ -204,6 +204,7 @@ export class AstEditTool implements AgentTool<typeof astEditSchema, AstEditToolD
|
|
|
204
204
|
const scope = await resolveToolSearchScope({
|
|
205
205
|
rawPaths: params.paths,
|
|
206
206
|
cwd: this.session.cwd,
|
|
207
|
+
getArtifactsDir: this.session.getArtifactsDir,
|
|
207
208
|
internalUrlAction: "rewrite",
|
|
208
209
|
});
|
|
209
210
|
const { searchPath: resolvedSearchPath, scopePath, isDirectory, multiTargets, globFilter } = scope;
|
package/src/tools/ast-grep.ts
CHANGED
|
@@ -151,6 +151,7 @@ export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolD
|
|
|
151
151
|
const scope = await resolveToolSearchScope({
|
|
152
152
|
rawPaths: params.paths,
|
|
153
153
|
cwd: this.session.cwd,
|
|
154
|
+
getArtifactsDir: this.session.getArtifactsDir,
|
|
154
155
|
internalUrlAction: "search",
|
|
155
156
|
});
|
|
156
157
|
const { searchPath: resolvedSearchPath, scopePath, isDirectory, multiTargets, globFilter } = scope;
|
package/src/tools/bash.ts
CHANGED
|
@@ -693,7 +693,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
693
693
|
throw error instanceof Error ? error : new Error(String(error));
|
|
694
694
|
}
|
|
695
695
|
},
|
|
696
|
-
{ ownerId },
|
|
696
|
+
{ ownerId, metadata: { monitor: true } },
|
|
697
697
|
);
|
|
698
698
|
currentJobId = jobId;
|
|
699
699
|
return { jobId, label, commandCwd: prepared.commandCwd };
|
package/src/tools/cron.ts
CHANGED
|
@@ -137,6 +137,7 @@ function deleteRecord(ownerId: string | undefined, id: string): boolean {
|
|
|
137
137
|
disposeRecord(record);
|
|
138
138
|
const deleted = state.jobs.delete(id);
|
|
139
139
|
if (state.jobs.size === 0) schedulesByOwner.delete(key);
|
|
140
|
+
if (deleted) notifyCronChange();
|
|
140
141
|
return deleted;
|
|
141
142
|
}
|
|
142
143
|
|
|
@@ -158,6 +159,7 @@ export function clearOwnerSchedules(ownerId: string | undefined): void {
|
|
|
158
159
|
state.jobs.clear();
|
|
159
160
|
state.cleanupRegistered = false;
|
|
160
161
|
schedulesByOwner.delete(key);
|
|
162
|
+
notifyCronChange();
|
|
161
163
|
}
|
|
162
164
|
|
|
163
165
|
/** Reset every owner's schedule store. Test-only. */
|
|
@@ -167,6 +169,51 @@ export function resetCronRegistryForTests(): void {
|
|
|
167
169
|
clearOwnerSchedules(ownerId);
|
|
168
170
|
}
|
|
169
171
|
schedulesByOwner.clear();
|
|
172
|
+
notifyCronChange();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Module-level listeners notified whenever the cron schedule set changes. */
|
|
176
|
+
const cronChangeListeners = new Set<() => void>();
|
|
177
|
+
|
|
178
|
+
/** Subscribe to cron schedule-set changes. Returns an unsubscribe function. */
|
|
179
|
+
export function onCronChange(cb: () => void): () => void {
|
|
180
|
+
cronChangeListeners.add(cb);
|
|
181
|
+
return () => {
|
|
182
|
+
cronChangeListeners.delete(cb);
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function notifyCronChange(): void {
|
|
187
|
+
for (const cb of cronChangeListeners) {
|
|
188
|
+
try {
|
|
189
|
+
cb();
|
|
190
|
+
} catch (error) {
|
|
191
|
+
logger.warn("Cron change listener failed", {
|
|
192
|
+
error: error instanceof Error ? error.message : String(error),
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Snapshot the scheduled cron jobs for an owner (or all owners when omitted). */
|
|
199
|
+
export function listCronSnapshots(ownerId?: string): CronJobSnapshot[] {
|
|
200
|
+
const out: CronJobSnapshot[] = [];
|
|
201
|
+
if (ownerId !== undefined) {
|
|
202
|
+
const state = schedulesByOwner.get(ownerKey(ownerId));
|
|
203
|
+
if (state) {
|
|
204
|
+
for (const record of state.jobs.values()) out.push(record.snapshot);
|
|
205
|
+
}
|
|
206
|
+
return out;
|
|
207
|
+
}
|
|
208
|
+
for (const state of schedulesByOwner.values()) {
|
|
209
|
+
for (const record of state.jobs.values()) out.push(record.snapshot);
|
|
210
|
+
}
|
|
211
|
+
return out;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Delete a scheduled cron job by owner-scoped id. Returns true when removed. */
|
|
215
|
+
export function deleteCronJobById(ownerId: string | undefined, id: string): boolean {
|
|
216
|
+
return deleteRecord(ownerId, id);
|
|
170
217
|
}
|
|
171
218
|
|
|
172
219
|
const CRON_FIELD_BOUNDS: Array<{ name: string; min: number; max: number }> = [
|
|
@@ -448,6 +495,7 @@ function scheduleRecord(ownerId: string | undefined, state: OwnerScheduleState,
|
|
|
448
495
|
});
|
|
449
496
|
record.snapshot.nextFireAt = fireAt;
|
|
450
497
|
record.timer = setCronTimeout(() => fireRecord(ownerId, state, record.snapshot.id), fireAt - now);
|
|
498
|
+
notifyCronChange();
|
|
451
499
|
}
|
|
452
500
|
|
|
453
501
|
function scheduleExpiry(ownerId: string | undefined, record: CronScheduleRecord): void {
|
package/src/tools/find.ts
CHANGED
|
@@ -155,7 +155,10 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
|
|
155
155
|
if (hasGlobPathChars(rawPattern)) {
|
|
156
156
|
throw new ToolError(`Glob patterns are not supported for internal URLs: ${rawPattern}`);
|
|
157
157
|
}
|
|
158
|
-
const resource = await internalRouter.resolve(rawPattern
|
|
158
|
+
const resource = await internalRouter.resolve(rawPattern, {
|
|
159
|
+
cwd: this.session.cwd,
|
|
160
|
+
getArtifactsDir: this.session.getArtifactsDir,
|
|
161
|
+
});
|
|
159
162
|
if (!resource.sourcePath) {
|
|
160
163
|
throw new ToolError(`Cannot find internal URL without a backing file: ${rawPattern}`);
|
|
161
164
|
}
|
package/src/tools/index.ts
CHANGED
|
@@ -150,6 +150,8 @@ export interface ToolSession {
|
|
|
150
150
|
requireYieldTool?: boolean;
|
|
151
151
|
/** Task recursion depth (0 = top-level, 1 = first child, etc.) */
|
|
152
152
|
taskDepth?: number;
|
|
153
|
+
/** Current role-agent type/name for nested task sessions. */
|
|
154
|
+
currentAgentType?: string;
|
|
153
155
|
/** Get session file */
|
|
154
156
|
getSessionFile: () => string | null;
|
|
155
157
|
/** Get eval kernel owner ID for session-scoped retained-kernel cleanup. */
|
package/src/tools/path-utils.ts
CHANGED
|
@@ -625,6 +625,7 @@ export function resolveReadPath(filePath: string, cwd: string): string {
|
|
|
625
625
|
export interface ToolScopeOptions {
|
|
626
626
|
rawPaths: string[];
|
|
627
627
|
cwd: string;
|
|
628
|
+
getArtifactsDir?: () => string | null;
|
|
628
629
|
/** Verb used in the "Cannot {action} internal URL without a backing file: …" message. */
|
|
629
630
|
internalUrlAction: string;
|
|
630
631
|
/** Collect absolute paths flagged immutable by their internal-URL handler. */
|
|
@@ -655,7 +656,7 @@ export interface ToolScopeResolution {
|
|
|
655
656
|
* 5. stat the resolved base path so callers can branch on directory vs file scope.
|
|
656
657
|
*/
|
|
657
658
|
export async function resolveToolSearchScope(opts: ToolScopeOptions): Promise<ToolScopeResolution> {
|
|
658
|
-
const { rawPaths: inputs, cwd, internalUrlAction } = opts;
|
|
659
|
+
const { rawPaths: inputs, cwd, internalUrlAction, getArtifactsDir } = opts;
|
|
659
660
|
const rawPaths = inputs.map(normalizePathLikeInput);
|
|
660
661
|
if (rawPaths.some(rawPath => rawPath.length === 0)) {
|
|
661
662
|
throw new ToolError("`paths` must contain non-empty paths or globs");
|
|
@@ -671,7 +672,7 @@ export async function resolveToolSearchScope(opts: ToolScopeOptions): Promise<To
|
|
|
671
672
|
if (hasGlobPathChars(rawPath)) {
|
|
672
673
|
throw new ToolError(`Glob patterns are not supported for internal URLs: ${rawPath}`);
|
|
673
674
|
}
|
|
674
|
-
const resource = await internalRouter.resolve(rawPath);
|
|
675
|
+
const resource = await internalRouter.resolve(rawPath, { cwd, getArtifactsDir });
|
|
675
676
|
if (!resource.sourcePath) {
|
|
676
677
|
throw new ToolError(`Cannot ${internalUrlAction} internal URL without a backing file: ${rawPath}`);
|
|
677
678
|
}
|
package/src/tools/read.ts
CHANGED
|
@@ -2045,6 +2045,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
2045
2045
|
// Resolve the internal URL
|
|
2046
2046
|
const resource = await internalRouter.resolve(url, {
|
|
2047
2047
|
cwd: this.session.cwd,
|
|
2048
|
+
getArtifactsDir: this.session.getArtifactsDir,
|
|
2048
2049
|
settings: this.session.settings,
|
|
2049
2050
|
signal,
|
|
2050
2051
|
});
|
package/src/tools/search.ts
CHANGED
|
@@ -280,6 +280,7 @@ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDeta
|
|
|
280
280
|
const scope = await resolveToolSearchScope({
|
|
281
281
|
rawPaths: resolvedPaths,
|
|
282
282
|
cwd: this.session.cwd,
|
|
283
|
+
getArtifactsDir: this.session.getArtifactsDir,
|
|
283
284
|
internalUrlAction: "search",
|
|
284
285
|
trackImmutableSources: true,
|
|
285
286
|
surfaceExactFilePaths: true,
|
package/src/tools/skill.ts
CHANGED
|
@@ -138,7 +138,12 @@ export class SkillTool implements AgentTool<typeof skillSchema, SkillToolDetails
|
|
|
138
138
|
{ triggerTurn: false },
|
|
139
139
|
);
|
|
140
140
|
|
|
141
|
-
const summary =
|
|
141
|
+
const summary = JSON.stringify({
|
|
142
|
+
callee: skill.name,
|
|
143
|
+
path: skill.filePath,
|
|
144
|
+
args: args || undefined,
|
|
145
|
+
lineCount: built.details.lineCount,
|
|
146
|
+
});
|
|
142
147
|
return {
|
|
143
148
|
content: [{ type: "text", text: summary }],
|
|
144
149
|
details: {
|