@abelxiaoxing/cadence 1.1.1 → 1.2.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/src/index.ts CHANGED
@@ -210,6 +210,42 @@ const DESIGN_CONTROL_REQUEST_SCHEMA = {
210
210
  description:
211
211
  "One closed durable Design control operation. The operation body is strict and idempotent by operationId.",
212
212
  oneOf: [
213
+ ...(["requirement", "change"] as const).map((field) => ({
214
+ type: "object" as const,
215
+ properties: {
216
+ operation: { type: "string" as const, enum: ["start"] },
217
+ operationId: { type: "string" as const },
218
+ [field]: {
219
+ type: "string" as const,
220
+ description:
221
+ field === "requirement"
222
+ ? "Raw Design requirement; code derives and stores only its stable hash."
223
+ : "Existing canonical change name to resume or revise.",
224
+ },
225
+ },
226
+ required: ["operation", "operationId", field],
227
+ additionalProperties: false,
228
+ })),
229
+ {
230
+ type: "object",
231
+ properties: {
232
+ operation: { type: "string", enum: ["status"] },
233
+ runId: { type: "string" },
234
+ },
235
+ required: ["operation", "runId"],
236
+ additionalProperties: false,
237
+ },
238
+ {
239
+ type: "object",
240
+ properties: {
241
+ operation: { type: "string", enum: ["bind-change"] },
242
+ runId: { type: "string" },
243
+ operationId: { type: "string" },
244
+ change: { type: "string" },
245
+ },
246
+ required: ["operation", "runId", "operationId", "change"],
247
+ additionalProperties: false,
248
+ },
213
249
  {
214
250
  type: "object",
215
251
  properties: {
@@ -218,7 +254,11 @@ const DESIGN_CONTROL_REQUEST_SCHEMA = {
218
254
  operationId: { type: "string" },
219
255
  decisionId: { type: "string" },
220
256
  category: { type: "string", enum: ["behavior", "technical"] },
221
- contractHash: { type: "string" },
257
+ contract: {
258
+ type: "string",
259
+ description:
260
+ "Normalized decision contract; code hashes it and never persists the text.",
261
+ },
222
262
  refs: { type: "array", items: { type: "string" } },
223
263
  },
224
264
  required: [
@@ -227,7 +267,7 @@ const DESIGN_CONTROL_REQUEST_SCHEMA = {
227
267
  "operationId",
228
268
  "decisionId",
229
269
  "category",
230
- "contractHash",
270
+ "contract",
231
271
  "refs",
232
272
  ],
233
273
  additionalProperties: false,
@@ -238,10 +278,25 @@ const DESIGN_CONTROL_REQUEST_SCHEMA = {
238
278
  operation: { type: "string", enum: ["approve-gate"] },
239
279
  runId: { type: "string" },
240
280
  operationId: { type: "string" },
241
- gate: { type: "string", enum: ["gate-a", "gate-b"] },
242
- contractHash: { type: "string" },
281
+ gate: { type: "string", enum: ["gate-a"] },
282
+ contract: {
283
+ type: "string",
284
+ description:
285
+ "Complete approved WHAT contract; code hashes it and never persists the text.",
286
+ },
287
+ },
288
+ required: ["operation", "runId", "operationId", "gate", "contract"],
289
+ additionalProperties: false,
290
+ },
291
+ {
292
+ type: "object",
293
+ properties: {
294
+ operation: { type: "string", enum: ["approve-gate"] },
295
+ runId: { type: "string" },
296
+ operationId: { type: "string" },
297
+ gate: { type: "string", enum: ["gate-b"] },
243
298
  },
244
- required: ["operation", "runId", "operationId", "gate", "contractHash"],
299
+ required: ["operation", "runId", "operationId", "gate"],
245
300
  additionalProperties: false,
246
301
  },
247
302
  {
@@ -417,7 +472,7 @@ export interface PackageDeliverySourceOptions {
417
472
 
418
473
  export interface PackageWorkflowDeliverySource extends WorkflowDeliverySource {
419
474
  discoverLatest(input: {
420
- stage: "abel-design" | "abel-implement";
475
+ stage: "abel-implement";
421
476
  change: string;
422
477
  }): Promise<WorkflowAvailableDelivery | undefined>;
423
478
  }
@@ -1418,6 +1473,7 @@ export function openPackageWorkflowControlEngine(
1418
1473
  const routeResolution = loadRoutePolicy({
1419
1474
  cwd: consumerRoot,
1420
1475
  home: homedir(),
1476
+ ...(initialContext.model ? { parentModel: initialContext.model } : {}),
1421
1477
  });
1422
1478
  const stateRoot = resolveStateRoot({
1423
1479
  consumerRoot,
@@ -1614,6 +1670,7 @@ export function openPackageWorkflowControlEngine(
1614
1670
  const operationRouteResolution = loadRoutePolicy({
1615
1671
  cwd: consumerRoot,
1616
1672
  home: homedir(),
1673
+ ...(context.model ? { parentModel: context.model } : {}),
1617
1674
  });
1618
1675
  engine.updateRoutePolicy(
1619
1676
  operationRouteResolution.ok
@@ -1636,12 +1693,7 @@ export function openPackageWorkflowControlEngine(
1636
1693
  operationRouteResolution,
1637
1694
  engine.routePolicyStatus(),
1638
1695
  );
1639
- const designStatus =
1640
- validation.value.stage === "abel-design" &&
1641
- typeof outcome.runId === "string"
1642
- ? { design: design.status(outcome.runId) }
1643
- : {};
1644
- return { ...outcome, ...designStatus, routePolicy };
1696
+ return { ...outcome, routePolicy };
1645
1697
  });
1646
1698
  },
1647
1699
  executeDesign(request: unknown) {
@@ -1781,10 +1833,9 @@ export function registerWorkflowControl(
1781
1833
  },
1782
1834
  stage: {
1783
1835
  type: "string",
1784
- enum: ["abel-design", "abel-implement"],
1836
+ enum: ["abel-implement"],
1785
1837
  },
1786
1838
  change: { type: "string" },
1787
- provisionalKey: { type: "string" },
1788
1839
  operationId: { type: "string" },
1789
1840
  deliveryRevision: { type: "integer", minimum: 1 },
1790
1841
  receiptHash: { type: "string" },
@@ -1818,7 +1869,7 @@ export function registerWorkflowControl(
1818
1869
  label: "Abel Control",
1819
1870
  description:
1820
1871
  kind === "command"
1821
- ? "Private stage-bound Abel workflow control. Accepts durable Design and Implement change commands."
1872
+ ? "Private stage-bound Abel workflow control. Accepts durable Implement change commands."
1822
1873
  : "Private stage-bound Abel packet control. Accepts bounded Design and Diagnose packet operations.",
1823
1874
  executionMode: "parallel",
1824
1875
  parameters:
@@ -2007,10 +2058,6 @@ export function registerWorkflowControl(
2007
2058
  ["completed", "discarded", "rejected"].includes(String(payload.state))
2008
2059
  ) {
2009
2060
  await deactivateStage();
2010
- } else if (validation.value.stage === "abel-design") {
2011
- // Design starts/statuses through the command surface, then switches to
2012
- // its bounded evidence and artifact packet surface for the next turn.
2013
- registerDispatchTool("packet");
2014
2061
  }
2015
2062
  let display: ReturnType<typeof activity.finalizeWorkflow>;
2016
2063
  if (tuiControl) {
@@ -2068,7 +2115,7 @@ export function registerWorkflowControl(
2068
2115
  if (activePrompt === "abel-design" && prompt !== "abel-design") {
2069
2116
  restoreDesignTools();
2070
2117
  }
2071
- registerDispatchTool(prompt === "abel-diagnose" ? "packet" : "command");
2118
+ registerDispatchTool(prompt === "abel-implement" ? "command" : "packet");
2072
2119
  activePrompt = prompt;
2073
2120
  const sessionId = ctx.sessionManager?.getSessionId?.();
2074
2121
  if (typeof sessionId === "string") {
@@ -20,7 +20,6 @@ import { runtimeForWorkerRoute } from "./parent-provider.ts";
20
20
  import {
21
21
  loadRoutePolicy,
22
22
  type RoutePolicy,
23
- unavailableRoutePolicy,
24
23
  type WorkerRole,
25
24
  } from "./route-policy.ts";
26
25
  import { type BrokerActivityUpdate, RunWorkerBroker } from "./worker-broker.ts";
@@ -319,7 +318,7 @@ export class PacketRuntime {
319
318
  if (!agent) {
320
319
  throw new Error("packet agent definition is unavailable");
321
320
  }
322
- const broker = this.#brokerFor(context.cwd);
321
+ const broker = this.#brokerFor(context);
323
322
  const routed = await broker.run({
324
323
  runId: `${packet.stage}:${packet.id}`,
325
324
  operationId: packet.id,
@@ -405,8 +404,8 @@ export class PacketRuntime {
405
404
  return routed.value.child;
406
405
  }
407
406
 
408
- #brokerFor(cwd: string): RunWorkerBroker {
409
- const root = path.resolve(cwd);
407
+ #brokerFor(context: PacketContext): RunWorkerBroker {
408
+ const root = path.resolve(context.cwd);
410
409
  const existing = this.#brokers.get(root);
411
410
  let policy = this.#routePolicy;
412
411
  if (policy && existing) return existing;
@@ -414,12 +413,13 @@ export class PacketRuntime {
414
413
  const resolution = loadRoutePolicy({
415
414
  cwd: root,
416
415
  ...(this.#routePolicyHome ? { home: this.#routePolicyHome } : {}),
416
+ ...(context.model ? { parentModel: context.model } : {}),
417
417
  });
418
- if (!resolution.ok && resolution.source.kind !== "none") {
418
+ if (!resolution.ok) {
419
419
  const code = resolution.diagnostics[0]?.code ?? "policy-invalid";
420
420
  throw new Error(`route-policy-unavailable:${code}`);
421
421
  }
422
- policy = resolution.ok ? resolution.policy : unavailableRoutePolicy();
422
+ policy = resolution.policy;
423
423
  }
424
424
  const serializedPolicy = JSON.stringify(policy);
425
425
  if (existing && this.#brokerPolicies.get(root) === serializedPolicy) {
@@ -1,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- import { existsSync, lstatSync, readFileSync } from "node:fs";
2
+ import { lstatSync, readFileSync } from "node:fs";
3
3
  import { homedir } from "node:os";
4
4
  import path from "node:path";
5
5
  import { ROLES } from "./contracts.ts";
@@ -21,6 +21,11 @@ export interface RouteCapabilities {
21
21
  maxTokens: number;
22
22
  }
23
23
 
24
+ export interface ParentModelCapabilities {
25
+ contextWindow: number;
26
+ maxTokens: number;
27
+ }
28
+
24
29
  interface RouteBase {
25
30
  id: string;
26
31
  kind: "inherited" | "custom";
@@ -50,10 +55,9 @@ export interface RoutePolicy {
50
55
 
51
56
  export type RoutePolicySource =
52
57
  | { kind: "project" | "user"; path: string }
53
- | { kind: "none" };
58
+ | { kind: "default" };
54
59
 
55
60
  export type RoutePolicyDiagnosticCode =
56
- | "policy-missing"
57
61
  | "policy-unreadable"
58
62
  | "policy-invalid-json"
59
63
  | "policy-invalid"
@@ -77,7 +81,7 @@ export type ParsedRoutePolicy =
77
81
  export type RoutePolicyResolution =
78
82
  | {
79
83
  ok: true;
80
- source: Extract<RoutePolicySource, { kind: "project" | "user" }>;
84
+ source: RoutePolicySource;
81
85
  policy: RoutePolicy;
82
86
  }
83
87
  | {
@@ -87,8 +91,8 @@ export type RoutePolicyResolution =
87
91
  };
88
92
 
89
93
  /**
90
- * Internal fail-closed policy used while the selected external policy is
91
- * missing or invalid. It deliberately contains no inherited-parent route:
94
+ * Internal fail-closed policy used while a selected explicit policy is
95
+ * invalid. It deliberately contains no inherited-parent route:
92
96
  * the broker therefore reports endpoint-unavailable without making a Worker
93
97
  * request, while the local durable control plane remains available.
94
98
  *
@@ -351,8 +355,16 @@ export function parseRoutePolicy(value: unknown): ParsedRoutePolicy {
351
355
  };
352
356
  }
353
357
 
354
- /** Parent-model route used by read-only Design and Diagnose packets. */
355
- export function parentRoutePolicy(): RoutePolicy {
358
+ /** Default parent-model route used when no explicit policy file exists. */
359
+ export function parentRoutePolicy(
360
+ parentModel?: ParentModelCapabilities,
361
+ ): RoutePolicy {
362
+ const contextWindow = positiveInteger(parentModel?.contextWindow)
363
+ ? parentModel.contextWindow
364
+ : 1;
365
+ const maxTokens = positiveInteger(parentModel?.maxTokens)
366
+ ? Math.min(parentModel.maxTokens, contextWindow)
367
+ : 1;
356
368
  const parsed = parseRoutePolicy({
357
369
  version: ROUTE_POLICY_VERSION,
358
370
  routes: {
@@ -361,8 +373,8 @@ export function parentRoutePolicy(): RoutePolicy {
361
373
  capabilities: {
362
374
  roles: [...ROLES],
363
375
  dialects: [...ROUTE_DIALECTS],
364
- contextWindow: 1_000_000,
365
- maxTokens: 500_000,
376
+ contextWindow,
377
+ maxTokens,
366
378
  },
367
379
  },
368
380
  },
@@ -372,25 +384,41 @@ export function parentRoutePolicy(): RoutePolicy {
372
384
  return parsed.policy;
373
385
  }
374
386
 
375
- function selectedSource(cwd: string, home: string): RoutePolicySource {
387
+ function selectedSource(
388
+ cwd: string,
389
+ home: string,
390
+ ): { kind: "project" | "user"; path: string } | { kind: "none" } {
391
+ const mayExist = (candidate: string): boolean => {
392
+ try {
393
+ return lstatSync(candidate, { throwIfNoEntry: false }) !== undefined;
394
+ } catch {
395
+ // Permission and path-observation failures are explicit unreadable
396
+ // policy candidates, not proof that the policy is absent.
397
+ return true;
398
+ }
399
+ };
376
400
  const projectPath = path.join(cwd, ".pi", "cadence", "routes.json");
377
- if (existsSync(projectPath)) return { kind: "project", path: projectPath };
401
+ if (mayExist(projectPath)) return { kind: "project", path: projectPath };
378
402
  const userPath = path.join(home, ".pi", "agent", "cadence", "routes.json");
379
- if (existsSync(userPath)) return { kind: "user", path: userPath };
403
+ if (mayExist(userPath)) return { kind: "user", path: userPath };
380
404
  return { kind: "none" };
381
405
  }
382
406
 
383
407
  export function loadRoutePolicy(
384
- options: { cwd?: string; home?: string } = {},
408
+ options: {
409
+ cwd?: string;
410
+ home?: string;
411
+ parentModel?: ParentModelCapabilities;
412
+ } = {},
385
413
  ): RoutePolicyResolution {
386
414
  const cwd = path.resolve(options.cwd ?? process.cwd());
387
415
  const home = path.resolve(options.home ?? homedir());
388
416
  const source = selectedSource(cwd, home);
389
417
  if (source.kind === "none") {
390
418
  return {
391
- ok: false,
392
- source,
393
- diagnostics: [{ code: "policy-missing" }],
419
+ ok: true,
420
+ source: { kind: "default" },
421
+ policy: parentRoutePolicy(options.parentModel),
394
422
  };
395
423
  }
396
424
  let bytes: string;
package/src/run-state.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
- import type { ControlStage } from "./control-contracts.ts";
2
+
3
+ export const RUN_STAGES = ["abel-design", "abel-implement"] as const;
4
+ export type RunStage = (typeof RUN_STAGES)[number];
3
5
 
4
6
  export const RUN_STATES = [
5
7
  "created",
@@ -57,7 +59,7 @@ export interface DeliveryBindingProjection {
57
59
  export interface RunProjection {
58
60
  runId: string;
59
61
  rootHash: string;
60
- stage: ControlStage;
62
+ stage: RunStage;
61
63
  change?: string;
62
64
  provisionalKey?: string;
63
65
  state: RunState;
package/src/run-store.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { chmodSync, closeSync, lstatSync, openSync } from "node:fs";
3
3
  import { DatabaseSync } from "node:sqlite";
4
- import type { ControlStage } from "./control-contracts.ts";
5
4
  import {
6
5
  createRunEvent,
7
6
  type DeliveryGate,
8
7
  type RunEvent,
9
8
  type RunProjection,
9
+ type RunStage,
10
10
  type RunState,
11
11
  reduceRunEvents,
12
12
  ZERO_EVENT_HASH,
@@ -37,6 +37,7 @@ interface EventRow {
37
37
  }
38
38
 
39
39
  interface OperationRow {
40
+ kind: string;
40
41
  state: string;
41
42
  outcome_json: string | null;
42
43
  lease_token: string | null;
@@ -49,7 +50,7 @@ export interface RunStoreOptions {
49
50
  }
50
51
 
51
52
  export interface StartRunInput {
52
- stage: ControlStage;
53
+ stage: RunStage;
53
54
  change?: string;
54
55
  provisionalKey?: string;
55
56
  operationId: string;
@@ -338,7 +339,7 @@ export class RunStore {
338
339
  #operation(runId: string, operationId: string): OperationRow | undefined {
339
340
  return this.#database
340
341
  .prepare(
341
- `SELECT state, outcome_json, lease_token, lease_expires_at
342
+ `SELECT kind, state, outcome_json, lease_token, lease_expires_at
342
343
  FROM operations WHERE run_id = ? AND operation_id = ?`,
343
344
  )
344
345
  .get(runId, operationId) as OperationRow | undefined;
@@ -347,8 +348,10 @@ export class RunStore {
347
348
  #operationOutcome(
348
349
  runId: string,
349
350
  operationId: string,
351
+ kind: string,
350
352
  ): RunProjection | undefined {
351
353
  const row = this.#operation(runId, operationId);
354
+ if (row && row.kind !== kind) throw new Error("operation-id-conflict");
352
355
  if (row?.state !== "committed" || row.outcome_json === null)
353
356
  return undefined;
354
357
  return parseProjection(row.outcome_json);
@@ -465,6 +468,7 @@ export class RunStore {
465
468
  const replay = this.#operationOutcome(
466
469
  existing.run_id,
467
470
  input.operationId,
471
+ "start",
468
472
  );
469
473
  if (replay) return replay;
470
474
  const projection = parseProjection(existing.projection_json);
@@ -539,7 +543,11 @@ export class RunStore {
539
543
  requireIdentifier(input.operationId, "operation-id");
540
544
  if (!CHANGE_NAME.test(input.change)) throw new Error("invalid-change");
541
545
  return this.#transaction(() => {
542
- const replay = this.#operationOutcome(input.runId, input.operationId);
546
+ const replay = this.#operationOutcome(
547
+ input.runId,
548
+ input.operationId,
549
+ "change-bound",
550
+ );
543
551
  if (replay) return replay;
544
552
  const current = this.status(input.runId);
545
553
  if (current.stage !== "abel-design" || !current.provisionalKey) {
@@ -597,7 +605,11 @@ export class RunStore {
597
605
  if (input.lease.runId !== input.runId) throw new Error("lease-fenced");
598
606
  this.#assertLease(input.lease);
599
607
  }
600
- const replay = this.#operationOutcome(input.runId, input.operationId);
608
+ const replay = this.#operationOutcome(
609
+ input.runId,
610
+ input.operationId,
611
+ "delivery-bound",
612
+ );
601
613
  if (replay) return replay;
602
614
  const existing = this.#database
603
615
  .prepare(
@@ -911,7 +923,11 @@ export class RunStore {
911
923
  if (input.lease.runId !== input.runId) throw new Error("lease-fenced");
912
924
  this.#assertLease(input.lease);
913
925
  }
914
- const replay = this.#operationOutcome(input.runId, input.operationId);
926
+ const replay = this.#operationOutcome(
927
+ input.runId,
928
+ input.operationId,
929
+ "state-transitioned",
930
+ );
915
931
  if (replay) return replay;
916
932
  const current = this.status(input.runId);
917
933
  const projection = this.#appendEvent(input.runId, "state-transitioned", {