@abelxiaoxing/cadence 1.0.14 → 1.1.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 +39 -13
- package/agents/contract-reviewer.md +2 -2
- package/agents/design-explorer.md +1 -1
- package/agents/diagnosis-worker.md +6 -3
- package/agents/implementation-worker.md +14 -9
- package/config/.env.example +5 -17
- package/config/routes.example.json +42 -0
- package/package.json +3 -3
- package/prompts/abel-design.md +206 -228
- package/prompts/abel-diagnose.md +39 -18
- package/prompts/abel-implement.md +122 -178
- package/prompts/abel-init.md +36 -33
- package/skills/abel-workflow/SKILL.md +190 -168
- package/src/apply-transaction.ts +1427 -0
- package/src/artifact-store.ts +204 -0
- package/src/child-session.ts +136 -17
- package/src/contracts.ts +121 -696
- package/src/control-contracts.ts +246 -0
- package/src/delivery-compiler.ts +1118 -0
- package/src/design-control.ts +846 -0
- package/src/design-journal.ts +1569 -0
- package/src/file-snapshot.ts +5 -0
- package/src/implement-graph.ts +11 -4
- package/src/index.ts +1833 -118
- package/src/isolation-backend.ts +269 -0
- package/src/packet-runtime.ts +533 -0
- package/src/parent-provider.ts +23 -4
- package/src/route-policy.ts +483 -0
- package/src/run-state.ts +430 -0
- package/src/run-store.ts +990 -0
- package/src/safe-path.ts +11 -0
- package/src/scoped-tools.ts +36 -0
- package/src/state-root.ts +215 -0
- package/src/subagent-activity.ts +526 -27
- package/src/submit-tool.ts +274 -0
- package/src/task-ledger.ts +1380 -0
- package/src/verification-capability.ts +560 -45
- package/src/worker-broker.ts +703 -0
- package/src/workflow-engine.ts +7706 -0
- package/src/workspace-store.ts +544 -0
- package/src/candidate-preflight.ts +0 -1618
- package/src/drain.ts +0 -20
- package/src/patch.ts +0 -505
- package/src/result-store.ts +0 -312
- package/src/runtime.ts +0 -2436
- package/src/scheduler.ts +0 -384
- package/src/subagent-endpoint.ts +0 -158
- package/src/worker.ts +0 -204
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import type { Usage } from "@earendil-works/pi-ai";
|
|
3
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import { Activation, type ActivationState } from "./activation.ts";
|
|
5
|
+
import { loadAgentDefinitions } from "./agent-registry.ts";
|
|
6
|
+
import {
|
|
7
|
+
type ChildFailureKind,
|
|
8
|
+
type ChildSessionResult,
|
|
9
|
+
runChildSession,
|
|
10
|
+
UsageAggregator,
|
|
11
|
+
} from "./child-session.ts";
|
|
12
|
+
import {
|
|
13
|
+
type ChildFailure,
|
|
14
|
+
LIMITS,
|
|
15
|
+
type PacketEnvelope,
|
|
16
|
+
validatePacketEnvelope,
|
|
17
|
+
} from "./contracts.ts";
|
|
18
|
+
import type { ParentPayloadBridge } from "./parent-payload-bridge.ts";
|
|
19
|
+
import { runtimeForWorkerRoute } from "./parent-provider.ts";
|
|
20
|
+
import {
|
|
21
|
+
loadRoutePolicy,
|
|
22
|
+
type RoutePolicy,
|
|
23
|
+
unavailableRoutePolicy,
|
|
24
|
+
type WorkerRole,
|
|
25
|
+
} from "./route-policy.ts";
|
|
26
|
+
import { type BrokerActivityUpdate, RunWorkerBroker } from "./worker-broker.ts";
|
|
27
|
+
|
|
28
|
+
export const PACKET_ACTIONS = ["run", "cancel", "finish"] as const;
|
|
29
|
+
export type PacketAction = (typeof PACKET_ACTIONS)[number];
|
|
30
|
+
|
|
31
|
+
export type PacketActivityState =
|
|
32
|
+
| "queued"
|
|
33
|
+
| "connecting"
|
|
34
|
+
| "waiting-first-response"
|
|
35
|
+
| "running"
|
|
36
|
+
| "retrying"
|
|
37
|
+
| "completed"
|
|
38
|
+
| "failed"
|
|
39
|
+
| "cancelled"
|
|
40
|
+
| "timed-out";
|
|
41
|
+
|
|
42
|
+
export type PacketFailureReason =
|
|
43
|
+
| "subagent failed"
|
|
44
|
+
| "subagent cancelled"
|
|
45
|
+
| "phase timed out";
|
|
46
|
+
|
|
47
|
+
export interface PacketActivityEvent {
|
|
48
|
+
state: PacketActivityState;
|
|
49
|
+
requestId: string;
|
|
50
|
+
role: string;
|
|
51
|
+
phase: string;
|
|
52
|
+
objective: string;
|
|
53
|
+
sequence: number;
|
|
54
|
+
attempt?: number;
|
|
55
|
+
maxAttempts?: number;
|
|
56
|
+
code?: string;
|
|
57
|
+
wait?: string;
|
|
58
|
+
failureReason?: PacketFailureReason;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export type PacketActivityObserver = (
|
|
62
|
+
event: PacketActivityEvent,
|
|
63
|
+
) => void | Promise<void>;
|
|
64
|
+
|
|
65
|
+
export type PacketDispatchResult =
|
|
66
|
+
| {
|
|
67
|
+
ok: true;
|
|
68
|
+
action: PacketAction;
|
|
69
|
+
result?: unknown;
|
|
70
|
+
usage?: Usage;
|
|
71
|
+
}
|
|
72
|
+
| {
|
|
73
|
+
ok: false;
|
|
74
|
+
notReady?: true;
|
|
75
|
+
error: string;
|
|
76
|
+
failure?: ChildFailure;
|
|
77
|
+
usage?: Usage;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export interface PacketRuntimeOptions {
|
|
81
|
+
activation?: Activation;
|
|
82
|
+
parentPayloadBridge: ParentPayloadBridge;
|
|
83
|
+
routePolicy?: RoutePolicy;
|
|
84
|
+
routePolicyHome?: string;
|
|
85
|
+
environment?: Readonly<Record<string, string | undefined>>;
|
|
86
|
+
concurrency?: number;
|
|
87
|
+
childRunner?: PacketChildRunner;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export type PacketContext = Pick<
|
|
91
|
+
ExtensionContext,
|
|
92
|
+
"cwd" | "model" | "modelRegistry"
|
|
93
|
+
>;
|
|
94
|
+
|
|
95
|
+
export type PacketChildRunner = (input: {
|
|
96
|
+
packet: PacketEnvelope;
|
|
97
|
+
context: PacketContext;
|
|
98
|
+
signal: AbortSignal;
|
|
99
|
+
}) => Promise<ChildSessionResult>;
|
|
100
|
+
|
|
101
|
+
interface WaitingPacket {
|
|
102
|
+
controller: AbortController;
|
|
103
|
+
resolve(acquired: boolean): void;
|
|
104
|
+
onAbort(): void;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function cancelledResult(signal: AbortSignal): PacketDispatchResult {
|
|
108
|
+
const reason = signal.reason;
|
|
109
|
+
return {
|
|
110
|
+
ok: false,
|
|
111
|
+
error:
|
|
112
|
+
reason instanceof Error
|
|
113
|
+
? reason.message
|
|
114
|
+
: typeof reason === "string"
|
|
115
|
+
? reason
|
|
116
|
+
: "packet cancelled",
|
|
117
|
+
failure: { kind: "cancelled", code: "cancelled" },
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function failureState(kind: ChildFailureKind): PacketActivityState {
|
|
122
|
+
return kind === "cancelled"
|
|
123
|
+
? "cancelled"
|
|
124
|
+
: kind === "timed-out"
|
|
125
|
+
? "timed-out"
|
|
126
|
+
: "failed";
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function syntheticChildFailure(
|
|
130
|
+
error: string,
|
|
131
|
+
failure: ChildFailure,
|
|
132
|
+
failureKind: ChildFailureKind = failure.kind === "cancelled"
|
|
133
|
+
? "cancelled"
|
|
134
|
+
: "failed",
|
|
135
|
+
): ChildSessionResult {
|
|
136
|
+
return {
|
|
137
|
+
ok: false,
|
|
138
|
+
error,
|
|
139
|
+
failure,
|
|
140
|
+
failureKind,
|
|
141
|
+
transportFailure: failure.kind === "transport",
|
|
142
|
+
disposeCount: 0,
|
|
143
|
+
usage: new UsageAggregator().total(),
|
|
144
|
+
classification: {
|
|
145
|
+
finalCategory: "no-final-assistant",
|
|
146
|
+
attempts: 0,
|
|
147
|
+
schema: "invalid",
|
|
148
|
+
identity: { request: false, role: false, task: false, phase: false },
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export class PacketRuntime {
|
|
154
|
+
readonly activation: Activation;
|
|
155
|
+
readonly limits = LIMITS;
|
|
156
|
+
readonly #parentPayloadBridge: ParentPayloadBridge;
|
|
157
|
+
readonly #routePolicy?: RoutePolicy;
|
|
158
|
+
readonly #routePolicyHome?: string;
|
|
159
|
+
readonly #environment: Readonly<Record<string, string | undefined>>;
|
|
160
|
+
readonly #concurrency: number;
|
|
161
|
+
readonly #childRunner?: PacketChildRunner;
|
|
162
|
+
readonly #brokers = new Map<string, RunWorkerBroker>();
|
|
163
|
+
readonly #brokerPolicies = new Map<string, string>();
|
|
164
|
+
readonly #controllers = new Set<AbortController>();
|
|
165
|
+
readonly #operations = new Set<Promise<PacketDispatchResult>>();
|
|
166
|
+
readonly #waiting: WaitingPacket[] = [];
|
|
167
|
+
#active = 0;
|
|
168
|
+
#sequence = 0;
|
|
169
|
+
|
|
170
|
+
constructor(options: PacketRuntimeOptions) {
|
|
171
|
+
this.activation = options.activation ?? new Activation();
|
|
172
|
+
this.#parentPayloadBridge = options.parentPayloadBridge;
|
|
173
|
+
this.#routePolicy = options.routePolicy;
|
|
174
|
+
this.#routePolicyHome = options.routePolicyHome;
|
|
175
|
+
this.#environment = options.environment ?? process.env;
|
|
176
|
+
this.#childRunner = options.childRunner;
|
|
177
|
+
this.#concurrency = Math.max(
|
|
178
|
+
1,
|
|
179
|
+
Math.min(
|
|
180
|
+
options.concurrency ?? LIMITS.maxActiveChildSessions,
|
|
181
|
+
LIMITS.maxActiveChildSessions,
|
|
182
|
+
),
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
validateRequest(value: unknown) {
|
|
187
|
+
return validatePacketEnvelope(value);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async execute(
|
|
191
|
+
action: string,
|
|
192
|
+
params: { request?: unknown },
|
|
193
|
+
context?: PacketContext,
|
|
194
|
+
signal?: AbortSignal,
|
|
195
|
+
observer?: PacketActivityObserver,
|
|
196
|
+
): Promise<PacketDispatchResult> {
|
|
197
|
+
if (!(PACKET_ACTIONS as readonly string[]).includes(action)) {
|
|
198
|
+
throw new Error(`unknown packet action: ${String(action)}`);
|
|
199
|
+
}
|
|
200
|
+
if (!this.activation.isActive()) {
|
|
201
|
+
return { ok: false, notReady: true, error: "dispatcher is not active" };
|
|
202
|
+
}
|
|
203
|
+
if (action === "cancel") {
|
|
204
|
+
await this.cancel();
|
|
205
|
+
return { ok: true, action };
|
|
206
|
+
}
|
|
207
|
+
if (action === "finish") {
|
|
208
|
+
await this.drain();
|
|
209
|
+
return { ok: true, action };
|
|
210
|
+
}
|
|
211
|
+
const operation = this.#run(params.request, context, signal, observer);
|
|
212
|
+
this.#operations.add(operation);
|
|
213
|
+
try {
|
|
214
|
+
return await operation;
|
|
215
|
+
} finally {
|
|
216
|
+
this.#operations.delete(operation);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async #run(
|
|
221
|
+
request: unknown,
|
|
222
|
+
context?: PacketContext,
|
|
223
|
+
parentSignal?: AbortSignal,
|
|
224
|
+
observer?: PacketActivityObserver,
|
|
225
|
+
): Promise<PacketDispatchResult> {
|
|
226
|
+
if (!context) return { ok: false, error: "run requires extension context" };
|
|
227
|
+
const validation = validatePacketEnvelope(request);
|
|
228
|
+
if (!validation.ok) return { ok: false, error: validation.reason };
|
|
229
|
+
const packet = validation.value;
|
|
230
|
+
const sequence = ++this.#sequence;
|
|
231
|
+
this.#notify(packet, observer, sequence, "queued");
|
|
232
|
+
|
|
233
|
+
const controller = new AbortController();
|
|
234
|
+
this.#controllers.add(controller);
|
|
235
|
+
const forwardCancellation = () => {
|
|
236
|
+
if (!controller.signal.aborted) controller.abort(parentSignal?.reason);
|
|
237
|
+
};
|
|
238
|
+
if (parentSignal?.aborted) forwardCancellation();
|
|
239
|
+
else
|
|
240
|
+
parentSignal?.addEventListener("abort", forwardCancellation, {
|
|
241
|
+
once: true,
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
let acquired = false;
|
|
245
|
+
try {
|
|
246
|
+
acquired = await this.#acquire(controller);
|
|
247
|
+
if (!acquired || controller.signal.aborted) {
|
|
248
|
+
this.#notify(packet, observer, sequence, "cancelled");
|
|
249
|
+
return cancelledResult(controller.signal);
|
|
250
|
+
}
|
|
251
|
+
if (this.#childRunner) {
|
|
252
|
+
this.#notify(packet, observer, sequence, "running");
|
|
253
|
+
}
|
|
254
|
+
const usage = new UsageAggregator();
|
|
255
|
+
const child = await this.#dispatchChild(
|
|
256
|
+
packet,
|
|
257
|
+
context,
|
|
258
|
+
controller.signal,
|
|
259
|
+
(activity) =>
|
|
260
|
+
this.#notifyBrokerActivity(packet, observer, sequence, activity),
|
|
261
|
+
);
|
|
262
|
+
usage.add("launch:0", child.usage);
|
|
263
|
+
if (!child.ok) {
|
|
264
|
+
const state = failureState(child.failureKind);
|
|
265
|
+
this.#notify(packet, observer, sequence, state);
|
|
266
|
+
return {
|
|
267
|
+
ok: false,
|
|
268
|
+
error: child.error,
|
|
269
|
+
failure: child.failure,
|
|
270
|
+
usage: usage.total(),
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
this.#notify(packet, observer, sequence, "completed");
|
|
274
|
+
return {
|
|
275
|
+
ok: true,
|
|
276
|
+
action: "run",
|
|
277
|
+
result: child.result,
|
|
278
|
+
usage: usage.total(),
|
|
279
|
+
};
|
|
280
|
+
} catch (error) {
|
|
281
|
+
this.#notify(
|
|
282
|
+
packet,
|
|
283
|
+
observer,
|
|
284
|
+
sequence,
|
|
285
|
+
controller.signal.aborted ? "cancelled" : "failed",
|
|
286
|
+
);
|
|
287
|
+
throw error;
|
|
288
|
+
} finally {
|
|
289
|
+
parentSignal?.removeEventListener("abort", forwardCancellation);
|
|
290
|
+
this.#controllers.delete(controller);
|
|
291
|
+
if (acquired) this.#release();
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async #dispatchChild(
|
|
296
|
+
packet: PacketEnvelope,
|
|
297
|
+
context: PacketContext,
|
|
298
|
+
signal: AbortSignal,
|
|
299
|
+
onActivity?: (event: BrokerActivityUpdate) => void,
|
|
300
|
+
): Promise<ChildSessionResult> {
|
|
301
|
+
if (signal.aborted) {
|
|
302
|
+
const result = cancelledResult(signal);
|
|
303
|
+
return syntheticChildFailure(
|
|
304
|
+
result.ok ? "packet cancelled" : result.error,
|
|
305
|
+
{ kind: "cancelled", code: "cancelled" },
|
|
306
|
+
"cancelled",
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
if (this.#childRunner) {
|
|
310
|
+
return this.#childRunner({
|
|
311
|
+
packet: structuredClone(packet),
|
|
312
|
+
context,
|
|
313
|
+
signal,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
const agent = loadAgentDefinitions().find(
|
|
317
|
+
(candidate) => candidate.role === packet.role,
|
|
318
|
+
);
|
|
319
|
+
if (!agent) {
|
|
320
|
+
throw new Error("packet agent definition is unavailable");
|
|
321
|
+
}
|
|
322
|
+
const broker = this.#brokerFor(context.cwd);
|
|
323
|
+
const routed = await broker.run({
|
|
324
|
+
runId: `${packet.stage}:${packet.id}`,
|
|
325
|
+
operationId: packet.id,
|
|
326
|
+
role: packet.role as WorkerRole,
|
|
327
|
+
signal,
|
|
328
|
+
...(onActivity ? { onActivity } : {}),
|
|
329
|
+
execute: async (attempt) => {
|
|
330
|
+
const phase = await runtimeForWorkerRoute(
|
|
331
|
+
attempt.route,
|
|
332
|
+
context,
|
|
333
|
+
this.#parentPayloadBridge,
|
|
334
|
+
attempt.signal,
|
|
335
|
+
this.#environment,
|
|
336
|
+
);
|
|
337
|
+
if (!phase.ok) {
|
|
338
|
+
if (phase.failure.kind === "cancelled") {
|
|
339
|
+
return { kind: "phase-failure" as const, phase };
|
|
340
|
+
}
|
|
341
|
+
throw new Error(phase.failure.code);
|
|
342
|
+
}
|
|
343
|
+
const child = await runChildSession({
|
|
344
|
+
cwd: context.cwd,
|
|
345
|
+
modelRuntime: phase.modelRuntime,
|
|
346
|
+
model: phase.model,
|
|
347
|
+
systemPrompt: [
|
|
348
|
+
agent.content,
|
|
349
|
+
packet.objective,
|
|
350
|
+
packet.context.agents,
|
|
351
|
+
packet.context.contract,
|
|
352
|
+
`<packet-contract>${JSON.stringify({
|
|
353
|
+
requestId: packet.id,
|
|
354
|
+
phase: packet.phase,
|
|
355
|
+
readSet: packet.declared.read,
|
|
356
|
+
writeSet: packet.declared.write,
|
|
357
|
+
output: packet.output,
|
|
358
|
+
})}</packet-contract>`,
|
|
359
|
+
].join("\n\n"),
|
|
360
|
+
requestId: packet.id,
|
|
361
|
+
role: packet.role,
|
|
362
|
+
phase: packet.phase,
|
|
363
|
+
output: packet.output as "evidence" | "diff",
|
|
364
|
+
roots: packet.roots.map((root) => path.resolve(context.cwd, root)),
|
|
365
|
+
allowedPaths: [
|
|
366
|
+
...new Set([...packet.declared.read, ...packet.declared.write]),
|
|
367
|
+
],
|
|
368
|
+
timeoutMs: LIMITS.phaseTimeoutMs,
|
|
369
|
+
signal: attempt.signal,
|
|
370
|
+
failureOverride: phase.failureOverride,
|
|
371
|
+
onStreamStart: attempt.onHeaders,
|
|
372
|
+
onStreamProgress: attempt.onProgress,
|
|
373
|
+
});
|
|
374
|
+
if (!child.ok && child.failure.kind === "transport") {
|
|
375
|
+
throw new Error(child.failure.code);
|
|
376
|
+
}
|
|
377
|
+
return { kind: "child" as const, child };
|
|
378
|
+
},
|
|
379
|
+
});
|
|
380
|
+
if (!routed.ok) {
|
|
381
|
+
const cancelled = routed.state === "cancelled" || signal.aborted;
|
|
382
|
+
const timedOut = !cancelled && routed.code === "phase-timeout";
|
|
383
|
+
return syntheticChildFailure(
|
|
384
|
+
cancelled ? "packet cancelled" : routed.code,
|
|
385
|
+
cancelled
|
|
386
|
+
? { kind: "cancelled", code: "cancelled" }
|
|
387
|
+
: timedOut
|
|
388
|
+
? {
|
|
389
|
+
kind: "transport",
|
|
390
|
+
code: "timeout",
|
|
391
|
+
stage: "child-timeout",
|
|
392
|
+
}
|
|
393
|
+
: {
|
|
394
|
+
kind: "transport",
|
|
395
|
+
code: "transport-failure",
|
|
396
|
+
stage: "child-provider-stream",
|
|
397
|
+
},
|
|
398
|
+
cancelled ? "cancelled" : timedOut ? "timed-out" : "failed",
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
if (routed.value.kind === "phase-failure") {
|
|
402
|
+
const phase = routed.value.phase;
|
|
403
|
+
return syntheticChildFailure(phase.error, phase.failure);
|
|
404
|
+
}
|
|
405
|
+
return routed.value.child;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
#brokerFor(cwd: string): RunWorkerBroker {
|
|
409
|
+
const root = path.resolve(cwd);
|
|
410
|
+
const existing = this.#brokers.get(root);
|
|
411
|
+
let policy = this.#routePolicy;
|
|
412
|
+
if (policy && existing) return existing;
|
|
413
|
+
if (!policy) {
|
|
414
|
+
const resolution = loadRoutePolicy({
|
|
415
|
+
cwd: root,
|
|
416
|
+
...(this.#routePolicyHome ? { home: this.#routePolicyHome } : {}),
|
|
417
|
+
});
|
|
418
|
+
if (!resolution.ok && resolution.source.kind !== "none") {
|
|
419
|
+
const code = resolution.diagnostics[0]?.code ?? "policy-invalid";
|
|
420
|
+
throw new Error(`route-policy-unavailable:${code}`);
|
|
421
|
+
}
|
|
422
|
+
policy = resolution.ok ? resolution.policy : unavailableRoutePolicy();
|
|
423
|
+
}
|
|
424
|
+
const serializedPolicy = JSON.stringify(policy);
|
|
425
|
+
if (existing && this.#brokerPolicies.get(root) === serializedPolicy) {
|
|
426
|
+
return existing;
|
|
427
|
+
}
|
|
428
|
+
const broker = new RunWorkerBroker(policy);
|
|
429
|
+
this.#brokers.set(root, broker);
|
|
430
|
+
this.#brokerPolicies.set(root, serializedPolicy);
|
|
431
|
+
return broker;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
#notifyBrokerActivity(
|
|
435
|
+
packet: PacketEnvelope,
|
|
436
|
+
observer: PacketActivityObserver | undefined,
|
|
437
|
+
sequence: number,
|
|
438
|
+
activity: BrokerActivityUpdate,
|
|
439
|
+
): void {
|
|
440
|
+
this.#notify(packet, observer, sequence, activity.state, {
|
|
441
|
+
attempt: activity.attempt,
|
|
442
|
+
maxAttempts: activity.maxAttempts,
|
|
443
|
+
...(activity.code ? { code: activity.code } : {}),
|
|
444
|
+
...(activity.wait ? { wait: activity.wait } : {}),
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
#notify(
|
|
449
|
+
packet: PacketEnvelope,
|
|
450
|
+
observer: PacketActivityObserver | undefined,
|
|
451
|
+
sequence: number,
|
|
452
|
+
state: PacketActivityState,
|
|
453
|
+
metadata: Pick<
|
|
454
|
+
PacketActivityEvent,
|
|
455
|
+
"attempt" | "maxAttempts" | "code" | "wait"
|
|
456
|
+
> = {},
|
|
457
|
+
): void {
|
|
458
|
+
if (!observer) return;
|
|
459
|
+
const event: PacketActivityEvent = {
|
|
460
|
+
state,
|
|
461
|
+
requestId: packet.id,
|
|
462
|
+
role: packet.role,
|
|
463
|
+
phase: packet.phase,
|
|
464
|
+
objective: packet.objective,
|
|
465
|
+
sequence,
|
|
466
|
+
...metadata,
|
|
467
|
+
...(state === "cancelled"
|
|
468
|
+
? { failureReason: "subagent cancelled" as const }
|
|
469
|
+
: state === "timed-out"
|
|
470
|
+
? { failureReason: "phase timed out" as const }
|
|
471
|
+
: state === "failed"
|
|
472
|
+
? { failureReason: "subagent failed" as const }
|
|
473
|
+
: {}),
|
|
474
|
+
};
|
|
475
|
+
try {
|
|
476
|
+
const result = observer(event);
|
|
477
|
+
if (result instanceof Promise) void result.catch(() => undefined);
|
|
478
|
+
} catch {
|
|
479
|
+
// Presentation observers cannot alter execution.
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
#acquire(controller: AbortController): Promise<boolean> {
|
|
484
|
+
if (controller.signal.aborted) return Promise.resolve(false);
|
|
485
|
+
if (this.#active < this.#concurrency) {
|
|
486
|
+
this.#active += 1;
|
|
487
|
+
return Promise.resolve(true);
|
|
488
|
+
}
|
|
489
|
+
return new Promise<boolean>((resolve) => {
|
|
490
|
+
let waiting: WaitingPacket;
|
|
491
|
+
const onAbort = () => {
|
|
492
|
+
const index = this.#waiting.indexOf(waiting);
|
|
493
|
+
if (index >= 0) this.#waiting.splice(index, 1);
|
|
494
|
+
resolve(false);
|
|
495
|
+
};
|
|
496
|
+
waiting = { controller, resolve, onAbort };
|
|
497
|
+
this.#waiting.push(waiting);
|
|
498
|
+
controller.signal.addEventListener("abort", onAbort, { once: true });
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
#release(): void {
|
|
503
|
+
this.#active -= 1;
|
|
504
|
+
while (this.#waiting.length > 0) {
|
|
505
|
+
const next = this.#waiting.shift();
|
|
506
|
+
if (!next || next.controller.signal.aborted) continue;
|
|
507
|
+
next.controller.signal.removeEventListener("abort", next.onAbort);
|
|
508
|
+
this.#active += 1;
|
|
509
|
+
next.resolve(true);
|
|
510
|
+
break;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
async cancel(): Promise<void> {
|
|
515
|
+
const reason = new Error("packet runtime cancelled");
|
|
516
|
+
for (const controller of this.#controllers) {
|
|
517
|
+
if (!controller.signal.aborted) controller.abort(reason);
|
|
518
|
+
}
|
|
519
|
+
await Promise.allSettled([...this.#operations]);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
async drain(): Promise<void> {
|
|
523
|
+
this.activation.drain();
|
|
524
|
+
await this.cancel();
|
|
525
|
+
this.#brokers.clear();
|
|
526
|
+
this.#brokerPolicies.clear();
|
|
527
|
+
this.#parentPayloadBridge.clear();
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
get state(): ActivationState {
|
|
531
|
+
return this.activation.state;
|
|
532
|
+
}
|
|
533
|
+
}
|
package/src/parent-provider.ts
CHANGED
|
@@ -20,7 +20,12 @@ import {
|
|
|
20
20
|
type ParentPayloadCallback,
|
|
21
21
|
type ParentPayloadCapture,
|
|
22
22
|
} from "./parent-payload-bridge.ts";
|
|
23
|
-
import
|
|
23
|
+
import {
|
|
24
|
+
type CustomRoutePolicy,
|
|
25
|
+
type InheritedRoutePolicy,
|
|
26
|
+
type ResolvedCustomRoute,
|
|
27
|
+
resolveCustomRoute,
|
|
28
|
+
} from "./route-policy.ts";
|
|
24
29
|
|
|
25
30
|
const SUBAGENT_PROVIDER_ID = "abel-subagent";
|
|
26
31
|
|
|
@@ -71,7 +76,7 @@ const SUBAGENT_STREAMS = {
|
|
|
71
76
|
},
|
|
72
77
|
};
|
|
73
78
|
|
|
74
|
-
function customSubagentModel(endpoint:
|
|
79
|
+
function customSubagentModel(endpoint: ResolvedCustomRoute): Model<string> {
|
|
75
80
|
return {
|
|
76
81
|
id: endpoint.model,
|
|
77
82
|
name: endpoint.model,
|
|
@@ -90,7 +95,7 @@ function customSubagentModel(endpoint: SubagentEndpoint): Model<string> {
|
|
|
90
95
|
}
|
|
91
96
|
|
|
92
97
|
function customSubagentProvider(
|
|
93
|
-
endpoint:
|
|
98
|
+
endpoint: ResolvedCustomRoute,
|
|
94
99
|
model: Model<string>,
|
|
95
100
|
): Provider {
|
|
96
101
|
return createProvider({
|
|
@@ -118,10 +123,12 @@ function customSubagentProvider(
|
|
|
118
123
|
}
|
|
119
124
|
|
|
120
125
|
export async function customPhaseRuntime(
|
|
121
|
-
|
|
126
|
+
route: CustomRoutePolicy,
|
|
122
127
|
signal?: AbortSignal,
|
|
128
|
+
environment: Readonly<Record<string, string | undefined>> = process.env,
|
|
123
129
|
): Promise<PhaseRuntimeResult> {
|
|
124
130
|
if (signal?.aborted) return cancelledPhaseRuntime();
|
|
131
|
+
const endpoint = resolveCustomRoute(route, environment);
|
|
125
132
|
const model = customSubagentModel(endpoint);
|
|
126
133
|
const provider = customSubagentProvider(endpoint, model);
|
|
127
134
|
let modelRuntime: ModelRuntime;
|
|
@@ -419,6 +426,18 @@ export async function runtimeFromContext(
|
|
|
419
426
|
};
|
|
420
427
|
}
|
|
421
428
|
|
|
429
|
+
export async function runtimeForWorkerRoute(
|
|
430
|
+
route: CustomRoutePolicy | InheritedRoutePolicy,
|
|
431
|
+
ctx: Pick<ExtensionContext, "model" | "modelRegistry">,
|
|
432
|
+
payloadBridge: ParentPayloadBridge,
|
|
433
|
+
signal?: AbortSignal,
|
|
434
|
+
environment: Readonly<Record<string, string | undefined>> = process.env,
|
|
435
|
+
): Promise<PhaseRuntimeResult> {
|
|
436
|
+
return route.kind === "custom"
|
|
437
|
+
? customPhaseRuntime(route, signal, environment)
|
|
438
|
+
: runtimeFromContext(ctx, payloadBridge, signal);
|
|
439
|
+
}
|
|
440
|
+
|
|
422
441
|
function abortable<T>(promise: Promise<T>, signal?: AbortSignal): Promise<T> {
|
|
423
442
|
if (!signal) return promise;
|
|
424
443
|
signal.throwIfAborted();
|