@enderfga/claw-orchestrator 5.0.0 → 6.0.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 +28 -28
- package/dist/bin/cli.js +107 -1
- package/dist/bin/cli.js.map +1 -1
- package/dist/src/acp-server.d.ts +5 -5
- package/dist/src/acp-server.js +3 -3
- package/dist/src/acp-server.js.map +1 -1
- package/dist/src/autoloop/dispatcher.d.ts +22 -0
- package/dist/src/autoloop/dispatcher.js +71 -13
- package/dist/src/autoloop/dispatcher.js.map +1 -1
- package/dist/src/autoloop/messages.d.ts +10 -0
- package/dist/src/autoloop/messages.js.map +1 -1
- package/dist/src/autoloop/runner.js +6 -0
- package/dist/src/autoloop/runner.js.map +1 -1
- package/dist/src/constants.d.ts +0 -6
- package/dist/src/constants.js +0 -6
- package/dist/src/constants.js.map +1 -1
- package/dist/src/council.d.ts +15 -0
- package/dist/src/council.js +48 -35
- package/dist/src/council.js.map +1 -1
- package/dist/src/dashboard/index.html +191 -6
- package/dist/src/embedded-server.js +132 -9
- package/dist/src/embedded-server.js.map +1 -1
- package/dist/src/fanout.d.ts +30 -1
- package/dist/src/fanout.js +32 -3
- package/dist/src/fanout.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +360 -4
- package/dist/src/index.js.map +1 -1
- package/dist/src/kernel/agent-step.d.ts +59 -0
- package/dist/src/kernel/agent-step.js +100 -0
- package/dist/src/kernel/agent-step.js.map +1 -0
- package/dist/src/kernel/conditions.d.ts +11 -0
- package/dist/src/kernel/conditions.js +24 -0
- package/dist/src/kernel/conditions.js.map +1 -0
- package/dist/src/kernel/engine.d.ts +319 -0
- package/dist/src/kernel/engine.js +1047 -0
- package/dist/src/kernel/engine.js.map +1 -0
- package/dist/src/kernel/exec.d.ts +43 -0
- package/dist/src/kernel/exec.js +112 -0
- package/dist/src/kernel/exec.js.map +1 -0
- package/dist/src/kernel/file-lock.d.ts +50 -0
- package/dist/src/kernel/file-lock.js +135 -0
- package/dist/src/kernel/file-lock.js.map +1 -0
- package/dist/src/kernel/nodes/agent.d.ts +4 -0
- package/dist/src/kernel/nodes/agent.js +35 -0
- package/dist/src/kernel/nodes/agent.js.map +1 -0
- package/dist/src/kernel/nodes/autoloop.d.ts +78 -0
- package/dist/src/kernel/nodes/autoloop.js +75 -0
- package/dist/src/kernel/nodes/autoloop.js.map +1 -0
- package/dist/src/kernel/nodes/council.d.ts +12 -0
- package/dist/src/kernel/nodes/council.js +88 -0
- package/dist/src/kernel/nodes/council.js.map +1 -0
- package/dist/src/kernel/nodes/fanout.d.ts +11 -0
- package/dist/src/kernel/nodes/fanout.js +63 -0
- package/dist/src/kernel/nodes/fanout.js.map +1 -0
- package/dist/src/kernel/nodes/human-gate.d.ts +4 -0
- package/dist/src/kernel/nodes/human-gate.js +7 -0
- package/dist/src/kernel/nodes/human-gate.js.map +1 -0
- package/dist/src/kernel/nodes/index.d.ts +12 -0
- package/dist/src/kernel/nodes/index.js +21 -0
- package/dist/src/kernel/nodes/index.js.map +1 -0
- package/dist/src/kernel/nodes/router.d.ts +4 -0
- package/dist/src/kernel/nodes/router.js +12 -0
- package/dist/src/kernel/nodes/router.js.map +1 -0
- package/dist/src/kernel/nodes/subflow.d.ts +13 -0
- package/dist/src/kernel/nodes/subflow.js +38 -0
- package/dist/src/kernel/nodes/subflow.js.map +1 -0
- package/dist/src/kernel/nodes/ultraapp.d.ts +60 -0
- package/dist/src/kernel/nodes/ultraapp.js +62 -0
- package/dist/src/kernel/nodes/ultraapp.js.map +1 -0
- package/dist/src/kernel/nodes/verifier.d.ts +14 -0
- package/dist/src/kernel/nodes/verifier.js +84 -0
- package/dist/src/kernel/nodes/verifier.js.map +1 -0
- package/dist/src/kernel/projections.d.ts +42 -0
- package/dist/src/kernel/projections.js +133 -0
- package/dist/src/kernel/projections.js.map +1 -0
- package/dist/src/kernel/repo.d.ts +13 -0
- package/dist/src/kernel/repo.js +64 -0
- package/dist/src/kernel/repo.js.map +1 -0
- package/dist/src/kernel/secrets.d.ts +25 -0
- package/dist/src/kernel/secrets.js +48 -0
- package/dist/src/kernel/secrets.js.map +1 -0
- package/dist/src/kernel/store.d.ts +225 -0
- package/dist/src/kernel/store.js +838 -0
- package/dist/src/kernel/store.js.map +1 -0
- package/dist/src/kernel/templates/index.d.ts +140 -0
- package/dist/src/kernel/templates/index.js +266 -0
- package/dist/src/kernel/templates/index.js.map +1 -0
- package/dist/src/kernel/types.d.ts +326 -0
- package/dist/src/kernel/types.js +19 -0
- package/dist/src/kernel/types.js.map +1 -0
- package/dist/src/models.d.ts +1 -1
- package/dist/src/models.js +31 -3
- package/dist/src/models.js.map +1 -1
- package/dist/src/persistent-cursor-session.js +6 -1
- package/dist/src/persistent-cursor-session.js.map +1 -1
- package/dist/src/persistent-grok-session.d.ts +40 -0
- package/dist/src/persistent-grok-session.js +197 -0
- package/dist/src/persistent-grok-session.js.map +1 -0
- package/dist/src/run-ledger.d.ts +57 -3
- package/dist/src/run-ledger.js +45 -2
- package/dist/src/run-ledger.js.map +1 -1
- package/dist/src/session-manager.d.ts +176 -129
- package/dist/src/session-manager.js +657 -603
- package/dist/src/session-manager.js.map +1 -1
- package/dist/src/types.d.ts +37 -4
- package/dist/src/types.js +15 -1
- package/dist/src/types.js.map +1 -1
- package/dist/src/ultraapp/build.d.ts +117 -3
- package/dist/src/ultraapp/build.js +319 -3
- package/dist/src/ultraapp/build.js.map +1 -1
- package/dist/src/ultraapp/contract.d.ts +52 -0
- package/dist/src/ultraapp/contract.js +83 -0
- package/dist/src/ultraapp/contract.js.map +1 -0
- package/dist/src/ultraapp/conventions.js +9 -2
- package/dist/src/ultraapp/conventions.js.map +1 -1
- package/dist/src/ultraapp/fix-on-failure.d.ts +21 -2
- package/dist/src/ultraapp/fix-on-failure.js +46 -62
- package/dist/src/ultraapp/fix-on-failure.js.map +1 -1
- package/dist/src/ultraapp/manager.d.ts +107 -2
- package/dist/src/ultraapp/manager.js +305 -86
- package/dist/src/ultraapp/manager.js.map +1 -1
- package/dist/src/verify/baseline.d.ts +73 -0
- package/dist/src/verify/baseline.js +186 -0
- package/dist/src/verify/baseline.js.map +1 -0
- package/dist/src/verify/contract.d.ts +116 -0
- package/dist/src/verify/contract.js +142 -0
- package/dist/src/verify/contract.js.map +1 -0
- package/dist/src/verify/evidence.d.ts +61 -0
- package/dist/src/verify/evidence.js +133 -0
- package/dist/src/verify/evidence.js.map +1 -0
- package/dist/src/verify/runner.d.ts +63 -0
- package/dist/src/verify/runner.js +317 -0
- package/dist/src/verify/runner.js.map +1 -0
- package/openclaw.plugin.json +8 -0
- package/package.json +2 -2
- package/skills/SKILL.md +121 -80
- package/skills/references/acp.md +18 -18
- package/skills/references/autoloop.md +148 -72
- package/skills/references/claude-cli-tracking.md +4 -4
- package/skills/references/cli.md +103 -60
- package/skills/references/council.md +109 -37
- package/skills/references/dashboard.md +34 -6
- package/skills/references/getting-started.md +14 -14
- package/skills/references/inbox.md +4 -4
- package/skills/references/mcp.md +39 -34
- package/skills/references/multi-engine.md +109 -51
- package/skills/references/observability.md +88 -27
- package/skills/references/openai-compat.md +40 -40
- package/skills/references/sessions.md +44 -26
- package/skills/references/tools.md +402 -309
- package/skills/references/ultra.md +45 -45
- package/skills/references/ultraapp.md +126 -50
- package/skills/references/verification.md +187 -0
- package/skills/references/workflow.md +362 -0
- package/dist/src/ultraapp/fix-on-failure-session.d.ts +0 -23
- package/dist/src/ultraapp/fix-on-failure-session.js +0 -51
- package/dist/src/ultraapp/fix-on-failure-session.js.map +0 -1
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Baseline capture — the primitive nothing in this repo had.
|
|
3
|
+
*
|
|
4
|
+
* Two existing call sites tried to answer "what did this run change" and both
|
|
5
|
+
* get it wrong in a way that matters:
|
|
6
|
+
*
|
|
7
|
+
* - `council.ts` diffs `HEAD~20..HEAD` (with a `HEAD~10` fallback), a hardcoded
|
|
8
|
+
* magic window that silently returns nothing on shallow history and has no
|
|
9
|
+
* relationship to when the run actually started.
|
|
10
|
+
* - `autoloop/dispatcher.ts` runs a bare `git diff`, which lists tracked
|
|
11
|
+
* modifications only. Files the Coder *created* appear in neither the patch
|
|
12
|
+
* nor the `--name-only` fallback, yet the following `git add -A` commits them.
|
|
13
|
+
* So the Reviewer audits a diff that structurally cannot show new files.
|
|
14
|
+
*
|
|
15
|
+
* Here the base commit is recorded when the run starts, and the change set is
|
|
16
|
+
* tracked-diff ∪ untracked-files. Nothing mutates the caller's index: untracked
|
|
17
|
+
* files are rendered with `git diff --no-index /dev/null <file>`, which emits a
|
|
18
|
+
* proper new-file patch without an `add -N`.
|
|
19
|
+
*/
|
|
20
|
+
export type ChangeStatus = 'added' | 'modified' | 'deleted' | 'untracked';
|
|
21
|
+
export interface ChangedFile {
|
|
22
|
+
path: string;
|
|
23
|
+
status: ChangeStatus;
|
|
24
|
+
insertions: number;
|
|
25
|
+
deletions: number;
|
|
26
|
+
}
|
|
27
|
+
/** `git rev-parse HEAD`, or undefined when cwd is not a repo (or has no commits yet). */
|
|
28
|
+
export declare function captureBaseline(cwd: string): Promise<string | undefined>;
|
|
29
|
+
/**
|
|
30
|
+
* Everything the run touched since `baseSha`: tracked changes in the working
|
|
31
|
+
* tree (committed or not) plus every untracked file.
|
|
32
|
+
*/
|
|
33
|
+
export declare function changedFilesSince(cwd: string, baseSha: string | undefined): Promise<ChangedFile[]>;
|
|
34
|
+
/**
|
|
35
|
+
* A unified patch covering the same set: `git diff <base>` for tracked changes,
|
|
36
|
+
* then a synthetic new-file patch per untracked file appended after it.
|
|
37
|
+
*/
|
|
38
|
+
export declare function capturePatch(cwd: string, baseSha: string | undefined): Promise<string>;
|
|
39
|
+
/**
|
|
40
|
+
* A digest of the working tree's current *content*.
|
|
41
|
+
*
|
|
42
|
+
* Evidence describes the tree as it was when the checks ran. If anything moves
|
|
43
|
+
* afterwards the bundle still exists but no longer describes what is there, and
|
|
44
|
+
* a run that keeps reporting `verified` on the strength of it is claiming
|
|
45
|
+
* something nobody measured. Comparing this before and after is how the kernel
|
|
46
|
+
* notices.
|
|
47
|
+
*
|
|
48
|
+
* The first version of this hashed `git rev-parse HEAD` plus
|
|
49
|
+
* `git status --porcelain`, which does not work: porcelain reports a file's
|
|
50
|
+
* *state*, not its bytes. A file that was already `M` before the verifier and
|
|
51
|
+
* is rewritten afterwards still prints ` M a.txt`, so the digest was unchanged
|
|
52
|
+
* and a stale verdict sailed through. The common case — an agent editing a file
|
|
53
|
+
* it had already edited — was exactly the case it missed.
|
|
54
|
+
*
|
|
55
|
+
* So this hashes content: HEAD, the full `git diff HEAD` patch (which covers
|
|
56
|
+
* staged and unstaged changes to tracked files), and the path plus bytes of
|
|
57
|
+
* every untracked file.
|
|
58
|
+
*
|
|
59
|
+
* Returns undefined outside a repo — a caller that cannot fingerprint must not
|
|
60
|
+
* assume the tree held still.
|
|
61
|
+
*/
|
|
62
|
+
export declare function treeFingerprint(cwd: string): Promise<string | undefined>;
|
|
63
|
+
/**
|
|
64
|
+
* True when `file` sits under `prefix`.
|
|
65
|
+
*
|
|
66
|
+
* Segment-aware, so `srcfoo/a.ts` is not under `src`. `.` and `''` both mean the
|
|
67
|
+
* repo root and match everything — without that, a policy of `requirePaths: ['.']`
|
|
68
|
+
* ("the run must change something") reports that nothing changed no matter what
|
|
69
|
+
* the run did.
|
|
70
|
+
*/
|
|
71
|
+
export declare function isUnder(file: string, prefix: string): boolean;
|
|
72
|
+
/** Resolve a check-relative path against the run cwd. */
|
|
73
|
+
export declare function resolveIn(cwd: string, p: string): string;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Baseline capture — the primitive nothing in this repo had.
|
|
3
|
+
*
|
|
4
|
+
* Two existing call sites tried to answer "what did this run change" and both
|
|
5
|
+
* get it wrong in a way that matters:
|
|
6
|
+
*
|
|
7
|
+
* - `council.ts` diffs `HEAD~20..HEAD` (with a `HEAD~10` fallback), a hardcoded
|
|
8
|
+
* magic window that silently returns nothing on shallow history and has no
|
|
9
|
+
* relationship to when the run actually started.
|
|
10
|
+
* - `autoloop/dispatcher.ts` runs a bare `git diff`, which lists tracked
|
|
11
|
+
* modifications only. Files the Coder *created* appear in neither the patch
|
|
12
|
+
* nor the `--name-only` fallback, yet the following `git add -A` commits them.
|
|
13
|
+
* So the Reviewer audits a diff that structurally cannot show new files.
|
|
14
|
+
*
|
|
15
|
+
* Here the base commit is recorded when the run starts, and the change set is
|
|
16
|
+
* tracked-diff ∪ untracked-files. Nothing mutates the caller's index: untracked
|
|
17
|
+
* files are rendered with `git diff --no-index /dev/null <file>`, which emits a
|
|
18
|
+
* proper new-file patch without an `add -N`.
|
|
19
|
+
*/
|
|
20
|
+
import crypto from 'node:crypto';
|
|
21
|
+
import fs from 'node:fs';
|
|
22
|
+
import path from 'node:path';
|
|
23
|
+
import { exec } from '../kernel/exec.js';
|
|
24
|
+
const GIT_TIMEOUT_MS = 60_000;
|
|
25
|
+
/** `git rev-parse HEAD`, or undefined when cwd is not a repo (or has no commits yet). */
|
|
26
|
+
export async function captureBaseline(cwd) {
|
|
27
|
+
const r = await exec('git', ['-C', cwd, 'rev-parse', 'HEAD'], { timeoutMs: GIT_TIMEOUT_MS });
|
|
28
|
+
if (r.code !== 0)
|
|
29
|
+
return undefined;
|
|
30
|
+
const sha = r.out.trim();
|
|
31
|
+
return /^[0-9a-f]{7,40}$/i.test(sha) ? sha : undefined;
|
|
32
|
+
}
|
|
33
|
+
async function untrackedFiles(cwd) {
|
|
34
|
+
const r = await exec('git', ['-C', cwd, 'ls-files', '--others', '--exclude-standard'], {
|
|
35
|
+
timeoutMs: GIT_TIMEOUT_MS,
|
|
36
|
+
});
|
|
37
|
+
if (r.code !== 0)
|
|
38
|
+
return [];
|
|
39
|
+
return r.out
|
|
40
|
+
.split('\n')
|
|
41
|
+
.map((l) => l.trim())
|
|
42
|
+
.filter(Boolean);
|
|
43
|
+
}
|
|
44
|
+
async function countLines(cwd, file) {
|
|
45
|
+
const r = await exec('git', ['-C', cwd, 'diff', '--no-index', '--numstat', '/dev/null', file], {
|
|
46
|
+
timeoutMs: GIT_TIMEOUT_MS,
|
|
47
|
+
});
|
|
48
|
+
// `--no-index` exits 1 when there are differences, which is the normal case here.
|
|
49
|
+
const first = r.out.split('\n').find((l) => l.trim() !== '');
|
|
50
|
+
if (!first)
|
|
51
|
+
return 0;
|
|
52
|
+
const n = Number(first.split('\t')[0]);
|
|
53
|
+
return Number.isFinite(n) ? n : 0;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Everything the run touched since `baseSha`: tracked changes in the working
|
|
57
|
+
* tree (committed or not) plus every untracked file.
|
|
58
|
+
*/
|
|
59
|
+
export async function changedFilesSince(cwd, baseSha) {
|
|
60
|
+
const files = [];
|
|
61
|
+
if (baseSha) {
|
|
62
|
+
const r = await exec('git', ['-C', cwd, 'diff', '--numstat', '--no-renames', baseSha, '--'], {
|
|
63
|
+
timeoutMs: GIT_TIMEOUT_MS,
|
|
64
|
+
});
|
|
65
|
+
if (r.code === 0) {
|
|
66
|
+
for (const line of r.out.split('\n')) {
|
|
67
|
+
const parts = line.split('\t');
|
|
68
|
+
if (parts.length < 3)
|
|
69
|
+
continue;
|
|
70
|
+
const insertions = Number(parts[0]);
|
|
71
|
+
const deletions = Number(parts[1]);
|
|
72
|
+
const file = parts.slice(2).join('\t').trim();
|
|
73
|
+
if (!file)
|
|
74
|
+
continue;
|
|
75
|
+
// A binary file reports "-\t-"; count it as touched with zero line deltas.
|
|
76
|
+
files.push({
|
|
77
|
+
path: file,
|
|
78
|
+
status: deletions > 0 && insertions === 0 ? 'deleted' : 'modified',
|
|
79
|
+
insertions: Number.isFinite(insertions) ? insertions : 0,
|
|
80
|
+
deletions: Number.isFinite(deletions) ? deletions : 0,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const seen = new Set(files.map((f) => f.path));
|
|
86
|
+
for (const file of await untrackedFiles(cwd)) {
|
|
87
|
+
if (seen.has(file))
|
|
88
|
+
continue;
|
|
89
|
+
files.push({ path: file, status: 'untracked', insertions: await countLines(cwd, file), deletions: 0 });
|
|
90
|
+
}
|
|
91
|
+
return files.sort((a, b) => a.path.localeCompare(b.path));
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* A unified patch covering the same set: `git diff <base>` for tracked changes,
|
|
95
|
+
* then a synthetic new-file patch per untracked file appended after it.
|
|
96
|
+
*/
|
|
97
|
+
export async function capturePatch(cwd, baseSha) {
|
|
98
|
+
const parts = [];
|
|
99
|
+
if (baseSha) {
|
|
100
|
+
const r = await exec('git', ['-C', cwd, 'diff', '--unified=3', '--no-renames', baseSha, '--'], {
|
|
101
|
+
timeoutMs: GIT_TIMEOUT_MS,
|
|
102
|
+
});
|
|
103
|
+
if (r.code === 0 && r.out.trim())
|
|
104
|
+
parts.push(r.out.replace(/\n*$/, '\n'));
|
|
105
|
+
}
|
|
106
|
+
for (const file of await untrackedFiles(cwd)) {
|
|
107
|
+
const r = await exec('git', ['-C', cwd, 'diff', '--no-index', '--unified=3', '/dev/null', file], {
|
|
108
|
+
cwd,
|
|
109
|
+
timeoutMs: GIT_TIMEOUT_MS,
|
|
110
|
+
});
|
|
111
|
+
// Exit 1 = "differences found", the expected outcome for a new file.
|
|
112
|
+
if ((r.code === 0 || r.code === 1) && r.out.trim())
|
|
113
|
+
parts.push(r.out.replace(/\n*$/, '\n'));
|
|
114
|
+
}
|
|
115
|
+
return parts.join('');
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* A digest of the working tree's current *content*.
|
|
119
|
+
*
|
|
120
|
+
* Evidence describes the tree as it was when the checks ran. If anything moves
|
|
121
|
+
* afterwards the bundle still exists but no longer describes what is there, and
|
|
122
|
+
* a run that keeps reporting `verified` on the strength of it is claiming
|
|
123
|
+
* something nobody measured. Comparing this before and after is how the kernel
|
|
124
|
+
* notices.
|
|
125
|
+
*
|
|
126
|
+
* The first version of this hashed `git rev-parse HEAD` plus
|
|
127
|
+
* `git status --porcelain`, which does not work: porcelain reports a file's
|
|
128
|
+
* *state*, not its bytes. A file that was already `M` before the verifier and
|
|
129
|
+
* is rewritten afterwards still prints ` M a.txt`, so the digest was unchanged
|
|
130
|
+
* and a stale verdict sailed through. The common case — an agent editing a file
|
|
131
|
+
* it had already edited — was exactly the case it missed.
|
|
132
|
+
*
|
|
133
|
+
* So this hashes content: HEAD, the full `git diff HEAD` patch (which covers
|
|
134
|
+
* staged and unstaged changes to tracked files), and the path plus bytes of
|
|
135
|
+
* every untracked file.
|
|
136
|
+
*
|
|
137
|
+
* Returns undefined outside a repo — a caller that cannot fingerprint must not
|
|
138
|
+
* assume the tree held still.
|
|
139
|
+
*/
|
|
140
|
+
export async function treeFingerprint(cwd) {
|
|
141
|
+
const status = await exec('git', ['-C', cwd, 'status', '--porcelain'], { timeoutMs: GIT_TIMEOUT_MS });
|
|
142
|
+
if (status.code !== 0)
|
|
143
|
+
return undefined;
|
|
144
|
+
const head = await exec('git', ['-C', cwd, 'rev-parse', 'HEAD'], { timeoutMs: GIT_TIMEOUT_MS });
|
|
145
|
+
const tracked = await exec('git', ['-C', cwd, 'diff', 'HEAD'], { timeoutMs: GIT_TIMEOUT_MS });
|
|
146
|
+
const hash = crypto.createHash('sha256');
|
|
147
|
+
hash.update(head.code === 0 ? head.out.trim() : 'no-head');
|
|
148
|
+
hash.update('\u0000');
|
|
149
|
+
hash.update(tracked.code === 0 ? tracked.out : status.out);
|
|
150
|
+
// Untracked files are invisible to `git diff`, so hash them by hand — path and
|
|
151
|
+
// bytes both, so a rename and an edit are each detected.
|
|
152
|
+
for (const file of (await untrackedFiles(cwd)).sort()) {
|
|
153
|
+
hash.update('\u0000');
|
|
154
|
+
hash.update(file);
|
|
155
|
+
try {
|
|
156
|
+
hash.update(fs.readFileSync(path.join(cwd, file)));
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
// Unreadable (a dangling symlink, a race with a delete): record that we
|
|
160
|
+
// could not read it rather than silently treating it as empty.
|
|
161
|
+
hash.update('\u0000unreadable');
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return hash.digest('hex');
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* True when `file` sits under `prefix`.
|
|
168
|
+
*
|
|
169
|
+
* Segment-aware, so `srcfoo/a.ts` is not under `src`. `.` and `''` both mean the
|
|
170
|
+
* repo root and match everything — without that, a policy of `requirePaths: ['.']`
|
|
171
|
+
* ("the run must change something") reports that nothing changed no matter what
|
|
172
|
+
* the run did.
|
|
173
|
+
*/
|
|
174
|
+
export function isUnder(file, prefix) {
|
|
175
|
+
const norm = (p) => p.replace(/\\/g, '/').replace(/^\.\//, '').replace(/\/+$/, '').replace(/^\.$/, '');
|
|
176
|
+
const f = norm(file);
|
|
177
|
+
const p = norm(prefix);
|
|
178
|
+
if (p === '')
|
|
179
|
+
return true;
|
|
180
|
+
return f === p || f.startsWith(p + '/');
|
|
181
|
+
}
|
|
182
|
+
/** Resolve a check-relative path against the run cwd. */
|
|
183
|
+
export function resolveIn(cwd, p) {
|
|
184
|
+
return path.isAbsolute(p) ? p : path.join(cwd, p);
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=baseline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baseline.js","sourceRoot":"","sources":["../../../src/verify/baseline.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Acceptance contracts — what the runtime itself checks before calling a run done.
|
|
3
|
+
*
|
|
4
|
+
* Every completion signal that existed before this module was the agent grading
|
|
5
|
+
* its own work: council terminated on a regex over agent prose (`consensus.ts`),
|
|
6
|
+
* autoloop's `eval_output` was whatever the Coder passed to a tool call
|
|
7
|
+
* (`autoloop/agent-tools.ts`), ultraapp's frontend gate was a sentence in a
|
|
8
|
+
* persona string (`ultraapp/council-adapter.ts`). A contract is the opposite:
|
|
9
|
+
* a caller-declared list of checks that *we* execute and whose exit codes we read.
|
|
10
|
+
*
|
|
11
|
+
* Provenance rule, and the reason `normalizeContract` exists: a contract may
|
|
12
|
+
* come from the caller or from a mode default, never from agent output. If an
|
|
13
|
+
* agent could declare its own checks we would be back to self-grading with extra
|
|
14
|
+
* steps. Nothing in the kernel reads a contract out of a node's result.
|
|
15
|
+
*/
|
|
16
|
+
/** Argv form only — there is no shell string anywhere, so there is nothing to inject into. */
|
|
17
|
+
export interface CommandCheck {
|
|
18
|
+
type: 'command';
|
|
19
|
+
cmd: string;
|
|
20
|
+
args?: string[];
|
|
21
|
+
/** Relative to the run cwd when not absolute. */
|
|
22
|
+
cwd?: string;
|
|
23
|
+
timeoutMs?: number;
|
|
24
|
+
/** Exit code that counts as pass. Default 0. */
|
|
25
|
+
expectExit?: number;
|
|
26
|
+
}
|
|
27
|
+
export interface HttpCheck {
|
|
28
|
+
type: 'http';
|
|
29
|
+
url: string;
|
|
30
|
+
/** Default 200. */
|
|
31
|
+
expectStatus?: number;
|
|
32
|
+
/** Total time to keep retrying before failing. Default 30s. */
|
|
33
|
+
timeoutMs?: number;
|
|
34
|
+
intervalMs?: number;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Captures and stores a screenshot; it does not judge pixels. The judgement is
|
|
38
|
+
* still a human's or an agent's, but whether the capture actually happened is
|
|
39
|
+
* now decided by the runtime instead of by an agent's self-report. Do not
|
|
40
|
+
* describe this as visual regression — it is evidence collection.
|
|
41
|
+
*/
|
|
42
|
+
export interface ScreenshotCheck {
|
|
43
|
+
type: 'screenshot';
|
|
44
|
+
url: string;
|
|
45
|
+
viewports: Array<{
|
|
46
|
+
width: number;
|
|
47
|
+
height: number;
|
|
48
|
+
label?: string;
|
|
49
|
+
}>;
|
|
50
|
+
/** Chrome/Chromium binary; resolved from the usual locations when omitted. */
|
|
51
|
+
browserBin?: string;
|
|
52
|
+
timeoutMs?: number;
|
|
53
|
+
}
|
|
54
|
+
export interface DiffPolicyCheck {
|
|
55
|
+
type: 'diff_policy';
|
|
56
|
+
maxFiles?: number;
|
|
57
|
+
/** Glob-ish prefixes that must not be touched. */
|
|
58
|
+
forbidPaths?: string[];
|
|
59
|
+
/** At least one changed file must sit under one of these. */
|
|
60
|
+
requirePaths?: string[];
|
|
61
|
+
}
|
|
62
|
+
export interface FileCheck {
|
|
63
|
+
type: 'file';
|
|
64
|
+
path: string;
|
|
65
|
+
/** Default true. `false` asserts absence. */
|
|
66
|
+
exists?: boolean;
|
|
67
|
+
/** Applied to file contents when present. */
|
|
68
|
+
matches?: string;
|
|
69
|
+
}
|
|
70
|
+
export type CheckSpec = CommandCheck | HttpCheck | ScreenshotCheck | DiffPolicyCheck | FileCheck;
|
|
71
|
+
export interface ContractCheck {
|
|
72
|
+
/** Stable id used in the evidence bundle. Generated when omitted. */
|
|
73
|
+
id?: string;
|
|
74
|
+
spec: CheckSpec;
|
|
75
|
+
/**
|
|
76
|
+
* A failing non-required check is recorded in the bundle but does not refute
|
|
77
|
+
* the run. The predecessor field (`fix-on-failure.ts` `steps[].required`) was
|
|
78
|
+
* declared but never read; here it is honoured.
|
|
79
|
+
*/
|
|
80
|
+
required?: boolean;
|
|
81
|
+
}
|
|
82
|
+
export interface AcceptanceContract {
|
|
83
|
+
id?: string;
|
|
84
|
+
checks: ContractCheck[];
|
|
85
|
+
/**
|
|
86
|
+
* On red, spawn a fixer session and re-run the whole check list, up to N times.
|
|
87
|
+
* Ported from ultraapp's fix-on-failure loop. 0 disables.
|
|
88
|
+
*/
|
|
89
|
+
fixOnFailureRounds?: number;
|
|
90
|
+
}
|
|
91
|
+
export interface CheckResult {
|
|
92
|
+
id: string;
|
|
93
|
+
type: CheckSpec['type'];
|
|
94
|
+
required: boolean;
|
|
95
|
+
passed: boolean;
|
|
96
|
+
durationMs: number;
|
|
97
|
+
/** Human-readable one-liner: the command that failed, the status we got, etc. */
|
|
98
|
+
detail: string;
|
|
99
|
+
/** Tail of captured output, byte-capped. */
|
|
100
|
+
tail?: string;
|
|
101
|
+
/** Paths (relative to the evidence dir) of files this check produced. */
|
|
102
|
+
artifacts?: string[];
|
|
103
|
+
/** Set when the check was killed by its own timeout rather than exiting. */
|
|
104
|
+
timedOut?: boolean;
|
|
105
|
+
}
|
|
106
|
+
export declare const DEFAULT_CHECK_TIMEOUT_MS: number;
|
|
107
|
+
export declare const DEFAULT_HTTP_TIMEOUT_MS = 30000;
|
|
108
|
+
export declare const DEFAULT_SCREENSHOT_TIMEOUT_MS = 60000;
|
|
109
|
+
/**
|
|
110
|
+
* Parse an untrusted contract, dropping anything unrecognised. Returns undefined
|
|
111
|
+
* when nothing usable survives, so a malformed contract fails loudly at declare
|
|
112
|
+
* time rather than silently degrading to "no checks" at completion time.
|
|
113
|
+
*/
|
|
114
|
+
export declare function normalizeContract(raw: unknown): AcceptanceContract | undefined;
|
|
115
|
+
/** True when every required check passed. Non-required failures are recorded, not fatal. */
|
|
116
|
+
export declare function contractPassed(results: CheckResult[]): boolean;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Acceptance contracts — what the runtime itself checks before calling a run done.
|
|
3
|
+
*
|
|
4
|
+
* Every completion signal that existed before this module was the agent grading
|
|
5
|
+
* its own work: council terminated on a regex over agent prose (`consensus.ts`),
|
|
6
|
+
* autoloop's `eval_output` was whatever the Coder passed to a tool call
|
|
7
|
+
* (`autoloop/agent-tools.ts`), ultraapp's frontend gate was a sentence in a
|
|
8
|
+
* persona string (`ultraapp/council-adapter.ts`). A contract is the opposite:
|
|
9
|
+
* a caller-declared list of checks that *we* execute and whose exit codes we read.
|
|
10
|
+
*
|
|
11
|
+
* Provenance rule, and the reason `normalizeContract` exists: a contract may
|
|
12
|
+
* come from the caller or from a mode default, never from agent output. If an
|
|
13
|
+
* agent could declare its own checks we would be back to self-grading with extra
|
|
14
|
+
* steps. Nothing in the kernel reads a contract out of a node's result.
|
|
15
|
+
*/
|
|
16
|
+
export const DEFAULT_CHECK_TIMEOUT_MS = 10 * 60_000;
|
|
17
|
+
export const DEFAULT_HTTP_TIMEOUT_MS = 30_000;
|
|
18
|
+
export const DEFAULT_SCREENSHOT_TIMEOUT_MS = 60_000;
|
|
19
|
+
// ─── Normalization ──────────────────────────────────────────────────────────
|
|
20
|
+
function str(v) {
|
|
21
|
+
return typeof v === 'string' && v.trim() !== '' ? v : undefined;
|
|
22
|
+
}
|
|
23
|
+
function num(v) {
|
|
24
|
+
return typeof v === 'number' && Number.isFinite(v) ? v : undefined;
|
|
25
|
+
}
|
|
26
|
+
function strArray(v) {
|
|
27
|
+
if (!Array.isArray(v))
|
|
28
|
+
return undefined;
|
|
29
|
+
const out = v.filter((x) => typeof x === 'string');
|
|
30
|
+
return out.length === v.length ? out : undefined;
|
|
31
|
+
}
|
|
32
|
+
function normalizeSpec(raw) {
|
|
33
|
+
if (!raw || typeof raw !== 'object')
|
|
34
|
+
return undefined;
|
|
35
|
+
const r = raw;
|
|
36
|
+
switch (r.type) {
|
|
37
|
+
case 'command': {
|
|
38
|
+
const cmd = str(r.cmd);
|
|
39
|
+
if (!cmd)
|
|
40
|
+
return undefined;
|
|
41
|
+
const args = strArray(r.args) ?? [];
|
|
42
|
+
return {
|
|
43
|
+
type: 'command',
|
|
44
|
+
cmd,
|
|
45
|
+
args,
|
|
46
|
+
cwd: str(r.cwd),
|
|
47
|
+
timeoutMs: num(r.timeoutMs),
|
|
48
|
+
expectExit: num(r.expectExit),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
case 'http': {
|
|
52
|
+
const url = str(r.url);
|
|
53
|
+
if (!url)
|
|
54
|
+
return undefined;
|
|
55
|
+
return {
|
|
56
|
+
type: 'http',
|
|
57
|
+
url,
|
|
58
|
+
expectStatus: num(r.expectStatus),
|
|
59
|
+
timeoutMs: num(r.timeoutMs),
|
|
60
|
+
intervalMs: num(r.intervalMs),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
case 'screenshot': {
|
|
64
|
+
const url = str(r.url);
|
|
65
|
+
if (!url || !Array.isArray(r.viewports) || r.viewports.length === 0)
|
|
66
|
+
return undefined;
|
|
67
|
+
const viewports = [];
|
|
68
|
+
for (const v of r.viewports) {
|
|
69
|
+
if (!v || typeof v !== 'object')
|
|
70
|
+
return undefined;
|
|
71
|
+
const vv = v;
|
|
72
|
+
const width = num(vv.width);
|
|
73
|
+
const height = num(vv.height);
|
|
74
|
+
if (!width || !height)
|
|
75
|
+
return undefined;
|
|
76
|
+
viewports.push({ width, height, label: str(vv.label) });
|
|
77
|
+
}
|
|
78
|
+
return { type: 'screenshot', url, viewports, browserBin: str(r.browserBin), timeoutMs: num(r.timeoutMs) };
|
|
79
|
+
}
|
|
80
|
+
case 'diff_policy':
|
|
81
|
+
return {
|
|
82
|
+
type: 'diff_policy',
|
|
83
|
+
maxFiles: num(r.maxFiles),
|
|
84
|
+
forbidPaths: strArray(r.forbidPaths),
|
|
85
|
+
requirePaths: strArray(r.requirePaths),
|
|
86
|
+
};
|
|
87
|
+
case 'file': {
|
|
88
|
+
const p = str(r.path);
|
|
89
|
+
if (!p)
|
|
90
|
+
return undefined;
|
|
91
|
+
return {
|
|
92
|
+
type: 'file',
|
|
93
|
+
path: p,
|
|
94
|
+
exists: typeof r.exists === 'boolean' ? r.exists : true,
|
|
95
|
+
matches: str(r.matches),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
default:
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Parse an untrusted contract, dropping anything unrecognised. Returns undefined
|
|
104
|
+
* when nothing usable survives, so a malformed contract fails loudly at declare
|
|
105
|
+
* time rather than silently degrading to "no checks" at completion time.
|
|
106
|
+
*/
|
|
107
|
+
export function normalizeContract(raw) {
|
|
108
|
+
if (!raw || typeof raw !== 'object')
|
|
109
|
+
return undefined;
|
|
110
|
+
const r = raw;
|
|
111
|
+
const rawChecks = Array.isArray(r.checks) ? r.checks : undefined;
|
|
112
|
+
if (!rawChecks)
|
|
113
|
+
return undefined;
|
|
114
|
+
const checks = [];
|
|
115
|
+
for (let i = 0; i < rawChecks.length; i++) {
|
|
116
|
+
const entry = rawChecks[i];
|
|
117
|
+
if (!entry || typeof entry !== 'object')
|
|
118
|
+
continue;
|
|
119
|
+
// Accept both `{spec: {...}}` and a bare check spec, since hand-written
|
|
120
|
+
// contracts in tool calls tend to omit the wrapper.
|
|
121
|
+
const spec = normalizeSpec('spec' in entry ? entry.spec : entry);
|
|
122
|
+
if (!spec)
|
|
123
|
+
continue;
|
|
124
|
+
checks.push({
|
|
125
|
+
id: str(entry.id) ?? `${spec.type}-${i + 1}`,
|
|
126
|
+
spec,
|
|
127
|
+
required: entry.required === false ? false : true,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
if (checks.length === 0)
|
|
131
|
+
return undefined;
|
|
132
|
+
return {
|
|
133
|
+
id: str(r.id),
|
|
134
|
+
checks,
|
|
135
|
+
fixOnFailureRounds: num(r.fixOnFailureRounds),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/** True when every required check passed. Non-required failures are recorded, not fatal. */
|
|
139
|
+
export function contractPassed(results) {
|
|
140
|
+
return results.every((r) => r.passed || !r.required);
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../../../src/verify/contract.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evidence bundles — the machine-readable record of what was checked.
|
|
3
|
+
*
|
|
4
|
+
* A run's claim to be finished is only as good as what can be re-read after the
|
|
5
|
+
* process that made it is gone. So every verifier attempt writes a directory:
|
|
6
|
+
* the verdict and per-check results as JSON, the failure tails as plain text,
|
|
7
|
+
* whatever files the checks produced (screenshots), and the patch the run
|
|
8
|
+
* actually made, against the base commit recorded when it started.
|
|
9
|
+
*/
|
|
10
|
+
import type { Logger } from '../logger.js';
|
|
11
|
+
import { type ChangedFile } from './baseline.js';
|
|
12
|
+
import { type CheckResult } from './contract.js';
|
|
13
|
+
export interface EvidenceBundle {
|
|
14
|
+
evidenceId: string;
|
|
15
|
+
runId: string;
|
|
16
|
+
/** Node that produced it, or `'run'` for a standalone `verify_run`. */
|
|
17
|
+
node: string;
|
|
18
|
+
contractId?: string;
|
|
19
|
+
createdAt: string;
|
|
20
|
+
cwd: string;
|
|
21
|
+
baseSha?: string;
|
|
22
|
+
headSha?: string;
|
|
23
|
+
passed: boolean;
|
|
24
|
+
/** Fix-on-red rounds consumed before this verdict. */
|
|
25
|
+
rounds: number;
|
|
26
|
+
results: CheckResult[];
|
|
27
|
+
changedFiles: ChangedFile[];
|
|
28
|
+
/**
|
|
29
|
+
* Digest of the working tree at the moment the checks finished. The kernel
|
|
30
|
+
* re-computes it when the run ends: if it moved, this bundle describes an
|
|
31
|
+
* earlier tree and the verdict no longer applies to what is on disk.
|
|
32
|
+
* Undefined outside a git repo, where we cannot tell.
|
|
33
|
+
*/
|
|
34
|
+
treeFingerprint?: string;
|
|
35
|
+
}
|
|
36
|
+
export declare function evidenceRoot(runDir: string): string;
|
|
37
|
+
export declare function evidenceDir(runDir: string, evidenceId: string): string;
|
|
38
|
+
/** `<node>-<attempt>` — stable, sortable, and readable in a ledger row. */
|
|
39
|
+
export declare function makeEvidenceId(node: string, attempt: number): string;
|
|
40
|
+
export interface WriteEvidenceArgs {
|
|
41
|
+
runDir: string;
|
|
42
|
+
runId: string;
|
|
43
|
+
node: string;
|
|
44
|
+
evidenceId: string;
|
|
45
|
+
cwd: string;
|
|
46
|
+
baseSha?: string;
|
|
47
|
+
contractId?: string;
|
|
48
|
+
results: CheckResult[];
|
|
49
|
+
rounds: number;
|
|
50
|
+
logger?: Logger;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Write the bundle. Best-effort like the run ledger: a bundle that fails to land
|
|
54
|
+
* must not change the verdict it is describing — the verdict is already decided
|
|
55
|
+
* by `results`.
|
|
56
|
+
*/
|
|
57
|
+
export declare function writeEvidence(args: WriteEvidenceArgs): Promise<EvidenceBundle>;
|
|
58
|
+
export declare function readEvidence(runDir: string, evidenceId: string): EvidenceBundle | undefined;
|
|
59
|
+
export declare function listEvidence(runDir: string): string[];
|
|
60
|
+
/** Render a bundle for the CLI. Pure — no I/O. */
|
|
61
|
+
export declare function formatEvidence(bundle: EvidenceBundle): string;
|