@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
@@ -8,6 +8,7 @@
8
8
  * This enables reliable agent:// URL resolution and prevents artifact collisions.
9
9
  */
10
10
  import * as fs from "node:fs/promises";
11
+ import { validateAllocatedTaskId, validateTaskId } from "./id";
11
12
 
12
13
  function escapeRegExp(value: string): string {
13
14
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -73,8 +74,8 @@ export class AgentOutputManager {
73
74
  */
74
75
  async allocate(id: string): Promise<string> {
75
76
  await this.#ensureInitialized();
76
- const prefix = this.#parentPrefix ? `${this.#parentPrefix}.` : "";
77
- return `${prefix}${this.#nextId++}-${id}`;
77
+ const prefix = this.#parentPrefix ? `${validateAllocatedTaskId(this.#parentPrefix)}.` : "";
78
+ return `${prefix}${this.#nextId++}-${validateTaskId(id)}`;
78
79
  }
79
80
 
80
81
  /**
@@ -85,8 +86,8 @@ export class AgentOutputManager {
85
86
  */
86
87
  async allocateBatch(ids: string[]): Promise<string[]> {
87
88
  await this.#ensureInitialized();
88
- const prefix = this.#parentPrefix ? `${this.#parentPrefix}.` : "";
89
- return ids.map(id => `${prefix}${this.#nextId++}-${id}`);
89
+ const prefix = this.#parentPrefix ? `${validateAllocatedTaskId(this.#parentPrefix)}.` : "";
90
+ return ids.map(id => `${prefix}${this.#nextId++}-${validateTaskId(id)}`);
90
91
  }
91
92
 
92
93
  /**
@@ -0,0 +1,297 @@
1
+ import type { SingleResult, TaskToolDetails } from "./types";
2
+
3
+ export interface TaskRoi {
4
+ tokens: number;
5
+ contextTokens?: number;
6
+ clonedTokens?: number;
7
+ costTotal?: number;
8
+ outputBytes?: number;
9
+ outputLines?: number;
10
+ producedChanges: boolean;
11
+ materialContribution: boolean;
12
+ lowRoi: boolean;
13
+ }
14
+ export interface TaskResultReceipt {
15
+ index: number;
16
+ id: string;
17
+ agent: string;
18
+ agentSource: SingleResult["agentSource"];
19
+ task: string;
20
+ assignment?: string;
21
+ description?: string;
22
+ status: "completed" | "failed" | "aborted" | "merge_failed" | "paused";
23
+ exitCode: number;
24
+ aborted?: boolean;
25
+ paused?: boolean;
26
+ truncated: boolean;
27
+ durationMs: number;
28
+ tokens: number;
29
+ contextTokens?: number;
30
+ contextWindow?: number;
31
+ modelOverride?: string | string[];
32
+ usage?: SingleResult["usage"];
33
+ cost?: number;
34
+ branchName?: string;
35
+ retryFailure?: { attempt: number; errorSummary: string };
36
+ errorSummary?: string;
37
+ abortSummary?: string;
38
+ preview: string;
39
+ previewTruncated: boolean;
40
+ outputRef?: { uri: string; sizeBytes: number; lineCount: number; sha256?: string };
41
+ outputUnavailable?: boolean;
42
+ review?: {
43
+ overallCorrectness?: string;
44
+ findingCount: number;
45
+ findings?: Array<{ severity?: string; summary: string }>;
46
+ };
47
+ extractedToolCounts?: Record<string, number>;
48
+ forkContext?: SingleResult["forkContext"];
49
+ roi?: TaskRoi;
50
+ }
51
+
52
+ const BANNED_RAW_TASK_KEYS = new Set([
53
+ "output",
54
+ "stderr",
55
+ "extractedToolData",
56
+ "resultText",
57
+ "errorText",
58
+ "artifactPayload",
59
+ "rawResult",
60
+ "rawResults",
61
+ "rawNestedResults",
62
+ "fullOutput",
63
+ "full_result",
64
+ "toolOutput",
65
+ "toolResultRaw",
66
+ "stdout",
67
+ "rawOutput",
68
+ "recentOutput",
69
+ "currentToolArgs",
70
+ "inflightTaskDetails",
71
+ ]);
72
+
73
+ function truncateText(value: string | undefined, maxChars: number): string | undefined {
74
+ if (!value) return undefined;
75
+ return value.length > maxChars ? value.slice(0, maxChars) : value;
76
+ }
77
+
78
+ function buildSafeSynopsis(raw: SingleResult, outputRef: TaskResultReceipt["outputRef"]): string {
79
+ const status = getStatus(raw);
80
+ if (raw.retryFailure) {
81
+ return `Task ${status}; retry stopped after attempt ${raw.retryFailure.attempt}.`;
82
+ }
83
+ if (raw.abortReason) {
84
+ return `Task ${status}; abort reason recorded.`;
85
+ }
86
+ if (raw.error) {
87
+ return `Task ${status}; error recorded.`;
88
+ }
89
+ if (outputRef) {
90
+ return `Task ${status}; output stored in ${outputRef.uri} (${outputRef.lineCount} lines, ${outputRef.sizeBytes} bytes).`;
91
+ }
92
+ return `Task ${status}; output artifact unavailable.`;
93
+ }
94
+
95
+ function getStatus(raw: SingleResult): TaskResultReceipt["status"] {
96
+ if (raw.paused) return "paused";
97
+ if (raw.aborted) return "aborted";
98
+ if (raw.exitCode === 0 && raw.error) return "merge_failed";
99
+ if (raw.exitCode !== 0 || raw.error) return "failed";
100
+ return "completed";
101
+ }
102
+
103
+ function buildReview(raw: SingleResult): TaskResultReceipt["review"] | undefined {
104
+ const data = raw.extractedToolData;
105
+ if (!data) return undefined;
106
+ const yields = Array.isArray(data.yield) ? data.yield : [];
107
+ const reviewYield = yields
108
+ .map(item => (item && typeof item === "object" ? (item as { data?: unknown }).data : undefined))
109
+ .findLast(item => item && typeof item === "object" && "overall_correctness" in item) as
110
+ | { overall_correctness?: unknown }
111
+ | undefined;
112
+ const rawFindings = Array.isArray(data.report_finding) ? data.report_finding : [];
113
+ const findings = rawFindings.slice(0, 20).map(item => {
114
+ const value = item && typeof item === "object" ? (item as Record<string, unknown>) : {};
115
+ const severity =
116
+ typeof value.severity === "string"
117
+ ? value.severity
118
+ : typeof value.priority === "string"
119
+ ? value.priority
120
+ : undefined;
121
+ const summaryValue = value.summary ?? value.title ?? value.message ?? value.body ?? "finding";
122
+ return { severity, summary: truncateText(String(summaryValue), 200) ?? "finding" };
123
+ });
124
+ if (!reviewYield && findings.length === 0) return undefined;
125
+ return {
126
+ overallCorrectness:
127
+ typeof reviewYield?.overall_correctness === "string" ? reviewYield.overall_correctness : undefined,
128
+ findingCount: rawFindings.length,
129
+ findings: findings.length > 0 ? findings : undefined,
130
+ };
131
+ }
132
+
133
+ function hasReviewFindings(raw: SingleResult): boolean {
134
+ const findings = raw.extractedToolData?.report_finding;
135
+ return Array.isArray(findings) && findings.length > 0;
136
+ }
137
+
138
+ function hasNonEmptyPreview(raw: SingleResult): boolean {
139
+ return Boolean((raw.output.trim() || raw.stderr.trim()).trim());
140
+ }
141
+
142
+ /**
143
+ * Heuristic task ROI signal built only from receipt-safe accounting fields.
144
+ * Advisory only: these flags never change task success/failure semantics.
145
+ */
146
+ export function buildTaskRoi(raw: SingleResult): TaskRoi {
147
+ const outputBytes = raw.outputMeta?.byteSize ?? (raw.outputMeta ? Buffer.byteLength(raw.output, "utf8") : undefined);
148
+ const outputLines = raw.outputMeta?.lineCount;
149
+ const producedChanges =
150
+ raw.producedChanges ??
151
+ Boolean(raw.branchName || (Array.isArray(raw.nestedPatches) && raw.nestedPatches.length > 0));
152
+ const status = getStatus(raw);
153
+ const terminal = status !== "paused" && !raw.aborted;
154
+ const materialContribution = Boolean(
155
+ producedChanges ||
156
+ (outputBytes !== undefined && outputBytes > 0) ||
157
+ hasReviewFindings(raw) ||
158
+ (status === "completed" && raw.tokens > 0 && hasNonEmptyPreview(raw)),
159
+ );
160
+ const lowRoi = terminal && raw.tokens > 0 && !materialContribution;
161
+ return {
162
+ tokens: raw.tokens,
163
+ contextTokens: raw.contextTokens,
164
+ clonedTokens: raw.forkContext?.clonedTokens,
165
+ costTotal: raw.usage?.cost.total,
166
+ outputBytes,
167
+ outputLines,
168
+ producedChanges,
169
+ materialContribution,
170
+ lowRoi,
171
+ };
172
+ }
173
+
174
+ export function buildTaskRoiSummary(receipts: readonly TaskResultReceipt[]): TaskToolDetails["roiSummary"] {
175
+ const totalCostTotal = receipts.reduce((total, receipt) => total + (receipt.roi?.costTotal ?? 0), 0);
176
+ const totalClonedTokens = receipts.reduce((total, receipt) => total + (receipt.roi?.clonedTokens ?? 0), 0);
177
+ return {
178
+ childCount: receipts.length,
179
+ totalTokens: receipts.reduce((total, receipt) => total + (receipt.roi?.tokens ?? receipt.tokens), 0),
180
+ totalCostTotal: totalCostTotal > 0 ? totalCostTotal : undefined,
181
+ totalClonedTokens: totalClonedTokens > 0 ? totalClonedTokens : undefined,
182
+ lowRoiChildIds: receipts.filter(receipt => receipt.roi?.lowRoi).map(receipt => receipt.id),
183
+ };
184
+ }
185
+
186
+ export function buildTaskReceipt(raw: SingleResult): TaskResultReceipt {
187
+ const outputRef = raw.outputMeta
188
+ ? {
189
+ uri: `agent://${raw.id}`,
190
+ sizeBytes: raw.outputMeta.byteSize ?? Buffer.byteLength(raw.output, "utf8"),
191
+ lineCount: raw.outputMeta.lineCount,
192
+ sha256: raw.outputMeta.sha256,
193
+ }
194
+ : undefined;
195
+ const preview = buildSafeSynopsis(raw, outputRef);
196
+ const extractedToolCounts = raw.extractedToolData
197
+ ? Object.fromEntries(
198
+ Object.entries(raw.extractedToolData).map(([tool, values]) => [
199
+ tool,
200
+ Array.isArray(values) ? values.length : 0,
201
+ ]),
202
+ )
203
+ : undefined;
204
+ return {
205
+ index: raw.index,
206
+ id: raw.id,
207
+ agent: raw.agent,
208
+ agentSource: raw.agentSource,
209
+ task: raw.task,
210
+ assignment: raw.assignment,
211
+ description: raw.description,
212
+ status: getStatus(raw),
213
+ exitCode: raw.exitCode,
214
+ aborted: raw.aborted,
215
+ paused: raw.paused,
216
+ truncated: raw.truncated,
217
+ durationMs: raw.durationMs,
218
+ tokens: raw.tokens,
219
+ contextTokens: raw.contextTokens,
220
+ contextWindow: raw.contextWindow,
221
+ modelOverride: raw.modelOverride,
222
+ usage: raw.usage,
223
+ cost: raw.usage?.cost.total,
224
+ branchName: raw.branchName,
225
+ retryFailure: raw.retryFailure
226
+ ? { attempt: raw.retryFailure.attempt, errorSummary: "Retry failure recorded." }
227
+ : undefined,
228
+ errorSummary: raw.error ? "Error recorded." : undefined,
229
+ abortSummary: raw.abortReason ? "Abort reason recorded." : undefined,
230
+ preview,
231
+ previewTruncated: false,
232
+ outputRef,
233
+ outputUnavailable: outputRef ? undefined : true,
234
+ review: buildReview(raw),
235
+ extractedToolCounts,
236
+ forkContext: raw.forkContext,
237
+ roi: buildTaskRoi(raw),
238
+ };
239
+ }
240
+
241
+ /**
242
+ * Raw, pre-sanitization task details: the internal shape produced during task
243
+ * execution, where `results` are full `SingleResult` objects. The public
244
+ * `TaskToolDetails` exposes only receipts.
245
+ */
246
+ export interface RawTaskToolDetails {
247
+ projectAgentsDir: string | null;
248
+ results: SingleResult[];
249
+ totalDurationMs: number;
250
+ usage?: TaskToolDetails["usage"];
251
+ async?: TaskToolDetails["async"];
252
+ forkContextClonedTokens?: number;
253
+ roiSummary?: TaskToolDetails["roiSummary"];
254
+ }
255
+
256
+ /** Central converter from raw task details to receipt-only public details. */
257
+ export function sanitizeTaskToolDetails(raw: RawTaskToolDetails): TaskToolDetails {
258
+ return {
259
+ projectAgentsDir: raw.projectAgentsDir,
260
+ results: raw.results.map(buildTaskReceipt),
261
+ totalDurationMs: raw.totalDurationMs,
262
+ usage: raw.usage,
263
+ forkContextClonedTokens: raw.forkContextClonedTokens,
264
+ roiSummary: raw.roiSummary ?? buildTaskRoiSummary(raw.results.map(buildTaskReceipt)),
265
+ async: raw.async,
266
+ };
267
+ }
268
+
269
+ export function findRawTaskLeakKeys(value: unknown): string[] {
270
+ const found = new Set<string>();
271
+ const seen = new WeakSet<object>();
272
+ const visit = (current: unknown) => {
273
+ if (!current || typeof current !== "object") return;
274
+ if (seen.has(current)) return;
275
+ seen.add(current);
276
+ if (Array.isArray(current)) {
277
+ for (const item of current) visit(item);
278
+ return;
279
+ }
280
+ for (const [key, child] of Object.entries(current)) {
281
+ // Banned keys only leak when they carry text or structure. A numeric
282
+ // value (e.g. the `output` token count on a canonical `Usage` record,
283
+ // whose shape is `input/output/cacheRead/cacheWrite/totalTokens`) is safe.
284
+ if (BANNED_RAW_TASK_KEYS.has(key) && typeof child !== "number") found.add(key);
285
+ visit(child);
286
+ }
287
+ };
288
+ visit(value);
289
+ return [...found].sort();
290
+ }
291
+
292
+ export function assertNoRawTaskFields(value: unknown, surface: string): void {
293
+ const keys = findRawTaskLeakKeys(value);
294
+ if (keys.length > 0) {
295
+ throw new Error(`${surface} contains raw task fields: ${keys.join(", ")}`);
296
+ }
297
+ }
@@ -6,12 +6,13 @@
6
6
  */
7
7
  import path from "node:path";
8
8
  import type { Component } from "@gajae-code/tui";
9
- import { Container, Text } from "@gajae-code/tui";
9
+ import { Text } from "@gajae-code/tui";
10
10
  import { formatNumber } from "@gajae-code/utils";
11
11
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
12
12
  import type { Theme } from "../modes/theme/theme";
13
13
  import {
14
14
  formatBadge,
15
+ formatBytes,
15
16
  formatDuration,
16
17
  formatMoreItems,
17
18
  formatStatusIcon,
@@ -27,8 +28,9 @@ import {
27
28
  type SubmitReviewDetails,
28
29
  } from "../tools/review";
29
30
  import { Ellipsis, Hasher, type RenderCache, renderStatusLine } from "../tui";
31
+ import type { TaskResultReceipt } from "./receipt";
30
32
  import { subprocessToolRegistry } from "./subprocess-tool-registry";
31
- import type { AgentProgress, SingleResult, TaskParams, TaskToolDetails } from "./types";
33
+ import type { AgentProgress, TaskParams, TaskToolDetails } from "./types";
32
34
 
33
35
  /**
34
36
  * Get status icon for agent state.
@@ -47,6 +49,8 @@ function getStatusIcon(status: AgentProgress["status"], theme: Theme, spinnerFra
47
49
  return formatStatusIcon("error", theme);
48
50
  case "aborted":
49
51
  return formatStatusIcon("aborted", theme);
52
+ case "paused":
53
+ return formatStatusIcon("pending", theme);
50
54
  }
51
55
  }
52
56
 
@@ -137,21 +141,6 @@ function formatTaskId(id: string): string {
137
141
  return `${indices} ${labels}`;
138
142
  }
139
143
 
140
- const MISSING_YIELD_WARNING_PREFIX = "SYSTEM WARNING: Subagent exited without calling yield tool";
141
-
142
- function extractMissingYieldWarning(output: string): { warning?: string; rest: string } {
143
- const lines = output.split("\n");
144
- const firstLine = lines[0]?.trim() ?? "";
145
- if (!firstLine.startsWith(MISSING_YIELD_WARNING_PREFIX)) {
146
- return { rest: output };
147
- }
148
- const rest = lines
149
- .slice(1)
150
- .join("\n")
151
- .replace(/^\s*\n+/, "");
152
- return { warning: firstLine, rest };
153
- }
154
-
155
144
  function buildTreePrefix(ancestors: boolean[], theme: Theme): string {
156
145
  return ancestors.map(hasNext => (hasNext ? `${theme.tree.vertical} ` : " ")).join("");
157
146
  }
@@ -611,9 +600,10 @@ function renderAgentProgress(
611
600
  if (progress.retryState && progress.status === "running") {
612
601
  const remainingMs = Math.max(0, progress.retryState.startedAtMs + progress.retryState.delayMs - Date.now());
613
602
  const waitLabel = remainingMs > 0 ? `in ${formatDuration(remainingMs)}` : "now";
614
- const summary =
615
- `retrying ${progress.retryState.attempt}/${progress.retryState.maxAttempts} ${waitLabel}: ` +
616
- truncateToWidth(replaceTabs(progress.retryState.errorMessage), 60);
603
+ const attemptLabel = progress.retryState.unbounded
604
+ ? `attempt ${progress.retryState.attempt}`
605
+ : `${progress.retryState.attempt}/${progress.retryState.maxAttempts}`;
606
+ const summary = `retrying ${attemptLabel} ${waitLabel}: ${truncateToWidth(replaceTabs(progress.retryState.errorMessage), 60)}`;
617
607
  lines.push(`${continuePrefix}${theme.tree.hook} ${theme.fg("warning", summary)}`);
618
608
  } else if (progress.retryFailure && progress.status !== "running") {
619
609
  const summary = `auto-retry gave up after ${progress.retryFailure.attempt} attempt${
@@ -801,35 +791,24 @@ function renderFindings(
801
791
  /**
802
792
  * Render final result for a single agent.
803
793
  */
804
- function renderAgentResult(result: SingleResult, isLast: boolean, expanded: boolean, theme: Theme): string[] {
794
+ function renderAgentResult(result: TaskResultReceipt, isLast: boolean, expanded: boolean, theme: Theme): string[] {
805
795
  const lines: string[] = [];
806
796
  const prefix = isLast ? theme.fg("dim", theme.tree.last) : theme.fg("dim", theme.tree.branch);
807
797
  const continuePrefix = isLast ? " " : `${theme.fg("dim", theme.tree.vertical)} `;
808
798
 
809
- const { warning: missingCompleteWarning, rest: outputWithoutWarning } = extractMissingYieldWarning(result.output);
810
- const aborted = result.aborted ?? false;
811
- const mergeFailed = !aborted && result.exitCode === 0 && !!result.error;
812
- const success = !aborted && result.exitCode === 0 && !result.error;
813
- const needsWarning = Boolean(missingCompleteWarning) && success;
814
- const icon = aborted
815
- ? theme.status.aborted
816
- : needsWarning
817
- ? theme.status.warning
818
- : success
819
- ? theme.status.success
799
+ const success = result.status === "completed";
800
+ const mergeFailed = result.status === "merge_failed";
801
+ const aborted = result.status === "aborted";
802
+ const icon = success
803
+ ? theme.status.success
804
+ : aborted
805
+ ? theme.status.aborted
806
+ : mergeFailed
807
+ ? theme.status.warning
820
808
  : theme.status.error;
821
- const iconColor = needsWarning ? "warning" : success ? "success" : mergeFailed ? "warning" : "error";
822
- const statusText = aborted
823
- ? "aborted"
824
- : needsWarning
825
- ? "warning"
826
- : success
827
- ? "done"
828
- : mergeFailed
829
- ? "merge failed"
830
- : "failed";
809
+ const iconColor = success ? "success" : mergeFailed ? "warning" : "error";
810
+ const statusText = mergeFailed ? "merge failed" : success ? "done" : result.status;
831
811
 
832
- // Main status line: id: description [status] · stats · ⟨agent⟩
833
812
  const description = result.description?.trim();
834
813
  const displayId = formatTaskId(result.id);
835
814
  const titlePart = description ? `${theme.bold(displayId)}: ${description}` : displayId;
@@ -844,121 +823,62 @@ function renderAgentResult(result: SingleResult, isLast: boolean, expanded: bool
844
823
  tokens: result.tokens,
845
824
  contextTokens: result.contextTokens,
846
825
  contextWindow: result.contextWindow,
847
- cost: result.usage?.cost.total ?? 0,
826
+ cost: result.cost ?? result.usage?.cost.total ?? 0,
848
827
  },
849
828
  theme,
850
829
  );
851
830
  statusLine += `${theme.sep.dot}${theme.fg("dim", formatDuration(result.durationMs))}`;
852
-
853
- if (result.truncated) {
831
+ if (result.truncated || result.previewTruncated) {
854
832
  statusLine += ` ${theme.fg("warning", "[truncated]")}`;
855
833
  }
856
-
857
834
  lines.push(statusLine);
858
835
 
859
836
  lines.push(...renderTaskSection(result.assignment ?? result.task, continuePrefix, expanded, theme));
860
837
 
861
- if (aborted && result.abortReason) {
862
- lines.push(
863
- `${continuePrefix}${theme.fg("error", theme.status.aborted)} ${theme.fg("dim", truncateToWidth(replaceTabs(result.abortReason), 80))}`,
864
- );
865
- }
866
- // Check for review result (yield with review schema + report_finding)
867
- const completeData = result.extractedToolData?.yield as Array<{ data: unknown }> | undefined;
868
- const reportFindingData = normalizeReportFindings(result.extractedToolData?.report_finding);
869
-
870
- // Extract review verdict from yield tool's data field if it matches SubmitReviewDetails
871
- const reviewData = completeData
872
- ?.map(c => c.data as SubmitReviewDetails)
873
- .filter(d => d && typeof d === "object" && "overall_correctness" in d);
874
- const submitReviewData = reviewData && reviewData.length > 0 ? reviewData : undefined;
875
-
876
- if (submitReviewData && submitReviewData.length > 0) {
877
- // Use combined review renderer
878
- const summary = submitReviewData[submitReviewData.length - 1];
879
- const findings = reportFindingData;
880
- lines.push(...renderReviewResult(summary, findings, continuePrefix, expanded, theme));
881
- return lines;
882
- }
883
- if (reportFindingData.length > 0) {
884
- const hasCompleteData = completeData && completeData.length > 0;
885
- const message = hasCompleteData
886
- ? "Review verdict missing expected fields"
887
- : "Review incomplete (yield not called)";
888
- lines.push(`${continuePrefix}${theme.fg("warning", theme.status.warning)} ${theme.fg("dim", message)}`);
889
- lines.push(`${continuePrefix}${formatFindingSummary(reportFindingData, theme)}`);
890
- lines.push(...renderFindings(reportFindingData, continuePrefix, expanded, theme));
891
- return lines;
892
- }
893
-
894
- // Check for extracted tool data with custom renderers (skip review tools)
895
- let hasCustomRendering = false;
896
- const deferredToolLines: string[] = [];
897
- if (result.extractedToolData) {
898
- for (const [toolName, dataArray] of Object.entries(result.extractedToolData)) {
899
- // Skip review tools - handled above
900
- if (toolName === "yield" || toolName === "report_finding") continue;
901
-
902
- const handler = subprocessToolRegistry.getHandler(toolName);
903
- if (handler?.renderFinal && (dataArray as unknown[]).length > 0) {
904
- const isTaskTool = toolName === "task";
905
- const component = handler.renderFinal(dataArray as unknown[], theme, expanded);
906
- const target = isTaskTool ? deferredToolLines : lines;
907
- if (!isTaskTool) {
908
- hasCustomRendering = true;
909
- target.push(`${continuePrefix}${theme.fg("dim", `Tool: ${toolName}`)}`);
910
- }
911
- if (component instanceof Text) {
912
- // Prefix each line with continuePrefix
913
- const text = component.getText();
914
- for (const line of text.split("\n")) {
915
- target.push(`${continuePrefix}${line}`);
916
- }
917
- } else if (component instanceof Container) {
918
- // For containers, render each child
919
- for (const child of (component as Container).children) {
920
- if (child instanceof Text) {
921
- target.push(`${continuePrefix}${child.getText()}`);
922
- }
923
- }
838
+ if (result.review) {
839
+ if (result.review.overallCorrectness) {
840
+ lines.push(`${continuePrefix}${theme.fg("dim", `Review: ${result.review.overallCorrectness}`)}`);
841
+ }
842
+ if (result.review.findingCount > 0) {
843
+ lines.push(`${continuePrefix}${theme.fg("dim", `${result.review.findingCount} findings`)}`);
844
+ if (expanded && result.review.findings) {
845
+ for (const finding of result.review.findings) {
846
+ const severity = finding.severity ? `${finding.severity}: ` : "";
847
+ lines.push(`${continuePrefix}${theme.fg("dim", `- ${severity}${finding.summary}`)}`);
924
848
  }
925
849
  }
926
850
  }
851
+ } else {
852
+ lines.push(...renderOutputSection(result.preview, continuePrefix, expanded, theme, 3, 12));
853
+ }
854
+ if (result.roi?.lowRoi) {
855
+ lines.push(`${continuePrefix}${theme.fg("warning", "low ROI: produced no material contribution")}`);
927
856
  }
928
857
 
929
- if (hasCustomRendering && missingCompleteWarning) {
858
+ if (result.outputRef) {
930
859
  lines.push(
931
- `${continuePrefix}${theme.fg("warning", theme.status.warning)} ${theme.fg(
860
+ `${continuePrefix}${theme.fg(
932
861
  "dim",
933
- truncateToWidth(missingCompleteWarning, 80),
862
+ `Output: ${result.outputRef.uri} (${formatBytes(result.outputRef.sizeBytes)}, ${result.outputRef.lineCount} lines)`,
934
863
  )}`,
935
864
  );
865
+ } else if (result.outputUnavailable) {
866
+ lines.push(`${continuePrefix}${theme.fg("dim", "Output artifact unavailable")}`);
936
867
  }
937
868
 
938
- // Fallback to output preview if no custom rendering
939
- if (!hasCustomRendering) {
869
+ if (result.branchName && success) {
870
+ lines.push(`${continuePrefix}${theme.fg("dim", `Branch: ${result.branchName}`)}`);
871
+ }
872
+ if (result.abortSummary) {
940
873
  lines.push(
941
- ...renderOutputSection(outputWithoutWarning, continuePrefix, expanded, theme, 3, 12, missingCompleteWarning),
874
+ `${continuePrefix}${theme.fg("error", theme.status.aborted)} ${theme.fg("dim", truncateToWidth(replaceTabs(result.abortSummary), 80))}`,
942
875
  );
943
876
  }
944
-
945
- if (deferredToolLines.length > 0) {
946
- lines.push(...deferredToolLines);
947
- }
948
-
949
- if (result.patchPath && !aborted && result.exitCode === 0) {
950
- lines.push(`${continuePrefix}${theme.fg("dim", `Patch: ${result.patchPath}`)}`);
951
- } else if (result.branchName && !aborted && result.exitCode === 0) {
952
- lines.push(`${continuePrefix}${theme.fg("dim", `Branch: ${result.branchName}`)}`);
953
- }
954
-
955
- // Error message
956
- if (result.error && (!success || mergeFailed) && (!aborted || result.error !== result.abortReason)) {
877
+ if (result.errorSummary && (!success || mergeFailed)) {
957
878
  lines.push(
958
- `${continuePrefix}${theme.fg(mergeFailed ? "warning" : "error", truncateToWidth(replaceTabs(result.error), 70))}`,
879
+ `${continuePrefix}${theme.fg(mergeFailed ? "warning" : "error", truncateToWidth(replaceTabs(result.errorSummary), 70))}`,
959
880
  );
960
881
  }
961
-
962
882
  return lines;
963
883
  }
964
884
 
@@ -1006,9 +926,9 @@ export function renderResult(
1006
926
  lines.push(...renderAgentResult(res, isLast, expanded, theme));
1007
927
  });
1008
928
 
1009
- const abortedCount = details.results.filter(r => r.aborted).length;
1010
- const mergeFailedCount = details.results.filter(r => !r.aborted && r.exitCode === 0 && r.error).length;
1011
- const successCount = details.results.filter(r => !r.aborted && r.exitCode === 0 && !r.error).length;
929
+ const abortedCount = details.results.filter(r => r.status === "aborted").length;
930
+ const mergeFailedCount = details.results.filter(r => r.status === "merge_failed").length;
931
+ const successCount = details.results.filter(r => r.status === "completed").length;
1012
932
  const failCount = details.results.length - successCount - mergeFailedCount - abortedCount;
1013
933
  let summary = `${theme.fg("dim", "Total:")} `;
1014
934
  if (abortedCount > 0) {