@abelxiaoxing/cadence 1.0.13 → 1.1.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/README.md +29 -13
- package/agents/contract-reviewer.md +2 -2
- package/agents/design-explorer.md +1 -1
- package/agents/diagnosis-worker.md +6 -3
- package/agents/implementation-worker.md +14 -9
- package/config/.env.example +5 -17
- package/config/routes.example.json +42 -0
- package/package.json +3 -3
- package/prompts/abel-design.md +150 -228
- package/prompts/abel-diagnose.md +37 -18
- package/prompts/abel-implement.md +111 -178
- package/prompts/abel-init.md +36 -33
- package/skills/abel-workflow/SKILL.md +156 -168
- package/src/apply-transaction.ts +1427 -0
- package/src/artifact-store.ts +204 -0
- package/src/child-session.ts +136 -17
- package/src/contracts.ts +105 -696
- package/src/control-contracts.ts +259 -0
- package/src/delivery-compiler.ts +1088 -0
- package/src/file-snapshot.ts +5 -0
- package/src/implement-graph.ts +11 -4
- package/src/index.ts +1465 -133
- package/src/isolation-backend.ts +269 -0
- package/src/packet-runtime.ts +533 -0
- package/src/parent-provider.ts +23 -4
- package/src/route-policy.ts +483 -0
- package/src/run-state.ts +393 -0
- package/src/run-store.ts +918 -0
- package/src/safe-path.ts +11 -0
- package/src/scoped-tools.ts +36 -0
- package/src/state-root.ts +215 -0
- package/src/subagent-activity.ts +526 -27
- package/src/submit-tool.ts +274 -0
- package/src/task-ledger.ts +1380 -0
- package/src/verification-capability.ts +529 -42
- package/src/worker-broker.ts +703 -0
- package/src/workflow-engine.ts +7391 -0
- package/src/workspace-store.ts +544 -0
- package/src/candidate-preflight.ts +0 -1618
- package/src/drain.ts +0 -20
- package/src/patch.ts +0 -505
- package/src/result-store.ts +0 -312
- package/src/runtime.ts +0 -2436
- package/src/scheduler.ts +0 -384
- package/src/subagent-endpoint.ts +0 -158
- package/src/worker.ts +0 -204
package/src/safe-path.ts
CHANGED
|
@@ -64,3 +64,14 @@ export function observeSafePath(
|
|
|
64
64
|
export function isSafeRegularFile(root: string, relative: string): boolean {
|
|
65
65
|
return observeSafePath(root, relative).kind === "file";
|
|
66
66
|
}
|
|
67
|
+
|
|
68
|
+
/** Require an exact regular-file or absent workspace fact without following links. */
|
|
69
|
+
export function requireSafeWorkspacePath(
|
|
70
|
+
root: string,
|
|
71
|
+
relative: string,
|
|
72
|
+
expected: "file" | "absent",
|
|
73
|
+
): SafePathObservation {
|
|
74
|
+
const observation = observeSafePath(root, relative);
|
|
75
|
+
if (observation.kind !== expected) throw new Error("unsafe-workspace-path");
|
|
76
|
+
return observation;
|
|
77
|
+
}
|
package/src/scoped-tools.ts
CHANGED
|
@@ -132,6 +132,42 @@ export type Observation =
|
|
|
132
132
|
| { kind: "file"; path: string }
|
|
133
133
|
| { kind: "dir"; path: string };
|
|
134
134
|
|
|
135
|
+
export class ScopedObservationCollector {
|
|
136
|
+
readonly #observations = new Map<string, Observation>();
|
|
137
|
+
readonly #limit: number;
|
|
138
|
+
#truncated = false;
|
|
139
|
+
|
|
140
|
+
constructor(limit = 512) {
|
|
141
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 4096) {
|
|
142
|
+
throw new Error("observation-limit-invalid");
|
|
143
|
+
}
|
|
144
|
+
this.#limit = limit;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
readonly observe = (observation: Observation): void => {
|
|
148
|
+
const key = `${observation.kind}:${observation.path}`;
|
|
149
|
+
if (this.#observations.has(key)) return;
|
|
150
|
+
if (this.#observations.size >= this.#limit) {
|
|
151
|
+
this.#truncated = true;
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
this.#observations.set(key, { ...observation });
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
projection(): { observations: Observation[]; truncated: boolean } {
|
|
158
|
+
return {
|
|
159
|
+
observations: [...this.#observations.values()]
|
|
160
|
+
.map((entry) => ({ ...entry }))
|
|
161
|
+
.sort(
|
|
162
|
+
(left, right) =>
|
|
163
|
+
left.path.localeCompare(right.path) ||
|
|
164
|
+
left.kind.localeCompare(right.kind),
|
|
165
|
+
),
|
|
166
|
+
truncated: this.#truncated,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
135
171
|
export interface ScopedToolDef {
|
|
136
172
|
name: string;
|
|
137
173
|
description: string;
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { chmodSync, lstatSync, mkdirSync, realpathSync } from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
export const STATE_ROOT_ERROR_CODES = [
|
|
7
|
+
"consumer-root-unavailable",
|
|
8
|
+
"invalid-state-base",
|
|
9
|
+
"state-component-symlink",
|
|
10
|
+
"state-component-type",
|
|
11
|
+
"state-root-inside-consumer",
|
|
12
|
+
] as const;
|
|
13
|
+
|
|
14
|
+
export type StateRootErrorCode = (typeof STATE_ROOT_ERROR_CODES)[number];
|
|
15
|
+
|
|
16
|
+
export class StateRootError extends Error {
|
|
17
|
+
readonly code: StateRootErrorCode;
|
|
18
|
+
|
|
19
|
+
constructor(code: StateRootErrorCode) {
|
|
20
|
+
super(code);
|
|
21
|
+
this.name = "StateRootError";
|
|
22
|
+
this.code = code;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface StateRootOptions {
|
|
27
|
+
consumerRoot: string;
|
|
28
|
+
xdgStateHome?: string;
|
|
29
|
+
homeDir?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ResolvedStateRoot {
|
|
33
|
+
consumerRoot: string;
|
|
34
|
+
consumerRootHash: string;
|
|
35
|
+
stateBase: string;
|
|
36
|
+
productRoot: string;
|
|
37
|
+
rootsDir: string;
|
|
38
|
+
rootDir: string;
|
|
39
|
+
databasePath: string;
|
|
40
|
+
artifactsDir: string;
|
|
41
|
+
workspacesDir: string;
|
|
42
|
+
transactionsDir: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function isContainedBy(parent: string, candidate: string): boolean {
|
|
46
|
+
const relative = path.relative(parent, candidate);
|
|
47
|
+
return (
|
|
48
|
+
relative === "" ||
|
|
49
|
+
(!relative.startsWith("..") && !path.isAbsolute(relative))
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function inspectExistingComponents(absolutePath: string): void {
|
|
54
|
+
const parsed = path.parse(absolutePath);
|
|
55
|
+
const relativeParts = absolutePath
|
|
56
|
+
.slice(parsed.root.length)
|
|
57
|
+
.split(path.sep)
|
|
58
|
+
.filter(Boolean);
|
|
59
|
+
let current = parsed.root;
|
|
60
|
+
for (const part of relativeParts) {
|
|
61
|
+
current = path.join(current, part);
|
|
62
|
+
let stat: ReturnType<typeof lstatSync>;
|
|
63
|
+
try {
|
|
64
|
+
stat = lstatSync(current);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return;
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
if (stat.isSymbolicLink()) {
|
|
70
|
+
throw new StateRootError("state-component-symlink");
|
|
71
|
+
}
|
|
72
|
+
if (!stat.isDirectory() && current !== absolutePath) {
|
|
73
|
+
throw new StateRootError("state-component-type");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function canonicalFuturePath(input: string): string {
|
|
79
|
+
const absolute = path.resolve(input);
|
|
80
|
+
inspectExistingComponents(absolute);
|
|
81
|
+
let cursor = absolute;
|
|
82
|
+
const missing: string[] = [];
|
|
83
|
+
for (;;) {
|
|
84
|
+
try {
|
|
85
|
+
return path.join(realpathSync.native(cursor), ...missing.reverse());
|
|
86
|
+
} catch (error) {
|
|
87
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
88
|
+
const parent = path.dirname(cursor);
|
|
89
|
+
if (parent === cursor) throw error;
|
|
90
|
+
missing.push(path.basename(cursor));
|
|
91
|
+
cursor = parent;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function requireAbsoluteDirectoryBase(
|
|
97
|
+
xdgStateHome: string | undefined,
|
|
98
|
+
homeDir: string,
|
|
99
|
+
): string {
|
|
100
|
+
if (xdgStateHome && path.isAbsolute(xdgStateHome)) {
|
|
101
|
+
return canonicalFuturePath(xdgStateHome);
|
|
102
|
+
}
|
|
103
|
+
if (!path.isAbsolute(homeDir)) {
|
|
104
|
+
throw new StateRootError("invalid-state-base");
|
|
105
|
+
}
|
|
106
|
+
return canonicalFuturePath(path.join(homeDir, ".local", "state"));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function resolveStateRoot(options: StateRootOptions): ResolvedStateRoot {
|
|
110
|
+
let consumerRoot: string;
|
|
111
|
+
try {
|
|
112
|
+
consumerRoot = realpathSync.native(path.resolve(options.consumerRoot));
|
|
113
|
+
if (!lstatSync(consumerRoot).isDirectory()) {
|
|
114
|
+
throw new StateRootError("consumer-root-unavailable");
|
|
115
|
+
}
|
|
116
|
+
} catch (error) {
|
|
117
|
+
if (error instanceof StateRootError) throw error;
|
|
118
|
+
throw new StateRootError("consumer-root-unavailable");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const stateBase = requireAbsoluteDirectoryBase(
|
|
122
|
+
options.xdgStateHome,
|
|
123
|
+
options.homeDir ?? os.homedir(),
|
|
124
|
+
);
|
|
125
|
+
const consumerRootHash = createHash("sha256")
|
|
126
|
+
.update(consumerRoot)
|
|
127
|
+
.digest("hex");
|
|
128
|
+
const productRoot = canonicalFuturePath(path.join(stateBase, "abel-cadence"));
|
|
129
|
+
const rootsDir = canonicalFuturePath(path.join(productRoot, "roots"));
|
|
130
|
+
const rootDir = canonicalFuturePath(path.join(rootsDir, consumerRootHash));
|
|
131
|
+
|
|
132
|
+
if (isContainedBy(consumerRoot, rootDir)) {
|
|
133
|
+
throw new StateRootError("state-root-inside-consumer");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
consumerRoot,
|
|
138
|
+
consumerRootHash,
|
|
139
|
+
stateBase,
|
|
140
|
+
productRoot,
|
|
141
|
+
rootsDir,
|
|
142
|
+
rootDir,
|
|
143
|
+
databasePath: path.join(rootDir, "control.sqlite3"),
|
|
144
|
+
artifactsDir: path.join(rootDir, "artifacts"),
|
|
145
|
+
workspacesDir: path.join(rootDir, "workspaces"),
|
|
146
|
+
transactionsDir: path.join(rootDir, "transactions"),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function ensureDirectory(directory: string): void {
|
|
151
|
+
let stat: ReturnType<typeof lstatSync>;
|
|
152
|
+
try {
|
|
153
|
+
stat = lstatSync(directory);
|
|
154
|
+
} catch (error) {
|
|
155
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
156
|
+
const parent = path.dirname(directory);
|
|
157
|
+
if (parent !== directory) ensureDirectory(parent);
|
|
158
|
+
mkdirSync(directory, { mode: 0o700 });
|
|
159
|
+
chmodSync(directory, 0o700);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (stat.isSymbolicLink()) {
|
|
163
|
+
throw new StateRootError("state-component-symlink");
|
|
164
|
+
}
|
|
165
|
+
if (!stat.isDirectory()) {
|
|
166
|
+
throw new StateRootError("state-component-type");
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function prepareStateRoot(
|
|
171
|
+
resolved: ResolvedStateRoot,
|
|
172
|
+
): ResolvedStateRoot {
|
|
173
|
+
const checked = resolveStateRoot({
|
|
174
|
+
consumerRoot: resolved.consumerRoot,
|
|
175
|
+
xdgStateHome: resolved.stateBase,
|
|
176
|
+
homeDir: path.dirname(resolved.stateBase),
|
|
177
|
+
});
|
|
178
|
+
if (
|
|
179
|
+
checked.consumerRootHash !== resolved.consumerRootHash ||
|
|
180
|
+
checked.rootDir !== resolved.rootDir
|
|
181
|
+
) {
|
|
182
|
+
throw new StateRootError("invalid-state-base");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
ensureDirectory(resolved.stateBase);
|
|
186
|
+
for (const directory of [
|
|
187
|
+
resolved.productRoot,
|
|
188
|
+
resolved.rootsDir,
|
|
189
|
+
resolved.rootDir,
|
|
190
|
+
resolved.artifactsDir,
|
|
191
|
+
resolved.workspacesDir,
|
|
192
|
+
resolved.transactionsDir,
|
|
193
|
+
]) {
|
|
194
|
+
ensureDirectory(directory);
|
|
195
|
+
chmodSync(directory, 0o700);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
const database = lstatSync(resolved.databasePath);
|
|
200
|
+
if (database.isSymbolicLink()) {
|
|
201
|
+
throw new StateRootError("state-component-symlink");
|
|
202
|
+
}
|
|
203
|
+
if (!database.isFile()) {
|
|
204
|
+
throw new StateRootError("state-component-type");
|
|
205
|
+
}
|
|
206
|
+
} catch (error) {
|
|
207
|
+
if (
|
|
208
|
+
error instanceof StateRootError ||
|
|
209
|
+
(error as NodeJS.ErrnoException).code !== "ENOENT"
|
|
210
|
+
) {
|
|
211
|
+
throw error;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return { ...resolved };
|
|
215
|
+
}
|