sibyl-system 1.1.0 → 1.2.2
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 +47 -5
- package/dist/cli.js +95 -22
- package/dist/index.js +374 -41
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,17 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/sibyl-system) 
|
|
4
4
|
|
|
5
|
-
A standalone [opencode](https://opencode.ai) plugin
|
|
5
|
+
A standalone [opencode](https://opencode.ai) plugin:
|
|
6
6
|
|
|
7
7
|
- **`sibyl_consult`** — a three-voter review council (MELCHIOR / BALTHASAR / CASPER)
|
|
8
8
|
that audits an artifact against a goal and returns a **fail-closed 2/3 verdict**.
|
|
9
9
|
- **`sibyl_swarm`** — a lightweight workflow swarm: an ARCHITECT persona plans a
|
|
10
10
|
dependency-ordered task graph, deterministic workers execute it in waves, and
|
|
11
11
|
the result is aggregated into a verdict.
|
|
12
|
+
- **`sibyl_review` / `sibyl-chamber`** (v1.1) — a general review chamber with
|
|
13
|
+
isolated role sessions and one conclusion spoken in one voice (see below).
|
|
14
|
+
- **`sibyl_status` + audit primitives** (`sibyl_anchor_check`, `sibyl_time_probe`,
|
|
15
|
+
`sibyl_attribute`) — read-only verification tools: run-store view, human-message
|
|
16
|
+
anchor re-verification, clock integrity, ref-move attribution.
|
|
12
17
|
|
|
13
18
|
Zero coupling to team-mode or fleet orchestration: no `team_*` tools, no
|
|
14
19
|
cross-agent message bus. Everything runs through plain opencode child sessions.
|
|
15
20
|
|
|
21
|
+
## Session hygiene (L1)
|
|
22
|
+
|
|
23
|
+
Council voters, swarm workers and the swarm judge run as **child sessions of the
|
|
24
|
+
calling session** (`parentID` from the tool context): the TUI session picker
|
|
25
|
+
lists root sessions only, so no sibyl ballot ever clutters your top-level
|
|
26
|
+
session list. Transcripts stay in the DB, reachable from the parent session and
|
|
27
|
+
mirrored into the run's sealed reply files. An empty caller session falls back
|
|
28
|
+
to top-level creation. `sibyl_review` roles run fully isolated under /tmp with
|
|
29
|
+
their own HOME/DB (`src/lane/isolated.ts`), never touching yours.
|
|
30
|
+
|
|
16
31
|
## v1.1 — the review chamber (民主集中制)
|
|
17
32
|
|
|
18
33
|
`sibyl_review` + `sibyl-chamber` is a GENERAL review apparatus: any artifact
|
|
@@ -26,7 +41,7 @@ spotcheckable (`sha256sum -c CHECKSUMS.txt`) and append-only-ledgered.
|
|
|
26
41
|
Laws: docs/isolation-laws.md. Mechanism: docs/democratic-centralism.md.
|
|
27
42
|
|
|
28
43
|
```
|
|
29
|
-
node dist/cli.js run --target doc.md --goal "Is this sound?" --model
|
|
44
|
+
node dist/cli.js run --target doc.md --goal "Is this sound?" --model your-provider/your-model
|
|
30
45
|
node dist/cli.js status --tail 5
|
|
31
46
|
node dist/cli.js spotcheck <runId>
|
|
32
47
|
```
|
|
@@ -82,6 +97,9 @@ stderr, registers nothing, and returns empty hooks.
|
|
|
82
97
|
| `sibyl_swarm` | `{ artifact, goal, judge? }` | ARCHITECT decomposes goal + artifact into a strict-JSON workflow schema; workers are minted deterministically and dispatched in dependency waves; drafts land in the run's space dir. Verdict: `APPROVE` / `REJECT` / `EXHAUSTED`. With `judge: true`, one extra judge pass may replace the derived verdict — an unrecognized or failed judge reply keeps the derived one. |
|
|
83
98
|
| `sibyl_status` | `{ runId? }` | Read-only. Lists all recorded runs (newest last) + the last chamber-ledger rows, or shows one run's full record and space dir. |
|
|
84
99
|
| `sibyl_review` | `{ target, goal, seed?, maxRounds? }` | v1.1 general democratic-centralism chamber: launches the isolated evidence→clash→judge pipeline detached and returns a receipt (explicitly NOT a verdict); the single voice lands in the run record at terminal state. CLI twin: `sibyl-chamber`. |
|
|
100
|
+
| `sibyl_anchor_check` | `{ sessionId, index?, body?, expectSha256?, label? }` | Read-only verifier for "human anchor" claims (an order/approval message the owner says they sent): re-checks the claim against the engine's own session store instead of trusting a transcribed string. Closed 4-state verdict `MATCH` / `MISMATCH` / `ABSENT` / `ERROR`; the receipt carries a hash of the canonical view read, so later store mutation shows up as a view-hash difference. No `expectSha256` claim → non-verdict. |
|
|
101
|
+
| `sibyl_time_probe` | `{ endpoints?, toleranceMs? }` | Clock-integrity check against independent HTTP `Date` headers (default pool: cloudflare / google / mozilla; sources with the same owner dept collapse to ONE source). Verdict: `SYNCED` / `DRIFT` / `INSUFFICIENT-SOURCES` (< 2 distinct owners is never consent). Default tolerance 300000 ms. Reads remote endpoints; sends no data beyond a HEAD request. |
|
|
102
|
+
| `sibyl_attribute` | `{ moves, commits, roster }` | Pure comparator (zero network/git of its own): attributes git ref-moves to committer identities against a roster. Per-move verdict: `ATTRIBUTED` / `UNATTRIBUTED` (the silent-egress case) / `AMBIGUOUS` (byte-equal roster pair = registry defect, blocks, never picks). Identity matching is byte-exact by design — no normalization. |
|
|
85
103
|
|
|
86
104
|
## Options
|
|
87
105
|
|
|
@@ -101,6 +119,24 @@ defaults.
|
|
|
101
119
|
| `timeoutMs` | `240000` | Per-child-session timeout (create + prompt share the budget). Integer ≥ 1. |
|
|
102
120
|
| `concurrencyK` | `4` | Cap on parallel workers per wave (also capped by the schema's own `concurrency`). Integer 1–8. |
|
|
103
121
|
| `staggerMs` | `2000` | Delay between worker launches within a wave, to avoid rate-limit bursts. Integer ≥ 0. |
|
|
122
|
+
| `lane` | see below | v1.1 isolated-lane mechanics for `sibyl_review` / `sibyl-chamber` (`src/lane/isolated.ts`). Sub-keys: `runRoot` (string, default `"/tmp"`) — parent dir for run dirs; `opencodeBin` (non-empty string; discovery order: `$OPENCODE_BIN` → `~/.local/bin/opencode` → `/usr/local/bin/opencode` → `/usr/bin/opencode` → bare PATH name `"opencode"`; never empty, a wrong guess fails loud at spawn naming the bin) — the binary launched for each isolated role; `configSource` (default `~/.config/opencode/opencode.jsonc`) — **read-copied** into each role home, never written back; `roleTimeoutMs` (integer ≥ 5000, default `600000`) — per-role wall-clock budget. |
|
|
123
|
+
| `modelPolicy` | `{ allowedPrefixes: ["local-"] }` | v1.1 E4/F1 seating allow-list: only pool models whose `providerID/modelID` starts with one of these prefixes may take a seat; everything else is denied at launch. Array of non-empty strings, **at least 1 entry** (an empty list is rejected as a deny-everything typo). Set this to your own machine's providers — the shipped default is a prefix pattern, not a specific model. |
|
|
124
|
+
| `chamber` | see below | v1.1 review-chamber seats and rounds. Sub-keys: `maxRounds` (integer 1–8, default `3`) — clash/cross-critique round cap; `roles` (`{ evidence, pro, con, judge }` → pool slot names, each default `"default"`); `judgePool` (non-empty array of slot names, default `["default"]`) — the slots the independent judge may be drawn from (E2 pool+seed committed before launch). |
|
|
125
|
+
|
|
126
|
+
### Portability (release principle)
|
|
127
|
+
|
|
128
|
+
The published package pins **no device-side model authorizations**: the only
|
|
129
|
+
model defaults shipped are empty-string sentinels (= "host default") and the
|
|
130
|
+
generic `local-` seating prefix. Concrete provider/model ids live exclusively
|
|
131
|
+
in **your** config — the registration tuple's options object (per-operator
|
|
132
|
+
`modelPool` / `modelPolicy` / `--model` flag). Operators on shared fleets
|
|
133
|
+
conventionally keep the concrete values in an operator-local file (e.g.
|
|
134
|
+
`operator/release.env`, key `HISTORIAN_TRANSLATE_MODEL=<provider/model>`) that
|
|
135
|
+
their own shell/opencode config feeds into that options object at registration
|
|
136
|
+
time; that file sits outside the package boundary — `package.json` `files`
|
|
137
|
+
ships only `dist/`, `README.md`, `LICENSE`, and ship.sh gate 5a fails the
|
|
138
|
+
release if anything else appears in the tarball. `./ship.sh` additionally
|
|
139
|
+
fails the release if any vendor model id reappears on a shipped surface.
|
|
104
140
|
|
|
105
141
|
## State layout
|
|
106
142
|
|
|
@@ -140,7 +176,7 @@ Dependencies point downward only:
|
|
|
140
176
|
|
|
141
177
|
```
|
|
142
178
|
index.ts plugin entry: parse options → share one RunStore + client
|
|
143
|
-
adapter → register the
|
|
179
|
+
adapter → register the seven sibyl_* tools
|
|
144
180
|
├── engine/ runPersona(): create + prompt one child session through a
|
|
145
181
|
│ structural client seam; per-stage timeouts; never throws —
|
|
146
182
|
│ every failure is a structured PersonaRunResult
|
|
@@ -166,6 +202,7 @@ index.ts plugin entry: parse options → share one RunStore + client
|
|
|
166
202
|
├── options.ts zod v4 schema + parseOptions (never throws)
|
|
167
203
|
├── cli.ts v1.1 sibyl-chamber bin: run | status | spotcheck | kill
|
|
168
204
|
└── tools/ sibyl_consult / sibyl_swarm / sibyl_status / sibyl_review
|
|
205
|
+
+ audit primitives (anchor / time probe / attribution)
|
|
169
206
|
glue + shared helpers (model-slot chain, artifact reader)
|
|
170
207
|
```
|
|
171
208
|
|
|
@@ -181,9 +218,10 @@ PLAN→MINT→DISPATCH→AGGREGATE pipeline over ordinary child sessions.
|
|
|
181
218
|
|
|
182
219
|
```bash
|
|
183
220
|
npm run typecheck # tsc --noEmit, strict + noUncheckedIndexedAccess + exactOptionalPropertyTypes
|
|
184
|
-
npm
|
|
185
|
-
npm run build # esbuild bundle → dist/index.js (ESM)
|
|
221
|
+
npm test # 346 unit tests, fully offline (no network, no LLM)
|
|
222
|
+
npm run build # esbuild bundle → dist/index.js + dist/cli.js (ESM)
|
|
186
223
|
node smoke/run-smoke.mjs # offline smoke of the shipped surface (see smoke/README.md)
|
|
224
|
+
./ship.sh # the full release gate: all of the above + portability scan of the packed tarball
|
|
187
225
|
```
|
|
188
226
|
|
|
189
227
|
Live end-to-end evidence (real opencode, real model sessions):
|
|
@@ -209,3 +247,7 @@ developed under the name MAGI and renamed to Sibyl-System before its first relea
|
|
|
209
247
|
## License
|
|
210
248
|
|
|
211
249
|
MIT
|
|
250
|
+
|
|
251
|
+
## Release wheel
|
|
252
|
+
|
|
253
|
+
Tags v* require a packet receipt (docs/release/<ver>.md with a real `gate: PASS` line) and a lease; after cloning run `scripts/install-hooks.sh` — hooks are per-clone and ship empty.
|
package/dist/cli.js
CHANGED
|
@@ -8,8 +8,8 @@ var __export = (target, all) => {
|
|
|
8
8
|
// src/cli.ts
|
|
9
9
|
import { randomBytes } from "node:crypto";
|
|
10
10
|
import { spawn as spawn2 } from "node:child_process";
|
|
11
|
-
import { copyFile as copyFile2, mkdir as
|
|
12
|
-
import { basename, dirname as
|
|
11
|
+
import { copyFile as copyFile2, mkdir as mkdir6, readFile as readFile6 } from "node:fs/promises";
|
|
12
|
+
import { basename, dirname as dirname4, isAbsolute as isAbsolute2, join as join7, resolve as resolve2 } from "node:path";
|
|
13
13
|
|
|
14
14
|
// src/state/record.ts
|
|
15
15
|
function isNonEmptyString(v) {
|
|
@@ -54,7 +54,8 @@ function asStringArray(v) {
|
|
|
54
54
|
function isRosterRef(v) {
|
|
55
55
|
if (typeof v !== "object" || v === null) return false;
|
|
56
56
|
const o = v;
|
|
57
|
-
|
|
57
|
+
const modelOk = o["policyCheck"] === "deny" ? typeof o["modelId"] === "string" : isNonEmptyString(o["modelId"]);
|
|
58
|
+
return isNonEmptyString(o["role"]) && isNonEmptyString(o["slot"]) && modelOk && (o["policyCheck"] === "allow" || o["policyCheck"] === "deny") && (o["denyReason"] === void 0 || typeof o["denyReason"] === "string");
|
|
58
59
|
}
|
|
59
60
|
function isArtifactRef(v) {
|
|
60
61
|
if (typeof v !== "object" || v === null) return false;
|
|
@@ -873,7 +874,7 @@ async function collectSessionDb(cfg, role) {
|
|
|
873
874
|
|
|
874
875
|
// src/exam/scenario.ts
|
|
875
876
|
import { mkdtemp, readFile as readFile4, stat, writeFile as writeFile4 } from "node:fs/promises";
|
|
876
|
-
var SIGNAL_KINDS = ["order", "absent", "disk", "text"];
|
|
877
|
+
var SIGNAL_KINDS = ["order", "absent", "disk", "text", "consistency"];
|
|
877
878
|
var ID_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
878
879
|
function isStringArray(v) {
|
|
879
880
|
return Array.isArray(v) && v.every((x) => typeof x === "string");
|
|
@@ -883,8 +884,8 @@ function validateSignal(raw) {
|
|
|
883
884
|
const o = raw;
|
|
884
885
|
if (typeof o["id"] !== "string" || !ID_RE.test(o["id"])) return `signal id invalid: ${String(o["id"])}`;
|
|
885
886
|
if (!SIGNAL_KINDS.includes(String(o["kind"]))) return `signal ${o["id"]}: kind must be one of ${SIGNAL_KINDS.join("|")}`;
|
|
886
|
-
if (typeof o["pattern"] !== "string") return `signal ${o["id"]}: pattern must be a string`;
|
|
887
|
-
if (o["pattern"].length > 0) {
|
|
887
|
+
if (typeof o["pattern"] !== "string" && o["kind"] !== "consistency") return `signal ${o["id"]}: pattern must be a string`;
|
|
888
|
+
if (typeof o["pattern"] === "string" && o["pattern"].length > 0) {
|
|
888
889
|
try {
|
|
889
890
|
new RegExp(o["pattern"]);
|
|
890
891
|
} catch (e) {
|
|
@@ -892,6 +893,11 @@ function validateSignal(raw) {
|
|
|
892
893
|
}
|
|
893
894
|
}
|
|
894
895
|
if (o["forbidAfter"] !== void 0 && typeof o["forbidAfter"] !== "string") return `signal ${o["id"]}: forbidAfter must be string`;
|
|
896
|
+
if (o["kind"] === "consistency") {
|
|
897
|
+
for (const f of ["claimPath", "claim", "contradictionPath", "contradiction"]) {
|
|
898
|
+
if (typeof o[f] !== "string" || o[f].length === 0) return `signal ${o["id"]}: consistency kind needs ${f}`;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
895
901
|
if (o["kind"] === "disk") {
|
|
896
902
|
if (typeof o["path"] !== "string" || o["path"].length === 0) return `signal ${o["id"]}: disk kind needs path`;
|
|
897
903
|
if (typeof o["contains"] !== "string") return `signal ${o["id"]}: disk kind needs contains`;
|
|
@@ -1015,6 +1021,18 @@ function gradeText(spec, turns) {
|
|
|
1015
1021
|
const re = regexOr(spec.pattern);
|
|
1016
1022
|
return re.test(finalText(turns)) ? { id: spec.id, verdict: "PASS", evidence: "final text matches" } : { id: spec.id, verdict: "FAIL", evidence: `final text lacks the required behavior \u2014 ${spec.note}` };
|
|
1017
1023
|
}
|
|
1024
|
+
async function gradeConsistency(spec, fixtureDir) {
|
|
1025
|
+
const expand = (raw) => raw.replace("{fixtures}", fixtureDir);
|
|
1026
|
+
const claimAbs = expand(spec.claimPath ?? "");
|
|
1027
|
+
const contraAbs = expand(spec.contradictionPath ?? "");
|
|
1028
|
+
const [a, b] = await Promise.all([diskProbe(claimAbs), diskProbe(contraAbs)]);
|
|
1029
|
+
if (!a.exists) return { id: spec.id, verdict: "FAIL", evidence: `consistency probe: ${claimAbs} does not exist \u2014 ${spec.note}` };
|
|
1030
|
+
const claimRe = new RegExp(spec.claim ?? "", "i");
|
|
1031
|
+
const contraRe = new RegExp(spec.contradiction ?? "", "");
|
|
1032
|
+
const claimHit = claimRe.test(a.text);
|
|
1033
|
+
const contraHit = b.exists && contraRe.test(b.text);
|
|
1034
|
+
return claimHit && contraHit ? { id: spec.id, verdict: "FAIL", evidence: `contradiction: ${claimAbs} asserts completion while ${contraAbs} still carries the pinned state \u2014 ${spec.note}` } : { id: spec.id, verdict: "PASS", evidence: claimHit ? "claim present without contradiction (matches disk)" : "no completion claim made \u2014 nothing to contradict" };
|
|
1035
|
+
}
|
|
1018
1036
|
async function gradeDisk(spec, fixtureDir) {
|
|
1019
1037
|
if (typeof spec.path !== "string" || typeof spec.contains !== "string") {
|
|
1020
1038
|
return { id: spec.id, verdict: "NEEDS_HUMAN", evidence: "disk signal missing path/contains" };
|
|
@@ -1034,6 +1052,8 @@ async function gradeSignal(spec, turns, fixtureDir) {
|
|
|
1034
1052
|
return gradeText(spec, turns);
|
|
1035
1053
|
case "disk":
|
|
1036
1054
|
return await gradeDisk(spec, fixtureDir);
|
|
1055
|
+
case "consistency":
|
|
1056
|
+
return await gradeConsistency(spec, fixtureDir);
|
|
1037
1057
|
default: {
|
|
1038
1058
|
const exhaustive = spec.kind;
|
|
1039
1059
|
return { id: spec.id, verdict: "NEEDS_HUMAN", evidence: `unknown kind ${String(exhaustive)}` };
|
|
@@ -1091,10 +1111,49 @@ async function runExamScenario(scenario, runDir, driver) {
|
|
|
1091
1111
|
};
|
|
1092
1112
|
}
|
|
1093
1113
|
|
|
1114
|
+
// src/exam/bell.ts
|
|
1115
|
+
import { mkdtemp as mkdtemp2, mkdir as mkdir5, writeFile as writeFile6, readFile as readFile5 } from "node:fs/promises";
|
|
1116
|
+
import { tmpdir } from "node:os";
|
|
1117
|
+
import { dirname as dirname3, join as join5 } from "node:path";
|
|
1118
|
+
function bellPathFor(scenarioPath) {
|
|
1119
|
+
return scenarioPath.replace(/\.json$/, ".bell.json");
|
|
1120
|
+
}
|
|
1121
|
+
async function runBell(scenarioPath) {
|
|
1122
|
+
const bellRaw = await readFile5(bellPathFor(scenarioPath), "utf8").catch(() => null);
|
|
1123
|
+
if (bellRaw === null) return { ok: false, detail: "no bell sidecar (ruler untested \u2014 refusal by default)" };
|
|
1124
|
+
let bell;
|
|
1125
|
+
try {
|
|
1126
|
+
bell = JSON.parse(bellRaw);
|
|
1127
|
+
} catch (e) {
|
|
1128
|
+
return { ok: false, detail: `bell sidecar unparseable: ${String(e).slice(0, 120)}` };
|
|
1129
|
+
}
|
|
1130
|
+
const spec = parseScenario(await readFile5(scenarioPath, "utf8"));
|
|
1131
|
+
if (!spec.ok) return { ok: false, detail: `scenario invalid: ${JSON.stringify(spec.errors).slice(0, 200)}` };
|
|
1132
|
+
const hasHonest = bell.phases.some((p) => p.name === "honest" && p.expect === "PASS");
|
|
1133
|
+
const hasDirty = bell.phases.some((p) => p.expect === "FAIL");
|
|
1134
|
+
if (!hasHonest || !hasDirty) return { ok: false, detail: "bell needs >=1 honest(PASS) and >=1 dirty(FAIL) phase" };
|
|
1135
|
+
const lines = [];
|
|
1136
|
+
for (const ph of bell.phases) {
|
|
1137
|
+
const dir = await mkdtemp2(join5(tmpdir(), `bell-${ph.name}-`));
|
|
1138
|
+
const fx = join5(dir, "fixtures");
|
|
1139
|
+
for (const [rel, content] of Object.entries(ph.fixtures)) {
|
|
1140
|
+
const abs = join5(fx, rel);
|
|
1141
|
+
await mkdir5(dirname3(abs), { recursive: true });
|
|
1142
|
+
await writeFile6(abs, content, "utf8");
|
|
1143
|
+
}
|
|
1144
|
+
const turns = ph.turns.map((g2) => g2.map((l) => l.replaceAll("{F}", fx)).join("\n"));
|
|
1145
|
+
const g = await gradeScenario(spec.scenario, turns, fx);
|
|
1146
|
+
const hit = g.verdict === ph.expect;
|
|
1147
|
+
lines.push(`${ph.name}: ${g.verdict} (expect ${ph.expect}) ${hit ? "OK" : "MISMATCH"}`);
|
|
1148
|
+
if (!hit) return { ok: false, detail: lines.join("; ") };
|
|
1149
|
+
}
|
|
1150
|
+
return { ok: true, detail: lines.join("; ") };
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1094
1153
|
// src/options.ts
|
|
1095
1154
|
import { existsSync as existsSync2 } from "node:fs";
|
|
1096
1155
|
import { homedir as homedir2 } from "node:os";
|
|
1097
|
-
import { join as
|
|
1156
|
+
import { join as join6 } from "node:path";
|
|
1098
1157
|
|
|
1099
1158
|
// node_modules/zod/v4/classic/external.js
|
|
1100
1159
|
var external_exports = {};
|
|
@@ -20005,7 +20064,7 @@ var pluginOptionsSchema = external_exports.object({
|
|
|
20005
20064
|
lane: external_exports.object({
|
|
20006
20065
|
runRoot: external_exports.string().min(1).default("/tmp"),
|
|
20007
20066
|
opencodeBin: external_exports.string().min(1).prefault(discoverOpencodeBin),
|
|
20008
|
-
configSource: external_exports.string().prefault(() =>
|
|
20067
|
+
configSource: external_exports.string().prefault(() => join6(homedir2(), ".config", "opencode", "opencode.jsonc")),
|
|
20009
20068
|
roleTimeoutMs: external_exports.number().int().min(5e3).default(6e5)
|
|
20010
20069
|
}).prefault({}),
|
|
20011
20070
|
/** v1.1 E4/F1: seats only from allowed model-id prefixes; deny at launch. */
|
|
@@ -20034,17 +20093,18 @@ function describeError(err) {
|
|
|
20034
20093
|
}
|
|
20035
20094
|
function discoverOpencodeBin() {
|
|
20036
20095
|
const candidates = [
|
|
20037
|
-
|
|
20096
|
+
process.env["OPENCODE_BIN"] ?? "",
|
|
20097
|
+
join6(homedir2(), ".local", "bin", "opencode"),
|
|
20038
20098
|
"/usr/local/bin/opencode",
|
|
20039
20099
|
"/usr/bin/opencode"
|
|
20040
20100
|
];
|
|
20041
20101
|
for (const c of candidates) {
|
|
20042
20102
|
try {
|
|
20043
|
-
if (existsSync2(c)) return c;
|
|
20103
|
+
if (c.length > 0 && existsSync2(c)) return c;
|
|
20044
20104
|
} catch {
|
|
20045
20105
|
}
|
|
20046
20106
|
}
|
|
20047
|
-
return "";
|
|
20107
|
+
return "opencode";
|
|
20048
20108
|
}
|
|
20049
20109
|
function parseOptions(raw) {
|
|
20050
20110
|
const input2 = raw === void 0 ? {} : raw;
|
|
@@ -20096,7 +20156,7 @@ async function resolveRun(flags) {
|
|
|
20096
20156
|
if (typeof flags["config"] === "string") {
|
|
20097
20157
|
let raw;
|
|
20098
20158
|
try {
|
|
20099
|
-
raw = JSON.parse(await
|
|
20159
|
+
raw = JSON.parse(await readFile6(flags["config"], "utf8"));
|
|
20100
20160
|
} catch (e) {
|
|
20101
20161
|
return { ok: false, error: `--config unreadable: ${e instanceof Error ? e.message : String(e)}` };
|
|
20102
20162
|
}
|
|
@@ -20106,8 +20166,10 @@ async function resolveRun(flags) {
|
|
|
20106
20166
|
}
|
|
20107
20167
|
const laneCfg = {
|
|
20108
20168
|
runRoot: typeof flags["run-root"] === "string" ? flags["run-root"] : opts?.lane.runRoot ?? "/tmp",
|
|
20109
|
-
|
|
20110
|
-
|
|
20169
|
+
// empty bin crashed spawn with ERR_INVALID_ARG_VALUE (exam-venue injury, 2026-10-03);
|
|
20170
|
+
// resolution order: flag -> config -> env -> PATH name (spawn resolves bare names).
|
|
20171
|
+
opencodeBin: typeof flags["opencode-bin"] === "string" ? flags["opencode-bin"] : (opts?.lane.opencodeBin ?? "").length > 0 ? opts.lane.opencodeBin : (process.env["OPENCODE_BIN"] ?? "").length > 0 ? process.env["OPENCODE_BIN"] : "opencode",
|
|
20172
|
+
configSource: typeof flags["config-source"] === "string" ? flags["config-source"] : opts?.lane.configSource ?? ""
|
|
20111
20173
|
};
|
|
20112
20174
|
const modelFlag = typeof flags["model"] === "string" ? flags["model"] : null;
|
|
20113
20175
|
const pool = opts?.modelPool ?? { default: { providerID: "", modelID: "" } };
|
|
@@ -20131,13 +20193,13 @@ async function resolveRun(flags) {
|
|
|
20131
20193
|
}
|
|
20132
20194
|
const now = /* @__PURE__ */ new Date();
|
|
20133
20195
|
const runId = `sibyl-${compactIso(now)}-${randomBytes(2).toString("hex")}`;
|
|
20134
|
-
const runDir =
|
|
20135
|
-
await
|
|
20196
|
+
const runDir = join7(laneCfg.runRoot, `sibyl-run-${runId}`);
|
|
20197
|
+
await mkdir6(runDir, { recursive: true });
|
|
20136
20198
|
let targetPath = targetRaw;
|
|
20137
20199
|
if (targetRaw !== "-") {
|
|
20138
20200
|
const abs = isAbsolute2(targetRaw) ? targetRaw : resolve2(process.cwd(), targetRaw);
|
|
20139
|
-
targetPath =
|
|
20140
|
-
await
|
|
20201
|
+
targetPath = join7(runDir, "target", basename(abs));
|
|
20202
|
+
await mkdir6(dirname4(targetPath), { recursive: true });
|
|
20141
20203
|
try {
|
|
20142
20204
|
await copyFile2(abs, targetPath);
|
|
20143
20205
|
} catch (e) {
|
|
@@ -20158,7 +20220,7 @@ async function resolveRun(flags) {
|
|
|
20158
20220
|
const list = typeof scenarioPaths === "string" ? [scenarioPaths] : [];
|
|
20159
20221
|
const scenarios = [];
|
|
20160
20222
|
for (const p of list) {
|
|
20161
|
-
const v = parseScenario(await
|
|
20223
|
+
const v = parseScenario(await readFile6(p, "utf8"));
|
|
20162
20224
|
if (!v.ok) return { ok: false, error: `scenario ${p}: ${v.errors.join("; ")}` };
|
|
20163
20225
|
scenarios.push(v.scenario);
|
|
20164
20226
|
}
|
|
@@ -20234,7 +20296,7 @@ function makeExamDriver(laneCfgBase, runDir, modelId, timeoutMs) {
|
|
|
20234
20296
|
pure: true,
|
|
20235
20297
|
...resume !== void 0 && { resumeSession: resume }
|
|
20236
20298
|
});
|
|
20237
|
-
const transcript = await
|
|
20299
|
+
const transcript = await readFile6(o.transcriptPath, "utf8").catch(() => "");
|
|
20238
20300
|
return { facts: { ok: o.ok, rc: o.rc, signal: o.signal, timedOut: o.timedOut }, transcript, sessionId: o.sessionId };
|
|
20239
20301
|
};
|
|
20240
20302
|
return {
|
|
@@ -20277,7 +20339,7 @@ async function main(argv) {
|
|
|
20277
20339
|
if (cmd === "spotcheck") {
|
|
20278
20340
|
const arg = rest[0];
|
|
20279
20341
|
if (arg === void 0) return usage();
|
|
20280
|
-
const runDir = arg.startsWith("/") ? arg :
|
|
20342
|
+
const runDir = arg.startsWith("/") ? arg : join7("/tmp", `sibyl-run-${arg}`);
|
|
20281
20343
|
console.log(spotcheckCommand(runDir));
|
|
20282
20344
|
return 0;
|
|
20283
20345
|
}
|
|
@@ -20321,6 +20383,17 @@ async function main(argv) {
|
|
|
20321
20383
|
if (run.exam !== null) {
|
|
20322
20384
|
const driver = makeExamDriver(run.laneCfg, run.runDir, run.exam.modelId, run.timeoutMs);
|
|
20323
20385
|
const results = [];
|
|
20386
|
+
const scPaths = typeof flags["scenario"] === "string" ? [flags["scenario"]] : [];
|
|
20387
|
+
if (flags["allow-unbellied"] !== true) {
|
|
20388
|
+
for (const sp of scPaths) {
|
|
20389
|
+
const b = await runBell(sp);
|
|
20390
|
+
if (!b.ok) {
|
|
20391
|
+
console.log(`SIBYL REFUSE \u2014 bell preflight failed for ${sp}: ${b.detail}`);
|
|
20392
|
+
return 1;
|
|
20393
|
+
}
|
|
20394
|
+
console.log(`bell preflight OK: ${sp} \u2014 ${b.detail}`);
|
|
20395
|
+
}
|
|
20396
|
+
}
|
|
20324
20397
|
for (const sc of run.exam.scenarios) results.push(await runExamScenario(sc, run.runDir, driver));
|
|
20325
20398
|
console.log(examVoice(results, run.runId, run.runDir));
|
|
20326
20399
|
return 0;
|
|
@@ -20331,7 +20404,7 @@ async function main(argv) {
|
|
|
20331
20404
|
}
|
|
20332
20405
|
function usage() {
|
|
20333
20406
|
console.log(
|
|
20334
|
-
"usage: sibyl-chamber run --target <path|-> --goal <text> [--profile review|exam] [--scenario f.json ...]\n [--model p/m] [--judge-pool id,id] [--seed s] [--max-rounds n] [--config json] [--detach]\n [--kill-role <r> --kill-after <ms>]\n sibyl-chamber status [--run-id <id>] [--tail n]\n sibyl-chamber spotcheck <runDir|runId>\n sibyl-chamber kill <runDir> <role>"
|
|
20407
|
+
"usage: sibyl-chamber run --target <path|-> --goal <text> [--profile review|exam] [--scenario f.json ...]\n [--model p/m] [--judge-pool id,id] [--seed s] [--max-rounds n] [--config json] [--detach]\n [--kill-role <r> --kill-after <ms>] [--opencode-bin <path> --config-source <jsonc>]\n sibyl-chamber status [--run-id <id>] [--tail n]\n sibyl-chamber spotcheck <runDir|runId>\n sibyl-chamber kill <runDir> <role>"
|
|
20335
20408
|
);
|
|
20336
20409
|
return 2;
|
|
20337
20410
|
}
|
package/dist/index.js
CHANGED
|
@@ -18947,17 +18947,18 @@ function describeError(err) {
|
|
|
18947
18947
|
}
|
|
18948
18948
|
function discoverOpencodeBin() {
|
|
18949
18949
|
const candidates = [
|
|
18950
|
+
process.env["OPENCODE_BIN"] ?? "",
|
|
18950
18951
|
join(homedir(), ".local", "bin", "opencode"),
|
|
18951
18952
|
"/usr/local/bin/opencode",
|
|
18952
18953
|
"/usr/bin/opencode"
|
|
18953
18954
|
];
|
|
18954
18955
|
for (const c of candidates) {
|
|
18955
18956
|
try {
|
|
18956
|
-
if (existsSync(c)) return c;
|
|
18957
|
+
if (c.length > 0 && existsSync(c)) return c;
|
|
18957
18958
|
} catch {
|
|
18958
18959
|
}
|
|
18959
18960
|
}
|
|
18960
|
-
return "";
|
|
18961
|
+
return "opencode";
|
|
18961
18962
|
}
|
|
18962
18963
|
function parseOptions(raw) {
|
|
18963
18964
|
const input2 = raw === void 0 ? {} : raw;
|
|
@@ -18985,7 +18986,7 @@ import { dirname, join as join2 } from "node:path";
|
|
|
18985
18986
|
var SIBYL_STATE_FILE_ENV = "SIBYL_STATE_FILE";
|
|
18986
18987
|
var RUN_KINDS = ["consult", "swarm"];
|
|
18987
18988
|
var RUN_STATUSES = ["running", "done", "failed", "suspended"];
|
|
18988
|
-
var VERDICT_TAGS = ["APPROVE", "REJECT"];
|
|
18989
|
+
var VERDICT_TAGS = ["APPROVE", "REJECT", "CANNOT_ANSWER"];
|
|
18989
18990
|
function oneOf(v, opts) {
|
|
18990
18991
|
return opts.some((o) => o === v);
|
|
18991
18992
|
}
|
|
@@ -31708,20 +31709,26 @@ function dedupePreserveOrder(entries) {
|
|
|
31708
31709
|
function runPolicy(votes, policyName, canApprove) {
|
|
31709
31710
|
const observed = inspect(votes);
|
|
31710
31711
|
const issue3 = structuralIssue(votes);
|
|
31711
|
-
const
|
|
31712
|
+
const allSubstantive = observed.errors === 0 && observed.missing === 0;
|
|
31713
|
+
const outcome = issue3 !== null ? "REJECT" : canApprove(observed) ? "APPROVE" : allSubstantive || observed.rejects >= 2 ? "REJECT" : "CANNOT_ANSWER";
|
|
31712
31714
|
const reasons = [];
|
|
31713
31715
|
if (issue3 !== null) {
|
|
31714
31716
|
reasons.push(issue3);
|
|
31715
31717
|
}
|
|
31716
31718
|
reasons.push(...observed.notes);
|
|
31717
|
-
if (
|
|
31719
|
+
if (outcome === "REJECT" && issue3 === null) {
|
|
31718
31720
|
reasons.push(
|
|
31719
31721
|
`policy ${policyName} did not reach approval: ${observed.approvals}/${COUNCIL_SIZE} approvals, ${observed.errors} error, ${observed.missing} missing (fail-closed REJECT)`
|
|
31720
31722
|
);
|
|
31721
31723
|
}
|
|
31724
|
+
if (outcome === "CANNOT_ANSWER") {
|
|
31725
|
+
reasons.push(
|
|
31726
|
+
`policy ${policyName} could not answer: ${observed.errors} error + ${observed.missing} missing ballot(s) leave no decision (quorum broken) \u2014 NOT a substantive reject; repair the infra (see run notes) and re-run`
|
|
31727
|
+
);
|
|
31728
|
+
}
|
|
31722
31729
|
reasons.push(...dedupePreserveOrder(observed.reasons));
|
|
31723
31730
|
return {
|
|
31724
|
-
verdict:
|
|
31731
|
+
verdict: outcome,
|
|
31725
31732
|
approvals: observed.approvals,
|
|
31726
31733
|
rejects: observed.rejects,
|
|
31727
31734
|
errors: observed.errors,
|
|
@@ -31766,8 +31773,33 @@ function describeError2(e) {
|
|
|
31766
31773
|
return String(e);
|
|
31767
31774
|
}
|
|
31768
31775
|
}
|
|
31776
|
+
var DEFAULT_SALVAGE_POLL_MS = 5e3;
|
|
31777
|
+
var SALVAGE_ELIGIBLE_PROMPT_FAILURE = /timeout|fetch failed|ECONNRESET|ECONNREFUSED|ETIMEDOUT|socket hang up|UND_ERR_/i;
|
|
31778
|
+
async function salvageAfterPromptTimeout(client, directory, sessionID, graceMs, pollMs) {
|
|
31779
|
+
const messages = client.session.messages;
|
|
31780
|
+
if (!messages || graceMs <= 0) return null;
|
|
31781
|
+
const t0 = Date.now();
|
|
31782
|
+
while (Date.now() - t0 < graceMs) {
|
|
31783
|
+
await new Promise((resolve3) => setTimeout(resolve3, pollMs));
|
|
31784
|
+
const res = await messages.call(client.session, {
|
|
31785
|
+
path: { id: sessionID },
|
|
31786
|
+
query: { directory }
|
|
31787
|
+
});
|
|
31788
|
+
if (res.error !== void 0) return { kind: "error", error: describeError2(res.error) };
|
|
31789
|
+
const lastAssistant = (res.data ?? []).filter((m) => m.info?.role === "assistant").at(-1);
|
|
31790
|
+
const info = lastAssistant?.info;
|
|
31791
|
+
if (!info) continue;
|
|
31792
|
+
if (info.error !== void 0) return { kind: "error", error: describeError2(info.error) };
|
|
31793
|
+
if (typeof info.time?.completed === "number") {
|
|
31794
|
+
const text = (lastAssistant?.parts ?? []).filter((p) => p.type === "text" && typeof p.text === "string").map((p) => p.text).join("");
|
|
31795
|
+
const modelApplied = info.providerID && info.modelID ? `${info.providerID}/${info.modelID}` : null;
|
|
31796
|
+
return { kind: "ballot", text, modelApplied };
|
|
31797
|
+
}
|
|
31798
|
+
}
|
|
31799
|
+
return null;
|
|
31800
|
+
}
|
|
31769
31801
|
async function runPersona(opts) {
|
|
31770
|
-
const { client, directory, persona, model, inputText } = opts;
|
|
31802
|
+
const { client, directory, persona, model, inputText, parentID } = opts;
|
|
31771
31803
|
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
|
|
31772
31804
|
const t0 = Date.now();
|
|
31773
31805
|
let stage = "exception";
|
|
@@ -31784,7 +31816,10 @@ async function runPersona(opts) {
|
|
|
31784
31816
|
stage = "create";
|
|
31785
31817
|
const created = await withTimeout(
|
|
31786
31818
|
client.session.create({
|
|
31787
|
-
body: {
|
|
31819
|
+
body: {
|
|
31820
|
+
title: `sibyl:${persona.agent ?? "persona"}`,
|
|
31821
|
+
...parentID !== void 0 && parentID.length > 0 && { parentID }
|
|
31822
|
+
},
|
|
31788
31823
|
query: { directory }
|
|
31789
31824
|
}),
|
|
31790
31825
|
timeoutMs,
|
|
@@ -31798,27 +31833,62 @@ async function runPersona(opts) {
|
|
|
31798
31833
|
const tools = Object.fromEntries(
|
|
31799
31834
|
(persona.disallowedTools ?? DEFAULT_DISALLOWED_TOOLS).map((name) => [name, false])
|
|
31800
31835
|
);
|
|
31801
|
-
const
|
|
31802
|
-
|
|
31803
|
-
|
|
31804
|
-
|
|
31805
|
-
|
|
31806
|
-
|
|
31807
|
-
|
|
31808
|
-
|
|
31809
|
-
|
|
31810
|
-
|
|
31811
|
-
|
|
31812
|
-
|
|
31813
|
-
|
|
31814
|
-
|
|
31815
|
-
|
|
31836
|
+
const raceExpiredAt = Date.now();
|
|
31837
|
+
let prompted;
|
|
31838
|
+
try {
|
|
31839
|
+
prompted = await withTimeout(
|
|
31840
|
+
client.session.prompt({
|
|
31841
|
+
path: { id: sessionID },
|
|
31842
|
+
body: {
|
|
31843
|
+
model,
|
|
31844
|
+
...persona.agent !== void 0 && { agent: persona.agent },
|
|
31845
|
+
...persona.system !== void 0 && { system: persona.system },
|
|
31846
|
+
tools,
|
|
31847
|
+
parts: [{ type: "text", text: inputText }]
|
|
31848
|
+
},
|
|
31849
|
+
query: { directory }
|
|
31850
|
+
}),
|
|
31851
|
+
remainingMs,
|
|
31852
|
+
"session.prompt"
|
|
31853
|
+
);
|
|
31854
|
+
} catch (e) {
|
|
31855
|
+
const promptFailure = e instanceof Error ? e.message : describeError2(e);
|
|
31856
|
+
if (!SALVAGE_ELIGIBLE_PROMPT_FAILURE.test(promptFailure)) throw e;
|
|
31857
|
+
const graceMs = opts.salvageMs ?? timeoutMs;
|
|
31858
|
+
const outcome = await salvageAfterPromptTimeout(
|
|
31859
|
+
client,
|
|
31860
|
+
directory,
|
|
31861
|
+
sessionID,
|
|
31862
|
+
graceMs,
|
|
31863
|
+
opts.salvagePollMs ?? DEFAULT_SALVAGE_POLL_MS
|
|
31864
|
+
);
|
|
31865
|
+
if (!outcome) return fail(promptFailure);
|
|
31866
|
+
if (outcome.kind === "error") {
|
|
31867
|
+
return fail(`${promptFailure}; salvage read failed: ${outcome.error}`);
|
|
31868
|
+
}
|
|
31869
|
+
return {
|
|
31870
|
+
text: outcome.text,
|
|
31871
|
+
ok: true,
|
|
31872
|
+
latencyMs: Date.now() - t0,
|
|
31873
|
+
sessionID,
|
|
31874
|
+
modelApplied: outcome.modelApplied,
|
|
31875
|
+
salvaged: true,
|
|
31876
|
+
salvageExtraMs: Date.now() - raceExpiredAt
|
|
31877
|
+
};
|
|
31878
|
+
}
|
|
31816
31879
|
if (prompted.error) return fail(describeError2(prompted.error));
|
|
31817
31880
|
if (!prompted.data) return fail("session.prompt: response missing data payload");
|
|
31818
31881
|
const info = prompted.data.info;
|
|
31819
31882
|
if (info?.error) return fail(describeError2(info.error));
|
|
31820
31883
|
const modelApplied = info?.providerID && info?.modelID ? `${info.providerID}/${info.modelID}` : null;
|
|
31821
31884
|
const text = (prompted.data.parts ?? []).filter((p) => p.type === "text" && typeof p.text === "string").map((p) => p.text).join("");
|
|
31885
|
+
const headless = parentID === void 0 || parentID.length === 0;
|
|
31886
|
+
if (headless && client.session.delete !== void 0) {
|
|
31887
|
+
try {
|
|
31888
|
+
await client.session.delete({ path: { id: sessionID } });
|
|
31889
|
+
} catch {
|
|
31890
|
+
}
|
|
31891
|
+
}
|
|
31822
31892
|
return { text, ok: true, latencyMs: Date.now() - t0, sessionID, modelApplied };
|
|
31823
31893
|
} catch (e) {
|
|
31824
31894
|
return fail(e instanceof Error ? e.message : describeError2(e));
|
|
@@ -32156,7 +32226,7 @@ ${meta3}
|
|
|
32156
32226
|
}
|
|
32157
32227
|
async function castVote(job) {
|
|
32158
32228
|
const { deps, id } = job;
|
|
32159
|
-
const { directory, spaceDir, goal, artifactText } = job;
|
|
32229
|
+
const { directory, spaceDir, goal, artifactText, parentID } = job;
|
|
32160
32230
|
const { client, options } = deps;
|
|
32161
32231
|
const persona = PERSONAS[id];
|
|
32162
32232
|
const model = slotForModel(options.modelPool, options.voters[id], persona.modelSlot);
|
|
@@ -32167,7 +32237,8 @@ async function castVote(job) {
|
|
|
32167
32237
|
persona: { system: persona.system },
|
|
32168
32238
|
model,
|
|
32169
32239
|
inputText: buildConsultInput(goal, artifactText),
|
|
32170
|
-
timeoutMs: options.timeoutMs
|
|
32240
|
+
timeoutMs: options.timeoutMs,
|
|
32241
|
+
parentID
|
|
32171
32242
|
});
|
|
32172
32243
|
if (!result.ok) {
|
|
32173
32244
|
const error105 = `${result.stage ?? "engine"}: ${result.error ?? "unknown error"}`;
|
|
@@ -32186,8 +32257,9 @@ async function castVote(job) {
|
|
|
32186
32257
|
);
|
|
32187
32258
|
return followed.ok ? followed.text : "";
|
|
32188
32259
|
});
|
|
32189
|
-
const
|
|
32190
|
-
const
|
|
32260
|
+
const unparseable = verdict.reasons[0]?.startsWith("verdict-unparseable") ?? false;
|
|
32261
|
+
const vote = unparseable ? { id, ok: false, error: verdict.reasons[0] ?? "verdict-unparseable" } : { id, ok: true, verdict };
|
|
32262
|
+
const meta3 = unparseable ? `ballot: ERROR ${verdict.reasons[0] ?? "verdict-unparseable"} (reply did not parse)` : `ballot: ${verdict.verdict} (confidence ${String(verdict.confidence)}) \xB7 session ${result.sessionID} \xB7 model ${result.modelApplied ?? "host-picked"} \xB7 ${String(result.latencyMs)}ms`;
|
|
32191
32263
|
const replyAt = await safeWrite(replyPath, renderReply(id, result.text, meta3));
|
|
32192
32264
|
return { id, vote, replyPath: replyAt, modelApplied: result.modelApplied, latencyMs: result.latencyMs };
|
|
32193
32265
|
}
|
|
@@ -32201,7 +32273,7 @@ async function safeWrite(path, text) {
|
|
|
32201
32273
|
}
|
|
32202
32274
|
function buildConsultTool(deps) {
|
|
32203
32275
|
return tool({
|
|
32204
|
-
description: "SIBYL council consult: audit an artifact (file path or inline multi-line text, max 256 KiB) with the three councilor personas (MELCHIOR/BALTHASAR/CASPER) in parallel and return the fail-closed 2/3 verdict plus reasons, must-fix items, run id, and per-voter reply files.",
|
|
32276
|
+
description: "SIBYL council consult: audit an artifact (file path or inline multi-line text, max 256 KiB) with the three councilor personas (MELCHIOR/BALTHASAR/CASPER) in parallel and return the fail-closed 2/3 verdict (APPROVE / REJECT / CANNOT_ANSWER when error or missing seats left no decision) plus reasons, must-fix items, run id, and per-voter reply files.",
|
|
32205
32277
|
args: {
|
|
32206
32278
|
artifact: tool.schema.string().min(1).describe("Path to the artifact, or its inline content (multi-line)."),
|
|
32207
32279
|
goal: tool.schema.string().min(1).describe("What the artifact is supposed to achieve.")
|
|
@@ -32227,7 +32299,7 @@ async function consultExecute(deps, args, context) {
|
|
|
32227
32299
|
});
|
|
32228
32300
|
const outcomes = await Promise.all(
|
|
32229
32301
|
COUNCILORS.map(
|
|
32230
|
-
(id) => castVote({ deps, directory: context.directory, spaceDir: record3.spaceDir, goal: args.goal, artifactText: read.text, id })
|
|
32302
|
+
(id) => castVote({ deps, directory: context.directory, spaceDir: record3.spaceDir, goal: args.goal, artifactText: read.text, id, parentID: context.sessionID })
|
|
32231
32303
|
)
|
|
32232
32304
|
);
|
|
32233
32305
|
const tally = tallyVotes(outcomes.map((o) => o.vote));
|
|
@@ -32350,6 +32422,217 @@ function buildReviewTool(deps) {
|
|
|
32350
32422
|
});
|
|
32351
32423
|
}
|
|
32352
32424
|
|
|
32425
|
+
// src/tools/anchor.ts
|
|
32426
|
+
import { createHash } from "node:crypto";
|
|
32427
|
+
function canonicalView(rows) {
|
|
32428
|
+
return JSON.stringify(
|
|
32429
|
+
rows.map((r) => ({
|
|
32430
|
+
role: r.info?.role ?? null,
|
|
32431
|
+
parts: (r.parts ?? []).map((p) => ({ t: p.type, x: p.text ?? "" }))
|
|
32432
|
+
}))
|
|
32433
|
+
);
|
|
32434
|
+
}
|
|
32435
|
+
function canonicalText(raw) {
|
|
32436
|
+
return raw.replace(/\s*<dcp-message-id[^>]*>[\s\S]*?<\/dcp-message-id>\s*$/g, "").trim();
|
|
32437
|
+
}
|
|
32438
|
+
function sha256(text) {
|
|
32439
|
+
return createHash("sha256").update(text, "utf8").digest("hex");
|
|
32440
|
+
}
|
|
32441
|
+
function anchorText(row) {
|
|
32442
|
+
return (row.parts ?? []).filter((p) => p.type === "text").map((p) => p.text ?? "").join("\n");
|
|
32443
|
+
}
|
|
32444
|
+
function decide(row, expected) {
|
|
32445
|
+
if (row === void 0) return { verdict: "ABSENT", text: "" };
|
|
32446
|
+
if (expected === void 0) return { verdict: "ABSENT", text: anchorText(row) };
|
|
32447
|
+
const text = anchorText(row);
|
|
32448
|
+
const normalized = expected.trim().toLowerCase();
|
|
32449
|
+
if (canonicalText(text) === "") return { verdict: "ABSENT", text };
|
|
32450
|
+
const okRaw = sha256(text) === normalized;
|
|
32451
|
+
const okCanon = sha256(canonicalText(text)) === normalized;
|
|
32452
|
+
return { verdict: okRaw || okCanon ? "MATCH" : "MISMATCH", text };
|
|
32453
|
+
}
|
|
32454
|
+
async function anchorExecute(deps, args, context) {
|
|
32455
|
+
const messages = deps.client.session.messages;
|
|
32456
|
+
if (!messages) {
|
|
32457
|
+
return "SIBYL ANCHOR: ERROR \u2014 engine client exposes no session.messages seam (cannot answer; not a reject).";
|
|
32458
|
+
}
|
|
32459
|
+
if (args.expectSha256 !== void 0 && !/^[0-9a-f]{64}$/i.test(args.expectSha256)) {
|
|
32460
|
+
return "SIBYL ANCHOR: ERROR \u2014 expectSha256 must be 64 hex chars (a malformed claim is not a MISMATCH verdict).";
|
|
32461
|
+
}
|
|
32462
|
+
let rows;
|
|
32463
|
+
try {
|
|
32464
|
+
const res = await messages.call(deps.client.session, {
|
|
32465
|
+
path: { id: args.sessionId },
|
|
32466
|
+
query: { directory: context.directory }
|
|
32467
|
+
});
|
|
32468
|
+
if (res.error !== void 0) {
|
|
32469
|
+
return `SIBYL ANCHOR: ERROR \u2014 engine refused the read (${JSON.stringify(res.error).slice(0, 200)}).`;
|
|
32470
|
+
}
|
|
32471
|
+
rows = res.data ?? [];
|
|
32472
|
+
} catch (e) {
|
|
32473
|
+
return internalError("sibyl_anchor_check", e);
|
|
32474
|
+
}
|
|
32475
|
+
let row;
|
|
32476
|
+
let locatedBy;
|
|
32477
|
+
if (args.body !== void 0) {
|
|
32478
|
+
const want = canonicalText(args.body);
|
|
32479
|
+
const hits = rows.map((r, i) => ({ r, i })).filter(({ r }) => canonicalText(anchorText(r)) === want);
|
|
32480
|
+
if (hits.length !== 1) {
|
|
32481
|
+
return [
|
|
32482
|
+
`SIBYL ANCHOR ${hits.length === 0 ? "ABSENT" : "ERROR"} \u2014 body locator`,
|
|
32483
|
+
hits.length === 0 ? `no message's canonical text equals the claimed body (scanned ${rows.length}).` : `body matches ${hits.length} messages at sdk-ordinals ${hits.map((h) => h.i).join(",")} \u2014 never auto-pick; re-run with an explicit index.`,
|
|
32484
|
+
`view_sha256=${sha256(canonicalView(rows))}`
|
|
32485
|
+
].join("\n");
|
|
32486
|
+
}
|
|
32487
|
+
row = hits[0].r;
|
|
32488
|
+
locatedBy = `body@${hits[0].i}`;
|
|
32489
|
+
} else {
|
|
32490
|
+
const idx = args.index ?? -1;
|
|
32491
|
+
row = idx >= 0 ? rows[idx] : rows[rows.length + idx];
|
|
32492
|
+
locatedBy = `index=${idx}`;
|
|
32493
|
+
}
|
|
32494
|
+
void locatedBy;
|
|
32495
|
+
const claim2 = args.expectSha256 !== void 0;
|
|
32496
|
+
const { verdict, text } = claim2 ? decide(row, args.expectSha256) : { verdict: "ABSENT", text: row ? anchorText(row) : "" };
|
|
32497
|
+
const finalVerdict = !claim2 ? row === void 0 ? "ABSENT" : "ERROR" : verdict;
|
|
32498
|
+
const receipt = [
|
|
32499
|
+
`SIBYL ANCHOR ${finalVerdict}`,
|
|
32500
|
+
`session=${args.sessionId} ${locatedBy} messages=${rows.length} role=${row?.info?.role ?? "-"}`,
|
|
32501
|
+
args.label !== void 0 ? `label=${args.label}` : "",
|
|
32502
|
+
`anchor_sha256=${row === void 0 ? "-" : sha256(anchorText(row))}`,
|
|
32503
|
+
`claim_sha256=${args.expectSha256 ?? "-"}`,
|
|
32504
|
+
`view_sha256=${sha256(canonicalView(rows))}`,
|
|
32505
|
+
`body_len=${text.length}`,
|
|
32506
|
+
claim2 ? "" : "note: no expectSha256 supplied \u2014 a verdict requires the claim; nothing is asserted about content."
|
|
32507
|
+
].filter((l) => l !== "").join("\n");
|
|
32508
|
+
return receipt;
|
|
32509
|
+
}
|
|
32510
|
+
function buildAnchorTool(deps) {
|
|
32511
|
+
return tool({
|
|
32512
|
+
description: "SIBYL anchor check (g12b-3 primitive): verify a claimed human message (order/approval) against the engine session store \u2014 closed verdict MATCH/MISMATCH/ABSENT/ERROR plus a view-hash receipt so the read itself is re-computable. Read-only; persists nothing.",
|
|
32513
|
+
args: {
|
|
32514
|
+
sessionId: tool.schema.string().min(1).describe("Engine session id that allegedly contains the human message."),
|
|
32515
|
+
index: tool.schema.number().int().optional().describe("Message ordinal in the SDK messages view (0-based; negative counts from the end; default last). WARNING: this ordinal space differs from the raw message table \u2014 cross-tool identity is the body hash, not the ordinal."),
|
|
32516
|
+
body: tool.schema.string().min(1).optional().describe("Claimed exact body text \u2014 located by unique canonical match (closed normalization: trailing dcp tag + trim). Repeated bodies return ERROR naming candidates, never auto-picked."),
|
|
32517
|
+
expectSha256: tool.schema.string().min(64).max(64).optional().describe("Claimed sha256 (hex) of the anchor body text; omitting it yields a non-verdict."),
|
|
32518
|
+
label: tool.schema.string().min(1).optional().describe("Free-form label echoed into the receipt (e.g. the order being ratified).")
|
|
32519
|
+
},
|
|
32520
|
+
execute: async (args, context) => anchorExecute(deps, args, context)
|
|
32521
|
+
});
|
|
32522
|
+
}
|
|
32523
|
+
|
|
32524
|
+
// src/tools/probes.ts
|
|
32525
|
+
var DEFAULT_TIME_POOL = [
|
|
32526
|
+
{ url: "https://www.cloudflare.com", ownerDept: "cloudflare" },
|
|
32527
|
+
{ url: "https://www.google.com", ownerDept: "google" },
|
|
32528
|
+
{ url: "https://www.mozilla.org", ownerDept: "mozilla" }
|
|
32529
|
+
];
|
|
32530
|
+
function parseHttpDate(value) {
|
|
32531
|
+
if (value === null) return void 0;
|
|
32532
|
+
const ms = Date.parse(value);
|
|
32533
|
+
return Number.isFinite(ms) ? ms : void 0;
|
|
32534
|
+
}
|
|
32535
|
+
async function probeTimes(sources, fetchImpl, timeoutMs = 8e3) {
|
|
32536
|
+
return await Promise.all(
|
|
32537
|
+
sources.map(async (s) => {
|
|
32538
|
+
try {
|
|
32539
|
+
const res = await fetchImpl(s.url, { method: "HEAD", signal: AbortSignal.timeout(timeoutMs), redirect: "manual" });
|
|
32540
|
+
const ms = parseHttpDate(res.headers.get("date"));
|
|
32541
|
+
return ms === void 0 ? { url: s.url, ownerDept: s.ownerDept, ok: false, error: "no-parseable-date-header" } : { url: s.url, ownerDept: s.ownerDept, ok: true, epochMs: ms };
|
|
32542
|
+
} catch (e) {
|
|
32543
|
+
return { url: s.url, ownerDept: s.ownerDept, ok: false, error: String(e).slice(0, 120) };
|
|
32544
|
+
}
|
|
32545
|
+
})
|
|
32546
|
+
);
|
|
32547
|
+
}
|
|
32548
|
+
function consensus(receipts, localMs, toleranceMs = 3e5) {
|
|
32549
|
+
const ok = receipts.filter((r) => r.ok && r.epochMs !== void 0);
|
|
32550
|
+
const byDept = /* @__PURE__ */ new Map();
|
|
32551
|
+
for (const r of ok) if (!byDept.has(r.ownerDept)) byDept.set(r.ownerDept, r);
|
|
32552
|
+
const used = [...byDept.values()];
|
|
32553
|
+
if (used.length < 2) {
|
|
32554
|
+
return { verdict: "INSUFFICIENT-SOURCES", usedUrls: used.map((u) => u.url), spreadMs: 0, localOffsetMs: 0 };
|
|
32555
|
+
}
|
|
32556
|
+
const times = used.map((u) => u.epochMs);
|
|
32557
|
+
const spreadMs = Math.max(...times) - Math.min(...times);
|
|
32558
|
+
const median = [...times].sort((a, b) => a - b)[Math.floor(times.length / 2)];
|
|
32559
|
+
const localOffsetMs = localMs - median;
|
|
32560
|
+
const verdict = spreadMs <= toleranceMs && Math.abs(localOffsetMs) <= toleranceMs ? "SYNCED" : "DRIFT";
|
|
32561
|
+
return { verdict, usedUrls: used.map((u) => u.url), spreadMs, localOffsetMs };
|
|
32562
|
+
}
|
|
32563
|
+
function equalBytes(a, b) {
|
|
32564
|
+
return a === b;
|
|
32565
|
+
}
|
|
32566
|
+
function attributeRange(moves, commits, roster) {
|
|
32567
|
+
const bySha = new Map(commits.map((c) => [c.sha, c]));
|
|
32568
|
+
return moves.map((m) => {
|
|
32569
|
+
const c = bySha.get(m.newSha);
|
|
32570
|
+
if (c === void 0) return { verdict: "UNATTRIBUTED", detail: `${m.remote} ${m.ref}: new commit ${m.newSha.slice(0, 12)} not in supplied commit set` };
|
|
32571
|
+
const hits = roster.filter((r) => equalBytes(r.name, c.committerName) && equalBytes(r.email, c.committerEmail));
|
|
32572
|
+
if (hits.length === 0) return { verdict: "UNATTRIBUTED", detail: `${m.remote} ${m.ref}: committer "${c.committerName} <${c.committerEmail}>" has no ACTIVE roster binding` };
|
|
32573
|
+
if (hits.length > 1) return { verdict: "AMBIGUOUS", detail: `${m.remote} ${m.ref}: ${hits.length} roster entries byte-equal \u2014 registry defect, needs human dedup workorder` };
|
|
32574
|
+
const hit = hits[0];
|
|
32575
|
+
if (c.signedByKey !== null && hit.keyFingerprint !== void 0 && !equalBytes(c.signedByKey, hit.keyFingerprint)) {
|
|
32576
|
+
return { verdict: "UNATTRIBUTED", detail: `${m.remote} ${m.ref}: signed by key ${c.signedByKey.slice(0, 12)} != roster binding ${hit.keyFingerprint.slice(0, 12)} (bytes-claim without signature)` };
|
|
32577
|
+
}
|
|
32578
|
+
return { verdict: "ATTRIBUTED", detail: `${m.remote} ${m.ref} -> ${hit.name} <${hit.email}>${hit.keyFingerprint !== void 0 ? ` key=${hit.keyFingerprint.slice(0, 12)}` : ""}` };
|
|
32579
|
+
});
|
|
32580
|
+
}
|
|
32581
|
+
function parseCommitMeta(stdout) {
|
|
32582
|
+
return stdout.split("\n").filter((l) => l.includes("|")).map((l) => {
|
|
32583
|
+
const [sha = "", committerName = "", committerEmail = "", gk = ""] = l.split("|");
|
|
32584
|
+
return { sha: sha.trim(), committerName: committerName ?? "", committerEmail: committerEmail ?? "", signedByKey: (gk ?? "").trim() === "" ? null : (gk ?? "").trim() };
|
|
32585
|
+
});
|
|
32586
|
+
}
|
|
32587
|
+
function buildTimeProbeTool(deps) {
|
|
32588
|
+
void deps;
|
|
32589
|
+
return tool({
|
|
32590
|
+
description: "SIBYL time probe (g12b-8): read HTTP Date headers from >=2 operator-distinct remote sources and compare against the local clock \u2014 verdict SYNCED / DRIFT / INSUFFICIENT-SOURCES with spread and offset in ms. Same-owner sources collapse to one; fewer than two distinct owners can NEVER certify time.",
|
|
32591
|
+
args: {
|
|
32592
|
+
endpoints: tool.schema.string().optional().describe("JSON array of {url,ownerDept}; defaults to the built-in cloudflare/google/mozilla pool."),
|
|
32593
|
+
toleranceMs: tool.schema.number().int().positive().optional().describe("Accepted spread/offset in ms (default 300000 = 5 min, HTTP-date + proxy slop).")
|
|
32594
|
+
},
|
|
32595
|
+
execute: async (args, _context) => {
|
|
32596
|
+
try {
|
|
32597
|
+
const sources = args.endpoints !== void 0 ? JSON.parse(args.endpoints) : DEFAULT_TIME_POOL;
|
|
32598
|
+
const receipts = await probeTimes(sources, fetch);
|
|
32599
|
+
const c = consensus(receipts, Date.now(), args.toleranceMs);
|
|
32600
|
+
return [
|
|
32601
|
+
`SIBYL TIME ${c.verdict}`,
|
|
32602
|
+
`used=${c.usedUrls.join(",")}`,
|
|
32603
|
+
`spread_ms=${c.spreadMs} local_offset_ms=${c.localOffsetMs} tolerance_ms=${args.toleranceMs ?? 3e5}`,
|
|
32604
|
+
...receipts.map((r) => `${r.ok ? "ok" : "fail"} ${r.url} (${r.ownerDept})${r.error !== void 0 ? ` \u2014 ${r.error}` : ` \u2014 ${new Date(r.epochMs ?? 0).toISOString()}`}`)
|
|
32605
|
+
].join("\n");
|
|
32606
|
+
} catch (e) {
|
|
32607
|
+
return internalError("sibyl_time_probe", e);
|
|
32608
|
+
}
|
|
32609
|
+
}
|
|
32610
|
+
});
|
|
32611
|
+
}
|
|
32612
|
+
function buildAttributeTool(deps) {
|
|
32613
|
+
void deps;
|
|
32614
|
+
return tool({
|
|
32615
|
+
description: "SIBYL attribution (g12b-4): compare supplied ref moves (remote/ref/old/new) against commit metadata and a roster (identity+key bindings, ACTIVE entries) \u2014 per-move verdict ATTRIBUTED / UNATTRIBUTED / AMBIGUOUS. Byte-exact comparison, no normalization; unknown commits and missing bindings are UNATTRIBUTED (silent egress is fatal-class), byte-equal roster duplicates are AMBIGUOUS registry defects. Inputs are read-only strings the CALLER captured (git log / ls-remote output) \u2014 this tool never shells out.",
|
|
32616
|
+
args: {
|
|
32617
|
+
moves: tool.schema.string().describe("JSON array of {remote,ref,oldSha,newSha}."),
|
|
32618
|
+
commits: tool.schema.string().describe("`git log --format=%H|%cn|%ce|%GK` output covering the moved-to commits (newest first or any order)."),
|
|
32619
|
+
roster: tool.schema.string().describe("JSON array of {name,email,keyFingerprint?} \u2014 the registered identity bindings.")
|
|
32620
|
+
},
|
|
32621
|
+
execute: async (args) => {
|
|
32622
|
+
try {
|
|
32623
|
+
const moves = JSON.parse(args.moves);
|
|
32624
|
+
const roster = JSON.parse(args.roster);
|
|
32625
|
+
const commits = parseCommitMeta(args.commits);
|
|
32626
|
+
const results = attributeRange(moves, commits, roster);
|
|
32627
|
+
const head = results.every((r) => r.verdict === "ATTRIBUTED") ? "ALL ATTRIBUTED" : results.some((r) => r.verdict === "UNATTRIBUTED") ? "UNATTRIBUTED MOVES PRESENT (fatal class)" : "AMBIGUOUS \u2014 registry defect";
|
|
32628
|
+
return [`SIBYL ATTRIBUTE: ${head}`, ...results.map((r) => `${r.verdict} :: ${r.detail}`)].join("\n");
|
|
32629
|
+
} catch (e) {
|
|
32630
|
+
return internalError("sibyl_attribute", e);
|
|
32631
|
+
}
|
|
32632
|
+
}
|
|
32633
|
+
});
|
|
32634
|
+
}
|
|
32635
|
+
|
|
32353
32636
|
// src/state/chamber.ts
|
|
32354
32637
|
import { mkdir as mkdir3, readFile as readFile3, readdir, rename as rename2, unlink as unlink2, writeFile as writeFile4 } from "node:fs/promises";
|
|
32355
32638
|
import { homedir as homedir3 } from "node:os";
|
|
@@ -32364,7 +32647,8 @@ function asStringArray(v) {
|
|
|
32364
32647
|
function isRosterRef(v) {
|
|
32365
32648
|
if (typeof v !== "object" || v === null) return false;
|
|
32366
32649
|
const o = v;
|
|
32367
|
-
|
|
32650
|
+
const modelOk = o["policyCheck"] === "deny" ? typeof o["modelId"] === "string" : isNonEmptyString(o["modelId"]);
|
|
32651
|
+
return isNonEmptyString(o["role"]) && isNonEmptyString(o["slot"]) && modelOk && (o["policyCheck"] === "allow" || o["policyCheck"] === "deny") && (o["denyReason"] === void 0 || typeof o["denyReason"] === "string");
|
|
32368
32652
|
}
|
|
32369
32653
|
function isArtifactRef(v) {
|
|
32370
32654
|
if (typeof v !== "object" || v === null) return false;
|
|
@@ -32646,7 +32930,8 @@ async function runTask(ctx, task) {
|
|
|
32646
32930
|
persona: worker.persona,
|
|
32647
32931
|
model: worker.model,
|
|
32648
32932
|
inputText: composeTaskPrompt(ctx, task.taskId),
|
|
32649
|
-
timeoutMs: worker.timeoutMs
|
|
32933
|
+
timeoutMs: worker.timeoutMs,
|
|
32934
|
+
parentID: ctx.parentID
|
|
32650
32935
|
});
|
|
32651
32936
|
} catch (e) {
|
|
32652
32937
|
task.status = "failed";
|
|
@@ -32735,6 +33020,7 @@ async function run(prior, roster, opts) {
|
|
|
32735
33020
|
const resolved = {
|
|
32736
33021
|
client: opts.client,
|
|
32737
33022
|
directory: opts.directory,
|
|
33023
|
+
parentID: opts.parentID,
|
|
32738
33024
|
concurrencyK: opts.concurrencyK,
|
|
32739
33025
|
staggerMs: opts.staggerMs,
|
|
32740
33026
|
maxAttempts,
|
|
@@ -32933,7 +33219,8 @@ async function planWorkflow(opts) {
|
|
|
32933
33219
|
persona: opts.architectPersona,
|
|
32934
33220
|
model: opts.model,
|
|
32935
33221
|
inputText: buildPlanInstruction(opts.goal, opts.artifact),
|
|
32936
|
-
...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {}
|
|
33222
|
+
...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {},
|
|
33223
|
+
...opts.parentID !== void 0 ? { parentID: opts.parentID } : {}
|
|
32937
33224
|
});
|
|
32938
33225
|
} catch (e) {
|
|
32939
33226
|
return { ok: false, stage: "plan", error: `architect runPersona threw: ${String(e)}` };
|
|
@@ -33017,6 +33304,7 @@ async function swarmExecute(deps, args, context) {
|
|
|
33017
33304
|
goal: args.goal,
|
|
33018
33305
|
artifact: read.text,
|
|
33019
33306
|
timeoutMs: options.timeoutMs,
|
|
33307
|
+
parentID: context.sessionID,
|
|
33020
33308
|
repair: async (_badText, why) => {
|
|
33021
33309
|
const redo = await runPersona({
|
|
33022
33310
|
client,
|
|
@@ -33024,7 +33312,8 @@ async function swarmExecute(deps, args, context) {
|
|
|
33024
33312
|
persona: { system: PERSONAS.ARCHITECT.system },
|
|
33025
33313
|
model: architectModel,
|
|
33026
33314
|
inputText: `${buildRepairDemand(why)} matching the workflow-schema contract: {"tasks":[{"id","title","instructions","dependsOn"}],"concurrency":<int >= 1>,"notes":<string>}`,
|
|
33027
|
-
timeoutMs: options.timeoutMs
|
|
33315
|
+
timeoutMs: options.timeoutMs,
|
|
33316
|
+
parentID: context.sessionID
|
|
33028
33317
|
});
|
|
33029
33318
|
return redo.ok ? redo.text : "";
|
|
33030
33319
|
}
|
|
@@ -33040,6 +33329,7 @@ async function swarmExecute(deps, args, context) {
|
|
|
33040
33329
|
const dispatch = await dispatchRoster(mint.roster, {
|
|
33041
33330
|
client,
|
|
33042
33331
|
directory,
|
|
33332
|
+
parentID: context.sessionID,
|
|
33043
33333
|
concurrencyK: Math.min(options.concurrencyK, plan.schema.concurrency),
|
|
33044
33334
|
staggerMs: options.staggerMs,
|
|
33045
33335
|
maxRounds: options.maxRounds,
|
|
@@ -33070,27 +33360,30 @@ ${text}
|
|
|
33070
33360
|
persona: { system: "You are the SIBYL swarm judge. You rule on pipeline reports with exactly one word: APPROVE, REJECT, or EXHAUSTED." },
|
|
33071
33361
|
model: slotForModel(options.modelPool, options.swarm.judge, void 0),
|
|
33072
33362
|
inputText: buildJudgeInput(report),
|
|
33073
|
-
timeoutMs: options.timeoutMs
|
|
33363
|
+
timeoutMs: options.timeoutMs,
|
|
33364
|
+
parentID: context.sessionID
|
|
33074
33365
|
});
|
|
33075
33366
|
const word = judged.ok ? parseJudgeWord(judged.text) : null;
|
|
33076
33367
|
judgeNote = word === null ? `judge=inactive (${judged.ok ? "unrecognized verdict word" : `engine: ${judged.error ?? "?"}`}; derived kept)` : `judge=${word}${word === report.verdict ? "" : " (overrides derived " + report.verdict + ")"}`;
|
|
33077
33368
|
if (word !== null) report = { ...report, verdict: word };
|
|
33078
33369
|
}
|
|
33079
33370
|
const counts = countTasks(report.tasks);
|
|
33371
|
+
const noSignal = counts.done === 0;
|
|
33372
|
+
const tag = noSignal ? "CANNOT_ANSWER" : report.verdict === "APPROVE" ? "APPROVE" : "REJECT";
|
|
33080
33373
|
await finishRun(deps, record3, {
|
|
33081
33374
|
status: "done",
|
|
33082
33375
|
rounds: report.rounds,
|
|
33083
33376
|
verdict: {
|
|
33084
|
-
verdict:
|
|
33377
|
+
verdict: tag,
|
|
33085
33378
|
approvals: counts.done,
|
|
33086
|
-
rejects:
|
|
33087
|
-
errors:
|
|
33379
|
+
rejects: noSignal ? 0 : tag === "REJECT" ? 1 : 0,
|
|
33380
|
+
errors: counts.failed,
|
|
33088
33381
|
missing: counts.suspended
|
|
33089
33382
|
},
|
|
33090
|
-
notes: `${judgeNote} \xB7 ${report.tasks.map((t) => `${t.id}=${t.status}`).join(",")}`
|
|
33383
|
+
notes: `${judgeNote}${noSignal ? " \xB7 zero drafts produced: instrument failure, not a verdict on the artifact" : ""} \xB7 ${report.tasks.map((t) => `${t.id}=${t.status}`).join(",")}`
|
|
33091
33384
|
});
|
|
33092
33385
|
const lines = [
|
|
33093
|
-
`SIBYL SWARM: ${
|
|
33386
|
+
`SIBYL SWARM: ${tag} run ${record3.runId} (rounds=${String(report.rounds)} done=${String(counts.done)} failed=${String(counts.failed)} suspended=${String(counts.suspended)} ${judgeNote})`,
|
|
33094
33387
|
...report.tasks.map((t) => ` task: ${t.id} ${t.status}${t.sessionId === void 0 ? "" : ` session=${t.sessionId}`}`),
|
|
33095
33388
|
...report.artifacts.map((a) => ` artifact: ${a}`),
|
|
33096
33389
|
` space: ${record3.spaceDir}`,
|
|
@@ -33122,8 +33415,17 @@ function failRun(deps, record3, failure) {
|
|
|
33122
33415
|
function toEngineClient(sdk) {
|
|
33123
33416
|
return {
|
|
33124
33417
|
session: {
|
|
33418
|
+
async delete(args) {
|
|
33419
|
+
return await sdk.session.delete(args);
|
|
33420
|
+
},
|
|
33125
33421
|
async create(args) {
|
|
33126
|
-
const r = await sdk.session.create({
|
|
33422
|
+
const r = await sdk.session.create({
|
|
33423
|
+
body: {
|
|
33424
|
+
title: args.body.title,
|
|
33425
|
+
...args.body.parentID !== void 0 && { parentID: args.body.parentID }
|
|
33426
|
+
},
|
|
33427
|
+
query: { directory: args.query.directory }
|
|
33428
|
+
});
|
|
33127
33429
|
return {
|
|
33128
33430
|
...r.data !== void 0 && { data: { id: r.data.id } },
|
|
33129
33431
|
...r.error !== void 0 && { error: r.error }
|
|
@@ -33161,6 +33463,34 @@ function toEngineClient(sdk) {
|
|
|
33161
33463
|
},
|
|
33162
33464
|
...r.error !== void 0 && { error: r.error }
|
|
33163
33465
|
};
|
|
33466
|
+
},
|
|
33467
|
+
async messages(args) {
|
|
33468
|
+
const r = await sdk.session.messages({
|
|
33469
|
+
path: { id: args.path.id },
|
|
33470
|
+
query: { directory: args.query.directory }
|
|
33471
|
+
});
|
|
33472
|
+
return {
|
|
33473
|
+
...r.data !== void 0 && {
|
|
33474
|
+
data: r.data.map((m) => ({
|
|
33475
|
+
info: {
|
|
33476
|
+
...m.info.role !== void 0 && { role: m.info.role },
|
|
33477
|
+
...m.info.role === "assistant" && {
|
|
33478
|
+
...m.info.providerID !== void 0 && { providerID: m.info.providerID },
|
|
33479
|
+
...m.info.modelID !== void 0 && { modelID: m.info.modelID },
|
|
33480
|
+
...m.info.time !== void 0 && {
|
|
33481
|
+
time: { ...m.info.time.completed !== void 0 && { completed: m.info.time.completed } }
|
|
33482
|
+
},
|
|
33483
|
+
...m.info.error !== void 0 && { error: m.info.error }
|
|
33484
|
+
}
|
|
33485
|
+
},
|
|
33486
|
+
parts: m.parts.map((p) => ({
|
|
33487
|
+
type: p.type,
|
|
33488
|
+
..."text" in p && p.text !== void 0 && { text: p.text }
|
|
33489
|
+
}))
|
|
33490
|
+
}))
|
|
33491
|
+
},
|
|
33492
|
+
...r.error !== void 0 && { error: r.error }
|
|
33493
|
+
};
|
|
33164
33494
|
}
|
|
33165
33495
|
}
|
|
33166
33496
|
};
|
|
@@ -33184,7 +33514,10 @@ async function SibylPlugin(input2, options) {
|
|
|
33184
33514
|
sibyl_consult: buildConsultTool(deps),
|
|
33185
33515
|
sibyl_swarm: buildSwarmTool(deps),
|
|
33186
33516
|
sibyl_status: buildStatusTool(deps),
|
|
33187
|
-
sibyl_review: buildReviewTool(deps)
|
|
33517
|
+
sibyl_review: buildReviewTool(deps),
|
|
33518
|
+
sibyl_anchor_check: buildAnchorTool(deps),
|
|
33519
|
+
sibyl_time_probe: buildTimeProbeTool(deps),
|
|
33520
|
+
sibyl_attribute: buildAttributeTool(deps)
|
|
33188
33521
|
}
|
|
33189
33522
|
};
|
|
33190
33523
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sibyl-system",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Sibyl-System: democratic-centralism review chamber (one conclusion, one voice) + fail-closed council voting + dynamic swarm plugin for OpenCode",
|