loki-mode 11.3.8 → 11.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -18
- package/SKILL.md +3 -3
- package/VERSION +1 -1
- package/agents/custom_agents.py +280 -0
- package/autonomy/council-v2.sh +22 -1
- package/autonomy/edit.sh +242 -0
- package/autonomy/lib/chat.sh +312 -0
- package/autonomy/lib/help-core.sh +164 -0
- package/autonomy/lib/json-output.sh +38 -0
- package/autonomy/lib/run-summary.sh +96 -0
- package/autonomy/loki +105 -10
- package/autonomy/route.sh +270 -0
- package/autonomy/run.sh +6 -0
- package/autonomy/setup-wizard.sh +161 -0
- package/autonomy/verify.sh +22 -2
- package/bin/loki +73 -18
- package/completions/_loki +55 -0
- package/completions/loki.bash +49 -3
- package/dashboard/__init__.py +1 -1
- package/docs/CLI-CORE-VERBS.md +112 -0
- package/docs/CLI-REFERENCE.md +7 -1
- package/docs/COMPARE-OPENCODE.md +94 -0
- package/docs/COMPONENTS.md +1 -0
- package/docs/INSTALLATION.md +56 -12
- package/docs/KNOWN-ISSUES.md +2 -12
- package/docs/QUICKSTART.md +108 -0
- package/docs/environment-variables.md +9 -2
- package/docs/exit-codes.md +29 -12
- package/docs/v10/CLI-MODERN.md +10 -4
- package/docs/v10/FAILURE-CLASSES.md +2 -2
- package/docs/v10/GUIDE.md +270 -44
- package/docs/v10/METRICS.md +6 -0
- package/docs/v10/OPENCODE-PARITY-PLAN.md +84 -0
- package/docs/v10/PROGRESS.md +7 -0
- package/loki-ts/dist/loki.js +968 -933
- package/mcp/__init__.py +1 -1
- package/package.json +2 -2
- package/packages/control-plane/dist/server.js +198 -86
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/providers/claude.sh +16 -2
- package/providers/model_route_exec.sh +22 -0
- package/providers/model_routes.sh +188 -0
- package/skills/providers.md +13 -0
- package/templates/agents/planner.md +10 -0
- package/templates/agents/reviewer.md +10 -0
- package/templates/agents/tester.md +10 -0
- package/web-app/dist/assets/{AdminPage-D1jlQGeE.js → AdminPage-CYJ2BWRC.js} +1 -1
- package/web-app/dist/assets/{Avatar-QUX_aJfQ.js → Avatar-DdK4wynI.js} +1 -1
- package/web-app/dist/assets/{Badge-pysXUR6J.js → Badge-CrX3MI7J.js} +1 -1
- package/web-app/dist/assets/{Button-CWzlTw9P.js → Button-709-wwGM.js} +1 -1
- package/web-app/dist/assets/{CockpitPage-DLCoLdyF.js → CockpitPage-BapdTInO.js} +1 -1
- package/web-app/dist/assets/{ComparePage-CCENGIti.js → ComparePage-DT99pGvA.js} +1 -1
- package/web-app/dist/assets/{ErrorBoundary-BssCcbgv.js → ErrorBoundary-B0uJSys8.js} +1 -1
- package/web-app/dist/assets/{EvidenceReceiptPanel-CwmAqJ90.js → EvidenceReceiptPanel-BtMFgElP.js} +1 -1
- package/web-app/dist/assets/{GitHubIssuesPanel-DuSw8shb.js → GitHubIssuesPanel-DjJuU941.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-CGvLbDjh.js → GitHubPRsPanel-HXfEX1BC.js} +1 -1
- package/web-app/dist/assets/{HomePage-BrBX5Gn2.js → HomePage-Cqyves-S.js} +2 -2
- package/web-app/dist/assets/{LoginPage-DPjr7QHU.js → LoginPage-jUbtC7_Y.js} +1 -1
- package/web-app/dist/assets/{MagicPage-Dxd7FWF2.js → MagicPage-DHRsqjgR.js} +1 -1
- package/web-app/dist/assets/{MetricsPage-BP47Urni.js → MetricsPage-B-pED05R.js} +1 -1
- package/web-app/dist/assets/{NotFoundPage-D7_07YoB.js → NotFoundPage-CsbUs21R.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-SQ183efy.js → ProjectPage-BtlLEQwB.js} +3 -3
- package/web-app/dist/assets/{ProjectsPage-Cgr1K2Ae.js → ProjectsPage-DaRbRjSp.js} +1 -1
- package/web-app/dist/assets/{SettingsPage-kyYOSDt0.js → SettingsPage-DALrtP3F.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-CRd-H1A4.js → ShowcasePage-CoPHSNP0.js} +1 -1
- package/web-app/dist/assets/{SystemSettingsPage-BHBdGXod.js → SystemSettingsPage-kOuSN50O.js} +1 -1
- package/web-app/dist/assets/{TeamsPage-CI3Wdbjw.js → TeamsPage-DGWK0LMp.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-DfhCgP6i.js → TemplatesPage-C-_10CwA.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-Cs87YXLJ.js → TerminalOutput-Cz_vPbXL.js} +1 -1
- package/web-app/dist/assets/{activity-CrQx5SbK.js → activity-icIsXJVa.js} +1 -1
- package/web-app/dist/assets/{bell-Dde5IbnI.js → bell-DbPR2_4r.js} +1 -1
- package/web-app/dist/assets/{bot-trRuIJT6.js → bot-DvjP3Yh5.js} +1 -1
- package/web-app/dist/assets/{check-DmsthR-3.js → check-D1OBxHeU.js} +1 -1
- package/web-app/dist/assets/{chevron-left-C5wZb7mg.js → chevron-left-D09jDZ29.js} +1 -1
- package/web-app/dist/assets/{circle-alert-BCb173fK.js → circle-alert-DyZMLOcC.js} +1 -1
- package/web-app/dist/assets/{clock-CvcqL3Ma.js → clock-CdpdJTId.js} +1 -1
- package/web-app/dist/assets/{cloud-CW1r3-AN.js → cloud-CoLTlmoj.js} +1 -1
- package/web-app/dist/assets/{code-xml-uxBTXAfZ.js → code-xml-DrkE2ZZY.js} +1 -1
- package/web-app/dist/assets/{database-DBrvW0o1.js → database-CMQQuM2R.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-WrOyoDcN.js → dollar-sign-CuioDaOG.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-8JC8TI2_.js → file-code-corner-Cx9j9K1S.js} +1 -1
- package/web-app/dist/assets/{file-plus-CgUdOr2u.js → file-plus-CYD2zaaI.js} +1 -1
- package/web-app/dist/assets/{globe-w-0x46nt.js → globe-6w-hnQkL.js} +1 -1
- package/web-app/dist/assets/{hammer-Civ6_wQq.js → hammer-BpqtautL.js} +1 -1
- package/web-app/dist/assets/{index-wvGJotyF.js → index-Dzs-9EgJ.js} +3 -3
- package/web-app/dist/assets/{layers-Cc3wLG39.js → layers-D5hd7jb7.js} +1 -1
- package/web-app/dist/assets/{loader-circle-BedHqfS4.js → loader-circle-C6Y9x_7H.js} +1 -1
- package/web-app/dist/assets/{lock-CRyzk3QG.js → lock-D51L7rYh.js} +1 -1
- package/web-app/dist/assets/{package-DCT1v7W2.js → package-BJF8hkP8.js} +1 -1
- package/web-app/dist/assets/{plus-C7eerOb_.js → plus-D5kUQ79n.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-DO753Y_B.js → refresh-cw-BKdlCa7F.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-ptlyGV4I.js → rotate-ccw-_bd_YI1e.js} +1 -1
- package/web-app/dist/assets/{scroll-text-CqPwk4yI.js → scroll-text-DbkegIxF.js} +1 -1
- package/web-app/dist/assets/{server-BP_VvY9m.js → server-DAwqFIsq.js} +1 -1
- package/web-app/dist/assets/{shield-alert-Bf0waB0a.js → shield-alert-BuR1tPHu.js} +1 -1
- package/web-app/dist/assets/{thumbs-up-p1roSIsw.js → thumbs-up-BF6p3mRd.js} +1 -1
- package/web-app/dist/assets/{trash-2-BrMaXAxl.js → trash-2-BVpnkqqz.js} +1 -1
- package/web-app/dist/assets/{trending-down-DaGcCn2W.js → trending-down-DpsDvRwW.js} +1 -1
- package/web-app/dist/assets/{trending-up-CKPupOSE.js → trending-up-mFogTjt3.js} +1 -1
- package/web-app/dist/assets/{upload-CR0gzibW.js → upload-4T8zpBsi.js} +1 -1
- package/web-app/dist/assets/{usePolling-kKzJRjFw.js → usePolling-DPXQv8WO.js} +1 -1
- package/web-app/dist/assets/{user-CPOXPTtD.js → user-BYGIFaTm.js} +1 -1
- package/web-app/dist/index.html +1 -1
|
@@ -7618,6 +7618,12 @@ function withheldSentinel(fresh = false) {
|
|
|
7618
7618
|
sentinel = null;
|
|
7619
7619
|
return sentinel ??= `${SENTINEL_PREFIX}${process.pid}${randomBytes2(8).toString("hex")}INVALID`;
|
|
7620
7620
|
}
|
|
7621
|
+
function removeConfigDir() {
|
|
7622
|
+
if (configDir)
|
|
7623
|
+
try {
|
|
7624
|
+
rmSync4(configDir, { recursive: true, force: true });
|
|
7625
|
+
} catch {}
|
|
7626
|
+
}
|
|
7621
7627
|
function emptyGhConfigDir(env = process.env) {
|
|
7622
7628
|
if (configDir && isDir(configDir))
|
|
7623
7629
|
return configDir;
|
|
@@ -7630,11 +7636,14 @@ function emptyGhConfigDir(env = process.env) {
|
|
|
7630
7636
|
}
|
|
7631
7637
|
if (!exitHooked) {
|
|
7632
7638
|
exitHooked = true;
|
|
7633
|
-
process.once("exit",
|
|
7634
|
-
|
|
7635
|
-
|
|
7636
|
-
|
|
7637
|
-
|
|
7639
|
+
process.once("exit", removeConfigDir);
|
|
7640
|
+
}
|
|
7641
|
+
if (!sigtermHooked) {
|
|
7642
|
+
sigtermHooked = true;
|
|
7643
|
+
process.prependListener("SIGTERM", () => {
|
|
7644
|
+
removeConfigDir();
|
|
7645
|
+
if (process.listenerCount("SIGTERM") === 1)
|
|
7646
|
+
process.exit(143);
|
|
7638
7647
|
});
|
|
7639
7648
|
}
|
|
7640
7649
|
return configDir;
|
|
@@ -7654,10 +7663,13 @@ function hardenCredentialEnv(env, fresh = false) {
|
|
|
7654
7663
|
env[`GIT_CONFIG_VALUE_${n2}`] = "";
|
|
7655
7664
|
env["GIT_CONFIG_COUNT"] = String(n2 + 1);
|
|
7656
7665
|
}
|
|
7666
|
+
const params = env["GIT_CONFIG_PARAMETERS"];
|
|
7667
|
+
if (params && !params.trimEnd().endsWith("'credential.helper'=''"))
|
|
7668
|
+
env["GIT_CONFIG_PARAMETERS"] = `${params} 'credential.helper'=''`;
|
|
7657
7669
|
env["GIT_TERMINAL_PROMPT"] = "0";
|
|
7658
7670
|
env["GCM_INTERACTIVE"] = "never";
|
|
7659
7671
|
}
|
|
7660
|
-
var GITHUB_TOKEN_VARS, SENTINEL_PREFIX = "ghp_LOKIWITHHELDsentinel", sentinel = null, configDir = null, exitHooked = false, isDir = (p) => {
|
|
7672
|
+
var GITHUB_TOKEN_VARS, SENTINEL_PREFIX = "ghp_LOKIWITHHELDsentinel", sentinel = null, configDir = null, exitHooked = false, sigtermHooked = false, isDir = (p) => {
|
|
7661
7673
|
try {
|
|
7662
7674
|
return !lstatSync4(p).isSymbolicLink() && statSync12(p).isDirectory();
|
|
7663
7675
|
} catch {
|
|
@@ -9110,6 +9122,37 @@ var init_claude_model = __esm(() => {
|
|
|
9110
9122
|
FAMILY_ALIASES = new Set(["haiku", "sonnet", "opus", "fable"]);
|
|
9111
9123
|
});
|
|
9112
9124
|
|
|
9125
|
+
// ../../loki-ts/src/providers/price_table.ts
|
|
9126
|
+
var PRICE_TABLE;
|
|
9127
|
+
var init_price_table = __esm(() => {
|
|
9128
|
+
PRICE_TABLE = Object.freeze({
|
|
9129
|
+
"gpt-5.3-codex": { input: 1.75, output: 14, cachedInput: 0.175 },
|
|
9130
|
+
"gpt-5-codex": { input: 1.25, output: 10, cachedInput: 0.125 },
|
|
9131
|
+
"gpt-5": { input: 1.25, output: 10, cachedInput: 0.125 },
|
|
9132
|
+
"gpt-4.1": { input: 2, output: 8, cachedInput: 0.5 },
|
|
9133
|
+
"gpt-4o": { input: 2.5, output: 10, cachedInput: 1.25 },
|
|
9134
|
+
"o4-mini": { input: 1.1, output: 4.4, cachedInput: 0.275 },
|
|
9135
|
+
o3: { input: 2, output: 8, cachedInput: 0.5 },
|
|
9136
|
+
"claude-opus": { input: 4, output: 20, cachedInput: 0.2 },
|
|
9137
|
+
"claude-sonnet": { input: 2, output: 10, cachedInput: 0.1 },
|
|
9138
|
+
"claude-haiku": { input: 0.1, output: 0.5, cachedInput: 0.01 },
|
|
9139
|
+
"gemini-2.5-pro": { input: 1.25, output: 10 },
|
|
9140
|
+
gemini: { input: 1.25, output: 10 },
|
|
9141
|
+
deepseek: { input: 0.28, output: 0.42 },
|
|
9142
|
+
minimax: { input: 0.3, output: 1.2 }
|
|
9143
|
+
});
|
|
9144
|
+
});
|
|
9145
|
+
|
|
9146
|
+
// ../../loki-ts/src/providers/provider_cost.ts
|
|
9147
|
+
var ESTIMATED_PROVIDERS;
|
|
9148
|
+
var init_provider_cost = __esm(() => {
|
|
9149
|
+
init_price_table();
|
|
9150
|
+
ESTIMATED_PROVIDERS = new Set(["codex", "cline", "aider"]);
|
|
9151
|
+
});
|
|
9152
|
+
|
|
9153
|
+
// ../../loki-ts/src/providers/model_routes.ts
|
|
9154
|
+
var init_model_routes = () => {};
|
|
9155
|
+
|
|
9113
9156
|
// ../../loki-ts/src/engine10/hooks.ts
|
|
9114
9157
|
var hooks;
|
|
9115
9158
|
var init_hooks = __esm(() => {
|
|
@@ -9222,6 +9265,8 @@ function sumResultCosts(lokiRoot, iterations, context = []) {
|
|
|
9222
9265
|
const unmetered = rec2["source"] === UNMETERED;
|
|
9223
9266
|
if (unmetered)
|
|
9224
9267
|
out2.unmetered = true;
|
|
9268
|
+
if (rec2["estimated"] === true)
|
|
9269
|
+
out2.estimated = true;
|
|
9225
9270
|
const noUsage = !unmetered && inTok === 0 && outTok === 0 && cacheR === 0 && cacheC === 0;
|
|
9226
9271
|
if (typeof c !== "number" || !Number.isFinite(c) || noUsage) {
|
|
9227
9272
|
out2.missing.push(iter);
|
|
@@ -9347,14 +9392,23 @@ var init_session_route = __esm(() => {
|
|
|
9347
9392
|
init_unit_model();
|
|
9348
9393
|
});
|
|
9349
9394
|
|
|
9395
|
+
// ../../loki-ts/src/providers/provider_guard.ts
|
|
9396
|
+
var init_provider_guard = __esm(() => {
|
|
9397
|
+
init_safe_git();
|
|
9398
|
+
});
|
|
9399
|
+
|
|
9350
9400
|
// ../../loki-ts/src/engine10/session.ts
|
|
9351
9401
|
var STDERR_TAIL_BYTES, ADVISOR_MARKED_STAGES;
|
|
9352
9402
|
var init_session = __esm(() => {
|
|
9403
|
+
init_provider_cost();
|
|
9404
|
+
init_model_routes();
|
|
9353
9405
|
init_cost();
|
|
9354
9406
|
init_budget();
|
|
9355
9407
|
init_hooks();
|
|
9356
9408
|
init_session_route();
|
|
9357
9409
|
init_safe_git();
|
|
9410
|
+
init_provider_guard();
|
|
9411
|
+
init_budget();
|
|
9358
9412
|
STDERR_TAIL_BYTES = 64 * 1024;
|
|
9359
9413
|
ADVISOR_MARKED_STAGES = new Set(["plan", "fix"]);
|
|
9360
9414
|
});
|
|
@@ -11807,6 +11861,55 @@ var init_visual_evidence = __esm(() => {
|
|
|
11807
11861
|
PAGE_RE = /^(?:.*\/)?(?:app|pages|src|public)\/.*\.(?:html|jsx|tsx|vue|svelte)$/;
|
|
11808
11862
|
});
|
|
11809
11863
|
|
|
11864
|
+
// ../../loki-ts/src/council/role_agents.ts
|
|
11865
|
+
import { existsSync as existsSync27, readdirSync as readdirSync15, readFileSync as readFileSync33 } from "fs";
|
|
11866
|
+
import { homedir as homedir8 } from "os";
|
|
11867
|
+
import { join as join47 } from "path";
|
|
11868
|
+
function agentDirs(repoDir, env = process.env) {
|
|
11869
|
+
return [env["LOKI_AGENTS_PROJECT_DIR"] || join47(repoDir, ".loki", "agents"), env["LOKI_AGENTS_USER_DIR"] || join47(env["XDG_CONFIG_HOME"] || join47(homedir8(), ".config"), "loki", "agents")];
|
|
11870
|
+
}
|
|
11871
|
+
function parseAgent(file, stem2) {
|
|
11872
|
+
const m = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/.exec(readFileSync33(file, "utf8"));
|
|
11873
|
+
if (!m)
|
|
11874
|
+
return null;
|
|
11875
|
+
const body = m[2].trim();
|
|
11876
|
+
if (!body)
|
|
11877
|
+
return null;
|
|
11878
|
+
const kv = (k) => new RegExp(`^${k}:[ \\t]*["']?([^"'\\r\\n#]*?)["']?[ \\t]*$`, "m").exec(m[1])?.[1]?.trim() ?? "";
|
|
11879
|
+
const role = (kv("role") || stem2).toLowerCase();
|
|
11880
|
+
if (!ROLES.includes(role))
|
|
11881
|
+
return null;
|
|
11882
|
+
return { name: kv("name") || stem2, role, provider: (kv("provider") || "claude").toLowerCase(), model: kv("model"), prompt: body.slice(0, 4000) };
|
|
11883
|
+
}
|
|
11884
|
+
function loadRoleAgent(repoDir, role, env = process.env) {
|
|
11885
|
+
for (const d of agentDirs(repoDir, env)) {
|
|
11886
|
+
if (!existsSync27(d))
|
|
11887
|
+
continue;
|
|
11888
|
+
let names;
|
|
11889
|
+
try {
|
|
11890
|
+
names = readdirSync15(d).filter((n2) => n2.endsWith(".md") && !n2.startsWith(".")).sort();
|
|
11891
|
+
} catch {
|
|
11892
|
+
continue;
|
|
11893
|
+
}
|
|
11894
|
+
const ordered = [...names.filter((n2) => n2 === `${role}.md`), ...names.filter((n2) => n2 !== `${role}.md`)];
|
|
11895
|
+
for (const n2 of ordered) {
|
|
11896
|
+
try {
|
|
11897
|
+
const a = parseAgent(join47(d, n2), n2.slice(0, -3));
|
|
11898
|
+
if (a && a.role === role)
|
|
11899
|
+
return a;
|
|
11900
|
+
} catch {}
|
|
11901
|
+
}
|
|
11902
|
+
}
|
|
11903
|
+
return null;
|
|
11904
|
+
}
|
|
11905
|
+
function loadReviewStageAgent(repoDir, env = process.env) {
|
|
11906
|
+
return loadRoleAgent(repoDir, "reviewer", env) ?? loadRoleAgent(repoDir, "council", env);
|
|
11907
|
+
}
|
|
11908
|
+
var ROLES;
|
|
11909
|
+
var init_role_agents = __esm(() => {
|
|
11910
|
+
ROLES = ["council", "reviewer"];
|
|
11911
|
+
});
|
|
11912
|
+
|
|
11810
11913
|
// ../../loki-ts/src/util/shell.ts
|
|
11811
11914
|
async function readStreamCapped(stream, maxBytes = MAX_STDOUT_BYTES) {
|
|
11812
11915
|
const reader = stream.getReader();
|
|
@@ -11879,8 +11982,8 @@ var init_shell = __esm(() => {
|
|
|
11879
11982
|
});
|
|
11880
11983
|
|
|
11881
11984
|
// ../../loki-ts/src/engine10/stages/xreview.ts
|
|
11882
|
-
import { existsSync as
|
|
11883
|
-
import { join as
|
|
11985
|
+
import { existsSync as existsSync28, readFileSync as readFileSync34 } from "fs";
|
|
11986
|
+
import { join as join48 } from "path";
|
|
11884
11987
|
function minVerdict(verdict, review) {
|
|
11885
11988
|
const ceiling = review?.level === "block" ? "PARTIAL" : verdict;
|
|
11886
11989
|
return RANK2[ceiling] < RANK2[verdict] ? ceiling : verdict;
|
|
@@ -11893,11 +11996,11 @@ function explicitReview(repoDir, env) {
|
|
|
11893
11996
|
if (flag && isOff(ev))
|
|
11894
11997
|
return "off";
|
|
11895
11998
|
for (const f of ["loki.yaml", "loki.yml"]) {
|
|
11896
|
-
const p =
|
|
11897
|
-
if (!
|
|
11999
|
+
const p = join48(repoDir, f);
|
|
12000
|
+
if (!existsSync28(p))
|
|
11898
12001
|
continue;
|
|
11899
12002
|
try {
|
|
11900
|
-
const m = /^review:[ \t]*["']?([A-Za-z]+)["']?[ \t]*(?:#.*)?$/m.exec(
|
|
12003
|
+
const m = /^review:[ \t]*["']?([A-Za-z]+)["']?[ \t]*(?:#.*)?$/m.exec(readFileSync34(p, "utf8"));
|
|
11901
12004
|
const v = asProvider(m?.[1]);
|
|
11902
12005
|
if (v)
|
|
11903
12006
|
return v;
|
|
@@ -11926,9 +12029,15 @@ function reviewReceipt(builder, xr, env = process.env) {
|
|
|
11926
12029
|
const differs = !!xr && xr.level !== "not_run" && xr.provider !== builder && (builder === "claude" || builder === "codex");
|
|
11927
12030
|
return { review: { provider: xr?.provider ?? null, vendor_differs: differs }, notProven: differs ? [] : ["judge shares builder vendor or none configured"] };
|
|
11928
12031
|
}
|
|
11929
|
-
function reviewArgv(p, prompt, env = process.env) {
|
|
12032
|
+
function reviewArgv(p, prompt, env = process.env, model) {
|
|
11930
12033
|
const cli = reviewCli(p, env);
|
|
11931
|
-
return p === "codex" ? [cli, "exec", "--sandbox", "read-only", prompt] : [cli, "-p", prompt, "--allowedTools", "Read,Grep,Glob", "--disallowedTools", "Write,Edit,NotebookEdit,Bash"];
|
|
12034
|
+
return p === "codex" ? [cli, "exec", "--sandbox", "read-only", prompt] : [cli, "-p", prompt, ...model ? ["--model", model] : [], "--allowedTools", "Read,Grep,Glob", "--disallowedTools", "Write,Edit,NotebookEdit,Bash"];
|
|
12035
|
+
}
|
|
12036
|
+
function configuredReviewer(repoDir, env = process.env) {
|
|
12037
|
+
const a = loadReviewStageAgent(repoDir, env);
|
|
12038
|
+
if (!a)
|
|
12039
|
+
return null;
|
|
12040
|
+
return { prompt: a.prompt, model: a.provider === "claude" ? a.model : "", provider: asProvider(a.provider) };
|
|
11932
12041
|
}
|
|
11933
12042
|
function parseReview(p, out2) {
|
|
11934
12043
|
const m = /^\s*VERDICT:\s*(PASS|FLAG|BLOCK)\b/im.exec(out2);
|
|
@@ -11941,7 +12050,8 @@ function parseReview(p, out2) {
|
|
|
11941
12050
|
return { provider: p, level, notes };
|
|
11942
12051
|
}
|
|
11943
12052
|
async function crossReview(ctx, verdict, head) {
|
|
11944
|
-
const
|
|
12053
|
+
const agent = configuredReviewer(ctx.repoDir);
|
|
12054
|
+
const p = reviewProvider(ctx.repoDir, process.env, ctx.provider) ?? agent?.provider ?? null;
|
|
11945
12055
|
if (!p)
|
|
11946
12056
|
return null;
|
|
11947
12057
|
if (verdict !== "VERIFIED" && verdict !== "ALREADY_SATISFIED")
|
|
@@ -11950,7 +12060,8 @@ async function crossReview(ctx, verdict, head) {
|
|
|
11950
12060
|
if (!d || d.exitCode !== 0)
|
|
11951
12061
|
return { provider: p, level: "not_run", notes: [`cross-review not run: diff unavailable for ${p}`] };
|
|
11952
12062
|
const prompt = [
|
|
11953
|
-
|
|
12063
|
+
agent ? `${agent.prompt}
|
|
12064
|
+
Review ONLY the diff below. Do not modify files.` : "You are an independent code reviewer. Review ONLY the diff below for correctness bugs, spec violations and missing tests. Do not modify files.",
|
|
11954
12065
|
"Reply with a first line exactly `VERDICT: PASS`, `VERDICT: FLAG` (concerns) or `VERDICT: BLOCK` (a reproduced defect), then one `- reason` line per concern.",
|
|
11955
12066
|
"",
|
|
11956
12067
|
"```diff",
|
|
@@ -11959,7 +12070,7 @@ async function crossReview(ctx, verdict, head) {
|
|
|
11959
12070
|
].join(`
|
|
11960
12071
|
`);
|
|
11961
12072
|
try {
|
|
11962
|
-
const r = await run(reviewArgv(p, prompt), { cwd: ctx.repoDir, timeoutMs: TIMEOUT_MS });
|
|
12073
|
+
const r = await run(reviewArgv(p, prompt, process.env, p === "claude" ? agent?.model : undefined), { cwd: ctx.repoDir, timeoutMs: TIMEOUT_MS });
|
|
11963
12074
|
if (r.exitCode !== 0)
|
|
11964
12075
|
return { provider: p, level: "not_run", notes: [`cross-review not run: ${p} exited ${r.exitCode}`] };
|
|
11965
12076
|
return parseReview(p, r.stdout);
|
|
@@ -11972,20 +12083,21 @@ var RANK2, TIMEOUT_MS = 300000, MAX_DIFF = 60000, MAX_NOTES = 5, asProvider = (v
|
|
|
11972
12083
|
return s === "codex" || s === "claude" ? s : null;
|
|
11973
12084
|
}, isOff = (v) => /^(off|none|false|0)$/i.test(v?.trim() ?? ""), xvendorDefault = (env = process.env) => /^(1|true|yes|on)$/i.test(env["LOKI_XVENDOR_DEFAULT"]?.trim() ?? ""), reviewCli = (p, env = process.env) => env[`LOKI_${p.toUpperCase()}_CLI`]?.trim() || p, clean3 = (s) => s.replace(/[\x00-\x1f\x7f`]+/g, " ").trim().slice(0, 300);
|
|
11974
12085
|
var init_xreview = __esm(() => {
|
|
12086
|
+
init_role_agents();
|
|
11975
12087
|
init_shell();
|
|
11976
12088
|
init_safe_git();
|
|
11977
12089
|
RANK2 = { FAILED: 0, SPEC_CONFLICT: 0, PARTIAL: 1, ALREADY_SATISFIED: 2, VERIFIED: 2 };
|
|
11978
12090
|
});
|
|
11979
12091
|
|
|
11980
12092
|
// ../../loki-ts/src/runner/router/route_record.ts
|
|
11981
|
-
import { existsSync as
|
|
11982
|
-
import { join as
|
|
12093
|
+
import { existsSync as existsSync29, lstatSync as lstatSync11, mkdirSync as mkdirSync15, readFileSync as readFileSync35, writeFileSync as writeFileSync16 } from "fs";
|
|
12094
|
+
import { join as join49 } from "path";
|
|
11983
12095
|
function loadRouteRecord(runDir) {
|
|
11984
12096
|
try {
|
|
11985
|
-
const p =
|
|
11986
|
-
if (!
|
|
12097
|
+
const p = join49(runDir, ROUTE_RECORD_FILE);
|
|
12098
|
+
if (!existsSync29(p) || !lstatSync11(p).isFile())
|
|
11987
12099
|
return;
|
|
11988
|
-
const v = JSON.parse(
|
|
12100
|
+
const v = JSON.parse(readFileSync35(p, "utf8"));
|
|
11989
12101
|
return typeof v === "object" && v !== null && !Array.isArray(v) ? v : undefined;
|
|
11990
12102
|
} catch {
|
|
11991
12103
|
return;
|
|
@@ -11995,7 +12107,7 @@ var ROUTE_RECORD_FILE = "route.json";
|
|
|
11995
12107
|
var init_route_record = () => {};
|
|
11996
12108
|
|
|
11997
12109
|
// ../../loki-ts/src/runner/router/history_store.ts
|
|
11998
|
-
import { existsSync as
|
|
12110
|
+
import { existsSync as existsSync30, mkdirSync as mkdirSync16, readFileSync as readFileSync36, renameSync as renameSync6, rmSync as rmSync12, writeFileSync as writeFileSync17 } from "fs";
|
|
11999
12111
|
import { resolve as resolve11 } from "path";
|
|
12000
12112
|
function validRun(v) {
|
|
12001
12113
|
if (typeof v !== "object" || v === null)
|
|
@@ -12008,10 +12120,10 @@ function historyPath(key, cacheRoot) {
|
|
|
12008
12120
|
}
|
|
12009
12121
|
function readRunHistory(key, cacheRoot) {
|
|
12010
12122
|
const path = historyPath(key, cacheRoot);
|
|
12011
|
-
if (!
|
|
12123
|
+
if (!existsSync30(path))
|
|
12012
12124
|
return [];
|
|
12013
12125
|
try {
|
|
12014
|
-
const parsed = JSON.parse(
|
|
12126
|
+
const parsed = JSON.parse(readFileSync36(path, "utf8"));
|
|
12015
12127
|
const runs2 = parsed?.runs;
|
|
12016
12128
|
return Array.isArray(runs2) ? runs2.filter(validRun) : [];
|
|
12017
12129
|
} catch {
|
|
@@ -12045,7 +12157,7 @@ var init_history_store = __esm(() => {
|
|
|
12045
12157
|
});
|
|
12046
12158
|
|
|
12047
12159
|
// ../../loki-ts/src/runner/router/history.ts
|
|
12048
|
-
import { dirname as dirname21, join as
|
|
12160
|
+
import { dirname as dirname21, join as join50, resolve as resolve12 } from "path";
|
|
12049
12161
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
12050
12162
|
function shapeKey(model) {
|
|
12051
12163
|
if (!model || model.status !== "ok")
|
|
@@ -12123,8 +12235,8 @@ var init_cost_preview = __esm(() => {
|
|
|
12123
12235
|
});
|
|
12124
12236
|
|
|
12125
12237
|
// ../../loki-ts/src/features/contract.ts
|
|
12126
|
-
import { existsSync as
|
|
12127
|
-
import { dirname as dirname22, join as
|
|
12238
|
+
import { existsSync as existsSync31, lstatSync as lstatSync12, mkdirSync as mkdirSync17, readFileSync as readFileSync37, writeFileSync as writeFileSync18 } from "fs";
|
|
12239
|
+
import { dirname as dirname22, join as join51 } from "path";
|
|
12128
12240
|
import { createHash as createHash11 } from "crypto";
|
|
12129
12241
|
function contractEnabled(env = process.env) {
|
|
12130
12242
|
return env["LOKI_CONTRACT"] !== "0";
|
|
@@ -12156,7 +12268,7 @@ function untracedLines(trace) {
|
|
|
12156
12268
|
return trace.criteria.filter((c) => c.status === "no_match").map((c) => `contract ${sanitizeCriterion(c.id)} untraced (no keyword match in changed files): ${sanitizeCriterion(c.text)}`);
|
|
12157
12269
|
}
|
|
12158
12270
|
function contractPath(repoDir) {
|
|
12159
|
-
return
|
|
12271
|
+
return join51(repoDir, ".loki", "contract.json");
|
|
12160
12272
|
}
|
|
12161
12273
|
function readContract(repoDir) {
|
|
12162
12274
|
const p = contractPath(repoDir);
|
|
@@ -12172,7 +12284,7 @@ function readContract(repoDir) {
|
|
|
12172
12284
|
return { contract: null, notes: ["contract unreadable: too large"], sha256: null };
|
|
12173
12285
|
let raw2;
|
|
12174
12286
|
try {
|
|
12175
|
-
raw2 =
|
|
12287
|
+
raw2 = readFileSync37(p);
|
|
12176
12288
|
} catch {
|
|
12177
12289
|
return { contract: null, notes: ["contract unreadable: read failed"], sha256: null };
|
|
12178
12290
|
}
|
|
@@ -12241,8 +12353,8 @@ var init_contract = __esm(() => {
|
|
|
12241
12353
|
});
|
|
12242
12354
|
|
|
12243
12355
|
// ../../loki-ts/src/supply/supply_guard.ts
|
|
12244
|
-
import { existsSync as
|
|
12245
|
-
import { basename as basename17, join as
|
|
12356
|
+
import { existsSync as existsSync32, readFileSync as readFileSync38, statSync as statSync16 } from "fs";
|
|
12357
|
+
import { basename as basename17, join as join52 } from "path";
|
|
12246
12358
|
function supplyEnabled(env) {
|
|
12247
12359
|
return env["LOKI_SUPPLY_GUARD"] !== "0";
|
|
12248
12360
|
}
|
|
@@ -12257,13 +12369,13 @@ function isManifestPath(path) {
|
|
|
12257
12369
|
return MANIFESTS.test(basename17(path));
|
|
12258
12370
|
}
|
|
12259
12371
|
function readDeclared(repoDir) {
|
|
12260
|
-
const p =
|
|
12261
|
-
if (!
|
|
12372
|
+
const p = join52(repoDir, ".loki", DECLARED_FILE);
|
|
12373
|
+
if (!existsSync32(p))
|
|
12262
12374
|
return { deps: [], problem: null };
|
|
12263
12375
|
try {
|
|
12264
12376
|
if (statSync16(p).size > MAX_FILE_BYTES)
|
|
12265
12377
|
return { deps: [], problem: "declaration file too large" };
|
|
12266
|
-
const j = JSON.parse(
|
|
12378
|
+
const j = JSON.parse(readFileSync38(p, "utf8"));
|
|
12267
12379
|
if (!Array.isArray(j))
|
|
12268
12380
|
return { deps: [], problem: "declaration is not a JSON array" };
|
|
12269
12381
|
const deps = [];
|
|
@@ -12283,11 +12395,11 @@ function readDeclared(repoDir) {
|
|
|
12283
12395
|
}
|
|
12284
12396
|
}
|
|
12285
12397
|
function readAllowlist(repoDir) {
|
|
12286
|
-
const p =
|
|
12287
|
-
if (!
|
|
12398
|
+
const p = join52(repoDir, ".loki", "supply-allowlist");
|
|
12399
|
+
if (!existsSync32(p))
|
|
12288
12400
|
return new Set;
|
|
12289
12401
|
try {
|
|
12290
|
-
return new Set(
|
|
12402
|
+
return new Set(readFileSync38(p, "utf8").split(`
|
|
12291
12403
|
`).map((l) => l.replace(/#.*$/, "").trim()).filter(Boolean));
|
|
12292
12404
|
} catch {
|
|
12293
12405
|
return new Set;
|
|
@@ -12328,7 +12440,7 @@ function userConfigPrivate(eco, repoDir, env, goEnv = {}, name = "") {
|
|
|
12328
12440
|
if (eco === "npm") {
|
|
12329
12441
|
if (privateUrlList([env["npm_config_registry"] ?? "", env["NPM_CONFIG_REGISTRY"] ?? ""]))
|
|
12330
12442
|
return true;
|
|
12331
|
-
return files(
|
|
12443
|
+
return files(join52(repoDir, ".npmrc"), home ? join52(home, ".npmrc") : "").some((t) => t.split(`
|
|
12332
12444
|
`).some((l) => {
|
|
12333
12445
|
const m = /^\s*(?:(@[^:\s]+):)?registry\s*=\s*(\S+)/.exec(l);
|
|
12334
12446
|
return !!m && privateUrl(m[2]) && (m[1] === undefined || name.startsWith(`${m[1]}/`));
|
|
@@ -12337,7 +12449,7 @@ function userConfigPrivate(eco, repoDir, env, goEnv = {}, name = "") {
|
|
|
12337
12449
|
if (eco === "pypi") {
|
|
12338
12450
|
if (privateUrlList([env["PIP_INDEX_URL"] ?? "", env["PIP_EXTRA_INDEX_URL"] ?? ""]))
|
|
12339
12451
|
return true;
|
|
12340
|
-
const cfg = [env["PIP_CONFIG_FILE"] ?? "", home ?
|
|
12452
|
+
const cfg = [env["PIP_CONFIG_FILE"] ?? "", home ? join52(home, ".config", "pip", "pip.conf") : "", home ? join52(home, ".pip", "pip.conf") : "", join52(repoDir, "pip.conf")];
|
|
12341
12453
|
return files(...cfg).some((t) => t.split(`
|
|
12342
12454
|
`).some((l) => {
|
|
12343
12455
|
const m = /^\s*(?:extra-)?index-url\s*[=:]\s*(.+)$/.exec(l);
|
|
@@ -12347,7 +12459,7 @@ function userConfigPrivate(eco, repoDir, env, goEnv = {}, name = "") {
|
|
|
12347
12459
|
if (eco === "cargo") {
|
|
12348
12460
|
if (Object.keys(env).some((k) => /^CARGO_REGISTRIES_.+_INDEX$/.test(k) || /^CARGO_SOURCE_/.test(k)))
|
|
12349
12461
|
return true;
|
|
12350
|
-
return files(
|
|
12462
|
+
return files(join52(repoDir, ".cargo", "config.toml"), join52(repoDir, ".cargo", "config"), home ? join52(home, ".cargo", "config.toml") : "", home ? join52(home, ".cargo", "config") : "").some((t) => /^\s*\[(registries\.|source\.)/m.test(t) || /replace-with\s*=/.test(t));
|
|
12351
12463
|
}
|
|
12352
12464
|
if (eco === "go") {
|
|
12353
12465
|
const gp = goEnv["GOPROXY"] ?? env["GOPROXY"] ?? "";
|
|
@@ -12470,7 +12582,7 @@ var MIN_AGE_DAYS = 7, DECLARED_FILE = "supply-declared.json", MAX_CHECKED = 50,
|
|
|
12470
12582
|
}
|
|
12471
12583
|
}, PUBLIC_HOSTS, hostOf = (u) => u.trim().toLowerCase().replace(/^[a-z][a-z0-9+.-]*:\/\//, "").replace(/^www\./, "").split(/[/?#\s]/)[0].replace(/^[^@]*@/, "").replace(/:\d+$/, ""), privateUrl = (u) => /[a-z0-9]/i.test(u) && !PUBLIC_HOSTS.has(hostOf(u)), readSmall = (p) => {
|
|
12472
12584
|
try {
|
|
12473
|
-
return statSync16(p).size <= MAX_FILE_BYTES ?
|
|
12585
|
+
return statSync16(p).size <= MAX_FILE_BYTES ? readFileSync38(p, "utf8") : "";
|
|
12474
12586
|
} catch {
|
|
12475
12587
|
return "";
|
|
12476
12588
|
}
|
|
@@ -12486,9 +12598,9 @@ var init_supply_guard = __esm(() => {
|
|
|
12486
12598
|
|
|
12487
12599
|
// ../../loki-ts/src/engine10/stages/seal.ts
|
|
12488
12600
|
import { createHash as createHash12, randomBytes as randomBytes4, createPrivateKey, createPublicKey as createPublicKey2, generateKeyPairSync, sign } from "crypto";
|
|
12489
|
-
import { chmodSync as chmodSync5, existsSync as
|
|
12490
|
-
import { homedir as
|
|
12491
|
-
import { basename as basename18, dirname as dirname23, isAbsolute as isAbsolute8, join as
|
|
12601
|
+
import { chmodSync as chmodSync5, existsSync as existsSync33, readdirSync as readdirSync16, linkSync, mkdirSync as mkdirSync18, readFileSync as readFileSync39, statSync as statSync17, unlinkSync as unlinkSync5, writeFileSync as writeFileSync19 } from "fs";
|
|
12602
|
+
import { homedir as homedir9 } from "os";
|
|
12603
|
+
import { basename as basename18, dirname as dirname23, isAbsolute as isAbsolute8, join as join53, relative as relative9 } from "path";
|
|
12492
12604
|
function sanitizeReason(s) {
|
|
12493
12605
|
const collapsed = s.replace(/[\x00-\x1f\x7f]+/g, " ").trim();
|
|
12494
12606
|
const capped = collapsed.length > 500 ? `${collapsed.slice(0, 500)}...` : collapsed;
|
|
@@ -12516,9 +12628,9 @@ function loadSigningKey(generate = true) {
|
|
|
12516
12628
|
let pem = inline ?? "";
|
|
12517
12629
|
if (!inline) {
|
|
12518
12630
|
const given = process.env["LOKI_RECEIPT_SIGNING_KEY_FILE"]?.trim();
|
|
12519
|
-
const file = given ||
|
|
12631
|
+
const file = given || join53(process.env["HOME"] || homedir9(), ".loki", "keys", RECEIPT_SIGNER_BASENAME);
|
|
12520
12632
|
try {
|
|
12521
|
-
pem =
|
|
12633
|
+
pem = readFileSync39(file);
|
|
12522
12634
|
if (!given) {
|
|
12523
12635
|
for (const [f, m] of [[file, 384], [dirname23(file), 448]])
|
|
12524
12636
|
if (statSync17(f).mode & 63)
|
|
@@ -12539,7 +12651,7 @@ function loadSigningKey(generate = true) {
|
|
|
12539
12651
|
} finally {
|
|
12540
12652
|
unlinkSync5(tmp);
|
|
12541
12653
|
}
|
|
12542
|
-
pem =
|
|
12654
|
+
pem = readFileSync39(file);
|
|
12543
12655
|
}
|
|
12544
12656
|
}
|
|
12545
12657
|
const k = createPrivateKey(pem);
|
|
@@ -12621,7 +12733,7 @@ function renderReceiptMd(r) {
|
|
|
12621
12733
|
`);
|
|
12622
12734
|
}
|
|
12623
12735
|
var DEEP_NOT_PROVEN, SIGNING_UNAVAILABLE = "receipt signing unavailable (no usable signing key: invalid key or unwritable ~/.loki/keys)", EXCLUDE_LOKI = ":(exclude).loki", sha2562 = (s) => createHash12("sha256").update(s).digest("hex"), str3 = (v) => typeof v === "string" && v !== "" ? v : null, strs2 = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string" && x !== "") : [], b64u = (b) => Buffer.from(b).toString("base64url"), kidOf2 = (pub) => b64u(createHash12("sha256").update(`{"crv":"Ed25519","kty":"OKP","x":"${pub.export({ format: "jwk" }).x}"}`).digest()), receiptRel = (ctx) => {
|
|
12624
|
-
const r = relative9(ctx.repoDir,
|
|
12736
|
+
const r = relative9(ctx.repoDir, join53(ctx.runDir, "receipt.json"));
|
|
12625
12737
|
return r.startsWith("..") || isAbsolute8(r) ? `.loki/runs/${ctx.runId}/receipt.json` : r;
|
|
12626
12738
|
}, commitStage, sealStage;
|
|
12627
12739
|
var init_seal = __esm(() => {
|
|
@@ -12724,7 +12836,7 @@ var init_seal = __esm(() => {
|
|
|
12724
12836
|
if (wallNotRun > 0) {
|
|
12725
12837
|
try {
|
|
12726
12838
|
const kept = new Set((Array.isArray(o.wall?.files) ? o.wall.files : []).map((f) => basename18(String(f.path))));
|
|
12727
|
-
for (const n2 of
|
|
12839
|
+
for (const n2 of readdirSync16(join53(ctx.runDir, "wall")).sort())
|
|
12728
12840
|
if (!kept.has(n2))
|
|
12729
12841
|
notProven.add(`wall test discarded: ${n2} (not_run)`);
|
|
12730
12842
|
} catch {}
|
|
@@ -12783,19 +12895,19 @@ var init_seal = __esm(() => {
|
|
|
12783
12895
|
for (const [s, d] of Object.entries(o))
|
|
12784
12896
|
if (typeof d?.duration_s === "number")
|
|
12785
12897
|
stages[s] = d.duration_s;
|
|
12786
|
-
const T = hooks.time, time = T ? T.build(ctx, stages, T.firstEventMs(
|
|
12898
|
+
const T = hooks.time, time = T ? T.build(ctx, stages, T.firstEventMs(join53(ctx.runDir, "events.jsonl"))) : { wall_s: Object.values(stages).reduce((a, b) => a + (b ?? 0), 0), stages }, totalS = T?.reconciled(time) ?? null;
|
|
12787
12899
|
const iterIds = Object.values(o).flatMap((d) => [...strs2(d?.iteration_ids), ...strs2([d?.iteration_id])]);
|
|
12788
12900
|
if (iterIds.length === 0)
|
|
12789
12901
|
notProven.add("cost not measured (no iteration ids recorded)");
|
|
12790
12902
|
const cost = ctx.cost.read(ctx.repoDir, iterIds);
|
|
12791
12903
|
if (cost.unmetered)
|
|
12792
12904
|
notProven.add("cost unmetered (CLI invoker; recorded as 0)");
|
|
12793
|
-
const eventsPath =
|
|
12905
|
+
const eventsPath = join53(ctx.runDir, "events.jsonl");
|
|
12794
12906
|
const wallFiles = Array.isArray(o.wall?.files) ? o.wall.files : [];
|
|
12795
12907
|
const wallPassed = typeof o.verify?.wall_passed === "boolean" ? o.verify.wall_passed : null;
|
|
12796
12908
|
if (wallFiles.length > 0 && wallPassed === null)
|
|
12797
12909
|
notProven.add("wall result not recorded by verify");
|
|
12798
|
-
const routeBlock = buildRouteBlock(process.env, ctx.provider, o.implement?.route ?? o.plan?.route_record ?? (routerEnabled(process.env) ? loadRouteRecord(ctx.runDir) : undefined), cost.router ?? (routerEnabled(process.env) ? sumResultCosts(
|
|
12910
|
+
const routeBlock = buildRouteBlock(process.env, ctx.provider, o.implement?.route ?? o.plan?.route_record ?? (routerEnabled(process.env) ? loadRouteRecord(ctx.runDir) : undefined), cost.router ?? (routerEnabled(process.env) ? sumResultCosts(join53(ctx.repoDir, ".loki"), iterIds).router : undefined), ctx.model);
|
|
12799
12911
|
if (routeBlock)
|
|
12800
12912
|
for (const l of routeNotProven(routeBlock))
|
|
12801
12913
|
notProven.add(l);
|
|
@@ -12837,7 +12949,7 @@ var init_seal = __esm(() => {
|
|
|
12837
12949
|
provider: ctx.provider,
|
|
12838
12950
|
model: ctx.model,
|
|
12839
12951
|
resumed: o.intake?.resumed === true,
|
|
12840
|
-
events_sha256: sha2562(
|
|
12952
|
+
events_sha256: sha2562(existsSync33(eventsPath) ? readFileSync39(eventsPath) : ""),
|
|
12841
12953
|
...await sealEvidence(ctx.repoDir, ctx.runDir, o, notProven, signal, ctx.emit),
|
|
12842
12954
|
log_seal: true,
|
|
12843
12955
|
...receiptBlock(process.env, cost.usd, cost.unmetered === true, totalS),
|
|
@@ -12858,10 +12970,10 @@ var init_seal = __esm(() => {
|
|
|
12858
12970
|
}
|
|
12859
12971
|
const receipt = { ...body, receipt_sha256: hash, verification: { jwt: sig.jwt, kid: sig.kid } };
|
|
12860
12972
|
mkdirSync18(ctx.runDir, { recursive: true });
|
|
12861
|
-
const path =
|
|
12973
|
+
const path = join53(ctx.runDir, "receipt.json");
|
|
12862
12974
|
writeFileSync19(path, JSON.stringify(receipt, null, 2) + `
|
|
12863
12975
|
`);
|
|
12864
|
-
writeFileSync19(
|
|
12976
|
+
writeFileSync19(join53(ctx.runDir, "receipt.md"), renderReceiptMd(receipt));
|
|
12865
12977
|
recordRun(process.env, ctx.repoDir, ctx.model, verdict, cost.usd, cost.unmetered === true, totalS);
|
|
12866
12978
|
const signed = sig.jwt !== null;
|
|
12867
12979
|
const data = { receipt_path: path, receipt_sha256: hash, signed, kid: sig.kid, verdict, not_proven: receipt.not_proven, ...mp ? { mutation_line: mp.line } : {}, ...routeBlock ? { route_line: routePrLine(routeBlock) } : {} };
|
|
@@ -12876,7 +12988,7 @@ var init_seal = __esm(() => {
|
|
|
12876
12988
|
|
|
12877
12989
|
// ../../loki-ts/src/features/receipt_dsse.ts
|
|
12878
12990
|
import { createPublicKey as createPublicKey3, sign as sign2, verify as verify2 } from "crypto";
|
|
12879
|
-
import { readFileSync as
|
|
12991
|
+
import { readFileSync as readFileSync40 } from "fs";
|
|
12880
12992
|
function pae(payloadType, body) {
|
|
12881
12993
|
const t = Buffer.from(payloadType, "utf8");
|
|
12882
12994
|
return Buffer.concat([Buffer.from(`DSSEv1 ${t.length} `), t, Buffer.from(` ${body.length} `), body]);
|
|
@@ -12953,7 +13065,7 @@ function verifyDsseReceipt(env, d) {
|
|
|
12953
13065
|
}
|
|
12954
13066
|
function runIdGuard(receiptPath, runId) {
|
|
12955
13067
|
try {
|
|
12956
|
-
return envelopeRunIdProblem(JSON.parse(
|
|
13068
|
+
return envelopeRunIdProblem(JSON.parse(readFileSync40(receiptPath, "utf8")), runId);
|
|
12957
13069
|
} catch {
|
|
12958
13070
|
return null;
|
|
12959
13071
|
}
|
|
@@ -12967,8 +13079,8 @@ var init_keys_cmd = __esm(() => {
|
|
|
12967
13079
|
});
|
|
12968
13080
|
|
|
12969
13081
|
// ../../loki-ts/src/engine10/verify_cmd.ts
|
|
12970
|
-
import { existsSync as
|
|
12971
|
-
import { dirname as dirname24, join as
|
|
13082
|
+
import { existsSync as existsSync34, readdirSync as readdirSync17, readFileSync as readFileSync41, statSync as statSync18 } from "fs";
|
|
13083
|
+
import { dirname as dirname24, join as join54 } from "path";
|
|
12972
13084
|
import { createHash as createHash13, createPublicKey as createPublicKey4, verify as verify3 } from "crypto";
|
|
12973
13085
|
function pubFor(kid, given) {
|
|
12974
13086
|
if (given)
|
|
@@ -12976,7 +13088,7 @@ function pubFor(kid, given) {
|
|
|
12976
13088
|
const active = loadSigningKey(false);
|
|
12977
13089
|
const pubs = (process.env["LOKI_RECEIPT_RETIRED_PUBKEYS"] ?? "").split(":").map((f) => f.trim()).filter(Boolean).flatMap((f) => {
|
|
12978
13090
|
try {
|
|
12979
|
-
return [createPublicKey4(
|
|
13091
|
+
return [createPublicKey4(readFileSync41(f))];
|
|
12980
13092
|
} catch {
|
|
12981
13093
|
return [];
|
|
12982
13094
|
}
|
|
@@ -13008,10 +13120,10 @@ function checkEventLog(receiptPath, receipt) {
|
|
|
13008
13120
|
const bound = receipt["events_sha256"];
|
|
13009
13121
|
if (typeof bound !== "string" || bound === createHash13("sha256").digest("hex"))
|
|
13010
13122
|
return null;
|
|
13011
|
-
const path =
|
|
13012
|
-
if (!
|
|
13123
|
+
const path = join54(dirname24(receiptPath), "events.jsonl");
|
|
13124
|
+
if (!existsSync34(path))
|
|
13013
13125
|
return "events.jsonl is missing";
|
|
13014
|
-
const raw2 =
|
|
13126
|
+
const raw2 = readFileSync41(path, "utf8"), events2 = readEvents(path), lines = raw2.split(`
|
|
13015
13127
|
`).filter((l) => l.trim() !== "");
|
|
13016
13128
|
if (events2.length !== lines.length || events2.some((e, i) => e.seq !== i))
|
|
13017
13129
|
return "events.jsonl has a forged, edited or missing line";
|
|
@@ -13027,7 +13139,7 @@ function checkEventLog(receiptPath, receipt) {
|
|
|
13027
13139
|
return h.digest("hex") === bound ? null : "events.jsonl does not match the hash recorded at seal";
|
|
13028
13140
|
}
|
|
13029
13141
|
function checkLogSeal(receiptPath, hash, kid, given) {
|
|
13030
|
-
const path =
|
|
13142
|
+
const path = join54(dirname24(receiptPath), "events.jsonl"), events2 = readEvents(path), done = events2.findIndex((e) => e.type === "run.completed");
|
|
13031
13143
|
if (!events2.some((e) => e.type === "receipt.sealed" && e.data["receipt_sha256"] === hash))
|
|
13032
13144
|
return { verdict: "TAMPERED", reason: "events.jsonl has no receipt.sealed event for this receipt" };
|
|
13033
13145
|
if (done < 0)
|
|
@@ -13035,7 +13147,7 @@ function checkLogSeal(receiptPath, hash, kid, given) {
|
|
|
13035
13147
|
const seal = events2[done + 1], d = seal?.type === "log.sealed" ? seal.data : null;
|
|
13036
13148
|
if (!d)
|
|
13037
13149
|
return { verdict: "TAMPERED", reason: "events.jsonl has no signed log.sealed line after run.completed" };
|
|
13038
|
-
const lines =
|
|
13150
|
+
const lines = readFileSync41(path, "utf8").split(`
|
|
13039
13151
|
`).filter((l) => l.trim() !== ""), sha3 = createHash13("sha256").update(lines.slice(0, done + 1).join(`
|
|
13040
13152
|
`) + `
|
|
13041
13153
|
`).digest("hex");
|
|
@@ -13045,11 +13157,11 @@ function checkLogSeal(receiptPath, hash, kid, given) {
|
|
|
13045
13157
|
return null;
|
|
13046
13158
|
}
|
|
13047
13159
|
async function verifyReceipt(receiptPath, deps = {}) {
|
|
13048
|
-
if (!
|
|
13160
|
+
if (!existsSync34(receiptPath))
|
|
13049
13161
|
return { verdict: "UNCHECKED", reasons: [`receipt not found: ${receiptPath}`] };
|
|
13050
13162
|
let receipt;
|
|
13051
13163
|
try {
|
|
13052
|
-
receipt = JSON.parse((deps.read ?? ((p) =>
|
|
13164
|
+
receipt = JSON.parse((deps.read ?? ((p) => readFileSync41(p, "utf8")))(receiptPath));
|
|
13053
13165
|
} catch {
|
|
13054
13166
|
return { verdict: "UNCHECKED", reasons: ["receipt.json is not valid JSON"] };
|
|
13055
13167
|
}
|
|
@@ -13104,8 +13216,8 @@ var init_verify_cmd = __esm(() => {
|
|
|
13104
13216
|
});
|
|
13105
13217
|
|
|
13106
13218
|
// src/server/app.ts
|
|
13107
|
-
import { existsSync as
|
|
13108
|
-
import { join as
|
|
13219
|
+
import { existsSync as existsSync36, readFileSync as readFileSync44, statSync as statSync19 } from "fs";
|
|
13220
|
+
import { join as join57, resolve as resolve13, sep as sep14 } from "path";
|
|
13109
13221
|
|
|
13110
13222
|
// node_modules/hono/dist/request/constants.js
|
|
13111
13223
|
var GET_MATCH_RESULT = Symbol();
|
|
@@ -23352,9 +23464,9 @@ function mount20(ctx) {
|
|
|
23352
23464
|
}
|
|
23353
23465
|
|
|
23354
23466
|
// src/server/routes/verify.ts
|
|
23355
|
-
import { readFileSync as
|
|
23467
|
+
import { readFileSync as readFileSync42, realpathSync as realpathSync20 } from "fs";
|
|
23356
23468
|
import { createPublicKey as createPublicKey5 } from "crypto";
|
|
23357
|
-
import { join as
|
|
23469
|
+
import { join as join55, sep as sep13 } from "path";
|
|
23358
23470
|
init_verify_cmd();
|
|
23359
23471
|
init_seal();
|
|
23360
23472
|
init_receipt_dsse();
|
|
@@ -23381,11 +23493,11 @@ function mount21(ctx) {
|
|
|
23381
23493
|
return done(404, "receipt not available", "not_found");
|
|
23382
23494
|
let runDir, file;
|
|
23383
23495
|
try {
|
|
23384
|
-
const root = realpathSync20(
|
|
23385
|
-
runDir = realpathSync20(
|
|
23386
|
-
if (runDir !==
|
|
23496
|
+
const root = realpathSync20(join55(repo.p, ".loki", "runs"));
|
|
23497
|
+
runDir = realpathSync20(join55(root, run2));
|
|
23498
|
+
if (runDir !== join55(root, run2))
|
|
23387
23499
|
return done(404, "receipt not found", "not_found");
|
|
23388
|
-
file = realpathSync20(
|
|
23500
|
+
file = realpathSync20(join55(runDir, "receipt.json"));
|
|
23389
23501
|
} catch {
|
|
23390
23502
|
return done(404, "receipt not found", "not_found");
|
|
23391
23503
|
}
|
|
@@ -23404,7 +23516,7 @@ function mount21(ctx) {
|
|
|
23404
23516
|
sha3 = r.receiptSha256 ?? null;
|
|
23405
23517
|
}
|
|
23406
23518
|
try {
|
|
23407
|
-
outcome = outcomeOf(JSON.parse(
|
|
23519
|
+
outcome = outcomeOf(JSON.parse(readFileSync42(file, "utf8")));
|
|
23408
23520
|
} catch {
|
|
23409
23521
|
outcome = "UNREADABLE";
|
|
23410
23522
|
}
|
|
@@ -23466,8 +23578,8 @@ function ttlCache(ttlMs = PROBE_CACHE_MS, now2 = Date.now) {
|
|
|
23466
23578
|
}
|
|
23467
23579
|
|
|
23468
23580
|
// src/server/legacy/shim.ts
|
|
23469
|
-
import { existsSync as
|
|
23470
|
-
import { join as
|
|
23581
|
+
import { existsSync as existsSync35, readFileSync as readFileSync43 } from "fs";
|
|
23582
|
+
import { join as join56 } from "path";
|
|
23471
23583
|
|
|
23472
23584
|
// src/server/legacy/routes.ts
|
|
23473
23585
|
var LEGACY_ROUTES = [
|
|
@@ -23839,10 +23951,10 @@ var logged = new Set;
|
|
|
23839
23951
|
var toHono = (p) => p.replace(/\{([A-Za-z_]+):path\}/g, ":$1{.+}").replace(/\{([A-Za-z_]+)\}/g, ":$1");
|
|
23840
23952
|
var NOT_SUPPORTED = "not yet supported by the Control Plane";
|
|
23841
23953
|
var repoVersion = () => {
|
|
23842
|
-
for (const f of [
|
|
23954
|
+
for (const f of [join56(import.meta.dir, "../../../../../VERSION")]) {
|
|
23843
23955
|
try {
|
|
23844
|
-
if (
|
|
23845
|
-
return
|
|
23956
|
+
if (existsSync35(f))
|
|
23957
|
+
return readFileSync43(f, "utf8").trim();
|
|
23846
23958
|
} catch {}
|
|
23847
23959
|
}
|
|
23848
23960
|
return "unknown";
|
|
@@ -24024,7 +24136,7 @@ function legacyShim(opts) {
|
|
|
24024
24136
|
|
|
24025
24137
|
// src/server/app.ts
|
|
24026
24138
|
var MAX_BODY2 = 1e6;
|
|
24027
|
-
var defaultUiDir = () => [
|
|
24139
|
+
var defaultUiDir = () => [join57(import.meta.dir, "../../ui/dist"), join57(import.meta.dir, "../ui/dist")].find((d) => existsSync36(join57(d, "index.html")));
|
|
24028
24140
|
function createApp(opts) {
|
|
24029
24141
|
const uiDir = opts.uiDir ?? defaultUiDir();
|
|
24030
24142
|
const { db, sqlite } = openDb(opts.dbPath);
|
|
@@ -24053,10 +24165,10 @@ function createApp(opts) {
|
|
|
24053
24165
|
await next();
|
|
24054
24166
|
});
|
|
24055
24167
|
const readInstalled = () => {
|
|
24056
|
-
for (const f of [process.env.LOKI_VERSION_FILE,
|
|
24168
|
+
for (const f of [process.env.LOKI_VERSION_FILE, join57(import.meta.dir, "../../../../VERSION")]) {
|
|
24057
24169
|
try {
|
|
24058
24170
|
if (f)
|
|
24059
|
-
return
|
|
24171
|
+
return readFileSync44(f, "utf8").trim() || "unknown";
|
|
24060
24172
|
} catch {}
|
|
24061
24173
|
}
|
|
24062
24174
|
return "unknown";
|
|
@@ -24167,11 +24279,11 @@ function createApp(opts) {
|
|
|
24167
24279
|
return c.json({ error: "not found" }, 404);
|
|
24168
24280
|
const root = resolve13(uiDir);
|
|
24169
24281
|
const f = resolve13(root, `.${p}`);
|
|
24170
|
-
if (f.startsWith(root + sep14) &&
|
|
24282
|
+
if (f.startsWith(root + sep14) && existsSync36(f) && statSync19(f).isFile())
|
|
24171
24283
|
return new Response(Bun.file(f));
|
|
24172
24284
|
if (/\.[a-z0-9]+$/i.test(p))
|
|
24173
24285
|
return c.json({ error: "not found" }, 404);
|
|
24174
|
-
return new Response(Bun.file(
|
|
24286
|
+
return new Response(Bun.file(join57(root, "index.html")));
|
|
24175
24287
|
});
|
|
24176
24288
|
return { app, db, close: () => {
|
|
24177
24289
|
clearInterval(reconciler);
|