@abelxiaoxing/cadence 1.0.1 → 1.0.3

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/worker.ts CHANGED
@@ -1,91 +1,92 @@
1
- // Process-local logical Worker registry for private orchestration. A Worker
2
- // is pinned by the first accepted phase: logical ID, stage, role, the
3
- // provider/model/API/thinking identity, and the approved non-snapshot
4
- // contract. Later accepted Red/Green/optional Refactor phases receive fresh
5
- // snapshots and fresh phase-local auth for the SAME pinned identity. A failed
6
- // phase may be mechanically redispatched exactly once when its canonical
7
- // request is identical apart from refreshed snapshot hashes; a second failure
8
- // blocks that branch. No credential, transcript, or retry ledger is retained.
1
+ // Process-local task registry for the private Implement runtime.
9
2
 
10
- import type { RequestEnvelope } from "./contracts.ts";
3
+ import type {
4
+ CandidateFailure,
5
+ ImplementationPhase,
6
+ PhaseAttempt,
7
+ TaskBoundary,
8
+ TaskFailure,
9
+ } from "./contracts.ts";
10
+ import type { ConflictDeclaration } from "./scheduler.ts";
11
11
 
12
- export interface WorkerContract {
13
- stage: string;
14
- role: string;
15
- id: string;
16
- objective: string;
17
- roots: string[];
18
- agents: string;
19
- contractText: string;
20
- read: string[];
21
- write: string[];
22
- conflicts: string[];
23
- resources: string[];
24
- verificationLock?: string;
25
- output: string;
12
+ export type TaskState =
13
+ | {
14
+ kind: "ready";
15
+ phase: ImplementationPhase;
16
+ launchIndex: 0 | 1;
17
+ correction?: CandidateFailure;
18
+ }
19
+ | {
20
+ kind: "candidate-pending";
21
+ phase: ImplementationPhase;
22
+ launchIndex: 0 | 1;
23
+ originRequestId: string;
24
+ resultId: string;
25
+ }
26
+ | {
27
+ kind: "agents-checkpoint-pending";
28
+ finalPhase: "green" | "refactor";
29
+ attemptIndex: 0 | 1;
30
+ }
31
+ | { kind: "blocked"; phase: ImplementationPhase; failure: TaskFailure }
32
+ | { kind: "completed"; finalPhase: "green" | "refactor" };
33
+
34
+ export interface TaskRecord {
35
+ key: string;
36
+ workspaceRoot: string;
37
+ workerIdentity: string;
38
+ boundary: TaskBoundary;
39
+ conflict: ConflictDeclaration;
40
+ state: TaskState;
26
41
  }
27
42
 
28
- export interface LogicalWorker {
29
- identity: string;
30
- contract: WorkerContract;
31
- redispatchUsed: boolean;
43
+ export function taskRecordKey(
44
+ workspaceRoot: string,
45
+ changeId: string,
46
+ taskId: string,
47
+ ): string {
48
+ return `${workspaceRoot}\0${changeId}\0${taskId}`;
32
49
  }
33
50
 
34
- // Pins the semantic scope of a logical Worker, excluding the phase label so a
35
- // later Red/Green/Refactor phase is recognized as the same Worker.
36
- export function contractOf(envelope: RequestEnvelope): WorkerContract {
51
+ export function taskConflictOf(boundary: TaskBoundary): ConflictDeclaration {
52
+ const phases = [
53
+ boundary.phases.red,
54
+ boundary.phases.green,
55
+ ...(boundary.phases.refactor ? [boundary.phases.refactor] : []),
56
+ ];
57
+ const write = new Set(phases.flatMap((phase) => phase.write));
58
+ if (boundary.agents.impact !== "none" && boundary.agents.target) {
59
+ write.add(boundary.agents.target);
60
+ }
37
61
  return {
38
- stage: envelope.stage,
39
- role: envelope.role,
40
- id: envelope.id,
41
- objective: envelope.objective,
42
- roots: [...envelope.roots],
43
- agents: envelope.context.agents,
44
- contractText: envelope.context.contract,
45
- read: [...envelope.declared.read],
46
- write: [...envelope.declared.write],
47
- conflicts: [...envelope.declared.conflicts],
48
- resources: [...envelope.declared.resources],
49
- verificationLock: envelope.declared.verificationLock,
50
- output: envelope.output,
62
+ taskId: boundary.taskId,
63
+ read: [...new Set(phases.flatMap((phase) => phase.read))],
64
+ write: [...write],
65
+ conflicts: [...boundary.scheduling.conflicts],
66
+ resources: [...boundary.scheduling.resources],
67
+ verificationLocks: [
68
+ ...new Set(
69
+ phases.flatMap((phase) =>
70
+ phase.verificationLock ? [phase.verificationLock] : [],
71
+ ),
72
+ ),
73
+ ],
51
74
  };
52
75
  }
53
76
 
54
- export function sameContract(a: WorkerContract, b: WorkerContract): boolean {
55
- if (
56
- a.stage !== b.stage ||
57
- a.role !== b.role ||
58
- a.id !== b.id ||
59
- a.objective !== b.objective ||
60
- a.agents !== b.agents ||
61
- a.contractText !== b.contractText ||
62
- a.output !== b.output ||
63
- a.verificationLock !== b.verificationLock
64
- )
65
- return false;
66
- return (
67
- arrayEqual(a.roots, b.roots) &&
68
- arrayEqual(a.read, b.read) &&
69
- arrayEqual(a.write, b.write) &&
70
- arrayEqual(a.conflicts, b.conflicts) &&
71
- arrayEqual(a.resources, b.resources)
72
- );
73
- }
74
-
75
- function arrayEqual(a: string[], b: string[]): boolean {
76
- return a.length === b.length && a.every((value, index) => value === b[index]);
77
- }
78
-
79
- // Stable provider/model/API/thinking identity fingerprint for a phase. Each
80
- // child attempt re-derives it while the phase-local auth boundary is called
81
- // anew, so the identity stays pinned across fresh phases.
77
+ // Stable provider/model/API identity fingerprint. Fresh auth may be acquired
78
+ // for each phase, while the selected execution identity remains pinned.
82
79
  export function workerIdentity(model: {
83
80
  provider?: unknown;
81
+ api?: unknown;
84
82
  baseUrl?: unknown;
85
83
  id?: unknown;
86
84
  name?: unknown;
87
85
  }): string {
88
- const parts: string[] = [`p:${String(model.provider ?? "")}`];
86
+ const parts: string[] = [
87
+ `p:${String(model.provider ?? "")}`,
88
+ `a:${String(model.api ?? "")}`,
89
+ ];
89
90
  if (model.baseUrl !== undefined) parts.push(`u:${String(model.baseUrl)}`);
90
91
  if (typeof model.id === "string") parts.push(`id:${model.id}`);
91
92
  if (typeof model.name === "string") parts.push(`n:${model.name}`);
@@ -93,27 +94,55 @@ export function workerIdentity(model: {
93
94
  }
94
95
 
95
96
  export class WorkerRegistry {
96
- private readonly workers = new Map<string, LogicalWorker>();
97
+ private readonly tasks = new Map<string, TaskRecord>();
98
+
99
+ has(key: string): boolean {
100
+ return this.tasks.has(key);
101
+ }
97
102
 
98
- has(id: string): boolean {
99
- return this.workers.has(id);
103
+ get(key: string): TaskRecord | undefined {
104
+ return this.tasks.get(key);
100
105
  }
101
106
 
102
- get(id: string): LogicalWorker | undefined {
103
- return this.workers.get(id);
107
+ find(workspaceRoot: string, taskId: string): TaskRecord | undefined {
108
+ const matches = [...this.tasks.values()].filter(
109
+ (record) =>
110
+ record.workspaceRoot === workspaceRoot &&
111
+ record.boundary.taskId === taskId,
112
+ );
113
+ return matches.length === 1 ? matches[0] : undefined;
104
114
  }
105
115
 
106
- pin(contract: WorkerContract, identity: string): LogicalWorker {
107
- const worker: LogicalWorker = {
108
- identity,
109
- contract,
110
- redispatchUsed: false,
116
+ open(
117
+ boundary: TaskBoundary,
118
+ identity: string,
119
+ workspaceRoot: string,
120
+ attempt: PhaseAttempt,
121
+ ): TaskRecord {
122
+ const key = taskRecordKey(
123
+ workspaceRoot,
124
+ boundary.changeId,
125
+ boundary.taskId,
126
+ );
127
+ if (this.tasks.has(key)) throw new Error("duplicate task open");
128
+ const storedBoundary = structuredClone(boundary);
129
+ const record: TaskRecord = {
130
+ key,
131
+ workspaceRoot,
132
+ workerIdentity: identity,
133
+ boundary: storedBoundary,
134
+ conflict: taskConflictOf(storedBoundary),
135
+ state: { kind: "ready", phase: attempt.phase, launchIndex: 0 },
111
136
  };
112
- this.workers.set(contract.id, worker);
113
- return worker;
137
+ this.tasks.set(key, record);
138
+ return record;
139
+ }
140
+
141
+ values(): TaskRecord[] {
142
+ return [...this.tasks.values()];
114
143
  }
115
144
 
116
145
  clear(): void {
117
- this.workers.clear();
146
+ this.tasks.clear();
118
147
  }
119
148
  }