severally-mcp 2.4.0 → 2.4.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.
Files changed (40) hide show
  1. package/README.ja.md +44 -19
  2. package/README.md +44 -18
  3. package/package.json +3 -1
  4. package/plugins/severally/.claude-plugin/plugin.json +1 -1
  5. package/plugins/severally/.codex-plugin/plugin.json +1 -1
  6. package/plugins/severally/dist/severally-mcp.mjs +585 -255
  7. package/plugins/severally/skills/_template/reference.md.tmpl +2 -2
  8. package/plugins/severally/skills/antigravity/severally/reference.md +2 -2
  9. package/plugins/severally/skills/claude/severally/reference.md +2 -2
  10. package/plugins/severally/skills/codex/severally/reference.md +2 -2
  11. package/plugins/severally/skills/opencode/severally/reference.md +2 -2
  12. package/scripts/build.mjs +57 -54
  13. package/scripts/doctor.mjs +15 -4
  14. package/scripts/install.mjs +73 -9
  15. package/scripts/live-check.mjs +11 -5
  16. package/scripts/live-mcp-check.mjs +10 -4
  17. package/src/adapters/antigravity-sandbox.mjs +24 -6
  18. package/src/adapters/antigravity.mjs +80 -17
  19. package/src/adapters/claude-code.mjs +49 -8
  20. package/src/adapters/codex.mjs +56 -6
  21. package/src/adapters/opencode-sandbox.mjs +20 -2
  22. package/src/adapters/opencode.mjs +115 -19
  23. package/src/brief.mjs +84 -7
  24. package/src/consultants.mjs +184 -25
  25. package/src/cross-spawn-escape.d.ts +7 -0
  26. package/src/export.mjs +50 -6
  27. package/src/expose-paths.mjs +149 -7
  28. package/src/failures.mjs +8 -1
  29. package/src/jobs.mjs +578 -132
  30. package/src/jsonc.mjs +11 -3
  31. package/src/parse-result.mjs +91 -14
  32. package/src/platform.mjs +52 -2
  33. package/src/policy.mjs +151 -28
  34. package/src/redact.mjs +22 -3
  35. package/src/result-schema.mjs +13 -0
  36. package/src/run.mjs +158 -12
  37. package/src/schema.mjs +84 -11
  38. package/src/server.mjs +49 -14
  39. package/src/status.mjs +16 -4
  40. package/src/store.mjs +287 -6
@@ -62,7 +62,7 @@ function stripJsonc(text2) {
62
62
  }
63
63
  };
64
64
  while (i < text2.length) {
65
- const c = text2[i];
65
+ const c = text2.charAt(i);
66
66
  const next = text2[i + 1];
67
67
  if (inString) {
68
68
  out.push(c);
@@ -110,6 +110,7 @@ function parseJsonc(text2) {
110
110
  }
111
111
  var init_jsonc = __esm({
112
112
  "src/jsonc.mjs"() {
113
+ "use strict";
113
114
  }
114
115
  });
115
116
 
@@ -634,7 +635,10 @@ function findCommand(command, { cwd = process.cwd(), env = process.env } = {}) {
634
635
  if (typeof command !== "string" || !command) return null;
635
636
  cwd = path.resolve(cwd);
636
637
  const windows = process.platform === "win32";
637
- const value = (name) => env[windows ? Object.keys(env).find((key) => key.toUpperCase() === name) : name];
638
+ const value = (name) => {
639
+ const key = windows ? Object.keys(env).find((k) => k.toUpperCase() === name) : name;
640
+ return key === void 0 ? void 0 : env[key];
641
+ };
638
642
  const explicit = command.includes("/") || windows && command.includes("\\");
639
643
  const dirs = explicit ? [cwd] : [...windows ? [cwd] : [], ...(value("PATH") ?? "").split(path.delimiter).filter(Boolean)];
640
644
  for (const dir of dirs) {
@@ -654,7 +658,8 @@ function findCommand(command, { cwd = process.cwd(), env = process.env } = {}) {
654
658
  return null;
655
659
  }
656
660
  function execCommandSync(command, args, options = {}) {
657
- const opts = { windowsHide: true, encoding: "utf8", ...options };
661
+ const encoding = "utf8";
662
+ const opts = { windowsHide: true, encoding, ...options };
658
663
  const batch = batchInvocation(command, args, opts);
659
664
  const result = batch ? nativeSpawnSync(batch.command, batch.args, batch.options) : import_cross_spawn.default.sync(command, args, opts);
660
665
  if (result.error) throw result.error;
@@ -666,6 +671,7 @@ function execCommandSync(command, args, options = {}) {
666
671
  var import_which, import_cross_spawn, import_escape;
667
672
  var init_platform = __esm({
668
673
  "src/platform.mjs"() {
674
+ "use strict";
669
675
  import_which = __toESM(require_which(), 1);
670
676
  import_cross_spawn = __toESM(require_cross_spawn(), 1);
671
677
  import_escape = __toESM(require_escape(), 1);
@@ -691,7 +697,10 @@ function modelVersion(model) {
691
697
  const at = words.findIndex(isDigitWord);
692
698
  if (at === -1) return null;
693
699
  const digits = [];
694
- for (let i = at; i < words.length && isDigitWord(words[i]); i++) digits.push(Number(words[i].replace(/^v/, "")));
700
+ for (const w of words.slice(at)) {
701
+ if (!isDigitWord(w)) break;
702
+ digits.push(Number(w.replace(/^v/, "")));
703
+ }
695
704
  return digits;
696
705
  }
697
706
  function vendorOf(model, explicit = null) {
@@ -715,7 +724,9 @@ function byTier(cands, tier) {
715
724
  if (!tier.family(cands)) return cands;
716
725
  const rank = (c) => {
717
726
  const words = modelWords(c.model);
718
- return tier.last ? tier.words.indexOf(words.at(-1)) : tier.words.findIndex((w) => words.includes(w));
727
+ const last = words.at(-1);
728
+ if (tier.last) return last === void 0 ? -1 : tier.words.indexOf(last);
729
+ return tier.words.findIndex((w) => words.includes(w));
719
730
  };
720
731
  const ranks = cands.map(rank);
721
732
  if (ranks.includes(-1) || new Set(ranks).size === 1) return cands;
@@ -723,11 +734,18 @@ function byTier(cands, tier) {
723
734
  return cands.filter((_, i) => ranks[i] === best);
724
735
  }
725
736
  function byVersion(cands) {
726
- const stem = modelStem(cands[0].model).join("-");
727
- const versions = cands.map((c) => modelVersion(c.model));
728
- if (versions.includes(null) || cands.some((c) => modelStem(c.model).join("-") !== stem)) return cands;
729
- const best = versions.reduce((a, b) => compareVersions(a, b) >= 0 ? a : b);
730
- return cands.filter((_, i) => compareVersions(versions[i], best) === 0);
737
+ const [first] = cands;
738
+ if (!first) return cands;
739
+ const stem = modelStem(first.model).join("-");
740
+ if (cands.some((c) => modelStem(c.model).join("-") !== stem)) return cands;
741
+ const versioned = [];
742
+ for (const c of cands) {
743
+ const version2 = modelVersion(c.model);
744
+ if (version2 === null) return cands;
745
+ versioned.push({ c, version: version2 });
746
+ }
747
+ const best = versioned.map((x) => x.version).reduce((a, b) => compareVersions(a, b) >= 0 ? a : b);
748
+ return versioned.filter((x) => compareVersions(x.version, best) === 0).map((x) => x.c);
731
749
  }
732
750
  function rankCandidates(cands, ranking = []) {
733
751
  let left = byRanking(cands, ranking);
@@ -736,7 +754,7 @@ function rankCandidates(cands, ranking = []) {
736
754
  left = byTier(left, tier);
737
755
  }
738
756
  if (left.length > 1) left = byVersion(left);
739
- return left.length === 1 ? left[0] : null;
757
+ return left.length === 1 ? left[0] ?? null : null;
740
758
  }
741
759
  function resolveConsultant(input2, consultants, ranking = []) {
742
760
  const key = normalizeName(input2);
@@ -747,8 +765,9 @@ function resolveConsultant(input2, consultants, ranking = []) {
747
765
  if (CLI_ALIASES[key]) cands = consultants.filter((c) => c.cli === CLI_ALIASES[key]);
748
766
  else if (VENDOR_WORDS[key]) cands = consultants.filter((c) => c.vendor === VENDOR_WORDS[key]);
749
767
  else cands = consultants.filter((c) => matchesWords(c.model, input2));
750
- if (cands.length === 0) return { none: true };
751
- if (cands.length === 1) return { consultant: cands[0] };
768
+ const [only] = cands;
769
+ if (!only) return { none: true };
770
+ if (cands.length === 1) return { consultant: only };
752
771
  const winner = rankCandidates(cands, ranking);
753
772
  return winner ? { consultant: winner } : { ambiguous: cands };
754
773
  }
@@ -773,7 +792,8 @@ function buildConsultants(config2) {
773
792
  for (const key of Object.keys(cfg)) {
774
793
  if (!TOP_KEYS.includes(key)) fault(`unknown top-level key "${key}" (allowed: ${TOP_KEYS.join(", ")})`);
775
794
  }
776
- if (isObject(cfg.server) && cfg.server.network !== void 0 && !["on", "off"].includes(cfg.server.network)) {
795
+ const networkModes = ["on", "off"];
796
+ if (isObject(cfg.server) && cfg.server.network !== void 0 && !networkModes.includes(cfg.server.network)) {
777
797
  fault('server.network must be "on" or "off"');
778
798
  }
779
799
  let ranking = [];
@@ -782,11 +802,12 @@ function buildConsultants(config2) {
782
802
  ranking = cfg.ranking.map((r) => r.trim());
783
803
  else fault("ranking must be an array of non-empty strings");
784
804
  }
805
+ const cliIds = CLI_IDS;
785
806
  if (cfg.clis !== void 0) {
786
807
  if (!isObject(cfg.clis)) fault("clis must be an object keyed by CLI");
787
808
  else {
788
809
  for (const [id2, def] of Object.entries(cfg.clis)) {
789
- if (!CLI_IDS.includes(id2)) {
810
+ if (!cliIds.includes(id2)) {
790
811
  problems.push(`clis.${id2}: unknown CLI (known: ${CLI_IDS.join(", ")})`);
791
812
  continue;
792
813
  }
@@ -840,7 +861,7 @@ function buildConsultants(config2) {
840
861
  }
841
862
  for (const k of Object.keys(def))
842
863
  if (!CONSULTANT_KEYS.includes(k)) problems.push(`${at}.${k}: unknown key (allowed: ${CONSULTANT_KEYS.join(", ")})`);
843
- if (!CLI_IDS.includes(def.cli)) problems.push(`${at}.cli: must be one of ${CLI_IDS.join(", ")}`);
864
+ if (!cliIds.includes(def.cli)) problems.push(`${at}.cli: must be one of ${CLI_IDS.join(", ")}`);
844
865
  if (typeof def.model !== "string" || def.model.trim() === "") problems.push(`${at}.model: required`);
845
866
  for (const k of ["effort", "vendor", "note"]) {
846
867
  if (def[k] !== void 0 && typeof def[k] !== "string") problems.push(`${at}.${k}: must be a string`);
@@ -858,15 +879,19 @@ function buildConsultants(config2) {
858
879
  broken[name] = problems.slice(before);
859
880
  continue;
860
881
  }
861
- const pair = `${def.cli}
862
- ${def.model.trim()}`;
882
+ const valid = (
883
+ /** @type {ConsultantDef} */
884
+ def
885
+ );
886
+ const pair = `${valid.cli}
887
+ ${valid.model.trim()}`;
863
888
  if (seen.has(pair)) {
864
889
  problems.push(`${at}: same cli and model as consultants.${seen.get(pair)}; asking one model twice adds no independence`);
865
890
  broken[name] = problems.slice(before);
866
891
  continue;
867
892
  }
868
893
  seen.set(pair, name);
869
- consultants.push(make(name, def));
894
+ consultants.push(make(name, valid));
870
895
  }
871
896
  }
872
897
  return { consultants: Object.freeze(consultants), ranking: Object.freeze(ranking), problems, global: global2, broken, brokenClis };
@@ -880,7 +905,7 @@ function make(name, def) {
880
905
  effort: typeof def.effort === "string" && def.effort.trim() !== "" ? def.effort.trim() : null,
881
906
  timeoutMs: typeof def.timeout_ms === "number" ? clamp(def.timeout_ms, 1e3, 72e5) : null,
882
907
  maxBudgetUsd: typeof def.max_budget_usd === "number" ? clamp(def.max_budget_usd, 0.05, 20) : null,
883
- vendor: vendorOf(model, def.vendor ?? null),
908
+ vendor: vendorOf(model, def.vendor),
884
909
  enabled: def.enabled !== false,
885
910
  note: typeof def.note === "string" && def.note.trim() !== "" ? def.note.trim() : null
886
911
  });
@@ -888,6 +913,7 @@ function make(name, def) {
888
913
  var CLI_IDS, DEFAULT_MODELS, CLI_VENDORS, table, CLI_ALIASES, VENDOR_WORDS, STEM_VENDORS, sameStem, TIERS, RESERVED_NAMES, isDigitWord, containsRun, compareVersions, TOP_KEYS, CLI_KEYS, CONSULTANT_KEYS, isObject, clamp;
889
914
  var init_consultants = __esm({
890
915
  "src/consultants.mjs"() {
916
+ "use strict";
891
917
  CLI_IDS = Object.freeze(["codex", "claude-code", "antigravity", "opencode"]);
892
918
  DEFAULT_MODELS = Object.freeze({
893
919
  codex: "gpt-6-astra",
@@ -960,7 +986,7 @@ function isInstalled(command) {
960
986
  }
961
987
  function resolveCaller(raw) {
962
988
  const key = normalizeName(raw);
963
- return typeof key === "string" && Object.hasOwn(CALLER_ALIASES, key) ? CALLER_ALIASES[key] : null;
989
+ return typeof key === "string" ? CALLER_ALIASES[key] ?? null : null;
964
990
  }
965
991
  function detectCaller(env = process.env) {
966
992
  if (env.CLAUDECODE === "1" || env.CLAUDE_CODE_ENTRYPOINT) return "claude-code";
@@ -1020,6 +1046,11 @@ function isProtectedPath(abs) {
1020
1046
  function consultantByName(name) {
1021
1047
  return POLICY.consultants.find((c) => c.name === name);
1022
1048
  }
1049
+ function consultantNamed(name) {
1050
+ const c = consultantByName(name);
1051
+ if (!c) throw new Error(`no consultant named "${name}"`);
1052
+ return c;
1053
+ }
1023
1054
  function consultantAvailable(c) {
1024
1055
  return c.enabled && POLICY.clis[c.cli].available;
1025
1056
  }
@@ -1051,17 +1082,20 @@ function limitsSummary() {
1051
1082
  max_concurrent_jobs: POLICY.maxConcurrent,
1052
1083
  max_wait_ms: POLICY.maxWaitMs,
1053
1084
  input_char_budget: POLICY.input.totalCharsMax,
1054
- // Stated so it is true of all three consultants. Reading is allowed for
1055
- // every one of them -- what keeps a consultation brief-only is that the
1056
- // child starts in an empty working directory and is never told where the
1057
- // repository is, not that it cannot read. Codex is additionally sandboxed
1058
- // read-only rather than execution-free, so do not advertise "no execution".
1059
- consultant_permissions: "web search/browse and reading local files allowed, but the consultant starts in an empty working directory and is not told where your repository is -- unless context.expose_paths names files or directories, which are copied read-only into that working directory and are then the only part of your repository it has; file edits, network access, MCP tools and further consultations denied; the Codex consultant additionally has a read-only shell"
1085
+ // Stated so it is true of all four CLIs. Reading is allowed for every one
1086
+ // of them -- what keeps a consultation brief-only is that the child starts
1087
+ // in an empty working directory and is never told where the repository
1088
+ // is, not that it cannot read. Only Claude Code drops its whole-disk read
1089
+ // when files are exposed (claude-code.mjs). Codex is additionally
1090
+ // sandboxed read-only rather than execution-free, so do not advertise
1091
+ // "no execution".
1092
+ consultant_permissions: "web search/browse and reading local files allowed; the consultant starts in an empty working directory and is not told where your repository is. context.expose_paths copies the files or directories it names read-only into that working directory, but that is not a read boundary: apart from the protected credential paths, Codex, Antigravity and OpenCode can read any file your account can, and so can Claude Code when nothing is exposed; file edits, network access, MCP tools and further consultations denied; the Codex consultant additionally has a read-only shell"
1060
1093
  };
1061
1094
  }
1062
1095
  var CONFIG_HOME, CONFIG_PATH, configError, CONFIG, BUILT, FROM, LOCAL, usable, cfgCli, cfgServer, num, limit, str, CALLER_ALIASES, CALLER_INPUTS, MODES, VERDICTS, STANCES, binFor, disabledNote, isEnabled, CODEX_BIN, CLAUDE_BIN, AGY_BIN, OPENCODE_BIN, extras, POLICY, PROTECTED_HOME_PATHS, PROTECTED_SEVERALLY_PATHS, PROTECTED_FILE_NAMES, escapeRe, PATHS_CASE_INSENSITIVE, artifactKinds;
1063
1096
  var init_policy = __esm({
1064
1097
  "src/policy.mjs"() {
1098
+ "use strict";
1065
1099
  init_jsonc();
1066
1100
  init_platform();
1067
1101
  init_consultants();
@@ -1072,7 +1106,8 @@ var init_policy = __esm({
1072
1106
  try {
1073
1107
  return parseJsonc(fs2.readFileSync(CONFIG_PATH, "utf8"));
1074
1108
  } catch (err) {
1075
- if (err.code !== "ENOENT") configError = `${CONFIG_PATH}: ${err.message}`;
1109
+ const code = err instanceof Error && "code" in err ? err.code : void 0;
1110
+ if (code !== "ENOENT") configError = `${CONFIG_PATH}: ${err instanceof Error ? err.message : String(err)}`;
1076
1111
  return {};
1077
1112
  }
1078
1113
  })();
@@ -1083,17 +1118,22 @@ var init_policy = __esm({
1083
1118
  }
1084
1119
  FROM = configError ? buildConsultants({}) : BUILT;
1085
1120
  LOCAL = configError ? [] : BUILT.problems.filter((p) => !BUILT.global.includes(p));
1086
- usable = configError ? {} : CONFIG;
1121
+ usable = configError || !isObject(CONFIG) ? {} : CONFIG;
1087
1122
  cfgCli = (id2) => {
1088
- if (Object.hasOwn(BUILT.brokenClis, id2) && !configError) {
1123
+ const broken = Object.hasOwn(BUILT.brokenClis, id2) ? BUILT.brokenClis[id2] : void 0;
1124
+ if (broken && !configError) {
1089
1125
  return {
1090
1126
  enabled: false,
1091
- note: `turned off: the config file has a mistake in clis.${id2}: ${BUILT.brokenClis[id2].join("; ")}`
1127
+ note: `turned off: the config file has a mistake in clis.${id2}: ${broken.join("; ")}`
1092
1128
  };
1093
1129
  }
1094
- return usable.clis && typeof usable.clis === "object" ? usable.clis[id2] ?? {} : {};
1130
+ const def = isObject(usable.clis) ? usable.clis[id2] : void 0;
1131
+ return isObject(def) ? (
1132
+ /** @type {CliSection} */
1133
+ def
1134
+ ) : {};
1095
1135
  };
1096
- cfgServer = usable.server && typeof usable.server === "object" ? usable.server : {};
1136
+ cfgServer = isObject(usable.server) ? usable.server : {};
1097
1137
  num = (name, dflt, min, max) => {
1098
1138
  const raw = process.env[name];
1099
1139
  if (raw === void 0 || raw === "") return dflt;
@@ -1131,7 +1171,8 @@ var init_policy = __esm({
1131
1171
  CALLER_INPUTS = Object.freeze(Object.keys(CALLER_ALIASES));
1132
1172
  MODES = ["explore", "review", "debate"];
1133
1173
  VERDICTS = ["unverified", "confirmed", "not_applicable", "unverifiable"];
1134
- STANCES = ["proceed", "do_not_proceed", "alternative", "undetermined"];
1174
+ STANCES = /** @type {const} */
1175
+ ["proceed", "do_not_proceed", "alternative", "undetermined"];
1135
1176
  binFor = (id2, envName, dflt) => {
1136
1177
  const fromEnv = process.env[envName];
1137
1178
  if (fromEnv !== void 0 && fromEnv !== "") return expandHome(fromEnv);
@@ -1151,7 +1192,10 @@ var init_policy = __esm({
1151
1192
  CLAUDE_BIN = binFor("claude-code", "SEVERALLY_CLAUDE_BIN", "claude");
1152
1193
  AGY_BIN = binFor("antigravity", "SEVERALLY_AGY_BIN", "agy");
1153
1194
  OPENCODE_BIN = binFor("opencode", "SEVERALLY_OPENCODE_BIN", "opencode");
1154
- extras = (id2) => ({ extraArgs: cfgCli(id2).args ?? [], extraEnv: cfgCli(id2).env ?? {} });
1195
+ extras = (id2) => ({
1196
+ extraArgs: cfgCli(id2).args ?? [],
1197
+ extraEnv: cfgCli(id2).env ?? {}
1198
+ });
1155
1199
  POLICY = Object.freeze({
1156
1200
  home: str("SEVERALLY_HOME", path2.join(os.homedir(), ".severally")),
1157
1201
  // Per CLI: how to launch it and whether it can run here. What runs on it is
@@ -32610,12 +32654,12 @@ var init_ajvProvider_CEoC_sr = __esm({
32610
32654
  },
32611
32655
  code(cxt) {
32612
32656
  const { gen, data, schema, parentSchema, it } = cxt;
32613
- const { oneOf } = parentSchema;
32657
+ const { oneOf: oneOf2 } = parentSchema;
32614
32658
  if (!it.opts.discriminator) throw new Error("discriminator: requires discriminator option");
32615
32659
  const tagName = schema.propertyName;
32616
32660
  if (typeof tagName != "string") throw new Error("discriminator: requires propertyName");
32617
32661
  if (schema.mapping) throw new Error("discriminator: mapping is not supported");
32618
- if (!oneOf) throw new Error("discriminator: requires oneOf keyword");
32662
+ if (!oneOf2) throw new Error("discriminator: requires oneOf keyword");
32619
32663
  const valid = gen.let("valid", false);
32620
32664
  const tag = gen.const("tag", (0, codegen_1._)`${data}${(0, codegen_1.getProperty)(tagName)}`);
32621
32665
  gen.if((0, codegen_1._)`typeof ${tag} == "string"`, () => validateMapping(), () => cxt.error(false, {
@@ -32653,8 +32697,8 @@ var init_ajvProvider_CEoC_sr = __esm({
32653
32697
  const oneOfMapping = {};
32654
32698
  const topRequired = hasRequired(parentSchema);
32655
32699
  let tagRequired = true;
32656
- for (let i = 0; i < oneOf.length; i++) {
32657
- let sch = oneOf[i];
32700
+ for (let i = 0; i < oneOf2.length; i++) {
32701
+ let sch = oneOf2[i];
32658
32702
  if ((sch === null || sch === void 0 ? void 0 : sch.$ref) && !(0, util_1.schemaHasRulesButRef)(sch, it.self.RULES)) {
32659
32703
  const ref = sch.$ref;
32660
32704
  sch = compile_1.resolveRef.call(it.self, it.schemaEnv.root, it.baseId, ref);
@@ -35561,11 +35605,11 @@ var init_mcp_DIH4cS6P = __esm({
35561
35605
  * @param errorMessage - The error message.
35562
35606
  * @returns The tool error result.
35563
35607
  */
35564
- createToolError(errorMessage2) {
35608
+ createToolError(errorMessage3) {
35565
35609
  return {
35566
35610
  content: [{
35567
35611
  type: "text",
35568
- text: errorMessage2
35612
+ text: errorMessage3
35569
35613
  }],
35570
35614
  isError: true
35571
35615
  };
@@ -36508,8 +36552,8 @@ var init_stdio = __esm({
36508
36552
  import path3 from "node:path";
36509
36553
  function charCount(req) {
36510
36554
  let n = req.question.length + (req.objective?.length ?? 0);
36511
- for (const c of req.constraints) n += c.length;
36512
- for (const c of req.success_criteria) n += c.length;
36555
+ for (const c of req.constraints ?? []) n += c.length;
36556
+ for (const c of req.success_criteria ?? []) n += c.length;
36513
36557
  for (const f of req.context.facts) n += f.length;
36514
36558
  for (const c of req.context.counterpoints) n += c.length;
36515
36559
  if (req.context.proposal) n += req.context.proposal.length;
@@ -36522,19 +36566,27 @@ function parseRequest(raw, { isFollowup = false, parentMode = null, groupFollowu
36522
36566
  const issues = parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`);
36523
36567
  throw new RequestError(`request failed validation: ${issues.join("; ")}`, "invalid_request", issues);
36524
36568
  }
36525
- const req = parsed.data;
36526
- for (const k of ["constraints", "success_criteria"]) {
36527
- if (req.context[k] === void 0) continue;
36569
+ const req = (
36570
+ /** @type {DraftRequest} */
36571
+ parsed.data
36572
+ );
36573
+ const hoist = (k) => {
36574
+ const inner = req.context[k];
36575
+ if (inner === void 0) return;
36528
36576
  if (req[k] !== void 0) {
36529
36577
  throw new RequestError(`pass ${k} once: at the top of the request or inside context, not both`, "invalid_request");
36530
36578
  }
36531
- req[k] = req.context[k];
36579
+ req[k] = inner;
36532
36580
  delete req.context[k];
36533
- }
36581
+ };
36582
+ hoist("constraints");
36583
+ hoist("success_criteria");
36534
36584
  req.network ??= "on";
36535
36585
  req.success_criteria ??= [];
36536
36586
  req.constraints ??= [];
36537
- for (const k of ["facts", "counterpoints", "artifacts"]) req.context[k] ??= [];
36587
+ req.context.facts ??= [];
36588
+ req.context.counterpoints ??= [];
36589
+ req.context.artifacts ??= [];
36538
36590
  const hasSingle = req.target !== void 0 && req.target !== null;
36539
36591
  const hasMany = Array.isArray(req.targets) && req.targets.length > 0;
36540
36592
  if (hasSingle && hasMany) {
@@ -36605,7 +36657,8 @@ function parseRequest(raw, { isFollowup = false, parentMode = null, groupFollowu
36605
36657
  );
36606
36658
  }
36607
36659
  req.targets = unique;
36608
- req.target = unique[0];
36660
+ req.target = /** @type {string} */
36661
+ unique[0];
36609
36662
  req.fanout = unique.length > 1;
36610
36663
  req.caller = resolveCaller(req.caller ?? "") ?? null;
36611
36664
  req.caller_model = req.caller_model ?? null;
@@ -36656,6 +36709,7 @@ function parseRequest(raw, { isFollowup = false, parentMode = null, groupFollowu
36656
36709
  var L, trimmed, artifactSchema, exposedPathSchema, gitRevision, contextSchema, targetSpec, INITIATORS, NETWORK_MODES, requestSchema, RequestError;
36657
36710
  var init_schema = __esm({
36658
36711
  "src/schema.mjs"() {
36712
+ "use strict";
36659
36713
  init_zod();
36660
36714
  init_policy();
36661
36715
  init_consultants();
@@ -36740,6 +36794,11 @@ var init_schema = __esm({
36740
36794
  followup_to: external_exports.string().trim().max(80).nullish()
36741
36795
  }).strict();
36742
36796
  RequestError = class extends Error {
36797
+ /**
36798
+ * @param {string} message
36799
+ * @param {string} [code]
36800
+ * @param {unknown[]} [details]
36801
+ */
36743
36802
  constructor(message, code = "invalid_request", details = void 0) {
36744
36803
  super(message);
36745
36804
  this.name = "RequestError";
@@ -36754,6 +36813,7 @@ var init_schema = __esm({
36754
36813
  var s, enumOf, obj, arr, CONSULT_RESULT_SCHEMA, RESULT_KEYS;
36755
36814
  var init_result_schema = __esm({
36756
36815
  "src/result-schema.mjs"() {
36816
+ "use strict";
36757
36817
  s = (desc) => ({ type: "string", description: desc });
36758
36818
  enumOf = (values, desc) => ({ type: "string", enum: values, description: desc });
36759
36819
  obj = (properties, description) => ({
@@ -36844,14 +36904,14 @@ var init_result_schema = __esm({
36844
36904
  function looksLikeCode(name, value) {
36845
36905
  if (CODE_WORDS.has(value.toLowerCase()) || value.startsWith("${") || /^\$[A-Z_][A-Z0-9_]*$/.test(value) || /^(?:process\.env\.|os\.environ\b)/.test(value))
36846
36906
  return true;
36847
- if (value.length >= 8 && /^PASSW(?:OR)?D$/i.test(name.split("_").at(-1))) return false;
36907
+ if (value.length >= 8 && /^PASSW(?:OR)?D$/i.test(name.split("_").at(-1) ?? "")) return false;
36848
36908
  return /^[A-Za-z_][\w$]*(?:\.[A-Za-z_$][\w$]*)+$/.test(value) || /^[A-Za-z_][\w$.]*\(/.test(value) || value.startsWith("&");
36849
36909
  }
36850
36910
  function redact(value) {
36851
36911
  if (value === null || value === void 0) return value;
36852
36912
  if (typeof value === "string") {
36853
36913
  let out = value;
36854
- for (const [re, rep] of PATTERNS) out = out.replace(re, rep);
36914
+ for (const [re, rep] of PATTERNS) out = typeof rep === "string" ? out.replace(re, rep) : out.replace(re, rep);
36855
36915
  return out;
36856
36916
  }
36857
36917
  if (Array.isArray(value)) return value.map(redact);
@@ -36865,6 +36925,7 @@ function redact(value) {
36865
36925
  var CODE_WORDS, PATTERNS;
36866
36926
  var init_redact = __esm({
36867
36927
  "src/redact.mjs"() {
36928
+ "use strict";
36868
36929
  CODE_WORDS = /* @__PURE__ */ new Set([
36869
36930
  "true",
36870
36931
  "false",
@@ -36960,7 +37021,7 @@ function statEntry(source) {
36960
37021
  try {
36961
37022
  stat = fs3.lstatSync(source);
36962
37023
  } catch (err) {
36963
- throw new ExposeError(`expose_paths entry "${source}" cannot be read: ${err.message}`, "not_found");
37024
+ throw new ExposeError(`expose_paths entry "${source}" cannot be read: ${messageOf(err)}`, "not_found");
36964
37025
  }
36965
37026
  if (stat.isSymbolicLink()) {
36966
37027
  throw new ExposeError(`expose_paths entry "${source}" is a symlink; name the path it points at instead`, "invalid_type");
@@ -36975,7 +37036,7 @@ function walkDir(root, dir, onFile, totals, skipped) {
36975
37036
  try {
36976
37037
  items = fs3.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
36977
37038
  } catch (err) {
36978
- throw new ExposeError(`expose_paths entry "${dir}" cannot be read: ${err.message}`, "not_found");
37039
+ throw new ExposeError(`expose_paths entry "${dir}" cannot be read: ${messageOf(err)}`, "not_found");
36979
37040
  }
36980
37041
  for (const item of items) {
36981
37042
  const child = path4.join(dir, item.name);
@@ -37002,7 +37063,11 @@ function walkDir(root, dir, onFile, totals, skipped) {
37002
37063
  function byPathParts(a, b) {
37003
37064
  const x = a.split("/");
37004
37065
  const y = b.split("/");
37005
- for (let i = 0; i < Math.min(x.length, y.length); i++) if (x[i] !== y[i]) return x[i] < y[i] ? -1 : 1;
37066
+ for (const [i, part] of x.entries()) {
37067
+ const other = y[i];
37068
+ if (other === void 0) break;
37069
+ if (part !== other) return part < other ? -1 : 1;
37070
+ }
37006
37071
  return x.length - y.length;
37007
37072
  }
37008
37073
  function walkGit(dir, exposedAs, onFile, totals, skipped) {
@@ -37213,7 +37278,7 @@ function reexposeChange(workdir, entry, manifest) {
37213
37278
  const changes = [];
37214
37279
  for (const c of entry.changes ?? []) {
37215
37280
  const m = /^(.*) \(([^()]+)\)$/.exec(c);
37216
- const p = m ? m[1] : c;
37281
+ const p = m?.[1] ?? c;
37217
37282
  if (p.includes("[REDACTED")) {
37218
37283
  manifest.skipped.push({ source: path4.join(entry.source, p), reason: "masked", exposed_as: `workspace/repo/${p}` });
37219
37284
  continue;
@@ -37268,10 +37333,7 @@ function git(cwd, args) {
37268
37333
  env: { ...env, GIT_OPTIONAL_LOCKS: "0" }
37269
37334
  });
37270
37335
  if (r.error || r.status !== 0) {
37271
- const why = (
37272
- /** @type {NodeJS.ErrnoException} */
37273
- r.error?.code === "ENOENT" ? "git is not on PATH" : String(r.stderr ?? r.error?.message ?? "").trim()
37274
- );
37336
+ const why = r.error && "code" in r.error && r.error.code === "ENOENT" ? "git is not on PATH" : String(r.stderr ?? r.error?.message ?? "").trim();
37275
37337
  throw new ExposeError(`context.expose_diff: git ${args[0]} failed in "${cwd}": ${why}`, "not_found");
37276
37338
  }
37277
37339
  return r.stdout;
@@ -37283,7 +37345,11 @@ function exposeDiff(workdir, { repo, base = "HEAD" }, manifest, maxChars) {
37283
37345
  git(top, ["diff", "--name-status", "-z", "--no-renames", "--no-relative", "--no-ext-diff", "--no-textconv", base, "--"])
37284
37346
  );
37285
37347
  const changes = [];
37286
- for (let i = 0; i + 1 < tokens.length; i += 2) changes.push({ status: tokens[i], path: tokens[i + 1] });
37348
+ for (let i = 0; i + 1 < tokens.length; i += 2) {
37349
+ const status2 = tokens[i];
37350
+ const file2 = tokens[i + 1];
37351
+ if (status2 !== void 0 && file2 !== void 0) changes.push({ status: status2, path: file2 });
37352
+ }
37287
37353
  for (const p of nulSplit(git(top, ["ls-files", "--others", "--exclude-standard", "-z"])))
37288
37354
  changes.push({ status: "untracked", path: p });
37289
37355
  const before = { ...manifest.totals };
@@ -37336,18 +37402,24 @@ function exposeDiff(workdir, { repo, base = "HEAD" }, manifest, maxChars) {
37336
37402
  }
37337
37403
  return text2;
37338
37404
  }
37339
- var ExposeError, nulSplit;
37405
+ var ExposeError, messageOf, nulSplit;
37340
37406
  var init_expose_paths = __esm({
37341
37407
  "src/expose-paths.mjs"() {
37408
+ "use strict";
37342
37409
  init_policy();
37343
37410
  init_redact();
37344
37411
  ExposeError = class extends Error {
37412
+ /**
37413
+ * @param {string} message
37414
+ * @param {string} code
37415
+ */
37345
37416
  constructor(message, code) {
37346
37417
  super(message);
37347
37418
  this.name = "ExposeError";
37348
37419
  this.code = code;
37349
37420
  }
37350
37421
  };
37422
+ messageOf = (err) => err instanceof Error ? err.message : String(err);
37351
37423
  nulSplit = (buf) => buf.toString("utf8").split("\0").filter(Boolean);
37352
37424
  }
37353
37425
  });
@@ -37458,7 +37530,7 @@ function renderExposeManifest(manifest) {
37458
37530
  if (skipped.length) skipped.unshift("");
37459
37531
  return [
37460
37532
  "The lead exposed these paths; they are copied under ./workspace in your working directory and you may read",
37461
- "them directly. They are the only part of the lead's repository you have -- if you need something that is",
37533
+ "them directly. They are the only part of the lead's repository the lead gave you -- if you need something that is",
37462
37534
  "not here, record it under `unknowns` rather than guessing. Text files were passed through the same",
37463
37535
  "credential masking as this brief; binary files were copied unchanged.",
37464
37536
  "",
@@ -37518,8 +37590,9 @@ function renderBrief(req, chain = { round: 1, priorRounds: [] }, exposeManifest
37518
37590
  const blocks = chain.priorRounds.map(renderRound);
37519
37591
  let kept = 0;
37520
37592
  let used = 0;
37521
- for (let i = blocks.length - 1; i >= 0 && (kept === 0 || used + blocks[i].length <= HISTORY_CHARS); i--) {
37522
- used += blocks[i].length;
37593
+ for (const block of [...blocks].reverse()) {
37594
+ if (kept > 0 && used + block.length > HISTORY_CHARS) break;
37595
+ used += block.length;
37523
37596
  kept += 1;
37524
37597
  }
37525
37598
  const left = chain.priorRounds.slice(0, blocks.length - kept).map((p) => p.round);
@@ -37543,6 +37616,7 @@ function renderBrief(req, chain = { round: 1, priorRounds: [] }, exposeManifest
37543
37616
  var MODE_BRIEFS, GUARDRAILS_HEAD, GUARDRAILS_RULES, WEB_ON, WEB_OFF, WORKDIR_EMPTY, WORKDIR_EXPOSED, GUARDRAILS_TAIL, EXPOSE_LISTING_MAX, clip, HISTORY_CHARS;
37544
37617
  var init_brief = __esm({
37545
37618
  "src/brief.mjs"() {
37619
+ "use strict";
37546
37620
  init_policy();
37547
37621
  init_result_schema();
37548
37622
  init_redact();
@@ -37581,7 +37655,7 @@ var init_brief = __esm({
37581
37655
  WEB_ON = "- You may search and browse the web freely; cite what you actually opened in `references`.";
37582
37656
  WEB_OFF = "- Web search and browsing are switched off for this consultation. Answer from the brief, and record what you would have looked up under `unknowns`.";
37583
37657
  WORKDIR_EMPTY = "- You are running in an empty working directory and do not have the lead's repository; everything you are meant to have is in the brief below. If something is missing, record it under `unknowns` instead of guessing or substituting an assumption.";
37584
- WORKDIR_EXPOSED = '- Your working directory is not empty: the lead exposed specific paths, listed under "Files available to you" below and copied read-only under ./workspace. Those are the only part of the lead\'s repository you have. If something you need is missing, record it under `unknowns` instead of guessing or substituting an assumption.';
37658
+ WORKDIR_EXPOSED = '- Your working directory is not empty: the lead exposed specific paths, listed under "Files available to you" below and copied read-only under ./workspace. Those are the only part of the lead\'s repository the lead gave you. If something you need is missing, record it under `unknowns` instead of guessing or substituting an assumption.';
37585
37659
  GUARDRAILS_TAIL = [
37586
37660
  "- Never include credentials, API keys, tokens, or environment variable values in your answer.",
37587
37661
  "- Do not output your internal reasoning trace. Report conclusions with their grounds and evidence.",
@@ -37611,15 +37685,15 @@ function clampText(v, max) {
37611
37685
  }
37612
37686
  function level(v, fallback = null) {
37613
37687
  const t = typeof v === "string" ? v.trim().toLowerCase() : "";
37614
- return LEVELS.includes(t) ? t : fallback;
37688
+ return oneOf(LEVELS, t) ? t : fallback;
37615
37689
  }
37616
37690
  function basis(v) {
37617
37691
  const t = typeof v === "string" ? v.trim().toLowerCase() : "";
37618
- return BASIS.includes(t) ? t : null;
37692
+ return oneOf(BASIS, t) ? t : null;
37619
37693
  }
37620
37694
  function stance(v) {
37621
37695
  const t = typeof v === "string" ? v.trim().toLowerCase() : "";
37622
- return STANCES.includes(t) ? t : null;
37696
+ return oneOf(STANCES, t) ? t : null;
37623
37697
  }
37624
37698
  function extractJson(text2) {
37625
37699
  if (typeof text2 !== "string") return null;
@@ -37627,9 +37701,7 @@ function extractJson(text2) {
37627
37701
  if (!trimmed2) return null;
37628
37702
  const candidates = [];
37629
37703
  if (trimmed2.startsWith("{")) candidates.push(trimmed2);
37630
- const fence2 = /```(?:json)?\s*([\s\S]*?)```/gi;
37631
- let m;
37632
- while ((m = fence2.exec(trimmed2)) !== null) candidates.push(m[1].trim());
37704
+ for (const [, body = ""] of trimmed2.matchAll(/```(?:json)?\s*([\s\S]*?)```/gi)) candidates.push(body.trim());
37633
37705
  const start = trimmed2.indexOf("{");
37634
37706
  if (start !== -1) {
37635
37707
  let depth = 0;
@@ -37679,7 +37751,10 @@ function mapList(raw, fields, { requiredField, idPrefix = null }) {
37679
37751
  item[key] = kind === "level" ? level(entry[key]) : clampText(entry[key], O.itemTextMax);
37680
37752
  }
37681
37753
  if (!item[requiredField]) continue;
37682
- out.push(item);
37754
+ out.push(
37755
+ /** @type {T} */
37756
+ item
37757
+ );
37683
37758
  }
37684
37759
  return out;
37685
37760
  }
@@ -37758,10 +37833,14 @@ function normalizeResult(raw) {
37758
37833
  ).filter((r) => r.title || r.url)
37759
37834
  };
37760
37835
  const listTruncated = Object.fromEntries(
37761
- ["findings", "alternatives", "unknowns", "decision_changers", "next_checks", "remaining_disagreements"].filter((k) => Array.isArray(src[k]) && src[k].length > O.listMax).map((k) => [k, src[k].length])
37836
+ ["findings", "alternatives", "unknowns", "decision_changers", "next_checks", "remaining_disagreements"].flatMap((k) => {
37837
+ const v = src[k];
37838
+ return Array.isArray(v) && v.length > O.listMax ? [[k, v.length]] : [];
37839
+ })
37762
37840
  );
37841
+ const fields = result;
37763
37842
  const missing = RESULT_KEYS.filter((k) => {
37764
- const v = result[k];
37843
+ const v = fields[k];
37765
37844
  return v === null || v === void 0 || Array.isArray(v) && v.length === 0;
37766
37845
  });
37767
37846
  const groundless = result.findings.filter((f) => !f.grounds).length;
@@ -37775,13 +37854,18 @@ function normalizeResult(raw) {
37775
37854
  }
37776
37855
  };
37777
37856
  }
37778
- var OutputError, O, LEVELS, BASIS;
37857
+ var OutputError, O, oneOf, LEVELS, BASIS;
37779
37858
  var init_parse_result = __esm({
37780
37859
  "src/parse-result.mjs"() {
37860
+ "use strict";
37781
37861
  init_policy();
37782
37862
  init_result_schema();
37783
37863
  init_redact();
37784
37864
  OutputError = class extends Error {
37865
+ /**
37866
+ * @param {string} message
37867
+ * @param {string} [detail]
37868
+ */
37785
37869
  constructor(message, detail) {
37786
37870
  super(message);
37787
37871
  this.name = "OutputError";
@@ -37789,6 +37873,7 @@ var init_parse_result = __esm({
37789
37873
  }
37790
37874
  };
37791
37875
  O = POLICY.output;
37876
+ oneOf = (list, t) => list.some((x) => x === t);
37792
37877
  LEVELS = ["high", "medium", "low"];
37793
37878
  BASIS = ["sufficient", "thin", "insufficient"];
37794
37879
  }
@@ -37806,6 +37891,7 @@ function isRetriable(kind) {
37806
37891
  var RULES, AUTO_RETRY_KINDS;
37807
37892
  var init_failures = __esm({
37808
37893
  "src/failures.mjs"() {
37894
+ "use strict";
37809
37895
  RULES = [
37810
37896
  [
37811
37897
  /\b(usage limit|quota (exceeded|reached|exhausted)|out of credits|reached your .* limit|insufficient_quota|credit balance)\b/i,
@@ -37846,8 +37932,8 @@ function childEnv(target, extra = {}, operator = {}) {
37846
37932
  return { ...env, ...extra };
37847
37933
  }
37848
37934
  function providerEnvNames(target) {
37849
- const provider = JSON.stringify(POLICY.clis?.[target]?.provider ?? {});
37850
- return new Set([...provider.matchAll(/\{env:([A-Za-z_][A-Za-z0-9_]*)\}/g)].map((m) => m[1]));
37935
+ const provider = JSON.stringify(target === "opencode" ? POLICY.clis.opencode.provider : {});
37936
+ return new Set([...provider.matchAll(/\{env:([A-Za-z_][A-Za-z0-9_]*)\}/g)].flatMap(([, name]) => name ?? []));
37851
37937
  }
37852
37938
  function foreignSecret(target, key, keep) {
37853
37939
  if (keep.has(key) || (OWN_PREFIX[target] ?? []).some((p) => key.startsWith(p))) return false;
@@ -37905,10 +37991,10 @@ function runChild({
37905
37991
  let timedOut = false;
37906
37992
  let cancelled = false;
37907
37993
  let stalled = false;
37908
- let settleTimer = null;
37994
+ let settleTimer;
37909
37995
  let settleAt = Infinity;
37910
37996
  const watch = (line) => {
37911
- const ms = settleAfter2(line);
37997
+ const ms = settleAfter2?.(line);
37912
37998
  if (ms == null || Date.now() + ms >= settleAt) return;
37913
37999
  settleAt = Date.now() + ms;
37914
38000
  clearTimeout(settleTimer);
@@ -37940,7 +38026,7 @@ function runChild({
37940
38026
  const compact = compactLine2 ?? ((l) => l);
37941
38027
  const flushLines = () => {
37942
38028
  const lines = pending.split("\n");
37943
- pending = lines.pop();
38029
+ pending = lines.pop() ?? "";
37944
38030
  for (const l of lines) {
37945
38031
  if (settleAfter2) watch(l);
37946
38032
  append("out", `${compact(l)}
@@ -37972,7 +38058,7 @@ function runChild({
37972
38058
  const done = new Promise((resolve) => {
37973
38059
  let settled2 = false;
37974
38060
  let pipesForced = false;
37975
- let orphanTimer = null;
38061
+ let orphanTimer;
37976
38062
  const finish = (payload) => {
37977
38063
  if (settled2) return;
37978
38064
  settled2 = true;
@@ -38019,6 +38105,7 @@ function runChild({
38019
38105
  var DROP_EXACT, DROP_PREFIX, TARGET_DROP_PREFIX, TARGET_DROP_EXACT, SECRET_PREFIX, SECRET_SUFFIX, SECRET_EXACT, OWN_PREFIX, ProcHandle;
38020
38106
  var init_run = __esm({
38021
38107
  "src/run.mjs"() {
38108
+ "use strict";
38022
38109
  init_platform();
38023
38110
  init_policy();
38024
38111
  DROP_EXACT = /* @__PURE__ */ new Set(["CLAUDECODE", "CLAUDE_PID", "CLAUDE_EFFORT", "AI_AGENT", "ANTHROPIC_LOG", "NODE_OPTIONS"]);
@@ -38048,6 +38135,10 @@ var init_run = __esm({
38048
38135
  opencode: ["ZAI_"]
38049
38136
  };
38050
38137
  ProcHandle = class {
38138
+ /**
38139
+ * @param {import('node:child_process').ChildProcess} child
38140
+ * @param {() => string} [readStdout]
38141
+ */
38051
38142
  constructor(child, readStdout = () => "") {
38052
38143
  this.child = child;
38053
38144
  this.killed = false;
@@ -38057,6 +38148,7 @@ var init_run = __esm({
38057
38148
  * Kill the child and every descendant it spawned -- also after the child
38058
38149
  * itself has exited: a descendant still holding its pipes keeps the
38059
38150
  * consultation from closing, and on POSIX it is still in the group.
38151
+ * @param {NodeJS.Signals} [signal]
38060
38152
  */
38061
38153
  stop(signal = "SIGTERM") {
38062
38154
  if (this.killed || !this.child) return;
@@ -38279,7 +38371,7 @@ function withSeatLock(fn) {
38279
38371
  fs4.mkdirSync(lock);
38280
38372
  break;
38281
38373
  } catch (err) {
38282
- if (err.code !== "EEXIST") throw err;
38374
+ if (!(err instanceof Error && "code" in err && err.code === "EEXIST")) throw err;
38283
38375
  try {
38284
38376
  if (Date.now() - fs4.statSync(lock).mtimeMs > LOCK_STALE_MS) fs4.rmdirSync(lock);
38285
38377
  } catch {
@@ -38322,7 +38414,7 @@ function takeSeats(seats, cap, held) {
38322
38414
  } catch {
38323
38415
  }
38324
38416
  }
38325
- for (const mine of seats.filter((x) => x.chain_id)) {
38417
+ for (const mine of seats.filter(continuesChain)) {
38326
38418
  let other = live.find((x) => x.chain_id === mine.chain_id);
38327
38419
  if (!other && fs4.existsSync(roundPath(mine.chain_id, mine.round))) {
38328
38420
  try {
@@ -38389,9 +38481,10 @@ function cleanupJobDir(jobId) {
38389
38481
  }
38390
38482
  }
38391
38483
  }
38392
- var roundPath, JOB_ID, GROUP_ID, runningDir, runningFile, seatsDir, seatFile, LOCK_WAIT_MS, LOCK_STALE_MS, cancelFile, cancelRequested;
38484
+ var roundPath, JOB_ID, GROUP_ID, runningDir, runningFile, seatsDir, seatFile, LOCK_WAIT_MS, LOCK_STALE_MS, continuesChain, cancelFile, cancelRequested;
38393
38485
  var init_store = __esm({
38394
38486
  "src/store.mjs"() {
38487
+ "use strict";
38395
38488
  init_policy();
38396
38489
  roundPath = (chainId, round) => path6.join(POLICY.home, "history", chainId, `round-${String(round).padStart(2, "0")}.json`);
38397
38490
  JOB_ID = /^job_[0-9a-f]{12}$/;
@@ -38402,6 +38495,7 @@ var init_store = __esm({
38402
38495
  seatFile = (jobId) => path6.join(seatsDir(), `${jobId}.json`);
38403
38496
  LOCK_WAIT_MS = 2e3;
38404
38497
  LOCK_STALE_MS = 1e4;
38498
+ continuesChain = (seat) => Boolean(seat.chain_id);
38405
38499
  cancelFile = (jobId) => path6.join(seatsDir(), `${jobId}.cancel`);
38406
38500
  cancelRequested = (jobId) => JOB_ID.test(jobId) && fs4.existsSync(cancelFile(jobId));
38407
38501
  }
@@ -38503,8 +38597,8 @@ function interpret({ stdout, stderr, code, lastMessageText }) {
38503
38597
  for (const e of events3) walkUsage(e, usage);
38504
38598
  let text2 = (lastMessageText || "").trim();
38505
38599
  if (!text2) {
38506
- for (let i = events3.length - 1; i >= 0 && !text2; i--) {
38507
- const e = events3[i];
38600
+ for (const e of events3.toReversed()) {
38601
+ if (text2) break;
38508
38602
  const item = e.item ?? e;
38509
38603
  const candidate = item && item.type && String(item.type).includes("agent_message") && (item.text ?? item.message) || e.type === "item.completed" && item && (item.text ?? item.message) || null;
38510
38604
  if (typeof candidate === "string" && candidate.trim()) text2 = candidate.trim();
@@ -38570,9 +38664,15 @@ function toolCalls({ stdout }) {
38570
38664
  return [...seen.values()].reduce((o, n) => ({ ...o, [n]: (o[n] ?? 0) + 1 }), {});
38571
38665
  }
38572
38666
  function usageRecord(raw) {
38573
- const u = raw ?? {};
38667
+ const u = (
38668
+ /** @type {Record<string, number>} */
38669
+ raw ?? {}
38670
+ );
38574
38671
  const pick2 = (...names) => {
38575
- for (const n of names) if (typeof u[n] === "number") return u[n];
38672
+ for (const n of names) {
38673
+ const v = u[n];
38674
+ if (typeof v === "number") return v;
38675
+ }
38576
38676
  return null;
38577
38677
  };
38578
38678
  return {
@@ -38599,6 +38699,7 @@ function boundary({ args }) {
38599
38699
  var FORBIDDEN_FLAGS, RESERVED_FLAGS, PROFILE, listDir;
38600
38700
  var init_codex = __esm({
38601
38701
  "src/adapters/codex.mjs"() {
38702
+ "use strict";
38602
38703
  init_policy();
38603
38704
  init_failures();
38604
38705
  FORBIDDEN_FLAGS = [
@@ -38697,9 +38798,9 @@ function buildInvocation2({ workdir, guardrails, consultant, hasExposedPaths = f
38697
38798
  return { command: t.cli, args, model: chosen, workdir };
38698
38799
  }
38699
38800
  function lastJsonObject(stdout) {
38700
- const lines = (stdout || "").split("\n");
38701
- for (let i = lines.length - 1; i >= 0; i--) {
38702
- const t = lines[i].trim();
38801
+ const lines = (stdout || "").split("\n").reverse();
38802
+ for (const line of lines) {
38803
+ const t = line.trim();
38703
38804
  if (!t.startsWith("{")) continue;
38704
38805
  try {
38705
38806
  const parsed = JSON.parse(t);
@@ -38738,7 +38839,11 @@ function interpret2({ stdout, stderr, code }) {
38738
38839
  }
38739
38840
  return { ok: true, text: text2, payload, usageRaw: payload, denials: payload.permission_denials ?? [] };
38740
38841
  }
38741
- function usageRecord2(payload) {
38842
+ function usageRecord2(raw) {
38843
+ const payload = (
38844
+ /** @type {ClaudeResult | null} */
38845
+ raw
38846
+ );
38742
38847
  if (!payload) return null;
38743
38848
  const u = payload.usage ?? {};
38744
38849
  const num2 = (v) => typeof v === "number" ? v : null;
@@ -38753,7 +38858,10 @@ function usageRecord2(payload) {
38753
38858
  };
38754
38859
  }
38755
38860
  function boundary2({ args, hasExposedPaths }) {
38756
- const tools = args[args.indexOf("--tools") + 1];
38861
+ const tools = (
38862
+ /** @type {string} */
38863
+ args[args.indexOf("--tools") + 1]
38864
+ );
38757
38865
  return {
38758
38866
  reads: hasExposedPaths ? "the exposed copy only" : "whole disk",
38759
38867
  shell: "none",
@@ -38767,6 +38875,7 @@ function boundary2({ args, hasExposedPaths }) {
38767
38875
  var FORBIDDEN_FLAGS2, RESERVED_FLAGS2, posixAbsolute;
38768
38876
  var init_claude_code = __esm({
38769
38877
  "src/adapters/claude-code.mjs"() {
38878
+ "use strict";
38770
38879
  init_policy();
38771
38880
  init_failures();
38772
38881
  init_result_schema();
@@ -38987,13 +39096,14 @@ function prepareSandbox({ workdir, network = "on" }) {
38987
39096
  var SANDBOX_ALLOW, SANDBOX_DENY, ALLOWED_TOOLS, WEB_TOOLS, PATH_TOOLS, TOKEN_REL;
38988
39097
  var init_antigravity_sandbox = __esm({
38989
39098
  "src/adapters/antigravity-sandbox.mjs"() {
39099
+ "use strict";
38990
39100
  init_policy();
38991
39101
  init_store();
38992
39102
  SANDBOX_ALLOW = Object.freeze(["read_url(*)", "read_file(*)"]);
38993
39103
  SANDBOX_DENY = Object.freeze(["write_file(*)", "command(*)", "mcp(*)", "execute_url(*)", "unsandboxed(*)"]);
38994
- ALLOWED_TOOLS = Object.freeze(["view_file", "search_web", "read_url_content", "finish"]);
39104
+ ALLOWED_TOOLS = Object.freeze(["view_file", "read_file", "search_web", "read_url_content", "finish"]);
38995
39105
  WEB_TOOLS = /* @__PURE__ */ new Set(["search_web", "read_url_content"]);
38996
- PATH_TOOLS = ["view_file", "read_url_content"];
39106
+ PATH_TOOLS = ["view_file", "read_file", "read_url_content"];
38997
39107
  TOKEN_REL = path8.join(".gemini", "antigravity-cli", "antigravity-oauth-token");
38998
39108
  }
38999
39109
  });
@@ -39001,6 +39111,8 @@ var init_antigravity_sandbox = __esm({
39001
39111
  // src/adapters/antigravity.mjs
39002
39112
  var antigravity_exports = {};
39003
39113
  __export(antigravity_exports, {
39114
+ ALLOWED_TOOLS: () => ALLOWED_TOOLS,
39115
+ FILE_READ_TOOLS: () => FILE_READ_TOOLS,
39004
39116
  FORBIDDEN_FLAGS: () => FORBIDDEN_FLAGS3,
39005
39117
  RESERVED_FLAGS: () => RESERVED_FLAGS3,
39006
39118
  boundary: () => boundary3,
@@ -39047,19 +39159,17 @@ function events(stdout) {
39047
39159
  return out;
39048
39160
  }
39049
39161
  function finalEnvelope(stdout) {
39050
- const all = events(stdout);
39051
- for (let i = all.length - 1; i >= 0; i--) {
39052
- const e = all[i];
39162
+ for (const e of events(stdout).reverse()) {
39053
39163
  if (e.event === "result" && e.result && typeof e.result === "object") return e.result;
39054
39164
  if (e.event === void 0 && (e.status || e.response || e.structured_output)) return e;
39055
39165
  }
39056
39166
  return null;
39057
39167
  }
39058
39168
  function progress2({ stdout }) {
39059
- const steps = events(stdout).filter((e) => e.event === "step_update" && e.step_update);
39060
- if (!steps.length) return null;
39061
- const last = steps[steps.length - 1].step_update;
39062
- const seen = new Set(steps.map((s2) => s2.step_update.step_index)).size;
39169
+ const steps = events(stdout).flatMap((e) => e.event === "step_update" && e.step_update ? [e.step_update] : []);
39170
+ const last = steps.at(-1);
39171
+ if (!last) return null;
39172
+ const seen = new Set(steps.map((s2) => s2.step_index)).size;
39063
39173
  const what = last.tool_name ? `tool ${last.tool_name}` : last.step_type ?? "a step";
39064
39174
  return `${seen} step(s), last was ${what} (${last.state ?? "state unknown"})`;
39065
39175
  }
@@ -39123,7 +39233,11 @@ function interpret3({ stdout, stderr, code }) {
39123
39233
  }
39124
39234
  return { ok: true, text: text2, usageRaw: payload, denials: payload.denied_actions ?? [] };
39125
39235
  }
39126
- function usageRecord3(payload) {
39236
+ function usageRecord3(raw) {
39237
+ const payload = (
39238
+ /** @type {AgyEnvelope | null} */
39239
+ raw
39240
+ );
39127
39241
  if (!payload) return null;
39128
39242
  const u = payload.usage ?? {};
39129
39243
  const num2 = (v) => typeof v === "number" ? v : null;
@@ -39139,23 +39253,28 @@ function usageRecord3(payload) {
39139
39253
  turns: num2(payload.num_turns),
39140
39254
  // Which permissions the sandbox refused. A denied read_url usually explains
39141
39255
  // a thin evidence basis, so it is worth keeping in the record.
39142
- denied_actions: Array.isArray(payload.denied_actions) ? payload.denied_actions.map((d) => d?.action).filter(Boolean) : null
39256
+ denied_actions: Array.isArray(payload.denied_actions) ? payload.denied_actions.flatMap((d) => d?.action ? [d.action] : []) : null
39143
39257
  };
39144
39258
  }
39145
39259
  function boundary3({ sandbox }) {
39260
+ const { tools } = (
39261
+ /** @type {Sandbox} */
39262
+ sandbox
39263
+ );
39146
39264
  return {
39147
39265
  reads: "whole disk",
39148
39266
  shell: "none",
39149
- web: sandbox.tools.includes("search_web"),
39267
+ web: tools.includes("search_web"),
39150
39268
  writes: false,
39151
39269
  mcp: false,
39152
39270
  protected_paths: "denied (tool hook)",
39153
- tools: [...sandbox.tools]
39271
+ tools: [...tools]
39154
39272
  };
39155
39273
  }
39156
- var FORBIDDEN_FLAGS3, RESERVED_FLAGS3, stopSignal;
39274
+ var FORBIDDEN_FLAGS3, RESERVED_FLAGS3, FILE_READ_TOOLS, stopSignal;
39157
39275
  var init_antigravity = __esm({
39158
39276
  "src/adapters/antigravity.mjs"() {
39277
+ "use strict";
39159
39278
  init_policy();
39160
39279
  init_failures();
39161
39280
  init_antigravity_sandbox();
@@ -39180,6 +39299,7 @@ var init_antigravity = __esm({
39180
39299
  "-i"
39181
39300
  ];
39182
39301
  RESERVED_FLAGS3 = ["--print", "-p", "--prompt", "--project", "--remote-control", "--effort"];
39302
+ FILE_READ_TOOLS = ["view_file", "read_file"];
39183
39303
  stopSignal = "SIGQUIT";
39184
39304
  }
39185
39305
  });
@@ -39273,9 +39393,9 @@ function prepareSandbox2({ workdir, network = "on" }) {
39273
39393
  fs8.rmSync(root, { recursive: true, force: true });
39274
39394
  } catch {
39275
39395
  }
39276
- }
39396
+ },
39397
+ seed: (provider) => seedCredentialFromAuth(sandbox, provider)
39277
39398
  };
39278
- sandbox.seed = (provider) => seedCredentialFromAuth(sandbox, provider);
39279
39399
  return sandbox;
39280
39400
  }
39281
39401
  function seedCredentialFromAuth(sandbox, provider) {
@@ -39330,6 +39450,7 @@ function seedCredentialFromAuth(sandbox, provider) {
39330
39450
  var DatabaseSync, SANDBOX_DENY2, SANDBOX_ALLOW2, WEB_TOOLS2, SANDBOX_TOOLS_OFF, AUTH_REL;
39331
39451
  var init_opencode_sandbox = __esm({
39332
39452
  "src/adapters/opencode-sandbox.mjs"() {
39453
+ "use strict";
39333
39454
  init_policy();
39334
39455
  init_store();
39335
39456
  DatabaseSync = null;
@@ -39366,6 +39487,7 @@ var init_opencode_sandbox = __esm({
39366
39487
  // src/adapters/opencode.mjs
39367
39488
  var opencode_exports = {};
39368
39489
  __export(opencode_exports, {
39490
+ FILE_READ_TOOLS: () => FILE_READ_TOOLS2,
39369
39491
  FORBIDDEN_FLAGS: () => FORBIDDEN_FLAGS4,
39370
39492
  RESERVED_FLAGS: () => RESERVED_FLAGS4,
39371
39493
  boundary: () => boundary4,
@@ -39512,14 +39634,14 @@ function interpret4({ stdout, stderr, code, signal = null, truncated = false })
39512
39634
  let text2 = null;
39513
39635
  let textIndex = -1;
39514
39636
  let lastErrorIndex = -1;
39637
+ let lastError = "";
39515
39638
  let finishedAfterText = false;
39516
39639
  let tokens = null;
39517
39640
  let cost = null;
39518
39641
  let steps = 0;
39519
39642
  const errors = [];
39520
39643
  const earlierTextParts = [];
39521
- for (let i = 0; i < all.length; i++) {
39522
- const e = all[i];
39644
+ for (const [i, e] of all.entries()) {
39523
39645
  const part = e.part;
39524
39646
  if (e.type === "text" && part?.type === "text" && part.time?.end && typeof part.text === "string" && part.text.trim()) {
39525
39647
  if (text2 !== null) earlierTextParts.push(text2);
@@ -39532,7 +39654,8 @@ function interpret4({ stdout, stderr, code, signal = null, truncated = false })
39532
39654
  if (part.tokens && typeof part.tokens === "object") tokens = part.tokens;
39533
39655
  if (typeof part.cost === "number") cost = part.cost;
39534
39656
  } else if (e.type === "error" && e.error) {
39535
- errors.push(errorMessage(e.error));
39657
+ lastError = errorMessage(e.error);
39658
+ errors.push(lastError);
39536
39659
  lastErrorIndex = i;
39537
39660
  }
39538
39661
  }
@@ -39547,14 +39670,14 @@ function interpret4({ stdout, stderr, code, signal = null, truncated = false })
39547
39670
  };
39548
39671
  }
39549
39672
  if (lastErrorIndex > textIndex) {
39550
- const msg = errors[errors.length - 1];
39673
+ const msg = lastError;
39551
39674
  return { ok: false, failureKind: classifyMessage(msg), message: msg, usageRaw };
39552
39675
  }
39553
39676
  if (code !== 0) {
39554
39677
  const { msg, kind } = failureMessage({ errors, stderr, stdout, code, signal, what: "after producing an answer" });
39555
39678
  return { ok: false, failureKind: kind, message: msg, usageRaw };
39556
39679
  }
39557
- if (truncated && !(finishedAfterText && all.length > 0 && all[all.length - 1].type === "step_finish")) {
39680
+ if (truncated && !(finishedAfterText && all.at(-1)?.type === "step_finish")) {
39558
39681
  return {
39559
39682
  ok: false,
39560
39683
  failureKind: "invalid_output",
@@ -39577,14 +39700,19 @@ function toolCalls3({ stdout }) {
39577
39700
  return [...seen.values()].reduce((o, n) => ({ ...o, [n]: (o[n] ?? 0) + 1 }), {});
39578
39701
  }
39579
39702
  function progress3({ stdout }) {
39580
- const seen = events2(stdout).filter((e) => ["step_start", "step_finish", "text", "tool_use"].includes(e.type));
39581
- if (!seen.length) return null;
39582
- const last = seen[seen.length - 1];
39703
+ const seen = events2(stdout).filter(
39704
+ (e) => e.type !== void 0 && ["step_start", "step_finish", "text", "tool_use"].includes(e.type)
39705
+ );
39706
+ const last = seen.at(-1);
39707
+ if (!last) return null;
39583
39708
  const what = last.type === "tool_use" && last.part?.tool ? `tool ${last.part.tool}` : last.type;
39584
39709
  return `${seen.length} event(s), last was ${what}`;
39585
39710
  }
39586
39711
  function usageRecord4(raw) {
39587
- const r = raw ?? {};
39712
+ const r = (
39713
+ /** @type {OpencodeUsage | null} */
39714
+ raw ?? {}
39715
+ );
39588
39716
  const t = r.tokens && typeof r.tokens === "object" ? r.tokens : {};
39589
39717
  const num2 = (v) => typeof v === "number" && Number.isFinite(v) ? v : null;
39590
39718
  const cache = t.cache && typeof t.cache === "object" ? t.cache : {};
@@ -39601,19 +39729,24 @@ function usageRecord4(raw) {
39601
39729
  };
39602
39730
  }
39603
39731
  function boundary4({ sandbox }) {
39732
+ const { tools } = (
39733
+ /** @type {Sandbox} */
39734
+ sandbox
39735
+ );
39604
39736
  return {
39605
39737
  reads: "whole disk",
39606
39738
  shell: "none",
39607
- web: sandbox.tools.includes("webfetch"),
39739
+ web: tools.includes("webfetch"),
39608
39740
  writes: false,
39609
39741
  mcp: false,
39610
39742
  protected_paths: "denied (permission rules)",
39611
- tools: [...sandbox.tools]
39743
+ tools: [...tools]
39612
39744
  };
39613
39745
  }
39614
- var FORBIDDEN_FLAGS4, standaloneSupport, probePromise, RESERVED_FLAGS4;
39746
+ var FORBIDDEN_FLAGS4, standaloneSupport, probePromise, RESERVED_FLAGS4, FILE_READ_TOOLS2;
39615
39747
  var init_opencode = __esm({
39616
39748
  "src/adapters/opencode.mjs"() {
39749
+ "use strict";
39617
39750
  init_policy();
39618
39751
  init_failures();
39619
39752
  init_platform();
@@ -39642,6 +39775,7 @@ var init_opencode = __esm({
39642
39775
  standaloneSupport = null;
39643
39776
  probePromise = null;
39644
39777
  RESERVED_FLAGS4 = ["--model", "--standalone", "--wizard"];
39778
+ FILE_READ_TOOLS2 = ["read", "grep"];
39645
39779
  }
39646
39780
  });
39647
39781
 
@@ -39662,7 +39796,7 @@ function ownerState(jobId, owner) {
39662
39796
  process.kill(owner.pid, 0);
39663
39797
  pidAlive = true;
39664
39798
  } catch (err) {
39665
- pidAlive = err.code === "EPERM";
39799
+ pidAlive = err instanceof Error && "code" in err && err.code === "EPERM";
39666
39800
  }
39667
39801
  if (!pidAlive) return "dead";
39668
39802
  return Date.now() > Date.parse(owner.deadline_at) ? "overdue" : "alive";
@@ -39710,7 +39844,7 @@ async function settled(promise2, ms, signal) {
39710
39844
  await (promise2 ? Promise.race([promise2, until]) : until);
39711
39845
  } finally {
39712
39846
  clearTimeout(timer);
39713
- signal?.removeEventListener("abort", stop);
39847
+ if (stop) signal?.removeEventListener("abort", stop);
39714
39848
  }
39715
39849
  }
39716
39850
  function parses(text2) {
@@ -39735,11 +39869,18 @@ function tally(values) {
39735
39869
  return out;
39736
39870
  }
39737
39871
  function recordSummary(job) {
39738
- const ids = recordableIds(job.result);
39739
- const written = new Set(job.record.entries.map((e) => e.id));
39872
+ const record2 = (
39873
+ /** @type {JobRecord} */
39874
+ job.record
39875
+ );
39876
+ const ids = recordableIds(
39877
+ /** @type {ConsultResult} */
39878
+ job.result
39879
+ );
39880
+ const written = new Set(record2.entries.map((e) => e.id));
39740
39881
  return {
39741
- ...job.record,
39742
- verdicts: tally(job.record.entries.map((e) => e.verdict)),
39882
+ ...record2,
39883
+ verdicts: tally(record2.entries.map((e) => e.verdict)),
39743
39884
  coverage: { recordable: ids.length, recorded: written.size },
39744
39885
  unrecorded: ids.filter((id2) => !written.has(id2))
39745
39886
  };
@@ -39755,13 +39896,16 @@ function inherit(raw, round) {
39755
39896
  const { brief } = round;
39756
39897
  const out = raw.network === void 0 && round.network ? { ...raw, network: round.network } : { ...raw };
39757
39898
  if (!brief) return out;
39758
- for (const k of ["objective", "success_criteria", "constraints"]) {
39759
- if (out[k] === void 0 && raw.context?.[k] === void 0 && brief[k] != null && !(Array.isArray(brief[k]) && !brief[k].length))
39760
- out[k] = brief[k];
39899
+ const topKeys = ["objective", "success_criteria", "constraints"];
39900
+ for (const k of topKeys) {
39901
+ const v = brief[k];
39902
+ if (out[k] === void 0 && raw.context?.[k] === void 0 && v != null && !(Array.isArray(v) && !v.length)) out[k] = v;
39761
39903
  }
39762
39904
  const ctx = { ...raw.context ?? {} };
39763
- for (const k of ["facts", "proposal", "counterpoints"]) {
39764
- if (ctx[k] === void 0 && brief[k] != null && !(Array.isArray(brief[k]) && !brief[k].length)) ctx[k] = brief[k];
39905
+ const contextKeys = ["facts", "proposal", "counterpoints"];
39906
+ for (const k of contextKeys) {
39907
+ const v = brief[k];
39908
+ if (ctx[k] === void 0 && v != null && !(Array.isArray(v) && !v.length)) ctx[k] = v;
39765
39909
  }
39766
39910
  const artifacts = [...brief.artifacts ?? []];
39767
39911
  const last = artifacts.at(-1);
@@ -39793,8 +39937,22 @@ function adviceCaveat(normalized) {
39793
39937
  }
39794
39938
  return notes.length ? notes.join("; ") : null;
39795
39939
  }
39940
+ function unreadCaveat(calls, readTools) {
39941
+ if (!calls || !readTools) return null;
39942
+ if (readTools.some((t) => calls[t])) return null;
39943
+ const failed = readTools.reduce((n, t) => n + (calls[`${t}:error`] ?? 0), 0);
39944
+ const how = failed ? `${failed} failed or were refused` : "none was made";
39945
+ return `files were exposed, but no call to read one succeeded (${how})`;
39946
+ }
39947
+ function refusedToolCaveat(calls, allowed) {
39948
+ if (!calls || !allowed) return null;
39949
+ const names = Object.keys(calls).filter((k) => k.endsWith(":error")).map((k) => k.slice(0, -":error".length)).filter((t) => !allowed.includes(t));
39950
+ if (!names.length) return null;
39951
+ return `the consultant's CLI called ${names.join(", ")}, which severally does not allow, and was refused; a new CLI release may have renamed a tool`;
39952
+ }
39796
39953
  function sameVendorCaveat(caller, callerModel, consultant) {
39797
- const callerVendor = (callerModel ? vendorOf(callerModel) : null) ?? (caller ? CLI_VENDORS[caller] ?? null : null);
39954
+ const callerCli = CLI_IDS.find((c) => c === caller);
39955
+ const callerVendor = (callerModel ? vendorOf(callerModel) : null) ?? (callerCli ? CLI_VENDORS[callerCli] ?? null : null);
39798
39956
  const vendor = consultant.vendor;
39799
39957
  if (!callerVendor || !vendor || callerVendor !== vendor) return null;
39800
39958
  const consultantModel = consultant.model;
@@ -39804,11 +39962,12 @@ function sameVendorCaveat(caller, callerModel, consultant) {
39804
39962
  }
39805
39963
  function comparison(members2, budget) {
39806
39964
  const each = (budget - COMPARISON_NOTE_CHARS) / Math.max(1, members2.length) - COLUMN_FIXED_CHARS;
39807
- let columns = members2.map((m) => byTarget(m, each));
39965
+ const firstPass = members2.map((m) => ({ m, column: byTarget(m, each) }));
39966
+ let columns = firstPass.map((x) => x.column);
39808
39967
  const cutShort = columns.filter((c) => c.truncated).length;
39809
39968
  if (each !== Infinity && cutShort) {
39810
39969
  const spare = columns.reduce((n, c) => n + (c.truncated ? 0 : Math.max(0, each + COLUMN_FIXED_CHARS - printed(c))), 0);
39811
- columns = members2.map((m, i) => columns[i].truncated ? byTarget(m, each + spare / cutShort) : columns[i]);
39970
+ columns = firstPass.map(({ m, column }) => column.truncated ? byTarget(m, each + spare / cutShort) : column);
39812
39971
  }
39813
39972
  return {
39814
39973
  by_target: columns,
@@ -39938,8 +40097,12 @@ function pointsOf(view, ids) {
39938
40097
  const verdicts = new Map((view.record?.entries ?? []).map((e) => [e.id, e]));
39939
40098
  const lists = { finding: r.findings, unknown: r.unknowns, next_check: r.next_checks };
39940
40099
  const points = [...new Set(ids)].map((id2) => {
39941
- const [kind, item] = Object.entries(lists).map(([k, l]) => [k, l.find((x) => x.id === id2)]).find(([, x]) => x);
39942
- return { kind, ...item, recorded: verdicts.get(id2) ?? null };
40100
+ const [point] = Object.entries(lists).flatMap(([kind, l]) => {
40101
+ const item = l.find((x) => x.id === id2);
40102
+ return item ? [{ kind, ...item }] : [];
40103
+ });
40104
+ if (!point) throw new RequestError(`"${id2}" is not a point in this answer`, "unknown_entry_id");
40105
+ return { ...point, recorded: verdicts.get(id2) ?? null };
39943
40106
  });
39944
40107
  return {
39945
40108
  job_id: view.job_id,
@@ -40043,6 +40206,7 @@ function assertNoForbiddenFlags(target, args) {
40043
40206
  var ADAPTERS, MIN_RETRY_MS, CANCEL_POLL_MS, API_KEY_VARS, CREDENTIAL_FILE_VARS, ALL_API_CREDENTIAL_VARS, USAGE_FIGURES, id, LIVE_HERE, JobManager, PARTIAL_TAIL_CHARS, GROUP_CHARS, COLUMN_FIXED_CHARS, COMPARISON_NOTE_CHARS, QUESTION_CHARS, FAILURE_MESSAGE_CHARS, SUMMARY_CHARS, CAVEAT_CHARS, ITEM_CHARS, MIN_TEXT_CHARS, flagNames;
40044
40207
  var init_jobs = __esm({
40045
40208
  "src/jobs.mjs"() {
40209
+ "use strict";
40046
40210
  init_policy();
40047
40211
  init_consultants();
40048
40212
  init_schema();
@@ -40078,6 +40242,10 @@ var init_jobs = __esm({
40078
40242
  // A consultation this process does not hold: finished (its round file), or
40079
40243
  // still in progress (its marker). A marker whose owner is gone becomes a
40080
40244
  // failed round, `interrupted`, written once by whichever process claims it.
40245
+ /**
40246
+ * @param {string} jobId
40247
+ * @returns {StoredRound | null}
40248
+ */
40081
40249
  #fromDisk(jobId) {
40082
40250
  const done = loadRound(jobId);
40083
40251
  if (done) return done;
@@ -40090,6 +40258,12 @@ var init_jobs = __esm({
40090
40258
  // something the next reader finishes: the round is written (the index
40091
40259
  // takes it once), then the marker goes. A round already on disk wins -- its
40092
40260
  // owner finished and died before removing the marker.
40261
+ /**
40262
+ * @param {string} jobId
40263
+ * @param {StoredRound} rec
40264
+ * @param {'alive' | 'dead' | 'overdue'} state
40265
+ * @returns {Round}
40266
+ */
40093
40267
  #interrupt(jobId, rec, state) {
40094
40268
  const done = loadRound(jobId);
40095
40269
  if (done) {
@@ -40120,25 +40294,33 @@ var init_jobs = __esm({
40120
40294
  for (const jobId of listRunning()) {
40121
40295
  const rec = loadRunning(jobId);
40122
40296
  const state = rec && ownerState(jobId, rec.owner);
40123
- if (rec && state !== "alive") this.#interrupt(jobId, rec, state);
40297
+ if (rec && state && state !== "alive") this.#interrupt(jobId, rec, state);
40124
40298
  }
40125
40299
  }
40300
+ /**
40301
+ * @param {StoredRound} rec
40302
+ * @returns {FullView}
40303
+ */
40126
40304
  #diskView(rec) {
40127
- const elsewhere = Boolean(rec.owner);
40128
40305
  const { owner, ...view } = rec;
40129
40306
  return {
40130
40307
  ...view,
40131
- cancellable: false,
40308
+ // consult_cancel asks the process that owns it to stop it.
40309
+ cancellable: Boolean(owner) && (view.status === "running" || view.status === "queued"),
40132
40310
  progress: null,
40133
40311
  record: view.record ? recordSummary(view) : null,
40134
- brief: elsewhere ? null : view.brief,
40135
- limits: elsewhere ? null : limitsSummary(),
40136
- next_step: elsewhere ? `running in another severally process (pid ${owner.pid}); poll again later -- it can only be cancelled from that process` : nextStep(view)
40312
+ brief: owner ? null : view.brief,
40313
+ limits: owner ? null : limitsSummary(),
40314
+ next_step: owner ? `running in another severally process (pid ${owner.pid}); poll again later, or stop it with consult_cancel` : nextStep(view)
40137
40315
  };
40138
40316
  }
40139
40317
  get running() {
40140
40318
  return [...this.jobs.values()].filter((j) => j.status === "running");
40141
40319
  }
40320
+ /**
40321
+ * @param {Job} job
40322
+ * @returns {Round}
40323
+ */
40142
40324
  #record(job) {
40143
40325
  return {
40144
40326
  job_id: job.job_id,
@@ -40181,13 +40363,25 @@ var init_jobs = __esm({
40181
40363
  }
40182
40364
  // `detail: "full"` returns the brief as stored; by default a finished
40183
40365
  // consultation names it (size, sha256, round file) instead of repeating it.
40184
- /** @param {string} jobId @param {{detail?: string}} [opts] */
40366
+ /**
40367
+ * @param {string} jobId
40368
+ * @param {{detail?: string | undefined}} [opts]
40369
+ * @returns {JobView | null}
40370
+ */
40185
40371
  view(jobId, { detail } = {}) {
40186
40372
  const job = this.jobs.get(jobId);
40187
40373
  if (!job) {
40188
- const rec2 = this.#fromDisk(jobId);
40189
- return rec2 ? compactBrief(this.#diskView(rec2), detail) : null;
40374
+ const rec = this.#fromDisk(jobId);
40375
+ return rec ? compactBrief(this.#diskView(rec), detail) : null;
40190
40376
  }
40377
+ return this.#jobView(job, detail);
40378
+ }
40379
+ /**
40380
+ * @param {Job} job
40381
+ * @param {string | undefined} detail
40382
+ * @returns {JobView}
40383
+ */
40384
+ #jobView(job, detail) {
40191
40385
  const rec = this.#record(job);
40192
40386
  rec.cancellable = job.status === "running" || job.status === "queued";
40193
40387
  rec.progress = job.status === "running" && job._handle ? ADAPTERS[job.cli]?.progress?.({ stdout: job._handle.stdoutSoFar() }) ?? null : null;
@@ -40207,30 +40401,35 @@ var init_jobs = __esm({
40207
40401
  // job_id and group_id can still be followed up and recorded against. The
40208
40402
  // index only picks and orders; each row shown is read from its round, which
40209
40403
  // holds the verdicts written since. Memory wins over disk for the same job.
40404
+ /** @param {{limit?: number}} [opts] */
40210
40405
  list({ limit: limit2 = 20 } = {}) {
40211
- const here = this.order.map((jid) => this.jobs.get(jid));
40212
- const seen = new Set(here.map((j) => j.job_id));
40213
- const elsewhere = [];
40406
+ const candidates = [];
40407
+ const seen = /* @__PURE__ */ new Set();
40408
+ for (const jid of this.order) {
40409
+ const job = this.jobs.get(jid);
40410
+ if (!job) continue;
40411
+ seen.add(job.job_id);
40412
+ candidates.push({ at: job.created_at ?? "", load: () => job });
40413
+ }
40214
40414
  for (const jobId of listRunning()) {
40215
40415
  const live = seen.has(jobId) ? null : loadRunning(jobId);
40216
40416
  if (!live) continue;
40217
40417
  seen.add(jobId);
40218
40418
  const state = ownerState(jobId, live.owner);
40219
40419
  const saved = state === "alive" ? null : loadRoundAt(live);
40220
- elsewhere.push({
40221
- rec: state === "alive" ? live : saved ?? { ...live, status: "failed", failure: { kind: "interrupted" } }
40222
- });
40420
+ const rec = state === "alive" ? live : saved ?? /** @type {Round} */
40421
+ { ...live, status: "failed", failure: { kind: "interrupted" } };
40422
+ candidates.push({ at: rec.created_at ?? "", load: () => rec });
40223
40423
  }
40224
40424
  for (const row of indexRows())
40225
40425
  if (!seen.has(row.job_id)) {
40226
40426
  seen.add(row.job_id);
40227
- elsewhere.push({ row });
40427
+ candidates.push({ at: row.created_at ?? "", load: () => loadRoundAt(row) });
40228
40428
  }
40229
- const at = (x) => x.created_at ?? x.rec?.created_at ?? x.row?.created_at ?? "";
40230
40429
  const picked = [];
40231
- for (const x of [...here, ...elsewhere].sort((p, q) => at(q) < at(p) ? -1 : at(q) > at(p) ? 1 : 0)) {
40430
+ for (const x of candidates.sort((p, q) => q.at < p.at ? -1 : q.at > p.at ? 1 : 0)) {
40232
40431
  if (picked.length >= limit2) break;
40233
- const j = x.row ? loadRoundAt(x.row) : x.rec ?? x;
40432
+ const j = x.load();
40234
40433
  if (j) picked.push(j);
40235
40434
  }
40236
40435
  return picked.map((j) => {
@@ -40267,7 +40466,7 @@ var init_jobs = __esm({
40267
40466
  const by = {};
40268
40467
  const reported = {};
40269
40468
  for (const j of this.jobs.values()) {
40270
- const attempts = [...(j.retries ?? []).map((r) => r.usage), j.usage].filter(Boolean);
40469
+ const attempts = [...(j.retries ?? []).map((r) => r.usage), j.usage].filter((u) => u != null);
40271
40470
  if (!attempts.length) continue;
40272
40471
  const t = by[j.target] ??= { consultations: 0, attempts: 0 };
40273
40472
  const seen = reported[j.target] ??= /* @__PURE__ */ new Set();
@@ -40281,11 +40480,13 @@ var init_jobs = __esm({
40281
40480
  }
40282
40481
  }
40283
40482
  }
40483
+ const out = {};
40284
40484
  for (const [target, t] of Object.entries(by)) {
40285
- t.not_reported = USAGE_FIGURES.filter((k) => !reported[target].has(k));
40485
+ out[target] = { ...t, not_reported: USAGE_FIGURES.filter((k) => !reported[target]?.has(k)) };
40286
40486
  }
40287
- return by;
40487
+ return out;
40288
40488
  }
40489
+ /** @param {RawRequest} rawRequest the tool arguments, not yet validated */
40289
40490
  start(rawRequest) {
40290
40491
  if (process.env.SEVERALLY_ACTIVE === "1") {
40291
40492
  throw new RequestError(
@@ -40308,29 +40509,30 @@ var init_jobs = __esm({
40308
40509
  fromGroup = this.#groupParents(followupTo);
40309
40510
  rawRequest = { ...rawRequest, targets: fromGroup.links.map((l) => l.parent.target) };
40310
40511
  } else if (followupTo) {
40311
- parent = this.jobs.get(followupTo) ?? loadRound(followupTo);
40312
- if (!parent) throw new RequestError(`followup_to references unknown job "${followupTo}"`, "unknown_job");
40313
- if (parent.status !== "completed") {
40512
+ const found = this.jobs.get(followupTo) ?? loadRound(followupTo);
40513
+ if (!found) throw new RequestError(`followup_to references unknown job "${followupTo}"`, "unknown_job");
40514
+ if (found.status !== "completed") {
40314
40515
  throw new RequestError(
40315
- `followup_to job "${followupTo}" is ${parent.status}; only a completed consultation can be followed up`,
40516
+ `followup_to job "${followupTo}" is ${found.status}; only a completed consultation can be followed up`,
40316
40517
  "followup_not_completed"
40317
40518
  );
40318
40519
  }
40319
- const rounds = this.#chainRounds(parent.chain_id);
40320
- const later = rounds.find((j) => j.round > parent.round && !["failed", "cancelled"].includes(j.status));
40520
+ const rounds = this.#chainRounds(found.chain_id);
40521
+ const later = rounds.find((j) => j.round > found.round && !["failed", "cancelled"].includes(j.status));
40321
40522
  if (later) {
40322
40523
  throw new RequestError(
40323
- `job "${followupTo}" is round ${parent.round}, and this consultation already has round ${later.round} (job "${later.job_id}", ${later.status}); follow up the latest round`,
40524
+ `job "${followupTo}" is round ${found.round}, and this consultation already has round ${later.round} (job "${later.job_id}", ${later.status}); follow up the latest round`,
40324
40525
  "followup_not_latest"
40325
40526
  );
40326
40527
  }
40327
- parent = { ...parent, nextRound: Math.max(...rounds.map((j) => j.round)) + 1 };
40528
+ parent = { ...found, nextRound: Math.max(...rounds.map((j) => j.round)) + 1 };
40328
40529
  }
40329
40530
  if (parent && (rawRequest.target ?? null) === null && !rawRequest.targets?.length) {
40330
40531
  rawRequest = { ...rawRequest, target: parent.target };
40331
40532
  }
40332
- if (parent || fromGroup)
40333
- rawRequest = inherit(rawRequest, fromGroup ? fromGroup.links[0].groupMember ?? fromGroup.links[0].parent : parent);
40533
+ const first = fromGroup?.links[0];
40534
+ const inheritFrom = first ? first.groupMember ?? first.parent : parent;
40535
+ if (inheritFrom) rawRequest = inherit(rawRequest, inheritFrom);
40334
40536
  const req = parseRequest(rawRequest, {
40335
40537
  isFollowup: Boolean(parent || fromGroup),
40336
40538
  parentMode: parent?.mode ?? fromGroup?.group.mode ?? null,
@@ -40357,18 +40559,21 @@ var init_jobs = __esm({
40357
40559
  );
40358
40560
  }
40359
40561
  const chainOf = (i) => fromGroup ? fromGroup.links[i] : parent ? { parent, round } : null;
40360
- const seats = req.targets.map((target, i) => ({
40361
- job_id: id("job"),
40362
- ...chainOf(i) ? { chain_id: chainOf(i).parent.chain_id, round: chainOf(i).round } : {},
40363
- // Twice the budget plus an hour: the deadline is wall-clock, the budget is
40364
- // not, so a suspended laptop must not make a live owner look overdue.
40365
- owner: {
40366
- pid: process.pid,
40367
- deadline_at: new Date(
40368
- Date.now() + 2 * timeoutMs(consultantByName(target)) + POLICY.killGraceMs + 36e5
40369
- ).toISOString()
40370
- }
40371
- }));
40562
+ const seats = req.targets.map((target, i) => {
40563
+ const link = chainOf(i);
40564
+ return {
40565
+ job_id: id("job"),
40566
+ ...link ? { chain_id: link.parent.chain_id, round: link.round } : {},
40567
+ // Twice the budget plus an hour: the deadline is wall-clock, the budget is
40568
+ // not, so a suspended laptop must not make a live owner look overdue.
40569
+ owner: {
40570
+ pid: process.pid,
40571
+ deadline_at: new Date(
40572
+ Date.now() + 2 * timeoutMs(consultantByName(target)) + POLICY.killGraceMs + 36e5
40573
+ ).toISOString()
40574
+ }
40575
+ };
40576
+ });
40372
40577
  const taken = takeSeats(seats, POLICY.maxConcurrent, (seat) => ownerState(seat.job_id, seat.owner) === "alive");
40373
40578
  if (taken.chainBusy) {
40374
40579
  const { live, mine } = taken.chainBusy;
@@ -40395,6 +40600,10 @@ var init_jobs = __esm({
40395
40600
  // round of its own chain. A member that is still running, never answered or
40396
40601
  // has used its rounds is skipped and named; if that is every member, the
40397
40602
  // request is refused.
40603
+ /**
40604
+ * @param {string} groupId
40605
+ * @returns {GroupParents}
40606
+ */
40398
40607
  #groupParents(groupId) {
40399
40608
  const group = this.groups.get(groupId) ?? loadGroup(groupId);
40400
40609
  if (!group) throw new RequestError(`followup_to references unknown group "${groupId}"`, "unknown_group");
@@ -40412,10 +40621,18 @@ var init_jobs = __esm({
40412
40621
  }
40413
40622
  const rounds = this.#chainRounds(member.chain_id);
40414
40623
  const latest = rounds.filter((j) => !["failed", "cancelled"].includes(j.status)).sort((a, b) => b.round - a.round)[0];
40624
+ if (!latest) {
40625
+ skipped.push({
40626
+ job_id: member.job_id,
40627
+ target: member.target,
40628
+ reason: `${member.status}, so there is no answer to follow up`
40629
+ });
40630
+ continue;
40631
+ }
40415
40632
  const nextRound = Math.max(...rounds.map((j) => j.round)) + 1;
40416
40633
  const consultant = consultantByName(member.target);
40417
- const reason = !latest ? `${member.status}, so there is no answer to follow up` : latest.status !== "completed" ? `round ${latest.round} is still ${latest.status}` : nextRound > POLICY.maxRounds ? `has used its ${POLICY.maxRounds} rounds` : !consultant || !consultantAvailable(consultant) ? "this consultant is no longer configured or available here" : null;
40418
- if (reason) skipped.push({ job_id: (latest ?? member).job_id, target: member.target, reason });
40634
+ const reason = latest.status !== "completed" ? `round ${latest.round} is still ${latest.status}` : nextRound > POLICY.maxRounds ? `has used its ${POLICY.maxRounds} rounds` : !consultant || !consultantAvailable(consultant) ? "this consultant is no longer configured or available here" : null;
40635
+ if (reason) skipped.push({ job_id: latest.job_id, target: member.target, reason });
40419
40636
  else links.push({ parent: { ...latest, nextRound }, round: nextRound, groupMember: member });
40420
40637
  }
40421
40638
  if (!links.length) {
@@ -40426,14 +40643,21 @@ var init_jobs = __esm({
40426
40643
  }
40427
40644
  return { group, links, skipped };
40428
40645
  }
40646
+ /**
40647
+ * @param {ParsedRequest} req
40648
+ * @param {Array<{parent: Round | null, round: number, groupMember?: Round}>} links
40649
+ * @param {Seat[]} seats
40650
+ * @param {GroupParents | null} [fromGroup]
40651
+ */
40429
40652
  #admitted(req, links, seats, fromGroup = null) {
40430
- const parent = links[0].groupMember ?? links[0].parent;
40653
+ const first = links[0];
40654
+ const parent = first?.groupMember ?? first?.parent;
40431
40655
  const groupId = id("group");
40432
40656
  let exposeManifest = null;
40433
- const parentEntries = req.context.expose_from_parent ? parent.brief?.expose_paths?.entries ?? [] : [];
40657
+ const parentEntries = req.context.expose_from_parent ? parent?.brief?.expose_paths?.entries ?? [] : [];
40434
40658
  if (req.context.expose_from_parent && parentEntries.length === 0) {
40435
40659
  throw new RequestError(
40436
- `context.expose_from_parent: job "${parent.job_id}" exposed no files, so there is nothing to copy again; name them in context.expose_paths`,
40660
+ `context.expose_from_parent: job "${parent?.job_id}" exposed no files, so there is nothing to copy again; name them in context.expose_paths`,
40437
40661
  "nothing_to_inherit"
40438
40662
  );
40439
40663
  }
@@ -40454,7 +40678,10 @@ var init_jobs = __esm({
40454
40678
  if (req.context.expose_diff) {
40455
40679
  exposeManifest.skipped.push({ source: d.source, reason: "replaced", exposed_as: d.exposed_as });
40456
40680
  } else {
40457
- if (reexposeChange(snapshot, d, exposeManifest)) exposeManifest.entries.at(-1).inherited = true;
40681
+ if (reexposeChange(snapshot, d, exposeManifest)) {
40682
+ const added = exposeManifest.entries.at(-1);
40683
+ if (added) added.inherited = true;
40684
+ }
40458
40685
  }
40459
40686
  }
40460
40687
  if (req.context.expose_diff) {
@@ -40463,28 +40690,32 @@ var init_jobs = __esm({
40463
40690
  if (diff) {
40464
40691
  req.context.artifacts = [
40465
40692
  ...req.context.artifacts,
40466
- { name: `git diff ${exposeManifest.entries.at(-1).base}`, kind: "diff", language: "diff", excerpt: diff }
40693
+ { name: `git diff ${exposeManifest.entries.at(-1)?.base}`, kind: "diff", language: "diff", excerpt: diff }
40467
40694
  ];
40468
40695
  }
40469
40696
  }
40470
40697
  markSinceParent(exposeManifest, parentEntries);
40471
40698
  if (parentEntries.length && !exposeManifest.entries.length && !req.context.facts.length && !req.context.artifacts.length) {
40472
40699
  throw new RequestError(
40473
- `context.expose_from_parent: nothing job "${parent.job_id}" exposed is there any more, and this request has no facts or artifacts`,
40700
+ `context.expose_from_parent: nothing job "${parent?.job_id}" exposed is there any more, and this request has no facts or artifacts`,
40474
40701
  "nothing_to_inherit"
40475
40702
  );
40476
40703
  }
40477
40704
  } catch (err) {
40478
40705
  cleanupJobDir(groupId);
40479
- if (err instanceof RequestError || !err.code) throw err;
40706
+ if (err instanceof RequestError || !(err instanceof Error) || !("code" in err) || !err.code) throw err;
40480
40707
  throw new RequestError(err.message, err.code === "too_large" ? "expose_paths_too_large" : "expose_path_invalid");
40481
40708
  }
40482
40709
  req._snapshot = path11.join(snapshot, "workspace");
40483
40710
  }
40484
40711
  const members2 = req.targets.map((target, i) => {
40485
- const { parent: from, round: round2 } = links[i];
40712
+ const link = links[i];
40713
+ const seat = seats[i];
40714
+ if (!link || !seat) throw new Error(`no follow-up link or seat for target "${target}"`);
40715
+ const { parent: from, round: round2 } = link;
40486
40716
  const chainId = from ? from.chain_id : id("chain");
40487
- const jobId = seats[i].job_id;
40717
+ const jobId = seat.job_id;
40718
+ const consultant = consultantNamed(target);
40488
40719
  const job = {
40489
40720
  job_id: jobId,
40490
40721
  chain_id: chainId,
@@ -40519,9 +40750,9 @@ var init_jobs = __esm({
40519
40750
  status: "queued",
40520
40751
  // What this consultant is: the CLI it runs on, the model and its
40521
40752
  // lineage, as the operator defined it.
40522
- cli: consultantByName(target).cli,
40523
- model: consultantByName(target).model,
40524
- vendor: consultantByName(target).vendor,
40753
+ cli: consultant.cli,
40754
+ model: consultant.model,
40755
+ vendor: consultant.vendor,
40525
40756
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
40526
40757
  started_at: null,
40527
40758
  finished_at: null,
@@ -40538,16 +40769,18 @@ var init_jobs = __esm({
40538
40769
  this.order.push(jobId);
40539
40770
  LIVE_HERE.add(jobId);
40540
40771
  this.#watchCancels();
40541
- job._owner = seats[i].owner;
40772
+ job._owner = seat.owner;
40542
40773
  this.#mark(job);
40543
40774
  return job;
40544
40775
  });
40545
40776
  this.#evict();
40777
+ const [firstMember] = members2;
40778
+ if (!firstMember) throw new Error("a consultation needs a target");
40546
40779
  const group = {
40547
40780
  group_id: groupId,
40548
40781
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
40549
40782
  mode: req.mode,
40550
- question: members2[0].question,
40783
+ question: firstMember.question,
40551
40784
  // already redacted, and byte-identical across the group
40552
40785
  job_ids: members2.map((m) => m.job_id)
40553
40786
  };
@@ -40564,13 +40797,13 @@ var init_jobs = __esm({
40564
40797
  });
40565
40798
  }
40566
40799
  if (req._snapshot) cleanupJobDir(groupId);
40567
- const { round } = members2[0];
40800
+ const { round } = firstMember;
40568
40801
  const sameRound = members2.every((m) => m.round === round);
40569
40802
  const common = {
40570
40803
  group_id: groupId,
40571
40804
  ...sameRound ? { round, rounds_remaining: POLICY.maxRounds - round } : {},
40572
40805
  ...fromGroup ? { followup_to_group: fromGroup.group.group_id, skipped: fromGroup.skipped } : {},
40573
- ...parentEntries.length ? { exposed_from_parent: inheritedSummary(exposeManifest) } : {},
40806
+ ...parentEntries.length && exposeManifest ? { exposed_from_parent: inheritedSummary(exposeManifest) } : {},
40574
40807
  mode: req.mode,
40575
40808
  // Counted, not refused: a brief of facts alone is a legitimate
40576
40809
  // consultation, but one about code that hands over no file is one the
@@ -40584,7 +40817,7 @@ var init_jobs = __esm({
40584
40817
  limits: limitsSummary()
40585
40818
  };
40586
40819
  if (!req.fanout) {
40587
- const job = members2[0];
40820
+ const job = firstMember;
40588
40821
  return {
40589
40822
  ...common,
40590
40823
  job_id: job.job_id,
@@ -40615,13 +40848,24 @@ var init_jobs = __esm({
40615
40848
  // (memory wins: it can hold a verdict written since the round was saved).
40616
40849
  // Each point goes with the lead's verdict on it, so the consultant answers
40617
40850
  // knowing which of its points were checked and how they came out.
40851
+ /**
40852
+ * @param {string} chainId
40853
+ * @returns {Round[]}
40854
+ */
40618
40855
  #chainRounds(chainId) {
40619
40856
  const byRound = new Map(loadChain(chainId).map((j) => [j.round, j]));
40620
40857
  for (const j of this.jobs.values()) if (j.chain_id === chainId) byRound.set(j.round, j);
40621
40858
  return [...byRound.values()];
40622
40859
  }
40860
+ /**
40861
+ * @param {string} chainId
40862
+ * @returns {PriorRound[]}
40863
+ */
40623
40864
  #chainHistory(chainId) {
40624
- return this.#chainRounds(chainId).filter((j) => j.status === "completed" && j.result).sort((a, b) => a.round - b.round).map((j) => {
40865
+ return this.#chainRounds(chainId).filter(
40866
+ /** @returns {j is Round & {result: ConsultResult}} */
40867
+ (j) => j.status === "completed" && Boolean(j.result)
40868
+ ).sort((a, b) => a.round - b.round).map((j) => {
40625
40869
  const verdicts = new Map((j.record?.entries ?? []).map((e) => [e.id, e]));
40626
40870
  const ordered = (items, shape) => [...items.filter((x) => verdicts.has(x.id)), ...items.filter((x) => !verdicts.has(x.id))].map((x) => ({
40627
40871
  id: x.id,
@@ -40646,6 +40890,7 @@ var init_jobs = __esm({
40646
40890
  #evict() {
40647
40891
  while (this.order.length > POLICY.maxJobsRetained) {
40648
40892
  const oldest = this.order.shift();
40893
+ if (oldest === void 0) break;
40649
40894
  const j = this.jobs.get(oldest);
40650
40895
  if (j && (j.status === "running" || j.status === "queued")) {
40651
40896
  this.order.push(oldest);
@@ -40659,14 +40904,22 @@ var init_jobs = __esm({
40659
40904
  // last member there is nothing left to compare, so drop the group rather than
40660
40905
  // keep answering as a complete fan-out with no answers in it (and keep the
40661
40906
  // Map, which holds the question text, from growing without bound).
40907
+ /** @param {string} groupId */
40662
40908
  #dropGroupIfEmpty(groupId) {
40663
40909
  const group = this.groups.get(groupId);
40664
40910
  if (!group) return;
40665
40911
  if (group.job_ids.some((jid) => this.jobs.has(jid))) return;
40666
40912
  this.groups.delete(groupId);
40667
40913
  }
40914
+ /**
40915
+ * @param {Job} job
40916
+ * @param {ParsedRequest} req
40917
+ * @param {import('./brief.mjs').BriefChain} chain
40918
+ * @param {string} [brief]
40919
+ * @returns {Promise<Job>}
40920
+ */
40668
40921
  async #execute(job, req, chain, brief) {
40669
- const consultant = consultantByName(job.target);
40922
+ const consultant = consultantNamed(job.target);
40670
40923
  const cli = consultant.cli;
40671
40924
  const adapter = ADAPTERS[cli];
40672
40925
  const workdir = makeWorkdir(job.job_id);
@@ -40674,7 +40927,7 @@ var init_jobs = __esm({
40674
40927
  try {
40675
40928
  fs9.cpSync(req._snapshot, path11.join(workdir, "workspace"), { recursive: true });
40676
40929
  } catch (err) {
40677
- return this.#fail(job, "spawn_error", `could not copy the exposed files: ${err.message}`);
40930
+ return this.#fail(job, "spawn_error", `could not copy the exposed files: ${err instanceof Error ? err.message : err}`);
40678
40931
  }
40679
40932
  }
40680
40933
  const text2 = brief ?? renderBrief(req, chain);
@@ -40720,7 +40973,9 @@ var init_jobs = __esm({
40720
40973
  env,
40721
40974
  input: text2,
40722
40975
  timeoutMs: timeout,
40723
- onCancelSignal: (fn) => job._cancelFns.push(fn),
40976
+ onCancelSignal: (fn) => {
40977
+ job._cancelFns.push(fn);
40978
+ },
40724
40979
  compactLine: adapter.compactLine,
40725
40980
  settleAfter: adapter.settleAfter,
40726
40981
  stopSignal: adapter.stopSignal
@@ -40761,7 +41016,7 @@ var init_jobs = __esm({
40761
41016
  }
40762
41017
  const lastMessageText = invocation.lastMessagePath ? readIfExists(invocation.lastMessagePath) : "";
40763
41018
  interpreted = adapter.interpret({ ...run, lastMessageText });
40764
- const { recoveredError } = interpreted;
41019
+ const recoveredError = interpreted.ok ? interpreted.recoveredError : null;
40765
41020
  if (interpreted.ok && recoveredError && !parses(interpreted.text)) {
40766
41021
  interpreted = { ok: false, ...recoveredError, usageRaw: interpreted.usageRaw };
40767
41022
  }
@@ -40845,7 +41100,9 @@ var init_jobs = __esm({
40845
41100
  adviceCaveat(normalized),
40846
41101
  sameVendorCaveat(job.caller, job.caller_model, consultant),
40847
41102
  interpreted.recoveredError && `the consultant's CLI reported an error mid-run and then finished its answer: ${redact(interpreted.recoveredError.message).slice(0, 300)}`,
40848
- leftover && "the answer parsed, but a process the consultant started kept its output open after it exited and was stopped (one that left its process group may still be running)"
41103
+ leftover && "the answer parsed, but a process the consultant started kept its output open after it exited and was stopped (one that left its process group may still be running)",
41104
+ req._snapshot && unreadCaveat(job.tool_calls, adapter.FILE_READ_TOOLS),
41105
+ refusedToolCaveat(job.tool_calls, adapter.ALLOWED_TOOLS)
40849
41106
  ].filter(Boolean);
40850
41107
  job.quality = {
40851
41108
  ...normalized.quality,
@@ -40860,6 +41117,13 @@ var init_jobs = __esm({
40860
41117
  sandbox?.cleanup();
40861
41118
  }
40862
41119
  }
41120
+ /**
41121
+ * @param {Job} job
41122
+ * @param {string} kind
41123
+ * @param {unknown} message
41124
+ * @param {string | null} [detail]
41125
+ * @returns {Job}
41126
+ */
40863
41127
  #fail(job, kind, message, detail) {
40864
41128
  if (job.status === "completed" || job.status === "failed" || job.status === "cancelled") return job;
40865
41129
  job.status = kind === "cancelled" ? "cancelled" : "failed";
@@ -40875,12 +41139,14 @@ var init_jobs = __esm({
40875
41139
  return job;
40876
41140
  }
40877
41141
  // The in-progress marker other processes read (see store.writeRunning).
41142
+ /** @param {Job} job */
40878
41143
  #mark(job) {
40879
41144
  try {
40880
41145
  writeRunning({ ...this.#record(job), owner: job._owner });
40881
41146
  } catch {
40882
41147
  }
40883
41148
  }
41149
+ /** @param {Job} job */
40884
41150
  #finish(job) {
40885
41151
  try {
40886
41152
  persistRound(this.#record(job));
@@ -40898,7 +41164,7 @@ var init_jobs = __esm({
40898
41164
  // consultant did not produce and a word outside VERDICTS, then stores what
40899
41165
  // was written. It never decides that a finding was adopted, and it never
40900
41166
  // fills in a verdict the lead did not write.
40901
- /** @param {Record<string, any>} [input] */
41167
+ /** @param {RawRecord} [input] */
40902
41168
  record(input2 = {}) {
40903
41169
  if (input2.records !== void 0) return this.#recordMany(input2);
40904
41170
  const allowed = ["job_id", "entries", "reflection"];
@@ -40918,6 +41184,7 @@ var init_jobs = __esm({
40918
41184
  // record is checked before anything is written, so a bad id leaves the
40919
41185
  // history as it was; then each job is written on its own, and a write that
40920
41186
  // fails is named in the answer rather than reported as saved.
41187
+ /** @param {RawRecord} input */
40921
41188
  #recordMany(input2) {
40922
41189
  const extra = Object.keys(input2).filter((k) => k !== "records");
40923
41190
  if (extra.length) {
@@ -40960,7 +41227,10 @@ var init_jobs = __esm({
40960
41227
  }
40961
41228
  // Everything that can be wrong with a record, found before anything is
40962
41229
  // written: what the job's record and reflection become, or a RequestError.
40963
- /** @param {{job_id?: string, entries?: any[], reflection?: any}} rec */
41230
+ /**
41231
+ * @param {RawRecord} rec
41232
+ * @returns {RecordChange}
41233
+ */
40964
41234
  #checkRecord({ job_id: jobId, entries, reflection }) {
40965
41235
  if (entries === void 0 && reflection === void 0) {
40966
41236
  throw new RequestError(
@@ -40984,7 +41254,7 @@ var init_jobs = __esm({
40984
41254
  }
40985
41255
  const ids = recordableIds(job.result);
40986
41256
  const now = (/* @__PURE__ */ new Date()).toISOString();
40987
- const merged = new Map((job.record?.entries ?? []).map((e) => [e.id, e]));
41257
+ const written = /* @__PURE__ */ new Map();
40988
41258
  for (const entry of entries ?? []) {
40989
41259
  const id2 = typeof entry?.id === "string" ? entry.id.trim() : "";
40990
41260
  if (!ids.includes(id2)) {
@@ -41000,7 +41270,7 @@ var init_jobs = __esm({
41000
41270
  "invalid_verdict"
41001
41271
  );
41002
41272
  }
41003
- merged.set(id2, {
41273
+ written.set(id2, {
41004
41274
  id: id2,
41005
41275
  verdict,
41006
41276
  effect: text(entry?.effect),
@@ -41025,21 +41295,28 @@ var init_jobs = __esm({
41025
41295
  }
41026
41296
  newReflection = { delta, related_item_ids: related, recorded_at: now };
41027
41297
  }
41028
- const newRecord = entries === void 0 ? void 0 : { updated_at: now, entries: ids.filter((id2) => merged.has(id2)).map((id2) => merged.get(id2)) };
41029
- return { job, live, record: newRecord, reflection: newReflection };
41298
+ return { job, live, entries: entries === void 0 ? void 0 : [...written.values()], reflection: newReflection };
41030
41299
  }
41031
- #saveRecord({ job, live, record: record2, reflection }) {
41300
+ /** @param {RecordChange} change */
41301
+ #saveRecord({ job, live, entries, reflection }) {
41032
41302
  const was = { record: job.record, reflection: job.reflection };
41033
- if (reflection !== void 0) job.reflection = reflection;
41034
- if (record2 !== void 0) job.record = record2;
41303
+ const disk = loadRoundAt(job);
41304
+ const base = disk?.result ? disk : job;
41305
+ job.reflection = reflection ?? base.reflection;
41306
+ if (entries !== void 0) {
41307
+ const merged = new Map((base.record?.entries ?? []).map((e) => [e.id, e]));
41308
+ for (const e of entries) merged.set(e.id, e);
41309
+ const ids = job.result ? recordableIds(job.result) : [];
41310
+ job.record = { updated_at: (/* @__PURE__ */ new Date()).toISOString(), entries: ids.flatMap((id2) => merged.get(id2) ?? []) };
41311
+ } else if (base.record) job.record = base.record;
41035
41312
  try {
41036
- persistRound(live ? this.#record(job) : job, { appendIndex: false });
41313
+ persistRound(live ? this.#record(live) : job, { appendIndex: false });
41037
41314
  } catch (e) {
41038
41315
  Object.assign(job, was);
41039
41316
  return {
41040
41317
  job_id: job.job_id,
41041
41318
  error: "record_not_saved",
41042
- message: `the verdict for "${job.job_id}" could not be written to the history (${e.message}); record it again`
41319
+ message: `the verdict for "${job.job_id}" could not be written to the history (${e instanceof Error ? e.message : e}); record it again`
41043
41320
  };
41044
41321
  }
41045
41322
  const summary = job.record && recordSummary(job);
@@ -41051,6 +41328,12 @@ var init_jobs = __esm({
41051
41328
  reflection_saved: job.reflection != null
41052
41329
  };
41053
41330
  }
41331
+ /**
41332
+ * @param {string} jobId
41333
+ * @param {number | undefined} waitMs
41334
+ * @param {{detail?: string | undefined}} [opts]
41335
+ * @param {AbortSignal} [signal]
41336
+ */
41054
41337
  async wait(jobId, waitMs, opts, signal) {
41055
41338
  const job = this.jobs.get(jobId);
41056
41339
  if (!job) return this.#waitOnDisk(() => this.view(jobId, opts), waitMs, signal);
@@ -41063,6 +41346,13 @@ var init_jobs = __esm({
41063
41346
  }
41064
41347
  // A consultation another process runs has no promise here, so its history
41065
41348
  // is re-read once a second until it is no longer running or the wait is up.
41349
+ /**
41350
+ * @template {{status: string}} T
41351
+ * @param {() => T | null} read
41352
+ * @param {number | undefined} waitMs
41353
+ * @param {AbortSignal} [signal]
41354
+ * @returns {Promise<T | null>}
41355
+ */
41066
41356
  async #waitOnDisk(read, waitMs, signal) {
41067
41357
  const until = Date.now() + Math.min(Math.max(0, waitMs ?? 0), POLICY.maxWaitMs);
41068
41358
  let view = read();
@@ -41085,6 +41375,7 @@ var init_jobs = __esm({
41085
41375
  }
41086
41376
  }, CANCEL_POLL_MS).unref();
41087
41377
  }
41378
+ /** @param {string} jobId */
41088
41379
  cancel(jobId) {
41089
41380
  const job = this.jobs.get(jobId);
41090
41381
  if (!job) {
@@ -41098,17 +41389,21 @@ var init_jobs = __esm({
41098
41389
  };
41099
41390
  }
41100
41391
  if (job.status !== "running" && job.status !== "queued") {
41101
- return { ...this.view(jobId), cancel_effect: `job was already ${job.status}` };
41392
+ return { ...this.#jobView(job, void 0), cancel_effect: `job was already ${job.status}` };
41102
41393
  }
41103
41394
  job.cancelRequested = true;
41104
41395
  for (const fn of job._cancelFns) fn();
41105
41396
  if (job._handle) job._handle.stop();
41106
- return { ...this.view(jobId), status: "cancelling", cancel_effect: "consultant process group signalled" };
41397
+ return { ...this.#jobView(job, void 0), status: "cancelling", cancel_effect: "consultant process group signalled" };
41107
41398
  }
41108
41399
  // Every field here is derived from the one `members` array it is handed, so a
41109
41400
  // group view can never report one status in `members` and another in
41110
41401
  // `comparison` or `status` for the same job.
41111
- /** @param {any} group @param {any[]} members @param {{detail?: string}} [opts] */
41402
+ /**
41403
+ * @param {Group} group
41404
+ * @param {JobView[]} members
41405
+ * @param {{detail?: string | undefined}} [opts]
41406
+ */
41112
41407
  #shapeGroup(group, members2, { detail } = {}) {
41113
41408
  const full = detail === "full";
41114
41409
  const cancelling = members2.some((m) => m.status === "cancelling");
@@ -41158,14 +41453,27 @@ var init_jobs = __esm({
41158
41453
  limits: live || !full ? null : limitsSummary()
41159
41454
  };
41160
41455
  const anyFinished = members2.some((m) => ["completed", "failed", "cancelled"].includes(m.status));
41161
- if (anyFinished) view.comparison = comparison(members2, full ? Infinity : GROUP_CHARS - printed(view));
41162
- return view;
41456
+ return anyFinished ? { ...view, comparison: comparison(members2, full ? Infinity : GROUP_CHARS - printed(view)) } : view;
41163
41457
  }
41458
+ /**
41459
+ * @param {string} groupId
41460
+ * @param {{detail?: string | undefined}} [opts]
41461
+ */
41164
41462
  groupView(groupId, opts) {
41165
41463
  const group = this.groups.get(groupId) ?? loadGroup(groupId);
41166
41464
  if (!group) return null;
41167
- return this.#shapeGroup(group, group.job_ids.map((jid) => this.view(jid, opts)).filter(Boolean), opts);
41465
+ return this.#shapeGroup(
41466
+ group,
41467
+ group.job_ids.map((jid) => this.view(jid, opts)).filter((v) => v != null),
41468
+ opts
41469
+ );
41168
41470
  }
41471
+ /**
41472
+ * @param {string} groupId
41473
+ * @param {number | undefined} waitMs
41474
+ * @param {{detail?: string | undefined}} [opts]
41475
+ * @param {AbortSignal} [signal]
41476
+ */
41169
41477
  async waitGroup(groupId, waitMs, opts, signal) {
41170
41478
  const group = this.groups.get(groupId);
41171
41479
  if (!group) return this.#waitOnDisk(() => this.groupView(groupId, opts), waitMs, signal);
@@ -41176,6 +41484,7 @@ var init_jobs = __esm({
41176
41484
  }
41177
41485
  return this.groupView(groupId, opts);
41178
41486
  }
41487
+ /** @param {string} groupId */
41179
41488
  cancelGroup(groupId) {
41180
41489
  const group = this.groups.get(groupId);
41181
41490
  if (!group) {
@@ -41191,7 +41500,7 @@ var init_jobs = __esm({
41191
41500
  const j = this.jobs.get(jid);
41192
41501
  return j && (j.status === "running" || j.status === "queued");
41193
41502
  }).length;
41194
- const members2 = group.job_ids.map((jid) => this.cancel(jid)).filter(Boolean);
41503
+ const members2 = group.job_ids.map((jid) => this.cancel(jid)).filter((v) => v != null);
41195
41504
  const total = group.job_ids.length;
41196
41505
  return {
41197
41506
  ...this.#shapeGroup(group, members2),
@@ -41242,7 +41551,7 @@ var init_jobs = __esm({
41242
41551
  MIN_TEXT_CHARS = 40;
41243
41552
  flagNames = (a) => {
41244
41553
  if (!a.startsWith("-") || a === "-") return [];
41245
- const name = a.split("=")[0];
41554
+ const [name = ""] = a.split("=");
41246
41555
  return a.startsWith("--") ? [name] : [name, a.slice(0, 2), `-${name}`];
41247
41556
  };
41248
41557
  }
@@ -41465,13 +41774,13 @@ function exportChain({ chain_id: chainId, group_id: groupId } = {}) {
41465
41774
  if (Boolean(chainId) === Boolean(groupId)) {
41466
41775
  throw new ExportError("pass exactly one of chain_id or group_id", "export_target_required");
41467
41776
  }
41468
- const chainIds = groupId ? chainsOfGroup(groupId) : [chainId];
41777
+ const chainIds = groupId ? chainsOfGroup(groupId) : chainId ? [chainId] : [];
41469
41778
  const perChain = chainIds.map((id2) => ({ chain_id: id2, rounds: roundsOf(id2) }));
41470
- const total = perChain.reduce((n, c) => n + c.rounds.length, 0);
41471
- if (total === 0) {
41779
+ const first = perChain.find((c) => c.rounds.length)?.rounds[0];
41780
+ if (!first) {
41472
41781
  throw new ExportError(`no consultation recorded under "${chainId ?? groupId}" in ~/.severally/history`, "unknown_chain");
41473
41782
  }
41474
- const first = perChain.find((c) => c.rounds.length)?.rounds[0];
41783
+ const total = perChain.reduce((n, c) => n + c.rounds.length, 0);
41475
41784
  const lines = [
41476
41785
  `# Consultation record: ${first.question}`,
41477
41786
  "",
@@ -41501,9 +41810,14 @@ function exportChain({ chain_id: chainId, group_id: groupId } = {}) {
41501
41810
  var ExportError, historyDir, bullets2;
41502
41811
  var init_export = __esm({
41503
41812
  "src/export.mjs"() {
41813
+ "use strict";
41504
41814
  init_policy();
41505
41815
  init_expose_paths();
41506
41816
  ExportError = class extends Error {
41817
+ /**
41818
+ * @param {string} message
41819
+ * @param {string} code
41820
+ */
41507
41821
  constructor(message, code) {
41508
41822
  super(message);
41509
41823
  this.name = "ExportError";
@@ -41540,6 +41854,7 @@ function staleInstall(home = os5.homedir(), severallyHome = POLICY.home) {
41540
41854
  // This release names it SEVERALLY_VERSION; older ones only had it in the server info.
41541
41855
  runtime: bundle?.match(/(?:SEVERALLY_VERSION = |name: ["']severally["'], version: )["'](\d+\.\d+\.\d+)["']/)?.[1],
41542
41856
  claude_skills_dir: (() => {
41857
+ if (plugin === null) return void 0;
41543
41858
  try {
41544
41859
  return JSON.parse(plugin).version;
41545
41860
  } catch {
@@ -41548,28 +41863,22 @@ function staleInstall(home = os5.homedir(), severallyHome = POLICY.home) {
41548
41863
  })()
41549
41864
  };
41550
41865
  const copies = { this_server: SEVERALLY_VERSION };
41551
- for (const [name, v] of Object.entries(found)) if (/^\d+\.\d+\.\d+$/.test(v ?? "")) copies[name] = v;
41866
+ for (const [name, v] of Object.entries(found)) if (v !== void 0 && /^\d+\.\d+\.\d+$/.test(v)) copies[name] = v;
41552
41867
  const versions = Object.values(copies);
41553
41868
  if (versions.every((v) => v === SEVERALLY_VERSION)) return null;
41554
41869
  const newest = versions.reduce((a, b) => older(a, b) ? b : a);
41555
41870
  return {
41556
41871
  copies,
41557
- behind: Object.keys(copies).filter((k) => older(copies[k], newest)),
41872
+ behind: Object.entries(copies).filter(([, v]) => older(v, newest)).map(([k]) => k),
41558
41873
  fix: `bring every copy to ${newest}: run \`npx -y severally-mcp@${newest} install\` (or \`node scripts/install.mjs\` from a checkout of that version) (it updates the runtime and the ~/.claude/skills copy), run \`/plugin marketplace update severally\` for a marketplace install, then restart the clients`
41559
41874
  };
41560
41875
  }
41561
41876
  function createServer(manager = new JobManager()) {
41562
41877
  const stale = staleInstall();
41563
41878
  const server = new McpServer({ name: "severally", version: SEVERALLY_VERSION }, { instructions: SERVER_INSTRUCTIONS });
41564
- const ok = (payload) => ({ content: [{ type: (
41565
- /** @type {const} */
41566
- "text"
41567
- ), text: JSON.stringify(payload, null, 2) }] });
41879
+ const ok = (payload) => ({ content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] });
41568
41880
  const fail = (payload) => ({
41569
- content: [{ type: (
41570
- /** @type {const} */
41571
- "text"
41572
- ), text: JSON.stringify(payload, null, 2) }],
41881
+ content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
41573
41882
  isError: true
41574
41883
  });
41575
41884
  server.registerTool(
@@ -41588,7 +41897,7 @@ function createServer(manager = new JobManager()) {
41588
41897
  if (err instanceof RequestError) {
41589
41898
  return fail({ error: err.code, message: err.message, details: err.details ?? null, limits: limitsSummary() });
41590
41899
  }
41591
- return fail({ error: "internal_error", message: String(err?.message ?? err) });
41900
+ return fail({ error: "internal_error", message: errorMessage2(err) });
41592
41901
  }
41593
41902
  }
41594
41903
  );
@@ -41631,9 +41940,15 @@ function createServer(manager = new JobManager()) {
41631
41940
  }, PROGRESS_EVERY_MS);
41632
41941
  let view;
41633
41942
  try {
41634
- view = group_id ? await manager.waitGroup(group_id, wait_ms ?? DEFAULT_WAIT_MS, { detail }, req?.signal) : await manager.wait(job_id, wait_ms ?? DEFAULT_WAIT_MS, { detail }, req?.signal);
41943
+ view = group_id ? await manager.waitGroup(group_id, wait_ms ?? DEFAULT_WAIT_MS, { detail }, req?.signal) : await manager.wait(
41944
+ /** @type {string} */
41945
+ job_id,
41946
+ wait_ms ?? DEFAULT_WAIT_MS,
41947
+ { detail },
41948
+ req?.signal
41949
+ );
41635
41950
  } finally {
41636
- clearInterval(timer);
41951
+ clearInterval(timer ?? void 0);
41637
41952
  }
41638
41953
  if (!view) {
41639
41954
  return fail({
@@ -41643,7 +41958,11 @@ function createServer(manager = new JobManager()) {
41643
41958
  }
41644
41959
  if (!ids || ["running", "queued", "cancelling"].includes(view.status)) return ok(view);
41645
41960
  try {
41646
- return ok(pointsOf(view, ids));
41961
+ return ok(pointsOf(
41962
+ /** @type {import('./jobs.mjs').JobView} */
41963
+ view,
41964
+ ids
41965
+ ));
41647
41966
  } catch (err) {
41648
41967
  if (err instanceof RequestError) return fail({ error: err.code, message: err.message });
41649
41968
  throw err;
@@ -41665,7 +41984,10 @@ function createServer(manager = new JobManager()) {
41665
41984
  if (Boolean(job_id) === Boolean(group_id)) {
41666
41985
  return fail({ error: "invalid_request", message: "pass exactly one of job_id or group_id" });
41667
41986
  }
41668
- const view = group_id ? manager.cancelGroup(group_id) : manager.cancel(job_id);
41987
+ const view = group_id ? manager.cancelGroup(group_id) : manager.cancel(
41988
+ /** @type {string} */
41989
+ job_id
41990
+ );
41669
41991
  if (!view) {
41670
41992
  return fail({
41671
41993
  error: "unknown_job",
@@ -41708,7 +42030,7 @@ function createServer(manager = new JobManager()) {
41708
42030
  if (err instanceof RequestError) {
41709
42031
  return fail({ error: err.code, message: err.message, details: err.details ?? null });
41710
42032
  }
41711
- return fail({ error: "internal_error", message: String(err?.message ?? err) });
42033
+ return fail({ error: "internal_error", message: errorMessage2(err) });
41712
42034
  }
41713
42035
  }
41714
42036
  );
@@ -41728,7 +42050,7 @@ function createServer(manager = new JobManager()) {
41728
42050
  return ok(exportChain({ chain_id, group_id }));
41729
42051
  } catch (err) {
41730
42052
  if (err instanceof ExportError) return fail({ error: err.code, message: err.message });
41731
- return fail({ error: "internal_error", message: String(err?.message ?? err) });
42053
+ return fail({ error: "internal_error", message: errorMessage2(err) });
41732
42054
  }
41733
42055
  }
41734
42056
  );
@@ -41779,9 +42101,10 @@ async function main() {
41779
42101
  process.on("exit", () => manager.killNow());
41780
42102
  serveStdio(() => createServer(manager).server);
41781
42103
  }
41782
- var SEVERALLY_VERSION, DEFAULT_WAIT_MS, PROGRESS_EVERY_MS, semver, older, SERVER_INSTRUCTIONS, startDescription, recordShape;
42104
+ var SEVERALLY_VERSION, DEFAULT_WAIT_MS, PROGRESS_EVERY_MS, semver, older, SERVER_INSTRUCTIONS, startDescription, recordShape, errorMessage2;
41783
42105
  var init_server = __esm({
41784
42106
  "src/server.mjs"() {
42107
+ "use strict";
41785
42108
  init_zod();
41786
42109
  init_dist();
41787
42110
  init_stdio();
@@ -41789,13 +42112,17 @@ var init_server = __esm({
41789
42112
  init_schema();
41790
42113
  init_jobs();
41791
42114
  init_export();
41792
- SEVERALLY_VERSION = "2.4.0";
42115
+ SEVERALLY_VERSION = "2.4.2";
41793
42116
  DEFAULT_WAIT_MS = process.env.SEVERALLY_MAX_WAIT_MS ? POLICY.maxWaitMs : Math.min(POLICY.maxWaitMs, 45e3);
41794
42117
  PROGRESS_EVERY_MS = 15e3;
41795
42118
  semver = (v) => String(v).split(".").map(Number);
41796
42119
  older = (a, b) => {
41797
42120
  const [x, y] = [semver(a), semver(b)];
41798
- for (let i = 0; i < 3; i++) if (x[i] !== y[i]) return x[i] < y[i];
42121
+ for (let i = 0; i < 3; i++) {
42122
+ const [p, q] = [x[i], y[i]];
42123
+ if (p === void 0 || q === void 0) return false;
42124
+ if (p !== q) return p < q;
42125
+ }
41799
42126
  return false;
41800
42127
  };
41801
42128
  SERVER_INSTRUCTIONS = `severally lets you get a genuinely independent opinion from another coding agent:
@@ -41858,6 +42185,7 @@ Model, permissions, rounds, timeout and size caps are fixed by this server.`;
41858
42185
  related_item_ids: external_exports.array(external_exports.string().min(1)).max(50).optional().describe("f1, u1, c1 ...")
41859
42186
  }).optional().describe("written after reading the answer")
41860
42187
  };
42188
+ errorMessage2 = (err) => String((typeof err === "object" && err !== null && "message" in err ? err.message : void 0) ?? err);
41861
42189
  }
41862
42190
  });
41863
42191
 
@@ -41875,7 +42203,7 @@ function registration({ cmd, checks }) {
41875
42203
  ran = true;
41876
42204
  if (/(^|[\s@:])severally([\s@:]|$)/m.test(out)) return "registered (listed by the client)";
41877
42205
  } catch (err) {
41878
- if (typeof err.status === "number") ran = true;
42206
+ if (typeof thrown(err, "status") === "number") ran = true;
41879
42207
  }
41880
42208
  }
41881
42209
  return ran ? "not registered" : `could not tell (${cmd} ${checks.map((a) => a.join(" ")).join(" / ")} failed)`;
@@ -41888,9 +42216,9 @@ function login(cli) {
41888
42216
  let ok = true;
41889
42217
  try {
41890
42218
  out = execCommandSync(check2.cmd, check2.args, { encoding: "utf8", timeout: 2e4, stdio: ["ignore", "pipe", "pipe"] });
41891
- } catch (err) {
41892
- if (typeof err.status !== "number") return `could not tell (${check2.cmd} ${check2.args.join(" ")} failed)`;
41893
- out = `${err.stdout ?? ""}`;
42219
+ } catch (e) {
42220
+ if (typeof thrown(e, "status") !== "number") return `could not tell (${check2.cmd} ${check2.args.join(" ")} failed)`;
42221
+ out = `${thrown(e, "stdout") ?? ""}`;
41894
42222
  ok = false;
41895
42223
  }
41896
42224
  try {
@@ -41928,9 +42256,10 @@ function status() {
41928
42256
  `);
41929
42257
  return problem ? 1 : 0;
41930
42258
  }
41931
- var CLIENTS, LOGIN;
42259
+ var CLIENTS, thrown, LOGIN;
41932
42260
  var init_status = __esm({
41933
42261
  "src/status.mjs"() {
42262
+ "use strict";
41934
42263
  init_policy();
41935
42264
  init_server();
41936
42265
  init_platform();
@@ -41954,6 +42283,7 @@ var init_status = __esm({
41954
42283
  { label: "Antigravity", cmd: "agy", checks: [["mcp", "list"]] },
41955
42284
  { label: "OpenCode", cmd: "opencode", checks: [["mcp", "list"]] }
41956
42285
  ];
42286
+ thrown = (e, key) => typeof e === "object" && e !== null ? Reflect.get(e, key) : void 0;
41957
42287
  LOGIN = {
41958
42288
  "claude-code": { cmd: "claude", args: ["auth", "status", "--json"], read: (out) => JSON.parse(out).loggedIn === true },
41959
42289
  // Codex prints its answer on stderr; the exit status carries the verdict.