sibyl-system 1.3.5 → 1.4.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 CHANGED
@@ -186,6 +186,13 @@ Plainly stated:
186
186
  A kinship hit stamps the run `independence=NOT-INDEPENDENT`: the ballot is
187
187
  preserved as data but is not in any effective path. A chain that cannot be
188
188
  read stamps `UNVERIFIABLE` — unreadable is never silently clean.
189
+ - **Refused launches leave records (v1.4.0, council 16a2):** an explicit `--run-id`
190
+ whose tool-staged `launch-context.json` is missing or malformed is REFUSED, with a
191
+ `launch-refusal.json` written into the private run dir — a pinned id claiming a
192
+ tool launch that never staged its finding is a provenance lie, not an absence.
193
+ The bare CLI lane (no `--run-id`) keeps the named `UNVERIFIABLE` leg, and once the
194
+ chamber runner regenerates its run record, that face outvouches any contradicted
195
+ staged claim ("record wins").
189
196
  - **Absence is a disability (W2).** Before any tally is honored, every declared
190
197
  voter/worker must have left a terminal row (`ok|error|timeout` + detail) in
191
198
  the run's `TERMINALS.txt`. A declared seat without its row is counted
@@ -204,7 +211,9 @@ Plainly stated:
204
211
  - Consult/swarm send the artifact text to your configured model pool providers.
205
212
  - The plugin writes under its state paths (the runs file and the per-run space) and,
206
213
  for chamber/exam lanes, sealed run directories under `lane.runRoot`
207
- (default `/tmp`) — role homes, transcripts, pidfiles, CHECKSUMS.
214
+ (default `/tmp`), every plugin-created dir `0700` (mkdir mode + explicit chmod:
215
+ unreachable to group/other regardless of umask — v1.4.0 private-dir law) —
216
+ role homes, transcripts, pidfiles, CHECKSUMS.
208
217
  - Voter/worker turns run with `bash`, `edit`, and `write` disabled per prompt.
209
218
 
210
219
  ## Architecture (`src/`)
@@ -263,7 +272,7 @@ PLAN→MINT→DISPATCH→AGGREGATE pipeline over ordinary child sessions.
263
272
 
264
273
  ```bash
265
274
  npm run typecheck # tsc --noEmit, strict + noUncheckedIndexedAccess + exactOptionalPropertyTypes
266
- npm test # full unit suite, fully offline (no network, no LLM; ~420 at v1.3.0 — the run prints the exact count)
275
+ npm test # full unit suite, fully offline (no network, no LLM; 459 at v1.4.0 — the run prints the exact count)
267
276
  npm run build # esbuild bundle → dist/index.js + dist/cli.js (ESM)
268
277
  node smoke/run-smoke.mjs # offline smoke of the shipped surface (see smoke/README.md)
269
278
  ./ship.sh # the full release gate: all of the above + portability scan of the packed tarball
package/dist/cli.js CHANGED
@@ -9,7 +9,16 @@ var __export = (target, all) => {
9
9
  import { randomBytes } from "node:crypto";
10
10
  import { spawn as spawn2 } from "node:child_process";
11
11
  import { existsSync as existsSync3 } from "node:fs";
12
- import { copyFile as copyFile2, mkdir as mkdir6, readFile as readFile6 } from "node:fs/promises";
12
+ import { access, copyFile as copyFile2, mkdir as mkdir6, readFile as readFile6, writeFile as writeFile7 } from "node:fs/promises";
13
+
14
+ // src/fsperm.ts
15
+ import { chmod, mkdir } from "node:fs/promises";
16
+ async function mkdirPrivate(dir) {
17
+ await mkdir(dir, { recursive: true, mode: 448 });
18
+ await chmod(dir, 448);
19
+ }
20
+
21
+ // src/cli.ts
13
22
  import { basename, dirname as dirname4, isAbsolute as isAbsolute2, join as join7, resolve as resolve2 } from "node:path";
14
23
 
15
24
  // src/state/record.ts
@@ -73,7 +82,7 @@ var COUNCIL_SIZE = COUNCILORS.length;
73
82
  var KNOWN_IDS = new Set(COUNCILORS);
74
83
 
75
84
  // src/chamber/protocol.ts
76
- import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
85
+ import { mkdir as mkdir3, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
77
86
  import { join as join2 } from "node:path";
78
87
 
79
88
  // src/lane/seating.ts
@@ -185,7 +194,7 @@ var FAIL_CLOSED_REJECT = rejectVerdict("no-parse-performed");
185
194
 
186
195
  // src/state/chamber.ts
187
196
  import { createHash as createHash2 } from "node:crypto";
188
- import { mkdir, readFile, readdir, rename, unlink, writeFile } from "node:fs/promises";
197
+ import { mkdir as mkdir2, readFile, readdir, rename, unlink, writeFile } from "node:fs/promises";
189
198
  import { homedir } from "node:os";
190
199
  import { dirname, isAbsolute, join, resolve } from "node:path";
191
200
  var DEFAULT_CHAMBER_LEDGER = join(homedir(), ".sibyl", "chamber-ledger.jsonl");
@@ -293,7 +302,7 @@ async function appendLedgerRow(ledgerPath, record2) {
293
302
  const stamped = { ...record2, serial };
294
303
  const line = JSON.stringify(stamped);
295
304
  if (line.includes("\n")) return { ok: false, reason: "row serializes with a newline \u2014 refused (ledger is one-JSON-per-line)" };
296
- await mkdir(dirname(ledgerPath), { recursive: true });
305
+ await mkdir2(dirname(ledgerPath), { recursive: true });
297
306
  await writeFile(ledgerPath, `${line}
298
307
  `, { flag: "a" });
299
308
  return { ok: true, serial, line };
@@ -334,7 +343,7 @@ var RECORD_FILE = "run-record.json";
334
343
  var CHECKSUMS_FILE = "CHECKSUMS.txt";
335
344
  var LEDGER_LINE_FILE = "ledger-line.txt";
336
345
  async function atomicWrite(path, content) {
337
- await mkdir(dirname(path), { recursive: true });
346
+ await mkdir2(dirname(path), { recursive: true });
338
347
  const tmp = `${path}.${String(process.pid)}.tmp`;
339
348
  try {
340
349
  await writeFile(tmp, content, "utf8");
@@ -636,9 +645,9 @@ var Protocol = class {
636
645
  }
637
646
  };
638
647
  async function runChamber(cfg, onRecord) {
639
- await mkdir2(join2(cfg.runDir, "pids"), { recursive: true });
640
- await mkdir2(join2(cfg.runDir, "evidence"), { recursive: true });
641
- for (const role of CHAMBER_ROLES) await mkdir2(join2(cfg.runDir, role), { recursive: true });
648
+ await mkdir3(join2(cfg.runDir, "pids"), { recursive: true });
649
+ await mkdir3(join2(cfg.runDir, "evidence"), { recursive: true });
650
+ for (const role of CHAMBER_ROLES) await mkdir3(join2(cfg.runDir, role), { recursive: true });
642
651
  const roster = CHAMBER_ROLES.map((role) => {
643
652
  const d = resolveSeat(role, cfg.slots[role], cfg.pool, cfg.policy);
644
653
  return d.ok ? { role, slot: d.slot, modelId: d.modelId, policyCheck: "allow" } : { role, slot: cfg.slots[role], modelId: "", policyCheck: "deny", denyReason: d.deny };
@@ -852,7 +861,7 @@ function renderVoice(voice) {
852
861
  // src/lane/isolated.ts
853
862
  import { spawn } from "node:child_process";
854
863
  import { createWriteStream, existsSync } from "node:fs";
855
- import { copyFile, mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
864
+ import { copyFile, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
856
865
  import { dirname as dirname2, join as join3 } from "node:path";
857
866
  var DEFAULT_PINNED_PATH = "/usr/bin:/bin";
858
867
  function composeSandboxArgs(opts) {
@@ -882,12 +891,12 @@ function rolePaths(cfg, role) {
882
891
  }
883
892
  async function prepareRoleHome(cfg, role) {
884
893
  const paths = rolePaths(cfg, role);
885
- await mkdir3(join3(paths.home, ".config", "opencode"), { recursive: true });
886
- await mkdir3(dirname2(paths.dbPath), { recursive: true });
887
- await mkdir3(join3(paths.home, ".cache", "opencode"), { recursive: true });
888
- await mkdir3(join3(paths.home, ".local", "state", "opencode"), { recursive: true });
889
- await mkdir3(paths.workspace, { recursive: true });
890
- await mkdir3(join3(cfg.runDir, "pids"), { recursive: true });
894
+ await mkdirPrivate(join3(paths.home, ".config", "opencode"));
895
+ await mkdirPrivate(dirname2(paths.dbPath));
896
+ await mkdirPrivate(join3(paths.home, ".cache", "opencode"));
897
+ await mkdirPrivate(join3(paths.home, ".local", "state", "opencode"));
898
+ await mkdirPrivate(paths.workspace);
899
+ await mkdirPrivate(join3(cfg.runDir, "pids"));
891
900
  try {
892
901
  if (cfg.configSource.length > 0) {
893
902
  await copyFile(cfg.configSource, join3(paths.home, ".config", "opencode", "opencode.jsonc"));
@@ -20381,17 +20390,51 @@ async function resolveRun(flags) {
20381
20390
  return { ok: false, error: `--run-id must be a bare sibyl run id, got "${flagRunId}"` };
20382
20391
  }
20383
20392
  const runId = flagRunId ?? `sibyl-${compactIso(now)}-${randomBytes(2).toString("hex")}`;
20393
+ if (targetRaw !== "-") {
20394
+ const abs = isAbsolute2(targetRaw) ? targetRaw : resolve2(process.cwd(), targetRaw);
20395
+ try {
20396
+ await access(abs);
20397
+ } catch (e) {
20398
+ return { ok: false, error: `--target unreadable: ${e instanceof Error ? e.message : String(e)}` };
20399
+ }
20400
+ }
20384
20401
  const runDir = join7(laneCfg.runRoot, `sibyl-run-${runId}`);
20385
- await mkdir6(runDir, { recursive: true });
20386
- let launchIndependence;
20402
+ await mkdirPrivate(runDir);
20403
+ let contextLeg = null;
20404
+ let contextProblem = null;
20387
20405
  try {
20388
20406
  const lc = JSON.parse(await readFile6(join7(runDir, "launch-context.json"), "utf8"));
20389
- launchIndependence = isIndependenceShape(lc["independence"]) ? lc["independence"] : {
20390
- status: "UNVERIFIABLE",
20391
- convenerChain: [],
20392
- evidence: "launch-context staged but its independence leg is malformed \u2014 not trusted; this review carries no independence anchor"
20407
+ if (isIndependenceShape(lc["independence"])) contextLeg = lc;
20408
+ else contextProblem = "launch-context staged but its independence leg is malformed";
20409
+ } catch (err) {
20410
+ contextProblem = err instanceof Error && err.name === "SyntaxError" ? "launch-context exists but is not valid JSON" : "no launch-context staged in the run dir";
20411
+ }
20412
+ if (flagRunId !== null && contextProblem !== null) {
20413
+ const refusal = {
20414
+ kind: "refused-launch",
20415
+ runId: flagRunId,
20416
+ reason: `${contextProblem} \u2014 a --run-id launch claims a tool-staged review that never happened; rerun through the sibyl_review tool, or drop --run-id for the supported bare headless launch`,
20417
+ at: now.toISOString()
20393
20418
  };
20394
- } catch {
20419
+ await writeFile7(join7(runDir, "launch-refusal.json"), `${JSON.stringify(refusal, null, 2)}
20420
+ `, "utf8");
20421
+ return { ok: false, error: refusal.reason };
20422
+ }
20423
+ let launchIndependence;
20424
+ if (contextLeg !== null) {
20425
+ launchIndependence = contextLeg["independence"];
20426
+ try {
20427
+ const face = JSON.parse(await readFile6(join7(runDir, "run-record.json"), "utf8"));
20428
+ const faceInd = face["independence"];
20429
+ if (isIndependenceShape(faceInd) && faceInd.status !== launchIndependence.status) {
20430
+ launchIndependence = {
20431
+ ...faceInd,
20432
+ evidence: `context claimed ${launchIndependence.status}, run record carries ${faceInd.status} \u2014 record wins. ${faceInd.evidence}`
20433
+ };
20434
+ }
20435
+ } catch {
20436
+ }
20437
+ } else {
20395
20438
  launchIndependence = {
20396
20439
  status: "UNVERIFIABLE",
20397
20440
  convenerChain: [],
package/dist/index.js CHANGED
@@ -18981,7 +18981,16 @@ function parseOptions(raw) {
18981
18981
 
18982
18982
  // src/state/index.ts
18983
18983
  import { randomBytes } from "node:crypto";
18984
- import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
18984
+ import { readFile, rename, unlink, writeFile } from "node:fs/promises";
18985
+
18986
+ // src/fsperm.ts
18987
+ import { chmod, mkdir } from "node:fs/promises";
18988
+ async function mkdirPrivate(dir) {
18989
+ await mkdir(dir, { recursive: true, mode: 448 });
18990
+ await chmod(dir, 448);
18991
+ }
18992
+
18993
+ // src/state/index.ts
18985
18994
  import { homedir as homedir2 } from "node:os";
18986
18995
  import { dirname, join as join2 } from "node:path";
18987
18996
 
@@ -19176,7 +19185,7 @@ var RunStore = class {
19176
19185
  * which briefly widens the crash window but never writes torn JSON. */
19177
19186
  async save(records, opts) {
19178
19187
  const dir = dirname(this.runsFile);
19179
- await mkdir(dir, { recursive: true });
19188
+ await mkdirPrivate(dir);
19180
19189
  const tmp = join2(dir, `runs.${process.pid}.${randomBytes(4).toString("hex")}.tmp`);
19181
19190
  const renameStep = opts?.rename ?? this.renameFile;
19182
19191
  try {
@@ -19219,7 +19228,7 @@ var RunStore = class {
19219
19228
  const now = /* @__PURE__ */ new Date();
19220
19229
  const runId = `sibyl-${compactIso(now)}-${randomBytes(2).toString("hex")}`;
19221
19230
  const spaceDir = join2(input2.spaceRootDir ?? this.spaceRoot, runId);
19222
- await mkdir(spaceDir, { recursive: true });
19231
+ await mkdirPrivate(spaceDir);
19223
19232
  const record3 = {
19224
19233
  runId,
19225
19234
  kind: input2.kind,
@@ -31989,6 +31998,8 @@ var LINEAGE_CHUNK = 256;
31989
31998
  var LINEAGE_SAMPLES = 32;
31990
31999
  var LINEAGE_UNION_RATIO = 0.6;
31991
32000
  var LINEAGE_MIN_WINDOWS = 3;
32001
+ var LINEAGE_FLOOR_MIN = 8;
32002
+ var LINEAGE_FLOOR_RATIO = 0.3;
31992
32003
  function sampleWindows(text) {
31993
32004
  const total = Math.floor(text.length / LINEAGE_CHUNK);
31994
32005
  if (total < LINEAGE_MIN_WINDOWS) return [];
@@ -32000,17 +32011,33 @@ function sampleWindows(text) {
32000
32011
  }
32001
32012
  function beginLineage(artifact) {
32002
32013
  if (artifact.kind !== "path" || artifact.mtimeMs === void 0) return null;
32003
- const chunks = sampleWindows(artifact.text);
32014
+ return lineageFrom(artifact.text, artifact.mtimeMs);
32015
+ }
32016
+ function lineageFrom(text, mtimeMs) {
32017
+ const chunks = sampleWindows(text);
32004
32018
  if (chunks.length < LINEAGE_MIN_WINDOWS) return null;
32019
+ const unique = new Set(chunks).size;
32005
32020
  return {
32006
32021
  chunks,
32007
- need: Math.max(2, Math.ceil(chunks.length * LINEAGE_UNION_RATIO)),
32008
- mtimeMs: artifact.mtimeMs,
32022
+ unique,
32023
+ need: Math.max(2, Math.ceil(unique * LINEAGE_UNION_RATIO)),
32024
+ mtimeMs,
32009
32025
  matched: /* @__PURE__ */ new Map(),
32010
32026
  copiedAfterMtime: 0,
32011
32027
  timeless: 0
32012
32028
  };
32013
32029
  }
32030
+ function lineageConvinced(lg) {
32031
+ const n = lg.matched.size;
32032
+ return n >= lg.need || n >= LINEAGE_FLOOR_MIN && n >= Math.ceil(lg.unique * LINEAGE_FLOOR_RATIO);
32033
+ }
32034
+ function creditWindows(lg, cands, label) {
32035
+ for (const cand of cands) {
32036
+ for (const chunk of lg.chunks) {
32037
+ if (!lg.matched.has(chunk) && cand.includes(chunk)) lg.matched.set(chunk, label);
32038
+ }
32039
+ }
32040
+ }
32014
32041
  function messageTime(m) {
32015
32042
  const t = m.info?.time?.created;
32016
32043
  return typeof t === "number" ? t : void 0;
@@ -32028,7 +32055,8 @@ function partCandidates(p) {
32028
32055
  }
32029
32056
  function lineageVerdict(lg, artifact) {
32030
32057
  const sources = [.../* @__PURE__ */ new Set([...lg.matched.values()])].slice(0, 3).join("; ");
32031
- return `content-lineage evidence: ${String(lg.matched.size)} of ${String(lg.chunks.length)} verbatim ${String(LINEAGE_CHUNK)}-byte windows of ${artifact.source} appear in convener-chain tool inputs that predate its mtime (${sources}) \u2014 the chain authored this artifact's bytes before/independently of this path`;
32058
+ const majority = lg.matched.size >= lg.need;
32059
+ return `content-lineage evidence: ${String(lg.matched.size)} of ${String(lg.unique)} DISTINCT verbatim ${String(LINEAGE_CHUNK)}-byte windows of ${artifact.source} appear in convener-chain tool inputs that predate its mtime (${sources}) \u2014 ${majority ? "majority" : "absolute floor"} of drafting; repeated identical windows counted once`;
32032
32060
  }
32033
32061
  function scanMessages(rows, convenerIndex, sid, artifact, lg) {
32034
32062
  for (const [mi, m] of rows.entries()) {
@@ -32055,14 +32083,13 @@ function scanMessages(rows, convenerIndex, sid, artifact, lg) {
32055
32083
  continue;
32056
32084
  }
32057
32085
  const label = `${p.tool ?? "tool"}@${sid}:${String(mi)}`;
32058
- for (const cand of partCandidates(p)) {
32086
+ const cands = partCandidates(p);
32087
+ for (const cand of cands) {
32059
32088
  if (artifact.kind === "path" && cand.includes(artifact.text.trim())) {
32060
32089
  return `content-lineage evidence: the FULL text of ${artifact.source} appears verbatim in ${label} (convener-chain tool input, before the file's mtime) \u2014 the chain wrote these bytes elsewhere and moved them here; the chain cannot sit on both sides of this ballot`;
32061
32090
  }
32062
- for (const [wi, chunk] of lg.chunks.entries()) {
32063
- if (!lg.matched.has(wi) && cand.includes(chunk)) lg.matched.set(wi, label);
32064
- }
32065
32091
  }
32092
+ creditWindows(lg, cands, label);
32066
32093
  }
32067
32094
  }
32068
32095
  }
@@ -32117,7 +32144,7 @@ async function assessIndependence(client, directory, convenerSessionID, artifact
32117
32144
  return { status: "NOT-INDEPENDENT", convenerChain: chain, evidence: hit };
32118
32145
  }
32119
32146
  }
32120
- if (lg !== null && lg.matched.size >= lg.need) {
32147
+ if (lg !== null && lineageConvinced(lg)) {
32121
32148
  return { status: "NOT-INDEPENDENT", convenerChain: chain, evidence: lineageVerdict(lg, artifact) };
32122
32149
  }
32123
32150
  if (scanFailed.length > 0 || walkIncomplete) {
@@ -32134,7 +32161,9 @@ async function assessIndependence(client, directory, convenerSessionID, artifact
32134
32161
  }
32135
32162
  function lineageFaceNote(lg) {
32136
32163
  if (lg === null) return "";
32137
- const parts = [`${String(lg.matched.size)}/${String(lg.chunks.length)} lineage windows matched (need ${String(lg.need)})`];
32164
+ const parts = [
32165
+ `${String(lg.matched.size)}/${String(lg.unique)} distinct lineage windows matched (majority need ${String(lg.need)}, floor ${String(LINEAGE_FLOOR_MIN)}@${String(LINEAGE_FLOOR_RATIO * 100)}%)`
32166
+ ];
32138
32167
  if (lg.copiedAfterMtime > 0) parts.push(`${String(lg.copiedAfterMtime)} part(s) excluded as post-mtime copies`);
32139
32168
  if (lg.timeless > 0) parts.push(`${String(lg.timeless)} part(s) lacked timestamps`);
32140
32169
  return `; content-lineage: ${parts.join(", ")}`;
@@ -32850,7 +32879,7 @@ function ballotTag(vote) {
32850
32879
  import { spawn } from "node:child_process";
32851
32880
  import { randomBytes as randomBytes2 } from "node:crypto";
32852
32881
  import { existsSync as existsSync2 } from "node:fs";
32853
- import { mkdir as mkdir3, writeFile as writeFile4 } from "node:fs/promises";
32882
+ import { writeFile as writeFile4 } from "node:fs/promises";
32854
32883
  import { dirname as dirname3, join as join6 } from "node:path";
32855
32884
  import { fileURLToPath } from "node:url";
32856
32885
  var REVIEW_TOOL_NAME = "sibyl_review";
@@ -32859,12 +32888,16 @@ function nodeBinary() {
32859
32888
  }
32860
32889
  function cliCommand(thisUrl, node2) {
32861
32890
  const moduleDir = dirname3(fileURLToPath(thisUrl));
32862
- const repoRoot = dirname3(dirname3(moduleDir));
32863
- const distCli = join6(repoRoot, "dist", "cli.js");
32864
- if (existsSync2(distCli)) return { bin: node2, args: [distCli] };
32865
- const srcCli = join6(repoRoot, "src", "cli.ts");
32866
- const hooks = join6(repoRoot, "tools", "register-ts.mjs");
32867
- return { bin: node2, args: ["--import", hooks, srcCli] };
32891
+ const roots = [dirname3(moduleDir), dirname3(dirname3(moduleDir))];
32892
+ for (const root of roots) {
32893
+ const distCli = join6(root, "dist", "cli.js");
32894
+ if (existsSync2(distCli)) return { bin: node2, args: [distCli] };
32895
+ const srcCli = join6(root, "src", "cli.ts");
32896
+ const hooks = join6(root, "tools", "register-ts.mjs");
32897
+ if (existsSync2(srcCli) && existsSync2(hooks)) return { bin: node2, args: ["--import", hooks, srcCli] };
32898
+ }
32899
+ const repoRoot = roots[1] ?? dirname3(dirname3(moduleDir));
32900
+ return { bin: node2, args: ["--import", join6(repoRoot, "tools", "register-ts.mjs"), join6(repoRoot, "src", "cli.ts")] };
32868
32901
  }
32869
32902
  async function reviewExecute(deps, args, context) {
32870
32903
  try {
@@ -32873,7 +32906,7 @@ async function reviewExecute(deps, args, context) {
32873
32906
  const now = /* @__PURE__ */ new Date();
32874
32907
  const runId = `sibyl-${compactIso(now)}-${randomBytes2(2).toString("hex")}`;
32875
32908
  const runDir = join6(opts.lane.runRoot, `sibyl-run-${runId}`);
32876
- await mkdir3(runDir, { recursive: true });
32909
+ await mkdirPrivate(runDir);
32877
32910
  const artifact = await readArtifact(args.target, context.directory);
32878
32911
  if (!artifact.ok) return `SIBYL review: ${artifact.error}`;
32879
32912
  let targetArg;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sibyl-system",
3
- "version": "1.3.5",
3
+ "version": "1.4.0",
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",
@@ -35,7 +35,7 @@
35
35
  "prepack": "npm run build"
36
36
  },
37
37
  "dependencies": {
38
- "@opencode-ai/plugin": "1.15.13",
38
+ "@opencode-ai/plugin": "1.19.4",
39
39
  "zod": "^4"
40
40
  },
41
41
  "devDependencies": {
@@ -45,5 +45,11 @@
45
45
  },
46
46
  "bin": {
47
47
  "sibyl-chamber": "./dist/cli.js"
48
+ },
49
+ "overrides": {
50
+ "toml": "5.0.1"
51
+ },
52
+ "engines": {
53
+ "node": ">=22"
48
54
  }
49
55
  }