@abelxiaoxing/cadence 1.4.0 → 1.4.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 +58 -4
- package/agents/design-explorer.md +17 -11
- package/agents/diagnosis-worker.md +9 -1
- package/agents/implementation-worker.md +3 -1
- package/config/plan-draft.example.json +122 -0
- package/config/plan-draft.multiple-tasks.example.json +345 -0
- package/package.json +5 -3
- package/prompts/abel-design.md +93 -44
- package/src/activity-contracts.ts +82 -0
- package/src/candidate-artifact.ts +143 -0
- package/src/candidate-patch.ts +31 -11
- package/src/candidate-workspace.ts +74 -0
- package/src/change-contract-schema.ts +191 -0
- package/src/change-contract.ts +5 -0
- package/src/change-verification.ts +896 -0
- package/src/child-model.ts +129 -0
- package/src/child-session.ts +293 -326
- package/src/contracts.ts +184 -76
- package/src/delivery-compiler.ts +103 -120
- package/src/delivery-revision.ts +42 -0
- package/src/design-control.ts +113 -4
- package/src/design-diagnostics.ts +150 -0
- package/src/durable-contracts.ts +210 -0
- package/src/durable-workflow.ts +148 -3570
- package/src/evidence-draft.ts +122 -0
- package/src/implement-graph.ts +3 -0
- package/src/implement-plan.ts +70 -0
- package/src/index.ts +59 -522
- package/src/model-source.ts +22 -0
- package/src/package-candidate.ts +224 -0
- package/src/package-workflow.ts +253 -0
- package/src/packet-runtime.ts +27 -48
- package/src/parent-provider.ts +86 -198
- package/src/phase-execution.ts +2248 -0
- package/src/pi-adapter.ts +19 -0
- package/src/plan-draft-summary.ts +146 -0
- package/src/plan-draft.ts +493 -0
- package/src/subagent-activity.ts +32 -66
- package/src/submit-tool.ts +20 -59
- package/src/transport-budget.ts +32 -0
- package/src/worker-broker.ts +37 -57
- package/src/workflow-policy.ts +14 -53
- package/src/workflow-recovery-policy.ts +95 -0
- package/src/workflow-scheduling.ts +105 -0
- package/src/workflow-state-machine.ts +105 -497
- package/src/workflow-status.ts +384 -0
- package/src/workspace-io-worker.mjs +7 -0
- package/src/empty-resource-loader.ts +0 -38
- package/src/parent-payload-bridge.ts +0 -523
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Model, Provider } from "@earendil-works/pi-ai";
|
|
2
|
+
|
|
3
|
+
/** Code-owned capability boundary. No session events, tools, UI or host context. */
|
|
4
|
+
export interface ParentModelSource {
|
|
5
|
+
readonly model?: Model<string>;
|
|
6
|
+
readonly modelRegistry: {
|
|
7
|
+
getProvider(id: string): Provider | undefined;
|
|
8
|
+
getApiKeyAndHeaders(model: Model<string>): Promise<
|
|
9
|
+
| {
|
|
10
|
+
ok: true;
|
|
11
|
+
apiKey?: string;
|
|
12
|
+
headers?: Record<string, string | null>;
|
|
13
|
+
baseUrl?: string;
|
|
14
|
+
env?: Record<string, string>;
|
|
15
|
+
}
|
|
16
|
+
| { ok: false }
|
|
17
|
+
>;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export interface PackageContext extends ParentModelSource {
|
|
21
|
+
readonly cwd: string;
|
|
22
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { runChildSession } from "./child-session.ts";
|
|
4
|
+
import { LIMITS } from "./contracts.ts";
|
|
5
|
+
import type { DurableWorkflowEngineOptions } from "./durable-contracts.ts";
|
|
6
|
+
import type { ParentModelSource } from "./model-source.ts";
|
|
7
|
+
import { runtimeForWorkerRoute } from "./parent-provider.ts";
|
|
8
|
+
import type { WorkerRoutePolicy } from "./route-policy.ts";
|
|
9
|
+
import { isSafeRegularFile } from "./safe-path.ts";
|
|
10
|
+
import {
|
|
11
|
+
classifyCandidateContextRequest,
|
|
12
|
+
permitsContextRead,
|
|
13
|
+
} from "./submit-tool.ts";
|
|
14
|
+
import { transportFailureError } from "./transport-budget.ts";
|
|
15
|
+
import type { DurableCandidateProposal } from "./workflow-policy.ts";
|
|
16
|
+
|
|
17
|
+
function childRequestId(
|
|
18
|
+
operationId: string,
|
|
19
|
+
taskId: string,
|
|
20
|
+
phase: string,
|
|
21
|
+
): string {
|
|
22
|
+
return `child-${createHash("sha256").update(`${operationId}\0${taskId}\0${phase}`).digest("hex").slice(0, 40)}`;
|
|
23
|
+
}
|
|
24
|
+
export async function proposePackageCandidate(
|
|
25
|
+
input: Parameters<DurableWorkflowEngineOptions["proposeCandidate"]>[0],
|
|
26
|
+
context: ParentModelSource | undefined,
|
|
27
|
+
implementationAgent: { content: string },
|
|
28
|
+
): Promise<DurableCandidateProposal> {
|
|
29
|
+
if (!context) {
|
|
30
|
+
return { kind: "paused", code: "parent-context-unavailable" };
|
|
31
|
+
}
|
|
32
|
+
const phaseRuntime = await runtimeForWorkerRoute(
|
|
33
|
+
input.route as WorkerRoutePolicy,
|
|
34
|
+
context,
|
|
35
|
+
input.signal,
|
|
36
|
+
process.env,
|
|
37
|
+
);
|
|
38
|
+
if (!phaseRuntime.ok) {
|
|
39
|
+
if (phaseRuntime.failure.kind === "cancelled") {
|
|
40
|
+
return { kind: "operation-cancelled", code: "cancelled" };
|
|
41
|
+
}
|
|
42
|
+
throw new Error(phaseRuntime.failure.code);
|
|
43
|
+
}
|
|
44
|
+
const phase = input.task.phases[input.phase];
|
|
45
|
+
if (!phase) {
|
|
46
|
+
return { kind: "paused", code: "task-phase-unavailable" };
|
|
47
|
+
}
|
|
48
|
+
const taskPhases = Object.values(input.task.phases);
|
|
49
|
+
const executionBoundary = input.artifactCorrection
|
|
50
|
+
? {
|
|
51
|
+
read: [
|
|
52
|
+
...new Set(
|
|
53
|
+
taskPhases.flatMap((boundary) => [
|
|
54
|
+
...boundary.read,
|
|
55
|
+
...boundary.write,
|
|
56
|
+
...boundary.delete,
|
|
57
|
+
]),
|
|
58
|
+
),
|
|
59
|
+
].sort(),
|
|
60
|
+
write: [
|
|
61
|
+
...new Set(taskPhases.flatMap((boundary) => boundary.write)),
|
|
62
|
+
].sort(),
|
|
63
|
+
delete: [
|
|
64
|
+
...new Set(taskPhases.flatMap((boundary) => boundary.delete)),
|
|
65
|
+
].sort(),
|
|
66
|
+
}
|
|
67
|
+
: {
|
|
68
|
+
read: [
|
|
69
|
+
...new Set(
|
|
70
|
+
taskPhases.flatMap((boundary) => [
|
|
71
|
+
...boundary.read,
|
|
72
|
+
...boundary.write,
|
|
73
|
+
...boundary.delete,
|
|
74
|
+
]),
|
|
75
|
+
),
|
|
76
|
+
].sort(),
|
|
77
|
+
write: [...phase.write],
|
|
78
|
+
delete: [...phase.delete],
|
|
79
|
+
};
|
|
80
|
+
executionBoundary.read = [
|
|
81
|
+
...new Set([
|
|
82
|
+
...executionBoundary.read,
|
|
83
|
+
...(input.contextReadPaths ?? []).filter(
|
|
84
|
+
(relative) =>
|
|
85
|
+
permitsContextRead(relative, input.task.roots) &&
|
|
86
|
+
isSafeRegularFile(input.workspaceRoot, relative),
|
|
87
|
+
),
|
|
88
|
+
]),
|
|
89
|
+
].sort();
|
|
90
|
+
const phaseContract = {
|
|
91
|
+
candidateId: input.candidateArtifact.identity.candidateId,
|
|
92
|
+
taskId: input.taskId,
|
|
93
|
+
phase: input.phase,
|
|
94
|
+
readSet: executionBoundary.read,
|
|
95
|
+
writeSet: executionBoundary.write,
|
|
96
|
+
deleteSet: executionBoundary.delete,
|
|
97
|
+
verification: structuredClone(phase.verification),
|
|
98
|
+
agentsImpact: input.task.agents.impact,
|
|
99
|
+
agentsTarget: input.task.agents.target ?? null,
|
|
100
|
+
agentsManagedOnly: true,
|
|
101
|
+
agentsWriteAllowed: false,
|
|
102
|
+
impactClosure: structuredClone(input.task.impactClosure),
|
|
103
|
+
...(input.artifactCorrection
|
|
104
|
+
? { artifactCorrection: structuredClone(input.artifactCorrection) }
|
|
105
|
+
: {}),
|
|
106
|
+
...(input.contextRequest
|
|
107
|
+
? { requestedContext: structuredClone(input.contextRequest) }
|
|
108
|
+
: {}),
|
|
109
|
+
...(input.recoveryFeedback
|
|
110
|
+
? { recoveryFeedback: structuredClone(input.recoveryFeedback) }
|
|
111
|
+
: {}),
|
|
112
|
+
...(input.repair
|
|
113
|
+
? {
|
|
114
|
+
repair: {
|
|
115
|
+
attempt: input.repair.attempt,
|
|
116
|
+
attribution: input.repair.attribution,
|
|
117
|
+
failureIdentities: [...input.repair.failureIdentities],
|
|
118
|
+
verification: structuredClone(input.task.repairVerification),
|
|
119
|
+
inBoundaryOnly: true,
|
|
120
|
+
},
|
|
121
|
+
}
|
|
122
|
+
: {}),
|
|
123
|
+
};
|
|
124
|
+
const child = await runChildSession({
|
|
125
|
+
cwd: input.workspaceRoot,
|
|
126
|
+
modelRuntime: phaseRuntime.modelRuntime,
|
|
127
|
+
model: phaseRuntime.model,
|
|
128
|
+
systemPrompt: [
|
|
129
|
+
implementationAgent.content,
|
|
130
|
+
input.task.objective,
|
|
131
|
+
input.task.context.agents,
|
|
132
|
+
input.task.context.contract,
|
|
133
|
+
"Use recoveryFeedback to change the failing approach. For compact-patch, prefer exact replace operations and omit unchanged bodies; submit one complete atomic patch. Never repeat an unchanged failing submission or weaken verification to obtain a pass.",
|
|
134
|
+
`<phase-contract>${JSON.stringify(phaseContract)}</phase-contract>`,
|
|
135
|
+
].join("\n\n"),
|
|
136
|
+
requestId: childRequestId(input.operationId, input.taskId, input.phase),
|
|
137
|
+
taskId: input.taskId,
|
|
138
|
+
role: "implementation-worker",
|
|
139
|
+
phase: input.phase,
|
|
140
|
+
output: "diff",
|
|
141
|
+
roots: input.task.roots.map((root) =>
|
|
142
|
+
path.resolve(input.workspaceRoot, root),
|
|
143
|
+
),
|
|
144
|
+
allowedPaths: [
|
|
145
|
+
...new Set([
|
|
146
|
+
...executionBoundary.read,
|
|
147
|
+
...executionBoundary.write,
|
|
148
|
+
...executionBoundary.delete,
|
|
149
|
+
]),
|
|
150
|
+
],
|
|
151
|
+
timeoutMs: LIMITS.phaseTimeoutMs,
|
|
152
|
+
signal: input.signal,
|
|
153
|
+
ledgerProjection: input.ledgerProjection,
|
|
154
|
+
candidateArtifact: input.candidateArtifact,
|
|
155
|
+
onStreamStart: input.onRequestStart,
|
|
156
|
+
onStreamHeaders: input.onHeaders,
|
|
157
|
+
onStreamProgress: input.onProgress,
|
|
158
|
+
});
|
|
159
|
+
if (!child.ok) {
|
|
160
|
+
if (child.failure.kind === "transport") {
|
|
161
|
+
throw transportFailureError(child.failure.code);
|
|
162
|
+
}
|
|
163
|
+
if (child.failure.kind === "cancelled") {
|
|
164
|
+
return { kind: "operation-cancelled", code: "cancelled" };
|
|
165
|
+
}
|
|
166
|
+
if (child.failure.kind === "approval-boundary") {
|
|
167
|
+
return { kind: "approval-needed", code: child.failure.code };
|
|
168
|
+
}
|
|
169
|
+
if (child.failure.kind === "execution-limit") {
|
|
170
|
+
return { kind: "paused", code: "needs-task-split" };
|
|
171
|
+
}
|
|
172
|
+
if (child.failure.kind === "result-limit") {
|
|
173
|
+
return { kind: "retryable", code: "needs-task-split" };
|
|
174
|
+
}
|
|
175
|
+
const attemptDiagnostic = {
|
|
176
|
+
finalCategory: child.classification.finalCategory,
|
|
177
|
+
submitAttempts: child.classification.attempts,
|
|
178
|
+
schema: child.classification.schema,
|
|
179
|
+
identityMismatch: Object.entries(child.classification.identity)
|
|
180
|
+
.filter(([, matches]) => !matches)
|
|
181
|
+
.map(([dimension]) => dimension),
|
|
182
|
+
};
|
|
183
|
+
return child.failure.kind === "environment" ||
|
|
184
|
+
child.failure.kind === "verification-adapter"
|
|
185
|
+
? { kind: "paused", code: child.failure.code, attemptDiagnostic }
|
|
186
|
+
: { kind: "retryable", code: child.failure.code, attemptDiagnostic };
|
|
187
|
+
}
|
|
188
|
+
const result = child.result;
|
|
189
|
+
if (result.kind === "context-request") {
|
|
190
|
+
return classifyCandidateContextRequest(result, {
|
|
191
|
+
phase: input.phase,
|
|
192
|
+
contextReadRoots: input.task.roots,
|
|
193
|
+
readPaths: phase.read,
|
|
194
|
+
writePaths: phase.write,
|
|
195
|
+
deletePaths: phase.delete,
|
|
196
|
+
taskPaths: [
|
|
197
|
+
...new Set(
|
|
198
|
+
Object.values(input.task.phases).flatMap((boundary) => [
|
|
199
|
+
...boundary.read,
|
|
200
|
+
...boundary.write,
|
|
201
|
+
...boundary.delete,
|
|
202
|
+
]),
|
|
203
|
+
),
|
|
204
|
+
],
|
|
205
|
+
redWritePaths: input.task.phases.red.write,
|
|
206
|
+
agents: {
|
|
207
|
+
impact: input.task.agents.impact,
|
|
208
|
+
...(input.task.agents.target
|
|
209
|
+
? { target: input.task.agents.target }
|
|
210
|
+
: {}),
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
if (result.kind !== "sealed-candidate") {
|
|
215
|
+
return { kind: "retryable", code: "candidate-diff-invalid" };
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
kind: "sealed-candidate",
|
|
219
|
+
candidateId: result.candidateId,
|
|
220
|
+
artifactHash: result.artifactHash,
|
|
221
|
+
bytes: result.bytes,
|
|
222
|
+
paths: [...result.paths],
|
|
223
|
+
};
|
|
224
|
+
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import type { WorkflowActivityUpdate } from "./activity-contracts.ts";
|
|
5
|
+
import { loadAgentDefinitions } from "./agent-registry.ts";
|
|
6
|
+
import type {
|
|
7
|
+
DesignEvidenceResult,
|
|
8
|
+
StructuredVerificationContract,
|
|
9
|
+
} from "./contracts.ts";
|
|
10
|
+
import { validateControlCommand } from "./control-contracts.ts";
|
|
11
|
+
import { DesignController } from "./design-control.ts";
|
|
12
|
+
import type { PackageContext } from "./model-source.ts";
|
|
13
|
+
import { inspectOpenSpecDelivery } from "./openspec-cli.ts";
|
|
14
|
+
import { proposePackageCandidate } from "./package-candidate.ts";
|
|
15
|
+
import { packageDeliverySource } from "./package-delivery.ts";
|
|
16
|
+
import {
|
|
17
|
+
changeVerificationResult,
|
|
18
|
+
executePackageVerification,
|
|
19
|
+
phaseVerificationResult,
|
|
20
|
+
} from "./package-verification.ts";
|
|
21
|
+
import {
|
|
22
|
+
inspectRoutePolicy,
|
|
23
|
+
loadRoutePolicy,
|
|
24
|
+
type RoutePolicyResolution,
|
|
25
|
+
unavailableRoutePolicy,
|
|
26
|
+
} from "./route-policy.ts";
|
|
27
|
+
import { resolveStateRoot } from "./state-root.ts";
|
|
28
|
+
import { captureVerificationEnvironmentIdentity } from "./verification-environment.ts";
|
|
29
|
+
import { openDurableWorkflowEngine } from "./workflow-engine.ts";
|
|
30
|
+
export interface PackageWorkflowService {
|
|
31
|
+
execute(
|
|
32
|
+
command: unknown,
|
|
33
|
+
context?: PackageContext,
|
|
34
|
+
signal?: AbortSignal,
|
|
35
|
+
onActivity?: (event: WorkflowActivityUpdate) => void,
|
|
36
|
+
): Promise<Record<string, unknown>>;
|
|
37
|
+
executeDesign(request: unknown): Promise<Record<string, unknown>>;
|
|
38
|
+
executeAmendment(
|
|
39
|
+
change: string,
|
|
40
|
+
batchId: string,
|
|
41
|
+
request: unknown,
|
|
42
|
+
): Promise<Record<string, unknown>>;
|
|
43
|
+
assertDesignRun(runId: string): void;
|
|
44
|
+
recordDesignEvidence(input: {
|
|
45
|
+
runId: string;
|
|
46
|
+
evidence: DesignEvidenceResult;
|
|
47
|
+
}): unknown;
|
|
48
|
+
close(): void | Promise<void>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function affectedVerification(
|
|
52
|
+
task: Parameters<
|
|
53
|
+
Parameters<typeof openDurableWorkflowEngine>[0]["proposeCandidate"]
|
|
54
|
+
>[0]["task"],
|
|
55
|
+
): StructuredVerificationContract {
|
|
56
|
+
if (task.affectedVerification) {
|
|
57
|
+
return structuredClone(task.affectedVerification);
|
|
58
|
+
}
|
|
59
|
+
const finalPhase = task.phases.refactor ?? task.phases.green;
|
|
60
|
+
const verification = structuredClone(finalPhase.verification);
|
|
61
|
+
const affected = [...new Set(task.impactClosure.affectedSuite)].sort();
|
|
62
|
+
if (affected.length === 0) return verification;
|
|
63
|
+
if (verification.kind === "vitest") {
|
|
64
|
+
return { ...verification, testFiles: affected };
|
|
65
|
+
}
|
|
66
|
+
if (verification.kind === "steps") {
|
|
67
|
+
return {
|
|
68
|
+
...verification,
|
|
69
|
+
steps: verification.steps.map((step) =>
|
|
70
|
+
step.kind === "vitest" ? { ...step, testFiles: affected } : step,
|
|
71
|
+
),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return verification;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function openPackageWorkflowService(
|
|
78
|
+
initialContext: PackageContext,
|
|
79
|
+
): PackageWorkflowService {
|
|
80
|
+
const consumerRoot = path.resolve(initialContext.cwd);
|
|
81
|
+
const routeResolution = loadRoutePolicy({
|
|
82
|
+
cwd: consumerRoot,
|
|
83
|
+
home: homedir(),
|
|
84
|
+
...(initialContext.model ? { parentModel: initialContext.model } : {}),
|
|
85
|
+
});
|
|
86
|
+
const stateRoot = resolveStateRoot({
|
|
87
|
+
consumerRoot,
|
|
88
|
+
xdgStateHome: process.env.XDG_STATE_HOME,
|
|
89
|
+
});
|
|
90
|
+
const contexts = new AsyncLocalStorage<PackageContext>();
|
|
91
|
+
const design = DesignController.open({
|
|
92
|
+
consumerRoot,
|
|
93
|
+
stateRoot,
|
|
94
|
+
inspectOpenSpec: inspectOpenSpecDelivery,
|
|
95
|
+
});
|
|
96
|
+
const implementationAgent = loadAgentDefinitions().find(
|
|
97
|
+
(agent) => agent.role === "implementation-worker",
|
|
98
|
+
);
|
|
99
|
+
if (!implementationAgent) {
|
|
100
|
+
throw new Error("implementation-worker-agent-unavailable");
|
|
101
|
+
}
|
|
102
|
+
const hostLimit = (name: string, fallback: number): number => {
|
|
103
|
+
const value =
|
|
104
|
+
process.env[name] === undefined ? fallback : Number(process.env[name]);
|
|
105
|
+
if (!Number.isSafeInteger(value) || value < 1)
|
|
106
|
+
throw new Error("verification-host-limit-invalid");
|
|
107
|
+
return value;
|
|
108
|
+
};
|
|
109
|
+
const maxReportBytes = hostLimit(
|
|
110
|
+
"ABEL_VERIFICATION_MAX_REPORT_BYTES",
|
|
111
|
+
64 * 1024 * 1024,
|
|
112
|
+
);
|
|
113
|
+
const workHardLimit = hostLimit("ABEL_WORK_MAX_UNITS", 512);
|
|
114
|
+
const engine = openDurableWorkflowEngine({
|
|
115
|
+
workHardLimit,
|
|
116
|
+
verificationPolicy: "report-file-v4",
|
|
117
|
+
verificationEnvironment: (plan, signal) =>
|
|
118
|
+
captureVerificationEnvironmentIdentity(
|
|
119
|
+
consumerRoot,
|
|
120
|
+
[
|
|
121
|
+
...plan.tasks.flatMap((task) => [
|
|
122
|
+
...Object.values(task.phases).map((phase) => phase.verification),
|
|
123
|
+
task.affectedVerification,
|
|
124
|
+
task.repairVerification,
|
|
125
|
+
]),
|
|
126
|
+
plan.verification.baseline.fullSuite,
|
|
127
|
+
plan.verification.change.fullSuite,
|
|
128
|
+
plan.verification.change.postApply,
|
|
129
|
+
...(plan.verification.agentsCheckpoint.verification
|
|
130
|
+
? [plan.verification.agentsCheckpoint.verification]
|
|
131
|
+
: []),
|
|
132
|
+
],
|
|
133
|
+
signal,
|
|
134
|
+
),
|
|
135
|
+
consumerRoot,
|
|
136
|
+
stateRoot,
|
|
137
|
+
deliverySource: packageDeliverySource(consumerRoot, {
|
|
138
|
+
verifyGateProof: (input) => design.verifyGateProof(input),
|
|
139
|
+
verifyFinalizedDelivery: (input) => design.verifyFinalizedDelivery(input),
|
|
140
|
+
}),
|
|
141
|
+
routePolicy: routeResolution.ok
|
|
142
|
+
? routeResolution.policy
|
|
143
|
+
: unavailableRoutePolicy(),
|
|
144
|
+
proposeCandidate: (input) =>
|
|
145
|
+
proposePackageCandidate(input, contexts.getStore(), implementationAgent),
|
|
146
|
+
verifyPhase: async (input) =>
|
|
147
|
+
phaseVerificationResult(
|
|
148
|
+
await executePackageVerification({
|
|
149
|
+
maxReportBytes,
|
|
150
|
+
executionWritePaths: input.executionWritePaths,
|
|
151
|
+
root: input.root,
|
|
152
|
+
dependencyOwner: consumerRoot,
|
|
153
|
+
verification: input.verification,
|
|
154
|
+
signal: input.signal,
|
|
155
|
+
}),
|
|
156
|
+
),
|
|
157
|
+
verifyChange: async (input) => {
|
|
158
|
+
const verifications = input.verification
|
|
159
|
+
? [input.verification]
|
|
160
|
+
: input.plan.tasks.map((task) => affectedVerification(task));
|
|
161
|
+
for (const verification of verifications) {
|
|
162
|
+
const result = await executePackageVerification({
|
|
163
|
+
maxReportBytes,
|
|
164
|
+
executionWritePaths: input.plan.tasks.flatMap((task) =>
|
|
165
|
+
Object.values(task.phases).flatMap((phase) => [
|
|
166
|
+
...phase.write,
|
|
167
|
+
...phase.delete,
|
|
168
|
+
]),
|
|
169
|
+
),
|
|
170
|
+
root: input.root,
|
|
171
|
+
dependencyOwner: consumerRoot,
|
|
172
|
+
verification,
|
|
173
|
+
signal: input.signal,
|
|
174
|
+
});
|
|
175
|
+
const observed = changeVerificationResult(result);
|
|
176
|
+
if (!observed.ok) return observed;
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
ok: true,
|
|
180
|
+
exitCode: 0,
|
|
181
|
+
classification: "expected-green",
|
|
182
|
+
};
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
return {
|
|
186
|
+
async execute(
|
|
187
|
+
command: unknown,
|
|
188
|
+
context = initialContext,
|
|
189
|
+
signal,
|
|
190
|
+
onActivity,
|
|
191
|
+
) {
|
|
192
|
+
const operationRouteResolution = loadRoutePolicy({
|
|
193
|
+
cwd: consumerRoot,
|
|
194
|
+
home: homedir(),
|
|
195
|
+
...(context.model ? { parentModel: context.model } : {}),
|
|
196
|
+
});
|
|
197
|
+
engine.updateRoutePolicy(
|
|
198
|
+
operationRouteResolution.ok
|
|
199
|
+
? operationRouteResolution.policy
|
|
200
|
+
: unavailableRoutePolicy(),
|
|
201
|
+
);
|
|
202
|
+
const validation = validateControlCommand(command);
|
|
203
|
+
if (!validation.ok) {
|
|
204
|
+
const error = new Error(validation.code);
|
|
205
|
+
error.name = "ControlCommandError";
|
|
206
|
+
throw error;
|
|
207
|
+
}
|
|
208
|
+
return contexts.run(context, async () => {
|
|
209
|
+
const outcome = await engine.execute(
|
|
210
|
+
validation.value,
|
|
211
|
+
signal,
|
|
212
|
+
onActivity,
|
|
213
|
+
);
|
|
214
|
+
const routePolicy = visibleRoutePolicyStatus(
|
|
215
|
+
operationRouteResolution,
|
|
216
|
+
engine.routePolicyStatus(),
|
|
217
|
+
);
|
|
218
|
+
return { ...outcome, routePolicy };
|
|
219
|
+
});
|
|
220
|
+
},
|
|
221
|
+
executeDesign(request: unknown) {
|
|
222
|
+
return design.execute(request);
|
|
223
|
+
},
|
|
224
|
+
executeAmendment(change: string, batchId: string, request: unknown) {
|
|
225
|
+
return engine.amend(change, batchId, request, (assertAuthority) =>
|
|
226
|
+
design.executeWithAuthority(request, assertAuthority),
|
|
227
|
+
);
|
|
228
|
+
},
|
|
229
|
+
assertDesignRun(runId: string) {
|
|
230
|
+
design.assertDesignRun(runId);
|
|
231
|
+
},
|
|
232
|
+
recordDesignEvidence(input) {
|
|
233
|
+
return design.recordEvidence(input);
|
|
234
|
+
},
|
|
235
|
+
async close() {
|
|
236
|
+
contexts.disable();
|
|
237
|
+
await engine.close();
|
|
238
|
+
design.close();
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function visibleRoutePolicyStatus(
|
|
244
|
+
resolution: RoutePolicyResolution,
|
|
245
|
+
brokerStatus: Record<string, unknown> | undefined,
|
|
246
|
+
): Record<string, unknown> {
|
|
247
|
+
if (!resolution.ok) return inspectRoutePolicy(resolution);
|
|
248
|
+
const inspected = brokerStatus ?? inspectRoutePolicy(resolution);
|
|
249
|
+
return {
|
|
250
|
+
...inspected,
|
|
251
|
+
source: { kind: resolution.source.kind },
|
|
252
|
+
};
|
|
253
|
+
}
|
package/src/packet-runtime.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import type { Usage } from "@earendil-works/pi-ai";
|
|
3
|
-
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
4
3
|
import { Activation, type ActivationState } from "./activation.ts";
|
|
5
4
|
import { loadAgentDefinitions } from "./agent-registry.ts";
|
|
6
5
|
import {
|
|
@@ -15,51 +14,34 @@ import {
|
|
|
15
14
|
type PacketEnvelope,
|
|
16
15
|
validatePacketEnvelope,
|
|
17
16
|
} from "./contracts.ts";
|
|
18
|
-
import type {
|
|
17
|
+
import type { PackageContext } from "./model-source.ts";
|
|
19
18
|
import { runtimeForWorkerRoute } from "./parent-provider.ts";
|
|
20
19
|
import {
|
|
21
20
|
loadRoutePolicy,
|
|
22
21
|
type RoutePolicy,
|
|
23
22
|
type WorkerRole,
|
|
24
23
|
} from "./route-policy.ts";
|
|
24
|
+
import {
|
|
25
|
+
isTransportTimeoutCode,
|
|
26
|
+
transportFailureError,
|
|
27
|
+
} from "./transport-budget.ts";
|
|
25
28
|
import { type BrokerActivityUpdate, RunWorkerBroker } from "./worker-broker.ts";
|
|
26
29
|
|
|
27
30
|
export const PACKET_ACTIONS = ["run", "cancel", "finish"] as const;
|
|
28
31
|
export type PacketAction = (typeof PACKET_ACTIONS)[number];
|
|
29
32
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
| "retrying"
|
|
36
|
-
| "completed"
|
|
37
|
-
| "failed"
|
|
38
|
-
| "cancelled"
|
|
39
|
-
| "timed-out";
|
|
40
|
-
|
|
41
|
-
export type PacketFailureReason =
|
|
42
|
-
| "subagent failed"
|
|
43
|
-
| "subagent cancelled"
|
|
44
|
-
| "phase timed out";
|
|
45
|
-
|
|
46
|
-
export interface PacketActivityEvent {
|
|
47
|
-
state: PacketActivityState;
|
|
48
|
-
requestId: string;
|
|
49
|
-
role: string;
|
|
50
|
-
phase: string;
|
|
51
|
-
objective: string;
|
|
52
|
-
sequence: number;
|
|
53
|
-
attempt?: number;
|
|
54
|
-
maxAttempts?: number;
|
|
55
|
-
code?: string;
|
|
56
|
-
wait?: string;
|
|
57
|
-
failureReason?: PacketFailureReason;
|
|
58
|
-
}
|
|
33
|
+
import type {
|
|
34
|
+
PacketActivityEvent,
|
|
35
|
+
PacketActivityObserver,
|
|
36
|
+
PacketActivityState,
|
|
37
|
+
} from "./activity-contracts.ts";
|
|
59
38
|
|
|
60
|
-
export type
|
|
61
|
-
|
|
62
|
-
|
|
39
|
+
export type {
|
|
40
|
+
PacketActivityEvent,
|
|
41
|
+
PacketActivityObserver,
|
|
42
|
+
PacketActivityState,
|
|
43
|
+
PacketFailureReason,
|
|
44
|
+
} from "./activity-contracts.ts";
|
|
63
45
|
|
|
64
46
|
export type PacketDispatchResult =
|
|
65
47
|
| {
|
|
@@ -78,7 +60,6 @@ export type PacketDispatchResult =
|
|
|
78
60
|
|
|
79
61
|
export interface PacketRuntimeOptions {
|
|
80
62
|
activation?: Activation;
|
|
81
|
-
parentPayloadBridge: ParentPayloadBridge;
|
|
82
63
|
routePolicy?: RoutePolicy;
|
|
83
64
|
routePolicyHome?: string;
|
|
84
65
|
environment?: Readonly<Record<string, string | undefined>>;
|
|
@@ -86,10 +67,7 @@ export interface PacketRuntimeOptions {
|
|
|
86
67
|
childRunner?: PacketChildRunner;
|
|
87
68
|
}
|
|
88
69
|
|
|
89
|
-
export type PacketContext =
|
|
90
|
-
ExtensionContext,
|
|
91
|
-
"cwd" | "model" | "modelRegistry"
|
|
92
|
-
>;
|
|
70
|
+
export type PacketContext = PackageContext;
|
|
93
71
|
|
|
94
72
|
export type PacketChildRunner = (input: {
|
|
95
73
|
packet: PacketEnvelope;
|
|
@@ -162,7 +140,6 @@ function syntheticChildFailure(
|
|
|
162
140
|
export class PacketRuntime {
|
|
163
141
|
readonly activation: Activation;
|
|
164
142
|
readonly limits = LIMITS;
|
|
165
|
-
readonly #parentPayloadBridge: ParentPayloadBridge;
|
|
166
143
|
readonly #routePolicy?: RoutePolicy;
|
|
167
144
|
readonly #routePolicyHome?: string;
|
|
168
145
|
readonly #environment: Readonly<Record<string, string | undefined>>;
|
|
@@ -178,7 +155,6 @@ export class PacketRuntime {
|
|
|
178
155
|
|
|
179
156
|
constructor(options: PacketRuntimeOptions) {
|
|
180
157
|
this.activation = options.activation ?? new Activation();
|
|
181
|
-
this.#parentPayloadBridge = options.parentPayloadBridge;
|
|
182
158
|
this.#routePolicy = options.routePolicy;
|
|
183
159
|
this.#routePolicyHome = options.routePolicyHome;
|
|
184
160
|
this.#environment = options.environment ?? process.env;
|
|
@@ -375,10 +351,8 @@ export class PacketRuntime {
|
|
|
375
351
|
const phase = await runtimeForWorkerRoute(
|
|
376
352
|
attempt.route,
|
|
377
353
|
context,
|
|
378
|
-
this.#parentPayloadBridge,
|
|
379
354
|
attempt.signal,
|
|
380
355
|
this.#environment,
|
|
381
|
-
{ onResponse: attempt.onHeaders },
|
|
382
356
|
);
|
|
383
357
|
if (!phase.ok) {
|
|
384
358
|
if (phase.failure.kind === "cancelled") {
|
|
@@ -422,19 +396,23 @@ export class PacketRuntime {
|
|
|
422
396
|
],
|
|
423
397
|
timeoutMs: LIMITS.phaseTimeoutMs,
|
|
424
398
|
signal: attempt.signal,
|
|
425
|
-
|
|
399
|
+
onStreamStart: attempt.onRequestStart,
|
|
400
|
+
onStreamHeaders: attempt.onHeaders,
|
|
426
401
|
onStreamProgress: attempt.onProgress,
|
|
427
402
|
});
|
|
428
403
|
brokerUsage.add(`attempt:${childAttempt++}`, child.usage);
|
|
429
404
|
if (!child.ok && child.failure.kind === "transport") {
|
|
430
|
-
throw
|
|
405
|
+
throw transportFailureError(child.failure.code);
|
|
431
406
|
}
|
|
432
407
|
return { kind: "child" as const, child };
|
|
433
408
|
},
|
|
434
409
|
});
|
|
435
410
|
if (!routed.ok) {
|
|
436
411
|
const cancelled = routed.state === "cancelled" || signal.aborted;
|
|
437
|
-
const timedOut =
|
|
412
|
+
const timedOut =
|
|
413
|
+
!cancelled &&
|
|
414
|
+
(isTransportTimeoutCode(routed.code) ||
|
|
415
|
+
routed.code === "phase-timeout");
|
|
438
416
|
return withBrokerUsage(
|
|
439
417
|
syntheticChildFailure(
|
|
440
418
|
cancelled ? "packet cancelled" : routed.code,
|
|
@@ -443,7 +421,9 @@ export class PacketRuntime {
|
|
|
443
421
|
: timedOut
|
|
444
422
|
? {
|
|
445
423
|
kind: "transport",
|
|
446
|
-
code:
|
|
424
|
+
code: isTransportTimeoutCode(routed.code)
|
|
425
|
+
? routed.code
|
|
426
|
+
: "timeout",
|
|
447
427
|
stage: "child-timeout",
|
|
448
428
|
}
|
|
449
429
|
: {
|
|
@@ -582,7 +562,6 @@ export class PacketRuntime {
|
|
|
582
562
|
await this.cancel();
|
|
583
563
|
this.#brokers.clear();
|
|
584
564
|
this.#brokerPolicies.clear();
|
|
585
|
-
this.#parentPayloadBridge.clear();
|
|
586
565
|
}
|
|
587
566
|
|
|
588
567
|
get state(): ActivationState {
|