severally-mcp 2.4.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONFIG.md +1 -0
- package/README.ja.md +44 -19
- package/README.md +44 -18
- package/config.example.json +1 -1
- package/docs/how-it-works.ja.md +2 -0
- package/docs/how-it-works.md +2 -0
- package/package.json +3 -1
- package/plugins/severally/.claude-plugin/plugin.json +1 -1
- package/plugins/severally/.codex-plugin/plugin.json +1 -1
- package/plugins/severally/dist/severally-mcp.mjs +630 -271
- package/plugins/severally/skills/_template/SKILL.md.tmpl +2 -1
- package/plugins/severally/skills/_template/reference.md.tmpl +2 -2
- package/plugins/severally/skills/antigravity/severally/SKILL.md +2 -1
- package/plugins/severally/skills/antigravity/severally/reference.md +2 -2
- package/plugins/severally/skills/claude/severally/SKILL.md +2 -1
- package/plugins/severally/skills/claude/severally/reference.md +2 -2
- package/plugins/severally/skills/codex/severally/SKILL.md +2 -1
- package/plugins/severally/skills/codex/severally/reference.md +2 -2
- package/plugins/severally/skills/opencode/severally/SKILL.md +2 -1
- package/plugins/severally/skills/opencode/severally/reference.md +2 -2
- package/scripts/build.mjs +57 -54
- package/scripts/doctor.mjs +15 -4
- package/scripts/init-config.mjs +2 -1
- package/scripts/install.mjs +73 -9
- package/scripts/live-check.mjs +11 -5
- package/scripts/live-mcp-check.mjs +10 -4
- package/src/adapters/antigravity-sandbox.mjs +24 -6
- package/src/adapters/antigravity.mjs +80 -17
- package/src/adapters/claude-code.mjs +49 -8
- package/src/adapters/codex.mjs +56 -6
- package/src/adapters/opencode-sandbox.mjs +20 -2
- package/src/adapters/opencode.mjs +115 -19
- package/src/brief.mjs +84 -7
- package/src/consultants.mjs +193 -27
- package/src/cross-spawn-escape.d.ts +7 -0
- package/src/export.mjs +50 -6
- package/src/expose-paths.mjs +149 -7
- package/src/failures.mjs +8 -1
- package/src/jobs.mjs +632 -152
- package/src/jsonc.mjs +11 -3
- package/src/parse-result.mjs +91 -14
- package/src/platform.mjs +52 -2
- package/src/policy.mjs +151 -28
- package/src/redact.mjs +22 -3
- package/src/result-schema.mjs +13 -0
- package/src/run.mjs +158 -12
- package/src/schema.mjs +84 -11
- package/src/server.mjs +49 -14
- package/src/status.mjs +16 -4
- 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
|
|
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) =>
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
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
|
-
|
|
751
|
-
if (
|
|
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
|
-
|
|
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 (!
|
|
810
|
+
if (!cliIds.includes(id2)) {
|
|
790
811
|
problems.push(`clis.${id2}: unknown CLI (known: ${CLI_IDS.join(", ")})`);
|
|
791
812
|
continue;
|
|
792
813
|
}
|
|
@@ -840,9 +861,9 @@ 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 (!
|
|
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
|
-
for (const k of ["effort", "vendor", "note"]) {
|
|
866
|
+
for (const k of ["effort", "fallback_model", "vendor", "note"]) {
|
|
846
867
|
if (def[k] !== void 0 && typeof def[k] !== "string") problems.push(`${at}.${k}: must be a string`);
|
|
847
868
|
}
|
|
848
869
|
if (def.enabled !== void 0 && typeof def.enabled !== "boolean") problems.push(`${at}.enabled: must be true or false`);
|
|
@@ -850,6 +871,10 @@ function buildConsultants(config2) {
|
|
|
850
871
|
if (def[k] !== void 0 && !(typeof def[k] === "number" && Number.isFinite(def[k])))
|
|
851
872
|
problems.push(`${at}.${k}: must be a number`);
|
|
852
873
|
}
|
|
874
|
+
if (typeof def.fallback_model === "string" && def.fallback_model.trim() === "")
|
|
875
|
+
problems.push(`${at}.fallback_model: must not be empty`);
|
|
876
|
+
else if (typeof def.fallback_model === "string" && def.fallback_model.trim() === String(def.model).trim())
|
|
877
|
+
problems.push(`${at}.fallback_model: must differ from model; the fallback is for when the model's quota is used up`);
|
|
853
878
|
if (def.max_budget_usd !== void 0 && def.cli !== "claude-code")
|
|
854
879
|
problems.push(`${at}.max_budget_usd: only a claude-code consultant has a budget`);
|
|
855
880
|
if (def.effort !== void 0 && def.cli === "antigravity")
|
|
@@ -858,15 +883,19 @@ function buildConsultants(config2) {
|
|
|
858
883
|
broken[name] = problems.slice(before);
|
|
859
884
|
continue;
|
|
860
885
|
}
|
|
861
|
-
const
|
|
862
|
-
|
|
886
|
+
const valid = (
|
|
887
|
+
/** @type {ConsultantDef} */
|
|
888
|
+
def
|
|
889
|
+
);
|
|
890
|
+
const pair = `${valid.cli}
|
|
891
|
+
${valid.model.trim()}`;
|
|
863
892
|
if (seen.has(pair)) {
|
|
864
893
|
problems.push(`${at}: same cli and model as consultants.${seen.get(pair)}; asking one model twice adds no independence`);
|
|
865
894
|
broken[name] = problems.slice(before);
|
|
866
895
|
continue;
|
|
867
896
|
}
|
|
868
897
|
seen.set(pair, name);
|
|
869
|
-
consultants.push(make(name,
|
|
898
|
+
consultants.push(make(name, valid));
|
|
870
899
|
}
|
|
871
900
|
}
|
|
872
901
|
return { consultants: Object.freeze(consultants), ranking: Object.freeze(ranking), problems, global: global2, broken, brokenClis };
|
|
@@ -880,7 +909,8 @@ function make(name, def) {
|
|
|
880
909
|
effort: typeof def.effort === "string" && def.effort.trim() !== "" ? def.effort.trim() : null,
|
|
881
910
|
timeoutMs: typeof def.timeout_ms === "number" ? clamp(def.timeout_ms, 1e3, 72e5) : null,
|
|
882
911
|
maxBudgetUsd: typeof def.max_budget_usd === "number" ? clamp(def.max_budget_usd, 0.05, 20) : null,
|
|
883
|
-
|
|
912
|
+
fallbackModel: typeof def.fallback_model === "string" && def.fallback_model.trim() !== "" ? def.fallback_model.trim() : null,
|
|
913
|
+
vendor: vendorOf(model, def.vendor),
|
|
884
914
|
enabled: def.enabled !== false,
|
|
885
915
|
note: typeof def.note === "string" && def.note.trim() !== "" ? def.note.trim() : null
|
|
886
916
|
});
|
|
@@ -888,6 +918,7 @@ function make(name, def) {
|
|
|
888
918
|
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
919
|
var init_consultants = __esm({
|
|
890
920
|
"src/consultants.mjs"() {
|
|
921
|
+
"use strict";
|
|
891
922
|
CLI_IDS = Object.freeze(["codex", "claude-code", "antigravity", "opencode"]);
|
|
892
923
|
DEFAULT_MODELS = Object.freeze({
|
|
893
924
|
codex: "gpt-6-astra",
|
|
@@ -932,7 +963,7 @@ var init_consultants = __esm({
|
|
|
932
963
|
};
|
|
933
964
|
TOP_KEYS = ["server", "clis", "consultants", "ranking"];
|
|
934
965
|
CLI_KEYS = ["enabled", "note", "bin", "args", "env", "provider"];
|
|
935
|
-
CONSULTANT_KEYS = ["cli", "model", "effort", "timeout_ms", "max_budget_usd", "vendor", "enabled", "note"];
|
|
966
|
+
CONSULTANT_KEYS = ["cli", "model", "effort", "timeout_ms", "max_budget_usd", "fallback_model", "vendor", "enabled", "note"];
|
|
936
967
|
isObject = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
937
968
|
clamp = (v, min, max) => Math.min(max, Math.max(min, v));
|
|
938
969
|
}
|
|
@@ -960,7 +991,7 @@ function isInstalled(command) {
|
|
|
960
991
|
}
|
|
961
992
|
function resolveCaller(raw) {
|
|
962
993
|
const key = normalizeName(raw);
|
|
963
|
-
return typeof key === "string"
|
|
994
|
+
return typeof key === "string" ? CALLER_ALIASES[key] ?? null : null;
|
|
964
995
|
}
|
|
965
996
|
function detectCaller(env = process.env) {
|
|
966
997
|
if (env.CLAUDECODE === "1" || env.CLAUDE_CODE_ENTRYPOINT) return "claude-code";
|
|
@@ -1020,6 +1051,11 @@ function isProtectedPath(abs) {
|
|
|
1020
1051
|
function consultantByName(name) {
|
|
1021
1052
|
return POLICY.consultants.find((c) => c.name === name);
|
|
1022
1053
|
}
|
|
1054
|
+
function consultantNamed(name) {
|
|
1055
|
+
const c = consultantByName(name);
|
|
1056
|
+
if (!c) throw new Error(`no consultant named "${name}"`);
|
|
1057
|
+
return c;
|
|
1058
|
+
}
|
|
1023
1059
|
function consultantAvailable(c) {
|
|
1024
1060
|
return c.enabled && POLICY.clis[c.cli].available;
|
|
1025
1061
|
}
|
|
@@ -1051,17 +1087,20 @@ function limitsSummary() {
|
|
|
1051
1087
|
max_concurrent_jobs: POLICY.maxConcurrent,
|
|
1052
1088
|
max_wait_ms: POLICY.maxWaitMs,
|
|
1053
1089
|
input_char_budget: POLICY.input.totalCharsMax,
|
|
1054
|
-
// Stated so it is true of all
|
|
1055
|
-
//
|
|
1056
|
-
//
|
|
1057
|
-
//
|
|
1058
|
-
//
|
|
1059
|
-
|
|
1090
|
+
// Stated so it is true of all four CLIs. Reading is allowed for every one
|
|
1091
|
+
// of them -- what keeps a consultation brief-only is that the child starts
|
|
1092
|
+
// in an empty working directory and is never told where the repository
|
|
1093
|
+
// is, not that it cannot read. Only Claude Code drops its whole-disk read
|
|
1094
|
+
// when files are exposed (claude-code.mjs). Codex is additionally
|
|
1095
|
+
// sandboxed read-only rather than execution-free, so do not advertise
|
|
1096
|
+
// "no execution".
|
|
1097
|
+
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
1098
|
};
|
|
1061
1099
|
}
|
|
1062
1100
|
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
1101
|
var init_policy = __esm({
|
|
1064
1102
|
"src/policy.mjs"() {
|
|
1103
|
+
"use strict";
|
|
1065
1104
|
init_jsonc();
|
|
1066
1105
|
init_platform();
|
|
1067
1106
|
init_consultants();
|
|
@@ -1072,7 +1111,8 @@ var init_policy = __esm({
|
|
|
1072
1111
|
try {
|
|
1073
1112
|
return parseJsonc(fs2.readFileSync(CONFIG_PATH, "utf8"));
|
|
1074
1113
|
} catch (err) {
|
|
1075
|
-
|
|
1114
|
+
const code = err instanceof Error && "code" in err ? err.code : void 0;
|
|
1115
|
+
if (code !== "ENOENT") configError = `${CONFIG_PATH}: ${err instanceof Error ? err.message : String(err)}`;
|
|
1076
1116
|
return {};
|
|
1077
1117
|
}
|
|
1078
1118
|
})();
|
|
@@ -1083,17 +1123,22 @@ var init_policy = __esm({
|
|
|
1083
1123
|
}
|
|
1084
1124
|
FROM = configError ? buildConsultants({}) : BUILT;
|
|
1085
1125
|
LOCAL = configError ? [] : BUILT.problems.filter((p) => !BUILT.global.includes(p));
|
|
1086
|
-
usable = configError ? {} : CONFIG;
|
|
1126
|
+
usable = configError || !isObject(CONFIG) ? {} : CONFIG;
|
|
1087
1127
|
cfgCli = (id2) => {
|
|
1088
|
-
|
|
1128
|
+
const broken = Object.hasOwn(BUILT.brokenClis, id2) ? BUILT.brokenClis[id2] : void 0;
|
|
1129
|
+
if (broken && !configError) {
|
|
1089
1130
|
return {
|
|
1090
1131
|
enabled: false,
|
|
1091
|
-
note: `turned off: the config file has a mistake in clis.${id2}: ${
|
|
1132
|
+
note: `turned off: the config file has a mistake in clis.${id2}: ${broken.join("; ")}`
|
|
1092
1133
|
};
|
|
1093
1134
|
}
|
|
1094
|
-
|
|
1135
|
+
const def = isObject(usable.clis) ? usable.clis[id2] : void 0;
|
|
1136
|
+
return isObject(def) ? (
|
|
1137
|
+
/** @type {CliSection} */
|
|
1138
|
+
def
|
|
1139
|
+
) : {};
|
|
1095
1140
|
};
|
|
1096
|
-
cfgServer = usable.server
|
|
1141
|
+
cfgServer = isObject(usable.server) ? usable.server : {};
|
|
1097
1142
|
num = (name, dflt, min, max) => {
|
|
1098
1143
|
const raw = process.env[name];
|
|
1099
1144
|
if (raw === void 0 || raw === "") return dflt;
|
|
@@ -1131,7 +1176,8 @@ var init_policy = __esm({
|
|
|
1131
1176
|
CALLER_INPUTS = Object.freeze(Object.keys(CALLER_ALIASES));
|
|
1132
1177
|
MODES = ["explore", "review", "debate"];
|
|
1133
1178
|
VERDICTS = ["unverified", "confirmed", "not_applicable", "unverifiable"];
|
|
1134
|
-
STANCES =
|
|
1179
|
+
STANCES = /** @type {const} */
|
|
1180
|
+
["proceed", "do_not_proceed", "alternative", "undetermined"];
|
|
1135
1181
|
binFor = (id2, envName, dflt) => {
|
|
1136
1182
|
const fromEnv = process.env[envName];
|
|
1137
1183
|
if (fromEnv !== void 0 && fromEnv !== "") return expandHome(fromEnv);
|
|
@@ -1151,7 +1197,10 @@ var init_policy = __esm({
|
|
|
1151
1197
|
CLAUDE_BIN = binFor("claude-code", "SEVERALLY_CLAUDE_BIN", "claude");
|
|
1152
1198
|
AGY_BIN = binFor("antigravity", "SEVERALLY_AGY_BIN", "agy");
|
|
1153
1199
|
OPENCODE_BIN = binFor("opencode", "SEVERALLY_OPENCODE_BIN", "opencode");
|
|
1154
|
-
extras = (id2) => ({
|
|
1200
|
+
extras = (id2) => ({
|
|
1201
|
+
extraArgs: cfgCli(id2).args ?? [],
|
|
1202
|
+
extraEnv: cfgCli(id2).env ?? {}
|
|
1203
|
+
});
|
|
1155
1204
|
POLICY = Object.freeze({
|
|
1156
1205
|
home: str("SEVERALLY_HOME", path2.join(os.homedir(), ".severally")),
|
|
1157
1206
|
// Per CLI: how to launch it and whether it can run here. What runs on it is
|
|
@@ -32610,12 +32659,12 @@ var init_ajvProvider_CEoC_sr = __esm({
|
|
|
32610
32659
|
},
|
|
32611
32660
|
code(cxt) {
|
|
32612
32661
|
const { gen, data, schema, parentSchema, it } = cxt;
|
|
32613
|
-
const { oneOf } = parentSchema;
|
|
32662
|
+
const { oneOf: oneOf2 } = parentSchema;
|
|
32614
32663
|
if (!it.opts.discriminator) throw new Error("discriminator: requires discriminator option");
|
|
32615
32664
|
const tagName = schema.propertyName;
|
|
32616
32665
|
if (typeof tagName != "string") throw new Error("discriminator: requires propertyName");
|
|
32617
32666
|
if (schema.mapping) throw new Error("discriminator: mapping is not supported");
|
|
32618
|
-
if (!
|
|
32667
|
+
if (!oneOf2) throw new Error("discriminator: requires oneOf keyword");
|
|
32619
32668
|
const valid = gen.let("valid", false);
|
|
32620
32669
|
const tag = gen.const("tag", (0, codegen_1._)`${data}${(0, codegen_1.getProperty)(tagName)}`);
|
|
32621
32670
|
gen.if((0, codegen_1._)`typeof ${tag} == "string"`, () => validateMapping(), () => cxt.error(false, {
|
|
@@ -32653,8 +32702,8 @@ var init_ajvProvider_CEoC_sr = __esm({
|
|
|
32653
32702
|
const oneOfMapping = {};
|
|
32654
32703
|
const topRequired = hasRequired(parentSchema);
|
|
32655
32704
|
let tagRequired = true;
|
|
32656
|
-
for (let i = 0; i <
|
|
32657
|
-
let sch =
|
|
32705
|
+
for (let i = 0; i < oneOf2.length; i++) {
|
|
32706
|
+
let sch = oneOf2[i];
|
|
32658
32707
|
if ((sch === null || sch === void 0 ? void 0 : sch.$ref) && !(0, util_1.schemaHasRulesButRef)(sch, it.self.RULES)) {
|
|
32659
32708
|
const ref = sch.$ref;
|
|
32660
32709
|
sch = compile_1.resolveRef.call(it.self, it.schemaEnv.root, it.baseId, ref);
|
|
@@ -35561,11 +35610,11 @@ var init_mcp_DIH4cS6P = __esm({
|
|
|
35561
35610
|
* @param errorMessage - The error message.
|
|
35562
35611
|
* @returns The tool error result.
|
|
35563
35612
|
*/
|
|
35564
|
-
createToolError(
|
|
35613
|
+
createToolError(errorMessage3) {
|
|
35565
35614
|
return {
|
|
35566
35615
|
content: [{
|
|
35567
35616
|
type: "text",
|
|
35568
|
-
text:
|
|
35617
|
+
text: errorMessage3
|
|
35569
35618
|
}],
|
|
35570
35619
|
isError: true
|
|
35571
35620
|
};
|
|
@@ -36508,8 +36557,8 @@ var init_stdio = __esm({
|
|
|
36508
36557
|
import path3 from "node:path";
|
|
36509
36558
|
function charCount(req) {
|
|
36510
36559
|
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;
|
|
36560
|
+
for (const c of req.constraints ?? []) n += c.length;
|
|
36561
|
+
for (const c of req.success_criteria ?? []) n += c.length;
|
|
36513
36562
|
for (const f of req.context.facts) n += f.length;
|
|
36514
36563
|
for (const c of req.context.counterpoints) n += c.length;
|
|
36515
36564
|
if (req.context.proposal) n += req.context.proposal.length;
|
|
@@ -36522,19 +36571,27 @@ function parseRequest(raw, { isFollowup = false, parentMode = null, groupFollowu
|
|
|
36522
36571
|
const issues = parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`);
|
|
36523
36572
|
throw new RequestError(`request failed validation: ${issues.join("; ")}`, "invalid_request", issues);
|
|
36524
36573
|
}
|
|
36525
|
-
const req =
|
|
36526
|
-
|
|
36527
|
-
|
|
36574
|
+
const req = (
|
|
36575
|
+
/** @type {DraftRequest} */
|
|
36576
|
+
parsed.data
|
|
36577
|
+
);
|
|
36578
|
+
const hoist = (k) => {
|
|
36579
|
+
const inner = req.context[k];
|
|
36580
|
+
if (inner === void 0) return;
|
|
36528
36581
|
if (req[k] !== void 0) {
|
|
36529
36582
|
throw new RequestError(`pass ${k} once: at the top of the request or inside context, not both`, "invalid_request");
|
|
36530
36583
|
}
|
|
36531
|
-
req[k] =
|
|
36584
|
+
req[k] = inner;
|
|
36532
36585
|
delete req.context[k];
|
|
36533
|
-
}
|
|
36586
|
+
};
|
|
36587
|
+
hoist("constraints");
|
|
36588
|
+
hoist("success_criteria");
|
|
36534
36589
|
req.network ??= "on";
|
|
36535
36590
|
req.success_criteria ??= [];
|
|
36536
36591
|
req.constraints ??= [];
|
|
36537
|
-
|
|
36592
|
+
req.context.facts ??= [];
|
|
36593
|
+
req.context.counterpoints ??= [];
|
|
36594
|
+
req.context.artifacts ??= [];
|
|
36538
36595
|
const hasSingle = req.target !== void 0 && req.target !== null;
|
|
36539
36596
|
const hasMany = Array.isArray(req.targets) && req.targets.length > 0;
|
|
36540
36597
|
if (hasSingle && hasMany) {
|
|
@@ -36605,7 +36662,8 @@ function parseRequest(raw, { isFollowup = false, parentMode = null, groupFollowu
|
|
|
36605
36662
|
);
|
|
36606
36663
|
}
|
|
36607
36664
|
req.targets = unique;
|
|
36608
|
-
req.target =
|
|
36665
|
+
req.target = /** @type {string} */
|
|
36666
|
+
unique[0];
|
|
36609
36667
|
req.fanout = unique.length > 1;
|
|
36610
36668
|
req.caller = resolveCaller(req.caller ?? "") ?? null;
|
|
36611
36669
|
req.caller_model = req.caller_model ?? null;
|
|
@@ -36656,6 +36714,7 @@ function parseRequest(raw, { isFollowup = false, parentMode = null, groupFollowu
|
|
|
36656
36714
|
var L, trimmed, artifactSchema, exposedPathSchema, gitRevision, contextSchema, targetSpec, INITIATORS, NETWORK_MODES, requestSchema, RequestError;
|
|
36657
36715
|
var init_schema = __esm({
|
|
36658
36716
|
"src/schema.mjs"() {
|
|
36717
|
+
"use strict";
|
|
36659
36718
|
init_zod();
|
|
36660
36719
|
init_policy();
|
|
36661
36720
|
init_consultants();
|
|
@@ -36740,6 +36799,11 @@ var init_schema = __esm({
|
|
|
36740
36799
|
followup_to: external_exports.string().trim().max(80).nullish()
|
|
36741
36800
|
}).strict();
|
|
36742
36801
|
RequestError = class extends Error {
|
|
36802
|
+
/**
|
|
36803
|
+
* @param {string} message
|
|
36804
|
+
* @param {string} [code]
|
|
36805
|
+
* @param {unknown[]} [details]
|
|
36806
|
+
*/
|
|
36743
36807
|
constructor(message, code = "invalid_request", details = void 0) {
|
|
36744
36808
|
super(message);
|
|
36745
36809
|
this.name = "RequestError";
|
|
@@ -36754,6 +36818,7 @@ var init_schema = __esm({
|
|
|
36754
36818
|
var s, enumOf, obj, arr, CONSULT_RESULT_SCHEMA, RESULT_KEYS;
|
|
36755
36819
|
var init_result_schema = __esm({
|
|
36756
36820
|
"src/result-schema.mjs"() {
|
|
36821
|
+
"use strict";
|
|
36757
36822
|
s = (desc) => ({ type: "string", description: desc });
|
|
36758
36823
|
enumOf = (values, desc) => ({ type: "string", enum: values, description: desc });
|
|
36759
36824
|
obj = (properties, description) => ({
|
|
@@ -36844,14 +36909,14 @@ var init_result_schema = __esm({
|
|
|
36844
36909
|
function looksLikeCode(name, value) {
|
|
36845
36910
|
if (CODE_WORDS.has(value.toLowerCase()) || value.startsWith("${") || /^\$[A-Z_][A-Z0-9_]*$/.test(value) || /^(?:process\.env\.|os\.environ\b)/.test(value))
|
|
36846
36911
|
return true;
|
|
36847
|
-
if (value.length >= 8 && /^PASSW(?:OR)?D$/i.test(name.split("_").at(-1))) return false;
|
|
36912
|
+
if (value.length >= 8 && /^PASSW(?:OR)?D$/i.test(name.split("_").at(-1) ?? "")) return false;
|
|
36848
36913
|
return /^[A-Za-z_][\w$]*(?:\.[A-Za-z_$][\w$]*)+$/.test(value) || /^[A-Za-z_][\w$.]*\(/.test(value) || value.startsWith("&");
|
|
36849
36914
|
}
|
|
36850
36915
|
function redact(value) {
|
|
36851
36916
|
if (value === null || value === void 0) return value;
|
|
36852
36917
|
if (typeof value === "string") {
|
|
36853
36918
|
let out = value;
|
|
36854
|
-
for (const [re, rep] of PATTERNS) out = out.replace(re, rep);
|
|
36919
|
+
for (const [re, rep] of PATTERNS) out = typeof rep === "string" ? out.replace(re, rep) : out.replace(re, rep);
|
|
36855
36920
|
return out;
|
|
36856
36921
|
}
|
|
36857
36922
|
if (Array.isArray(value)) return value.map(redact);
|
|
@@ -36865,6 +36930,7 @@ function redact(value) {
|
|
|
36865
36930
|
var CODE_WORDS, PATTERNS;
|
|
36866
36931
|
var init_redact = __esm({
|
|
36867
36932
|
"src/redact.mjs"() {
|
|
36933
|
+
"use strict";
|
|
36868
36934
|
CODE_WORDS = /* @__PURE__ */ new Set([
|
|
36869
36935
|
"true",
|
|
36870
36936
|
"false",
|
|
@@ -36960,7 +37026,7 @@ function statEntry(source) {
|
|
|
36960
37026
|
try {
|
|
36961
37027
|
stat = fs3.lstatSync(source);
|
|
36962
37028
|
} catch (err) {
|
|
36963
|
-
throw new ExposeError(`expose_paths entry "${source}" cannot be read: ${err
|
|
37029
|
+
throw new ExposeError(`expose_paths entry "${source}" cannot be read: ${messageOf(err)}`, "not_found");
|
|
36964
37030
|
}
|
|
36965
37031
|
if (stat.isSymbolicLink()) {
|
|
36966
37032
|
throw new ExposeError(`expose_paths entry "${source}" is a symlink; name the path it points at instead`, "invalid_type");
|
|
@@ -36975,7 +37041,7 @@ function walkDir(root, dir, onFile, totals, skipped) {
|
|
|
36975
37041
|
try {
|
|
36976
37042
|
items = fs3.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
36977
37043
|
} catch (err) {
|
|
36978
|
-
throw new ExposeError(`expose_paths entry "${dir}" cannot be read: ${err
|
|
37044
|
+
throw new ExposeError(`expose_paths entry "${dir}" cannot be read: ${messageOf(err)}`, "not_found");
|
|
36979
37045
|
}
|
|
36980
37046
|
for (const item of items) {
|
|
36981
37047
|
const child = path4.join(dir, item.name);
|
|
@@ -37002,7 +37068,11 @@ function walkDir(root, dir, onFile, totals, skipped) {
|
|
|
37002
37068
|
function byPathParts(a, b) {
|
|
37003
37069
|
const x = a.split("/");
|
|
37004
37070
|
const y = b.split("/");
|
|
37005
|
-
for (
|
|
37071
|
+
for (const [i, part] of x.entries()) {
|
|
37072
|
+
const other = y[i];
|
|
37073
|
+
if (other === void 0) break;
|
|
37074
|
+
if (part !== other) return part < other ? -1 : 1;
|
|
37075
|
+
}
|
|
37006
37076
|
return x.length - y.length;
|
|
37007
37077
|
}
|
|
37008
37078
|
function walkGit(dir, exposedAs, onFile, totals, skipped) {
|
|
@@ -37213,7 +37283,7 @@ function reexposeChange(workdir, entry, manifest) {
|
|
|
37213
37283
|
const changes = [];
|
|
37214
37284
|
for (const c of entry.changes ?? []) {
|
|
37215
37285
|
const m = /^(.*) \(([^()]+)\)$/.exec(c);
|
|
37216
|
-
const p = m
|
|
37286
|
+
const p = m?.[1] ?? c;
|
|
37217
37287
|
if (p.includes("[REDACTED")) {
|
|
37218
37288
|
manifest.skipped.push({ source: path4.join(entry.source, p), reason: "masked", exposed_as: `workspace/repo/${p}` });
|
|
37219
37289
|
continue;
|
|
@@ -37268,10 +37338,7 @@ function git(cwd, args) {
|
|
|
37268
37338
|
env: { ...env, GIT_OPTIONAL_LOCKS: "0" }
|
|
37269
37339
|
});
|
|
37270
37340
|
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
|
-
);
|
|
37341
|
+
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
37342
|
throw new ExposeError(`context.expose_diff: git ${args[0]} failed in "${cwd}": ${why}`, "not_found");
|
|
37276
37343
|
}
|
|
37277
37344
|
return r.stdout;
|
|
@@ -37283,7 +37350,11 @@ function exposeDiff(workdir, { repo, base = "HEAD" }, manifest, maxChars) {
|
|
|
37283
37350
|
git(top, ["diff", "--name-status", "-z", "--no-renames", "--no-relative", "--no-ext-diff", "--no-textconv", base, "--"])
|
|
37284
37351
|
);
|
|
37285
37352
|
const changes = [];
|
|
37286
|
-
for (let i = 0; i + 1 < tokens.length; i += 2)
|
|
37353
|
+
for (let i = 0; i + 1 < tokens.length; i += 2) {
|
|
37354
|
+
const status2 = tokens[i];
|
|
37355
|
+
const file2 = tokens[i + 1];
|
|
37356
|
+
if (status2 !== void 0 && file2 !== void 0) changes.push({ status: status2, path: file2 });
|
|
37357
|
+
}
|
|
37287
37358
|
for (const p of nulSplit(git(top, ["ls-files", "--others", "--exclude-standard", "-z"])))
|
|
37288
37359
|
changes.push({ status: "untracked", path: p });
|
|
37289
37360
|
const before = { ...manifest.totals };
|
|
@@ -37336,18 +37407,24 @@ function exposeDiff(workdir, { repo, base = "HEAD" }, manifest, maxChars) {
|
|
|
37336
37407
|
}
|
|
37337
37408
|
return text2;
|
|
37338
37409
|
}
|
|
37339
|
-
var ExposeError, nulSplit;
|
|
37410
|
+
var ExposeError, messageOf, nulSplit;
|
|
37340
37411
|
var init_expose_paths = __esm({
|
|
37341
37412
|
"src/expose-paths.mjs"() {
|
|
37413
|
+
"use strict";
|
|
37342
37414
|
init_policy();
|
|
37343
37415
|
init_redact();
|
|
37344
37416
|
ExposeError = class extends Error {
|
|
37417
|
+
/**
|
|
37418
|
+
* @param {string} message
|
|
37419
|
+
* @param {string} code
|
|
37420
|
+
*/
|
|
37345
37421
|
constructor(message, code) {
|
|
37346
37422
|
super(message);
|
|
37347
37423
|
this.name = "ExposeError";
|
|
37348
37424
|
this.code = code;
|
|
37349
37425
|
}
|
|
37350
37426
|
};
|
|
37427
|
+
messageOf = (err) => err instanceof Error ? err.message : String(err);
|
|
37351
37428
|
nulSplit = (buf) => buf.toString("utf8").split("\0").filter(Boolean);
|
|
37352
37429
|
}
|
|
37353
37430
|
});
|
|
@@ -37458,7 +37535,7 @@ function renderExposeManifest(manifest) {
|
|
|
37458
37535
|
if (skipped.length) skipped.unshift("");
|
|
37459
37536
|
return [
|
|
37460
37537
|
"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
|
|
37538
|
+
"them directly. They are the only part of the lead's repository the lead gave you -- if you need something that is",
|
|
37462
37539
|
"not here, record it under `unknowns` rather than guessing. Text files were passed through the same",
|
|
37463
37540
|
"credential masking as this brief; binary files were copied unchanged.",
|
|
37464
37541
|
"",
|
|
@@ -37518,8 +37595,9 @@ function renderBrief(req, chain = { round: 1, priorRounds: [] }, exposeManifest
|
|
|
37518
37595
|
const blocks = chain.priorRounds.map(renderRound);
|
|
37519
37596
|
let kept = 0;
|
|
37520
37597
|
let used = 0;
|
|
37521
|
-
for (
|
|
37522
|
-
used
|
|
37598
|
+
for (const block of [...blocks].reverse()) {
|
|
37599
|
+
if (kept > 0 && used + block.length > HISTORY_CHARS) break;
|
|
37600
|
+
used += block.length;
|
|
37523
37601
|
kept += 1;
|
|
37524
37602
|
}
|
|
37525
37603
|
const left = chain.priorRounds.slice(0, blocks.length - kept).map((p) => p.round);
|
|
@@ -37543,6 +37621,7 @@ function renderBrief(req, chain = { round: 1, priorRounds: [] }, exposeManifest
|
|
|
37543
37621
|
var MODE_BRIEFS, GUARDRAILS_HEAD, GUARDRAILS_RULES, WEB_ON, WEB_OFF, WORKDIR_EMPTY, WORKDIR_EXPOSED, GUARDRAILS_TAIL, EXPOSE_LISTING_MAX, clip, HISTORY_CHARS;
|
|
37544
37622
|
var init_brief = __esm({
|
|
37545
37623
|
"src/brief.mjs"() {
|
|
37624
|
+
"use strict";
|
|
37546
37625
|
init_policy();
|
|
37547
37626
|
init_result_schema();
|
|
37548
37627
|
init_redact();
|
|
@@ -37581,7 +37660,7 @@ var init_brief = __esm({
|
|
|
37581
37660
|
WEB_ON = "- You may search and browse the web freely; cite what you actually opened in `references`.";
|
|
37582
37661
|
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
37662
|
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
|
|
37663
|
+
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
37664
|
GUARDRAILS_TAIL = [
|
|
37586
37665
|
"- Never include credentials, API keys, tokens, or environment variable values in your answer.",
|
|
37587
37666
|
"- Do not output your internal reasoning trace. Report conclusions with their grounds and evidence.",
|
|
@@ -37611,15 +37690,15 @@ function clampText(v, max) {
|
|
|
37611
37690
|
}
|
|
37612
37691
|
function level(v, fallback = null) {
|
|
37613
37692
|
const t = typeof v === "string" ? v.trim().toLowerCase() : "";
|
|
37614
|
-
return LEVELS
|
|
37693
|
+
return oneOf(LEVELS, t) ? t : fallback;
|
|
37615
37694
|
}
|
|
37616
37695
|
function basis(v) {
|
|
37617
37696
|
const t = typeof v === "string" ? v.trim().toLowerCase() : "";
|
|
37618
|
-
return BASIS
|
|
37697
|
+
return oneOf(BASIS, t) ? t : null;
|
|
37619
37698
|
}
|
|
37620
37699
|
function stance(v) {
|
|
37621
37700
|
const t = typeof v === "string" ? v.trim().toLowerCase() : "";
|
|
37622
|
-
return STANCES
|
|
37701
|
+
return oneOf(STANCES, t) ? t : null;
|
|
37623
37702
|
}
|
|
37624
37703
|
function extractJson(text2) {
|
|
37625
37704
|
if (typeof text2 !== "string") return null;
|
|
@@ -37627,9 +37706,7 @@ function extractJson(text2) {
|
|
|
37627
37706
|
if (!trimmed2) return null;
|
|
37628
37707
|
const candidates = [];
|
|
37629
37708
|
if (trimmed2.startsWith("{")) candidates.push(trimmed2);
|
|
37630
|
-
const
|
|
37631
|
-
let m;
|
|
37632
|
-
while ((m = fence2.exec(trimmed2)) !== null) candidates.push(m[1].trim());
|
|
37709
|
+
for (const [, body = ""] of trimmed2.matchAll(/```(?:json)?\s*([\s\S]*?)```/gi)) candidates.push(body.trim());
|
|
37633
37710
|
const start = trimmed2.indexOf("{");
|
|
37634
37711
|
if (start !== -1) {
|
|
37635
37712
|
let depth = 0;
|
|
@@ -37679,7 +37756,10 @@ function mapList(raw, fields, { requiredField, idPrefix = null }) {
|
|
|
37679
37756
|
item[key] = kind === "level" ? level(entry[key]) : clampText(entry[key], O.itemTextMax);
|
|
37680
37757
|
}
|
|
37681
37758
|
if (!item[requiredField]) continue;
|
|
37682
|
-
out.push(
|
|
37759
|
+
out.push(
|
|
37760
|
+
/** @type {T} */
|
|
37761
|
+
item
|
|
37762
|
+
);
|
|
37683
37763
|
}
|
|
37684
37764
|
return out;
|
|
37685
37765
|
}
|
|
@@ -37758,10 +37838,14 @@ function normalizeResult(raw) {
|
|
|
37758
37838
|
).filter((r) => r.title || r.url)
|
|
37759
37839
|
};
|
|
37760
37840
|
const listTruncated = Object.fromEntries(
|
|
37761
|
-
["findings", "alternatives", "unknowns", "decision_changers", "next_checks", "remaining_disagreements"].
|
|
37841
|
+
["findings", "alternatives", "unknowns", "decision_changers", "next_checks", "remaining_disagreements"].flatMap((k) => {
|
|
37842
|
+
const v = src[k];
|
|
37843
|
+
return Array.isArray(v) && v.length > O.listMax ? [[k, v.length]] : [];
|
|
37844
|
+
})
|
|
37762
37845
|
);
|
|
37846
|
+
const fields = result;
|
|
37763
37847
|
const missing = RESULT_KEYS.filter((k) => {
|
|
37764
|
-
const v =
|
|
37848
|
+
const v = fields[k];
|
|
37765
37849
|
return v === null || v === void 0 || Array.isArray(v) && v.length === 0;
|
|
37766
37850
|
});
|
|
37767
37851
|
const groundless = result.findings.filter((f) => !f.grounds).length;
|
|
@@ -37775,13 +37859,18 @@ function normalizeResult(raw) {
|
|
|
37775
37859
|
}
|
|
37776
37860
|
};
|
|
37777
37861
|
}
|
|
37778
|
-
var OutputError, O, LEVELS, BASIS;
|
|
37862
|
+
var OutputError, O, oneOf, LEVELS, BASIS;
|
|
37779
37863
|
var init_parse_result = __esm({
|
|
37780
37864
|
"src/parse-result.mjs"() {
|
|
37865
|
+
"use strict";
|
|
37781
37866
|
init_policy();
|
|
37782
37867
|
init_result_schema();
|
|
37783
37868
|
init_redact();
|
|
37784
37869
|
OutputError = class extends Error {
|
|
37870
|
+
/**
|
|
37871
|
+
* @param {string} message
|
|
37872
|
+
* @param {string} [detail]
|
|
37873
|
+
*/
|
|
37785
37874
|
constructor(message, detail) {
|
|
37786
37875
|
super(message);
|
|
37787
37876
|
this.name = "OutputError";
|
|
@@ -37789,6 +37878,7 @@ var init_parse_result = __esm({
|
|
|
37789
37878
|
}
|
|
37790
37879
|
};
|
|
37791
37880
|
O = POLICY.output;
|
|
37881
|
+
oneOf = (list, t) => list.some((x) => x === t);
|
|
37792
37882
|
LEVELS = ["high", "medium", "low"];
|
|
37793
37883
|
BASIS = ["sufficient", "thin", "insufficient"];
|
|
37794
37884
|
}
|
|
@@ -37806,6 +37896,7 @@ function isRetriable(kind) {
|
|
|
37806
37896
|
var RULES, AUTO_RETRY_KINDS;
|
|
37807
37897
|
var init_failures = __esm({
|
|
37808
37898
|
"src/failures.mjs"() {
|
|
37899
|
+
"use strict";
|
|
37809
37900
|
RULES = [
|
|
37810
37901
|
[
|
|
37811
37902
|
/\b(usage limit|quota (exceeded|reached|exhausted)|out of credits|reached your .* limit|insufficient_quota|credit balance)\b/i,
|
|
@@ -37846,8 +37937,8 @@ function childEnv(target, extra = {}, operator = {}) {
|
|
|
37846
37937
|
return { ...env, ...extra };
|
|
37847
37938
|
}
|
|
37848
37939
|
function providerEnvNames(target) {
|
|
37849
|
-
const provider = JSON.stringify(POLICY.clis
|
|
37850
|
-
return new Set([...provider.matchAll(/\{env:([A-Za-z_][A-Za-z0-9_]*)\}/g)].
|
|
37940
|
+
const provider = JSON.stringify(target === "opencode" ? POLICY.clis.opencode.provider : {});
|
|
37941
|
+
return new Set([...provider.matchAll(/\{env:([A-Za-z_][A-Za-z0-9_]*)\}/g)].flatMap(([, name]) => name ?? []));
|
|
37851
37942
|
}
|
|
37852
37943
|
function foreignSecret(target, key, keep) {
|
|
37853
37944
|
if (keep.has(key) || (OWN_PREFIX[target] ?? []).some((p) => key.startsWith(p))) return false;
|
|
@@ -37905,10 +37996,10 @@ function runChild({
|
|
|
37905
37996
|
let timedOut = false;
|
|
37906
37997
|
let cancelled = false;
|
|
37907
37998
|
let stalled = false;
|
|
37908
|
-
let settleTimer
|
|
37999
|
+
let settleTimer;
|
|
37909
38000
|
let settleAt = Infinity;
|
|
37910
38001
|
const watch = (line) => {
|
|
37911
|
-
const ms = settleAfter2(line);
|
|
38002
|
+
const ms = settleAfter2?.(line);
|
|
37912
38003
|
if (ms == null || Date.now() + ms >= settleAt) return;
|
|
37913
38004
|
settleAt = Date.now() + ms;
|
|
37914
38005
|
clearTimeout(settleTimer);
|
|
@@ -37940,7 +38031,7 @@ function runChild({
|
|
|
37940
38031
|
const compact = compactLine2 ?? ((l) => l);
|
|
37941
38032
|
const flushLines = () => {
|
|
37942
38033
|
const lines = pending.split("\n");
|
|
37943
|
-
pending = lines.pop();
|
|
38034
|
+
pending = lines.pop() ?? "";
|
|
37944
38035
|
for (const l of lines) {
|
|
37945
38036
|
if (settleAfter2) watch(l);
|
|
37946
38037
|
append("out", `${compact(l)}
|
|
@@ -37972,7 +38063,7 @@ function runChild({
|
|
|
37972
38063
|
const done = new Promise((resolve) => {
|
|
37973
38064
|
let settled2 = false;
|
|
37974
38065
|
let pipesForced = false;
|
|
37975
|
-
let orphanTimer
|
|
38066
|
+
let orphanTimer;
|
|
37976
38067
|
const finish = (payload) => {
|
|
37977
38068
|
if (settled2) return;
|
|
37978
38069
|
settled2 = true;
|
|
@@ -38019,6 +38110,7 @@ function runChild({
|
|
|
38019
38110
|
var DROP_EXACT, DROP_PREFIX, TARGET_DROP_PREFIX, TARGET_DROP_EXACT, SECRET_PREFIX, SECRET_SUFFIX, SECRET_EXACT, OWN_PREFIX, ProcHandle;
|
|
38020
38111
|
var init_run = __esm({
|
|
38021
38112
|
"src/run.mjs"() {
|
|
38113
|
+
"use strict";
|
|
38022
38114
|
init_platform();
|
|
38023
38115
|
init_policy();
|
|
38024
38116
|
DROP_EXACT = /* @__PURE__ */ new Set(["CLAUDECODE", "CLAUDE_PID", "CLAUDE_EFFORT", "AI_AGENT", "ANTHROPIC_LOG", "NODE_OPTIONS"]);
|
|
@@ -38048,6 +38140,10 @@ var init_run = __esm({
|
|
|
38048
38140
|
opencode: ["ZAI_"]
|
|
38049
38141
|
};
|
|
38050
38142
|
ProcHandle = class {
|
|
38143
|
+
/**
|
|
38144
|
+
* @param {import('node:child_process').ChildProcess} child
|
|
38145
|
+
* @param {() => string} [readStdout]
|
|
38146
|
+
*/
|
|
38051
38147
|
constructor(child, readStdout = () => "") {
|
|
38052
38148
|
this.child = child;
|
|
38053
38149
|
this.killed = false;
|
|
@@ -38057,6 +38153,7 @@ var init_run = __esm({
|
|
|
38057
38153
|
* Kill the child and every descendant it spawned -- also after the child
|
|
38058
38154
|
* itself has exited: a descendant still holding its pipes keeps the
|
|
38059
38155
|
* consultation from closing, and on POSIX it is still in the group.
|
|
38156
|
+
* @param {NodeJS.Signals} [signal]
|
|
38060
38157
|
*/
|
|
38061
38158
|
stop(signal = "SIGTERM") {
|
|
38062
38159
|
if (this.killed || !this.child) return;
|
|
@@ -38279,7 +38376,7 @@ function withSeatLock(fn) {
|
|
|
38279
38376
|
fs4.mkdirSync(lock);
|
|
38280
38377
|
break;
|
|
38281
38378
|
} catch (err) {
|
|
38282
|
-
if (err.code
|
|
38379
|
+
if (!(err instanceof Error && "code" in err && err.code === "EEXIST")) throw err;
|
|
38283
38380
|
try {
|
|
38284
38381
|
if (Date.now() - fs4.statSync(lock).mtimeMs > LOCK_STALE_MS) fs4.rmdirSync(lock);
|
|
38285
38382
|
} catch {
|
|
@@ -38322,7 +38419,7 @@ function takeSeats(seats, cap, held) {
|
|
|
38322
38419
|
} catch {
|
|
38323
38420
|
}
|
|
38324
38421
|
}
|
|
38325
|
-
for (const mine of seats.filter(
|
|
38422
|
+
for (const mine of seats.filter(continuesChain)) {
|
|
38326
38423
|
let other = live.find((x) => x.chain_id === mine.chain_id);
|
|
38327
38424
|
if (!other && fs4.existsSync(roundPath(mine.chain_id, mine.round))) {
|
|
38328
38425
|
try {
|
|
@@ -38389,9 +38486,10 @@ function cleanupJobDir(jobId) {
|
|
|
38389
38486
|
}
|
|
38390
38487
|
}
|
|
38391
38488
|
}
|
|
38392
|
-
var roundPath, JOB_ID, GROUP_ID, runningDir, runningFile, seatsDir, seatFile, LOCK_WAIT_MS, LOCK_STALE_MS, cancelFile, cancelRequested;
|
|
38489
|
+
var roundPath, JOB_ID, GROUP_ID, runningDir, runningFile, seatsDir, seatFile, LOCK_WAIT_MS, LOCK_STALE_MS, continuesChain, cancelFile, cancelRequested;
|
|
38393
38490
|
var init_store = __esm({
|
|
38394
38491
|
"src/store.mjs"() {
|
|
38492
|
+
"use strict";
|
|
38395
38493
|
init_policy();
|
|
38396
38494
|
roundPath = (chainId, round) => path6.join(POLICY.home, "history", chainId, `round-${String(round).padStart(2, "0")}.json`);
|
|
38397
38495
|
JOB_ID = /^job_[0-9a-f]{12}$/;
|
|
@@ -38402,6 +38500,7 @@ var init_store = __esm({
|
|
|
38402
38500
|
seatFile = (jobId) => path6.join(seatsDir(), `${jobId}.json`);
|
|
38403
38501
|
LOCK_WAIT_MS = 2e3;
|
|
38404
38502
|
LOCK_STALE_MS = 1e4;
|
|
38503
|
+
continuesChain = (seat) => Boolean(seat.chain_id);
|
|
38405
38504
|
cancelFile = (jobId) => path6.join(seatsDir(), `${jobId}.cancel`);
|
|
38406
38505
|
cancelRequested = (jobId) => JOB_ID.test(jobId) && fs4.existsSync(cancelFile(jobId));
|
|
38407
38506
|
}
|
|
@@ -38503,8 +38602,8 @@ function interpret({ stdout, stderr, code, lastMessageText }) {
|
|
|
38503
38602
|
for (const e of events3) walkUsage(e, usage);
|
|
38504
38603
|
let text2 = (lastMessageText || "").trim();
|
|
38505
38604
|
if (!text2) {
|
|
38506
|
-
for (
|
|
38507
|
-
|
|
38605
|
+
for (const e of events3.toReversed()) {
|
|
38606
|
+
if (text2) break;
|
|
38508
38607
|
const item = e.item ?? e;
|
|
38509
38608
|
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
38609
|
if (typeof candidate === "string" && candidate.trim()) text2 = candidate.trim();
|
|
@@ -38570,9 +38669,15 @@ function toolCalls({ stdout }) {
|
|
|
38570
38669
|
return [...seen.values()].reduce((o, n) => ({ ...o, [n]: (o[n] ?? 0) + 1 }), {});
|
|
38571
38670
|
}
|
|
38572
38671
|
function usageRecord(raw) {
|
|
38573
|
-
const u =
|
|
38672
|
+
const u = (
|
|
38673
|
+
/** @type {Record<string, number>} */
|
|
38674
|
+
raw ?? {}
|
|
38675
|
+
);
|
|
38574
38676
|
const pick2 = (...names) => {
|
|
38575
|
-
for (const n of names)
|
|
38677
|
+
for (const n of names) {
|
|
38678
|
+
const v = u[n];
|
|
38679
|
+
if (typeof v === "number") return v;
|
|
38680
|
+
}
|
|
38576
38681
|
return null;
|
|
38577
38682
|
};
|
|
38578
38683
|
return {
|
|
@@ -38599,6 +38704,7 @@ function boundary({ args }) {
|
|
|
38599
38704
|
var FORBIDDEN_FLAGS, RESERVED_FLAGS, PROFILE, listDir;
|
|
38600
38705
|
var init_codex = __esm({
|
|
38601
38706
|
"src/adapters/codex.mjs"() {
|
|
38707
|
+
"use strict";
|
|
38602
38708
|
init_policy();
|
|
38603
38709
|
init_failures();
|
|
38604
38710
|
FORBIDDEN_FLAGS = [
|
|
@@ -38697,9 +38803,9 @@ function buildInvocation2({ workdir, guardrails, consultant, hasExposedPaths = f
|
|
|
38697
38803
|
return { command: t.cli, args, model: chosen, workdir };
|
|
38698
38804
|
}
|
|
38699
38805
|
function lastJsonObject(stdout) {
|
|
38700
|
-
const lines = (stdout || "").split("\n");
|
|
38701
|
-
for (
|
|
38702
|
-
const t =
|
|
38806
|
+
const lines = (stdout || "").split("\n").reverse();
|
|
38807
|
+
for (const line of lines) {
|
|
38808
|
+
const t = line.trim();
|
|
38703
38809
|
if (!t.startsWith("{")) continue;
|
|
38704
38810
|
try {
|
|
38705
38811
|
const parsed = JSON.parse(t);
|
|
@@ -38738,7 +38844,11 @@ function interpret2({ stdout, stderr, code }) {
|
|
|
38738
38844
|
}
|
|
38739
38845
|
return { ok: true, text: text2, payload, usageRaw: payload, denials: payload.permission_denials ?? [] };
|
|
38740
38846
|
}
|
|
38741
|
-
function usageRecord2(
|
|
38847
|
+
function usageRecord2(raw) {
|
|
38848
|
+
const payload = (
|
|
38849
|
+
/** @type {ClaudeResult | null} */
|
|
38850
|
+
raw
|
|
38851
|
+
);
|
|
38742
38852
|
if (!payload) return null;
|
|
38743
38853
|
const u = payload.usage ?? {};
|
|
38744
38854
|
const num2 = (v) => typeof v === "number" ? v : null;
|
|
@@ -38753,7 +38863,10 @@ function usageRecord2(payload) {
|
|
|
38753
38863
|
};
|
|
38754
38864
|
}
|
|
38755
38865
|
function boundary2({ args, hasExposedPaths }) {
|
|
38756
|
-
const tools =
|
|
38866
|
+
const tools = (
|
|
38867
|
+
/** @type {string} */
|
|
38868
|
+
args[args.indexOf("--tools") + 1]
|
|
38869
|
+
);
|
|
38757
38870
|
return {
|
|
38758
38871
|
reads: hasExposedPaths ? "the exposed copy only" : "whole disk",
|
|
38759
38872
|
shell: "none",
|
|
@@ -38767,6 +38880,7 @@ function boundary2({ args, hasExposedPaths }) {
|
|
|
38767
38880
|
var FORBIDDEN_FLAGS2, RESERVED_FLAGS2, posixAbsolute;
|
|
38768
38881
|
var init_claude_code = __esm({
|
|
38769
38882
|
"src/adapters/claude-code.mjs"() {
|
|
38883
|
+
"use strict";
|
|
38770
38884
|
init_policy();
|
|
38771
38885
|
init_failures();
|
|
38772
38886
|
init_result_schema();
|
|
@@ -38987,13 +39101,14 @@ function prepareSandbox({ workdir, network = "on" }) {
|
|
|
38987
39101
|
var SANDBOX_ALLOW, SANDBOX_DENY, ALLOWED_TOOLS, WEB_TOOLS, PATH_TOOLS, TOKEN_REL;
|
|
38988
39102
|
var init_antigravity_sandbox = __esm({
|
|
38989
39103
|
"src/adapters/antigravity-sandbox.mjs"() {
|
|
39104
|
+
"use strict";
|
|
38990
39105
|
init_policy();
|
|
38991
39106
|
init_store();
|
|
38992
39107
|
SANDBOX_ALLOW = Object.freeze(["read_url(*)", "read_file(*)"]);
|
|
38993
39108
|
SANDBOX_DENY = Object.freeze(["write_file(*)", "command(*)", "mcp(*)", "execute_url(*)", "unsandboxed(*)"]);
|
|
38994
|
-
ALLOWED_TOOLS = Object.freeze(["view_file", "search_web", "read_url_content", "finish"]);
|
|
39109
|
+
ALLOWED_TOOLS = Object.freeze(["view_file", "read_file", "search_web", "read_url_content", "finish"]);
|
|
38995
39110
|
WEB_TOOLS = /* @__PURE__ */ new Set(["search_web", "read_url_content"]);
|
|
38996
|
-
PATH_TOOLS = ["view_file", "read_url_content"];
|
|
39111
|
+
PATH_TOOLS = ["view_file", "read_file", "read_url_content"];
|
|
38997
39112
|
TOKEN_REL = path8.join(".gemini", "antigravity-cli", "antigravity-oauth-token");
|
|
38998
39113
|
}
|
|
38999
39114
|
});
|
|
@@ -39001,6 +39116,8 @@ var init_antigravity_sandbox = __esm({
|
|
|
39001
39116
|
// src/adapters/antigravity.mjs
|
|
39002
39117
|
var antigravity_exports = {};
|
|
39003
39118
|
__export(antigravity_exports, {
|
|
39119
|
+
ALLOWED_TOOLS: () => ALLOWED_TOOLS,
|
|
39120
|
+
FILE_READ_TOOLS: () => FILE_READ_TOOLS,
|
|
39004
39121
|
FORBIDDEN_FLAGS: () => FORBIDDEN_FLAGS3,
|
|
39005
39122
|
RESERVED_FLAGS: () => RESERVED_FLAGS3,
|
|
39006
39123
|
boundary: () => boundary3,
|
|
@@ -39047,19 +39164,17 @@ function events(stdout) {
|
|
|
39047
39164
|
return out;
|
|
39048
39165
|
}
|
|
39049
39166
|
function finalEnvelope(stdout) {
|
|
39050
|
-
const
|
|
39051
|
-
for (let i = all.length - 1; i >= 0; i--) {
|
|
39052
|
-
const e = all[i];
|
|
39167
|
+
for (const e of events(stdout).reverse()) {
|
|
39053
39168
|
if (e.event === "result" && e.result && typeof e.result === "object") return e.result;
|
|
39054
39169
|
if (e.event === void 0 && (e.status || e.response || e.structured_output)) return e;
|
|
39055
39170
|
}
|
|
39056
39171
|
return null;
|
|
39057
39172
|
}
|
|
39058
39173
|
function progress2({ stdout }) {
|
|
39059
|
-
const steps = events(stdout).
|
|
39060
|
-
|
|
39061
|
-
|
|
39062
|
-
const seen = new Set(steps.map((s2) => s2.
|
|
39174
|
+
const steps = events(stdout).flatMap((e) => e.event === "step_update" && e.step_update ? [e.step_update] : []);
|
|
39175
|
+
const last = steps.at(-1);
|
|
39176
|
+
if (!last) return null;
|
|
39177
|
+
const seen = new Set(steps.map((s2) => s2.step_index)).size;
|
|
39063
39178
|
const what = last.tool_name ? `tool ${last.tool_name}` : last.step_type ?? "a step";
|
|
39064
39179
|
return `${seen} step(s), last was ${what} (${last.state ?? "state unknown"})`;
|
|
39065
39180
|
}
|
|
@@ -39123,7 +39238,11 @@ function interpret3({ stdout, stderr, code }) {
|
|
|
39123
39238
|
}
|
|
39124
39239
|
return { ok: true, text: text2, usageRaw: payload, denials: payload.denied_actions ?? [] };
|
|
39125
39240
|
}
|
|
39126
|
-
function usageRecord3(
|
|
39241
|
+
function usageRecord3(raw) {
|
|
39242
|
+
const payload = (
|
|
39243
|
+
/** @type {AgyEnvelope | null} */
|
|
39244
|
+
raw
|
|
39245
|
+
);
|
|
39127
39246
|
if (!payload) return null;
|
|
39128
39247
|
const u = payload.usage ?? {};
|
|
39129
39248
|
const num2 = (v) => typeof v === "number" ? v : null;
|
|
@@ -39139,23 +39258,28 @@ function usageRecord3(payload) {
|
|
|
39139
39258
|
turns: num2(payload.num_turns),
|
|
39140
39259
|
// Which permissions the sandbox refused. A denied read_url usually explains
|
|
39141
39260
|
// a thin evidence basis, so it is worth keeping in the record.
|
|
39142
|
-
denied_actions: Array.isArray(payload.denied_actions) ? payload.denied_actions.
|
|
39261
|
+
denied_actions: Array.isArray(payload.denied_actions) ? payload.denied_actions.flatMap((d) => d?.action ? [d.action] : []) : null
|
|
39143
39262
|
};
|
|
39144
39263
|
}
|
|
39145
39264
|
function boundary3({ sandbox }) {
|
|
39265
|
+
const { tools } = (
|
|
39266
|
+
/** @type {Sandbox} */
|
|
39267
|
+
sandbox
|
|
39268
|
+
);
|
|
39146
39269
|
return {
|
|
39147
39270
|
reads: "whole disk",
|
|
39148
39271
|
shell: "none",
|
|
39149
|
-
web:
|
|
39272
|
+
web: tools.includes("search_web"),
|
|
39150
39273
|
writes: false,
|
|
39151
39274
|
mcp: false,
|
|
39152
39275
|
protected_paths: "denied (tool hook)",
|
|
39153
|
-
tools: [...
|
|
39276
|
+
tools: [...tools]
|
|
39154
39277
|
};
|
|
39155
39278
|
}
|
|
39156
|
-
var FORBIDDEN_FLAGS3, RESERVED_FLAGS3, stopSignal;
|
|
39279
|
+
var FORBIDDEN_FLAGS3, RESERVED_FLAGS3, FILE_READ_TOOLS, stopSignal;
|
|
39157
39280
|
var init_antigravity = __esm({
|
|
39158
39281
|
"src/adapters/antigravity.mjs"() {
|
|
39282
|
+
"use strict";
|
|
39159
39283
|
init_policy();
|
|
39160
39284
|
init_failures();
|
|
39161
39285
|
init_antigravity_sandbox();
|
|
@@ -39180,6 +39304,7 @@ var init_antigravity = __esm({
|
|
|
39180
39304
|
"-i"
|
|
39181
39305
|
];
|
|
39182
39306
|
RESERVED_FLAGS3 = ["--print", "-p", "--prompt", "--project", "--remote-control", "--effort"];
|
|
39307
|
+
FILE_READ_TOOLS = ["view_file", "read_file"];
|
|
39183
39308
|
stopSignal = "SIGQUIT";
|
|
39184
39309
|
}
|
|
39185
39310
|
});
|
|
@@ -39273,9 +39398,9 @@ function prepareSandbox2({ workdir, network = "on" }) {
|
|
|
39273
39398
|
fs8.rmSync(root, { recursive: true, force: true });
|
|
39274
39399
|
} catch {
|
|
39275
39400
|
}
|
|
39276
|
-
}
|
|
39401
|
+
},
|
|
39402
|
+
seed: (provider) => seedCredentialFromAuth(sandbox, provider)
|
|
39277
39403
|
};
|
|
39278
|
-
sandbox.seed = (provider) => seedCredentialFromAuth(sandbox, provider);
|
|
39279
39404
|
return sandbox;
|
|
39280
39405
|
}
|
|
39281
39406
|
function seedCredentialFromAuth(sandbox, provider) {
|
|
@@ -39330,6 +39455,7 @@ function seedCredentialFromAuth(sandbox, provider) {
|
|
|
39330
39455
|
var DatabaseSync, SANDBOX_DENY2, SANDBOX_ALLOW2, WEB_TOOLS2, SANDBOX_TOOLS_OFF, AUTH_REL;
|
|
39331
39456
|
var init_opencode_sandbox = __esm({
|
|
39332
39457
|
"src/adapters/opencode-sandbox.mjs"() {
|
|
39458
|
+
"use strict";
|
|
39333
39459
|
init_policy();
|
|
39334
39460
|
init_store();
|
|
39335
39461
|
DatabaseSync = null;
|
|
@@ -39366,6 +39492,7 @@ var init_opencode_sandbox = __esm({
|
|
|
39366
39492
|
// src/adapters/opencode.mjs
|
|
39367
39493
|
var opencode_exports = {};
|
|
39368
39494
|
__export(opencode_exports, {
|
|
39495
|
+
FILE_READ_TOOLS: () => FILE_READ_TOOLS2,
|
|
39369
39496
|
FORBIDDEN_FLAGS: () => FORBIDDEN_FLAGS4,
|
|
39370
39497
|
RESERVED_FLAGS: () => RESERVED_FLAGS4,
|
|
39371
39498
|
boundary: () => boundary4,
|
|
@@ -39512,14 +39639,14 @@ function interpret4({ stdout, stderr, code, signal = null, truncated = false })
|
|
|
39512
39639
|
let text2 = null;
|
|
39513
39640
|
let textIndex = -1;
|
|
39514
39641
|
let lastErrorIndex = -1;
|
|
39642
|
+
let lastError = "";
|
|
39515
39643
|
let finishedAfterText = false;
|
|
39516
39644
|
let tokens = null;
|
|
39517
39645
|
let cost = null;
|
|
39518
39646
|
let steps = 0;
|
|
39519
39647
|
const errors = [];
|
|
39520
39648
|
const earlierTextParts = [];
|
|
39521
|
-
for (
|
|
39522
|
-
const e = all[i];
|
|
39649
|
+
for (const [i, e] of all.entries()) {
|
|
39523
39650
|
const part = e.part;
|
|
39524
39651
|
if (e.type === "text" && part?.type === "text" && part.time?.end && typeof part.text === "string" && part.text.trim()) {
|
|
39525
39652
|
if (text2 !== null) earlierTextParts.push(text2);
|
|
@@ -39532,7 +39659,8 @@ function interpret4({ stdout, stderr, code, signal = null, truncated = false })
|
|
|
39532
39659
|
if (part.tokens && typeof part.tokens === "object") tokens = part.tokens;
|
|
39533
39660
|
if (typeof part.cost === "number") cost = part.cost;
|
|
39534
39661
|
} else if (e.type === "error" && e.error) {
|
|
39535
|
-
|
|
39662
|
+
lastError = errorMessage(e.error);
|
|
39663
|
+
errors.push(lastError);
|
|
39536
39664
|
lastErrorIndex = i;
|
|
39537
39665
|
}
|
|
39538
39666
|
}
|
|
@@ -39547,14 +39675,14 @@ function interpret4({ stdout, stderr, code, signal = null, truncated = false })
|
|
|
39547
39675
|
};
|
|
39548
39676
|
}
|
|
39549
39677
|
if (lastErrorIndex > textIndex) {
|
|
39550
|
-
const msg =
|
|
39678
|
+
const msg = lastError;
|
|
39551
39679
|
return { ok: false, failureKind: classifyMessage(msg), message: msg, usageRaw };
|
|
39552
39680
|
}
|
|
39553
39681
|
if (code !== 0) {
|
|
39554
39682
|
const { msg, kind } = failureMessage({ errors, stderr, stdout, code, signal, what: "after producing an answer" });
|
|
39555
39683
|
return { ok: false, failureKind: kind, message: msg, usageRaw };
|
|
39556
39684
|
}
|
|
39557
|
-
if (truncated && !(finishedAfterText && all.
|
|
39685
|
+
if (truncated && !(finishedAfterText && all.at(-1)?.type === "step_finish")) {
|
|
39558
39686
|
return {
|
|
39559
39687
|
ok: false,
|
|
39560
39688
|
failureKind: "invalid_output",
|
|
@@ -39577,14 +39705,19 @@ function toolCalls3({ stdout }) {
|
|
|
39577
39705
|
return [...seen.values()].reduce((o, n) => ({ ...o, [n]: (o[n] ?? 0) + 1 }), {});
|
|
39578
39706
|
}
|
|
39579
39707
|
function progress3({ stdout }) {
|
|
39580
|
-
const seen = events2(stdout).filter(
|
|
39581
|
-
|
|
39582
|
-
|
|
39708
|
+
const seen = events2(stdout).filter(
|
|
39709
|
+
(e) => e.type !== void 0 && ["step_start", "step_finish", "text", "tool_use"].includes(e.type)
|
|
39710
|
+
);
|
|
39711
|
+
const last = seen.at(-1);
|
|
39712
|
+
if (!last) return null;
|
|
39583
39713
|
const what = last.type === "tool_use" && last.part?.tool ? `tool ${last.part.tool}` : last.type;
|
|
39584
39714
|
return `${seen.length} event(s), last was ${what}`;
|
|
39585
39715
|
}
|
|
39586
39716
|
function usageRecord4(raw) {
|
|
39587
|
-
const r =
|
|
39717
|
+
const r = (
|
|
39718
|
+
/** @type {OpencodeUsage | null} */
|
|
39719
|
+
raw ?? {}
|
|
39720
|
+
);
|
|
39588
39721
|
const t = r.tokens && typeof r.tokens === "object" ? r.tokens : {};
|
|
39589
39722
|
const num2 = (v) => typeof v === "number" && Number.isFinite(v) ? v : null;
|
|
39590
39723
|
const cache = t.cache && typeof t.cache === "object" ? t.cache : {};
|
|
@@ -39601,19 +39734,24 @@ function usageRecord4(raw) {
|
|
|
39601
39734
|
};
|
|
39602
39735
|
}
|
|
39603
39736
|
function boundary4({ sandbox }) {
|
|
39737
|
+
const { tools } = (
|
|
39738
|
+
/** @type {Sandbox} */
|
|
39739
|
+
sandbox
|
|
39740
|
+
);
|
|
39604
39741
|
return {
|
|
39605
39742
|
reads: "whole disk",
|
|
39606
39743
|
shell: "none",
|
|
39607
|
-
web:
|
|
39744
|
+
web: tools.includes("webfetch"),
|
|
39608
39745
|
writes: false,
|
|
39609
39746
|
mcp: false,
|
|
39610
39747
|
protected_paths: "denied (permission rules)",
|
|
39611
|
-
tools: [...
|
|
39748
|
+
tools: [...tools]
|
|
39612
39749
|
};
|
|
39613
39750
|
}
|
|
39614
|
-
var FORBIDDEN_FLAGS4, standaloneSupport, probePromise, RESERVED_FLAGS4;
|
|
39751
|
+
var FORBIDDEN_FLAGS4, standaloneSupport, probePromise, RESERVED_FLAGS4, FILE_READ_TOOLS2;
|
|
39615
39752
|
var init_opencode = __esm({
|
|
39616
39753
|
"src/adapters/opencode.mjs"() {
|
|
39754
|
+
"use strict";
|
|
39617
39755
|
init_policy();
|
|
39618
39756
|
init_failures();
|
|
39619
39757
|
init_platform();
|
|
@@ -39642,6 +39780,7 @@ var init_opencode = __esm({
|
|
|
39642
39780
|
standaloneSupport = null;
|
|
39643
39781
|
probePromise = null;
|
|
39644
39782
|
RESERVED_FLAGS4 = ["--model", "--standalone", "--wizard"];
|
|
39783
|
+
FILE_READ_TOOLS2 = ["read", "grep"];
|
|
39645
39784
|
}
|
|
39646
39785
|
});
|
|
39647
39786
|
|
|
@@ -39662,7 +39801,7 @@ function ownerState(jobId, owner) {
|
|
|
39662
39801
|
process.kill(owner.pid, 0);
|
|
39663
39802
|
pidAlive = true;
|
|
39664
39803
|
} catch (err) {
|
|
39665
|
-
pidAlive = err.code === "EPERM";
|
|
39804
|
+
pidAlive = err instanceof Error && "code" in err && err.code === "EPERM";
|
|
39666
39805
|
}
|
|
39667
39806
|
if (!pidAlive) return "dead";
|
|
39668
39807
|
return Date.now() > Date.parse(owner.deadline_at) ? "overdue" : "alive";
|
|
@@ -39710,7 +39849,7 @@ async function settled(promise2, ms, signal) {
|
|
|
39710
39849
|
await (promise2 ? Promise.race([promise2, until]) : until);
|
|
39711
39850
|
} finally {
|
|
39712
39851
|
clearTimeout(timer);
|
|
39713
|
-
signal?.removeEventListener("abort", stop);
|
|
39852
|
+
if (stop) signal?.removeEventListener("abort", stop);
|
|
39714
39853
|
}
|
|
39715
39854
|
}
|
|
39716
39855
|
function parses(text2) {
|
|
@@ -39735,11 +39874,18 @@ function tally(values) {
|
|
|
39735
39874
|
return out;
|
|
39736
39875
|
}
|
|
39737
39876
|
function recordSummary(job) {
|
|
39738
|
-
const
|
|
39739
|
-
|
|
39877
|
+
const record2 = (
|
|
39878
|
+
/** @type {JobRecord} */
|
|
39879
|
+
job.record
|
|
39880
|
+
);
|
|
39881
|
+
const ids = recordableIds(
|
|
39882
|
+
/** @type {ConsultResult} */
|
|
39883
|
+
job.result
|
|
39884
|
+
);
|
|
39885
|
+
const written = new Set(record2.entries.map((e) => e.id));
|
|
39740
39886
|
return {
|
|
39741
|
-
...
|
|
39742
|
-
verdicts: tally(
|
|
39887
|
+
...record2,
|
|
39888
|
+
verdicts: tally(record2.entries.map((e) => e.verdict)),
|
|
39743
39889
|
coverage: { recordable: ids.length, recorded: written.size },
|
|
39744
39890
|
unrecorded: ids.filter((id2) => !written.has(id2))
|
|
39745
39891
|
};
|
|
@@ -39755,13 +39901,16 @@ function inherit(raw, round) {
|
|
|
39755
39901
|
const { brief } = round;
|
|
39756
39902
|
const out = raw.network === void 0 && round.network ? { ...raw, network: round.network } : { ...raw };
|
|
39757
39903
|
if (!brief) return out;
|
|
39758
|
-
|
|
39759
|
-
|
|
39760
|
-
|
|
39904
|
+
const topKeys = ["objective", "success_criteria", "constraints"];
|
|
39905
|
+
for (const k of topKeys) {
|
|
39906
|
+
const v = brief[k];
|
|
39907
|
+
if (out[k] === void 0 && raw.context?.[k] === void 0 && v != null && !(Array.isArray(v) && !v.length)) out[k] = v;
|
|
39761
39908
|
}
|
|
39762
39909
|
const ctx = { ...raw.context ?? {} };
|
|
39763
|
-
|
|
39764
|
-
|
|
39910
|
+
const contextKeys = ["facts", "proposal", "counterpoints"];
|
|
39911
|
+
for (const k of contextKeys) {
|
|
39912
|
+
const v = brief[k];
|
|
39913
|
+
if (ctx[k] === void 0 && v != null && !(Array.isArray(v) && !v.length)) ctx[k] = v;
|
|
39765
39914
|
}
|
|
39766
39915
|
const artifacts = [...brief.artifacts ?? []];
|
|
39767
39916
|
const last = artifacts.at(-1);
|
|
@@ -39793,8 +39942,22 @@ function adviceCaveat(normalized) {
|
|
|
39793
39942
|
}
|
|
39794
39943
|
return notes.length ? notes.join("; ") : null;
|
|
39795
39944
|
}
|
|
39945
|
+
function unreadCaveat(calls, readTools) {
|
|
39946
|
+
if (!calls || !readTools) return null;
|
|
39947
|
+
if (readTools.some((t) => calls[t])) return null;
|
|
39948
|
+
const failed = readTools.reduce((n, t) => n + (calls[`${t}:error`] ?? 0), 0);
|
|
39949
|
+
const how = failed ? `${failed} failed or were refused` : "none was made";
|
|
39950
|
+
return `files were exposed, but no call to read one succeeded (${how})`;
|
|
39951
|
+
}
|
|
39952
|
+
function refusedToolCaveat(calls, allowed) {
|
|
39953
|
+
if (!calls || !allowed) return null;
|
|
39954
|
+
const names = Object.keys(calls).filter((k) => k.endsWith(":error")).map((k) => k.slice(0, -":error".length)).filter((t) => !allowed.includes(t) && /read|view|open/i.test(t));
|
|
39955
|
+
if (!names.length) return null;
|
|
39956
|
+
return `the consultant's CLI called ${names.join(", ")}, which severally does not allow, and was refused; a new CLI release may have renamed its file-reading tool`;
|
|
39957
|
+
}
|
|
39796
39958
|
function sameVendorCaveat(caller, callerModel, consultant) {
|
|
39797
|
-
const
|
|
39959
|
+
const callerCli = CLI_IDS.find((c) => c === caller);
|
|
39960
|
+
const callerVendor = (callerModel ? vendorOf(callerModel) : null) ?? (callerCli ? CLI_VENDORS[callerCli] ?? null : null);
|
|
39798
39961
|
const vendor = consultant.vendor;
|
|
39799
39962
|
if (!callerVendor || !vendor || callerVendor !== vendor) return null;
|
|
39800
39963
|
const consultantModel = consultant.model;
|
|
@@ -39804,11 +39967,12 @@ function sameVendorCaveat(caller, callerModel, consultant) {
|
|
|
39804
39967
|
}
|
|
39805
39968
|
function comparison(members2, budget) {
|
|
39806
39969
|
const each = (budget - COMPARISON_NOTE_CHARS) / Math.max(1, members2.length) - COLUMN_FIXED_CHARS;
|
|
39807
|
-
|
|
39970
|
+
const firstPass = members2.map((m) => ({ m, column: byTarget(m, each) }));
|
|
39971
|
+
let columns = firstPass.map((x) => x.column);
|
|
39808
39972
|
const cutShort = columns.filter((c) => c.truncated).length;
|
|
39809
39973
|
if (each !== Infinity && cutShort) {
|
|
39810
39974
|
const spare = columns.reduce((n, c) => n + (c.truncated ? 0 : Math.max(0, each + COLUMN_FIXED_CHARS - printed(c))), 0);
|
|
39811
|
-
columns =
|
|
39975
|
+
columns = firstPass.map(({ m, column }) => column.truncated ? byTarget(m, each + spare / cutShort) : column);
|
|
39812
39976
|
}
|
|
39813
39977
|
return {
|
|
39814
39978
|
by_target: columns,
|
|
@@ -39938,8 +40102,12 @@ function pointsOf(view, ids) {
|
|
|
39938
40102
|
const verdicts = new Map((view.record?.entries ?? []).map((e) => [e.id, e]));
|
|
39939
40103
|
const lists = { finding: r.findings, unknown: r.unknowns, next_check: r.next_checks };
|
|
39940
40104
|
const points = [...new Set(ids)].map((id2) => {
|
|
39941
|
-
const [
|
|
39942
|
-
|
|
40105
|
+
const [point] = Object.entries(lists).flatMap(([kind, l]) => {
|
|
40106
|
+
const item = l.find((x) => x.id === id2);
|
|
40107
|
+
return item ? [{ kind, ...item }] : [];
|
|
40108
|
+
});
|
|
40109
|
+
if (!point) throw new RequestError(`"${id2}" is not a point in this answer`, "unknown_entry_id");
|
|
40110
|
+
return { ...point, recorded: verdicts.get(id2) ?? null };
|
|
39943
40111
|
});
|
|
39944
40112
|
return {
|
|
39945
40113
|
job_id: view.job_id,
|
|
@@ -40043,6 +40211,7 @@ function assertNoForbiddenFlags(target, args) {
|
|
|
40043
40211
|
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
40212
|
var init_jobs = __esm({
|
|
40045
40213
|
"src/jobs.mjs"() {
|
|
40214
|
+
"use strict";
|
|
40046
40215
|
init_policy();
|
|
40047
40216
|
init_consultants();
|
|
40048
40217
|
init_schema();
|
|
@@ -40078,6 +40247,10 @@ var init_jobs = __esm({
|
|
|
40078
40247
|
// A consultation this process does not hold: finished (its round file), or
|
|
40079
40248
|
// still in progress (its marker). A marker whose owner is gone becomes a
|
|
40080
40249
|
// failed round, `interrupted`, written once by whichever process claims it.
|
|
40250
|
+
/**
|
|
40251
|
+
* @param {string} jobId
|
|
40252
|
+
* @returns {StoredRound | null}
|
|
40253
|
+
*/
|
|
40081
40254
|
#fromDisk(jobId) {
|
|
40082
40255
|
const done = loadRound(jobId);
|
|
40083
40256
|
if (done) return done;
|
|
@@ -40090,6 +40263,12 @@ var init_jobs = __esm({
|
|
|
40090
40263
|
// something the next reader finishes: the round is written (the index
|
|
40091
40264
|
// takes it once), then the marker goes. A round already on disk wins -- its
|
|
40092
40265
|
// owner finished and died before removing the marker.
|
|
40266
|
+
/**
|
|
40267
|
+
* @param {string} jobId
|
|
40268
|
+
* @param {StoredRound} rec
|
|
40269
|
+
* @param {'alive' | 'dead' | 'overdue'} state
|
|
40270
|
+
* @returns {Round}
|
|
40271
|
+
*/
|
|
40093
40272
|
#interrupt(jobId, rec, state) {
|
|
40094
40273
|
const done = loadRound(jobId);
|
|
40095
40274
|
if (done) {
|
|
@@ -40120,25 +40299,33 @@ var init_jobs = __esm({
|
|
|
40120
40299
|
for (const jobId of listRunning()) {
|
|
40121
40300
|
const rec = loadRunning(jobId);
|
|
40122
40301
|
const state = rec && ownerState(jobId, rec.owner);
|
|
40123
|
-
if (rec && state !== "alive") this.#interrupt(jobId, rec, state);
|
|
40302
|
+
if (rec && state && state !== "alive") this.#interrupt(jobId, rec, state);
|
|
40124
40303
|
}
|
|
40125
40304
|
}
|
|
40305
|
+
/**
|
|
40306
|
+
* @param {StoredRound} rec
|
|
40307
|
+
* @returns {FullView}
|
|
40308
|
+
*/
|
|
40126
40309
|
#diskView(rec) {
|
|
40127
|
-
const elsewhere = Boolean(rec.owner);
|
|
40128
40310
|
const { owner, ...view } = rec;
|
|
40129
40311
|
return {
|
|
40130
40312
|
...view,
|
|
40131
|
-
|
|
40313
|
+
// consult_cancel asks the process that owns it to stop it.
|
|
40314
|
+
cancellable: Boolean(owner) && (view.status === "running" || view.status === "queued"),
|
|
40132
40315
|
progress: null,
|
|
40133
40316
|
record: view.record ? recordSummary(view) : null,
|
|
40134
|
-
brief:
|
|
40135
|
-
limits:
|
|
40136
|
-
next_step:
|
|
40317
|
+
brief: owner ? null : view.brief,
|
|
40318
|
+
limits: owner ? null : limitsSummary(),
|
|
40319
|
+
next_step: owner ? `running in another severally process (pid ${owner.pid}); poll again later, or stop it with consult_cancel` : nextStep(view)
|
|
40137
40320
|
};
|
|
40138
40321
|
}
|
|
40139
40322
|
get running() {
|
|
40140
40323
|
return [...this.jobs.values()].filter((j) => j.status === "running");
|
|
40141
40324
|
}
|
|
40325
|
+
/**
|
|
40326
|
+
* @param {Job} job
|
|
40327
|
+
* @returns {Round}
|
|
40328
|
+
*/
|
|
40142
40329
|
#record(job) {
|
|
40143
40330
|
return {
|
|
40144
40331
|
job_id: job.job_id,
|
|
@@ -40181,13 +40368,25 @@ var init_jobs = __esm({
|
|
|
40181
40368
|
}
|
|
40182
40369
|
// `detail: "full"` returns the brief as stored; by default a finished
|
|
40183
40370
|
// consultation names it (size, sha256, round file) instead of repeating it.
|
|
40184
|
-
/**
|
|
40371
|
+
/**
|
|
40372
|
+
* @param {string} jobId
|
|
40373
|
+
* @param {{detail?: string | undefined}} [opts]
|
|
40374
|
+
* @returns {JobView | null}
|
|
40375
|
+
*/
|
|
40185
40376
|
view(jobId, { detail } = {}) {
|
|
40186
40377
|
const job = this.jobs.get(jobId);
|
|
40187
40378
|
if (!job) {
|
|
40188
|
-
const
|
|
40189
|
-
return
|
|
40379
|
+
const rec = this.#fromDisk(jobId);
|
|
40380
|
+
return rec ? compactBrief(this.#diskView(rec), detail) : null;
|
|
40190
40381
|
}
|
|
40382
|
+
return this.#jobView(job, detail);
|
|
40383
|
+
}
|
|
40384
|
+
/**
|
|
40385
|
+
* @param {Job} job
|
|
40386
|
+
* @param {string | undefined} detail
|
|
40387
|
+
* @returns {JobView}
|
|
40388
|
+
*/
|
|
40389
|
+
#jobView(job, detail) {
|
|
40191
40390
|
const rec = this.#record(job);
|
|
40192
40391
|
rec.cancellable = job.status === "running" || job.status === "queued";
|
|
40193
40392
|
rec.progress = job.status === "running" && job._handle ? ADAPTERS[job.cli]?.progress?.({ stdout: job._handle.stdoutSoFar() }) ?? null : null;
|
|
@@ -40207,30 +40406,35 @@ var init_jobs = __esm({
|
|
|
40207
40406
|
// job_id and group_id can still be followed up and recorded against. The
|
|
40208
40407
|
// index only picks and orders; each row shown is read from its round, which
|
|
40209
40408
|
// holds the verdicts written since. Memory wins over disk for the same job.
|
|
40409
|
+
/** @param {{limit?: number}} [opts] */
|
|
40210
40410
|
list({ limit: limit2 = 20 } = {}) {
|
|
40211
|
-
const
|
|
40212
|
-
const seen = new Set(
|
|
40213
|
-
const
|
|
40411
|
+
const candidates = [];
|
|
40412
|
+
const seen = /* @__PURE__ */ new Set();
|
|
40413
|
+
for (const jid of this.order) {
|
|
40414
|
+
const job = this.jobs.get(jid);
|
|
40415
|
+
if (!job) continue;
|
|
40416
|
+
seen.add(job.job_id);
|
|
40417
|
+
candidates.push({ at: job.created_at ?? "", load: () => job });
|
|
40418
|
+
}
|
|
40214
40419
|
for (const jobId of listRunning()) {
|
|
40215
40420
|
const live = seen.has(jobId) ? null : loadRunning(jobId);
|
|
40216
40421
|
if (!live) continue;
|
|
40217
40422
|
seen.add(jobId);
|
|
40218
40423
|
const state = ownerState(jobId, live.owner);
|
|
40219
40424
|
const saved = state === "alive" ? null : loadRoundAt(live);
|
|
40220
|
-
|
|
40221
|
-
|
|
40222
|
-
});
|
|
40425
|
+
const rec = state === "alive" ? live : saved ?? /** @type {Round} */
|
|
40426
|
+
{ ...live, status: "failed", failure: { kind: "interrupted" } };
|
|
40427
|
+
candidates.push({ at: rec.created_at ?? "", load: () => rec });
|
|
40223
40428
|
}
|
|
40224
40429
|
for (const row of indexRows())
|
|
40225
40430
|
if (!seen.has(row.job_id)) {
|
|
40226
40431
|
seen.add(row.job_id);
|
|
40227
|
-
|
|
40432
|
+
candidates.push({ at: row.created_at ?? "", load: () => loadRoundAt(row) });
|
|
40228
40433
|
}
|
|
40229
|
-
const at = (x) => x.created_at ?? x.rec?.created_at ?? x.row?.created_at ?? "";
|
|
40230
40434
|
const picked = [];
|
|
40231
|
-
for (const x of
|
|
40435
|
+
for (const x of candidates.sort((p, q) => q.at < p.at ? -1 : q.at > p.at ? 1 : 0)) {
|
|
40232
40436
|
if (picked.length >= limit2) break;
|
|
40233
|
-
const j = x.
|
|
40437
|
+
const j = x.load();
|
|
40234
40438
|
if (j) picked.push(j);
|
|
40235
40439
|
}
|
|
40236
40440
|
return picked.map((j) => {
|
|
@@ -40267,7 +40471,7 @@ var init_jobs = __esm({
|
|
|
40267
40471
|
const by = {};
|
|
40268
40472
|
const reported = {};
|
|
40269
40473
|
for (const j of this.jobs.values()) {
|
|
40270
|
-
const attempts = [...(j.retries ?? []).map((r) => r.usage), j.usage].filter(
|
|
40474
|
+
const attempts = [...(j.retries ?? []).map((r) => r.usage), j.usage].filter((u) => u != null);
|
|
40271
40475
|
if (!attempts.length) continue;
|
|
40272
40476
|
const t = by[j.target] ??= { consultations: 0, attempts: 0 };
|
|
40273
40477
|
const seen = reported[j.target] ??= /* @__PURE__ */ new Set();
|
|
@@ -40281,11 +40485,13 @@ var init_jobs = __esm({
|
|
|
40281
40485
|
}
|
|
40282
40486
|
}
|
|
40283
40487
|
}
|
|
40488
|
+
const out = {};
|
|
40284
40489
|
for (const [target, t] of Object.entries(by)) {
|
|
40285
|
-
t
|
|
40490
|
+
out[target] = { ...t, not_reported: USAGE_FIGURES.filter((k) => !reported[target]?.has(k)) };
|
|
40286
40491
|
}
|
|
40287
|
-
return
|
|
40492
|
+
return out;
|
|
40288
40493
|
}
|
|
40494
|
+
/** @param {RawRequest} rawRequest the tool arguments, not yet validated */
|
|
40289
40495
|
start(rawRequest) {
|
|
40290
40496
|
if (process.env.SEVERALLY_ACTIVE === "1") {
|
|
40291
40497
|
throw new RequestError(
|
|
@@ -40308,29 +40514,30 @@ var init_jobs = __esm({
|
|
|
40308
40514
|
fromGroup = this.#groupParents(followupTo);
|
|
40309
40515
|
rawRequest = { ...rawRequest, targets: fromGroup.links.map((l) => l.parent.target) };
|
|
40310
40516
|
} else if (followupTo) {
|
|
40311
|
-
|
|
40312
|
-
if (!
|
|
40313
|
-
if (
|
|
40517
|
+
const found = this.jobs.get(followupTo) ?? loadRound(followupTo);
|
|
40518
|
+
if (!found) throw new RequestError(`followup_to references unknown job "${followupTo}"`, "unknown_job");
|
|
40519
|
+
if (found.status !== "completed") {
|
|
40314
40520
|
throw new RequestError(
|
|
40315
|
-
`followup_to job "${followupTo}" is ${
|
|
40521
|
+
`followup_to job "${followupTo}" is ${found.status}; only a completed consultation can be followed up`,
|
|
40316
40522
|
"followup_not_completed"
|
|
40317
40523
|
);
|
|
40318
40524
|
}
|
|
40319
|
-
const rounds = this.#chainRounds(
|
|
40320
|
-
const later = rounds.find((j) => j.round >
|
|
40525
|
+
const rounds = this.#chainRounds(found.chain_id);
|
|
40526
|
+
const later = rounds.find((j) => j.round > found.round && !["failed", "cancelled"].includes(j.status));
|
|
40321
40527
|
if (later) {
|
|
40322
40528
|
throw new RequestError(
|
|
40323
|
-
`job "${followupTo}" is round ${
|
|
40529
|
+
`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
40530
|
"followup_not_latest"
|
|
40325
40531
|
);
|
|
40326
40532
|
}
|
|
40327
|
-
parent = { ...
|
|
40533
|
+
parent = { ...found, nextRound: Math.max(...rounds.map((j) => j.round)) + 1 };
|
|
40328
40534
|
}
|
|
40329
40535
|
if (parent && (rawRequest.target ?? null) === null && !rawRequest.targets?.length) {
|
|
40330
40536
|
rawRequest = { ...rawRequest, target: parent.target };
|
|
40331
40537
|
}
|
|
40332
|
-
|
|
40333
|
-
|
|
40538
|
+
const first = fromGroup?.links[0];
|
|
40539
|
+
const inheritFrom = first ? first.groupMember ?? first.parent : parent;
|
|
40540
|
+
if (inheritFrom) rawRequest = inherit(rawRequest, inheritFrom);
|
|
40334
40541
|
const req = parseRequest(rawRequest, {
|
|
40335
40542
|
isFollowup: Boolean(parent || fromGroup),
|
|
40336
40543
|
parentMode: parent?.mode ?? fromGroup?.group.mode ?? null,
|
|
@@ -40357,18 +40564,21 @@ var init_jobs = __esm({
|
|
|
40357
40564
|
);
|
|
40358
40565
|
}
|
|
40359
40566
|
const chainOf = (i) => fromGroup ? fromGroup.links[i] : parent ? { parent, round } : null;
|
|
40360
|
-
const seats = req.targets.map((target, i) =>
|
|
40361
|
-
|
|
40362
|
-
|
|
40363
|
-
|
|
40364
|
-
|
|
40365
|
-
|
|
40366
|
-
|
|
40367
|
-
|
|
40368
|
-
|
|
40369
|
-
|
|
40370
|
-
|
|
40371
|
-
|
|
40567
|
+
const seats = req.targets.map((target, i) => {
|
|
40568
|
+
const link = chainOf(i);
|
|
40569
|
+
return {
|
|
40570
|
+
job_id: id("job"),
|
|
40571
|
+
...link ? { chain_id: link.parent.chain_id, round: link.round } : {},
|
|
40572
|
+
// Twice the budget plus an hour: the deadline is wall-clock, the budget is
|
|
40573
|
+
// not, so a suspended laptop must not make a live owner look overdue.
|
|
40574
|
+
owner: {
|
|
40575
|
+
pid: process.pid,
|
|
40576
|
+
deadline_at: new Date(
|
|
40577
|
+
Date.now() + 2 * timeoutMs(consultantByName(target)) + POLICY.killGraceMs + 36e5
|
|
40578
|
+
).toISOString()
|
|
40579
|
+
}
|
|
40580
|
+
};
|
|
40581
|
+
});
|
|
40372
40582
|
const taken = takeSeats(seats, POLICY.maxConcurrent, (seat) => ownerState(seat.job_id, seat.owner) === "alive");
|
|
40373
40583
|
if (taken.chainBusy) {
|
|
40374
40584
|
const { live, mine } = taken.chainBusy;
|
|
@@ -40395,6 +40605,10 @@ var init_jobs = __esm({
|
|
|
40395
40605
|
// round of its own chain. A member that is still running, never answered or
|
|
40396
40606
|
// has used its rounds is skipped and named; if that is every member, the
|
|
40397
40607
|
// request is refused.
|
|
40608
|
+
/**
|
|
40609
|
+
* @param {string} groupId
|
|
40610
|
+
* @returns {GroupParents}
|
|
40611
|
+
*/
|
|
40398
40612
|
#groupParents(groupId) {
|
|
40399
40613
|
const group = this.groups.get(groupId) ?? loadGroup(groupId);
|
|
40400
40614
|
if (!group) throw new RequestError(`followup_to references unknown group "${groupId}"`, "unknown_group");
|
|
@@ -40412,10 +40626,18 @@ var init_jobs = __esm({
|
|
|
40412
40626
|
}
|
|
40413
40627
|
const rounds = this.#chainRounds(member.chain_id);
|
|
40414
40628
|
const latest = rounds.filter((j) => !["failed", "cancelled"].includes(j.status)).sort((a, b) => b.round - a.round)[0];
|
|
40629
|
+
if (!latest) {
|
|
40630
|
+
skipped.push({
|
|
40631
|
+
job_id: member.job_id,
|
|
40632
|
+
target: member.target,
|
|
40633
|
+
reason: `${member.status}, so there is no answer to follow up`
|
|
40634
|
+
});
|
|
40635
|
+
continue;
|
|
40636
|
+
}
|
|
40415
40637
|
const nextRound = Math.max(...rounds.map((j) => j.round)) + 1;
|
|
40416
40638
|
const consultant = consultantByName(member.target);
|
|
40417
|
-
const reason =
|
|
40418
|
-
if (reason) skipped.push({ job_id:
|
|
40639
|
+
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;
|
|
40640
|
+
if (reason) skipped.push({ job_id: latest.job_id, target: member.target, reason });
|
|
40419
40641
|
else links.push({ parent: { ...latest, nextRound }, round: nextRound, groupMember: member });
|
|
40420
40642
|
}
|
|
40421
40643
|
if (!links.length) {
|
|
@@ -40426,14 +40648,21 @@ var init_jobs = __esm({
|
|
|
40426
40648
|
}
|
|
40427
40649
|
return { group, links, skipped };
|
|
40428
40650
|
}
|
|
40651
|
+
/**
|
|
40652
|
+
* @param {ParsedRequest} req
|
|
40653
|
+
* @param {Array<{parent: Round | null, round: number, groupMember?: Round}>} links
|
|
40654
|
+
* @param {Seat[]} seats
|
|
40655
|
+
* @param {GroupParents | null} [fromGroup]
|
|
40656
|
+
*/
|
|
40429
40657
|
#admitted(req, links, seats, fromGroup = null) {
|
|
40430
|
-
const
|
|
40658
|
+
const first = links[0];
|
|
40659
|
+
const parent = first?.groupMember ?? first?.parent;
|
|
40431
40660
|
const groupId = id("group");
|
|
40432
40661
|
let exposeManifest = null;
|
|
40433
|
-
const parentEntries = req.context.expose_from_parent ? parent
|
|
40662
|
+
const parentEntries = req.context.expose_from_parent ? parent?.brief?.expose_paths?.entries ?? [] : [];
|
|
40434
40663
|
if (req.context.expose_from_parent && parentEntries.length === 0) {
|
|
40435
40664
|
throw new RequestError(
|
|
40436
|
-
`context.expose_from_parent: job "${parent
|
|
40665
|
+
`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
40666
|
"nothing_to_inherit"
|
|
40438
40667
|
);
|
|
40439
40668
|
}
|
|
@@ -40454,7 +40683,10 @@ var init_jobs = __esm({
|
|
|
40454
40683
|
if (req.context.expose_diff) {
|
|
40455
40684
|
exposeManifest.skipped.push({ source: d.source, reason: "replaced", exposed_as: d.exposed_as });
|
|
40456
40685
|
} else {
|
|
40457
|
-
if (reexposeChange(snapshot, d, exposeManifest))
|
|
40686
|
+
if (reexposeChange(snapshot, d, exposeManifest)) {
|
|
40687
|
+
const added = exposeManifest.entries.at(-1);
|
|
40688
|
+
if (added) added.inherited = true;
|
|
40689
|
+
}
|
|
40458
40690
|
}
|
|
40459
40691
|
}
|
|
40460
40692
|
if (req.context.expose_diff) {
|
|
@@ -40463,28 +40695,32 @@ var init_jobs = __esm({
|
|
|
40463
40695
|
if (diff) {
|
|
40464
40696
|
req.context.artifacts = [
|
|
40465
40697
|
...req.context.artifacts,
|
|
40466
|
-
{ name: `git diff ${exposeManifest.entries.at(-1)
|
|
40698
|
+
{ name: `git diff ${exposeManifest.entries.at(-1)?.base}`, kind: "diff", language: "diff", excerpt: diff }
|
|
40467
40699
|
];
|
|
40468
40700
|
}
|
|
40469
40701
|
}
|
|
40470
40702
|
markSinceParent(exposeManifest, parentEntries);
|
|
40471
40703
|
if (parentEntries.length && !exposeManifest.entries.length && !req.context.facts.length && !req.context.artifacts.length) {
|
|
40472
40704
|
throw new RequestError(
|
|
40473
|
-
`context.expose_from_parent: nothing job "${parent
|
|
40705
|
+
`context.expose_from_parent: nothing job "${parent?.job_id}" exposed is there any more, and this request has no facts or artifacts`,
|
|
40474
40706
|
"nothing_to_inherit"
|
|
40475
40707
|
);
|
|
40476
40708
|
}
|
|
40477
40709
|
} catch (err) {
|
|
40478
40710
|
cleanupJobDir(groupId);
|
|
40479
|
-
if (err instanceof RequestError || !err.code) throw err;
|
|
40711
|
+
if (err instanceof RequestError || !(err instanceof Error) || !("code" in err) || !err.code) throw err;
|
|
40480
40712
|
throw new RequestError(err.message, err.code === "too_large" ? "expose_paths_too_large" : "expose_path_invalid");
|
|
40481
40713
|
}
|
|
40482
40714
|
req._snapshot = path11.join(snapshot, "workspace");
|
|
40483
40715
|
}
|
|
40484
40716
|
const members2 = req.targets.map((target, i) => {
|
|
40485
|
-
const
|
|
40717
|
+
const link = links[i];
|
|
40718
|
+
const seat = seats[i];
|
|
40719
|
+
if (!link || !seat) throw new Error(`no follow-up link or seat for target "${target}"`);
|
|
40720
|
+
const { parent: from, round: round2 } = link;
|
|
40486
40721
|
const chainId = from ? from.chain_id : id("chain");
|
|
40487
|
-
const jobId =
|
|
40722
|
+
const jobId = seat.job_id;
|
|
40723
|
+
const consultant = consultantNamed(target);
|
|
40488
40724
|
const job = {
|
|
40489
40725
|
job_id: jobId,
|
|
40490
40726
|
chain_id: chainId,
|
|
@@ -40519,9 +40755,9 @@ var init_jobs = __esm({
|
|
|
40519
40755
|
status: "queued",
|
|
40520
40756
|
// What this consultant is: the CLI it runs on, the model and its
|
|
40521
40757
|
// lineage, as the operator defined it.
|
|
40522
|
-
cli:
|
|
40523
|
-
model:
|
|
40524
|
-
vendor:
|
|
40758
|
+
cli: consultant.cli,
|
|
40759
|
+
model: consultant.model,
|
|
40760
|
+
vendor: consultant.vendor,
|
|
40525
40761
|
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
40526
40762
|
started_at: null,
|
|
40527
40763
|
finished_at: null,
|
|
@@ -40538,16 +40774,18 @@ var init_jobs = __esm({
|
|
|
40538
40774
|
this.order.push(jobId);
|
|
40539
40775
|
LIVE_HERE.add(jobId);
|
|
40540
40776
|
this.#watchCancels();
|
|
40541
|
-
job._owner =
|
|
40777
|
+
job._owner = seat.owner;
|
|
40542
40778
|
this.#mark(job);
|
|
40543
40779
|
return job;
|
|
40544
40780
|
});
|
|
40545
40781
|
this.#evict();
|
|
40782
|
+
const [firstMember] = members2;
|
|
40783
|
+
if (!firstMember) throw new Error("a consultation needs a target");
|
|
40546
40784
|
const group = {
|
|
40547
40785
|
group_id: groupId,
|
|
40548
40786
|
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
40549
40787
|
mode: req.mode,
|
|
40550
|
-
question:
|
|
40788
|
+
question: firstMember.question,
|
|
40551
40789
|
// already redacted, and byte-identical across the group
|
|
40552
40790
|
job_ids: members2.map((m) => m.job_id)
|
|
40553
40791
|
};
|
|
@@ -40564,13 +40802,13 @@ var init_jobs = __esm({
|
|
|
40564
40802
|
});
|
|
40565
40803
|
}
|
|
40566
40804
|
if (req._snapshot) cleanupJobDir(groupId);
|
|
40567
|
-
const { round } =
|
|
40805
|
+
const { round } = firstMember;
|
|
40568
40806
|
const sameRound = members2.every((m) => m.round === round);
|
|
40569
40807
|
const common = {
|
|
40570
40808
|
group_id: groupId,
|
|
40571
40809
|
...sameRound ? { round, rounds_remaining: POLICY.maxRounds - round } : {},
|
|
40572
40810
|
...fromGroup ? { followup_to_group: fromGroup.group.group_id, skipped: fromGroup.skipped } : {},
|
|
40573
|
-
...parentEntries.length ? { exposed_from_parent: inheritedSummary(exposeManifest) } : {},
|
|
40811
|
+
...parentEntries.length && exposeManifest ? { exposed_from_parent: inheritedSummary(exposeManifest) } : {},
|
|
40574
40812
|
mode: req.mode,
|
|
40575
40813
|
// Counted, not refused: a brief of facts alone is a legitimate
|
|
40576
40814
|
// consultation, but one about code that hands over no file is one the
|
|
@@ -40584,7 +40822,7 @@ var init_jobs = __esm({
|
|
|
40584
40822
|
limits: limitsSummary()
|
|
40585
40823
|
};
|
|
40586
40824
|
if (!req.fanout) {
|
|
40587
|
-
const job =
|
|
40825
|
+
const job = firstMember;
|
|
40588
40826
|
return {
|
|
40589
40827
|
...common,
|
|
40590
40828
|
job_id: job.job_id,
|
|
@@ -40615,13 +40853,24 @@ var init_jobs = __esm({
|
|
|
40615
40853
|
// (memory wins: it can hold a verdict written since the round was saved).
|
|
40616
40854
|
// Each point goes with the lead's verdict on it, so the consultant answers
|
|
40617
40855
|
// knowing which of its points were checked and how they came out.
|
|
40856
|
+
/**
|
|
40857
|
+
* @param {string} chainId
|
|
40858
|
+
* @returns {Round[]}
|
|
40859
|
+
*/
|
|
40618
40860
|
#chainRounds(chainId) {
|
|
40619
40861
|
const byRound = new Map(loadChain(chainId).map((j) => [j.round, j]));
|
|
40620
40862
|
for (const j of this.jobs.values()) if (j.chain_id === chainId) byRound.set(j.round, j);
|
|
40621
40863
|
return [...byRound.values()];
|
|
40622
40864
|
}
|
|
40865
|
+
/**
|
|
40866
|
+
* @param {string} chainId
|
|
40867
|
+
* @returns {PriorRound[]}
|
|
40868
|
+
*/
|
|
40623
40869
|
#chainHistory(chainId) {
|
|
40624
|
-
return this.#chainRounds(chainId).filter(
|
|
40870
|
+
return this.#chainRounds(chainId).filter(
|
|
40871
|
+
/** @returns {j is Round & {result: ConsultResult}} */
|
|
40872
|
+
(j) => j.status === "completed" && Boolean(j.result)
|
|
40873
|
+
).sort((a, b) => a.round - b.round).map((j) => {
|
|
40625
40874
|
const verdicts = new Map((j.record?.entries ?? []).map((e) => [e.id, e]));
|
|
40626
40875
|
const ordered = (items, shape) => [...items.filter((x) => verdicts.has(x.id)), ...items.filter((x) => !verdicts.has(x.id))].map((x) => ({
|
|
40627
40876
|
id: x.id,
|
|
@@ -40646,6 +40895,7 @@ var init_jobs = __esm({
|
|
|
40646
40895
|
#evict() {
|
|
40647
40896
|
while (this.order.length > POLICY.maxJobsRetained) {
|
|
40648
40897
|
const oldest = this.order.shift();
|
|
40898
|
+
if (oldest === void 0) break;
|
|
40649
40899
|
const j = this.jobs.get(oldest);
|
|
40650
40900
|
if (j && (j.status === "running" || j.status === "queued")) {
|
|
40651
40901
|
this.order.push(oldest);
|
|
@@ -40659,14 +40909,22 @@ var init_jobs = __esm({
|
|
|
40659
40909
|
// last member there is nothing left to compare, so drop the group rather than
|
|
40660
40910
|
// keep answering as a complete fan-out with no answers in it (and keep the
|
|
40661
40911
|
// Map, which holds the question text, from growing without bound).
|
|
40912
|
+
/** @param {string} groupId */
|
|
40662
40913
|
#dropGroupIfEmpty(groupId) {
|
|
40663
40914
|
const group = this.groups.get(groupId);
|
|
40664
40915
|
if (!group) return;
|
|
40665
40916
|
if (group.job_ids.some((jid) => this.jobs.has(jid))) return;
|
|
40666
40917
|
this.groups.delete(groupId);
|
|
40667
40918
|
}
|
|
40919
|
+
/**
|
|
40920
|
+
* @param {Job} job
|
|
40921
|
+
* @param {ParsedRequest} req
|
|
40922
|
+
* @param {import('./brief.mjs').BriefChain} chain
|
|
40923
|
+
* @param {string} [brief]
|
|
40924
|
+
* @returns {Promise<Job>}
|
|
40925
|
+
*/
|
|
40668
40926
|
async #execute(job, req, chain, brief) {
|
|
40669
|
-
const consultant =
|
|
40927
|
+
const consultant = consultantNamed(job.target);
|
|
40670
40928
|
const cli = consultant.cli;
|
|
40671
40929
|
const adapter = ADAPTERS[cli];
|
|
40672
40930
|
const workdir = makeWorkdir(job.job_id);
|
|
@@ -40674,7 +40932,7 @@ var init_jobs = __esm({
|
|
|
40674
40932
|
try {
|
|
40675
40933
|
fs9.cpSync(req._snapshot, path11.join(workdir, "workspace"), { recursive: true });
|
|
40676
40934
|
} catch (err) {
|
|
40677
|
-
return this.#fail(job, "spawn_error", `could not copy the exposed files: ${err.message}`);
|
|
40935
|
+
return this.#fail(job, "spawn_error", `could not copy the exposed files: ${err instanceof Error ? err.message : err}`);
|
|
40678
40936
|
}
|
|
40679
40937
|
}
|
|
40680
40938
|
const text2 = brief ?? renderBrief(req, chain);
|
|
@@ -40698,20 +40956,26 @@ var init_jobs = __esm({
|
|
|
40698
40956
|
this.#mark(job);
|
|
40699
40957
|
const t0 = Date.now();
|
|
40700
40958
|
if (adapter.prepare) await adapter.prepare();
|
|
40701
|
-
const
|
|
40702
|
-
|
|
40703
|
-
|
|
40704
|
-
|
|
40705
|
-
|
|
40706
|
-
|
|
40707
|
-
|
|
40708
|
-
|
|
40709
|
-
|
|
40710
|
-
|
|
40711
|
-
|
|
40712
|
-
|
|
40713
|
-
|
|
40714
|
-
|
|
40959
|
+
const launch = (c) => {
|
|
40960
|
+
const invocation2 = adapter.buildInvocation({
|
|
40961
|
+
workdir,
|
|
40962
|
+
schemaPath,
|
|
40963
|
+
guardrails: renderGuardrails(Boolean(req._snapshot), req.network),
|
|
40964
|
+
sandbox,
|
|
40965
|
+
consultant: c,
|
|
40966
|
+
hasExposedPaths: Boolean(req._snapshot),
|
|
40967
|
+
network: req.network
|
|
40968
|
+
});
|
|
40969
|
+
const problem = extraArgsProblem(cli, invocation2.args, t.extraArgs);
|
|
40970
|
+
if (problem) return problem;
|
|
40971
|
+
invocation2.args = withExtraArgs(invocation2.args, t.extraArgs);
|
|
40972
|
+
assertNoForbiddenFlags(cli, invocation2.args);
|
|
40973
|
+
job.boundary = adapter.boundary?.({ args: invocation2.args, sandbox, network: req.network, hasExposedPaths: Boolean(req._snapshot) }) ?? null;
|
|
40974
|
+
return invocation2;
|
|
40975
|
+
};
|
|
40976
|
+
const first = launch(consultant);
|
|
40977
|
+
if (typeof first === "string") return this.#fail(job, "spawn_error", `config clis.${cli}.args: ${first}`);
|
|
40978
|
+
let invocation = first;
|
|
40715
40979
|
const attempt = (timeout) => {
|
|
40716
40980
|
const { handle, done } = runChild({
|
|
40717
40981
|
command: invocation.command,
|
|
@@ -40720,7 +40984,9 @@ var init_jobs = __esm({
|
|
|
40720
40984
|
env,
|
|
40721
40985
|
input: text2,
|
|
40722
40986
|
timeoutMs: timeout,
|
|
40723
|
-
onCancelSignal: (fn) =>
|
|
40987
|
+
onCancelSignal: (fn) => {
|
|
40988
|
+
job._cancelFns.push(fn);
|
|
40989
|
+
},
|
|
40724
40990
|
compactLine: adapter.compactLine,
|
|
40725
40991
|
settleAfter: adapter.settleAfter,
|
|
40726
40992
|
stopSignal: adapter.stopSignal
|
|
@@ -40733,6 +40999,7 @@ var init_jobs = __esm({
|
|
|
40733
40999
|
let interpreted;
|
|
40734
41000
|
let authTried = false;
|
|
40735
41001
|
let autoRetries = 0;
|
|
41002
|
+
let fallenBack = false;
|
|
40736
41003
|
for (; ; ) {
|
|
40737
41004
|
job.tool_calls = adapter.toolCalls?.(run) ?? null;
|
|
40738
41005
|
job.duration_ms = Date.now() - t0;
|
|
@@ -40761,7 +41028,7 @@ var init_jobs = __esm({
|
|
|
40761
41028
|
}
|
|
40762
41029
|
const lastMessageText = invocation.lastMessagePath ? readIfExists(invocation.lastMessagePath) : "";
|
|
40763
41030
|
interpreted = adapter.interpret({ ...run, lastMessageText });
|
|
40764
|
-
const
|
|
41031
|
+
const recoveredError = interpreted.ok ? interpreted.recoveredError : null;
|
|
40765
41032
|
if (interpreted.ok && recoveredError && !parses(interpreted.text)) {
|
|
40766
41033
|
interpreted = { ok: false, ...recoveredError, usageRaw: interpreted.usageRaw };
|
|
40767
41034
|
}
|
|
@@ -40789,6 +41056,22 @@ var init_jobs = __esm({
|
|
|
40789
41056
|
continue;
|
|
40790
41057
|
}
|
|
40791
41058
|
}
|
|
41059
|
+
if (interpreted.failureKind === "usage_limit" && consultant.fallbackModel && !fallenBack && remaining >= MIN_RETRY_MS) {
|
|
41060
|
+
fallenBack = true;
|
|
41061
|
+
const fallback = consultant.fallbackModel;
|
|
41062
|
+
const next = launch({ ...consultant, model: fallback });
|
|
41063
|
+
if (typeof next === "string") return this.#fail(job, "spawn_error", `config clis.${cli}.args: ${next}`);
|
|
41064
|
+
invocation = next;
|
|
41065
|
+
job.retries.push({
|
|
41066
|
+
failure_kind: interpreted.failureKind,
|
|
41067
|
+
reason: `${job.model} quota used up; re-run on ${fallback}`,
|
|
41068
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
41069
|
+
usage: adapter.usageRecord(interpreted.usageRaw)
|
|
41070
|
+
});
|
|
41071
|
+
job.model = fallback;
|
|
41072
|
+
run = await attempt(remaining);
|
|
41073
|
+
continue;
|
|
41074
|
+
}
|
|
40792
41075
|
const wait = POLICY.retryBackoffMs * 3 ** autoRetries;
|
|
40793
41076
|
if (AUTO_RETRY_KINDS.includes(interpreted.failureKind) && autoRetries < POLICY.maxAutoRetries && remaining - wait >= MIN_RETRY_MS) {
|
|
40794
41077
|
autoRetries += 1;
|
|
@@ -40845,7 +41128,10 @@ var init_jobs = __esm({
|
|
|
40845
41128
|
adviceCaveat(normalized),
|
|
40846
41129
|
sameVendorCaveat(job.caller, job.caller_model, consultant),
|
|
40847
41130
|
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)"
|
|
41131
|
+
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)",
|
|
41132
|
+
fallenBack && `answered by ${job.model}: ${consultant.model}'s quota was used up`,
|
|
41133
|
+
req._snapshot && unreadCaveat(job.tool_calls, adapter.FILE_READ_TOOLS),
|
|
41134
|
+
refusedToolCaveat(job.tool_calls, adapter.ALLOWED_TOOLS)
|
|
40849
41135
|
].filter(Boolean);
|
|
40850
41136
|
job.quality = {
|
|
40851
41137
|
...normalized.quality,
|
|
@@ -40860,6 +41146,13 @@ var init_jobs = __esm({
|
|
|
40860
41146
|
sandbox?.cleanup();
|
|
40861
41147
|
}
|
|
40862
41148
|
}
|
|
41149
|
+
/**
|
|
41150
|
+
* @param {Job} job
|
|
41151
|
+
* @param {string} kind
|
|
41152
|
+
* @param {unknown} message
|
|
41153
|
+
* @param {string | null} [detail]
|
|
41154
|
+
* @returns {Job}
|
|
41155
|
+
*/
|
|
40863
41156
|
#fail(job, kind, message, detail) {
|
|
40864
41157
|
if (job.status === "completed" || job.status === "failed" || job.status === "cancelled") return job;
|
|
40865
41158
|
job.status = kind === "cancelled" ? "cancelled" : "failed";
|
|
@@ -40875,12 +41168,14 @@ var init_jobs = __esm({
|
|
|
40875
41168
|
return job;
|
|
40876
41169
|
}
|
|
40877
41170
|
// The in-progress marker other processes read (see store.writeRunning).
|
|
41171
|
+
/** @param {Job} job */
|
|
40878
41172
|
#mark(job) {
|
|
40879
41173
|
try {
|
|
40880
41174
|
writeRunning({ ...this.#record(job), owner: job._owner });
|
|
40881
41175
|
} catch {
|
|
40882
41176
|
}
|
|
40883
41177
|
}
|
|
41178
|
+
/** @param {Job} job */
|
|
40884
41179
|
#finish(job) {
|
|
40885
41180
|
try {
|
|
40886
41181
|
persistRound(this.#record(job));
|
|
@@ -40898,7 +41193,7 @@ var init_jobs = __esm({
|
|
|
40898
41193
|
// consultant did not produce and a word outside VERDICTS, then stores what
|
|
40899
41194
|
// was written. It never decides that a finding was adopted, and it never
|
|
40900
41195
|
// fills in a verdict the lead did not write.
|
|
40901
|
-
/** @param {
|
|
41196
|
+
/** @param {RawRecord} [input] */
|
|
40902
41197
|
record(input2 = {}) {
|
|
40903
41198
|
if (input2.records !== void 0) return this.#recordMany(input2);
|
|
40904
41199
|
const allowed = ["job_id", "entries", "reflection"];
|
|
@@ -40918,6 +41213,7 @@ var init_jobs = __esm({
|
|
|
40918
41213
|
// record is checked before anything is written, so a bad id leaves the
|
|
40919
41214
|
// history as it was; then each job is written on its own, and a write that
|
|
40920
41215
|
// fails is named in the answer rather than reported as saved.
|
|
41216
|
+
/** @param {RawRecord} input */
|
|
40921
41217
|
#recordMany(input2) {
|
|
40922
41218
|
const extra = Object.keys(input2).filter((k) => k !== "records");
|
|
40923
41219
|
if (extra.length) {
|
|
@@ -40960,7 +41256,10 @@ var init_jobs = __esm({
|
|
|
40960
41256
|
}
|
|
40961
41257
|
// Everything that can be wrong with a record, found before anything is
|
|
40962
41258
|
// written: what the job's record and reflection become, or a RequestError.
|
|
40963
|
-
/**
|
|
41259
|
+
/**
|
|
41260
|
+
* @param {RawRecord} rec
|
|
41261
|
+
* @returns {RecordChange}
|
|
41262
|
+
*/
|
|
40964
41263
|
#checkRecord({ job_id: jobId, entries, reflection }) {
|
|
40965
41264
|
if (entries === void 0 && reflection === void 0) {
|
|
40966
41265
|
throw new RequestError(
|
|
@@ -40984,7 +41283,7 @@ var init_jobs = __esm({
|
|
|
40984
41283
|
}
|
|
40985
41284
|
const ids = recordableIds(job.result);
|
|
40986
41285
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
40987
|
-
const
|
|
41286
|
+
const written = /* @__PURE__ */ new Map();
|
|
40988
41287
|
for (const entry of entries ?? []) {
|
|
40989
41288
|
const id2 = typeof entry?.id === "string" ? entry.id.trim() : "";
|
|
40990
41289
|
if (!ids.includes(id2)) {
|
|
@@ -41000,7 +41299,7 @@ var init_jobs = __esm({
|
|
|
41000
41299
|
"invalid_verdict"
|
|
41001
41300
|
);
|
|
41002
41301
|
}
|
|
41003
|
-
|
|
41302
|
+
written.set(id2, {
|
|
41004
41303
|
id: id2,
|
|
41005
41304
|
verdict,
|
|
41006
41305
|
effect: text(entry?.effect),
|
|
@@ -41025,21 +41324,28 @@ var init_jobs = __esm({
|
|
|
41025
41324
|
}
|
|
41026
41325
|
newReflection = { delta, related_item_ids: related, recorded_at: now };
|
|
41027
41326
|
}
|
|
41028
|
-
|
|
41029
|
-
return { job, live, record: newRecord, reflection: newReflection };
|
|
41327
|
+
return { job, live, entries: entries === void 0 ? void 0 : [...written.values()], reflection: newReflection };
|
|
41030
41328
|
}
|
|
41031
|
-
|
|
41329
|
+
/** @param {RecordChange} change */
|
|
41330
|
+
#saveRecord({ job, live, entries, reflection }) {
|
|
41032
41331
|
const was = { record: job.record, reflection: job.reflection };
|
|
41033
|
-
|
|
41034
|
-
|
|
41332
|
+
const disk = loadRoundAt(job);
|
|
41333
|
+
const base = disk?.result ? disk : job;
|
|
41334
|
+
job.reflection = reflection ?? base.reflection;
|
|
41335
|
+
if (entries !== void 0) {
|
|
41336
|
+
const merged = new Map((base.record?.entries ?? []).map((e) => [e.id, e]));
|
|
41337
|
+
for (const e of entries) merged.set(e.id, e);
|
|
41338
|
+
const ids = job.result ? recordableIds(job.result) : [];
|
|
41339
|
+
job.record = { updated_at: (/* @__PURE__ */ new Date()).toISOString(), entries: ids.flatMap((id2) => merged.get(id2) ?? []) };
|
|
41340
|
+
} else if (base.record) job.record = base.record;
|
|
41035
41341
|
try {
|
|
41036
|
-
persistRound(live ? this.#record(
|
|
41342
|
+
persistRound(live ? this.#record(live) : job, { appendIndex: false });
|
|
41037
41343
|
} catch (e) {
|
|
41038
41344
|
Object.assign(job, was);
|
|
41039
41345
|
return {
|
|
41040
41346
|
job_id: job.job_id,
|
|
41041
41347
|
error: "record_not_saved",
|
|
41042
|
-
message: `the verdict for "${job.job_id}" could not be written to the history (${e.message}); record it again`
|
|
41348
|
+
message: `the verdict for "${job.job_id}" could not be written to the history (${e instanceof Error ? e.message : e}); record it again`
|
|
41043
41349
|
};
|
|
41044
41350
|
}
|
|
41045
41351
|
const summary = job.record && recordSummary(job);
|
|
@@ -41051,6 +41357,12 @@ var init_jobs = __esm({
|
|
|
41051
41357
|
reflection_saved: job.reflection != null
|
|
41052
41358
|
};
|
|
41053
41359
|
}
|
|
41360
|
+
/**
|
|
41361
|
+
* @param {string} jobId
|
|
41362
|
+
* @param {number | undefined} waitMs
|
|
41363
|
+
* @param {{detail?: string | undefined}} [opts]
|
|
41364
|
+
* @param {AbortSignal} [signal]
|
|
41365
|
+
*/
|
|
41054
41366
|
async wait(jobId, waitMs, opts, signal) {
|
|
41055
41367
|
const job = this.jobs.get(jobId);
|
|
41056
41368
|
if (!job) return this.#waitOnDisk(() => this.view(jobId, opts), waitMs, signal);
|
|
@@ -41063,6 +41375,13 @@ var init_jobs = __esm({
|
|
|
41063
41375
|
}
|
|
41064
41376
|
// A consultation another process runs has no promise here, so its history
|
|
41065
41377
|
// is re-read once a second until it is no longer running or the wait is up.
|
|
41378
|
+
/**
|
|
41379
|
+
* @template {{status: string}} T
|
|
41380
|
+
* @param {() => T | null} read
|
|
41381
|
+
* @param {number | undefined} waitMs
|
|
41382
|
+
* @param {AbortSignal} [signal]
|
|
41383
|
+
* @returns {Promise<T | null>}
|
|
41384
|
+
*/
|
|
41066
41385
|
async #waitOnDisk(read, waitMs, signal) {
|
|
41067
41386
|
const until = Date.now() + Math.min(Math.max(0, waitMs ?? 0), POLICY.maxWaitMs);
|
|
41068
41387
|
let view = read();
|
|
@@ -41085,6 +41404,7 @@ var init_jobs = __esm({
|
|
|
41085
41404
|
}
|
|
41086
41405
|
}, CANCEL_POLL_MS).unref();
|
|
41087
41406
|
}
|
|
41407
|
+
/** @param {string} jobId */
|
|
41088
41408
|
cancel(jobId) {
|
|
41089
41409
|
const job = this.jobs.get(jobId);
|
|
41090
41410
|
if (!job) {
|
|
@@ -41098,17 +41418,21 @@ var init_jobs = __esm({
|
|
|
41098
41418
|
};
|
|
41099
41419
|
}
|
|
41100
41420
|
if (job.status !== "running" && job.status !== "queued") {
|
|
41101
|
-
return { ...this
|
|
41421
|
+
return { ...this.#jobView(job, void 0), cancel_effect: `job was already ${job.status}` };
|
|
41102
41422
|
}
|
|
41103
41423
|
job.cancelRequested = true;
|
|
41104
41424
|
for (const fn of job._cancelFns) fn();
|
|
41105
41425
|
if (job._handle) job._handle.stop();
|
|
41106
|
-
return { ...this
|
|
41426
|
+
return { ...this.#jobView(job, void 0), status: "cancelling", cancel_effect: "consultant process group signalled" };
|
|
41107
41427
|
}
|
|
41108
41428
|
// Every field here is derived from the one `members` array it is handed, so a
|
|
41109
41429
|
// group view can never report one status in `members` and another in
|
|
41110
41430
|
// `comparison` or `status` for the same job.
|
|
41111
|
-
/**
|
|
41431
|
+
/**
|
|
41432
|
+
* @param {Group} group
|
|
41433
|
+
* @param {JobView[]} members
|
|
41434
|
+
* @param {{detail?: string | undefined}} [opts]
|
|
41435
|
+
*/
|
|
41112
41436
|
#shapeGroup(group, members2, { detail } = {}) {
|
|
41113
41437
|
const full = detail === "full";
|
|
41114
41438
|
const cancelling = members2.some((m) => m.status === "cancelling");
|
|
@@ -41158,14 +41482,27 @@ var init_jobs = __esm({
|
|
|
41158
41482
|
limits: live || !full ? null : limitsSummary()
|
|
41159
41483
|
};
|
|
41160
41484
|
const anyFinished = members2.some((m) => ["completed", "failed", "cancelled"].includes(m.status));
|
|
41161
|
-
|
|
41162
|
-
return view;
|
|
41485
|
+
return anyFinished ? { ...view, comparison: comparison(members2, full ? Infinity : GROUP_CHARS - printed(view)) } : view;
|
|
41163
41486
|
}
|
|
41487
|
+
/**
|
|
41488
|
+
* @param {string} groupId
|
|
41489
|
+
* @param {{detail?: string | undefined}} [opts]
|
|
41490
|
+
*/
|
|
41164
41491
|
groupView(groupId, opts) {
|
|
41165
41492
|
const group = this.groups.get(groupId) ?? loadGroup(groupId);
|
|
41166
41493
|
if (!group) return null;
|
|
41167
|
-
return this.#shapeGroup(
|
|
41494
|
+
return this.#shapeGroup(
|
|
41495
|
+
group,
|
|
41496
|
+
group.job_ids.map((jid) => this.view(jid, opts)).filter((v) => v != null),
|
|
41497
|
+
opts
|
|
41498
|
+
);
|
|
41168
41499
|
}
|
|
41500
|
+
/**
|
|
41501
|
+
* @param {string} groupId
|
|
41502
|
+
* @param {number | undefined} waitMs
|
|
41503
|
+
* @param {{detail?: string | undefined}} [opts]
|
|
41504
|
+
* @param {AbortSignal} [signal]
|
|
41505
|
+
*/
|
|
41169
41506
|
async waitGroup(groupId, waitMs, opts, signal) {
|
|
41170
41507
|
const group = this.groups.get(groupId);
|
|
41171
41508
|
if (!group) return this.#waitOnDisk(() => this.groupView(groupId, opts), waitMs, signal);
|
|
@@ -41176,6 +41513,7 @@ var init_jobs = __esm({
|
|
|
41176
41513
|
}
|
|
41177
41514
|
return this.groupView(groupId, opts);
|
|
41178
41515
|
}
|
|
41516
|
+
/** @param {string} groupId */
|
|
41179
41517
|
cancelGroup(groupId) {
|
|
41180
41518
|
const group = this.groups.get(groupId);
|
|
41181
41519
|
if (!group) {
|
|
@@ -41191,7 +41529,7 @@ var init_jobs = __esm({
|
|
|
41191
41529
|
const j = this.jobs.get(jid);
|
|
41192
41530
|
return j && (j.status === "running" || j.status === "queued");
|
|
41193
41531
|
}).length;
|
|
41194
|
-
const members2 = group.job_ids.map((jid) => this.cancel(jid)).filter(
|
|
41532
|
+
const members2 = group.job_ids.map((jid) => this.cancel(jid)).filter((v) => v != null);
|
|
41195
41533
|
const total = group.job_ids.length;
|
|
41196
41534
|
return {
|
|
41197
41535
|
...this.#shapeGroup(group, members2),
|
|
@@ -41242,7 +41580,7 @@ var init_jobs = __esm({
|
|
|
41242
41580
|
MIN_TEXT_CHARS = 40;
|
|
41243
41581
|
flagNames = (a) => {
|
|
41244
41582
|
if (!a.startsWith("-") || a === "-") return [];
|
|
41245
|
-
const name = a.split("=")
|
|
41583
|
+
const [name = ""] = a.split("=");
|
|
41246
41584
|
return a.startsWith("--") ? [name] : [name, a.slice(0, 2), `-${name}`];
|
|
41247
41585
|
};
|
|
41248
41586
|
}
|
|
@@ -41465,13 +41803,13 @@ function exportChain({ chain_id: chainId, group_id: groupId } = {}) {
|
|
|
41465
41803
|
if (Boolean(chainId) === Boolean(groupId)) {
|
|
41466
41804
|
throw new ExportError("pass exactly one of chain_id or group_id", "export_target_required");
|
|
41467
41805
|
}
|
|
41468
|
-
const chainIds = groupId ? chainsOfGroup(groupId) : [chainId];
|
|
41806
|
+
const chainIds = groupId ? chainsOfGroup(groupId) : chainId ? [chainId] : [];
|
|
41469
41807
|
const perChain = chainIds.map((id2) => ({ chain_id: id2, rounds: roundsOf(id2) }));
|
|
41470
|
-
const
|
|
41471
|
-
if (
|
|
41808
|
+
const first = perChain.find((c) => c.rounds.length)?.rounds[0];
|
|
41809
|
+
if (!first) {
|
|
41472
41810
|
throw new ExportError(`no consultation recorded under "${chainId ?? groupId}" in ~/.severally/history`, "unknown_chain");
|
|
41473
41811
|
}
|
|
41474
|
-
const
|
|
41812
|
+
const total = perChain.reduce((n, c) => n + c.rounds.length, 0);
|
|
41475
41813
|
const lines = [
|
|
41476
41814
|
`# Consultation record: ${first.question}`,
|
|
41477
41815
|
"",
|
|
@@ -41501,9 +41839,14 @@ function exportChain({ chain_id: chainId, group_id: groupId } = {}) {
|
|
|
41501
41839
|
var ExportError, historyDir, bullets2;
|
|
41502
41840
|
var init_export = __esm({
|
|
41503
41841
|
"src/export.mjs"() {
|
|
41842
|
+
"use strict";
|
|
41504
41843
|
init_policy();
|
|
41505
41844
|
init_expose_paths();
|
|
41506
41845
|
ExportError = class extends Error {
|
|
41846
|
+
/**
|
|
41847
|
+
* @param {string} message
|
|
41848
|
+
* @param {string} code
|
|
41849
|
+
*/
|
|
41507
41850
|
constructor(message, code) {
|
|
41508
41851
|
super(message);
|
|
41509
41852
|
this.name = "ExportError";
|
|
@@ -41540,6 +41883,7 @@ function staleInstall(home = os5.homedir(), severallyHome = POLICY.home) {
|
|
|
41540
41883
|
// This release names it SEVERALLY_VERSION; older ones only had it in the server info.
|
|
41541
41884
|
runtime: bundle?.match(/(?:SEVERALLY_VERSION = |name: ["']severally["'], version: )["'](\d+\.\d+\.\d+)["']/)?.[1],
|
|
41542
41885
|
claude_skills_dir: (() => {
|
|
41886
|
+
if (plugin === null) return void 0;
|
|
41543
41887
|
try {
|
|
41544
41888
|
return JSON.parse(plugin).version;
|
|
41545
41889
|
} catch {
|
|
@@ -41548,28 +41892,22 @@ function staleInstall(home = os5.homedir(), severallyHome = POLICY.home) {
|
|
|
41548
41892
|
})()
|
|
41549
41893
|
};
|
|
41550
41894
|
const copies = { this_server: SEVERALLY_VERSION };
|
|
41551
|
-
for (const [name, v] of Object.entries(found)) if (/^\d+\.\d+\.\d+$/.test(v
|
|
41895
|
+
for (const [name, v] of Object.entries(found)) if (v !== void 0 && /^\d+\.\d+\.\d+$/.test(v)) copies[name] = v;
|
|
41552
41896
|
const versions = Object.values(copies);
|
|
41553
41897
|
if (versions.every((v) => v === SEVERALLY_VERSION)) return null;
|
|
41554
41898
|
const newest = versions.reduce((a, b) => older(a, b) ? b : a);
|
|
41555
41899
|
return {
|
|
41556
41900
|
copies,
|
|
41557
|
-
behind: Object.
|
|
41901
|
+
behind: Object.entries(copies).filter(([, v]) => older(v, newest)).map(([k]) => k),
|
|
41558
41902
|
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
41903
|
};
|
|
41560
41904
|
}
|
|
41561
41905
|
function createServer(manager = new JobManager()) {
|
|
41562
41906
|
const stale = staleInstall();
|
|
41563
41907
|
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) }] });
|
|
41908
|
+
const ok = (payload) => ({ content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] });
|
|
41568
41909
|
const fail = (payload) => ({
|
|
41569
|
-
content: [{ type: (
|
|
41570
|
-
/** @type {const} */
|
|
41571
|
-
"text"
|
|
41572
|
-
), text: JSON.stringify(payload, null, 2) }],
|
|
41910
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
41573
41911
|
isError: true
|
|
41574
41912
|
});
|
|
41575
41913
|
server.registerTool(
|
|
@@ -41588,7 +41926,7 @@ function createServer(manager = new JobManager()) {
|
|
|
41588
41926
|
if (err instanceof RequestError) {
|
|
41589
41927
|
return fail({ error: err.code, message: err.message, details: err.details ?? null, limits: limitsSummary() });
|
|
41590
41928
|
}
|
|
41591
|
-
return fail({ error: "internal_error", message:
|
|
41929
|
+
return fail({ error: "internal_error", message: errorMessage2(err) });
|
|
41592
41930
|
}
|
|
41593
41931
|
}
|
|
41594
41932
|
);
|
|
@@ -41631,9 +41969,15 @@ function createServer(manager = new JobManager()) {
|
|
|
41631
41969
|
}, PROGRESS_EVERY_MS);
|
|
41632
41970
|
let view;
|
|
41633
41971
|
try {
|
|
41634
|
-
view = group_id ? await manager.waitGroup(group_id, wait_ms ?? DEFAULT_WAIT_MS, { detail }, req?.signal) : await manager.wait(
|
|
41972
|
+
view = group_id ? await manager.waitGroup(group_id, wait_ms ?? DEFAULT_WAIT_MS, { detail }, req?.signal) : await manager.wait(
|
|
41973
|
+
/** @type {string} */
|
|
41974
|
+
job_id,
|
|
41975
|
+
wait_ms ?? DEFAULT_WAIT_MS,
|
|
41976
|
+
{ detail },
|
|
41977
|
+
req?.signal
|
|
41978
|
+
);
|
|
41635
41979
|
} finally {
|
|
41636
|
-
clearInterval(timer);
|
|
41980
|
+
clearInterval(timer ?? void 0);
|
|
41637
41981
|
}
|
|
41638
41982
|
if (!view) {
|
|
41639
41983
|
return fail({
|
|
@@ -41643,7 +41987,11 @@ function createServer(manager = new JobManager()) {
|
|
|
41643
41987
|
}
|
|
41644
41988
|
if (!ids || ["running", "queued", "cancelling"].includes(view.status)) return ok(view);
|
|
41645
41989
|
try {
|
|
41646
|
-
return ok(pointsOf(
|
|
41990
|
+
return ok(pointsOf(
|
|
41991
|
+
/** @type {import('./jobs.mjs').JobView} */
|
|
41992
|
+
view,
|
|
41993
|
+
ids
|
|
41994
|
+
));
|
|
41647
41995
|
} catch (err) {
|
|
41648
41996
|
if (err instanceof RequestError) return fail({ error: err.code, message: err.message });
|
|
41649
41997
|
throw err;
|
|
@@ -41665,7 +42013,10 @@ function createServer(manager = new JobManager()) {
|
|
|
41665
42013
|
if (Boolean(job_id) === Boolean(group_id)) {
|
|
41666
42014
|
return fail({ error: "invalid_request", message: "pass exactly one of job_id or group_id" });
|
|
41667
42015
|
}
|
|
41668
|
-
const view = group_id ? manager.cancelGroup(group_id) : manager.cancel(
|
|
42016
|
+
const view = group_id ? manager.cancelGroup(group_id) : manager.cancel(
|
|
42017
|
+
/** @type {string} */
|
|
42018
|
+
job_id
|
|
42019
|
+
);
|
|
41669
42020
|
if (!view) {
|
|
41670
42021
|
return fail({
|
|
41671
42022
|
error: "unknown_job",
|
|
@@ -41708,7 +42059,7 @@ function createServer(manager = new JobManager()) {
|
|
|
41708
42059
|
if (err instanceof RequestError) {
|
|
41709
42060
|
return fail({ error: err.code, message: err.message, details: err.details ?? null });
|
|
41710
42061
|
}
|
|
41711
|
-
return fail({ error: "internal_error", message:
|
|
42062
|
+
return fail({ error: "internal_error", message: errorMessage2(err) });
|
|
41712
42063
|
}
|
|
41713
42064
|
}
|
|
41714
42065
|
);
|
|
@@ -41728,7 +42079,7 @@ function createServer(manager = new JobManager()) {
|
|
|
41728
42079
|
return ok(exportChain({ chain_id, group_id }));
|
|
41729
42080
|
} catch (err) {
|
|
41730
42081
|
if (err instanceof ExportError) return fail({ error: err.code, message: err.message });
|
|
41731
|
-
return fail({ error: "internal_error", message:
|
|
42082
|
+
return fail({ error: "internal_error", message: errorMessage2(err) });
|
|
41732
42083
|
}
|
|
41733
42084
|
}
|
|
41734
42085
|
);
|
|
@@ -41779,9 +42130,10 @@ async function main() {
|
|
|
41779
42130
|
process.on("exit", () => manager.killNow());
|
|
41780
42131
|
serveStdio(() => createServer(manager).server);
|
|
41781
42132
|
}
|
|
41782
|
-
var SEVERALLY_VERSION, DEFAULT_WAIT_MS, PROGRESS_EVERY_MS, semver, older, SERVER_INSTRUCTIONS, startDescription, recordShape;
|
|
42133
|
+
var SEVERALLY_VERSION, DEFAULT_WAIT_MS, PROGRESS_EVERY_MS, semver, older, SERVER_INSTRUCTIONS, startDescription, recordShape, errorMessage2;
|
|
41783
42134
|
var init_server = __esm({
|
|
41784
42135
|
"src/server.mjs"() {
|
|
42136
|
+
"use strict";
|
|
41785
42137
|
init_zod();
|
|
41786
42138
|
init_dist();
|
|
41787
42139
|
init_stdio();
|
|
@@ -41789,13 +42141,17 @@ var init_server = __esm({
|
|
|
41789
42141
|
init_schema();
|
|
41790
42142
|
init_jobs();
|
|
41791
42143
|
init_export();
|
|
41792
|
-
SEVERALLY_VERSION = "2.
|
|
42144
|
+
SEVERALLY_VERSION = "2.5.0";
|
|
41793
42145
|
DEFAULT_WAIT_MS = process.env.SEVERALLY_MAX_WAIT_MS ? POLICY.maxWaitMs : Math.min(POLICY.maxWaitMs, 45e3);
|
|
41794
42146
|
PROGRESS_EVERY_MS = 15e3;
|
|
41795
42147
|
semver = (v) => String(v).split(".").map(Number);
|
|
41796
42148
|
older = (a, b) => {
|
|
41797
42149
|
const [x, y] = [semver(a), semver(b)];
|
|
41798
|
-
for (let i = 0; i < 3; i++)
|
|
42150
|
+
for (let i = 0; i < 3; i++) {
|
|
42151
|
+
const [p, q] = [x[i], y[i]];
|
|
42152
|
+
if (p === void 0 || q === void 0) return false;
|
|
42153
|
+
if (p !== q) return p < q;
|
|
42154
|
+
}
|
|
41799
42155
|
return false;
|
|
41800
42156
|
};
|
|
41801
42157
|
SERVER_INSTRUCTIONS = `severally lets you get a genuinely independent opinion from another coding agent:
|
|
@@ -41858,6 +42214,7 @@ Model, permissions, rounds, timeout and size caps are fixed by this server.`;
|
|
|
41858
42214
|
related_item_ids: external_exports.array(external_exports.string().min(1)).max(50).optional().describe("f1, u1, c1 ...")
|
|
41859
42215
|
}).optional().describe("written after reading the answer")
|
|
41860
42216
|
};
|
|
42217
|
+
errorMessage2 = (err) => String((typeof err === "object" && err !== null && "message" in err ? err.message : void 0) ?? err);
|
|
41861
42218
|
}
|
|
41862
42219
|
});
|
|
41863
42220
|
|
|
@@ -41875,7 +42232,7 @@ function registration({ cmd, checks }) {
|
|
|
41875
42232
|
ran = true;
|
|
41876
42233
|
if (/(^|[\s@:])severally([\s@:]|$)/m.test(out)) return "registered (listed by the client)";
|
|
41877
42234
|
} catch (err) {
|
|
41878
|
-
if (typeof err
|
|
42235
|
+
if (typeof thrown(err, "status") === "number") ran = true;
|
|
41879
42236
|
}
|
|
41880
42237
|
}
|
|
41881
42238
|
return ran ? "not registered" : `could not tell (${cmd} ${checks.map((a) => a.join(" ")).join(" / ")} failed)`;
|
|
@@ -41888,9 +42245,9 @@ function login(cli) {
|
|
|
41888
42245
|
let ok = true;
|
|
41889
42246
|
try {
|
|
41890
42247
|
out = execCommandSync(check2.cmd, check2.args, { encoding: "utf8", timeout: 2e4, stdio: ["ignore", "pipe", "pipe"] });
|
|
41891
|
-
} catch (
|
|
41892
|
-
if (typeof
|
|
41893
|
-
out = `${
|
|
42248
|
+
} catch (e) {
|
|
42249
|
+
if (typeof thrown(e, "status") !== "number") return `could not tell (${check2.cmd} ${check2.args.join(" ")} failed)`;
|
|
42250
|
+
out = `${thrown(e, "stdout") ?? ""}`;
|
|
41894
42251
|
ok = false;
|
|
41895
42252
|
}
|
|
41896
42253
|
try {
|
|
@@ -41928,9 +42285,10 @@ function status() {
|
|
|
41928
42285
|
`);
|
|
41929
42286
|
return problem ? 1 : 0;
|
|
41930
42287
|
}
|
|
41931
|
-
var CLIENTS, LOGIN;
|
|
42288
|
+
var CLIENTS, thrown, LOGIN;
|
|
41932
42289
|
var init_status = __esm({
|
|
41933
42290
|
"src/status.mjs"() {
|
|
42291
|
+
"use strict";
|
|
41934
42292
|
init_policy();
|
|
41935
42293
|
init_server();
|
|
41936
42294
|
init_platform();
|
|
@@ -41954,6 +42312,7 @@ var init_status = __esm({
|
|
|
41954
42312
|
{ label: "Antigravity", cmd: "agy", checks: [["mcp", "list"]] },
|
|
41955
42313
|
{ label: "OpenCode", cmd: "opencode", checks: [["mcp", "list"]] }
|
|
41956
42314
|
];
|
|
42315
|
+
thrown = (e, key) => typeof e === "object" && e !== null ? Reflect.get(e, key) : void 0;
|
|
41957
42316
|
LOGIN = {
|
|
41958
42317
|
"claude-code": { cmd: "claude", args: ["auth", "status", "--json"], read: (out) => JSON.parse(out).loggedIn === true },
|
|
41959
42318
|
// Codex prints its answer on stderr; the exit status carries the verdict.
|