@gajae-code/coding-agent 0.2.5 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/types/async/job-manager.d.ts +91 -2
  3. package/dist/types/cli/args.d.ts +1 -1
  4. package/dist/types/commands/deep-interview.d.ts +3 -0
  5. package/dist/types/commands/harness.d.ts +37 -0
  6. package/dist/types/config/keybindings.d.ts +5 -0
  7. package/dist/types/config/settings-schema.d.ts +10 -4
  8. package/dist/types/config/settings.d.ts +2 -0
  9. package/dist/types/debug/crash-diagnostics.d.ts +45 -0
  10. package/dist/types/debug/runtime-gauges.d.ts +6 -0
  11. package/dist/types/deep-interview/render-middleware.d.ts +6 -0
  12. package/dist/types/eval/py/executor.d.ts +2 -0
  13. package/dist/types/eval/py/kernel.d.ts +2 -0
  14. package/dist/types/exec/bash-executor.d.ts +10 -0
  15. package/dist/types/extensibility/custom-tools/types.d.ts +1 -0
  16. package/dist/types/extensibility/extensions/types.d.ts +6 -0
  17. package/dist/types/extensibility/shared-events.d.ts +1 -0
  18. package/dist/types/gjc-runtime/cli-write-receipt.d.ts +24 -0
  19. package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +1 -0
  20. package/dist/types/gjc-runtime/state-graph.d.ts +4 -0
  21. package/dist/types/gjc-runtime/state-migrations.d.ts +33 -0
  22. package/dist/types/gjc-runtime/state-renderer.d.ts +65 -0
  23. package/dist/types/gjc-runtime/state-runtime.d.ts +2 -0
  24. package/dist/types/gjc-runtime/state-schema.d.ts +317 -0
  25. package/dist/types/gjc-runtime/state-validation.d.ts +6 -0
  26. package/dist/types/gjc-runtime/state-writer.d.ts +147 -0
  27. package/dist/types/gjc-runtime/team-runtime.d.ts +81 -7
  28. package/dist/types/gjc-runtime/workflow-command-ref.d.ts +43 -0
  29. package/dist/types/gjc-runtime/workflow-manifest.d.ts +54 -0
  30. package/dist/types/harness-control-plane/classifier.d.ts +13 -0
  31. package/dist/types/harness-control-plane/control-endpoint.d.ts +31 -0
  32. package/dist/types/harness-control-plane/finalize.d.ts +47 -0
  33. package/dist/types/harness-control-plane/frame-mapper.d.ts +29 -0
  34. package/dist/types/harness-control-plane/operate.d.ts +35 -0
  35. package/dist/types/harness-control-plane/owner.d.ts +46 -0
  36. package/dist/types/harness-control-plane/preserve.d.ts +19 -0
  37. package/dist/types/harness-control-plane/receipts.d.ts +88 -0
  38. package/dist/types/harness-control-plane/rpc-adapter.d.ts +66 -0
  39. package/dist/types/harness-control-plane/seams.d.ts +21 -0
  40. package/dist/types/harness-control-plane/session-lease.d.ts +65 -0
  41. package/dist/types/harness-control-plane/state-machine.d.ts +19 -0
  42. package/dist/types/harness-control-plane/storage.d.ts +53 -0
  43. package/dist/types/harness-control-plane/types.d.ts +162 -0
  44. package/dist/types/hooks/skill-keywords.d.ts +2 -1
  45. package/dist/types/hooks/skill-state.d.ts +23 -29
  46. package/dist/types/internal-urls/agent-protocol.d.ts +2 -2
  47. package/dist/types/internal-urls/artifact-protocol.d.ts +2 -2
  48. package/dist/types/internal-urls/registry-helpers.d.ts +8 -7
  49. package/dist/types/internal-urls/types.d.ts +4 -0
  50. package/dist/types/lsp/index.d.ts +10 -10
  51. package/dist/types/modes/bridge/auth.d.ts +12 -0
  52. package/dist/types/modes/bridge/bridge-client-bridge.d.ts +9 -0
  53. package/dist/types/modes/bridge/bridge-mode.d.ts +44 -0
  54. package/dist/types/modes/bridge/bridge-ui-context.d.ts +88 -0
  55. package/dist/types/modes/bridge/event-stream.d.ts +8 -0
  56. package/dist/types/modes/components/custom-editor.d.ts +6 -0
  57. package/dist/types/modes/components/hook-selector.d.ts +1 -0
  58. package/dist/types/modes/components/jobs-overlay-model.d.ts +31 -0
  59. package/dist/types/modes/components/jobs-overlay.d.ts +30 -0
  60. package/dist/types/modes/components/status-line/types.d.ts +2 -0
  61. package/dist/types/modes/components/status-line.d.ts +2 -0
  62. package/dist/types/modes/controllers/input-controller.d.ts +1 -0
  63. package/dist/types/modes/controllers/selector-controller.d.ts +8 -0
  64. package/dist/types/modes/index.d.ts +1 -0
  65. package/dist/types/modes/interactive-mode.d.ts +2 -0
  66. package/dist/types/modes/jobs-observer.d.ts +57 -0
  67. package/dist/types/modes/rpc/host-tools.d.ts +1 -16
  68. package/dist/types/modes/rpc/host-uris.d.ts +1 -38
  69. package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +20 -0
  70. package/dist/types/modes/shared/agent-wire/command-validation.d.ts +2 -0
  71. package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +24 -0
  72. package/dist/types/modes/shared/agent-wire/handshake.d.ts +46 -0
  73. package/dist/types/modes/shared/agent-wire/host-tool-bridge.d.ts +16 -0
  74. package/dist/types/modes/shared/agent-wire/host-uri-bridge.d.ts +17 -0
  75. package/dist/types/modes/shared/agent-wire/protocol.d.ts +44 -0
  76. package/dist/types/modes/shared/agent-wire/responses.d.ts +4 -0
  77. package/dist/types/modes/shared/agent-wire/scopes.d.ts +18 -0
  78. package/dist/types/modes/shared/agent-wire/ui-request-broker.d.ts +42 -0
  79. package/dist/types/modes/shared/agent-wire/ui-result.d.ts +27 -0
  80. package/dist/types/modes/types.d.ts +2 -0
  81. package/dist/types/sdk.d.ts +4 -0
  82. package/dist/types/session/agent-session.d.ts +19 -1
  83. package/dist/types/skill-state/active-state.d.ts +2 -0
  84. package/dist/types/skill-state/deep-interview-mutation-guard.d.ts +1 -1
  85. package/dist/types/skill-state/workflow-state-contract.d.ts +25 -2
  86. package/dist/types/skill-state/workflow-state-version.d.ts +3 -0
  87. package/dist/types/task/executor.d.ts +3 -0
  88. package/dist/types/task/id.d.ts +7 -0
  89. package/dist/types/task/index.d.ts +5 -0
  90. package/dist/types/task/receipt.d.ts +85 -0
  91. package/dist/types/task/spawn-gate.d.ts +38 -0
  92. package/dist/types/task/types.d.ts +198 -14
  93. package/dist/types/tools/cron.d.ts +6 -0
  94. package/dist/types/tools/index.d.ts +2 -0
  95. package/dist/types/tools/path-utils.d.ts +1 -0
  96. package/dist/types/tools/subagent.d.ts +26 -1
  97. package/package.json +7 -7
  98. package/scripts/build-binary.ts +7 -0
  99. package/src/async/job-manager.ts +334 -6
  100. package/src/cli/args.ts +9 -2
  101. package/src/cli/auth-broker-cli.ts +1 -0
  102. package/src/cli/config-cli.ts +10 -2
  103. package/src/cli.ts +2 -0
  104. package/src/commands/deep-interview.ts +1 -0
  105. package/src/commands/harness.ts +862 -0
  106. package/src/commands/launch.ts +2 -2
  107. package/src/commands/state.ts +2 -1
  108. package/src/commands/team.ts +54 -39
  109. package/src/config/keybindings.ts +6 -0
  110. package/src/config/settings-schema.ts +13 -3
  111. package/src/config/settings.ts +5 -0
  112. package/src/dap/client.ts +17 -3
  113. package/src/debug/crash-diagnostics.ts +223 -0
  114. package/src/debug/runtime-gauges.ts +20 -0
  115. package/src/deep-interview/render-middleware.ts +372 -0
  116. package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
  117. package/src/defaults/gjc/skills/ralplan/SKILL.md +31 -2
  118. package/src/defaults/gjc/skills/team/SKILL.md +47 -21
  119. package/src/defaults/gjc/skills/ultragoal/SKILL.md +106 -13
  120. package/src/eval/py/executor.ts +21 -1
  121. package/src/eval/py/kernel.ts +15 -0
  122. package/src/exec/bash-executor.ts +41 -0
  123. package/src/extensibility/custom-tools/types.ts +1 -0
  124. package/src/extensibility/extensions/types.ts +6 -0
  125. package/src/extensibility/shared-events.ts +1 -0
  126. package/src/gjc-runtime/cli-write-receipt.ts +31 -0
  127. package/src/gjc-runtime/deep-interview-runtime.ts +98 -42
  128. package/src/gjc-runtime/goal-mode-request.ts +11 -3
  129. package/src/gjc-runtime/ralplan-runtime.ts +235 -43
  130. package/src/gjc-runtime/state-graph.ts +86 -0
  131. package/src/gjc-runtime/state-migrations.ts +179 -0
  132. package/src/gjc-runtime/state-renderer.ts +345 -0
  133. package/src/gjc-runtime/state-runtime.ts +1155 -46
  134. package/src/gjc-runtime/state-schema.ts +192 -0
  135. package/src/gjc-runtime/state-validation.ts +49 -0
  136. package/src/gjc-runtime/state-writer.ts +749 -0
  137. package/src/gjc-runtime/team-runtime.ts +1255 -189
  138. package/src/gjc-runtime/ultragoal-runtime.ts +460 -43
  139. package/src/gjc-runtime/workflow-command-ref.ts +239 -0
  140. package/src/gjc-runtime/workflow-manifest.generated.json +1601 -0
  141. package/src/gjc-runtime/workflow-manifest.ts +427 -0
  142. package/src/harness-control-plane/classifier.ts +128 -0
  143. package/src/harness-control-plane/control-endpoint.ts +148 -0
  144. package/src/harness-control-plane/finalize.ts +222 -0
  145. package/src/harness-control-plane/frame-mapper.ts +286 -0
  146. package/src/harness-control-plane/operate.ts +225 -0
  147. package/src/harness-control-plane/owner.ts +600 -0
  148. package/src/harness-control-plane/preserve.ts +102 -0
  149. package/src/harness-control-plane/receipts.ts +216 -0
  150. package/src/harness-control-plane/rpc-adapter.ts +276 -0
  151. package/src/harness-control-plane/seams.ts +39 -0
  152. package/src/harness-control-plane/session-lease.ts +388 -0
  153. package/src/harness-control-plane/state-machine.ts +98 -0
  154. package/src/harness-control-plane/storage.ts +257 -0
  155. package/src/harness-control-plane/types.ts +214 -0
  156. package/src/hooks/skill-keywords.ts +4 -2
  157. package/src/hooks/skill-state.ts +197 -64
  158. package/src/internal-urls/agent-protocol.ts +68 -21
  159. package/src/internal-urls/artifact-protocol.ts +12 -17
  160. package/src/internal-urls/docs-index.generated.ts +3 -2
  161. package/src/internal-urls/registry-helpers.ts +19 -16
  162. package/src/internal-urls/types.ts +4 -0
  163. package/src/lsp/client.ts +18 -2
  164. package/src/main.ts +21 -5
  165. package/src/modes/bridge/auth.ts +41 -0
  166. package/src/modes/bridge/bridge-client-bridge.ts +47 -0
  167. package/src/modes/bridge/bridge-mode.ts +520 -0
  168. package/src/modes/bridge/bridge-ui-context.ts +200 -0
  169. package/src/modes/bridge/event-stream.ts +70 -0
  170. package/src/modes/components/assistant-message.ts +5 -1
  171. package/src/modes/components/custom-editor.ts +101 -0
  172. package/src/modes/components/hook-selector.ts +133 -20
  173. package/src/modes/components/jobs-overlay-model.ts +109 -0
  174. package/src/modes/components/jobs-overlay.ts +172 -0
  175. package/src/modes/components/status-line/presets.ts +7 -5
  176. package/src/modes/components/status-line/segments.ts +25 -0
  177. package/src/modes/components/status-line/types.ts +2 -0
  178. package/src/modes/components/status-line.ts +9 -1
  179. package/src/modes/controllers/event-controller.ts +71 -6
  180. package/src/modes/controllers/extension-ui-controller.ts +43 -1
  181. package/src/modes/controllers/input-controller.ts +105 -9
  182. package/src/modes/controllers/selector-controller.ts +31 -1
  183. package/src/modes/index.ts +1 -0
  184. package/src/modes/interactive-mode.ts +28 -0
  185. package/src/modes/jobs-observer.ts +204 -0
  186. package/src/modes/rpc/host-tools.ts +1 -186
  187. package/src/modes/rpc/host-uris.ts +1 -235
  188. package/src/modes/rpc/rpc-client.ts +25 -10
  189. package/src/modes/rpc/rpc-mode.ts +12 -381
  190. package/src/modes/shared/agent-wire/command-dispatch.ts +341 -0
  191. package/src/modes/shared/agent-wire/command-validation.ts +131 -0
  192. package/src/modes/shared/agent-wire/event-envelope.ts +108 -0
  193. package/src/modes/shared/agent-wire/handshake.ts +117 -0
  194. package/src/modes/shared/agent-wire/host-tool-bridge.ts +194 -0
  195. package/src/modes/shared/agent-wire/host-uri-bridge.ts +236 -0
  196. package/src/modes/shared/agent-wire/protocol.ts +96 -0
  197. package/src/modes/shared/agent-wire/responses.ts +17 -0
  198. package/src/modes/shared/agent-wire/scopes.ts +89 -0
  199. package/src/modes/shared/agent-wire/ui-request-broker.ts +150 -0
  200. package/src/modes/shared/agent-wire/ui-result.ts +48 -0
  201. package/src/modes/types.ts +2 -0
  202. package/src/prompts/agents/executor.md +13 -0
  203. package/src/prompts/tools/subagent.md +39 -4
  204. package/src/prompts/tools/task-summary.md +3 -9
  205. package/src/prompts/tools/task.md +5 -1
  206. package/src/sdk.ts +8 -0
  207. package/src/session/agent-session.ts +445 -71
  208. package/src/session/session-manager.ts +13 -1
  209. package/src/skill-state/active-state.ts +58 -65
  210. package/src/skill-state/deep-interview-mutation-guard.ts +114 -17
  211. package/src/skill-state/initial-phase.ts +2 -0
  212. package/src/skill-state/workflow-state-contract.ts +33 -4
  213. package/src/skill-state/workflow-state-version.ts +3 -0
  214. package/src/slash-commands/builtin-registry.ts +8 -0
  215. package/src/task/executor.ts +79 -13
  216. package/src/task/id.ts +33 -0
  217. package/src/task/index.ts +376 -74
  218. package/src/task/output-manager.ts +5 -4
  219. package/src/task/receipt.ts +297 -0
  220. package/src/task/render.ts +54 -134
  221. package/src/task/spawn-gate.ts +132 -0
  222. package/src/task/types.ts +104 -10
  223. package/src/tools/ask.ts +88 -27
  224. package/src/tools/ast-edit.ts +1 -0
  225. package/src/tools/ast-grep.ts +1 -0
  226. package/src/tools/bash.ts +1 -1
  227. package/src/tools/cron.ts +48 -0
  228. package/src/tools/find.ts +4 -1
  229. package/src/tools/index.ts +2 -0
  230. package/src/tools/path-utils.ts +3 -2
  231. package/src/tools/read.ts +1 -0
  232. package/src/tools/search.ts +1 -0
  233. package/src/tools/skill.ts +6 -1
  234. package/src/tools/subagent.ts +423 -79
@@ -0,0 +1,132 @@
1
+ /** The hard, locked batch threshold enforced by the runtime gate. */
2
+ export const DEFAULT_SPAWN_THRESHOLD = 4;
3
+
4
+ /** The justification a large batch or reviewer-spawned explorer must supply to pass the hard gate. */
5
+ export interface SpawnPlanReceipt {
6
+ whyParallel: string;
7
+ whyNotLocal: string;
8
+ independence: string;
9
+ expectedReceiptShape: string;
10
+ maxInlineTokens: number;
11
+ }
12
+
13
+ export interface SpawnGateRequest {
14
+ /** Number of children the batch wants to spawn. */
15
+ childCount: number;
16
+ /** The spawn-plan receipt, when provided. */
17
+ plan?: SpawnPlanReceipt;
18
+ }
19
+
20
+ export interface ReviewerExploreGateRequest {
21
+ /** Agent type/name doing the spawning, when known. */
22
+ spawningAgentType?: string | null;
23
+ /** Target agent type/name requested by the task call. */
24
+ targetAgent: string;
25
+ /** The spawn-plan receipt, when provided. */
26
+ plan?: SpawnPlanReceipt;
27
+ }
28
+
29
+ export type SpawnGateOutcome = "allowed" | "rejected";
30
+
31
+ export interface SpawnGateDecision {
32
+ outcome: SpawnGateOutcome;
33
+ /** Human-readable reason, suitable for a blocked-result message. */
34
+ reason: string;
35
+ /** Whether a plan was required for this request. */
36
+ planRequired: boolean;
37
+ /** Missing plan field names when rejected for an incomplete plan. */
38
+ missingFields: readonly string[];
39
+ }
40
+
41
+ const REQUIRED_STRING_FIELDS = ["whyParallel", "whyNotLocal", "independence", "expectedReceiptShape"] as const;
42
+
43
+ export function findMissingPlanFields(plan: SpawnPlanReceipt | undefined): string[] {
44
+ if (plan === undefined) {
45
+ return [...REQUIRED_STRING_FIELDS, "maxInlineTokens"];
46
+ }
47
+ const missing: string[] = [];
48
+ for (const field of REQUIRED_STRING_FIELDS) {
49
+ const value = plan[field];
50
+ if (typeof value !== "string" || value.trim().length === 0) {
51
+ missing.push(field);
52
+ }
53
+ }
54
+ if (
55
+ typeof plan.maxInlineTokens !== "number" ||
56
+ !Number.isFinite(plan.maxInlineTokens) ||
57
+ plan.maxInlineTokens <= 0
58
+ ) {
59
+ missing.push("maxInlineTokens");
60
+ }
61
+ return missing;
62
+ }
63
+
64
+ export function decide(childCount: number, threshold: number, plan: SpawnPlanReceipt | undefined): SpawnGateDecision {
65
+ if (!Number.isInteger(childCount) || childCount < 0) {
66
+ throw new RangeError("childCount must be a non-negative integer");
67
+ }
68
+ if (!Number.isInteger(threshold) || threshold < 1) {
69
+ throw new RangeError("threshold must be a positive integer");
70
+ }
71
+
72
+ const planRequired = childCount > threshold;
73
+ if (!planRequired) {
74
+ return {
75
+ outcome: "allowed",
76
+ reason: `batch of ${childCount} is at or below threshold ${threshold}`,
77
+ planRequired: false,
78
+ missingFields: [],
79
+ };
80
+ }
81
+
82
+ const missingFields = findMissingPlanFields(plan);
83
+ if (missingFields.length > 0) {
84
+ return {
85
+ outcome: "rejected",
86
+ reason: `batch of ${childCount} exceeds threshold ${threshold} and the spawn-plan receipt is ${
87
+ plan === undefined ? "missing" : `incomplete (${missingFields.join(", ")})`
88
+ }`,
89
+ planRequired: true,
90
+ missingFields,
91
+ };
92
+ }
93
+
94
+ return {
95
+ outcome: "allowed",
96
+ reason: `batch of ${childCount} exceeds threshold ${threshold} and a complete spawn-plan receipt was provided`,
97
+ planRequired: true,
98
+ missingFields: [],
99
+ };
100
+ }
101
+
102
+ export function evaluateSpawnGate(request: SpawnGateRequest): SpawnGateDecision {
103
+ return decide(request.childCount, DEFAULT_SPAWN_THRESHOLD, request.plan);
104
+ }
105
+
106
+ export function evaluateReviewerExploreGate(request: ReviewerExploreGateRequest): SpawnGateDecision {
107
+ if (request.spawningAgentType !== "reviewer" || request.targetAgent !== "explore") {
108
+ return {
109
+ outcome: "allowed",
110
+ reason: "reviewer->explore gate does not apply",
111
+ planRequired: false,
112
+ missingFields: [],
113
+ };
114
+ }
115
+
116
+ const missingFields = findMissingPlanFields(request.plan);
117
+ if (missingFields.length > 0) {
118
+ return {
119
+ outcome: "rejected",
120
+ reason: `reviewer->explore spawn requires a complete spawn-plan receipt (${missingFields.join(", ")})`,
121
+ planRequired: true,
122
+ missingFields,
123
+ };
124
+ }
125
+
126
+ return {
127
+ outcome: "allowed",
128
+ reason: "reviewer->explore spawn has a complete spawn-plan receipt",
129
+ planRequired: true,
130
+ missingFields: [],
131
+ };
132
+ }
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) {
@@ -53,22 +57,33 @@ export interface SubagentLifecyclePayload {
53
57
  agent: string;
54
58
  agentSource: AgentSource;
55
59
  description?: string;
56
- status: "started" | "completed" | "failed" | "aborted";
60
+ status: "started" | "completed" | "failed" | "aborted" | "paused";
57
61
  sessionFile?: string;
58
62
  index: number;
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("camelcase identifier"),
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
- .boolean()
82
+ .enum(["none", "receipt", "last-turn", "bounded", "full"])
70
83
  .optional()
71
- .describe("explicit request to seed a subagent with sanitized parent context"),
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
- const itemSchema = createTaskItemSchema(contextEnabled);
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
  }
@@ -192,7 +220,7 @@ export interface AgentProgress {
192
220
  id: string;
193
221
  agent: string;
194
222
  agentSource: AgentSource;
195
- status: "pending" | "running" | "completed" | "failed" | "aborted";
223
+ status: "pending" | "running" | "completed" | "failed" | "aborted" | "paused";
196
224
  task: string;
197
225
  assignment?: string;
198
226
  description?: string;
@@ -230,6 +258,7 @@ export interface AgentProgress {
230
258
  retryState?: {
231
259
  attempt: number;
232
260
  maxAttempts: number;
261
+ unbounded?: boolean;
233
262
  delayMs: number;
234
263
  errorMessage: string;
235
264
  startedAtMs: number;
@@ -279,6 +308,7 @@ export interface SingleResult {
279
308
  error?: string;
280
309
  aborted?: boolean;
281
310
  abortReason?: string;
311
+ paused?: boolean;
282
312
  /** Aggregated usage from the subprocess, accumulated incrementally from message_end events. */
283
313
  usage?: Usage;
284
314
  /** Output path for the task result */
@@ -289,6 +319,8 @@ export interface SingleResult {
289
319
  branchName?: string;
290
320
  /** Nested repo patches to apply after parent merge */
291
321
  nestedPatches?: NestedRepoPatch[];
322
+ /** Whether isolated execution produced a non-empty root or nested patch. */
323
+ producedChanges?: boolean;
292
324
  /** Data extracted by registered subprocess tool handlers (keyed by tool name) */
293
325
  extractedToolData?: Record<string, unknown[]>;
294
326
  /**
@@ -302,21 +334,83 @@ export interface SingleResult {
302
334
  errorMessage: string;
303
335
  };
304
336
  /** Output metadata for agent:// URL integration */
305
- 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 };
306
340
  }
307
341
 
308
342
  /** Tool details for TUI rendering */
309
343
  export interface TaskToolDetails {
310
344
  projectAgentsDir: string | null;
311
- results: SingleResult[];
345
+ results: TaskResultReceipt[];
312
346
  totalDurationMs: number;
313
347
  /** Aggregated usage across all subagents. */
314
348
  usage?: Usage;
315
- outputPaths?: string[];
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
+ };
316
359
  progress?: AgentProgress[];
317
360
  async?: {
318
- state: "running" | "completed" | "failed";
361
+ state: "running" | "paused" | "queued" | "completed" | "failed";
319
362
  jobId: string;
320
363
  type: "task";
321
364
  };
322
365
  }
366
+ /**
367
+ * Persisted per-turn / per-subagent token record (Phase 0 instrumentation).
368
+ *
369
+ * Additive: this does not alter any existing task result shape. It is the
370
+ * durable, model-independent unit the deterministic orchestration-token
371
+ * benchmark (`@gajae-code/orchestration-token-benchmark`) consumes to measure
372
+ * token efficiency without any live-model calls.
373
+ */
374
+ export interface TaskTokenLog {
375
+ /** Subagent id, or "root" for the orchestrator's own turn. */
376
+ subagentId: string;
377
+ /** Agent name for attribution, when known. */
378
+ agent?: string;
379
+ /** 1-based turn index within the subagent's session. */
380
+ turn: number;
381
+ /** ISO-8601 timestamp the turn completed. */
382
+ at: string;
383
+ /** Cost-bearing input tokens (excludes cache reads), mirrors `Usage.input`. */
384
+ input: number;
385
+ /** Total output tokens for the turn, mirrors `Usage.output`. */
386
+ output: number;
387
+ /** Tokens read from the prompt cache, mirrors `Usage.cacheRead`. */
388
+ cacheRead: number;
389
+ /** Tokens written to the prompt cache, mirrors `Usage.cacheWrite`. */
390
+ cacheWrite: number;
391
+ /** input + output + cacheRead + cacheWrite. */
392
+ totalTokens: number;
393
+ /** Latest per-turn context-window occupancy, when known. */
394
+ contextTokens?: number;
395
+ /** Estimated USD cost for the turn, when known. */
396
+ cost?: number;
397
+ /** Model id used for the turn, when known. */
398
+ model?: string;
399
+ }
400
+
401
+ /**
402
+ * Deterministic aggregate token metrics computed from a set of `TaskTokenLog`
403
+ * entries. The cache-hit-rate field is the primary prompt-cache signal called
404
+ * out by the prefix-stability invariant (see the approved plan).
405
+ */
406
+ export interface TaskTokenMetrics {
407
+ /** Number of token-log entries aggregated. */
408
+ turns: number;
409
+ inputTokens: number;
410
+ outputTokens: number;
411
+ cacheReadTokens: number;
412
+ cacheWriteTokens: number;
413
+ totalTokens: number;
414
+ /** cacheRead / (input + cacheRead); 0 when there is no input-class traffic. */
415
+ cacheHitRate: number;
416
+ }
package/src/tools/ask.ts CHANGED
@@ -28,6 +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 {
32
+ formatDeepInterviewSelectorPrompt,
33
+ isDeepInterviewAskQuestion,
34
+ renderDeepInterviewAskQuestion,
35
+ } from "../deep-interview/render-middleware";
31
36
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
32
37
  import { getMarkdownTheme, type Theme, theme } from "../modes/theme/theme";
33
38
  import askDescription from "../prompts/tools/ask.md" with { type: "text" };
@@ -84,6 +89,7 @@ export interface AskToolDetails {
84
89
 
85
90
  const OTHER_OPTION = "Other (type your own)";
86
91
  const RECOMMENDED_SUFFIX = " (Recommended)";
92
+ const DEEP_INTERVIEW_SELECTOR_SCROLL_TITLE_ROWS = Number.MAX_SAFE_INTEGER;
87
93
 
88
94
  function getDoneOptionLabel(): string {
89
95
  return `${theme.status.success} Done selecting`;
@@ -115,6 +121,17 @@ function stripRecommendedSuffix(label: string): string {
115
121
  return label.endsWith(RECOMMENDED_SUFFIX) ? label.slice(0, -RECOMMENDED_SUFFIX.length) : label;
116
122
  }
117
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
+
118
135
  // =============================================================================
119
136
  // Question Selection Logic
120
137
  // =============================================================================
@@ -138,6 +155,8 @@ interface AskSingleQuestionOptions {
138
155
  signal?: AbortSignal;
139
156
  initialSelection?: Pick<SelectionResult, "selectedOptions" | "customInput">;
140
157
  navigation?: NavigationControls;
158
+ scrollTitleRows?: number;
159
+ otherOptionLabel?: string;
141
160
  }
142
161
 
143
162
  interface UIContext {
@@ -150,6 +169,7 @@ interface UIContext {
150
169
  signal?: AbortSignal;
151
170
  outline?: boolean;
152
171
  wrapFocused?: boolean;
172
+ scrollTitleRows?: number;
153
173
  onTimeout?: () => void;
154
174
  onLeft?: () => void;
155
175
  onRight?: () => void;
@@ -171,7 +191,7 @@ async function askSingleQuestion(
171
191
  multi: boolean,
172
192
  options: AskSingleQuestionOptions = {},
173
193
  ): Promise<SelectionResult> {
174
- const { recommended, timeout, signal, initialSelection, navigation } = options;
194
+ const { recommended, timeout, signal, initialSelection, navigation, scrollTitleRows } = options;
175
195
  const doneLabel = getDoneOptionLabel();
176
196
  let selectedOptions = [...(initialSelection?.selectedOptions ?? [])];
177
197
  let customInput = initialSelection?.customInput;
@@ -187,15 +207,18 @@ async function askSingleQuestion(
187
207
  timeoutTriggered = true;
188
208
  };
189
209
  let navigationAction: "back" | "forward" | undefined;
190
- const helpText = navigation
210
+ const baseHelpText = navigation
191
211
  ? "up/down navigate enter select ←/→ question esc cancel"
192
212
  : "up/down navigate enter select esc cancel";
213
+ const helpText =
214
+ scrollTitleRows === undefined ? baseHelpText : `${baseHelpText} wheel/PgUp/PgDn scroll question`;
193
215
  const dialogOptions = {
194
216
  initialIndex,
195
217
  timeout,
196
218
  signal,
197
219
  outline: true,
198
220
  wrapFocused: true,
221
+ scrollTitleRows,
199
222
  onTimeout,
200
223
  helpText,
201
224
  onLeft: navigation?.allowBack
@@ -225,6 +248,7 @@ async function askSingleQuestion(
225
248
  const input = signal ? await untilAborted(signal, showCustomInput) : await showCustomInput();
226
249
  return { input };
227
250
  };
251
+ const otherOptionLabel = options.otherOptionLabel ?? OTHER_OPTION;
228
252
 
229
253
  const promptWithProgress = navigation?.progressText ? `${question} (${navigation.progressText})` : question;
230
254
  if (multi) {
@@ -246,7 +270,7 @@ async function askSingleQuestion(
246
270
  if (!navigation?.allowForward && selected.size > 0) {
247
271
  opts.push(doneLabel);
248
272
  }
249
- opts.push(OTHER_OPTION);
273
+ opts.push(otherOptionLabel);
250
274
 
251
275
  const prefix = selected.size > 0 ? `(${selected.size} selected) ` : "";
252
276
  const {
@@ -267,7 +291,7 @@ async function askSingleQuestion(
267
291
  }
268
292
  if (choice === doneLabel) break;
269
293
 
270
- if (choice === OTHER_OPTION) {
294
+ if (choice === otherOptionLabel) {
271
295
  if (selectTimedOut) {
272
296
  timedOut = true;
273
297
  break;
@@ -309,7 +333,7 @@ async function askSingleQuestion(
309
333
  selectedOptions = Array.from(selected);
310
334
  } else {
311
335
  const displayLabels = addRecommendedSuffix(optionLabels, recommended);
312
- const optionsWithNavigation = [...displayLabels, OTHER_OPTION];
336
+ const optionsWithNavigation = [...displayLabels, otherOptionLabel];
313
337
 
314
338
  let initialIndex = recommended;
315
339
  const previouslySelected = selectedOptions[0];
@@ -338,7 +362,7 @@ async function askSingleQuestion(
338
362
  if (!timedOut) {
339
363
  return { selectedOptions, customInput, timedOut, cancelled: true };
340
364
  }
341
- } else if (choice === OTHER_OPTION) {
365
+ } else if (choice === otherOptionLabel) {
342
366
  if (!selectTimedOut) {
343
367
  const customResult = await promptForCustomInput();
344
368
  if (customResult.input !== undefined) {
@@ -452,22 +476,46 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
452
476
  q: AskParams["questions"][number],
453
477
  options?: { previous?: QuestionResult; navigation?: NavigationControls },
454
478
  ) => {
455
- const optionLabels = q.options.map(o => o.label);
479
+ const rawOptionLabels = q.options.map(o => o.label);
456
480
  try {
457
- const { selectedOptions, customInput, navigation, cancelled, timedOut } = await askSingleQuestion(
458
- ui,
459
- q.question,
460
- optionLabels,
461
- q.multi ?? false,
462
- {
463
- recommended: q.recommended,
464
- timeout: timeout ?? undefined,
465
- signal,
466
- initialSelection: options?.previous,
467
- navigation: options?.navigation,
468
- },
469
- );
470
- return { optionLabels, selectedOptions, customInput, navigation, cancelled, timedOut };
481
+ const deepInterviewPrompt = formatDeepInterviewSelectorPrompt(q.question);
482
+ const displayQuestion = deepInterviewPrompt ?? q.question;
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 };
471
519
  } catch (error) {
472
520
  if (error instanceof Error && error.name === "AbortError") {
473
521
  throw new ToolAbortError("Ask input was cancelled");
@@ -659,14 +707,17 @@ export const askToolRenderer = {
659
707
  container.addChild(
660
708
  new Text(` ${uiTheme.fg("dim", qBranch)} ${uiTheme.fg("dim", `[${q.id}]`)}${metaStr}`, 0, 0),
661
709
  );
662
- container.addChild(new Markdown(q.question, 3, 0, mdTheme, accentStyle));
710
+ const deepInterviewQuestion = renderDeepInterviewAskQuestion(q.question, uiTheme);
711
+ container.addChild(deepInterviewQuestion ?? new Markdown(q.question, 3, 0, mdTheme, accentStyle));
663
712
 
664
713
  const qOptions = q.options;
665
714
  if (qOptions?.length) {
666
715
  const entries = qOptions.map((opt, j) => {
667
716
  const isLastOpt = j === qOptions.length - 1;
668
717
  const optBranch = isLastOpt ? uiTheme.tree.last : uiTheme.tree.branch;
669
- const optLabel = renderInlineMarkdown(opt.label, mdTheme, t => uiTheme.fg("muted", t));
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));
670
721
  return {
671
722
  prefix: ` ${uiTheme.fg("dim", continuation)} ${uiTheme.fg("dim", optBranch)} ${uiTheme.fg("dim", uiTheme.checkbox.unchecked)} `,
672
723
  label: optLabel,
@@ -682,20 +733,24 @@ export const askToolRenderer = {
682
733
  if (!args.question) {
683
734
  return new Text(formatErrorMessage("No question provided", uiTheme), 0, 0);
684
735
  }
736
+ const question = args.question;
685
737
 
686
738
  const container = new Container();
687
739
  const meta: string[] = [];
688
740
  if (args.multi) meta.push("multi");
689
741
  if (args.options?.length) meta.push(`options:${args.options.length}`);
690
742
  container.addChild(new Text(`${label}${formatMeta(meta, uiTheme)}`, 0, 0));
691
- container.addChild(new Markdown(args.question, 1, 0, mdTheme, accentStyle));
743
+ const deepInterviewQuestion = renderDeepInterviewAskQuestion(question, uiTheme);
744
+ container.addChild(deepInterviewQuestion ?? new Markdown(question, 1, 0, mdTheme, accentStyle));
692
745
 
693
746
  const options = args.options;
694
747
  if (options?.length) {
695
748
  const entries = options.map((opt, i) => {
696
749
  const isLast = i === options.length - 1;
697
750
  const branch = isLast ? uiTheme.tree.last : uiTheme.tree.branch;
698
- const optLabel = renderInlineMarkdown(opt.label, mdTheme, t => uiTheme.fg("muted", t));
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));
699
754
  return {
700
755
  prefix: ` ${uiTheme.fg("dim", branch)} ${uiTheme.fg("dim", uiTheme.checkbox.unchecked)} `,
701
756
  label: optLabel,
@@ -752,7 +807,10 @@ export const askToolRenderer = {
752
807
  container.addChild(
753
808
  new Text(` ${uiTheme.fg("dim", branch)} ${statusIcon} ${uiTheme.fg("dim", `[${r.id}]`)}`, 0, 0),
754
809
  );
755
- container.addChild(new Markdown(r.question, 3, 0, mdTheme, accentStyle));
810
+ container.addChild(
811
+ renderDeepInterviewAskQuestion(r.question, uiTheme) ??
812
+ new Markdown(r.question, 3, 0, mdTheme, accentStyle),
813
+ );
756
814
 
757
815
  const answerLines: string[] = [];
758
816
  for (let j = 0; j < r.selectedOptions.length; j++) {
@@ -795,7 +853,10 @@ export const askToolRenderer = {
795
853
  const header = renderStatusLine({ icon: hasSelection ? "success" : "warning", title: "Ask" }, uiTheme);
796
854
  const container = new Container();
797
855
  container.addChild(new Text(header, 0, 0));
798
- container.addChild(new Markdown(details.question, 1, 0, mdTheme, accentStyle));
856
+ container.addChild(
857
+ renderDeepInterviewAskQuestion(details.question, uiTheme) ??
858
+ new Markdown(details.question, 1, 0, mdTheme, accentStyle),
859
+ );
799
860
 
800
861
  const answerLines: string[] = [];
801
862
  if (details.selectedOptions && details.selectedOptions.length > 0) {
@@ -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;
@@ -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 };