@abelxiaoxing/cadence 1.8.4 → 2.0.0
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 +94 -21
- 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
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/** Portable local proposal only. UUID/hash are prospective, not stored membership. */
|
|
2
|
+
|
|
3
|
+
import { text } from "./service/contracts/core.ts";
|
|
4
|
+
import {
|
|
5
|
+
type Action,
|
|
6
|
+
type Assignment,
|
|
7
|
+
ContractError,
|
|
8
|
+
type CurrentView,
|
|
9
|
+
canonicalJson,
|
|
10
|
+
decodeAction,
|
|
11
|
+
decodeCurrentView,
|
|
12
|
+
roleForPurpose,
|
|
13
|
+
utf8Bytes,
|
|
14
|
+
} from "./service-contract.ts";
|
|
15
|
+
|
|
16
|
+
export interface ProposalCrypto {
|
|
17
|
+
uuid(): string;
|
|
18
|
+
sha256(bytes: Uint8Array): string | Promise<string>;
|
|
19
|
+
}
|
|
20
|
+
export async function proposePlan(
|
|
21
|
+
input: CurrentView,
|
|
22
|
+
readablePlan: string,
|
|
23
|
+
assignments: readonly Assignment[],
|
|
24
|
+
crypto: ProposalCrypto,
|
|
25
|
+
): Promise<Extract<Action, { kind: "save-plan" }>> {
|
|
26
|
+
// Decode/copy BEFORE the digest await. Caller mutation cannot retarget a draft.
|
|
27
|
+
const current = decodeCurrentView(input);
|
|
28
|
+
if (
|
|
29
|
+
current.context.status !== "available" ||
|
|
30
|
+
!current.context.version.task ||
|
|
31
|
+
!current.readablePlan?.editable ||
|
|
32
|
+
current.savedGraph.status === "unavailable"
|
|
33
|
+
)
|
|
34
|
+
throw new ContractError("unavailable");
|
|
35
|
+
const context = current.context.version;
|
|
36
|
+
const task = context.task;
|
|
37
|
+
if (!task) throw new ContractError("stale");
|
|
38
|
+
const plan = text(readablePlan, 32768);
|
|
39
|
+
// The shared action/graph decoder checks every role/capability/dependency/UTF8
|
|
40
|
+
// and aggregate bound before invoking asynchronous caller-supplied crypto.
|
|
41
|
+
const action = decodeAction({
|
|
42
|
+
kind: "save-plan",
|
|
43
|
+
taskId: task.id,
|
|
44
|
+
plan,
|
|
45
|
+
graph: {
|
|
46
|
+
schema: "cadence-graph/2",
|
|
47
|
+
taskId: task.id,
|
|
48
|
+
taskKind: task.kind,
|
|
49
|
+
taskRevision: task.revision + 1,
|
|
50
|
+
revision: (task.graphRevision ?? 0) + 1,
|
|
51
|
+
planRevision:
|
|
52
|
+
current.savedGraph.status === "saved"
|
|
53
|
+
? current.savedGraph.planRevision + 1
|
|
54
|
+
: 1,
|
|
55
|
+
plan: {
|
|
56
|
+
recordId: crypto.uuid(),
|
|
57
|
+
version:
|
|
58
|
+
current.savedGraph.status === "saved"
|
|
59
|
+
? current.savedGraph.planRevision + 1
|
|
60
|
+
: 1,
|
|
61
|
+
field: "plan",
|
|
62
|
+
bytes: utf8Bytes(plan),
|
|
63
|
+
// Structural placeholder only, replaced by the exact digest below.
|
|
64
|
+
sha256: "0".repeat(64),
|
|
65
|
+
},
|
|
66
|
+
assignments,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
if (action.kind !== "save-plan") throw new ContractError("invalid");
|
|
70
|
+
for (const assignment of action.graph.assignments) {
|
|
71
|
+
const role = current.roles.find(
|
|
72
|
+
(r) => r.role === roleForPurpose(assignment.purpose),
|
|
73
|
+
);
|
|
74
|
+
if (
|
|
75
|
+
!role ||
|
|
76
|
+
canonicalJson(role) !== canonicalJson(assignment.role) ||
|
|
77
|
+
canonicalJson(context.model) !== canonicalJson(assignment.model)
|
|
78
|
+
)
|
|
79
|
+
throw new ContractError("stale");
|
|
80
|
+
}
|
|
81
|
+
action.graph.plan.sha256 = await crypto.sha256(
|
|
82
|
+
new TextEncoder().encode(plan),
|
|
83
|
+
);
|
|
84
|
+
const proposal = decodeAction(action);
|
|
85
|
+
if (proposal.kind !== "save-plan") throw new ContractError("invalid");
|
|
86
|
+
return proposal;
|
|
87
|
+
}
|
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
// Ordinary SDK child, not Pi CLI startup. No discovery, credential files or fallback executor.
|
|
2
|
+
import {
|
|
3
|
+
createProvider,
|
|
4
|
+
InMemoryCredentialStore,
|
|
5
|
+
InMemoryModelsStore,
|
|
6
|
+
} from "@earendil-works/pi-ai";
|
|
7
|
+
import {
|
|
8
|
+
createAgentSession,
|
|
9
|
+
createExtensionRuntime,
|
|
10
|
+
createLocalBashOperations,
|
|
11
|
+
ModelRuntime,
|
|
12
|
+
SessionManager,
|
|
13
|
+
SettingsManager,
|
|
14
|
+
} from "@earendil-works/pi-coding-agent";
|
|
15
|
+
import { CaptureEmitter, SdkCapture } from "./worker-capture.mjs";
|
|
16
|
+
import {
|
|
17
|
+
closed,
|
|
18
|
+
decodeWorkerPublicRequest,
|
|
19
|
+
LIMITS,
|
|
20
|
+
parseWireJson,
|
|
21
|
+
} from "./worker-public.mjs";
|
|
22
|
+
import { own } from "./worker-redaction.mjs";
|
|
23
|
+
import { captureBash, ToolIngress } from "./worker-tools.mjs";
|
|
24
|
+
|
|
25
|
+
const adapters = {
|
|
26
|
+
"openai-completions": () =>
|
|
27
|
+
import("@earendil-works/pi-ai/api/openai-completions"),
|
|
28
|
+
"openai-responses": () =>
|
|
29
|
+
import("@earendil-works/pi-ai/api/openai-responses"),
|
|
30
|
+
"openai-codex-responses": () =>
|
|
31
|
+
import("@earendil-works/pi-ai/api/openai-codex-responses"),
|
|
32
|
+
"anthropic-messages": () =>
|
|
33
|
+
import("@earendil-works/pi-ai/api/anthropic-messages"),
|
|
34
|
+
"google-generative-ai": () =>
|
|
35
|
+
import("@earendil-works/pi-ai/api/google-generative-ai"),
|
|
36
|
+
"mistral-conversations": () =>
|
|
37
|
+
import("@earendil-works/pi-ai/api/mistral-conversations"),
|
|
38
|
+
"azure-openai-responses": () =>
|
|
39
|
+
import("@earendil-works/pi-ai/api/azure-openai-responses"),
|
|
40
|
+
};
|
|
41
|
+
let session;
|
|
42
|
+
let ownedAgent;
|
|
43
|
+
let unsubscribe;
|
|
44
|
+
let emitter;
|
|
45
|
+
let capture;
|
|
46
|
+
let ingress;
|
|
47
|
+
let stdoutFailureListener;
|
|
48
|
+
let bytes = 0;
|
|
49
|
+
let inputBytes = 0;
|
|
50
|
+
let sdkEvents = 0;
|
|
51
|
+
let modelOutputBytes = 0;
|
|
52
|
+
let stage = "guards";
|
|
53
|
+
let failure;
|
|
54
|
+
let terminalStopReason = "UNKNOWN";
|
|
55
|
+
let requestPrepared = false;
|
|
56
|
+
let actualResponseStatus = "UNKNOWN";
|
|
57
|
+
let preflight = "UNKNOWN";
|
|
58
|
+
let cleanupFailures = 0; // Bit flags: unsubscribe=1, abort=2, dispose=4, owned close=8.
|
|
59
|
+
let pendingAbort;
|
|
60
|
+
let cleanupSettled = false;
|
|
61
|
+
const fail = (where, category) => {
|
|
62
|
+
const first = failure === undefined;
|
|
63
|
+
failure ??= { stage: where, class: category };
|
|
64
|
+
process.exitCode = 1;
|
|
65
|
+
if (first) {
|
|
66
|
+
if (
|
|
67
|
+
emitter?.state.position.workerSeq &&
|
|
68
|
+
!emitter.state.position.final &&
|
|
69
|
+
!emitter.broken
|
|
70
|
+
) {
|
|
71
|
+
try {
|
|
72
|
+
void emitter
|
|
73
|
+
.emit({
|
|
74
|
+
kind: "worker-failure",
|
|
75
|
+
code:
|
|
76
|
+
where === "cleanup"
|
|
77
|
+
? "cleanup"
|
|
78
|
+
: where === "network"
|
|
79
|
+
? "network"
|
|
80
|
+
: "capture",
|
|
81
|
+
})
|
|
82
|
+
.catch(() => {});
|
|
83
|
+
} catch {
|
|
84
|
+
/* A failed capture cannot fabricate a failure receipt. */
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// A cleanup promise can remain unsettled after all native handles close.
|
|
88
|
+
// Preserve the observed failure before awaiting it, without claiming cleanup.
|
|
89
|
+
try {
|
|
90
|
+
emitFailure();
|
|
91
|
+
} catch {
|
|
92
|
+
// Diagnostic I/O must not expose an exception or repair failed work.
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
const refuse = (where, category) => {
|
|
97
|
+
fail(where, category);
|
|
98
|
+
throw new Error("Worker refused");
|
|
99
|
+
};
|
|
100
|
+
const abortForFailure = () => {
|
|
101
|
+
// Attach a rejection handler immediately, including synchronous SDK throws.
|
|
102
|
+
// Do not let fire-and-forget abort print a secret-bearing unhandled rejection.
|
|
103
|
+
pendingAbort ??= (async () => {
|
|
104
|
+
try {
|
|
105
|
+
await session.abort();
|
|
106
|
+
} catch {
|
|
107
|
+
cleanupFailures |= 2;
|
|
108
|
+
fail("cleanup", "cleanup-failure");
|
|
109
|
+
}
|
|
110
|
+
})();
|
|
111
|
+
};
|
|
112
|
+
const emitFailure = () => {
|
|
113
|
+
// Construct from fixed labels/primitives only; never inspect exception text,
|
|
114
|
+
// provider diagnostics, response headers, payloads, usage or tool contents.
|
|
115
|
+
const line = `${JSON.stringify({
|
|
116
|
+
...failure,
|
|
117
|
+
outcome: "failed",
|
|
118
|
+
sdkEvents: Math.min(sdkEvents, LIMITS.sdkEvents + 1),
|
|
119
|
+
inputBytes: Math.min(inputBytes, 128 * 1024 + 1),
|
|
120
|
+
modelOutputBytes: Math.min(modelOutputBytes, LIMITS.childProtocolBytes + 1),
|
|
121
|
+
protocolBytes: Math.min(
|
|
122
|
+
bytes + (emitter?.state.bytes ?? 0),
|
|
123
|
+
LIMITS.childProtocolBytes + 1,
|
|
124
|
+
),
|
|
125
|
+
terminalStopReason,
|
|
126
|
+
preflight,
|
|
127
|
+
requestPrepared,
|
|
128
|
+
actualResponseStatus,
|
|
129
|
+
cleanupFailures,
|
|
130
|
+
cleanupSettled,
|
|
131
|
+
})}\n`;
|
|
132
|
+
// Diagnostic output shares the existing aggregate cap, not a new allowance.
|
|
133
|
+
const write = (text) => {
|
|
134
|
+
const size = Buffer.byteLength(text);
|
|
135
|
+
if (
|
|
136
|
+
bytes + (emitter?.state.bytes ?? 0) + size <=
|
|
137
|
+
LIMITS.childProtocolBytes
|
|
138
|
+
) {
|
|
139
|
+
bytes += size;
|
|
140
|
+
process.stderr.write(text);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
if (Buffer.byteLength(line) <= 512) write(line);
|
|
144
|
+
if (cleanupSettled)
|
|
145
|
+
write("SDK worker failed or returned an incomplete result.\n");
|
|
146
|
+
};
|
|
147
|
+
try {
|
|
148
|
+
if (
|
|
149
|
+
process.env.CADENCE_SUBAGENT_CHILD !== "1" ||
|
|
150
|
+
process.env.PI_OFFLINE !== "1"
|
|
151
|
+
)
|
|
152
|
+
refuse("guards", "guards-required");
|
|
153
|
+
// Only the parent's validated uppercase projection is input here. Capture in
|
|
154
|
+
// RAM before asynchronous setup; literal auth.env is never process routing.
|
|
155
|
+
let proxy;
|
|
156
|
+
try {
|
|
157
|
+
const httpProxy = process.env.HTTP_PROXY;
|
|
158
|
+
const httpsProxy = process.env.HTTPS_PROXY;
|
|
159
|
+
const optIn = process.env.NODE_USE_ENV_PROXY;
|
|
160
|
+
if (
|
|
161
|
+
httpProxy !== undefined ||
|
|
162
|
+
httpsProxy !== undefined ||
|
|
163
|
+
optIn !== undefined
|
|
164
|
+
) {
|
|
165
|
+
if (
|
|
166
|
+
typeof httpProxy !== "string" ||
|
|
167
|
+
!httpProxy ||
|
|
168
|
+
httpProxy !== httpsProxy ||
|
|
169
|
+
optIn !== "1"
|
|
170
|
+
)
|
|
171
|
+
refuse("network", "unsupported-network");
|
|
172
|
+
proxy = { httpProxy, httpsProxy, noProxy: "" };
|
|
173
|
+
}
|
|
174
|
+
} catch {
|
|
175
|
+
refuse("network", "unsupported-network");
|
|
176
|
+
}
|
|
177
|
+
stage = "parse";
|
|
178
|
+
const chunks = [];
|
|
179
|
+
for await (const chunk of process.stdin) {
|
|
180
|
+
inputBytes += chunk.length;
|
|
181
|
+
if (inputBytes > 128 * 1024) refuse("parse", "input-bound");
|
|
182
|
+
chunks.push(chunk);
|
|
183
|
+
}
|
|
184
|
+
const envelope = closed(
|
|
185
|
+
parseWireJson(
|
|
186
|
+
new TextDecoder("utf-8", { fatal: true }).decode(Buffer.concat(chunks)),
|
|
187
|
+
LIMITS.workerRequestBytes,
|
|
188
|
+
),
|
|
189
|
+
["request", "cwd", "model", "auth"],
|
|
190
|
+
);
|
|
191
|
+
const publicRequest = decodeWorkerPublicRequest(envelope.request);
|
|
192
|
+
const request = {
|
|
193
|
+
...envelope,
|
|
194
|
+
kind:
|
|
195
|
+
publicRequest.purpose === "edit"
|
|
196
|
+
? "code"
|
|
197
|
+
: publicRequest.purpose === "diagnosis"
|
|
198
|
+
? "diagnosis"
|
|
199
|
+
: "research",
|
|
200
|
+
prompt: publicRequest.prompt,
|
|
201
|
+
instructions: publicRequest.instructions,
|
|
202
|
+
thinkingLevel: publicRequest.model.thinking,
|
|
203
|
+
};
|
|
204
|
+
stage = "auth-model";
|
|
205
|
+
const { cwd, auth, kind, prompt, instructions, thinkingLevel } = request;
|
|
206
|
+
if (
|
|
207
|
+
!auth?.ok ||
|
|
208
|
+
typeof cwd !== "string" ||
|
|
209
|
+
typeof prompt !== "string" ||
|
|
210
|
+
typeof instructions !== "string" ||
|
|
211
|
+
!["code", "research", "diagnosis"].includes(kind)
|
|
212
|
+
)
|
|
213
|
+
refuse("auth-model", "invalid-request");
|
|
214
|
+
if (
|
|
215
|
+
Object.keys(auth.env ?? {}).some((name) =>
|
|
216
|
+
/(?:_FILE|_PATH|_CREDENTIALS)$/.test(name),
|
|
217
|
+
)
|
|
218
|
+
)
|
|
219
|
+
refuse("auth-model", "unsupported-auth");
|
|
220
|
+
const model = {
|
|
221
|
+
...request.model,
|
|
222
|
+
baseUrl: auth.baseUrl ?? request.model.baseUrl,
|
|
223
|
+
};
|
|
224
|
+
delete model.headers; // Parent auth already resolved these. Never config-interpolate literal credentials.
|
|
225
|
+
if (
|
|
226
|
+
!Object.hasOwn(adapters, model.api) ||
|
|
227
|
+
typeof model.provider !== "string" ||
|
|
228
|
+
typeof model.id !== "string" ||
|
|
229
|
+
model.id !== publicRequest.model.model ||
|
|
230
|
+
model.provider !== publicRequest.model.provider ||
|
|
231
|
+
model.api !== publicRequest.model.api
|
|
232
|
+
)
|
|
233
|
+
refuse("auth-model", "unsupported-model");
|
|
234
|
+
stage = "setup";
|
|
235
|
+
const api = await adapters[model.api]();
|
|
236
|
+
if (proxy) {
|
|
237
|
+
stage = "network";
|
|
238
|
+
try {
|
|
239
|
+
// SDK/adapter imports can reset native startup routing. Configure only
|
|
240
|
+
// this disposable child, using our declared public peer after imports.
|
|
241
|
+
const { EnvHttpProxyAgent, setGlobalDispatcher, getGlobalDispatcher } =
|
|
242
|
+
await import("undici");
|
|
243
|
+
if (
|
|
244
|
+
typeof EnvHttpProxyAgent !== "function" ||
|
|
245
|
+
typeof setGlobalDispatcher !== "function" ||
|
|
246
|
+
typeof getGlobalDispatcher !== "function"
|
|
247
|
+
)
|
|
248
|
+
refuse("network", "unsupported-network");
|
|
249
|
+
ownedAgent = new EnvHttpProxyAgent(proxy);
|
|
250
|
+
// Retain ownership even when installation or its identity check fails.
|
|
251
|
+
setGlobalDispatcher(ownedAgent);
|
|
252
|
+
if (getGlobalDispatcher() !== ownedAgent)
|
|
253
|
+
refuse("network", "unsupported-network");
|
|
254
|
+
} catch {
|
|
255
|
+
refuse("network", "unsupported-network");
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
stage = "setup";
|
|
259
|
+
const runtime = await ModelRuntime.create({
|
|
260
|
+
credentials: new InMemoryCredentialStore(),
|
|
261
|
+
modelsStore: new InMemoryModelsStore(),
|
|
262
|
+
modelsPath: null,
|
|
263
|
+
allowModelNetwork: false,
|
|
264
|
+
refreshOnCreate: false,
|
|
265
|
+
});
|
|
266
|
+
runtime.registerNativeProvider(
|
|
267
|
+
createProvider({
|
|
268
|
+
id: model.provider,
|
|
269
|
+
models: [model],
|
|
270
|
+
api,
|
|
271
|
+
auth: {
|
|
272
|
+
apiKey: {
|
|
273
|
+
name: "Resolved parent request",
|
|
274
|
+
resolve: async () => ({
|
|
275
|
+
auth: {
|
|
276
|
+
apiKey: auth.apiKey,
|
|
277
|
+
headers: auth.headers,
|
|
278
|
+
baseUrl: auth.baseUrl,
|
|
279
|
+
},
|
|
280
|
+
env: auth.env,
|
|
281
|
+
}),
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
}),
|
|
285
|
+
);
|
|
286
|
+
const extensionRuntime = createExtensionRuntime();
|
|
287
|
+
const resourceLoader = {
|
|
288
|
+
getExtensions: () => ({
|
|
289
|
+
extensions: [],
|
|
290
|
+
errors: [],
|
|
291
|
+
runtime: extensionRuntime,
|
|
292
|
+
}),
|
|
293
|
+
getSkills: () => ({ skills: [], diagnostics: [] }),
|
|
294
|
+
getPrompts: () => ({ prompts: [], diagnostics: [] }),
|
|
295
|
+
getThemes: () => ({ themes: [], diagnostics: [] }),
|
|
296
|
+
getAgentsFiles: () => ({ agentsFiles: [] }),
|
|
297
|
+
getSystemPrompt: () => instructions,
|
|
298
|
+
getSystemPromptSource: () => undefined,
|
|
299
|
+
getAppendSystemPrompt: () => [],
|
|
300
|
+
getAppendSystemPromptSources: () => [],
|
|
301
|
+
extendResources: () => {},
|
|
302
|
+
reload: async () => {
|
|
303
|
+
throw new Error("Discovery disabled");
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
const secrets = [
|
|
307
|
+
auth.apiKey ?? "",
|
|
308
|
+
...Object.values(auth.headers ?? {}).filter((v) => typeof v === "string"),
|
|
309
|
+
...Object.values(auth.env ?? {}),
|
|
310
|
+
...(proxy ? [proxy.httpProxy] : []),
|
|
311
|
+
];
|
|
312
|
+
const captureFailure = () => {
|
|
313
|
+
fail("events", "event-projection-failed");
|
|
314
|
+
abortForFailure();
|
|
315
|
+
};
|
|
316
|
+
emitter = new CaptureEmitter(
|
|
317
|
+
publicRequest.childRunId,
|
|
318
|
+
process.stdout,
|
|
319
|
+
captureFailure,
|
|
320
|
+
publicRequest,
|
|
321
|
+
);
|
|
322
|
+
capture = new SdkCapture(emitter, secrets);
|
|
323
|
+
ingress = new ToolIngress(secrets, captureFailure, emitter.queue);
|
|
324
|
+
stdoutFailureListener = captureFailure;
|
|
325
|
+
process.stdout.on("error", stdoutFailureListener);
|
|
326
|
+
stage = "session";
|
|
327
|
+
const requestedThinkingLevel = thinkingLevel ?? "off";
|
|
328
|
+
const created = await createAgentSession({
|
|
329
|
+
cwd,
|
|
330
|
+
agentDir: cwd,
|
|
331
|
+
model,
|
|
332
|
+
modelRuntime: runtime,
|
|
333
|
+
thinkingLevel: requestedThinkingLevel,
|
|
334
|
+
tools:
|
|
335
|
+
kind === "code"
|
|
336
|
+
? ["read", "grep", "find", "ls", "write", "edit", "bash"]
|
|
337
|
+
: ["read", "grep", "find", "ls"],
|
|
338
|
+
customTools:
|
|
339
|
+
kind === "code"
|
|
340
|
+
? [
|
|
341
|
+
captureBash(
|
|
342
|
+
cwd,
|
|
343
|
+
secrets,
|
|
344
|
+
captureFailure,
|
|
345
|
+
createLocalBashOperations,
|
|
346
|
+
emitter.queue,
|
|
347
|
+
),
|
|
348
|
+
]
|
|
349
|
+
: [],
|
|
350
|
+
resourceLoader,
|
|
351
|
+
sessionManager: SessionManager.inMemory(cwd),
|
|
352
|
+
settingsManager: SettingsManager.inMemory({
|
|
353
|
+
compaction: { enabled: false },
|
|
354
|
+
retry: { enabled: false },
|
|
355
|
+
cacheWarming: "off",
|
|
356
|
+
}),
|
|
357
|
+
});
|
|
358
|
+
session = created.session;
|
|
359
|
+
let effectiveThinkingLevel;
|
|
360
|
+
try {
|
|
361
|
+
effectiveThinkingLevel = session.thinkingLevel;
|
|
362
|
+
} catch {
|
|
363
|
+
refuse("session", "model-substitution");
|
|
364
|
+
}
|
|
365
|
+
if (
|
|
366
|
+
created.modelFallbackMessage ||
|
|
367
|
+
session.model?.id !== model.id ||
|
|
368
|
+
session.model?.provider !== model.provider ||
|
|
369
|
+
session.model?.api !== model.api ||
|
|
370
|
+
effectiveThinkingLevel !== requestedThinkingLevel
|
|
371
|
+
)
|
|
372
|
+
refuse("session", "model-substitution");
|
|
373
|
+
// Adapter retry defaults are not workflow retries; explicitly disable both.
|
|
374
|
+
const stream = session.agent.streamFunction;
|
|
375
|
+
session.agent.streamFunction = (selected, context, options) => {
|
|
376
|
+
const snapshot = { ...options, maxRetries: 0 };
|
|
377
|
+
const { onPayload, onResponse } = snapshot;
|
|
378
|
+
snapshot.onPayload = function (...args) {
|
|
379
|
+
// Preparation is not proof of sending, an HTTP response or inference.
|
|
380
|
+
requestPrepared = true;
|
|
381
|
+
return onPayload == null
|
|
382
|
+
? undefined
|
|
383
|
+
: Reflect.apply(onPayload, this, args);
|
|
384
|
+
};
|
|
385
|
+
snapshot.onResponse = function (...args) {
|
|
386
|
+
// Observation must not bypass the original callback, even for a bad peer.
|
|
387
|
+
// Public HTTP hook only; WS transports may never report a status here.
|
|
388
|
+
actualResponseStatus = "UNKNOWN";
|
|
389
|
+
try {
|
|
390
|
+
const status = args[0]?.status;
|
|
391
|
+
if (Number.isInteger(status) && status >= 100 && status <= 599)
|
|
392
|
+
actualResponseStatus = status;
|
|
393
|
+
} catch {
|
|
394
|
+
// Do not inspect/coerce the response or exception.
|
|
395
|
+
}
|
|
396
|
+
return onResponse == null
|
|
397
|
+
? undefined
|
|
398
|
+
: Reflect.apply(onResponse, this, args);
|
|
399
|
+
};
|
|
400
|
+
return stream(selected, context, snapshot);
|
|
401
|
+
};
|
|
402
|
+
// Public awaited core subscription, not synchronous AgentSession forwarding.
|
|
403
|
+
if (
|
|
404
|
+
typeof session.agent.subscribe !== "function" ||
|
|
405
|
+
!["parallel", "sequential"].includes(session.agent.toolExecution)
|
|
406
|
+
)
|
|
407
|
+
refuse("session", "capture-unsupported");
|
|
408
|
+
session.agent.toolExecution = "sequential";
|
|
409
|
+
if (session.agent.toolExecution !== "sequential")
|
|
410
|
+
refuse("session", "capture-unsupported");
|
|
411
|
+
session.agent.state.tools = ingress.wrap(session.agent.state.tools);
|
|
412
|
+
unsubscribe = session.agent.subscribe(async (event) => {
|
|
413
|
+
try {
|
|
414
|
+
sdkEvents++;
|
|
415
|
+
if (sdkEvents > LIMITS.sdkEvents) refuse("outputbounds", "event-bound");
|
|
416
|
+
if (own(event, "type") === "message_update") {
|
|
417
|
+
const update = own(event, "assistantMessageEvent");
|
|
418
|
+
const type = own(update, "type");
|
|
419
|
+
if (type === "text_delta" || type === "thinking_delta") {
|
|
420
|
+
const delta = own(update, "delta");
|
|
421
|
+
if (typeof delta !== "string") throw new Error("Malformed SDK event");
|
|
422
|
+
modelOutputBytes += Buffer.byteLength(delta);
|
|
423
|
+
if (modelOutputBytes > LIMITS.childProtocolBytes)
|
|
424
|
+
refuse("outputbounds", "model-output-bound");
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
if (own(event, "type") === "message_end") {
|
|
428
|
+
const message = own(event, "message");
|
|
429
|
+
if (own(message, "role") === "assistant") {
|
|
430
|
+
const reason = own(message, "stopReason");
|
|
431
|
+
terminalStopReason = [
|
|
432
|
+
"pending",
|
|
433
|
+
"stop",
|
|
434
|
+
"length",
|
|
435
|
+
"toolUse",
|
|
436
|
+
"error",
|
|
437
|
+
"aborted",
|
|
438
|
+
"deferred",
|
|
439
|
+
].includes(reason)
|
|
440
|
+
? reason
|
|
441
|
+
: "UNKNOWN";
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
if (failure) return;
|
|
445
|
+
await capture.event(event);
|
|
446
|
+
} catch {
|
|
447
|
+
fail("events", "event-projection-failed");
|
|
448
|
+
abortForFailure();
|
|
449
|
+
} finally {
|
|
450
|
+
try {
|
|
451
|
+
ingress.release(event);
|
|
452
|
+
} catch {
|
|
453
|
+
fail("events", "event-projection-failed");
|
|
454
|
+
abortForFailure();
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
stage = "ready";
|
|
459
|
+
await emitter.emit({ kind: "ready", requestProtocol: 2 });
|
|
460
|
+
await capture.context(publicRequest, session.systemPrompt);
|
|
461
|
+
stage = "prompt";
|
|
462
|
+
const suppliedPrompt = [
|
|
463
|
+
publicRequest.context,
|
|
464
|
+
...publicRequest.handoff.inputs.map(
|
|
465
|
+
(input) =>
|
|
466
|
+
`Dependency ${input.assignmentId} (recorded input):\n${input.text}`,
|
|
467
|
+
),
|
|
468
|
+
prompt,
|
|
469
|
+
]
|
|
470
|
+
.filter(Boolean)
|
|
471
|
+
.join("\n\n");
|
|
472
|
+
await session.prompt(suppliedPrompt, {
|
|
473
|
+
expandPromptTemplates: false,
|
|
474
|
+
preflightResult: (accepted) => {
|
|
475
|
+
preflight = accepted ? "accepted" : "rejected";
|
|
476
|
+
if (!accepted) fail("preflight", "preflight-rejected");
|
|
477
|
+
},
|
|
478
|
+
});
|
|
479
|
+
stage = "assistant-final";
|
|
480
|
+
if (failure || terminalStopReason !== "stop" || ingress.frames !== 0)
|
|
481
|
+
refuse("assistant-final", "terminal-incomplete");
|
|
482
|
+
await capture.final();
|
|
483
|
+
await emitter.drain();
|
|
484
|
+
} catch {
|
|
485
|
+
// The stage is a location, not a complete diagnosis of an SDK/provider cause.
|
|
486
|
+
fail(stage, stage === "parse" ? "parse-failed" : "sdk-failure");
|
|
487
|
+
} finally {
|
|
488
|
+
try {
|
|
489
|
+
capture?.dispose();
|
|
490
|
+
} catch {
|
|
491
|
+
fail("cleanup", "cleanup-failure");
|
|
492
|
+
}
|
|
493
|
+
// Start and rejection-handle owned close BEFORE any potentially pending SDK
|
|
494
|
+
// cleanup. Never restore or close the previous, unowned global dispatcher.
|
|
495
|
+
const closing = (async () => {
|
|
496
|
+
if (!ownedAgent) return;
|
|
497
|
+
try {
|
|
498
|
+
await ownedAgent.close();
|
|
499
|
+
} catch {
|
|
500
|
+
cleanupFailures |= 8;
|
|
501
|
+
fail("cleanup", "cleanup-failure");
|
|
502
|
+
}
|
|
503
|
+
})();
|
|
504
|
+
const unsubscribing = (async () => {
|
|
505
|
+
try {
|
|
506
|
+
await unsubscribe?.();
|
|
507
|
+
} catch {
|
|
508
|
+
cleanupFailures |= 1;
|
|
509
|
+
fail("cleanup", "cleanup-failure");
|
|
510
|
+
}
|
|
511
|
+
})();
|
|
512
|
+
const aborting = (async () => {
|
|
513
|
+
if (!session) return;
|
|
514
|
+
try {
|
|
515
|
+
await session.abort();
|
|
516
|
+
} catch {
|
|
517
|
+
cleanupFailures |= 2;
|
|
518
|
+
fail("cleanup", "cleanup-failure");
|
|
519
|
+
}
|
|
520
|
+
})();
|
|
521
|
+
const disposing = (async () => {
|
|
522
|
+
if (!session) return;
|
|
523
|
+
try {
|
|
524
|
+
await session.dispose();
|
|
525
|
+
} catch {
|
|
526
|
+
cleanupFailures |= 4;
|
|
527
|
+
fail("cleanup", "cleanup-failure");
|
|
528
|
+
}
|
|
529
|
+
})();
|
|
530
|
+
await Promise.all([
|
|
531
|
+
closing,
|
|
532
|
+
unsubscribing,
|
|
533
|
+
pendingAbort,
|
|
534
|
+
aborting,
|
|
535
|
+
disposing,
|
|
536
|
+
emitter?.drain(),
|
|
537
|
+
]);
|
|
538
|
+
// A final snapshot exists only after the actual cleanup attempts settle.
|
|
539
|
+
if (stdoutFailureListener)
|
|
540
|
+
process.stdout.removeListener("error", stdoutFailureListener);
|
|
541
|
+
cleanupSettled = true;
|
|
542
|
+
// Cleanup failures cannot become success, nor replace the first run failure.
|
|
543
|
+
try {
|
|
544
|
+
if (failure) emitFailure();
|
|
545
|
+
} catch {
|
|
546
|
+
// A failed diagnostic write cannot print an arbitrary exception or repair failure.
|
|
547
|
+
process.exitCode = 1;
|
|
548
|
+
}
|
|
549
|
+
}
|