@abelxiaoxing/cadence 1.8.4 → 2.0.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 (202) hide show
  1. package/README.md +324 -117
  2. package/THIRD_PARTY_NOTICES.md +15 -0
  3. package/agents/design-explorer.md +14 -18
  4. package/agents/diagnosis-worker.md +14 -14
  5. package/agents/implementation-worker.md +16 -17
  6. package/config/workflow-task.example.json +7 -0
  7. package/docs/remote-access.md +60 -0
  8. package/docs/reuse-and-distribution.md +44 -0
  9. package/docs/service-management.md +108 -0
  10. package/licenses/bundled-web.txt +6245 -0
  11. package/licenses/pi-web-ui-MIT.txt +21 -0
  12. package/licenses/pi-web-ui-reuse.md +22 -0
  13. package/package.json +92 -20
  14. package/src/index.ts +21 -1802
  15. package/src/native-build.ts +6 -0
  16. package/src/native-client.ts +103 -0
  17. package/src/native-export.ts +58 -0
  18. package/src/native-history.ts +359 -0
  19. package/src/operator-cli.mjs +453 -1937
  20. package/src/plan-proposal.ts +87 -0
  21. package/src/sdk-worker.mjs +549 -0
  22. package/src/service/contracts/active.ts +82 -0
  23. package/src/service/contracts/budget.ts +148 -0
  24. package/src/service/contracts/build.ts +67 -0
  25. package/src/service/contracts/capture.ts +48 -0
  26. package/src/service/contracts/core.ts +33 -0
  27. package/src/service/contracts/current.ts +261 -0
  28. package/src/service/contracts/domain.ts +68 -0
  29. package/src/service/contracts/history.ts +2245 -0
  30. package/src/service/contracts/models.ts +139 -0
  31. package/src/service/coordination.ts +862 -0
  32. package/src/service/private-files.ts +204 -0
  33. package/src/service-cli.mjs +5592 -0
  34. package/src/service-client.ts +747 -0
  35. package/src/service-contract.ts +245 -0
  36. package/src/service-host.mjs +22382 -0
  37. package/src/service-http-client.ts +425 -0
  38. package/src/service-wire.ts +372 -0
  39. package/src/task-shell.ts +1045 -0
  40. package/src/task-store.ts +479 -0
  41. package/src/task-types.ts +184 -0
  42. package/src/verification-diagnostics.ts +29 -72
  43. package/src/worker-capture.mjs +1197 -0
  44. package/src/worker-public.mjs +1753 -0
  45. package/src/worker-redaction.mjs +608 -0
  46. package/src/worker-tools.mjs +202 -0
  47. package/web/dist/assets/KaTeX_AMS-Regular.ttf +0 -0
  48. package/web/dist/assets/KaTeX_AMS-Regular.woff +0 -0
  49. package/web/dist/assets/KaTeX_AMS-Regular.woff2 +0 -0
  50. package/web/dist/assets/KaTeX_Caligraphic-Bold.ttf +0 -0
  51. package/web/dist/assets/KaTeX_Caligraphic-Bold.woff +0 -0
  52. package/web/dist/assets/KaTeX_Caligraphic-Bold.woff2 +0 -0
  53. package/web/dist/assets/KaTeX_Caligraphic-Regular.ttf +0 -0
  54. package/web/dist/assets/KaTeX_Caligraphic-Regular.woff +0 -0
  55. package/web/dist/assets/KaTeX_Caligraphic-Regular.woff2 +0 -0
  56. package/web/dist/assets/KaTeX_Fraktur-Bold.ttf +0 -0
  57. package/web/dist/assets/KaTeX_Fraktur-Bold.woff +0 -0
  58. package/web/dist/assets/KaTeX_Fraktur-Bold.woff2 +0 -0
  59. package/web/dist/assets/KaTeX_Fraktur-Regular.ttf +0 -0
  60. package/web/dist/assets/KaTeX_Fraktur-Regular.woff +0 -0
  61. package/web/dist/assets/KaTeX_Fraktur-Regular.woff2 +0 -0
  62. package/web/dist/assets/KaTeX_Main-Bold.ttf +0 -0
  63. package/web/dist/assets/KaTeX_Main-Bold.woff +0 -0
  64. package/web/dist/assets/KaTeX_Main-Bold.woff2 +0 -0
  65. package/web/dist/assets/KaTeX_Main-BoldItalic.ttf +0 -0
  66. package/web/dist/assets/KaTeX_Main-BoldItalic.woff +0 -0
  67. package/web/dist/assets/KaTeX_Main-BoldItalic.woff2 +0 -0
  68. package/web/dist/assets/KaTeX_Main-Italic.ttf +0 -0
  69. package/web/dist/assets/KaTeX_Main-Italic.woff +0 -0
  70. package/web/dist/assets/KaTeX_Main-Italic.woff2 +0 -0
  71. package/web/dist/assets/KaTeX_Main-Regular.ttf +0 -0
  72. package/web/dist/assets/KaTeX_Main-Regular.woff +0 -0
  73. package/web/dist/assets/KaTeX_Main-Regular.woff2 +0 -0
  74. package/web/dist/assets/KaTeX_Math-BoldItalic.ttf +0 -0
  75. package/web/dist/assets/KaTeX_Math-BoldItalic.woff +0 -0
  76. package/web/dist/assets/KaTeX_Math-BoldItalic.woff2 +0 -0
  77. package/web/dist/assets/KaTeX_Math-Italic.ttf +0 -0
  78. package/web/dist/assets/KaTeX_Math-Italic.woff +0 -0
  79. package/web/dist/assets/KaTeX_Math-Italic.woff2 +0 -0
  80. package/web/dist/assets/KaTeX_SansSerif-Bold.ttf +0 -0
  81. package/web/dist/assets/KaTeX_SansSerif-Bold.woff +0 -0
  82. package/web/dist/assets/KaTeX_SansSerif-Bold.woff2 +0 -0
  83. package/web/dist/assets/KaTeX_SansSerif-Italic.ttf +0 -0
  84. package/web/dist/assets/KaTeX_SansSerif-Italic.woff +0 -0
  85. package/web/dist/assets/KaTeX_SansSerif-Italic.woff2 +0 -0
  86. package/web/dist/assets/KaTeX_SansSerif-Regular.ttf +0 -0
  87. package/web/dist/assets/KaTeX_SansSerif-Regular.woff +0 -0
  88. package/web/dist/assets/KaTeX_SansSerif-Regular.woff2 +0 -0
  89. package/web/dist/assets/KaTeX_Script-Regular.ttf +0 -0
  90. package/web/dist/assets/KaTeX_Script-Regular.woff +0 -0
  91. package/web/dist/assets/KaTeX_Script-Regular.woff2 +0 -0
  92. package/web/dist/assets/KaTeX_Size1-Regular.ttf +0 -0
  93. package/web/dist/assets/KaTeX_Size1-Regular.woff +0 -0
  94. package/web/dist/assets/KaTeX_Size1-Regular.woff2 +0 -0
  95. package/web/dist/assets/KaTeX_Size2-Regular.ttf +0 -0
  96. package/web/dist/assets/KaTeX_Size2-Regular.woff +0 -0
  97. package/web/dist/assets/KaTeX_Size2-Regular.woff2 +0 -0
  98. package/web/dist/assets/KaTeX_Size3-Regular.ttf +0 -0
  99. package/web/dist/assets/KaTeX_Size3-Regular.woff +0 -0
  100. package/web/dist/assets/KaTeX_Size3-Regular.woff2 +0 -0
  101. package/web/dist/assets/KaTeX_Size4-Regular.ttf +0 -0
  102. package/web/dist/assets/KaTeX_Size4-Regular.woff +0 -0
  103. package/web/dist/assets/KaTeX_Size4-Regular.woff2 +0 -0
  104. package/web/dist/assets/KaTeX_Typewriter-Regular.ttf +0 -0
  105. package/web/dist/assets/KaTeX_Typewriter-Regular.woff +0 -0
  106. package/web/dist/assets/KaTeX_Typewriter-Regular.woff2 +0 -0
  107. package/web/dist/assets/cadence.css +2 -0
  108. package/web/dist/assets/cadence.js +7 -0
  109. package/web/dist/assets/markdown.js +296 -0
  110. package/web/dist/assets/react.js +9 -0
  111. package/web/dist/assets/rolldown-runtime.js +1 -0
  112. package/web/dist/assets/timeline-client.js +41 -0
  113. package/web/dist/assets/vis-timeline.css +1274 -0
  114. package/web/dist/assets/vis-timeline.js +32 -0
  115. package/web/dist/index.html +18 -0
  116. package/web/dist/manifest.json +505 -0
  117. package/config/plan-draft.example.json +0 -125
  118. package/config/plan-draft.multiple-tasks.example.json +0 -375
  119. package/config/plan-draft.quick.example.json +0 -78
  120. package/prompts/abel-design.md +0 -382
  121. package/prompts/abel-diagnose.md +0 -71
  122. package/prompts/abel-implement.md +0 -284
  123. package/prompts/abel-init.md +0 -52
  124. package/src/activation.ts +0 -44
  125. package/src/activity-contracts.ts +0 -82
  126. package/src/agent-registry.ts +0 -53
  127. package/src/apply-transaction.ts +0 -1437
  128. package/src/artifact-store.ts +0 -213
  129. package/src/build-windows-job.mjs +0 -95
  130. package/src/candidate-artifact.ts +0 -143
  131. package/src/candidate-context.ts +0 -289
  132. package/src/candidate-patch.ts +0 -271
  133. package/src/candidate-workspace.ts +0 -80
  134. package/src/canonical.ts +0 -16
  135. package/src/change-contract-schema.ts +0 -191
  136. package/src/change-contract.ts +0 -324
  137. package/src/change-verification.ts +0 -1617
  138. package/src/contracts.ts +0 -2466
  139. package/src/control-contracts.ts +0 -382
  140. package/src/delivery-compiler.ts +0 -1805
  141. package/src/delivery-revision.ts +0 -42
  142. package/src/design-control.ts +0 -1299
  143. package/src/design-diagnostics.ts +0 -181
  144. package/src/design-journal.ts +0 -1773
  145. package/src/directory-sync.ts +0 -36
  146. package/src/durable-contracts.ts +0 -227
  147. package/src/durable-workflow.ts +0 -822
  148. package/src/evidence-draft.ts +0 -122
  149. package/src/execution-profile.ts +0 -64
  150. package/src/execution-retention.ts +0 -77
  151. package/src/file-snapshot.ts +0 -163
  152. package/src/implement-continuation.ts +0 -559
  153. package/src/implement-graph.ts +0 -690
  154. package/src/implement-plan.ts +0 -72
  155. package/src/install-config.d.mts +0 -6
  156. package/src/isolation-backend.ts +0 -353
  157. package/src/model-source.ts +0 -22
  158. package/src/openspec-cli.ts +0 -494
  159. package/src/operator-cli-entry.mjs +0 -24
  160. package/src/operator-tools.ts +0 -309
  161. package/src/package-candidate-process.ts +0 -206
  162. package/src/package-candidate.ts +0 -24
  163. package/src/package-delivery.ts +0 -486
  164. package/src/package-state.ts +0 -320
  165. package/src/package-verification.ts +0 -635
  166. package/src/package-workflow.ts +0 -305
  167. package/src/packet-runtime.ts +0 -459
  168. package/src/phase-execution.ts +0 -2163
  169. package/src/pi-adapter.ts +0 -19
  170. package/src/plan-draft-summary.ts +0 -527
  171. package/src/plan-draft.ts +0 -502
  172. package/src/remove-path.ts +0 -32
  173. package/src/run-state.ts +0 -420
  174. package/src/run-store.ts +0 -1179
  175. package/src/safe-path.ts +0 -77
  176. package/src/scoped-grep-worker.mjs +0 -23
  177. package/src/scoped-tools.ts +0 -775
  178. package/src/single-task-draft.ts +0 -142
  179. package/src/sqlite-schema.ts +0 -171
  180. package/src/state-root.ts +0 -215
  181. package/src/storage-schema.ts +0 -360
  182. package/src/subagent-activity.ts +0 -1417
  183. package/src/subagent-process.ts +0 -476
  184. package/src/task-ledger.ts +0 -1338
  185. package/src/transport-budget.ts +0 -50
  186. package/src/verification-capability.ts +0 -1191
  187. package/src/verification-environment-io.ts +0 -389
  188. package/src/verification-environment.ts +0 -162
  189. package/src/verification-identity.ts +0 -77
  190. package/src/verification-scan.ts +0 -64
  191. package/src/windows-job-backend.ts +0 -419
  192. package/src/windows-job.c +0 -149
  193. package/src/workflow-engine.ts +0 -28
  194. package/src/workflow-policy.ts +0 -1670
  195. package/src/workflow-recovery-policy.ts +0 -97
  196. package/src/workflow-scheduling.ts +0 -105
  197. package/src/workflow-state-machine.ts +0 -4341
  198. package/src/workflow-status.ts +0 -576
  199. package/src/workspace-io-entry.mjs +0 -76
  200. package/src/workspace-io-worker.mjs +0 -1312
  201. package/src/workspace-io.ts +0 -89
  202. package/src/workspace-store.ts +0 -634
@@ -1,476 +0,0 @@
1
- import { type ChildProcessWithoutNullStreams, spawn } from "node:child_process";
2
- import path from "node:path";
3
- import { StringDecoder } from "node:string_decoder";
4
- import type { Usage } from "@earendil-works/pi-ai";
5
- import { loadAgentDefinitions } from "./agent-registry.ts";
6
-
7
- export const SUBAGENT_LIMITS = Object.freeze({
8
- timeoutMs: 20 * 60_000,
9
- maxOutputBytes: 256 * 1024,
10
- terminateGraceMs: 1_000,
11
- maxPromptBytes: 128 * 1024,
12
- });
13
-
14
- export type SubagentRole =
15
- | "design-explorer"
16
- | "implementation-worker"
17
- | "diagnosis-worker";
18
-
19
- export interface SubagentCapabilities {
20
- readonly tools: readonly string[];
21
- readonly writes: boolean;
22
- }
23
-
24
- export const SUBAGENT_CAPABILITIES: Readonly<
25
- Record<SubagentRole, SubagentCapabilities>
26
- > = Object.freeze({
27
- "design-explorer": { tools: ["read", "grep", "find", "ls"], writes: false },
28
- "implementation-worker": {
29
- tools: ["read", "grep", "find", "ls", "write", "edit", "bash"],
30
- writes: true,
31
- },
32
- "diagnosis-worker": {
33
- tools: ["read", "grep", "find", "ls"],
34
- writes: false,
35
- },
36
- });
37
-
38
- export type SubagentTerminalStatus =
39
- | "completed"
40
- | "failed"
41
- | "cancelled"
42
- | "timed-out"
43
- | "output-limit";
44
-
45
- export interface SubagentEvent {
46
- readonly type: string;
47
- readonly toolName?: string;
48
- readonly text?: string;
49
- }
50
-
51
- export interface SubagentProcessResult {
52
- readonly status: SubagentTerminalStatus;
53
- readonly finalText: string;
54
- readonly usage?: Usage;
55
- readonly stderr: string;
56
- readonly error?: string;
57
- readonly events: readonly SubagentEvent[];
58
- readonly outputBytes: number;
59
- readonly outputTruncated: boolean;
60
- readonly exitCode: number | null;
61
- }
62
-
63
- export interface SubagentProcessInput {
64
- readonly role: SubagentRole;
65
- readonly cwd: string;
66
- readonly prompt: string;
67
- readonly model?: { provider: string; id: string };
68
- readonly signal?: AbortSignal;
69
- readonly timeoutMs?: number;
70
- readonly maxOutputBytes?: number;
71
- readonly piExecutable?: string;
72
- readonly onEvent?: (event: SubagentEvent) => void;
73
- }
74
-
75
- const TRUNCATION_MARKER = "…[truncated]";
76
-
77
- function decodeUtf8(value: Buffer): string {
78
- const decoder = new TextDecoder("utf-8", { fatal: true });
79
- const maxBoundary = Math.min(3, value.length);
80
- // A bounded head may end, and a bounded tail may begin, in the middle of
81
- // one UTF-8 sequence. Try only those small boundary adjustments; valid
82
- // interior output is never discarded or rendered as U+FFFD.
83
- for (let start = 0; start <= maxBoundary; start += 1) {
84
- for (let end = value.length; end >= start; end -= 1) {
85
- if (value.length - end > 3) break;
86
- try {
87
- return decoder.decode(value.subarray(start, end));
88
- } catch {
89
- // Try the next possible boundary.
90
- }
91
- }
92
- }
93
- return value.toString("utf8");
94
- }
95
-
96
- function utf8Prefix(value: Buffer, limit: number): string {
97
- if (limit <= 0) return "";
98
- let end = Math.min(value.length, limit);
99
- while (end > 0 && (value[end - 1] & 0xc0) === 0x80) end -= 1;
100
- if (end < Math.min(value.length, limit) && end > 0) {
101
- const lead = value[end - 1];
102
- const expected = lead < 0x80 ? 1 : lead < 0xe0 ? 2 : lead < 0xf0 ? 3 : 4;
103
- if (expected <= Math.min(value.length, limit) - (end - 1)) {
104
- end = Math.min(value.length, limit);
105
- } else {
106
- end -= 1;
107
- }
108
- }
109
- return decodeUtf8(value.subarray(0, end));
110
- }
111
-
112
- function boundedText(value: string, limit: number): string {
113
- const bytes = Buffer.from(value, "utf8");
114
- if (bytes.length <= limit) return value;
115
- const markerBytes = Buffer.byteLength(TRUNCATION_MARKER, "utf8");
116
- if (limit <= markerBytes) return utf8Prefix(bytes, limit);
117
- return `${utf8Prefix(bytes, limit - markerBytes)}${TRUNCATION_MARKER}`;
118
- }
119
-
120
- class BoundedBuffer {
121
- #head = Buffer.alloc(0);
122
- #tail = Buffer.alloc(0);
123
- #bytes = 0;
124
- constructor(readonly limit: number) {}
125
- append(chunk: Buffer): void {
126
- this.#bytes += chunk.byteLength;
127
- if (this.limit <= 0) return;
128
- const headLimit = Math.ceil(this.limit / 2);
129
- if (this.#head.length < headLimit) {
130
- const take = Math.min(headLimit - this.#head.length, chunk.length);
131
- this.#head = Buffer.concat([this.#head, chunk.subarray(0, take)]);
132
- chunk = chunk.subarray(take);
133
- }
134
- const tailLimit = this.limit - headLimit;
135
- if (chunk.length > tailLimit) chunk = chunk.subarray(-tailLimit);
136
- if (chunk.length > 0 && tailLimit > 0) {
137
- this.#tail = Buffer.concat([this.#tail, chunk]).subarray(-tailLimit);
138
- }
139
- }
140
- get bytes(): number {
141
- return this.#bytes;
142
- }
143
- get truncated(): boolean {
144
- return this.#bytes > this.limit;
145
- }
146
- text(): string {
147
- return `${decodeUtf8(this.#head)}${decodeUtf8(this.#tail)}`;
148
- }
149
- }
150
-
151
- function textFromMessage(message: unknown): string {
152
- if (!message || typeof message !== "object") return "";
153
- const content = (message as { content?: unknown }).content;
154
- if (typeof content === "string") return content;
155
- if (!Array.isArray(content)) return "";
156
- return content
157
- .map((part) => {
158
- if (typeof part === "string") return part;
159
- if (!part || typeof part !== "object") return "";
160
- const value = part as {
161
- type?: unknown;
162
- text?: unknown;
163
- thinking?: unknown;
164
- };
165
- if (value.type === "text" && typeof value.text === "string")
166
- return value.text;
167
- if (value.type === "thinking" && typeof value.thinking === "string")
168
- return value.thinking;
169
- return "";
170
- })
171
- .join("");
172
- }
173
-
174
- function usageFromMessage(message: unknown): Usage | undefined {
175
- if (!message || typeof message !== "object") return undefined;
176
- const usage = (message as { usage?: unknown }).usage;
177
- return usage && typeof usage === "object" ? (usage as Usage) : undefined;
178
- }
179
-
180
- function executableFromEnvironment(env: NodeJS.ProcessEnv): string {
181
- const explicit = env.CADENCE_PI_EXECUTABLE ?? env.PI_EXECUTABLE;
182
- if (explicit) return explicit;
183
- const current = process.argv[1];
184
- if (
185
- current &&
186
- (path.basename(current) === "pi" ||
187
- current
188
- .replaceAll("\\", "/")
189
- .endsWith("/pi-coding-agent/dist/bundle/cli.js") ||
190
- current.endsWith("\\\\pi-coding-agent\\dist\\bundle\\cli.js"))
191
- ) {
192
- return current;
193
- }
194
- return "pi";
195
- }
196
-
197
- function modelArgument(model: SubagentProcessInput["model"]): string[] {
198
- if (!model?.provider || !model.id) return [];
199
- return ["--model", `${model.provider}/${model.id}`];
200
- }
201
-
202
- /** Parse the last JSON object in a fenced result block without trusting it as authority. */
203
- export function parseSubagentJson<T = unknown>(text: string): T | undefined {
204
- const matches = [...text.matchAll(/```(?:json)?\s*([\s\S]*?)```/giu)];
205
- for (let index = matches.length - 1; index >= 0; index -= 1) {
206
- try {
207
- const value: unknown = JSON.parse(matches[index]?.[1]?.trim() ?? "");
208
- return value as T;
209
- } catch {
210
- // A malformed final block is a result failure, not an identity.
211
- }
212
- }
213
- return undefined;
214
- }
215
-
216
- export function buildSubagentPrompt(input: {
217
- role: SubagentRole;
218
- objective: string;
219
- context?: string;
220
- agentContent?: string;
221
- finalConvention?: string;
222
- }): string {
223
- const definition =
224
- input.agentContent ??
225
- loadAgentDefinitions().find((agent) => agent.role === input.role)?.content;
226
- if (!definition) throw new Error("subagent-role-unavailable");
227
- return [
228
- definition,
229
- "You are running as an isolated one-shot Pi child. Do not invoke workflow control tools, submit tools, or another agent.",
230
- `Your allowed tools are: ${SUBAGENT_CAPABILITIES[input.role].tools.join(", ")}.`,
231
- SUBAGENT_CAPABILITIES[input.role].writes
232
- ? "You may edit only the disposable workspace supplied as your current working directory. The parent will inspect all changes; never commit or claim verification success."
233
- : "You are read-only and must not modify files or run commands.",
234
- input.objective,
235
- input.context ?? "",
236
- input.finalConvention ??
237
- "Finish with a concise summary. If structured evidence is required, put exactly one JSON object in a ```json code fence as the final answer.",
238
- ]
239
- .filter(Boolean)
240
- .join("\n\n");
241
- }
242
-
243
- function terminateProcess(
244
- child: ChildProcessWithoutNullStreams,
245
- graceMs: number,
246
- ): Promise<void> {
247
- return new Promise((resolve) => {
248
- let done = false;
249
- const finish = () => {
250
- if (done) return;
251
- done = true;
252
- clearTimeout(timer);
253
- resolve();
254
- };
255
- const timer = setTimeout(() => {
256
- if (process.platform !== "win32" && child.pid) {
257
- try {
258
- process.kill(-child.pid, "SIGKILL");
259
- } catch {
260
- // The process group may already have exited.
261
- }
262
- } else if (!child.killed) {
263
- child.kill("SIGKILL");
264
- }
265
- finish();
266
- }, graceMs);
267
- timer.unref?.();
268
- child.once("close", finish);
269
- if (process.platform !== "win32" && child.pid) {
270
- try {
271
- process.kill(-child.pid, "SIGTERM");
272
- } catch {
273
- child.kill("SIGTERM");
274
- }
275
- } else {
276
- child.kill("SIGTERM");
277
- }
278
- });
279
- }
280
-
281
- /** Run a fresh Pi CLI. No cwd or tool allowlist is treated as an OS sandbox. */
282
- export async function runSubagentProcess(
283
- input: SubagentProcessInput,
284
- ): Promise<SubagentProcessResult> {
285
- const timeoutMs = input.timeoutMs ?? SUBAGENT_LIMITS.timeoutMs;
286
- const maxOutputBytes = input.maxOutputBytes ?? SUBAGENT_LIMITS.maxOutputBytes;
287
- const promptBytes = Buffer.byteLength(input.prompt, "utf8");
288
- if (promptBytes > SUBAGENT_LIMITS.maxPromptBytes) {
289
- return {
290
- status: "output-limit",
291
- finalText: "",
292
- stderr: "",
293
- events: [],
294
- outputBytes: promptBytes,
295
- outputTruncated: true,
296
- exitCode: null,
297
- error: "subagent-prompt-limit",
298
- };
299
- }
300
- if (input.signal?.aborted) {
301
- return {
302
- status: "cancelled",
303
- finalText: "",
304
- stderr: "",
305
- events: [],
306
- outputBytes: 0,
307
- outputTruncated: false,
308
- exitCode: null,
309
- error: "cancelled",
310
- };
311
- }
312
- const tools = SUBAGENT_CAPABILITIES[input.role].tools.join(",");
313
- const args = [
314
- "--mode",
315
- "json",
316
- "-p",
317
- "--no-session",
318
- "--no-extensions",
319
- "--no-context-files",
320
- "--no-approve",
321
- "--tools",
322
- tools,
323
- ...modelArgument(input.model),
324
- ];
325
- const env = { ...process.env, CADENCE_SUBAGENT_CHILD: "1" };
326
- const output = new BoundedBuffer(maxOutputBytes);
327
- const stderr = new BoundedBuffer(Math.min(maxOutputBytes, 64 * 1024));
328
- const events: SubagentEvent[] = [];
329
- let child: ChildProcessWithoutNullStreams;
330
- try {
331
- child = spawn(
332
- input.piExecutable ?? executableFromEnvironment(process.env),
333
- args,
334
- {
335
- cwd: input.cwd,
336
- env,
337
- shell: false,
338
- stdio: ["pipe", "pipe", "pipe"],
339
- ...(process.platform === "win32" ? {} : { detached: true }),
340
- },
341
- );
342
- } catch {
343
- return {
344
- status: "failed",
345
- finalText: "",
346
- stderr: "",
347
- events,
348
- outputBytes: 0,
349
- outputTruncated: false,
350
- exitCode: null,
351
- error: "subagent-spawn-failed",
352
- };
353
- }
354
- const decoder = new StringDecoder("utf8");
355
- let finalText = "";
356
- let usage: Usage | undefined;
357
- let parseBuffer = "";
358
- let terminal: SubagentTerminalStatus | undefined;
359
- let terminalError: string | undefined;
360
- const emit = (event: SubagentEvent) => {
361
- events.push(event);
362
- try {
363
- input.onEvent?.(event);
364
- } catch {
365
- // Progress observers are presentation-only and cannot fail execution.
366
- }
367
- };
368
- const processLine = (rawLine: string): void => {
369
- const line = rawLine.replace(/\r$/u, "");
370
- if (!line || terminal) return;
371
- try {
372
- const event = JSON.parse(line) as Record<string, unknown>;
373
- const type = typeof event.type === "string" ? event.type : "unknown";
374
- const message = event.message;
375
- const text =
376
- type === "message_end" ? textFromMessage(message) : undefined;
377
- if (
378
- text &&
379
- message &&
380
- typeof message === "object" &&
381
- (message as { role?: unknown }).role === "assistant"
382
- ) {
383
- finalText = boundedText(text, maxOutputBytes);
384
- usage = usageFromMessage(message) ?? usage;
385
- }
386
- if (
387
- type === "message_update" &&
388
- event.usage &&
389
- typeof event.usage === "object"
390
- )
391
- usage = event.usage as Usage;
392
- emit({
393
- type,
394
- ...(typeof event.toolName === "string"
395
- ? { toolName: event.toolName }
396
- : {}),
397
- ...(text ? { text: boundedText(text, 4096) } : {}),
398
- });
399
- } catch {
400
- emit({ type: "malformed-event" });
401
- terminal = "failed";
402
- terminalError = "subagent-jsonl-malformed";
403
- void terminateProcess(child, SUBAGENT_LIMITS.terminateGraceMs);
404
- }
405
- };
406
- const consumeDecoded = (decoded: string): void => {
407
- if (!decoded || terminal) return;
408
- if (
409
- Buffer.byteLength(parseBuffer, "utf8") +
410
- Buffer.byteLength(decoded, "utf8") >
411
- maxOutputBytes
412
- ) {
413
- parseBuffer = "";
414
- terminal = "output-limit";
415
- void terminateProcess(child, SUBAGENT_LIMITS.terminateGraceMs);
416
- return;
417
- }
418
- parseBuffer += decoded;
419
- for (;;) {
420
- const newline = parseBuffer.indexOf("\n");
421
- if (newline < 0) break;
422
- const line = parseBuffer.slice(0, newline);
423
- parseBuffer = parseBuffer.slice(newline + 1);
424
- processLine(line);
425
- if (terminal) return;
426
- }
427
- };
428
- const consume = (chunk: Buffer) => {
429
- output.append(chunk);
430
- consumeDecoded(decoder.write(chunk));
431
- if (output.truncated && !terminal) {
432
- terminal = "output-limit";
433
- void terminateProcess(child, SUBAGENT_LIMITS.terminateGraceMs);
434
- }
435
- };
436
- child.stdout.on("data", consume);
437
- child.stderr.on("data", (chunk: Buffer) => stderr.append(chunk));
438
- const termination = new Promise<number | null>((resolve) => {
439
- // A spawn error is followed by close; keep the error listener only to
440
- // prevent an uncaught event while waiting for the pipes to settle.
441
- child.once("error", () => undefined);
442
- child.once("close", (code) => resolve(code));
443
- });
444
- const cancel = (status: "cancelled" | "timed-out") => {
445
- if (!terminal) terminal = status;
446
- void terminateProcess(child, SUBAGENT_LIMITS.terminateGraceMs);
447
- };
448
- const abortHandler = () => cancel("cancelled");
449
- input.signal?.addEventListener("abort", abortHandler, { once: true });
450
- const timer = setTimeout(() => cancel("timed-out"), timeoutMs);
451
- timer.unref?.();
452
- child.stdin.end(input.prompt);
453
- const exitCode = await termination;
454
- if (timer) clearTimeout(timer);
455
- input.signal?.removeEventListener("abort", abortHandler);
456
- const trailing = decoder.end();
457
- consumeDecoded(trailing);
458
- if (parseBuffer && !terminal) {
459
- processLine(parseBuffer);
460
- parseBuffer = "";
461
- }
462
- if (!terminal) terminal = exitCode === 0 ? "completed" : "failed";
463
- return {
464
- status: terminal,
465
- finalText,
466
- usage,
467
- stderr: boundedText(stderr.text(), 64 * 1024),
468
- ...(terminal === "failed"
469
- ? { error: terminalError ?? `subagent-exit-${exitCode ?? "unknown"}` }
470
- : {}),
471
- events,
472
- outputBytes: output.bytes,
473
- outputTruncated: output.truncated,
474
- exitCode,
475
- };
476
- }