@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.
- package/README.md +324 -117
- package/THIRD_PARTY_NOTICES.md +15 -0
- package/agents/design-explorer.md +14 -18
- package/agents/diagnosis-worker.md +14 -14
- package/agents/implementation-worker.md +16 -17
- package/config/workflow-task.example.json +7 -0
- package/docs/remote-access.md +60 -0
- package/docs/reuse-and-distribution.md +44 -0
- package/docs/service-management.md +108 -0
- package/licenses/bundled-web.txt +6245 -0
- package/licenses/pi-web-ui-MIT.txt +21 -0
- package/licenses/pi-web-ui-reuse.md +22 -0
- package/package.json +92 -20
- package/src/index.ts +21 -1802
- package/src/native-build.ts +6 -0
- package/src/native-client.ts +103 -0
- package/src/native-export.ts +58 -0
- package/src/native-history.ts +359 -0
- package/src/operator-cli.mjs +453 -1937
- package/src/plan-proposal.ts +87 -0
- package/src/sdk-worker.mjs +549 -0
- package/src/service/contracts/active.ts +82 -0
- package/src/service/contracts/budget.ts +148 -0
- package/src/service/contracts/build.ts +67 -0
- package/src/service/contracts/capture.ts +48 -0
- package/src/service/contracts/core.ts +33 -0
- package/src/service/contracts/current.ts +261 -0
- package/src/service/contracts/domain.ts +68 -0
- package/src/service/contracts/history.ts +2245 -0
- package/src/service/contracts/models.ts +139 -0
- package/src/service/coordination.ts +862 -0
- package/src/service/private-files.ts +204 -0
- package/src/service-cli.mjs +5592 -0
- package/src/service-client.ts +747 -0
- package/src/service-contract.ts +245 -0
- package/src/service-host.mjs +22382 -0
- package/src/service-http-client.ts +425 -0
- package/src/service-wire.ts +372 -0
- package/src/task-shell.ts +1045 -0
- package/src/task-store.ts +479 -0
- package/src/task-types.ts +184 -0
- package/src/verification-diagnostics.ts +29 -72
- package/src/worker-capture.mjs +1197 -0
- package/src/worker-public.mjs +1753 -0
- package/src/worker-redaction.mjs +608 -0
- package/src/worker-tools.mjs +202 -0
- package/web/dist/assets/KaTeX_AMS-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_AMS-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_AMS-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold.ttf +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold.woff +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold.ttf +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold.woff +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Bold.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-Bold.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Bold.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic.woff +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Italic.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-Italic.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Italic.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic.ttf +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic.woff +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/web/dist/assets/KaTeX_Math-Italic.ttf +0 -0
- package/web/dist/assets/KaTeX_Math-Italic.woff +0 -0
- package/web/dist/assets/KaTeX_Math-Italic.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Script-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Script-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Script-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size3-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Size3-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Size3-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/web/dist/assets/cadence.css +2 -0
- package/web/dist/assets/cadence.js +7 -0
- package/web/dist/assets/markdown.js +296 -0
- package/web/dist/assets/react.js +9 -0
- package/web/dist/assets/rolldown-runtime.js +1 -0
- package/web/dist/assets/timeline-client.js +41 -0
- package/web/dist/assets/vis-timeline.css +1274 -0
- package/web/dist/assets/vis-timeline.js +32 -0
- package/web/dist/index.html +18 -0
- package/web/dist/manifest.json +505 -0
- package/config/plan-draft.example.json +0 -125
- package/config/plan-draft.multiple-tasks.example.json +0 -375
- package/config/plan-draft.quick.example.json +0 -78
- package/prompts/abel-design.md +0 -382
- package/prompts/abel-diagnose.md +0 -71
- package/prompts/abel-implement.md +0 -284
- package/prompts/abel-init.md +0 -52
- package/src/activation.ts +0 -44
- package/src/activity-contracts.ts +0 -82
- package/src/agent-registry.ts +0 -53
- package/src/apply-transaction.ts +0 -1437
- package/src/artifact-store.ts +0 -213
- package/src/build-windows-job.mjs +0 -95
- package/src/candidate-artifact.ts +0 -143
- package/src/candidate-context.ts +0 -289
- package/src/candidate-patch.ts +0 -271
- package/src/candidate-workspace.ts +0 -80
- package/src/canonical.ts +0 -16
- package/src/change-contract-schema.ts +0 -191
- package/src/change-contract.ts +0 -324
- package/src/change-verification.ts +0 -1617
- package/src/contracts.ts +0 -2466
- package/src/control-contracts.ts +0 -382
- package/src/delivery-compiler.ts +0 -1805
- package/src/delivery-revision.ts +0 -42
- package/src/design-control.ts +0 -1299
- package/src/design-diagnostics.ts +0 -181
- package/src/design-journal.ts +0 -1773
- package/src/directory-sync.ts +0 -36
- package/src/durable-contracts.ts +0 -227
- package/src/durable-workflow.ts +0 -822
- package/src/evidence-draft.ts +0 -122
- package/src/execution-profile.ts +0 -64
- package/src/execution-retention.ts +0 -77
- package/src/file-snapshot.ts +0 -163
- package/src/implement-continuation.ts +0 -559
- package/src/implement-graph.ts +0 -690
- package/src/implement-plan.ts +0 -72
- package/src/install-config.d.mts +0 -6
- package/src/isolation-backend.ts +0 -353
- package/src/model-source.ts +0 -22
- package/src/openspec-cli.ts +0 -494
- package/src/operator-cli-entry.mjs +0 -24
- package/src/operator-tools.ts +0 -309
- package/src/package-candidate-process.ts +0 -206
- package/src/package-candidate.ts +0 -24
- package/src/package-delivery.ts +0 -486
- package/src/package-state.ts +0 -320
- package/src/package-verification.ts +0 -635
- package/src/package-workflow.ts +0 -305
- package/src/packet-runtime.ts +0 -459
- package/src/phase-execution.ts +0 -2163
- package/src/pi-adapter.ts +0 -19
- package/src/plan-draft-summary.ts +0 -527
- package/src/plan-draft.ts +0 -502
- package/src/remove-path.ts +0 -32
- package/src/run-state.ts +0 -420
- package/src/run-store.ts +0 -1179
- package/src/safe-path.ts +0 -77
- package/src/scoped-grep-worker.mjs +0 -23
- package/src/scoped-tools.ts +0 -775
- package/src/single-task-draft.ts +0 -142
- package/src/sqlite-schema.ts +0 -171
- package/src/state-root.ts +0 -215
- package/src/storage-schema.ts +0 -360
- package/src/subagent-activity.ts +0 -1417
- package/src/subagent-process.ts +0 -476
- package/src/task-ledger.ts +0 -1338
- package/src/transport-budget.ts +0 -50
- package/src/verification-capability.ts +0 -1191
- package/src/verification-environment-io.ts +0 -389
- package/src/verification-environment.ts +0 -162
- package/src/verification-identity.ts +0 -77
- package/src/verification-scan.ts +0 -64
- package/src/windows-job-backend.ts +0 -419
- package/src/windows-job.c +0 -149
- package/src/workflow-engine.ts +0 -28
- package/src/workflow-policy.ts +0 -1670
- package/src/workflow-recovery-policy.ts +0 -97
- package/src/workflow-scheduling.ts +0 -105
- package/src/workflow-state-machine.ts +0 -4341
- package/src/workflow-status.ts +0 -576
- package/src/workspace-io-entry.mjs +0 -76
- package/src/workspace-io-worker.mjs +0 -1312
- package/src/workspace-io.ts +0 -89
- package/src/workspace-store.ts +0 -634
package/src/subagent-process.ts
DELETED
|
@@ -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
|
-
}
|