@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/agents/contract-reviewer.md +2 -2
- package/agents/diagnosis-worker.md +3 -2
- package/agents/implementation-worker.md +3 -2
- package/package.json +1 -1
- package/prompts/abel-design.md +13 -3
- package/prompts/abel-implement.md +53 -13
- package/skills/abel-workflow/SKILL.md +60 -22
- package/src/candidate-preflight.ts +1468 -0
- package/src/child-session.ts +136 -30
- package/src/contracts.ts +1343 -63
- package/src/file-snapshot.ts +62 -8
- package/src/index.ts +99 -28
- package/src/parent-payload-bridge.ts +442 -0
- package/src/parent-provider.ts +166 -21
- package/src/patch.ts +331 -33
- package/src/result-store.ts +282 -7
- package/src/runtime.ts +1584 -154
- package/src/scheduler.ts +60 -16
- package/src/scoped-tools.ts +27 -6
- package/src/subagent-activity.ts +56 -25
- package/src/submit-tool.ts +15 -3
- package/src/worker.ts +114 -85
package/src/worker.ts
CHANGED
|
@@ -1,91 +1,92 @@
|
|
|
1
|
-
// Process-local
|
|
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 {
|
|
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
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
55
|
-
|
|
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[] = [
|
|
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
|
|
97
|
+
private readonly tasks = new Map<string, TaskRecord>();
|
|
98
|
+
|
|
99
|
+
has(key: string): boolean {
|
|
100
|
+
return this.tasks.has(key);
|
|
101
|
+
}
|
|
97
102
|
|
|
98
|
-
|
|
99
|
-
return this.
|
|
103
|
+
get(key: string): TaskRecord | undefined {
|
|
104
|
+
return this.tasks.get(key);
|
|
100
105
|
}
|
|
101
106
|
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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.
|
|
113
|
-
return
|
|
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.
|
|
146
|
+
this.tasks.clear();
|
|
118
147
|
}
|
|
119
148
|
}
|