@abelxiaoxing/cadence 1.3.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.
Files changed (76) hide show
  1. package/README.md +172 -13
  2. package/agents/design-explorer.md +17 -11
  3. package/agents/diagnosis-worker.md +9 -1
  4. package/agents/implementation-worker.md +3 -1
  5. package/config/plan-draft.example.json +122 -0
  6. package/config/plan-draft.multiple-tasks.example.json +345 -0
  7. package/package.json +9 -5
  8. package/prompts/abel-design.md +141 -38
  9. package/prompts/abel-diagnose.md +10 -1
  10. package/prompts/abel-implement.md +118 -20
  11. package/prompts/abel-init.md +9 -2
  12. package/src/activity-contracts.ts +82 -0
  13. package/src/apply-transaction.ts +16 -14
  14. package/src/artifact-store.ts +17 -0
  15. package/src/candidate-artifact.ts +143 -0
  16. package/src/candidate-patch.ts +33 -12
  17. package/src/candidate-workspace.ts +74 -0
  18. package/src/canonical.ts +16 -0
  19. package/src/change-contract-schema.ts +191 -0
  20. package/src/change-contract.ts +273 -0
  21. package/src/change-verification.ts +896 -0
  22. package/src/child-model.ts +129 -0
  23. package/src/child-session.ts +296 -328
  24. package/src/contracts.ts +325 -109
  25. package/src/control-contracts.ts +51 -1
  26. package/src/delivery-compiler.ts +241 -141
  27. package/src/delivery-revision.ts +42 -0
  28. package/src/design-control.ts +182 -15
  29. package/src/design-diagnostics.ts +150 -0
  30. package/src/design-journal.ts +216 -72
  31. package/src/durable-contracts.ts +210 -0
  32. package/src/durable-workflow.ts +963 -0
  33. package/src/evidence-draft.ts +122 -0
  34. package/src/implement-graph.ts +14 -16
  35. package/src/implement-plan.ts +70 -0
  36. package/src/index.ts +341 -1405
  37. package/src/isolation-backend.ts +84 -23
  38. package/src/model-source.ts +22 -0
  39. package/src/package-candidate.ts +224 -0
  40. package/src/package-delivery.ts +486 -0
  41. package/src/package-verification.ts +509 -0
  42. package/src/package-workflow.ts +253 -0
  43. package/src/packet-runtime.ts +27 -48
  44. package/src/parent-provider.ts +86 -198
  45. package/src/phase-execution.ts +2248 -0
  46. package/src/pi-adapter.ts +19 -0
  47. package/src/plan-draft-summary.ts +146 -0
  48. package/src/plan-draft.ts +493 -0
  49. package/src/route-policy.ts +1 -11
  50. package/src/run-state.ts +4 -16
  51. package/src/run-store.ts +5 -144
  52. package/src/scoped-grep-worker.mjs +23 -0
  53. package/src/scoped-tools.ts +312 -116
  54. package/src/sqlite-schema.ts +171 -0
  55. package/src/storage-schema.ts +352 -0
  56. package/src/subagent-activity.ts +46 -67
  57. package/src/submit-tool.ts +40 -61
  58. package/src/task-ledger.ts +14 -55
  59. package/src/transport-budget.ts +32 -0
  60. package/src/verification-capability.ts +298 -102
  61. package/src/verification-environment.ts +213 -0
  62. package/src/verification-identity.ts +77 -0
  63. package/src/worker-broker.ts +134 -91
  64. package/src/workflow-engine.ts +28 -8285
  65. package/src/workflow-policy.ts +1388 -0
  66. package/src/workflow-recovery-policy.ts +95 -0
  67. package/src/workflow-scheduling.ts +105 -0
  68. package/src/workflow-state-machine.ts +4086 -0
  69. package/src/workflow-status.ts +384 -0
  70. package/src/workspace-io-entry.mjs +58 -0
  71. package/src/workspace-io-worker.mjs +991 -0
  72. package/src/workspace-io.ts +84 -0
  73. package/src/workspace-store.ts +125 -19
  74. package/skills/abel-workflow/SKILL.md +0 -205
  75. package/src/empty-resource-loader.ts +0 -38
  76. package/src/parent-payload-bridge.ts +0 -523
@@ -1,25 +1,15 @@
1
1
  import {
2
+ createModels,
3
+ createProvider,
2
4
  InMemoryCredentialStore,
5
+ lazyApi,
3
6
  type Model,
7
+ type Models,
4
8
  type Provider,
5
9
  type StreamOptions,
6
10
  } from "@earendil-works/pi-ai";
7
- import {
8
- createProvider,
9
- stream as subagentStream,
10
- streamSimple as subagentStreamSimple,
11
- } from "@earendil-works/pi-ai/compat";
12
- import {
13
- type ExtensionContext,
14
- ModelRuntime,
15
- } from "@earendil-works/pi-coding-agent";
16
11
  import type { ChildFailure } from "./contracts.ts";
17
- import {
18
- type ParentPayloadBridge,
19
- ParentPayloadBridgeError,
20
- type ParentPayloadCallback,
21
- type ParentPayloadCapture,
22
- } from "./parent-payload-bridge.ts";
12
+ import type { ParentModelSource } from "./model-source.ts";
23
13
  import {
24
14
  type CustomRoutePolicy,
25
15
  type InheritedRoutePolicy,
@@ -33,26 +23,6 @@ export interface PhaseTransportObserver {
33
23
  onResponse?(): void;
34
24
  }
35
25
 
36
- function customStreamOptions<T extends StreamOptions>(
37
- model: Model<string>,
38
- options?: T,
39
- ): T | undefined {
40
- if (model.api !== "openai-responses") return options;
41
- const childOnPayload = options?.onPayload;
42
- return {
43
- ...options,
44
- onPayload: async (payload, requestModel) => {
45
- const transformed = await childOnPayload?.(payload, requestModel);
46
- const current = transformed === undefined ? payload : transformed;
47
- if (!current || typeof current !== "object" || Array.isArray(current))
48
- return current;
49
- const normalized = { ...current } as Record<string, unknown>;
50
- delete normalized.max_output_tokens;
51
- return normalized;
52
- },
53
- } as T;
54
- }
55
-
56
26
  function observedStreamOptions<T extends StreamOptions>(
57
27
  options?: T,
58
28
  observer?: PhaseTransportObserver,
@@ -68,41 +38,48 @@ function observedStreamOptions<T extends StreamOptions>(
68
38
  } as T;
69
39
  }
70
40
 
71
- // The compat dispatchers resolve the concrete API implementation from
72
- // model.api at call time, so one pair serves all supported dialects.
73
- function subagentStreams(observer?: PhaseTransportObserver) {
41
+ // Public API modules, selected explicitly by the closed route dialect. No
42
+ // global compat registry or provider-specific payload rewriting is involved.
43
+ const routeApis = {
44
+ "openai-completions": lazyApi(
45
+ () => import("@earendil-works/pi-ai/api/openai-completions"),
46
+ ),
47
+ "openai-responses": lazyApi(
48
+ () => import("@earendil-works/pi-ai/api/openai-responses"),
49
+ ),
50
+ "anthropic-messages": lazyApi(
51
+ () => import("@earendil-works/pi-ai/api/anthropic-messages"),
52
+ ),
53
+ };
54
+
55
+ function subagentStreams(
56
+ dialect: ResolvedCustomRoute["dialect"],
57
+ observer?: PhaseTransportObserver,
58
+ ) {
59
+ const api = routeApis[dialect];
74
60
  return {
75
- stream(
76
- model: Parameters<typeof subagentStream>[0],
77
- context: Parameters<typeof subagentStream>[1],
78
- options?: Parameters<typeof subagentStream>[2],
79
- ) {
80
- return subagentStream(
61
+ stream: ((model, context, options) =>
62
+ api.stream(
81
63
  model,
82
64
  context,
83
- observedStreamOptions(
84
- customStreamOptions(model as Model<string>, options),
85
- observer,
86
- ),
87
- );
88
- },
89
- streamSimple(
90
- model: Parameters<typeof subagentStreamSimple>[0],
91
- context: Parameters<typeof subagentStreamSimple>[1],
92
- options?: Parameters<typeof subagentStreamSimple>[2],
93
- ) {
94
- return subagentStreamSimple(
65
+ observedStreamOptions({ ...options, maxRetries: 0 }, observer),
66
+ )) as Provider["stream"],
67
+ streamSimple: ((model, context, options) =>
68
+ api.streamSimple(
95
69
  model,
96
70
  context,
97
- observedStreamOptions(
98
- customStreamOptions(model as Model<string>, options),
99
- observer,
100
- ),
101
- );
102
- },
71
+ observedStreamOptions({ ...options, maxRetries: 0 }, observer),
72
+ )) as Provider["streamSimple"],
103
73
  };
104
74
  }
105
75
 
76
+ function modelsForProvider(provider: Provider, signal?: AbortSignal): Models {
77
+ signal?.throwIfAborted();
78
+ const models = createModels({ credentials: new InMemoryCredentialStore() });
79
+ models.setProvider(provider);
80
+ return models;
81
+ }
82
+
106
83
  function customSubagentModel(endpoint: ResolvedCustomRoute): Model<string> {
107
84
  return {
108
85
  id: endpoint.model,
@@ -146,7 +123,7 @@ function customSubagentProvider(
146
123
  },
147
124
  },
148
125
  models: [model],
149
- api: subagentStreams(observer),
126
+ api: subagentStreams(endpoint.dialect, observer),
150
127
  }) as Provider;
151
128
  }
152
129
 
@@ -160,9 +137,9 @@ export async function customPhaseRuntime(
160
137
  const endpoint = resolveCustomRoute(route, environment);
161
138
  const model = customSubagentModel(endpoint);
162
139
  const provider = customSubagentProvider(endpoint, model, observer);
163
- let modelRuntime: ModelRuntime;
140
+ let modelRuntime: Models;
164
141
  try {
165
- modelRuntime = await runtimeForProvider(provider, signal);
142
+ modelRuntime = modelsForProvider(provider, signal);
166
143
  } catch (error) {
167
144
  if (isCancellation(error, signal)) return cancelledPhaseRuntime();
168
145
  throw error;
@@ -171,11 +148,6 @@ export async function customPhaseRuntime(
171
148
  return { ok: true, modelRuntime, model };
172
149
  }
173
150
 
174
- export interface PhasePayloadBridge {
175
- readonly bridge: ParentPayloadBridge;
176
- readonly capture: ParentPayloadCapture;
177
- }
178
-
179
151
  type PhaseRuntimeFailure = Extract<
180
152
  ChildFailure,
181
153
  { kind: "cancelled" | "environment" | "transport" }
@@ -184,9 +156,8 @@ type PhaseRuntimeFailure = Extract<
184
156
  export type PhaseRuntimeResult =
185
157
  | {
186
158
  ok: true;
187
- modelRuntime: ModelRuntime;
159
+ modelRuntime: Models;
188
160
  model: Model<string>;
189
- failureOverride?: () => PhaseRuntimeFailure | undefined;
190
161
  }
191
162
  | { ok: false; error: string; failure: PhaseRuntimeFailure };
192
163
 
@@ -208,53 +179,6 @@ function isCancellation(error: unknown, signal?: AbortSignal): boolean {
208
179
  return signal?.aborted === true && error === signal.reason;
209
180
  }
210
181
 
211
- function phasePayloadCallback(
212
- payloadBridge: PhasePayloadBridge,
213
- childOnPayload: ParentPayloadCallback | undefined,
214
- signal: AbortSignal | undefined,
215
- diagnostic?: { failure?: PhaseRuntimeFailure },
216
- ): ParentPayloadCallback {
217
- return async (payload, model) => {
218
- try {
219
- return await payloadBridge.bridge.composePayload(
220
- payloadBridge.capture,
221
- payload,
222
- model,
223
- childOnPayload,
224
- signal,
225
- );
226
- } catch (error) {
227
- if (error instanceof ParentPayloadBridgeError && diagnostic) {
228
- diagnostic.failure = {
229
- kind: "environment",
230
- code: error.code,
231
- stage: "child-provider-stream",
232
- };
233
- }
234
- throw error;
235
- }
236
- };
237
- }
238
-
239
- export async function runtimeForProvider(
240
- provider: Provider,
241
- signal?: AbortSignal,
242
- ): Promise<ModelRuntime> {
243
- signal?.throwIfAborted();
244
- const runtime = await abortable(
245
- ModelRuntime.create({
246
- credentials: new InMemoryCredentialStore(),
247
- modelsPath: null,
248
- refreshOnCreate: false,
249
- signal,
250
- }),
251
- signal,
252
- );
253
- signal?.throwIfAborted();
254
- runtime.registerNativeProvider(provider);
255
- return runtime;
256
- }
257
-
258
182
  export function phaseProvider(
259
183
  parent: Provider,
260
184
  auth: {
@@ -263,12 +187,13 @@ export function phaseProvider(
263
187
  baseUrl?: string;
264
188
  env?: Record<string, string>;
265
189
  },
266
- payloadBridge: PhasePayloadBridge,
267
- diagnostic?: { failure?: PhaseRuntimeFailure },
268
190
  observer?: PhaseTransportObserver,
269
191
  ): Provider {
192
+ auth = structuredClone(auth);
270
193
  return {
271
- ...parent,
194
+ id: parent.id,
195
+ name: parent.name,
196
+ getModels: () => parent.getModels(),
272
197
  baseUrl: auth.baseUrl ?? parent.baseUrl,
273
198
  headers: { ...parent.headers, ...auth.headers },
274
199
  auth: {
@@ -285,35 +210,21 @@ export function phaseProvider(
285
210
  },
286
211
  },
287
212
  stream(model, context, options) {
288
- const onPayload = phasePayloadCallback(
289
- payloadBridge,
290
- options?.onPayload as ParentPayloadCallback | undefined,
291
- options?.signal,
292
- diagnostic,
293
- );
294
213
  return parent.stream(model, context, {
295
214
  ...observedStreamOptions(options, observer),
296
215
  apiKey: auth.apiKey,
297
216
  headers: { ...options?.headers, ...auth.headers },
298
217
  env: { ...options?.env, ...auth.env },
299
218
  maxRetries: 0,
300
- onPayload,
301
219
  } as never);
302
220
  },
303
221
  streamSimple(model, context, options) {
304
- const onPayload = phasePayloadCallback(
305
- payloadBridge,
306
- options?.onPayload as ParentPayloadCallback | undefined,
307
- options?.signal,
308
- diagnostic,
309
- );
310
222
  return parent.streamSimple(model, context, {
311
223
  ...observedStreamOptions(options, observer),
312
224
  apiKey: auth.apiKey,
313
225
  headers: { ...options?.headers, ...auth.headers },
314
226
  env: { ...options?.env, ...auth.env },
315
227
  maxRetries: 0,
316
- onPayload,
317
228
  });
318
229
  },
319
230
  };
@@ -341,8 +252,7 @@ function sameSelectedModel(
341
252
  }
342
253
 
343
254
  export async function runtimeFromContext(
344
- ctx: Pick<ExtensionContext, "model" | "modelRegistry">,
345
- payloadBridge: ParentPayloadBridge,
255
+ ctx: ParentModelSource,
346
256
  signal?: AbortSignal,
347
257
  observer?: PhaseTransportObserver,
348
258
  ): Promise<PhaseRuntimeResult> {
@@ -353,8 +263,11 @@ export async function runtimeFromContext(
353
263
  "parent model is unavailable",
354
264
  );
355
265
  }
356
- const selectedModel = ctx.model;
266
+ const selectedModel = structuredClone(ctx.model);
357
267
  const selectedModelKey = modelKeyFor(selectedModel);
268
+ const admittedProvider = ctx.modelRegistry.getProvider(
269
+ selectedModel.provider,
270
+ );
358
271
  let resolved: Awaited<
359
272
  ReturnType<typeof ctx.modelRegistry.getApiKeyAndHeaders>
360
273
  >;
@@ -378,97 +291,72 @@ export async function runtimeFromContext(
378
291
  ...selectedModel,
379
292
  baseUrl: resolved.baseUrl ?? selectedModel.baseUrl,
380
293
  } as Model<string>;
381
- const effectiveModelKey = modelKeyFor(model);
382
- const capture = payloadBridge.capture(effectiveModelKey, ctx.modelRegistry);
383
- if (!capture) {
384
- const code =
385
- payloadBridge.diagnoseCapture(effectiveModelKey, ctx.modelRegistry) ??
386
- "parent-bridge-capture-not-ready";
294
+ const delegate = ctx.modelRegistry.getProvider(model.provider);
295
+ if (delegate !== admittedProvider)
387
296
  return phaseRuntimeFailure(
388
- { kind: "environment", code, stage: "phase-runtime" },
389
- code,
297
+ {
298
+ kind: "environment",
299
+ code: "sandbox-runtime-unavailable",
300
+ stage: "phase-runtime",
301
+ },
302
+ "parent Provider changed during authentication",
390
303
  );
391
- }
392
- const captureFailure = () => {
393
- const currentModel = ctx.model;
394
- if (
395
- currentModel === undefined ||
396
- !sameSelectedModel(currentModel, selectedModelKey)
397
- ) {
398
- return "parent-bridge-model-key-mismatch" as const;
399
- }
400
- if (
401
- payloadBridge.capture(effectiveModelKey, ctx.modelRegistry) === capture
402
- ) {
403
- return undefined;
404
- }
405
- return (
406
- payloadBridge.diagnoseCapture(effectiveModelKey, ctx.modelRegistry) ??
407
- "parent-bridge-capture-not-ready"
304
+ if (!delegate)
305
+ return phaseRuntimeFailure(
306
+ {
307
+ kind: "environment",
308
+ code: "sandbox-runtime-unavailable",
309
+ stage: "phase-runtime",
310
+ },
311
+ "parent Provider is unavailable",
408
312
  );
409
- };
410
- const initialCaptureFailure = captureFailure();
411
- if (initialCaptureFailure) {
313
+ if (!ctx.model || !sameSelectedModel(ctx.model, selectedModelKey))
412
314
  return phaseRuntimeFailure(
413
315
  {
414
316
  kind: "environment",
415
- code: initialCaptureFailure,
317
+ code: "sandbox-runtime-unavailable",
416
318
  stage: "phase-runtime",
417
319
  },
418
- initialCaptureFailure,
320
+ "parent model changed during authentication",
419
321
  );
420
- }
421
- const delegate = capture.delegate;
422
- if (!delegate) throw new Error("parent Provider is unavailable");
423
- const diagnostic: { failure?: PhaseRuntimeFailure } = {};
424
- const provider = phaseProvider(
425
- delegate,
426
- resolved,
427
- {
428
- bridge: payloadBridge,
429
- capture,
430
- },
431
- diagnostic,
432
- observer,
433
- );
434
- let modelRuntime: ModelRuntime;
322
+ // Snapshot effective Provider behavior and auth, never mutate its registry or
323
+ // capture host-session callbacks. Explicit provider implementations still run.
324
+ const provider = phaseProvider(delegate, resolved, observer);
325
+ let modelRuntime: Models;
435
326
  try {
436
- modelRuntime = await runtimeForProvider(provider, signal);
327
+ modelRuntime = modelsForProvider(provider, signal);
437
328
  } catch (error) {
438
329
  if (isCancellation(error, signal)) return cancelledPhaseRuntime();
439
330
  throw error;
440
331
  }
441
332
  if (signal?.aborted) return cancelledPhaseRuntime();
442
- const finalCaptureFailure = captureFailure();
443
- if (finalCaptureFailure) {
333
+ if (
334
+ !ctx.model ||
335
+ !sameSelectedModel(ctx.model, selectedModelKey) ||
336
+ ctx.modelRegistry.getProvider(model.provider) !== delegate
337
+ ) {
444
338
  return phaseRuntimeFailure(
445
339
  {
446
340
  kind: "environment",
447
- code: finalCaptureFailure,
341
+ code: "sandbox-runtime-unavailable",
448
342
  stage: "phase-runtime",
449
343
  },
450
- finalCaptureFailure,
344
+ "parent model or Provider changed during admission",
451
345
  );
452
346
  }
453
- return {
454
- ok: true,
455
- modelRuntime,
456
- model,
457
- failureOverride: () => diagnostic.failure,
458
- };
347
+ return { ok: true, modelRuntime, model };
459
348
  }
460
349
 
461
350
  export async function runtimeForWorkerRoute(
462
351
  route: CustomRoutePolicy | InheritedRoutePolicy,
463
- ctx: Pick<ExtensionContext, "model" | "modelRegistry">,
464
- payloadBridge: ParentPayloadBridge,
352
+ ctx: ParentModelSource,
465
353
  signal?: AbortSignal,
466
354
  environment: Readonly<Record<string, string | undefined>> = process.env,
467
355
  observer?: PhaseTransportObserver,
468
356
  ): Promise<PhaseRuntimeResult> {
469
357
  return route.kind === "custom"
470
358
  ? customPhaseRuntime(route, signal, environment, observer)
471
- : runtimeFromContext(ctx, payloadBridge, signal, observer);
359
+ : runtimeFromContext(ctx, signal, observer);
472
360
  }
473
361
 
474
362
  function abortable<T>(promise: Promise<T>, signal?: AbortSignal): Promise<T> {