@abelxiaoxing/cadence 1.8.2 → 1.8.4

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.
@@ -1,31 +1,21 @@
1
- import path from "node:path";
2
1
  import type { Usage } from "@earendil-works/pi-ai";
3
2
  import { Activation, type ActivationState } from "./activation.ts";
4
3
  import { loadAgentDefinitions } from "./agent-registry.ts";
5
- import {
6
- type ChildFailureKind,
7
- type ChildSessionResult,
8
- runChildSession,
9
- UsageAggregator,
10
- } from "./child-session.ts";
11
4
  import {
12
5
  type ChildFailure,
13
6
  LIMITS,
14
7
  type PacketEnvelope,
8
+ validateDiffResult,
9
+ validateEvidenceResult,
15
10
  validatePacketEnvelope,
16
11
  } from "./contracts.ts";
17
12
  import type { PackageContext } from "./model-source.ts";
18
- import { runtimeForWorkerRoute } from "./parent-provider.ts";
19
- import {
20
- loadRoutePolicy,
21
- type RoutePolicy,
22
- type WorkerRole,
23
- } from "./route-policy.ts";
13
+ import type { SubagentRole } from "./subagent-process.ts";
24
14
  import {
25
- isTransportTimeoutCode,
26
- transportFailureError,
27
- } from "./transport-budget.ts";
28
- import { type BrokerActivityUpdate, RunWorkerBroker } from "./worker-broker.ts";
15
+ buildSubagentPrompt,
16
+ runSubagentProcess,
17
+ type SubagentProcessResult,
18
+ } from "./subagent-process.ts";
29
19
 
30
20
  export const PACKET_ACTIONS = ["run", "cancel", "finish"] as const;
31
21
  export type PacketAction = (typeof PACKET_ACTIONS)[number];
@@ -60,30 +50,21 @@ export type PacketDispatchResult =
60
50
 
61
51
  export interface PacketRuntimeOptions {
62
52
  activation?: Activation;
63
- routePolicy?: RoutePolicy;
64
- routePolicyHome?: string;
65
- environment?: Readonly<Record<string, string | undefined>>;
66
53
  concurrency?: number;
67
54
  childRunner?: PacketChildRunner;
68
55
  }
69
56
 
70
57
  export type PacketContext = PackageContext;
71
58
 
59
+ export type PacketChildResult =
60
+ | { ok: true; result: unknown; usage?: Usage }
61
+ | { ok: false; error: string; failure: ChildFailure; usage?: Usage };
62
+
72
63
  export type PacketChildRunner = (input: {
73
64
  packet: PacketEnvelope;
74
65
  context: PacketContext;
75
66
  signal: AbortSignal;
76
- }) => Promise<ChildSessionResult>;
77
-
78
- type PacketRouteResult =
79
- | {
80
- kind: "phase-failure";
81
- phase: Extract<
82
- Awaited<ReturnType<typeof runtimeForWorkerRoute>>,
83
- { ok: false }
84
- >;
85
- }
86
- | { kind: "child"; child: ChildSessionResult };
67
+ }) => Promise<PacketChildResult>;
87
68
 
88
69
  interface WaitingPacket {
89
70
  controller: AbortController;
@@ -105,48 +86,52 @@ function cancelledResult(signal: AbortSignal): PacketDispatchResult {
105
86
  };
106
87
  }
107
88
 
108
- function failureState(kind: ChildFailureKind): PacketActivityState {
109
- return kind === "cancelled"
89
+ function failureState(failure: ChildFailure): PacketActivityState {
90
+ return failure.kind === "cancelled"
110
91
  ? "cancelled"
111
- : kind === "timed-out"
92
+ : failure.kind === "transport" && failure.code === "child-timeout"
112
93
  ? "timed-out"
113
94
  : "failed";
114
95
  }
115
96
 
116
- function syntheticChildFailure(
117
- error: string,
118
- failure: ChildFailure,
119
- failureKind: ChildFailureKind = failure.kind === "cancelled"
120
- ? "cancelled"
121
- : "failed",
122
- ): ChildSessionResult {
123
- return {
124
- ok: false,
125
- error,
126
- failure,
127
- failureKind,
128
- transportFailure: failure.kind === "transport",
129
- disposeCount: 0,
130
- usage: new UsageAggregator().total(),
131
- classification: {
132
- finalCategory: "no-final-assistant",
133
- attempts: 0,
134
- schema: "invalid",
135
- identity: { request: false, role: false, task: false, phase: false },
136
- },
137
- };
97
+ type ParsedChildResult =
98
+ | { ok: true; result: unknown }
99
+ | { ok: false; reason: string };
100
+
101
+ function parsePacketResult(
102
+ packet: PacketEnvelope,
103
+ text: string,
104
+ ): ParsedChildResult {
105
+ const fenced = [...text.matchAll(/```(?:json)?\s*([\s\S]*?)```/giu)];
106
+ let value: unknown;
107
+ for (let index = fenced.length - 1; index >= 0; index -= 1) {
108
+ try {
109
+ value = JSON.parse(fenced[index]?.[1]?.trim() ?? "");
110
+ break;
111
+ } catch {
112
+ // Keep looking for an earlier valid block; model text is not authority.
113
+ }
114
+ }
115
+ if (!value || typeof value !== "object" || Array.isArray(value))
116
+ return { ok: false, reason: "child-result-json-invalid" };
117
+ const candidate = value as Record<string, unknown>;
118
+ if (packet.output === "evidence") {
119
+ const checked = validateEvidenceResult(candidate);
120
+ return checked.ok
121
+ ? { ok: true, result: candidate }
122
+ : { ok: false, reason: checked.reason ?? "child-evidence-invalid" };
123
+ }
124
+ const checked = validateDiffResult(candidate);
125
+ return checked.ok
126
+ ? { ok: true, result: candidate }
127
+ : { ok: false, reason: checked.reason ?? "child-diff-invalid" };
138
128
  }
139
129
 
140
130
  export class PacketRuntime {
141
131
  readonly activation: Activation;
142
132
  readonly limits = LIMITS;
143
- readonly #routePolicy?: RoutePolicy;
144
- readonly #routePolicyHome?: string;
145
- readonly #environment: Readonly<Record<string, string | undefined>>;
146
133
  readonly #concurrency: number;
147
134
  readonly #childRunner?: PacketChildRunner;
148
- readonly #brokers = new Map<string, RunWorkerBroker>();
149
- readonly #brokerPolicies = new Map<string, string>();
150
135
  readonly #controllers = new Set<AbortController>();
151
136
  readonly #operations = new Set<Promise<PacketDispatchResult>>();
152
137
  readonly #waiting: WaitingPacket[] = [];
@@ -155,9 +140,6 @@ export class PacketRuntime {
155
140
 
156
141
  constructor(options: PacketRuntimeOptions) {
157
142
  this.activation = options.activation ?? new Activation();
158
- this.#routePolicy = options.routePolicy;
159
- this.#routePolicyHome = options.routePolicyHome;
160
- this.#environment = options.environment ?? process.env;
161
143
  this.#childRunner = options.childRunner;
162
144
  this.#concurrency = Math.max(
163
145
  1,
@@ -236,45 +218,24 @@ export class PacketRuntime {
236
218
  if (this.#childRunner) {
237
219
  this.#notify(packet, observer, sequence, "running");
238
220
  }
239
- const usage = new UsageAggregator();
240
- let child = await this.#dispatchChild(
221
+ const child = await this.#dispatchChild(
241
222
  packet,
242
223
  context,
243
224
  controller.signal,
244
225
  (activity) =>
245
- this.#notifyBrokerActivity(packet, observer, sequence, activity),
226
+ this.#notify(packet, observer, sequence, activity.state, {
227
+ attempt: activity.attempt,
228
+ maxAttempts: activity.maxAttempts,
229
+ }),
246
230
  );
247
- usage.add("launch:0", child.usage);
248
- if (
249
- this.#childRunner &&
250
- !child.ok &&
251
- child.failure.kind === "artifact" &&
252
- !controller.signal.aborted
253
- ) {
254
- this.#notifyBrokerActivity(packet, observer, sequence, {
255
- state: "retrying",
256
- attempt: 1,
257
- maxAttempts: 2,
258
- code: child.failure.code,
259
- wait: "bounded-policy",
260
- });
261
- child = await this.#dispatchChild(
262
- packet,
263
- context,
264
- controller.signal,
265
- (activity) =>
266
- this.#notifyBrokerActivity(packet, observer, sequence, activity),
267
- );
268
- usage.add("launch:1", child.usage);
269
- }
270
231
  if (!child.ok) {
271
- const state = failureState(child.failureKind);
232
+ const state = failureState(child.failure);
272
233
  this.#notify(packet, observer, sequence, state);
273
234
  return {
274
235
  ok: false,
275
236
  error: child.error,
276
237
  failure: child.failure,
277
- usage: usage.total(),
238
+ ...(child.usage ? { usage: child.usage } : {}),
278
239
  };
279
240
  }
280
241
  this.#notify(packet, observer, sequence, "completed");
@@ -282,7 +243,7 @@ export class PacketRuntime {
282
243
  ok: true,
283
244
  action: "run",
284
245
  result: child.result,
285
- usage: usage.total(),
246
+ ...(child.usage ? { usage: child.usage } : {}),
286
247
  };
287
248
  } catch (error) {
288
249
  this.#notify(
@@ -303,184 +264,114 @@ export class PacketRuntime {
303
264
  packet: PacketEnvelope,
304
265
  context: PacketContext,
305
266
  signal: AbortSignal,
306
- onActivity?: (event: BrokerActivityUpdate) => void,
307
- ): Promise<ChildSessionResult> {
308
- if (signal.aborted) {
309
- const result = cancelledResult(signal);
310
- return syntheticChildFailure(
311
- result.ok ? "packet cancelled" : result.error,
312
- { kind: "cancelled", code: "cancelled" },
313
- "cancelled",
314
- );
315
- }
316
- if (this.#childRunner) {
267
+ onActivity?: (event: {
268
+ state: PacketActivityState;
269
+ attempt: number;
270
+ maxAttempts: number;
271
+ }) => void,
272
+ ): Promise<PacketChildResult> {
273
+ if (signal.aborted)
274
+ return {
275
+ ok: false,
276
+ error: "packet cancelled",
277
+ failure: { kind: "cancelled", code: "cancelled" },
278
+ };
279
+ if (this.#childRunner)
317
280
  return this.#childRunner({
318
281
  packet: structuredClone(packet),
319
282
  context,
320
283
  signal,
321
284
  });
322
- }
323
285
  const agent = loadAgentDefinitions().find(
324
286
  (candidate) => candidate.role === packet.role,
325
287
  );
326
- if (!agent) {
327
- throw new Error("packet agent definition is unavailable");
328
- }
329
- const broker = this.#brokerFor(context);
330
- const brokerUsage = new UsageAggregator();
331
- let childAttempt = 0;
332
- const withBrokerUsage = (
333
- child: ChildSessionResult,
334
- ): ChildSessionResult => ({
335
- ...child,
336
- usage: brokerUsage.total(),
337
- });
338
- const routed = await broker.run<PacketRouteResult>({
339
- runId: `${packet.stage}:${packet.id}`,
340
- operationId: packet.id,
341
- role: packet.role as WorkerRole,
288
+ if (!agent) throw new Error("packet-agent-unavailable");
289
+ const role = packet.role as SubagentRole;
290
+ const result = await runSubagentProcess({
291
+ role,
292
+ cwd: context.cwd,
293
+ model: context.model
294
+ ? { provider: context.model.provider, id: context.model.id }
295
+ : undefined,
296
+ prompt: buildSubagentPrompt({
297
+ role,
298
+ agentContent: agent.content,
299
+ objective: packet.objective,
300
+ context: [
301
+ packet.context.agents,
302
+ packet.context.contract,
303
+ JSON.stringify({
304
+ id: packet.id,
305
+ phase: packet.phase,
306
+ read: packet.declared.read,
307
+ write: packet.declared.write,
308
+ output: packet.output,
309
+ }),
310
+ ].join("\n\n"),
311
+ }),
342
312
  signal,
343
- ...(onActivity ? { onActivity } : {}),
344
- classifyResult: (value) =>
345
- value.kind === "child" &&
346
- !value.child.ok &&
347
- value.child.failure.kind === "artifact"
348
- ? value.child.failure.code
349
- : undefined,
350
- execute: async (attempt) => {
351
- const phase = await runtimeForWorkerRoute(
352
- attempt.route,
353
- context,
354
- attempt.signal,
355
- this.#environment,
356
- );
357
- if (!phase.ok) {
358
- if (phase.failure.kind === "cancelled") {
359
- return { kind: "phase-failure" as const, phase };
360
- }
361
- throw new Error(phase.failure.code);
362
- }
363
- const child = await runChildSession({
364
- cwd: context.cwd,
365
- modelRuntime: phase.modelRuntime,
366
- model: phase.model,
367
- systemPrompt: [
368
- agent.content,
369
- packet.objective,
370
- packet.context.agents,
371
- packet.context.contract,
372
- `<packet-contract>${JSON.stringify({
373
- requestId: packet.id,
374
- phase: packet.phase,
375
- readSet: packet.declared.read,
376
- writeSet: packet.declared.write,
377
- output: packet.output,
378
- })}</packet-contract>`,
379
- ].join("\n\n"),
380
- requestId: packet.id,
381
- role: packet.role,
382
- phase: packet.phase,
383
- output: packet.output as "evidence" | "diff",
384
- ...(packet.role === "diagnosis-worker" && packet.output === "diff"
385
- ? {
386
- structuredPatch: {
387
- workspaceRoot: context.cwd,
388
- writePaths: packet.declared.write,
389
- deletePaths: packet.declared.write,
390
- },
391
- }
392
- : {}),
393
- roots: packet.roots.map((root) => path.resolve(context.cwd, root)),
394
- allowedPaths: [
395
- ...new Set([...packet.declared.read, ...packet.declared.write]),
396
- ],
397
- timeoutMs: LIMITS.phaseTimeoutMs,
398
- signal: attempt.signal,
399
- onStreamStart: attempt.onRequestStart,
400
- onStreamHeaders: attempt.onHeaders,
401
- onStreamProgress: attempt.onProgress,
402
- });
403
- brokerUsage.add(`attempt:${childAttempt++}`, child.usage);
404
- if (!child.ok && child.failure.kind === "transport") {
405
- throw transportFailureError(child.failure.code);
406
- }
407
- return { kind: "child" as const, child };
313
+ onEvent: (event) => {
314
+ if (event.type === "agent_start")
315
+ onActivity?.({
316
+ state: "waiting-first-response",
317
+ attempt: 1,
318
+ maxAttempts: 1,
319
+ });
320
+ if (event.type === "tool_execution_start")
321
+ onActivity?.({ state: "running", attempt: 1, maxAttempts: 1 });
408
322
  },
409
323
  });
410
- if (!routed.ok) {
411
- const cancelled = routed.state === "cancelled" || signal.aborted;
412
- const timedOut =
413
- !cancelled &&
414
- (isTransportTimeoutCode(routed.code) ||
415
- routed.code === "phase-timeout");
416
- return withBrokerUsage(
417
- syntheticChildFailure(
418
- cancelled ? "packet cancelled" : routed.code,
419
- cancelled
420
- ? { kind: "cancelled", code: "cancelled" }
421
- : timedOut
324
+ return this.#processResult(packet, result);
325
+ }
326
+
327
+ #processResult(
328
+ packet: PacketEnvelope,
329
+ result: SubagentProcessResult,
330
+ ): PacketChildResult {
331
+ if (result.status !== "completed") {
332
+ const failure: ChildFailure =
333
+ result.status === "cancelled"
334
+ ? { kind: "cancelled", code: "cancelled" }
335
+ : result.status === "timed-out"
336
+ ? {
337
+ kind: "transport",
338
+ code: "child-timeout",
339
+ stage: "child-timeout",
340
+ }
341
+ : result.status === "output-limit"
422
342
  ? {
423
- kind: "transport",
424
- code: isTransportTimeoutCode(routed.code)
425
- ? routed.code
426
- : "timeout",
427
- stage: "child-timeout",
343
+ kind: "result-limit",
344
+ limitBytes: LIMITS.maxCompleteResultBytes,
428
345
  }
429
346
  : {
430
347
  kind: "transport",
431
348
  code: "transport-failure",
432
349
  stage: "child-provider-stream",
433
- },
434
- cancelled ? "cancelled" : timedOut ? "timed-out" : "failed",
435
- ),
436
- );
437
- }
438
- if (routed.value.kind === "phase-failure") {
439
- const phase = routed.value.phase;
440
- return withBrokerUsage(syntheticChildFailure(phase.error, phase.failure));
441
- }
442
- return withBrokerUsage(routed.value.child);
443
- }
444
-
445
- #brokerFor(context: PacketContext): RunWorkerBroker {
446
- const root = path.resolve(context.cwd);
447
- const existing = this.#brokers.get(root);
448
- let policy = this.#routePolicy;
449
- if (policy && existing) return existing;
450
- if (!policy) {
451
- const resolution = loadRoutePolicy({
452
- cwd: root,
453
- ...(this.#routePolicyHome ? { home: this.#routePolicyHome } : {}),
454
- ...(context.model ? { parentModel: context.model } : {}),
455
- });
456
- if (!resolution.ok) {
457
- const code = resolution.diagnostics[0]?.code ?? "policy-invalid";
458
- throw new Error(`route-policy-unavailable:${code}`);
459
- }
460
- policy = resolution.policy;
461
- }
462
- const serializedPolicy = JSON.stringify(policy);
463
- if (existing && this.#brokerPolicies.get(root) === serializedPolicy) {
464
- return existing;
350
+ };
351
+ return {
352
+ ok: false,
353
+ error: result.error ?? result.status,
354
+ failure,
355
+ ...(result.usage ? { usage: result.usage } : {}),
356
+ };
465
357
  }
466
- const broker = new RunWorkerBroker(policy);
467
- this.#brokers.set(root, broker);
468
- this.#brokerPolicies.set(root, serializedPolicy);
469
- return broker;
470
- }
471
-
472
- #notifyBrokerActivity(
473
- packet: PacketEnvelope,
474
- observer: PacketActivityObserver | undefined,
475
- sequence: number,
476
- activity: BrokerActivityUpdate,
477
- ): void {
478
- this.#notify(packet, observer, sequence, activity.state, {
479
- attempt: activity.attempt,
480
- maxAttempts: activity.maxAttempts,
481
- ...(activity.code ? { code: activity.code } : {}),
482
- ...(activity.wait ? { wait: activity.wait } : {}),
483
- });
358
+ const parsed = parsePacketResult(packet, result.finalText);
359
+ if (!parsed.ok)
360
+ return {
361
+ ok: false,
362
+ error: parsed.reason,
363
+ failure: {
364
+ kind: "artifact",
365
+ code: "invalid-structural-result",
366
+ stage: "child-provider-stream",
367
+ },
368
+ ...(result.usage ? { usage: result.usage } : {}),
369
+ };
370
+ return {
371
+ ok: true,
372
+ result: parsed.result,
373
+ ...(result.usage ? { usage: result.usage } : {}),
374
+ };
484
375
  }
485
376
 
486
377
  #notify(
@@ -560,8 +451,6 @@ export class PacketRuntime {
560
451
  async drain(): Promise<void> {
561
452
  this.activation.drain();
562
453
  await this.cancel();
563
- this.#brokers.clear();
564
- this.#brokerPolicies.clear();
565
454
  }
566
455
 
567
456
  get state(): ActivationState {