loki-mode 10.8.0 → 10.9.1
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 +2 -2
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/loki +16 -31
- package/autonomy/tui.sh +5 -1
- package/dashboard/__init__.py +1 -1
- package/dashboard/api_phases.py +1 -1
- package/dashboard/api_start.py +0 -1
- package/dashboard/server.py +22 -138
- package/docs/COCKPIT-SPEC.md +3 -1
- package/docs/DASHBOARD-9.12-EVIDENCE.md +1 -1
- package/docs/DASHBOARD-ARCHITECTURE.md +13 -11
- package/docs/DASHBOARD_V2_CHECKLIST.md +2 -0
- package/docs/INSTALLATION.md +12 -11
- package/docs/MERGE-DEDUP-MAP.md +1 -1
- package/docs/MERGE-ROUTE-MAP.md +1 -1
- package/docs/MERGE3-PLAN.md +2 -2
- package/docs/R3-COST-OBSERVABILITY-DESIGN.md +2 -2
- package/docs/R4-TRUST-TRAJECTORY-DESIGN.md +1 -1
- package/docs/R5-AUTO-WIKI-DESIGN.md +1 -1
- package/docs/R6-ROLLBACK-CHECKPOINT-PLAN.md +1 -1
- package/docs/RARV-C-100X-PLAN.md +3 -3
- package/docs/SONNET5-DEFAULT-PLAN.md +2 -2
- package/docs/SYNERGY-TASKS.md +1 -1
- package/docs/TOOL-INTEGRATION.md +1 -1
- package/docs/TOP-100-BACKLOG.md +1 -1
- package/docs/alternative-installations.md +1 -1
- package/docs/architecture/ADR-001-runtime-migration.md +1 -1
- package/docs/architecture/DASHBOARD_V2_ARCHITECTURE.md +2 -0
- package/docs/audit-logging.md +2 -0
- package/docs/authentication.md +2 -0
- package/docs/authorization.md +2 -0
- package/docs/certification/02-enterprise-features/lab.md +2 -0
- package/docs/certification/02-enterprise-features/lesson.md +2 -0
- package/docs/certification/04-production-deployment/lab.md +2 -0
- package/docs/certification/04-production-deployment/lesson.md +2 -0
- package/docs/control-plane-migration.md +97 -0
- package/docs/dashboard-guide.md +6 -7
- package/docs/dev/release-checklist.md +9 -11
- package/docs/enterprise/integration-cookbook.md +2 -0
- package/docs/enterprise/migration.md +1 -1
- package/docs/enterprise/performance.md +3 -1
- package/docs/enterprise/sdk-guide.md +2 -0
- package/docs/enterprise/security.md +2 -0
- package/docs/plans/FORGE-AUTONOMOUS-QUEUE.md +2 -2
- package/docs/research-2026-07/OBSERVABILITY-TOOL-SPEC.md +1 -1
- package/docs/retrospectives/v7.5.15-honesty-audit.md +2 -2
- package/docs/test-scenarios/enterprise-scenarios.md +2 -2
- package/docs/v10/ARCHITECT-CUTS.md +9 -9
- package/docs/v10/BACKLOG.md +7 -7
- package/docs/v10/BOARD.md +25 -25
- package/docs/v10/CONTROL-PLANE.md +4 -4
- package/docs/v10/CP-ENTERPRISE-UI.md +66 -66
- package/docs/v10/D51-PHASE-B.md +1 -1
- package/docs/v10/DECISIONS.md +3 -3
- package/docs/v10/DEPS.md +10 -10
- package/docs/v10/FAILURE-CLASSES.md +45 -1
- package/docs/v10/LEGACY-REMOVAL.md +2 -2
- package/docs/v10/SWARM.md +2 -2
- package/loki-ts/dist/cockpit.js +11 -11
- package/loki-ts/dist/loki.js +596 -579
- package/magic/core/design_tokens.py +1 -2
- package/magic/core/generator.py +2 -2
- package/magic/tokens/README.md +1 -3
- package/mcp/__init__.py +1 -1
- package/package.json +2 -7
- package/packages/control-plane/dist/server.js +1683 -843
- package/packages/control-plane/src/server/legacy/routes-audit.ts +50 -0
- package/packages/control-plane/src/server/legacy/routes-control.ts +26 -0
- package/packages/control-plane/src/server/legacy/routes-memory.ts +30 -0
- package/packages/control-plane/src/server/legacy/routes.ts +58 -58
- package/packages/control-plane/src/server/legacy/shim.ts +10 -4
- package/packages/control-plane/src/server/routes/checkpoints.ts +171 -0
- package/packages/control-plane/src/server/routes/index.ts +4 -1
- package/packages/control-plane/src/server/routes/memory.ts +219 -0
- package/packages/control-plane/src/server/routes/session_control.ts +155 -0
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/references/magic-modules-patterns.md +2 -2
- package/skills/factory-operations.md +3 -3
- package/skills/magic-modules.md +1 -1
- package/skills/release-cadence.md +1 -1
- package/tools/audit-docs.py +1 -1
- package/tools/index-codebase.py +1 -1
- package/web-app/dist/assets/{AdminPage-DDDvUz0N.js → AdminPage-DqMnfZrH.js} +1 -1
- package/web-app/dist/assets/{Avatar-Dcdz46i6.js → Avatar-BEof29du.js} +1 -1
- package/web-app/dist/assets/{Badge-BB24cCpK.js → Badge-WD53OL5m.js} +1 -1
- package/web-app/dist/assets/{Button-D0-DmPZL.js → Button-Rzk5flhU.js} +1 -1
- package/web-app/dist/assets/{CockpitPage-CdjpU2JW.js → CockpitPage-Bqz3PCMh.js} +1 -1
- package/web-app/dist/assets/{ComparePage-DT5U0y0d.js → ComparePage-Byo2NzAZ.js} +1 -1
- package/web-app/dist/assets/{ErrorBoundary-lEHcl0EO.js → ErrorBoundary-BuE0kH3W.js} +1 -1
- package/web-app/dist/assets/{EvidenceReceiptPanel-GpnWvdCr.js → EvidenceReceiptPanel-CKFHOPBx.js} +1 -1
- package/web-app/dist/assets/{GitHubIssuesPanel-C7VOh1B7.js → GitHubIssuesPanel-Df_kTMqI.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-G_kOuINv.js → GitHubPRsPanel-CouvcKrZ.js} +1 -1
- package/web-app/dist/assets/{HomePage-PqRisOny.js → HomePage-dEpLzj0x.js} +2 -2
- package/web-app/dist/assets/{LoginPage-DU7pFAkf.js → LoginPage-6DVjUND5.js} +1 -1
- package/web-app/dist/assets/{MagicPage-C5JWIWMk.js → MagicPage-BDQ9MOfY.js} +1 -1
- package/web-app/dist/assets/{MetricsPage-CbIGS_qo.js → MetricsPage-CXiJ04G_.js} +1 -1
- package/web-app/dist/assets/{NotFoundPage-vU1pvdSt.js → NotFoundPage-BaXhZvyj.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-SsixIPPv.js → ProjectPage-BzE4V7Ti.js} +3 -3
- package/web-app/dist/assets/{ProjectsPage-BfQKSR9k.js → ProjectsPage-ipf6TLMX.js} +1 -1
- package/web-app/dist/assets/{SettingsPage-B8YfXAom.js → SettingsPage-BDxkUKN6.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-Ba407f2-.js → ShowcasePage-YQXD72SM.js} +1 -1
- package/web-app/dist/assets/{SystemSettingsPage-DzNQhhBS.js → SystemSettingsPage-Dou7zHS0.js} +1 -1
- package/web-app/dist/assets/{TeamsPage-tqUnpZ15.js → TeamsPage-CQarfcYr.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-ClB5EpI3.js → TemplatesPage-CPi1R8Xj.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-CBvpYfEu.js → TerminalOutput-WiGhWdYD.js} +1 -1
- package/web-app/dist/assets/{activity-C1s0L_X9.js → activity-DOa7ejQA.js} +1 -1
- package/web-app/dist/assets/{bell-DMEiXr73.js → bell-CX-VAlYN.js} +1 -1
- package/web-app/dist/assets/{bot-BF_xIOxu.js → bot-EDXaZmHc.js} +1 -1
- package/web-app/dist/assets/{check-Dv1Ynvrs.js → check-CnBdKk3L.js} +1 -1
- package/web-app/dist/assets/{chevron-left-Ddhsod7S.js → chevron-left-BJ_ydRQW.js} +1 -1
- package/web-app/dist/assets/{circle-alert-OATHUkBZ.js → circle-alert-tz8ufhc_.js} +1 -1
- package/web-app/dist/assets/{clock-C1Ltva07.js → clock-DFBZPSsH.js} +1 -1
- package/web-app/dist/assets/{cloud-B-S4oOy9.js → cloud-uy3p_5Q7.js} +1 -1
- package/web-app/dist/assets/{code-xml-D3Femn_F.js → code-xml-3CbYD9YL.js} +1 -1
- package/web-app/dist/assets/{database-DBSeFgO3.js → database-YiNBXc9c.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-sgDgNuGE.js → dollar-sign-D0wBa4Cv.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-C-MbdZEc.js → file-code-corner-DDdZVZ5v.js} +1 -1
- package/web-app/dist/assets/{file-plus-CCB6yqXv.js → file-plus-D09LTBqt.js} +1 -1
- package/web-app/dist/assets/{globe-DlbrNAj1.js → globe-BirTPwff.js} +1 -1
- package/web-app/dist/assets/{hammer-B-DWm695.js → hammer-DaE5qgnd.js} +1 -1
- package/web-app/dist/assets/{index-Dvny-1SU.js → index-D8t7KnQf.js} +3 -3
- package/web-app/dist/assets/{layers-99qsKpOT.js → layers-DVVrckRY.js} +1 -1
- package/web-app/dist/assets/{loader-circle-ZvZyZl3J.js → loader-circle-2-nMGmcb.js} +1 -1
- package/web-app/dist/assets/{lock-DosGjcxP.js → lock-D0Xj43pC.js} +1 -1
- package/web-app/dist/assets/{package-C-PumesN.js → package-BB7S3egm.js} +1 -1
- package/web-app/dist/assets/{plus-CTYYskTB.js → plus-BLhoMrn3.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-qW2ztTFS.js → refresh-cw-DMc2uPLw.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-ZIKrf-vO.js → rotate-ccw-DM3IP9x7.js} +1 -1
- package/web-app/dist/assets/{scroll-text-BamlXjAT.js → scroll-text-lH2uW7rB.js} +1 -1
- package/web-app/dist/assets/{server-BljXb79I.js → server-BkerZcdR.js} +1 -1
- package/web-app/dist/assets/{shield-alert-Dr6eT9c_.js → shield-alert-B8ohcUfu.js} +1 -1
- package/web-app/dist/assets/{thumbs-up-v3oBXZgU.js → thumbs-up-CTIxLzPD.js} +1 -1
- package/web-app/dist/assets/{trash-2-BOuIrCpi.js → trash-2-C3zIpsH5.js} +1 -1
- package/web-app/dist/assets/{trending-down-Dp7jMCgH.js → trending-down-BTT7wV4Y.js} +1 -1
- package/web-app/dist/assets/{trending-up-CDiur-nP.js → trending-up-BzHdCDvF.js} +1 -1
- package/web-app/dist/assets/{upload-DxIHXhNE.js → upload-HxR34-A8.js} +1 -1
- package/web-app/dist/assets/{usePolling-D2Uu-YZb.js → usePolling-DDj6Fupy.js} +1 -1
- package/web-app/dist/assets/{user-BVWCUAfF.js → user-qH5y9yNy.js} +1 -1
- package/web-app/dist/index.html +1 -1
- package/dashboard/static/assets/mermaid.min.js +0 -2030
- package/dashboard/static/cost.html +0 -344
- package/dashboard/static/favicon.svg +0 -5
- package/dashboard/static/index.html +0 -15577
- package/dashboard/static/proofs.html +0 -194
- package/dashboard/static/start.html +0 -131
- package/dashboard/static/trust.html +0 -325
|
@@ -879,16 +879,16 @@ var require_Collection = __commonJS((exports) => {
|
|
|
879
879
|
|
|
880
880
|
// node_modules/yaml/dist/stringify/stringifyComment.js
|
|
881
881
|
var require_stringifyComment = __commonJS((exports) => {
|
|
882
|
-
var stringifyComment = (
|
|
882
|
+
var stringifyComment = (str3) => str3.replace(/^(?!$)(?: $)?/gm, "#");
|
|
883
883
|
function indentComment(comment, indent) {
|
|
884
884
|
if (/^\n+$/.test(comment))
|
|
885
885
|
return comment.substring(1);
|
|
886
886
|
return indent ? comment.replace(/^(?! *$)/gm, indent) : comment;
|
|
887
887
|
}
|
|
888
|
-
var lineComment = (
|
|
888
|
+
var lineComment = (str3, indent, comment) => str3.endsWith(`
|
|
889
889
|
`) ? indentComment(comment, indent) : comment.includes(`
|
|
890
890
|
`) ? `
|
|
891
|
-
` + indentComment(comment, indent) : (
|
|
891
|
+
` + indentComment(comment, indent) : (str3.endsWith(" ") ? "" : " ") + comment;
|
|
892
892
|
exports.indentComment = indentComment;
|
|
893
893
|
exports.lineComment = lineComment;
|
|
894
894
|
exports.stringifyComment = stringifyComment;
|
|
@@ -1040,16 +1040,16 @@ var require_stringifyString = __commonJS((exports) => {
|
|
|
1040
1040
|
lineWidth: ctx.options.lineWidth,
|
|
1041
1041
|
minContentWidth: ctx.options.minContentWidth
|
|
1042
1042
|
});
|
|
1043
|
-
var containsDocumentMarker = (
|
|
1044
|
-
function lineLengthOverLimit(
|
|
1043
|
+
var containsDocumentMarker = (str3) => /^(%|---|\.\.\.)/m.test(str3);
|
|
1044
|
+
function lineLengthOverLimit(str3, lineWidth, indentLength) {
|
|
1045
1045
|
if (!lineWidth || lineWidth < 0)
|
|
1046
1046
|
return false;
|
|
1047
1047
|
const limit = lineWidth - indentLength;
|
|
1048
|
-
const strLen =
|
|
1048
|
+
const strLen = str3.length;
|
|
1049
1049
|
if (strLen <= limit)
|
|
1050
1050
|
return false;
|
|
1051
1051
|
for (let i = 0, start = 0;i < strLen; ++i) {
|
|
1052
|
-
if (
|
|
1052
|
+
if (str3[i] === `
|
|
1053
1053
|
`) {
|
|
1054
1054
|
if (i - start > limit)
|
|
1055
1055
|
return true;
|
|
@@ -1067,11 +1067,11 @@ var require_stringifyString = __commonJS((exports) => {
|
|
|
1067
1067
|
const { implicitKey } = ctx;
|
|
1068
1068
|
const minMultiLineLength = ctx.options.doubleQuotedMinMultiLineLength;
|
|
1069
1069
|
const indent = ctx.indent || (containsDocumentMarker(value) ? " " : "");
|
|
1070
|
-
let
|
|
1070
|
+
let str3 = "";
|
|
1071
1071
|
let start = 0;
|
|
1072
1072
|
for (let i = 0, ch = json[i];ch; ch = json[++i]) {
|
|
1073
1073
|
if (ch === " " && json[i + 1] === "\\" && json[i + 2] === "n") {
|
|
1074
|
-
|
|
1074
|
+
str3 += json.slice(start, i) + "\\ ";
|
|
1075
1075
|
i += 1;
|
|
1076
1076
|
start = i;
|
|
1077
1077
|
ch = "\\";
|
|
@@ -1080,38 +1080,38 @@ var require_stringifyString = __commonJS((exports) => {
|
|
|
1080
1080
|
switch (json[i + 1]) {
|
|
1081
1081
|
case "u":
|
|
1082
1082
|
{
|
|
1083
|
-
|
|
1083
|
+
str3 += json.slice(start, i);
|
|
1084
1084
|
const code = json.substr(i + 2, 4);
|
|
1085
1085
|
switch (code) {
|
|
1086
1086
|
case "0000":
|
|
1087
|
-
|
|
1087
|
+
str3 += "\\0";
|
|
1088
1088
|
break;
|
|
1089
1089
|
case "0007":
|
|
1090
|
-
|
|
1090
|
+
str3 += "\\a";
|
|
1091
1091
|
break;
|
|
1092
1092
|
case "000b":
|
|
1093
|
-
|
|
1093
|
+
str3 += "\\v";
|
|
1094
1094
|
break;
|
|
1095
1095
|
case "001b":
|
|
1096
|
-
|
|
1096
|
+
str3 += "\\e";
|
|
1097
1097
|
break;
|
|
1098
1098
|
case "0085":
|
|
1099
|
-
|
|
1099
|
+
str3 += "\\N";
|
|
1100
1100
|
break;
|
|
1101
1101
|
case "00a0":
|
|
1102
|
-
|
|
1102
|
+
str3 += "\\_";
|
|
1103
1103
|
break;
|
|
1104
1104
|
case "2028":
|
|
1105
|
-
|
|
1105
|
+
str3 += "\\L";
|
|
1106
1106
|
break;
|
|
1107
1107
|
case "2029":
|
|
1108
|
-
|
|
1108
|
+
str3 += "\\P";
|
|
1109
1109
|
break;
|
|
1110
1110
|
default:
|
|
1111
1111
|
if (code.substr(0, 2) === "00")
|
|
1112
|
-
|
|
1112
|
+
str3 += "\\x" + code.substr(2);
|
|
1113
1113
|
else
|
|
1114
|
-
|
|
1114
|
+
str3 += json.substr(i, 6);
|
|
1115
1115
|
}
|
|
1116
1116
|
i += 5;
|
|
1117
1117
|
start = i + 1;
|
|
@@ -1121,17 +1121,17 @@ var require_stringifyString = __commonJS((exports) => {
|
|
|
1121
1121
|
if (implicitKey || json[i + 2] === '"' || json.length < minMultiLineLength) {
|
|
1122
1122
|
i += 1;
|
|
1123
1123
|
} else {
|
|
1124
|
-
|
|
1124
|
+
str3 += json.slice(start, i) + `
|
|
1125
1125
|
|
|
1126
1126
|
`;
|
|
1127
1127
|
while (json[i + 2] === "\\" && json[i + 3] === "n" && json[i + 4] !== '"') {
|
|
1128
|
-
|
|
1128
|
+
str3 += `
|
|
1129
1129
|
`;
|
|
1130
1130
|
i += 2;
|
|
1131
1131
|
}
|
|
1132
|
-
|
|
1132
|
+
str3 += indent;
|
|
1133
1133
|
if (json[i + 2] === " ")
|
|
1134
|
-
|
|
1134
|
+
str3 += "\\";
|
|
1135
1135
|
i += 1;
|
|
1136
1136
|
start = i + 1;
|
|
1137
1137
|
}
|
|
@@ -1140,8 +1140,8 @@ var require_stringifyString = __commonJS((exports) => {
|
|
|
1140
1140
|
i += 1;
|
|
1141
1141
|
}
|
|
1142
1142
|
}
|
|
1143
|
-
|
|
1144
|
-
return implicitKey ?
|
|
1143
|
+
str3 = start ? str3 + json.slice(start) : json;
|
|
1144
|
+
return implicitKey ? str3 : foldFlowLines.foldFlowLines(str3, indent, foldFlowLines.FOLD_QUOTED, getFoldOptions(ctx, false));
|
|
1145
1145
|
}
|
|
1146
1146
|
function singleQuotedString(value, ctx) {
|
|
1147
1147
|
if (ctx.options.singleQuote === false || ctx.implicitKey && value.includes(`
|
|
@@ -1283,15 +1283,15 @@ ${indent}${start}${value}${end}`;
|
|
|
1283
1283
|
return quotedString(value, ctx);
|
|
1284
1284
|
}
|
|
1285
1285
|
}
|
|
1286
|
-
const
|
|
1286
|
+
const str3 = value.replace(/\n+/g, `$&
|
|
1287
1287
|
${indent}`);
|
|
1288
1288
|
if (actualString) {
|
|
1289
|
-
const test = (tag) => tag.default && tag.tag !== "tag:yaml.org,2002:str" && tag.test?.test(
|
|
1289
|
+
const test = (tag) => tag.default && tag.tag !== "tag:yaml.org,2002:str" && tag.test?.test(str3);
|
|
1290
1290
|
const { compat, tags } = ctx.doc.schema;
|
|
1291
1291
|
if (tags.some(test) || compat?.some(test))
|
|
1292
1292
|
return quotedString(value, ctx);
|
|
1293
1293
|
}
|
|
1294
|
-
return implicitKey ?
|
|
1294
|
+
return implicitKey ? str3 : foldFlowLines.foldFlowLines(str3, indent, foldFlowLines.FOLD_FLOW, getFoldOptions(ctx, false));
|
|
1295
1295
|
}
|
|
1296
1296
|
function stringifyString(item, ctx, onComment, onChompKeep) {
|
|
1297
1297
|
const { implicitKey, inFlow } = ctx;
|
|
@@ -1440,11 +1440,11 @@ var require_stringify = __commonJS((exports) => {
|
|
|
1440
1440
|
const props = stringifyProps(node, tagObj, ctx);
|
|
1441
1441
|
if (props.length > 0)
|
|
1442
1442
|
ctx.indentAtStart = (ctx.indentAtStart ?? 0) + props.length + 1;
|
|
1443
|
-
const
|
|
1443
|
+
const str3 = typeof tagObj.stringify === "function" ? tagObj.stringify(node, ctx, onComment, onChompKeep) : identity.isScalar(node) ? stringifyString.stringifyString(node, ctx, onComment, onChompKeep) : node.toString(ctx, onComment, onChompKeep);
|
|
1444
1444
|
if (!props)
|
|
1445
|
-
return
|
|
1446
|
-
return identity.isScalar(node) ||
|
|
1447
|
-
${ctx.indent}${
|
|
1445
|
+
return str3;
|
|
1446
|
+
return identity.isScalar(node) || str3[0] === "{" || str3[0] === "[" ? `${props} ${str3}` : `${props}
|
|
1447
|
+
${ctx.indent}${str3}`;
|
|
1448
1448
|
}
|
|
1449
1449
|
exports.createStringifyContext = createStringifyContext;
|
|
1450
1450
|
exports.stringify = stringify;
|
|
@@ -1476,8 +1476,8 @@ var require_stringifyPair = __commonJS((exports) => {
|
|
|
1476
1476
|
});
|
|
1477
1477
|
let keyCommentDone = false;
|
|
1478
1478
|
let chompKeep = false;
|
|
1479
|
-
let
|
|
1480
|
-
if (!explicitKey && !ctx.inFlow &&
|
|
1479
|
+
let str3 = stringify.stringify(key, ctx, () => keyCommentDone = true, () => chompKeep = true);
|
|
1480
|
+
if (!explicitKey && !ctx.inFlow && str3.length > 1024) {
|
|
1481
1481
|
if (simpleKeys)
|
|
1482
1482
|
throw new Error("With simple keys, single line scalar must not span more than 1024 characters");
|
|
1483
1483
|
explicitKey = true;
|
|
@@ -1486,27 +1486,27 @@ var require_stringifyPair = __commonJS((exports) => {
|
|
|
1486
1486
|
if (allNullValues || value == null) {
|
|
1487
1487
|
if (keyCommentDone && onComment)
|
|
1488
1488
|
onComment();
|
|
1489
|
-
return
|
|
1489
|
+
return str3 === "" ? "?" : explicitKey ? `? ${str3}` : str3;
|
|
1490
1490
|
}
|
|
1491
1491
|
} else if (allNullValues && !simpleKeys || value == null && explicitKey) {
|
|
1492
|
-
|
|
1492
|
+
str3 = `? ${str3}`;
|
|
1493
1493
|
if (keyComment && !keyCommentDone) {
|
|
1494
|
-
|
|
1494
|
+
str3 += stringifyComment.lineComment(str3, ctx.indent, commentString(keyComment));
|
|
1495
1495
|
} else if (chompKeep && onChompKeep)
|
|
1496
1496
|
onChompKeep();
|
|
1497
|
-
return
|
|
1497
|
+
return str3;
|
|
1498
1498
|
}
|
|
1499
1499
|
if (keyCommentDone)
|
|
1500
1500
|
keyComment = null;
|
|
1501
1501
|
if (explicitKey) {
|
|
1502
1502
|
if (keyComment)
|
|
1503
|
-
|
|
1504
|
-
|
|
1503
|
+
str3 += stringifyComment.lineComment(str3, ctx.indent, commentString(keyComment));
|
|
1504
|
+
str3 = `? ${str3}
|
|
1505
1505
|
${indent}:`;
|
|
1506
1506
|
} else {
|
|
1507
|
-
|
|
1507
|
+
str3 = `${str3}:`;
|
|
1508
1508
|
if (keyComment)
|
|
1509
|
-
|
|
1509
|
+
str3 += stringifyComment.lineComment(str3, ctx.indent, commentString(keyComment));
|
|
1510
1510
|
}
|
|
1511
1511
|
let vsb, vcb, valueComment;
|
|
1512
1512
|
if (identity.isNode(value)) {
|
|
@@ -1522,7 +1522,7 @@ ${indent}:`;
|
|
|
1522
1522
|
}
|
|
1523
1523
|
ctx.implicitKey = false;
|
|
1524
1524
|
if (!explicitKey && !keyComment && identity.isScalar(value))
|
|
1525
|
-
ctx.indentAtStart =
|
|
1525
|
+
ctx.indentAtStart = str3.length + 1;
|
|
1526
1526
|
chompKeep = false;
|
|
1527
1527
|
if (!indentSeq && indentStep.length >= 2 && !ctx.inFlow && !explicitKey && identity.isSeq(value) && !value.flow && !value.tag && !value.anchor) {
|
|
1528
1528
|
ctx.indent = ctx.indent.substring(2);
|
|
@@ -1572,16 +1572,16 @@ ${ctx.indent}`;
|
|
|
1572
1572
|
`) {
|
|
1573
1573
|
ws = "";
|
|
1574
1574
|
}
|
|
1575
|
-
|
|
1575
|
+
str3 += ws + valueStr;
|
|
1576
1576
|
if (ctx.inFlow) {
|
|
1577
1577
|
if (valueCommentDone && onComment)
|
|
1578
1578
|
onComment();
|
|
1579
1579
|
} else if (valueComment && !valueCommentDone) {
|
|
1580
|
-
|
|
1580
|
+
str3 += stringifyComment.lineComment(str3, ctx.indent, commentString(valueComment));
|
|
1581
1581
|
} else if (chompKeep && onChompKeep) {
|
|
1582
1582
|
onChompKeep();
|
|
1583
1583
|
}
|
|
1584
|
-
return
|
|
1584
|
+
return str3;
|
|
1585
1585
|
}
|
|
1586
1586
|
exports.stringifyPair = stringifyPair;
|
|
1587
1587
|
});
|
|
@@ -1794,33 +1794,33 @@ var require_stringifyCollection = __commonJS((exports) => {
|
|
|
1794
1794
|
}
|
|
1795
1795
|
}
|
|
1796
1796
|
chompKeep = false;
|
|
1797
|
-
let
|
|
1797
|
+
let str4 = stringify.stringify(item, itemCtx, () => comment2 = null, () => chompKeep = true);
|
|
1798
1798
|
if (comment2)
|
|
1799
|
-
|
|
1799
|
+
str4 += stringifyComment.lineComment(str4, itemIndent, commentString(comment2));
|
|
1800
1800
|
if (chompKeep && comment2)
|
|
1801
1801
|
chompKeep = false;
|
|
1802
|
-
lines.push(blockItemPrefix +
|
|
1802
|
+
lines.push(blockItemPrefix + str4);
|
|
1803
1803
|
}
|
|
1804
|
-
let
|
|
1804
|
+
let str3;
|
|
1805
1805
|
if (lines.length === 0) {
|
|
1806
|
-
|
|
1806
|
+
str3 = flowChars.start + flowChars.end;
|
|
1807
1807
|
} else {
|
|
1808
|
-
|
|
1808
|
+
str3 = lines[0];
|
|
1809
1809
|
for (let i = 1;i < lines.length; ++i) {
|
|
1810
1810
|
const line2 = lines[i];
|
|
1811
|
-
|
|
1811
|
+
str3 += line2 ? `
|
|
1812
1812
|
${indent}${line2}` : `
|
|
1813
1813
|
`;
|
|
1814
1814
|
}
|
|
1815
1815
|
}
|
|
1816
1816
|
if (comment) {
|
|
1817
|
-
|
|
1817
|
+
str3 += `
|
|
1818
1818
|
` + stringifyComment.indentComment(commentString(comment), indent);
|
|
1819
1819
|
if (onComment)
|
|
1820
1820
|
onComment();
|
|
1821
1821
|
} else if (chompKeep && onChompKeep)
|
|
1822
1822
|
onChompKeep();
|
|
1823
|
-
return
|
|
1823
|
+
return str3;
|
|
1824
1824
|
}
|
|
1825
1825
|
function stringifyFlowCollection({ items }, ctx, { flowChars, itemIndent }) {
|
|
1826
1826
|
const { indent, indentStep, flowCollectionPadding: fcPadding, options: { commentString } } = ctx;
|
|
@@ -1863,22 +1863,22 @@ ${indent}${line2}` : `
|
|
|
1863
1863
|
}
|
|
1864
1864
|
if (comment)
|
|
1865
1865
|
reqNewline = true;
|
|
1866
|
-
let
|
|
1867
|
-
reqNewline || (reqNewline = lines.length > linesAtValue ||
|
|
1866
|
+
let str3 = stringify.stringify(item, itemCtx, () => comment = null);
|
|
1867
|
+
reqNewline || (reqNewline = lines.length > linesAtValue || str3.includes(`
|
|
1868
1868
|
`));
|
|
1869
1869
|
if (i < items.length - 1) {
|
|
1870
|
-
|
|
1870
|
+
str3 += ",";
|
|
1871
1871
|
} else if (ctx.options.trailingComma) {
|
|
1872
1872
|
if (ctx.options.lineWidth > 0) {
|
|
1873
|
-
reqNewline || (reqNewline = lines.reduce((sum, line2) => sum + line2.length + 2, 2) + (
|
|
1873
|
+
reqNewline || (reqNewline = lines.reduce((sum, line2) => sum + line2.length + 2, 2) + (str3.length + 2) > ctx.options.lineWidth);
|
|
1874
1874
|
}
|
|
1875
1875
|
if (reqNewline) {
|
|
1876
|
-
|
|
1876
|
+
str3 += ",";
|
|
1877
1877
|
}
|
|
1878
1878
|
}
|
|
1879
1879
|
if (comment)
|
|
1880
|
-
|
|
1881
|
-
lines.push(
|
|
1880
|
+
str3 += stringifyComment.lineComment(str3, itemIndent, commentString(comment));
|
|
1881
|
+
lines.push(str3);
|
|
1882
1882
|
linesAtValue = lines.length;
|
|
1883
1883
|
}
|
|
1884
1884
|
const { start, end } = flowChars;
|
|
@@ -1890,12 +1890,12 @@ ${indent}${line2}` : `
|
|
|
1890
1890
|
reqNewline = ctx.options.lineWidth > 0 && len > ctx.options.lineWidth;
|
|
1891
1891
|
}
|
|
1892
1892
|
if (reqNewline) {
|
|
1893
|
-
let
|
|
1893
|
+
let str3 = start;
|
|
1894
1894
|
for (const line2 of lines)
|
|
1895
|
-
|
|
1895
|
+
str3 += line2 ? `
|
|
1896
1896
|
${indentStep}${indent}${line2}` : `
|
|
1897
1897
|
`;
|
|
1898
|
-
return `${
|
|
1898
|
+
return `${str3}
|
|
1899
1899
|
${indent}${end}`;
|
|
1900
1900
|
} else {
|
|
1901
1901
|
return `${start}${fcPadding}${lines.join(" ")}${fcPadding}${end}`;
|
|
@@ -2178,7 +2178,7 @@ var require_string = __commonJS((exports) => {
|
|
|
2178
2178
|
identify: (value) => typeof value === "string",
|
|
2179
2179
|
default: true,
|
|
2180
2180
|
tag: "tag:yaml.org,2002:str",
|
|
2181
|
-
resolve: (
|
|
2181
|
+
resolve: (str3) => str3,
|
|
2182
2182
|
stringify(item, ctx, onComment, onChompKeep) {
|
|
2183
2183
|
ctx = Object.assign({ actualString: true }, ctx);
|
|
2184
2184
|
return stringifyString.stringifyString(item, ctx, onComment, onChompKeep);
|
|
@@ -2210,7 +2210,7 @@ var require_bool = __commonJS((exports) => {
|
|
|
2210
2210
|
default: true,
|
|
2211
2211
|
tag: "tag:yaml.org,2002:bool",
|
|
2212
2212
|
test: /^(?:[Tt]rue|TRUE|[Ff]alse|FALSE)$/,
|
|
2213
|
-
resolve: (
|
|
2213
|
+
resolve: (str3) => new Scalar.Scalar(str3[0] === "t" || str3[0] === "T"),
|
|
2214
2214
|
stringify({ source, value }, ctx) {
|
|
2215
2215
|
if (source && boolTag.test.test(source)) {
|
|
2216
2216
|
const sv = source[0] === "t" || source[0] === "T";
|
|
@@ -2256,7 +2256,7 @@ var require_float = __commonJS((exports) => {
|
|
|
2256
2256
|
default: true,
|
|
2257
2257
|
tag: "tag:yaml.org,2002:float",
|
|
2258
2258
|
test: /^(?:[-+]?\.(?:inf|Inf|INF)|\.nan|\.NaN|\.NAN)$/,
|
|
2259
|
-
resolve: (
|
|
2259
|
+
resolve: (str3) => str3.slice(-3).toLowerCase() === "nan" ? NaN : str3[0] === "-" ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY,
|
|
2260
2260
|
stringify: stringifyNumber.stringifyNumber
|
|
2261
2261
|
};
|
|
2262
2262
|
var floatExp = {
|
|
@@ -2265,7 +2265,7 @@ var require_float = __commonJS((exports) => {
|
|
|
2265
2265
|
tag: "tag:yaml.org,2002:float",
|
|
2266
2266
|
format: "EXP",
|
|
2267
2267
|
test: /^[-+]?(?:\.[0-9]+|[0-9]+(?:\.[0-9]*)?)[eE][-+]?[0-9]+$/,
|
|
2268
|
-
resolve: (
|
|
2268
|
+
resolve: (str3) => parseFloat(str3),
|
|
2269
2269
|
stringify(node) {
|
|
2270
2270
|
const num = Number(node.value);
|
|
2271
2271
|
return isFinite(num) ? num.toExponential() : stringifyNumber.stringifyNumber(node);
|
|
@@ -2276,11 +2276,11 @@ var require_float = __commonJS((exports) => {
|
|
|
2276
2276
|
default: true,
|
|
2277
2277
|
tag: "tag:yaml.org,2002:float",
|
|
2278
2278
|
test: /^[-+]?(?:\.[0-9]+|[0-9]+\.[0-9]*)$/,
|
|
2279
|
-
resolve(
|
|
2280
|
-
const node = new Scalar.Scalar(parseFloat(
|
|
2281
|
-
const dot =
|
|
2282
|
-
if (dot !== -1 &&
|
|
2283
|
-
node.minFractionDigits =
|
|
2279
|
+
resolve(str3) {
|
|
2280
|
+
const node = new Scalar.Scalar(parseFloat(str3));
|
|
2281
|
+
const dot = str3.indexOf(".");
|
|
2282
|
+
if (dot !== -1 && str3[str3.length - 1] === "0")
|
|
2283
|
+
node.minFractionDigits = str3.length - dot - 1;
|
|
2284
2284
|
return node;
|
|
2285
2285
|
},
|
|
2286
2286
|
stringify: stringifyNumber.stringifyNumber
|
|
@@ -2294,7 +2294,7 @@ var require_float = __commonJS((exports) => {
|
|
|
2294
2294
|
var require_int = __commonJS((exports) => {
|
|
2295
2295
|
var stringifyNumber = require_stringifyNumber();
|
|
2296
2296
|
var intIdentify = (value) => typeof value === "bigint" || Number.isInteger(value);
|
|
2297
|
-
var intResolve = (
|
|
2297
|
+
var intResolve = (str3, offset, radix, { intAsBigInt }) => intAsBigInt ? BigInt(str3) : parseInt(str3.substring(offset), radix);
|
|
2298
2298
|
function intStringify(node, radix, prefix) {
|
|
2299
2299
|
const { value } = node;
|
|
2300
2300
|
if (intIdentify(value) && value >= 0)
|
|
@@ -2307,7 +2307,7 @@ var require_int = __commonJS((exports) => {
|
|
|
2307
2307
|
tag: "tag:yaml.org,2002:int",
|
|
2308
2308
|
format: "OCT",
|
|
2309
2309
|
test: /^0o[0-7]+$/,
|
|
2310
|
-
resolve: (
|
|
2310
|
+
resolve: (str3, _onError, opt) => intResolve(str3, 2, 8, opt),
|
|
2311
2311
|
stringify: (node) => intStringify(node, 8, "0o")
|
|
2312
2312
|
};
|
|
2313
2313
|
var int = {
|
|
@@ -2315,7 +2315,7 @@ var require_int = __commonJS((exports) => {
|
|
|
2315
2315
|
default: true,
|
|
2316
2316
|
tag: "tag:yaml.org,2002:int",
|
|
2317
2317
|
test: /^[-+]?[0-9]+$/,
|
|
2318
|
-
resolve: (
|
|
2318
|
+
resolve: (str3, _onError, opt) => intResolve(str3, 0, 10, opt),
|
|
2319
2319
|
stringify: stringifyNumber.stringifyNumber
|
|
2320
2320
|
};
|
|
2321
2321
|
var intHex = {
|
|
@@ -2324,7 +2324,7 @@ var require_int = __commonJS((exports) => {
|
|
|
2324
2324
|
tag: "tag:yaml.org,2002:int",
|
|
2325
2325
|
format: "HEX",
|
|
2326
2326
|
test: /^0x[0-9a-fA-F]+$/,
|
|
2327
|
-
resolve: (
|
|
2327
|
+
resolve: (str3, _onError, opt) => intResolve(str3, 2, 16, opt),
|
|
2328
2328
|
stringify: (node) => intStringify(node, 16, "0x")
|
|
2329
2329
|
};
|
|
2330
2330
|
exports.int = int;
|
|
@@ -2371,7 +2371,7 @@ var require_schema2 = __commonJS((exports) => {
|
|
|
2371
2371
|
identify: (value) => typeof value === "string",
|
|
2372
2372
|
default: true,
|
|
2373
2373
|
tag: "tag:yaml.org,2002:str",
|
|
2374
|
-
resolve: (
|
|
2374
|
+
resolve: (str3) => str3,
|
|
2375
2375
|
stringify: stringifyJSON
|
|
2376
2376
|
},
|
|
2377
2377
|
{
|
|
@@ -2388,7 +2388,7 @@ var require_schema2 = __commonJS((exports) => {
|
|
|
2388
2388
|
default: true,
|
|
2389
2389
|
tag: "tag:yaml.org,2002:bool",
|
|
2390
2390
|
test: /^true$|^false$/,
|
|
2391
|
-
resolve: (
|
|
2391
|
+
resolve: (str3) => str3 === "true",
|
|
2392
2392
|
stringify: stringifyJSON
|
|
2393
2393
|
},
|
|
2394
2394
|
{
|
|
@@ -2396,7 +2396,7 @@ var require_schema2 = __commonJS((exports) => {
|
|
|
2396
2396
|
default: true,
|
|
2397
2397
|
tag: "tag:yaml.org,2002:int",
|
|
2398
2398
|
test: /^-?(?:0|[1-9][0-9]*)$/,
|
|
2399
|
-
resolve: (
|
|
2399
|
+
resolve: (str3, _onError, { intAsBigInt }) => intAsBigInt ? BigInt(str3) : parseInt(str3, 10),
|
|
2400
2400
|
stringify: ({ value }) => intIdentify(value) ? value.toString() : JSON.stringify(value)
|
|
2401
2401
|
},
|
|
2402
2402
|
{
|
|
@@ -2404,7 +2404,7 @@ var require_schema2 = __commonJS((exports) => {
|
|
|
2404
2404
|
default: true,
|
|
2405
2405
|
tag: "tag:yaml.org,2002:float",
|
|
2406
2406
|
test: /^-?(?:0|[1-9][0-9]*)(?:\.[0-9]*)?(?:[eE][-+]?[0-9]+)?$/,
|
|
2407
|
-
resolve: (
|
|
2407
|
+
resolve: (str3) => parseFloat(str3),
|
|
2408
2408
|
stringify: stringifyJSON
|
|
2409
2409
|
}
|
|
2410
2410
|
];
|
|
@@ -2412,9 +2412,9 @@ var require_schema2 = __commonJS((exports) => {
|
|
|
2412
2412
|
default: true,
|
|
2413
2413
|
tag: "",
|
|
2414
2414
|
test: /^/,
|
|
2415
|
-
resolve(
|
|
2416
|
-
onError(`Unresolved plain scalar ${JSON.stringify(
|
|
2417
|
-
return
|
|
2415
|
+
resolve(str3, onError) {
|
|
2416
|
+
onError(`Unresolved plain scalar ${JSON.stringify(str3)}`);
|
|
2417
|
+
return str3;
|
|
2418
2418
|
}
|
|
2419
2419
|
};
|
|
2420
2420
|
var schema = [map.map, seq.seq].concat(jsonScalars, jsonError);
|
|
@@ -2434,10 +2434,10 @@ var require_binary = __commonJS((exports) => {
|
|
|
2434
2434
|
if (typeof node_buffer.Buffer === "function") {
|
|
2435
2435
|
return node_buffer.Buffer.from(src, "base64");
|
|
2436
2436
|
} else if (typeof atob === "function") {
|
|
2437
|
-
const
|
|
2438
|
-
const buffer = new Uint8Array(
|
|
2439
|
-
for (let i = 0;i <
|
|
2440
|
-
buffer[i] =
|
|
2437
|
+
const str3 = atob(src.replace(/[\n\r]/g, ""));
|
|
2438
|
+
const buffer = new Uint8Array(str3.length);
|
|
2439
|
+
for (let i = 0;i < str3.length; ++i)
|
|
2440
|
+
buffer[i] = str3.charCodeAt(i);
|
|
2441
2441
|
return buffer;
|
|
2442
2442
|
} else {
|
|
2443
2443
|
onError("This environment does not support reading binary tags; either Buffer or atob is required");
|
|
@@ -2448,29 +2448,29 @@ var require_binary = __commonJS((exports) => {
|
|
|
2448
2448
|
if (!value)
|
|
2449
2449
|
return "";
|
|
2450
2450
|
const buf = value;
|
|
2451
|
-
let
|
|
2451
|
+
let str3;
|
|
2452
2452
|
if (typeof node_buffer.Buffer === "function") {
|
|
2453
|
-
|
|
2453
|
+
str3 = buf instanceof node_buffer.Buffer ? buf.toString("base64") : node_buffer.Buffer.from(buf.buffer).toString("base64");
|
|
2454
2454
|
} else if (typeof btoa === "function") {
|
|
2455
2455
|
let s = "";
|
|
2456
2456
|
for (let i = 0;i < buf.length; ++i)
|
|
2457
2457
|
s += String.fromCharCode(buf[i]);
|
|
2458
|
-
|
|
2458
|
+
str3 = btoa(s);
|
|
2459
2459
|
} else {
|
|
2460
2460
|
throw new Error("This environment does not support writing binary tags; either Buffer or btoa is required");
|
|
2461
2461
|
}
|
|
2462
2462
|
type ?? (type = Scalar.Scalar.BLOCK_LITERAL);
|
|
2463
2463
|
if (type !== Scalar.Scalar.QUOTE_DOUBLE) {
|
|
2464
2464
|
const lineWidth = Math.max(ctx.options.lineWidth - ctx.indent.length, ctx.options.minContentWidth);
|
|
2465
|
-
const n2 = Math.ceil(
|
|
2465
|
+
const n2 = Math.ceil(str3.length / lineWidth);
|
|
2466
2466
|
const lines = new Array(n2);
|
|
2467
2467
|
for (let i = 0, o = 0;i < n2; ++i, o += lineWidth) {
|
|
2468
|
-
lines[i] =
|
|
2468
|
+
lines[i] = str3.substr(o, lineWidth);
|
|
2469
2469
|
}
|
|
2470
|
-
|
|
2470
|
+
str3 = lines.join(type === Scalar.Scalar.BLOCK_LITERAL ? `
|
|
2471
2471
|
` : " ");
|
|
2472
2472
|
}
|
|
2473
|
-
return stringifyString.stringifyString({ comment, type, value:
|
|
2473
|
+
return stringifyString.stringifyString({ comment, type, value: str3 }, ctx, onComment, onChompKeep);
|
|
2474
2474
|
}
|
|
2475
2475
|
};
|
|
2476
2476
|
exports.binary = binary;
|
|
@@ -2661,7 +2661,7 @@ var require_float2 = __commonJS((exports) => {
|
|
|
2661
2661
|
default: true,
|
|
2662
2662
|
tag: "tag:yaml.org,2002:float",
|
|
2663
2663
|
test: /^(?:[-+]?\.(?:inf|Inf|INF)|\.nan|\.NaN|\.NAN)$/,
|
|
2664
|
-
resolve: (
|
|
2664
|
+
resolve: (str3) => str3.slice(-3).toLowerCase() === "nan" ? NaN : str3[0] === "-" ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY,
|
|
2665
2665
|
stringify: stringifyNumber.stringifyNumber
|
|
2666
2666
|
};
|
|
2667
2667
|
var floatExp = {
|
|
@@ -2670,7 +2670,7 @@ var require_float2 = __commonJS((exports) => {
|
|
|
2670
2670
|
tag: "tag:yaml.org,2002:float",
|
|
2671
2671
|
format: "EXP",
|
|
2672
2672
|
test: /^[-+]?(?:[0-9][0-9_]*)?(?:\.[0-9_]*)?[eE][-+]?[0-9]+$/,
|
|
2673
|
-
resolve: (
|
|
2673
|
+
resolve: (str3) => parseFloat(str3.replace(/_/g, "")),
|
|
2674
2674
|
stringify(node) {
|
|
2675
2675
|
const num = Number(node.value);
|
|
2676
2676
|
return isFinite(num) ? num.toExponential() : stringifyNumber.stringifyNumber(node);
|
|
@@ -2681,11 +2681,11 @@ var require_float2 = __commonJS((exports) => {
|
|
|
2681
2681
|
default: true,
|
|
2682
2682
|
tag: "tag:yaml.org,2002:float",
|
|
2683
2683
|
test: /^[-+]?(?:[0-9][0-9_]*)?\.[0-9_]*$/,
|
|
2684
|
-
resolve(
|
|
2685
|
-
const node = new Scalar.Scalar(parseFloat(
|
|
2686
|
-
const dot =
|
|
2684
|
+
resolve(str3) {
|
|
2685
|
+
const node = new Scalar.Scalar(parseFloat(str3.replace(/_/g, "")));
|
|
2686
|
+
const dot = str3.indexOf(".");
|
|
2687
2687
|
if (dot !== -1) {
|
|
2688
|
-
const f =
|
|
2688
|
+
const f = str3.substring(dot + 1).replace(/_/g, "");
|
|
2689
2689
|
if (f[f.length - 1] === "0")
|
|
2690
2690
|
node.minFractionDigits = f.length;
|
|
2691
2691
|
}
|
|
@@ -2702,34 +2702,34 @@ var require_float2 = __commonJS((exports) => {
|
|
|
2702
2702
|
var require_int2 = __commonJS((exports) => {
|
|
2703
2703
|
var stringifyNumber = require_stringifyNumber();
|
|
2704
2704
|
var intIdentify = (value) => typeof value === "bigint" || Number.isInteger(value);
|
|
2705
|
-
function intResolve(
|
|
2706
|
-
const sign =
|
|
2705
|
+
function intResolve(str3, offset, radix, { intAsBigInt }) {
|
|
2706
|
+
const sign = str3[0];
|
|
2707
2707
|
if (sign === "-" || sign === "+")
|
|
2708
2708
|
offset += 1;
|
|
2709
|
-
|
|
2709
|
+
str3 = str3.substring(offset).replace(/_/g, "");
|
|
2710
2710
|
if (intAsBigInt) {
|
|
2711
2711
|
switch (radix) {
|
|
2712
2712
|
case 2:
|
|
2713
|
-
|
|
2713
|
+
str3 = `0b${str3}`;
|
|
2714
2714
|
break;
|
|
2715
2715
|
case 8:
|
|
2716
|
-
|
|
2716
|
+
str3 = `0o${str3}`;
|
|
2717
2717
|
break;
|
|
2718
2718
|
case 16:
|
|
2719
|
-
|
|
2719
|
+
str3 = `0x${str3}`;
|
|
2720
2720
|
break;
|
|
2721
2721
|
}
|
|
2722
|
-
const n3 = BigInt(
|
|
2722
|
+
const n3 = BigInt(str3);
|
|
2723
2723
|
return sign === "-" ? BigInt(-1) * n3 : n3;
|
|
2724
2724
|
}
|
|
2725
|
-
const n2 = parseInt(
|
|
2725
|
+
const n2 = parseInt(str3, radix);
|
|
2726
2726
|
return sign === "-" ? -1 * n2 : n2;
|
|
2727
2727
|
}
|
|
2728
2728
|
function intStringify(node, radix, prefix) {
|
|
2729
2729
|
const { value } = node;
|
|
2730
2730
|
if (intIdentify(value)) {
|
|
2731
|
-
const
|
|
2732
|
-
return value < 0 ? "-" + prefix +
|
|
2731
|
+
const str3 = value.toString(radix);
|
|
2732
|
+
return value < 0 ? "-" + prefix + str3.substr(1) : prefix + str3;
|
|
2733
2733
|
}
|
|
2734
2734
|
return stringifyNumber.stringifyNumber(node);
|
|
2735
2735
|
}
|
|
@@ -2739,7 +2739,7 @@ var require_int2 = __commonJS((exports) => {
|
|
|
2739
2739
|
tag: "tag:yaml.org,2002:int",
|
|
2740
2740
|
format: "BIN",
|
|
2741
2741
|
test: /^[-+]?0b[0-1_]+$/,
|
|
2742
|
-
resolve: (
|
|
2742
|
+
resolve: (str3, _onError, opt) => intResolve(str3, 2, 2, opt),
|
|
2743
2743
|
stringify: (node) => intStringify(node, 2, "0b")
|
|
2744
2744
|
};
|
|
2745
2745
|
var intOct = {
|
|
@@ -2748,7 +2748,7 @@ var require_int2 = __commonJS((exports) => {
|
|
|
2748
2748
|
tag: "tag:yaml.org,2002:int",
|
|
2749
2749
|
format: "OCT",
|
|
2750
2750
|
test: /^[-+]?0[0-7_]+$/,
|
|
2751
|
-
resolve: (
|
|
2751
|
+
resolve: (str3, _onError, opt) => intResolve(str3, 1, 8, opt),
|
|
2752
2752
|
stringify: (node) => intStringify(node, 8, "0")
|
|
2753
2753
|
};
|
|
2754
2754
|
var int = {
|
|
@@ -2756,7 +2756,7 @@ var require_int2 = __commonJS((exports) => {
|
|
|
2756
2756
|
default: true,
|
|
2757
2757
|
tag: "tag:yaml.org,2002:int",
|
|
2758
2758
|
test: /^[-+]?[0-9][0-9_]*$/,
|
|
2759
|
-
resolve: (
|
|
2759
|
+
resolve: (str3, _onError, opt) => intResolve(str3, 0, 10, opt),
|
|
2760
2760
|
stringify: stringifyNumber.stringifyNumber
|
|
2761
2761
|
};
|
|
2762
2762
|
var intHex = {
|
|
@@ -2765,7 +2765,7 @@ var require_int2 = __commonJS((exports) => {
|
|
|
2765
2765
|
tag: "tag:yaml.org,2002:int",
|
|
2766
2766
|
format: "HEX",
|
|
2767
2767
|
test: /^[-+]?0x[0-9a-fA-F_]+$/,
|
|
2768
|
-
resolve: (
|
|
2768
|
+
resolve: (str3, _onError, opt) => intResolve(str3, 2, 16, opt),
|
|
2769
2769
|
stringify: (node) => intStringify(node, 16, "0x")
|
|
2770
2770
|
};
|
|
2771
2771
|
exports.int = int;
|
|
@@ -2860,9 +2860,9 @@ var require_set = __commonJS((exports) => {
|
|
|
2860
2860
|
// node_modules/yaml/dist/schema/yaml-1.1/timestamp.js
|
|
2861
2861
|
var require_timestamp = __commonJS((exports) => {
|
|
2862
2862
|
var stringifyNumber = require_stringifyNumber();
|
|
2863
|
-
function parseSexagesimal(
|
|
2864
|
-
const sign =
|
|
2865
|
-
const parts = sign === "-" || sign === "+" ?
|
|
2863
|
+
function parseSexagesimal(str3, asBigInt) {
|
|
2864
|
+
const sign = str3[0];
|
|
2865
|
+
const parts = sign === "-" || sign === "+" ? str3.substring(1) : str3;
|
|
2866
2866
|
const num = (n2) => asBigInt ? BigInt(n2) : Number(n2);
|
|
2867
2867
|
const res = parts.replace(/_/g, "").split(":").reduce((res2, p) => res2 * num(60) + num(p), num(0));
|
|
2868
2868
|
return sign === "-" ? num(-1) * res : res;
|
|
@@ -2899,7 +2899,7 @@ var require_timestamp = __commonJS((exports) => {
|
|
|
2899
2899
|
tag: "tag:yaml.org,2002:int",
|
|
2900
2900
|
format: "TIME",
|
|
2901
2901
|
test: /^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+$/,
|
|
2902
|
-
resolve: (
|
|
2902
|
+
resolve: (str3, _onError, { intAsBigInt }) => parseSexagesimal(str3, intAsBigInt),
|
|
2903
2903
|
stringify: stringifySexagesimal
|
|
2904
2904
|
};
|
|
2905
2905
|
var floatTime = {
|
|
@@ -2908,7 +2908,7 @@ var require_timestamp = __commonJS((exports) => {
|
|
|
2908
2908
|
tag: "tag:yaml.org,2002:float",
|
|
2909
2909
|
format: "TIME",
|
|
2910
2910
|
test: /^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]*$/,
|
|
2911
|
-
resolve: (
|
|
2911
|
+
resolve: (str3) => parseSexagesimal(str3, false),
|
|
2912
2912
|
stringify: stringifySexagesimal
|
|
2913
2913
|
};
|
|
2914
2914
|
var timestamp = {
|
|
@@ -2916,8 +2916,8 @@ var require_timestamp = __commonJS((exports) => {
|
|
|
2916
2916
|
default: true,
|
|
2917
2917
|
tag: "tag:yaml.org,2002:timestamp",
|
|
2918
2918
|
test: RegExp("^([0-9]{4})-([0-9]{1,2})-([0-9]{1,2})" + "(?:" + "(?:t|T|[ \\t]+)" + "([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2}(\\.[0-9]+)?)" + "(?:[ \\t]*(Z|[-+][012]?[0-9](?::[0-9]{2})?))?" + ")?$"),
|
|
2919
|
-
resolve(
|
|
2920
|
-
const match2 =
|
|
2919
|
+
resolve(str3) {
|
|
2920
|
+
const match2 = str3.match(timestamp.test);
|
|
2921
2921
|
if (!match2)
|
|
2922
2922
|
throw new Error("!!timestamp expects a date, starting with yyyy-mm-dd");
|
|
2923
2923
|
const [, year, month, day, hour, minute, second] = match2.map(Number);
|
|
@@ -3696,10 +3696,10 @@ var require_resolve_block_map = __commonJS((exports) => {
|
|
|
3696
3696
|
let offset = bm.offset;
|
|
3697
3697
|
let commentEnd = null;
|
|
3698
3698
|
for (const collItem of bm.items) {
|
|
3699
|
-
const { start, key, sep:
|
|
3699
|
+
const { start, key, sep: sep3, value } = collItem;
|
|
3700
3700
|
const keyProps = resolveProps.resolveProps(start, {
|
|
3701
3701
|
indicator: "explicit-key-ind",
|
|
3702
|
-
next: key ??
|
|
3702
|
+
next: key ?? sep3?.[0],
|
|
3703
3703
|
offset,
|
|
3704
3704
|
onError,
|
|
3705
3705
|
parentIndent: bm.indent,
|
|
@@ -3713,7 +3713,7 @@ var require_resolve_block_map = __commonJS((exports) => {
|
|
|
3713
3713
|
else if ("indent" in key && key.indent !== bm.indent)
|
|
3714
3714
|
onError(offset, "BAD_INDENT", startColMsg);
|
|
3715
3715
|
}
|
|
3716
|
-
if (!keyProps.anchor && !keyProps.tag && !
|
|
3716
|
+
if (!keyProps.anchor && !keyProps.tag && !sep3) {
|
|
3717
3717
|
commentEnd = keyProps.end;
|
|
3718
3718
|
if (keyProps.comment) {
|
|
3719
3719
|
if (map.comment)
|
|
@@ -3738,7 +3738,7 @@ var require_resolve_block_map = __commonJS((exports) => {
|
|
|
3738
3738
|
ctx.atKey = false;
|
|
3739
3739
|
if (utilMapIncludes.mapIncludes(ctx, map.items, keyNode))
|
|
3740
3740
|
onError(keyStart, "DUPLICATE_KEY", "Map keys must be unique");
|
|
3741
|
-
const valueProps = resolveProps.resolveProps(
|
|
3741
|
+
const valueProps = resolveProps.resolveProps(sep3 ?? [], {
|
|
3742
3742
|
indicator: "map-value-ind",
|
|
3743
3743
|
next: value,
|
|
3744
3744
|
offset: keyNode.range[2],
|
|
@@ -3754,7 +3754,7 @@ var require_resolve_block_map = __commonJS((exports) => {
|
|
|
3754
3754
|
if (ctx.options.strict && keyProps.start < valueProps.found.offset - 1024)
|
|
3755
3755
|
onError(keyNode.range, "KEY_OVER_1024_CHARS", "The : indicator must be at most 1024 chars after the start of an implicit block mapping key");
|
|
3756
3756
|
}
|
|
3757
|
-
const valueNode = value ? composeNode(ctx, value, valueProps, onError) : composeEmptyNode(ctx, offset,
|
|
3757
|
+
const valueNode = value ? composeNode(ctx, value, valueProps, onError) : composeEmptyNode(ctx, offset, sep3, null, valueProps, onError);
|
|
3758
3758
|
if (ctx.schema.compat)
|
|
3759
3759
|
utilFlowIndentCheck.flowIndentCheck(bm.indent, value, onError);
|
|
3760
3760
|
offset = valueNode.range[2];
|
|
@@ -3840,7 +3840,7 @@ var require_resolve_end = __commonJS((exports) => {
|
|
|
3840
3840
|
let comment = "";
|
|
3841
3841
|
if (end) {
|
|
3842
3842
|
let hasSpace = false;
|
|
3843
|
-
let
|
|
3843
|
+
let sep3 = "";
|
|
3844
3844
|
for (const token of end) {
|
|
3845
3845
|
const { source, type } = token;
|
|
3846
3846
|
switch (type) {
|
|
@@ -3854,13 +3854,13 @@ var require_resolve_end = __commonJS((exports) => {
|
|
|
3854
3854
|
if (!comment)
|
|
3855
3855
|
comment = cb;
|
|
3856
3856
|
else
|
|
3857
|
-
comment +=
|
|
3858
|
-
|
|
3857
|
+
comment += sep3 + cb;
|
|
3858
|
+
sep3 = "";
|
|
3859
3859
|
break;
|
|
3860
3860
|
}
|
|
3861
3861
|
case "newline":
|
|
3862
3862
|
if (comment)
|
|
3863
|
-
|
|
3863
|
+
sep3 += source;
|
|
3864
3864
|
hasSpace = true;
|
|
3865
3865
|
break;
|
|
3866
3866
|
default:
|
|
@@ -3900,18 +3900,18 @@ var require_resolve_flow_collection = __commonJS((exports) => {
|
|
|
3900
3900
|
let offset = fc.offset + fc.start.source.length;
|
|
3901
3901
|
for (let i = 0;i < fc.items.length; ++i) {
|
|
3902
3902
|
const collItem = fc.items[i];
|
|
3903
|
-
const { start, key, sep:
|
|
3903
|
+
const { start, key, sep: sep3, value } = collItem;
|
|
3904
3904
|
const props = resolveProps.resolveProps(start, {
|
|
3905
3905
|
flow: fcName,
|
|
3906
3906
|
indicator: "explicit-key-ind",
|
|
3907
|
-
next: key ??
|
|
3907
|
+
next: key ?? sep3?.[0],
|
|
3908
3908
|
offset,
|
|
3909
3909
|
onError,
|
|
3910
3910
|
parentIndent: fc.indent,
|
|
3911
3911
|
startOnNewline: false
|
|
3912
3912
|
});
|
|
3913
3913
|
if (!props.found) {
|
|
3914
|
-
if (!props.anchor && !props.tag && !
|
|
3914
|
+
if (!props.anchor && !props.tag && !sep3 && !value) {
|
|
3915
3915
|
if (i === 0 && props.comma)
|
|
3916
3916
|
onError(props.comma, "UNEXPECTED_TOKEN", `Unexpected , in ${fcName}`);
|
|
3917
3917
|
else if (i < fc.items.length - 1)
|
|
@@ -3963,8 +3963,8 @@ var require_resolve_flow_collection = __commonJS((exports) => {
|
|
|
3963
3963
|
}
|
|
3964
3964
|
}
|
|
3965
3965
|
}
|
|
3966
|
-
if (!isMap && !
|
|
3967
|
-
const valueNode = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end,
|
|
3966
|
+
if (!isMap && !sep3 && !props.found) {
|
|
3967
|
+
const valueNode = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end, sep3, null, props, onError);
|
|
3968
3968
|
coll.items.push(valueNode);
|
|
3969
3969
|
offset = valueNode.range[2];
|
|
3970
3970
|
if (isBlock(value))
|
|
@@ -3976,7 +3976,7 @@ var require_resolve_flow_collection = __commonJS((exports) => {
|
|
|
3976
3976
|
if (isBlock(key))
|
|
3977
3977
|
onError(keyNode.range, "BLOCK_IN_FLOW", blockMsg);
|
|
3978
3978
|
ctx.atKey = false;
|
|
3979
|
-
const valueProps = resolveProps.resolveProps(
|
|
3979
|
+
const valueProps = resolveProps.resolveProps(sep3 ?? [], {
|
|
3980
3980
|
flow: fcName,
|
|
3981
3981
|
indicator: "map-value-ind",
|
|
3982
3982
|
next: value,
|
|
@@ -3987,8 +3987,8 @@ var require_resolve_flow_collection = __commonJS((exports) => {
|
|
|
3987
3987
|
});
|
|
3988
3988
|
if (valueProps.found) {
|
|
3989
3989
|
if (!isMap && !props.found && ctx.options.strict) {
|
|
3990
|
-
if (
|
|
3991
|
-
for (const st of
|
|
3990
|
+
if (sep3)
|
|
3991
|
+
for (const st of sep3) {
|
|
3992
3992
|
if (st === valueProps.found)
|
|
3993
3993
|
break;
|
|
3994
3994
|
if (st.type === "newline") {
|
|
@@ -4005,7 +4005,7 @@ var require_resolve_flow_collection = __commonJS((exports) => {
|
|
|
4005
4005
|
else
|
|
4006
4006
|
onError(valueProps.start, "MISSING_CHAR", `Missing , or : between ${fcName} items`);
|
|
4007
4007
|
}
|
|
4008
|
-
const valueNode = value ? composeNode(ctx, value, valueProps, onError) : valueProps.found ? composeEmptyNode(ctx, valueProps.end,
|
|
4008
|
+
const valueNode = value ? composeNode(ctx, value, valueProps, onError) : valueProps.found ? composeEmptyNode(ctx, valueProps.end, sep3, null, valueProps, onError) : null;
|
|
4009
4009
|
if (valueNode) {
|
|
4010
4010
|
if (isBlock(value))
|
|
4011
4011
|
onError(valueNode.range, "BLOCK_IN_FLOW", blockMsg);
|
|
@@ -4182,7 +4182,7 @@ var require_resolve_block_scalar = __commonJS((exports) => {
|
|
|
4182
4182
|
chompStart = i + 1;
|
|
4183
4183
|
}
|
|
4184
4184
|
let value = "";
|
|
4185
|
-
let
|
|
4185
|
+
let sep3 = "";
|
|
4186
4186
|
let prevMoreIndented = false;
|
|
4187
4187
|
for (let i = 0;i < contentStart; ++i)
|
|
4188
4188
|
value += lines[i][0].slice(trimIndent) + `
|
|
@@ -4200,33 +4200,33 @@ var require_resolve_block_scalar = __commonJS((exports) => {
|
|
|
4200
4200
|
indent = "";
|
|
4201
4201
|
}
|
|
4202
4202
|
if (type === Scalar.Scalar.BLOCK_LITERAL) {
|
|
4203
|
-
value +=
|
|
4204
|
-
|
|
4203
|
+
value += sep3 + indent.slice(trimIndent) + content;
|
|
4204
|
+
sep3 = `
|
|
4205
4205
|
`;
|
|
4206
4206
|
} else if (indent.length > trimIndent || content[0] === "\t") {
|
|
4207
|
-
if (
|
|
4208
|
-
|
|
4207
|
+
if (sep3 === " ")
|
|
4208
|
+
sep3 = `
|
|
4209
4209
|
`;
|
|
4210
|
-
else if (!prevMoreIndented &&
|
|
4210
|
+
else if (!prevMoreIndented && sep3 === `
|
|
4211
4211
|
`)
|
|
4212
|
-
|
|
4212
|
+
sep3 = `
|
|
4213
4213
|
|
|
4214
4214
|
`;
|
|
4215
|
-
value +=
|
|
4216
|
-
|
|
4215
|
+
value += sep3 + indent.slice(trimIndent) + content;
|
|
4216
|
+
sep3 = `
|
|
4217
4217
|
`;
|
|
4218
4218
|
prevMoreIndented = true;
|
|
4219
4219
|
} else if (content === "") {
|
|
4220
|
-
if (
|
|
4220
|
+
if (sep3 === `
|
|
4221
4221
|
`)
|
|
4222
4222
|
value += `
|
|
4223
4223
|
`;
|
|
4224
4224
|
else
|
|
4225
|
-
|
|
4225
|
+
sep3 = `
|
|
4226
4226
|
`;
|
|
4227
4227
|
} else {
|
|
4228
|
-
value +=
|
|
4229
|
-
|
|
4228
|
+
value += sep3 + content;
|
|
4229
|
+
sep3 = " ";
|
|
4230
4230
|
prevMoreIndented = false;
|
|
4231
4231
|
}
|
|
4232
4232
|
}
|
|
@@ -4406,27 +4406,27 @@ var require_resolve_flow_scalar = __commonJS((exports) => {
|
|
|
4406
4406
|
trimBoth = /^[ \t]+|[ \t]+$/g;
|
|
4407
4407
|
}
|
|
4408
4408
|
let res = match2[1].replace(trimEnd, "");
|
|
4409
|
-
let
|
|
4409
|
+
let sep3 = " ";
|
|
4410
4410
|
let pos = line2.lastIndex;
|
|
4411
4411
|
while (match2 = line2.exec(source)) {
|
|
4412
4412
|
const lm = match2[1].replace(trimBoth, "");
|
|
4413
4413
|
if (lm === "") {
|
|
4414
|
-
if (
|
|
4414
|
+
if (sep3 === `
|
|
4415
4415
|
`)
|
|
4416
|
-
res +=
|
|
4416
|
+
res += sep3;
|
|
4417
4417
|
else
|
|
4418
|
-
|
|
4418
|
+
sep3 = `
|
|
4419
4419
|
`;
|
|
4420
4420
|
} else {
|
|
4421
|
-
res +=
|
|
4422
|
-
|
|
4421
|
+
res += sep3 + lm;
|
|
4422
|
+
sep3 = " ";
|
|
4423
4423
|
}
|
|
4424
4424
|
pos = line2.lastIndex;
|
|
4425
4425
|
}
|
|
4426
4426
|
const last = /[ \t]*(.*)/sy;
|
|
4427
4427
|
last.lastIndex = pos;
|
|
4428
4428
|
match2 = last.exec(source);
|
|
4429
|
-
return res +
|
|
4429
|
+
return res + sep3 + (match2?.[1] ?? "");
|
|
4430
4430
|
}
|
|
4431
4431
|
function doubleQuotedValue(source, onError) {
|
|
4432
4432
|
let res = "";
|
|
@@ -5207,14 +5207,14 @@ var require_cst_stringify = __commonJS((exports) => {
|
|
|
5207
5207
|
}
|
|
5208
5208
|
}
|
|
5209
5209
|
}
|
|
5210
|
-
function stringifyItem({ start, key, sep:
|
|
5210
|
+
function stringifyItem({ start, key, sep: sep3, value }) {
|
|
5211
5211
|
let res = "";
|
|
5212
5212
|
for (const st of start)
|
|
5213
5213
|
res += st.source;
|
|
5214
5214
|
if (key)
|
|
5215
5215
|
res += stringifyToken(key);
|
|
5216
|
-
if (
|
|
5217
|
-
for (const st of
|
|
5216
|
+
if (sep3)
|
|
5217
|
+
for (const st of sep3)
|
|
5218
5218
|
res += st.source;
|
|
5219
5219
|
if (value)
|
|
5220
5220
|
res += stringifyToken(value);
|
|
@@ -6362,18 +6362,18 @@ var require_parser = __commonJS((exports) => {
|
|
|
6362
6362
|
if (this.type === "map-value-ind") {
|
|
6363
6363
|
const prev = getPrevProps(this.peek(2));
|
|
6364
6364
|
const start = getFirstKeyStartProps(prev);
|
|
6365
|
-
let
|
|
6365
|
+
let sep3;
|
|
6366
6366
|
if (scalar.end) {
|
|
6367
|
-
|
|
6368
|
-
|
|
6367
|
+
sep3 = scalar.end;
|
|
6368
|
+
sep3.push(this.sourceToken);
|
|
6369
6369
|
delete scalar.end;
|
|
6370
6370
|
} else
|
|
6371
|
-
|
|
6371
|
+
sep3 = [this.sourceToken];
|
|
6372
6372
|
const map = {
|
|
6373
6373
|
type: "block-map",
|
|
6374
6374
|
offset: scalar.offset,
|
|
6375
6375
|
indent: scalar.indent,
|
|
6376
|
-
items: [{ start, key: scalar, sep:
|
|
6376
|
+
items: [{ start, key: scalar, sep: sep3 }]
|
|
6377
6377
|
};
|
|
6378
6378
|
this.onKeyLine = true;
|
|
6379
6379
|
this.stack[this.stack.length - 1] = map;
|
|
@@ -6527,15 +6527,15 @@ var require_parser = __commonJS((exports) => {
|
|
|
6527
6527
|
} else if (isFlowToken(it.key) && !includesToken(it.sep, "newline")) {
|
|
6528
6528
|
const start2 = getFirstKeyStartProps(it.start);
|
|
6529
6529
|
const key = it.key;
|
|
6530
|
-
const
|
|
6531
|
-
|
|
6530
|
+
const sep3 = it.sep;
|
|
6531
|
+
sep3.push(this.sourceToken);
|
|
6532
6532
|
delete it.key;
|
|
6533
6533
|
delete it.sep;
|
|
6534
6534
|
this.stack.push({
|
|
6535
6535
|
type: "block-map",
|
|
6536
6536
|
offset: this.offset,
|
|
6537
6537
|
indent: this.indent,
|
|
6538
|
-
items: [{ start: start2, key, sep:
|
|
6538
|
+
items: [{ start: start2, key, sep: sep3 }]
|
|
6539
6539
|
});
|
|
6540
6540
|
} else if (start.length > 0) {
|
|
6541
6541
|
it.sep = it.sep.concat(start, this.sourceToken);
|
|
@@ -6729,13 +6729,13 @@ var require_parser = __commonJS((exports) => {
|
|
|
6729
6729
|
const prev = getPrevProps(parent);
|
|
6730
6730
|
const start = getFirstKeyStartProps(prev);
|
|
6731
6731
|
fixFlowSeqItems(fc);
|
|
6732
|
-
const
|
|
6733
|
-
|
|
6732
|
+
const sep3 = fc.end.splice(1, fc.end.length);
|
|
6733
|
+
sep3.push(this.sourceToken);
|
|
6734
6734
|
const map = {
|
|
6735
6735
|
type: "block-map",
|
|
6736
6736
|
offset: fc.offset,
|
|
6737
6737
|
indent: fc.indent,
|
|
6738
|
-
items: [{ start, key: fc, sep:
|
|
6738
|
+
items: [{ start, key: fc, sep: sep3 }]
|
|
6739
6739
|
};
|
|
6740
6740
|
this.onKeyLine = true;
|
|
6741
6741
|
this.stack[this.stack.length - 1] = map;
|
|
@@ -6959,8 +6959,8 @@ var require_public_api = __commonJS((exports) => {
|
|
|
6959
6959
|
});
|
|
6960
6960
|
|
|
6961
6961
|
// src/server/app.ts
|
|
6962
|
-
import { existsSync as
|
|
6963
|
-
import { join as
|
|
6962
|
+
import { existsSync as existsSync19, statSync as statSync13 } from "fs";
|
|
6963
|
+
import { join as join30, resolve as resolve6, sep as sep10 } from "path";
|
|
6964
6964
|
|
|
6965
6965
|
// node_modules/hono/dist/request/constants.js
|
|
6966
6966
|
var GET_MATCH_RESULT = Symbol();
|
|
@@ -13476,10 +13476,530 @@ function mount2(ctx) {
|
|
|
13476
13476
|
});
|
|
13477
13477
|
}
|
|
13478
13478
|
|
|
13479
|
+
// src/server/routes/checkpoints.ts
|
|
13480
|
+
import { execFileSync } from "child_process";
|
|
13481
|
+
import { appendFileSync, copyFileSync, cpSync, existsSync as existsSync5, lstatSync, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync7, realpathSync as realpathSync4, rmSync, statSync as statSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
13482
|
+
import { join as join9, sep as sep2 } from "path";
|
|
13483
|
+
|
|
13484
|
+
// src/server/audit.ts
|
|
13485
|
+
var clip = (s, n2) => s === undefined ? null : s.slice(0, n2);
|
|
13486
|
+
function audit3(db, e) {
|
|
13487
|
+
try {
|
|
13488
|
+
db.insert(actions).values({ ts: new Date().toISOString(), actor: e.actor ?? "local", kind: e.kind, target: clip(e.target, 500), result: e.result, detail: clip(e.detail, 2000) }).run();
|
|
13489
|
+
return true;
|
|
13490
|
+
} catch {
|
|
13491
|
+
return false;
|
|
13492
|
+
}
|
|
13493
|
+
}
|
|
13494
|
+
|
|
13495
|
+
// src/server/routes/cost_ledger.ts
|
|
13496
|
+
import { existsSync as existsSync4, readdirSync as readdirSync3, readFileSync as readFileSync6, statSync as statSync3 } from "fs";
|
|
13497
|
+
import { join as join8 } from "path";
|
|
13498
|
+
var isNum = (v) => typeof v === "number" && Number.isFinite(v);
|
|
13499
|
+
var isObj2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
13500
|
+
var r6 = (n2) => Math.round(n2 * 1e6) / 1e6;
|
|
13501
|
+
var MEASURED_FIELDS = ["cost_usd", "input_tokens", "output_tokens", "cache_read_tokens", "cache_creation_tokens"];
|
|
13502
|
+
var BUDGET_WARN_FRACTION = 0.8;
|
|
13503
|
+
var NOT_MEASURED = "not measured";
|
|
13504
|
+
function readJson(path) {
|
|
13505
|
+
try {
|
|
13506
|
+
return JSON.parse(readFileSync6(path, "utf8"));
|
|
13507
|
+
} catch {
|
|
13508
|
+
return null;
|
|
13509
|
+
}
|
|
13510
|
+
}
|
|
13511
|
+
function recordIsMeasured(rec, skip) {
|
|
13512
|
+
if (!isObj2(rec))
|
|
13513
|
+
return false;
|
|
13514
|
+
return MEASURED_FIELDS.some((k) => k !== skip && isNum(rec[k]) && rec[k] !== 0);
|
|
13515
|
+
}
|
|
13516
|
+
var DEFAULT_PRICING = {
|
|
13517
|
+
fable: { input: 10, output: 50 },
|
|
13518
|
+
"claude-fable-5": { input: 10, output: 50 },
|
|
13519
|
+
opus: { input: 5, output: 25 },
|
|
13520
|
+
sonnet: { input: 2, output: 10 },
|
|
13521
|
+
haiku: { input: 1, output: 5 },
|
|
13522
|
+
"gpt-5.3-codex": { input: 1.5, output: 12 },
|
|
13523
|
+
"gpt-5.6-sol": { input: 2.5, output: 20 },
|
|
13524
|
+
"gpt-5.6-terra": { input: 1.5, output: 12 },
|
|
13525
|
+
"gpt-5.6-luna": { input: 0.5, output: 4 }
|
|
13526
|
+
};
|
|
13527
|
+
function pricing(lokiDir) {
|
|
13528
|
+
const d = readJson(join8(lokiDir, "pricing.json"));
|
|
13529
|
+
const models = isObj2(d) && isObj2(d.models) ? d.models : {};
|
|
13530
|
+
return { ...DEFAULT_PRICING, ...models };
|
|
13531
|
+
}
|
|
13532
|
+
function modelCost(table, model, inp, out, cr = 0, cw = 0) {
|
|
13533
|
+
const p = table[model.toLowerCase()] ?? table.sonnet ?? {};
|
|
13534
|
+
const ir = isNum(p.input) ? p.input : 3;
|
|
13535
|
+
const or2 = isNum(p.output) ? p.output : 15;
|
|
13536
|
+
const crr = isNum(p.cache_read) ? p.cache_read : ir * 0.1;
|
|
13537
|
+
const cwr = isNum(p.cache_write) ? p.cache_write : ir * 1.25;
|
|
13538
|
+
return r6((inp * ir + out * or2 + cr * crr + cw * cwr) / 1e6);
|
|
13539
|
+
}
|
|
13540
|
+
function iterationRecords(lokiDir) {
|
|
13541
|
+
const dir = join8(lokiDir, "metrics", "efficiency");
|
|
13542
|
+
let names = [];
|
|
13543
|
+
try {
|
|
13544
|
+
names = readdirSync3(dir).filter((n2) => /^iteration-.*\.json$/.test(n2)).sort();
|
|
13545
|
+
} catch {
|
|
13546
|
+
return [];
|
|
13547
|
+
}
|
|
13548
|
+
const out = [];
|
|
13549
|
+
for (const n2 of names) {
|
|
13550
|
+
const d = readJson(join8(dir, n2));
|
|
13551
|
+
if (isObj2(d))
|
|
13552
|
+
out.push(d);
|
|
13553
|
+
}
|
|
13554
|
+
return out;
|
|
13555
|
+
}
|
|
13556
|
+
var num0 = (v) => isNum(v) ? v : 0;
|
|
13557
|
+
var priced = (table, d) => d.cost_usd === null || d.cost_usd === undefined ? modelCost(table, String(d.model ?? "sonnet").toLowerCase(), num0(d.input_tokens), num0(d.output_tokens), num0(d.cache_read_tokens), num0(d.cache_creation_tokens)) : num0(d.cost_usd);
|
|
13558
|
+
function costSnapshot(lokiDir) {
|
|
13559
|
+
const table = pricing(lokiDir);
|
|
13560
|
+
let tIn = 0, tOut = 0, tCr = 0, tCw = 0, est = 0;
|
|
13561
|
+
let recorded = false, unknownUsd = false;
|
|
13562
|
+
const byPhase = {}, byModel = {};
|
|
13563
|
+
for (const d of iterationRecords(lokiDir)) {
|
|
13564
|
+
const measured = recordIsMeasured(d);
|
|
13565
|
+
if (measured)
|
|
13566
|
+
recorded = true;
|
|
13567
|
+
const tokMeasured = recordIsMeasured(d, "cost_usd");
|
|
13568
|
+
const inp = num0(d.input_tokens), out = num0(d.output_tokens), cr = num0(d.cache_read_tokens), cw = num0(d.cache_creation_tokens);
|
|
13569
|
+
const model = String(d.model ?? "sonnet").toLowerCase();
|
|
13570
|
+
const phase = String(d.phase ?? "unknown");
|
|
13571
|
+
tIn += inp;
|
|
13572
|
+
tOut += out;
|
|
13573
|
+
tCr += cr;
|
|
13574
|
+
tCw += cw;
|
|
13575
|
+
const cost = priced(table, d);
|
|
13576
|
+
est += cost;
|
|
13577
|
+
for (const [bucket, name] of [[byPhase, phase], [byModel, model]]) {
|
|
13578
|
+
const b = bucket[name] ??= { input_tokens: 0, output_tokens: 0, cost_usd: 0, measured: false, tokens_measured: false };
|
|
13579
|
+
b.input_tokens += inp;
|
|
13580
|
+
b.output_tokens += out;
|
|
13581
|
+
b.cost_usd += cost;
|
|
13582
|
+
b.measured ||= measured;
|
|
13583
|
+
b.tokens_measured ||= tokMeasured;
|
|
13584
|
+
}
|
|
13585
|
+
}
|
|
13586
|
+
if (tIn === 0 && tOut === 0) {
|
|
13587
|
+
const ctx = readJson(join8(lokiDir, "context", "tracking.json"));
|
|
13588
|
+
const totals = isObj2(ctx) && isObj2(ctx.totals) ? ctx.totals : {};
|
|
13589
|
+
tIn = num0(totals.total_input);
|
|
13590
|
+
tOut = num0(totals.total_output);
|
|
13591
|
+
if (tIn > 0 || tOut > 0) {
|
|
13592
|
+
recorded = true;
|
|
13593
|
+
if (isNum(totals.total_cost_usd))
|
|
13594
|
+
est = totals.total_cost_usd;
|
|
13595
|
+
else
|
|
13596
|
+
unknownUsd = true;
|
|
13597
|
+
const model = String(isObj2(ctx) && ctx.provider || "unknown").toLowerCase();
|
|
13598
|
+
const per = isObj2(ctx) && Array.isArray(ctx.per_iteration) ? ctx.per_iteration : [];
|
|
13599
|
+
for (const it of per) {
|
|
13600
|
+
if (!isObj2(it))
|
|
13601
|
+
continue;
|
|
13602
|
+
const b = byModel[model] ??= { input_tokens: 0, output_tokens: 0, cost_usd: 0, measured: false, tokens_measured: true };
|
|
13603
|
+
b.input_tokens += num0(it.input_tokens);
|
|
13604
|
+
b.output_tokens += num0(it.output_tokens);
|
|
13605
|
+
if (isNum(it.cost_usd)) {
|
|
13606
|
+
b.cost_usd += it.cost_usd;
|
|
13607
|
+
b.measured = true;
|
|
13608
|
+
}
|
|
13609
|
+
}
|
|
13610
|
+
}
|
|
13611
|
+
}
|
|
13612
|
+
let limit = null, used = null, remaining = null;
|
|
13613
|
+
const bd = readJson(join8(lokiDir, "metrics", "budget.json"));
|
|
13614
|
+
if (isObj2(bd) && isNum(bd.limit)) {
|
|
13615
|
+
limit = bd.limit;
|
|
13616
|
+
if (recorded && !unknownUsd) {
|
|
13617
|
+
used = est;
|
|
13618
|
+
remaining = Math.max(0, limit - used);
|
|
13619
|
+
}
|
|
13620
|
+
}
|
|
13621
|
+
const readIn = tIn + tCr;
|
|
13622
|
+
const fmt = (m) => Object.fromEntries(Object.entries(m).map(([k, v]) => [k, {
|
|
13623
|
+
input_tokens: v.tokens_measured ? v.input_tokens : null,
|
|
13624
|
+
output_tokens: v.tokens_measured ? v.output_tokens : null,
|
|
13625
|
+
cost_usd: v.measured ? r6(v.cost_usd) : null
|
|
13626
|
+
}]));
|
|
13627
|
+
return {
|
|
13628
|
+
total_input_tokens: recorded ? tIn : null,
|
|
13629
|
+
total_output_tokens: recorded ? tOut : null,
|
|
13630
|
+
total_cache_read_tokens: recorded ? tCr : null,
|
|
13631
|
+
total_cache_creation_tokens: recorded ? tCw : null,
|
|
13632
|
+
total_tokens: recorded ? tIn + tOut + tCr + tCw : null,
|
|
13633
|
+
cache_hit_ratio: readIn > 0 ? Math.round(tCr / readIn * 1e4) / 1e4 : null,
|
|
13634
|
+
estimated_cost_usd: recorded && !unknownUsd ? r6(est) : null,
|
|
13635
|
+
cost_recorded: recorded,
|
|
13636
|
+
by_phase: fmt(byPhase),
|
|
13637
|
+
by_model: fmt(byModel),
|
|
13638
|
+
budget_limit: limit,
|
|
13639
|
+
budget_used: limit !== null && used !== null ? r6(used) : null,
|
|
13640
|
+
budget_remaining: remaining !== null ? r6(remaining) : null,
|
|
13641
|
+
...recorded ? {} : { not_measured: NOT_MEASURED }
|
|
13642
|
+
};
|
|
13643
|
+
}
|
|
13644
|
+
function budgetStatus(used, limit) {
|
|
13645
|
+
if (limit === null || limit <= 0)
|
|
13646
|
+
return "none";
|
|
13647
|
+
if (used === null)
|
|
13648
|
+
return "unknown";
|
|
13649
|
+
if (used >= limit)
|
|
13650
|
+
return "exceeded";
|
|
13651
|
+
return used >= BUDGET_WARN_FRACTION * limit ? "warn" : "ok";
|
|
13652
|
+
}
|
|
13653
|
+
function budgetSnapshot(lokiDir) {
|
|
13654
|
+
const table = pricing(lokiDir);
|
|
13655
|
+
let total = 0, recorded = false, unmeasured = 0;
|
|
13656
|
+
for (const d of iterationRecords(lokiDir)) {
|
|
13657
|
+
if (recordIsMeasured(d))
|
|
13658
|
+
recorded = true;
|
|
13659
|
+
else
|
|
13660
|
+
unmeasured++;
|
|
13661
|
+
total += priced(table, d);
|
|
13662
|
+
}
|
|
13663
|
+
let limit = null;
|
|
13664
|
+
const bd = readJson(join8(lokiDir, "metrics", "budget.json"));
|
|
13665
|
+
if (isObj2(bd)) {
|
|
13666
|
+
const l = bd.limit || bd.budget_limit;
|
|
13667
|
+
limit = isNum(l) ? l : null;
|
|
13668
|
+
}
|
|
13669
|
+
if (limit === null && process.env.LOKI_BUDGET_LIMIT) {
|
|
13670
|
+
const e = Number(process.env.LOKI_BUDGET_LIMIT);
|
|
13671
|
+
if (Number.isFinite(e))
|
|
13672
|
+
limit = e;
|
|
13673
|
+
}
|
|
13674
|
+
const used = recorded ? r6(total) : null;
|
|
13675
|
+
const ok = limit !== null && limit > 0 && used !== null;
|
|
13676
|
+
const status = budgetStatus(used, limit);
|
|
13677
|
+
return {
|
|
13678
|
+
limit,
|
|
13679
|
+
used,
|
|
13680
|
+
remaining: ok ? r6(Math.max(0, limit - used)) : null,
|
|
13681
|
+
percent_used: ok ? Math.round(used / limit * 1e4) / 100 : null,
|
|
13682
|
+
status,
|
|
13683
|
+
warn_threshold_percent: Math.round(BUDGET_WARN_FRACTION * 100),
|
|
13684
|
+
exceeded: status === "exceeded",
|
|
13685
|
+
partial: recorded && unmeasured > 0
|
|
13686
|
+
};
|
|
13687
|
+
}
|
|
13688
|
+
function costTimeline(lokiDir) {
|
|
13689
|
+
const table = pricing(lokiDir);
|
|
13690
|
+
const recs = iterationRecords(lokiDir).map((d, i) => ({ d, i })).sort((a, b) => num0(Number(a.d.iteration)) - num0(Number(b.d.iteration)) || a.i - b.i).map((x) => x.d);
|
|
13691
|
+
const iterations = [];
|
|
13692
|
+
let cum = 0, recorded = false, unmeasured = 0;
|
|
13693
|
+
for (const d of recs) {
|
|
13694
|
+
const measured = recordIsMeasured(d);
|
|
13695
|
+
if (measured)
|
|
13696
|
+
recorded = true;
|
|
13697
|
+
else
|
|
13698
|
+
unmeasured++;
|
|
13699
|
+
const model = String(d.model ?? "sonnet").toLowerCase();
|
|
13700
|
+
cum += priced(table, d);
|
|
13701
|
+
iterations.push({
|
|
13702
|
+
iteration: d.iteration ?? null,
|
|
13703
|
+
timestamp: d.timestamp ?? null,
|
|
13704
|
+
model: d.model ? model : null,
|
|
13705
|
+
phase: d.phase ?? "unknown",
|
|
13706
|
+
provider: d.provider ?? null,
|
|
13707
|
+
input_tokens: measured ? num0(d.input_tokens) : null,
|
|
13708
|
+
output_tokens: measured ? num0(d.output_tokens) : null,
|
|
13709
|
+
cost_usd: measured ? r6(priced(table, d)) : null,
|
|
13710
|
+
cumulative_usd: measured ? r6(cum) : null
|
|
13711
|
+
});
|
|
13712
|
+
}
|
|
13713
|
+
const runs2 = [];
|
|
13714
|
+
let projectTotal = 0, runsMeasured = 0;
|
|
13715
|
+
const proofs = join8(lokiDir, "proofs");
|
|
13716
|
+
let entries = [];
|
|
13717
|
+
try {
|
|
13718
|
+
entries = readdirSync3(proofs).sort();
|
|
13719
|
+
} catch {}
|
|
13720
|
+
for (const e of entries) {
|
|
13721
|
+
try {
|
|
13722
|
+
if (!statSync3(join8(proofs, e)).isDirectory())
|
|
13723
|
+
continue;
|
|
13724
|
+
} catch {
|
|
13725
|
+
continue;
|
|
13726
|
+
}
|
|
13727
|
+
const p = readJson(join8(proofs, e, "proof.json"));
|
|
13728
|
+
if (!isObj2(p))
|
|
13729
|
+
continue;
|
|
13730
|
+
const cost = isObj2(p.cost) ? p.cost : {};
|
|
13731
|
+
const usd = isNum(cost.usd) ? cost.usd : null;
|
|
13732
|
+
if (usd !== null) {
|
|
13733
|
+
projectTotal += usd;
|
|
13734
|
+
runsMeasured++;
|
|
13735
|
+
}
|
|
13736
|
+
runs2.push({
|
|
13737
|
+
run_id: p.run_id ?? e,
|
|
13738
|
+
generated_at: p.generated_at ?? null,
|
|
13739
|
+
model: isObj2(p.provider) ? p.provider.model ?? null : null,
|
|
13740
|
+
cost_usd: usd !== null ? r6(usd) : null,
|
|
13741
|
+
cost_partial: cost.cost_partial === true,
|
|
13742
|
+
files_changed: isObj2(p.files_changed) ? p.files_changed.count ?? null : null,
|
|
13743
|
+
final_verdict: isObj2(p.council) ? p.council.final_verdict ?? null : null
|
|
13744
|
+
});
|
|
13745
|
+
}
|
|
13746
|
+
runs2.sort((a, b) => String(b.generated_at ?? "").localeCompare(String(a.generated_at ?? "")));
|
|
13747
|
+
return {
|
|
13748
|
+
current_run: { iterations, total_usd: recorded ? r6(cum) : null, cost_recorded: recorded, partial: recorded && unmeasured > 0, unmeasured_iterations: unmeasured },
|
|
13749
|
+
runs: runs2,
|
|
13750
|
+
runs_count: runs2.length,
|
|
13751
|
+
project_total_usd: runsMeasured ? r6(projectTotal) : null,
|
|
13752
|
+
project_total_partial: runsMeasured > 0 && runsMeasured < runs2.length,
|
|
13753
|
+
budget: budgetSnapshot(lokiDir),
|
|
13754
|
+
...recorded || runsMeasured ? {} : { not_measured: NOT_MEASURED }
|
|
13755
|
+
};
|
|
13756
|
+
}
|
|
13757
|
+
var lokiDirOf = (repoDir) => join8(repoDir, ".loki");
|
|
13758
|
+
var lokiDirExists = (repoDir) => existsSync4(lokiDirOf(repoDir));
|
|
13759
|
+
function mount3(ctx) {
|
|
13760
|
+
const deny = { error: "loopback only without a token" };
|
|
13761
|
+
ctx.app.get("/v1/cost/snapshot", (c) => probeAllowed(ctx, c) ? c.json(costSnapshot(lokiDirOf(ctx.repoDir))) : c.json(deny, 403));
|
|
13762
|
+
ctx.app.get("/v1/cost/timeline", (c) => probeAllowed(ctx, c) ? c.json(costTimeline(lokiDirOf(ctx.repoDir))) : c.json(deny, 403));
|
|
13763
|
+
}
|
|
13764
|
+
|
|
13765
|
+
// src/server/routes/checkpoints.ts
|
|
13766
|
+
var isObj3 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
13767
|
+
var str2 = (v) => typeof v === "string" && v !== "" ? v : null;
|
|
13768
|
+
var SAFE_ID = /^[A-Za-z0-9_-]{1,128}$/;
|
|
13769
|
+
var MAX_CHECKPOINTS = 50;
|
|
13770
|
+
var validCheckpointId = (id) => SAFE_ID.test(id);
|
|
13771
|
+
var storeOf = (repoDir) => join9(repoDir, ".loki", "state", "checkpoints");
|
|
13772
|
+
var lokiOf = (repoDir) => join9(repoDir, ".loki");
|
|
13773
|
+
var readObj = (f) => {
|
|
13774
|
+
try {
|
|
13775
|
+
const d = JSON.parse(readFileSync7(f, "utf8"));
|
|
13776
|
+
return isObj3(d) ? d : null;
|
|
13777
|
+
} catch {
|
|
13778
|
+
return null;
|
|
13779
|
+
}
|
|
13780
|
+
};
|
|
13781
|
+
var stamp = (d) => d.toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "").replace("T", "-");
|
|
13782
|
+
function listCheckpoints(repoDir, limit = 20) {
|
|
13783
|
+
const store = storeOf(repoDir);
|
|
13784
|
+
let lines = [];
|
|
13785
|
+
try {
|
|
13786
|
+
lines = readFileSync7(join9(store, "index.jsonl"), "utf8").split(`
|
|
13787
|
+
`);
|
|
13788
|
+
} catch {
|
|
13789
|
+
return [];
|
|
13790
|
+
}
|
|
13791
|
+
const out = [];
|
|
13792
|
+
for (const line2 of lines) {
|
|
13793
|
+
if (!line2.trim())
|
|
13794
|
+
continue;
|
|
13795
|
+
let raw2;
|
|
13796
|
+
try {
|
|
13797
|
+
raw2 = JSON.parse(line2);
|
|
13798
|
+
} catch {
|
|
13799
|
+
continue;
|
|
13800
|
+
}
|
|
13801
|
+
if (!isObj3(raw2))
|
|
13802
|
+
continue;
|
|
13803
|
+
const id = String(raw2.id ?? "");
|
|
13804
|
+
if (!validCheckpointId(id))
|
|
13805
|
+
continue;
|
|
13806
|
+
const it = raw2.iter ?? raw2.iteration;
|
|
13807
|
+
const cp = {
|
|
13808
|
+
id,
|
|
13809
|
+
created_at: str2(raw2.ts) ?? str2(raw2.created_at) ?? str2(raw2.timestamp),
|
|
13810
|
+
git_sha: str2(raw2.sha) ?? str2(raw2.git_sha),
|
|
13811
|
+
message: str2(raw2.task) ?? str2(raw2.message) ?? str2(raw2.task_description),
|
|
13812
|
+
iteration: Number.isInteger(it) ? it : null
|
|
13813
|
+
};
|
|
13814
|
+
const meta = readObj(join9(store, id, "metadata.json"));
|
|
13815
|
+
if (meta) {
|
|
13816
|
+
cp.git_branch = str2(meta.git_branch);
|
|
13817
|
+
cp.provider = str2(meta.provider);
|
|
13818
|
+
cp.phase = str2(meta.phase);
|
|
13819
|
+
cp.message ??= str2(meta.task_description);
|
|
13820
|
+
cp.git_sha ??= str2(meta.git_sha);
|
|
13821
|
+
cp.created_at ??= str2(meta.timestamp);
|
|
13822
|
+
cp.files_count = countFiles(join9(store, id));
|
|
13823
|
+
}
|
|
13824
|
+
out.push(cp);
|
|
13825
|
+
}
|
|
13826
|
+
return out.reverse().slice(0, limit);
|
|
13827
|
+
}
|
|
13828
|
+
function countFiles(dir) {
|
|
13829
|
+
let n2 = 0;
|
|
13830
|
+
const walk = (d, top) => {
|
|
13831
|
+
for (const e of readdirSync4(d, { withFileTypes: true })) {
|
|
13832
|
+
if (e.isDirectory())
|
|
13833
|
+
walk(join9(d, e.name), false);
|
|
13834
|
+
else if (e.isFile() && !(top && e.name === "metadata.json"))
|
|
13835
|
+
n2++;
|
|
13836
|
+
}
|
|
13837
|
+
};
|
|
13838
|
+
try {
|
|
13839
|
+
walk(dir, true);
|
|
13840
|
+
} catch {}
|
|
13841
|
+
return n2;
|
|
13842
|
+
}
|
|
13843
|
+
function getCheckpoint(repoDir, id) {
|
|
13844
|
+
return validCheckpointId(id) ? readObj(join9(storeOf(repoDir), id, "metadata.json")) : null;
|
|
13845
|
+
}
|
|
13846
|
+
var gitHead = (repoDir) => {
|
|
13847
|
+
try {
|
|
13848
|
+
return execFileSync("git", ["rev-parse", "HEAD"], { cwd: repoDir, timeout: 5000, stdio: ["ignore", "pipe", "ignore"] }).toString().trim() || null;
|
|
13849
|
+
} catch {
|
|
13850
|
+
return null;
|
|
13851
|
+
}
|
|
13852
|
+
};
|
|
13853
|
+
function createCheckpoint(repoDir, message, now = new Date) {
|
|
13854
|
+
const store = storeOf(repoDir), loki = lokiOf(repoDir);
|
|
13855
|
+
mkdirSync3(store, { recursive: true });
|
|
13856
|
+
const id = `chk-${stamp(now)}`, dir = join9(store, id);
|
|
13857
|
+
mkdirSync3(dir, { recursive: true });
|
|
13858
|
+
for (const f of ["dashboard-state.json", "session.json"]) {
|
|
13859
|
+
try {
|
|
13860
|
+
if (existsSync5(join9(loki, f)))
|
|
13861
|
+
copyFileSync(join9(loki, f), join9(dir, f));
|
|
13862
|
+
} catch {}
|
|
13863
|
+
}
|
|
13864
|
+
try {
|
|
13865
|
+
if (existsSync5(join9(loki, "queue")))
|
|
13866
|
+
cpSync(join9(loki, "queue"), join9(dir, "queue"), { recursive: true });
|
|
13867
|
+
} catch {}
|
|
13868
|
+
const meta = { id, created_at: now.toISOString(), git_sha: gitHead(repoDir), message: message || null, files: readdirSync4(dir, { withFileTypes: true }).filter((e) => e.isFile()).map((e) => e.name) };
|
|
13869
|
+
writeFileSync3(join9(dir, "metadata.json"), JSON.stringify(meta, null, 2));
|
|
13870
|
+
appendFileSync(join9(store, "index.jsonl"), `${JSON.stringify(meta)}
|
|
13871
|
+
`);
|
|
13872
|
+
const mine = readdirSync4(store, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name.startsWith("chk-")).map((e) => e.name).sort();
|
|
13873
|
+
while (mine.length > MAX_CHECKPOINTS)
|
|
13874
|
+
rmSync(join9(store, mine.shift()), { recursive: true, force: true });
|
|
13875
|
+
return meta;
|
|
13876
|
+
}
|
|
13877
|
+
function rollbackCheckpoint(repoDir, id, now = new Date) {
|
|
13878
|
+
const store = storeOf(repoDir), loki = lokiOf(repoDir);
|
|
13879
|
+
let real2 = "", realStore = "";
|
|
13880
|
+
try {
|
|
13881
|
+
real2 = realpathSync4(join9(store, id));
|
|
13882
|
+
realStore = realpathSync4(store);
|
|
13883
|
+
} catch {
|
|
13884
|
+
return null;
|
|
13885
|
+
}
|
|
13886
|
+
if (!real2.startsWith(`${realStore}${sep2}`) || !statSync4(real2).isDirectory())
|
|
13887
|
+
return null;
|
|
13888
|
+
const preId = `rb-pre-${stamp(now)}`, pre = join9(store, preId);
|
|
13889
|
+
mkdirSync3(pre, { recursive: true });
|
|
13890
|
+
const notStore = (src) => src !== store;
|
|
13891
|
+
for (const n2 of ["session.json", "dashboard-state.json", "CONTINUITY.md", "autonomy-state.json"]) {
|
|
13892
|
+
try {
|
|
13893
|
+
if (existsSync5(join9(loki, n2)))
|
|
13894
|
+
copyFileSync(join9(loki, n2), join9(pre, n2));
|
|
13895
|
+
} catch {}
|
|
13896
|
+
}
|
|
13897
|
+
for (const d of ["state", "queue"]) {
|
|
13898
|
+
try {
|
|
13899
|
+
if (existsSync5(join9(loki, d)))
|
|
13900
|
+
cpSync(join9(loki, d), join9(pre, d), { recursive: true, filter: notStore });
|
|
13901
|
+
} catch {}
|
|
13902
|
+
}
|
|
13903
|
+
const preMeta = { id: preId, created_at: now.toISOString(), message: `pre-rollback snapshot (before restoring ${id})`, created_by: "control plane rollback" };
|
|
13904
|
+
writeFileSync3(join9(pre, "metadata.json"), JSON.stringify(preMeta, null, 2));
|
|
13905
|
+
appendFileSync(join9(store, "index.jsonl"), `${JSON.stringify(preMeta)}
|
|
13906
|
+
`);
|
|
13907
|
+
let restored = 0;
|
|
13908
|
+
const errors = [];
|
|
13909
|
+
for (const e of readdirSync4(real2, { withFileTypes: true })) {
|
|
13910
|
+
if (e.name === "metadata.json" || e.name === "worktree-snapshot.txt")
|
|
13911
|
+
continue;
|
|
13912
|
+
const src = join9(real2, e.name);
|
|
13913
|
+
try {
|
|
13914
|
+
if (lstatSync(src).isSymbolicLink()) {
|
|
13915
|
+
errors.push(`${e.name}: symlink skipped`);
|
|
13916
|
+
continue;
|
|
13917
|
+
}
|
|
13918
|
+
cpSync(src, join9(loki, e.name), { recursive: true, filter: (s) => s !== join9(real2, "state", "checkpoints") });
|
|
13919
|
+
restored++;
|
|
13920
|
+
} catch (err) {
|
|
13921
|
+
errors.push(`${e.name}: ${err.message}`);
|
|
13922
|
+
}
|
|
13923
|
+
}
|
|
13924
|
+
return { id, restored, pre_rollback_snapshot: preId, errors };
|
|
13925
|
+
}
|
|
13926
|
+
function auditSummary(db, days, now = Date.now()) {
|
|
13927
|
+
const since = new Date(now - days * 86400000).toISOString();
|
|
13928
|
+
const where = gte(audit.ts, since);
|
|
13929
|
+
const total = db.select({ n: sql`count(*)` }).from(audit).where(where).get()?.n ?? 0;
|
|
13930
|
+
const by = (col) => Object.fromEntries(db.select({ k: col, n: sql`count(*)` }).from(audit).where(where).groupBy(col).all().map((r) => [r.k, r.n]));
|
|
13931
|
+
return {
|
|
13932
|
+
period_days: days,
|
|
13933
|
+
total_events: total,
|
|
13934
|
+
by_action: by(audit.action),
|
|
13935
|
+
by_user: by(audit.actor),
|
|
13936
|
+
successful_events: null,
|
|
13937
|
+
failed_events: null,
|
|
13938
|
+
by_resource_type: null,
|
|
13939
|
+
recent_failures: null,
|
|
13940
|
+
not_measured: NOT_MEASURED
|
|
13941
|
+
};
|
|
13942
|
+
}
|
|
13943
|
+
var parseDays = (v) => {
|
|
13944
|
+
const n2 = Number.parseInt(v ?? "7", 10);
|
|
13945
|
+
return Number.isFinite(n2) && n2 >= 1 && n2 <= 3650 ? n2 : 7;
|
|
13946
|
+
};
|
|
13947
|
+
var parseLimit = (v) => {
|
|
13948
|
+
const n2 = Number.parseInt(v ?? "20", 10);
|
|
13949
|
+
return Number.isFinite(n2) ? Math.min(Math.max(n2, 1), 200) : 20;
|
|
13950
|
+
};
|
|
13951
|
+
function mount4(ctx) {
|
|
13952
|
+
const deny = { error: "loopback only without a token" };
|
|
13953
|
+
const { app, act, db, repoDir } = ctx;
|
|
13954
|
+
app.get("/v1/checkpoints", (c) => probeAllowed(ctx, c) ? c.json({ checkpoints: listCheckpoints(repoDir, parseLimit(c.req.query("limit"))) }) : c.json(deny, 403));
|
|
13955
|
+
app.get("/v1/checkpoints/:id", (c) => {
|
|
13956
|
+
if (!probeAllowed(ctx, c))
|
|
13957
|
+
return c.json(deny, 403);
|
|
13958
|
+
const id = c.req.param("id");
|
|
13959
|
+
if (!validCheckpointId(id))
|
|
13960
|
+
return c.json({ error: "invalid checkpoint id" }, 400);
|
|
13961
|
+
const m = getCheckpoint(repoDir, id);
|
|
13962
|
+
return m ? c.json(m) : c.json({ error: "checkpoint not found" }, 404);
|
|
13963
|
+
});
|
|
13964
|
+
app.get("/v1/audit/summary", (c) => probeAllowed(ctx, c) ? c.json(auditSummary(db, parseDays(c.req.query("days")))) : c.json(deny, 403));
|
|
13965
|
+
act.post("/v1/checkpoints", async (c) => {
|
|
13966
|
+
if (!ctx.local(c))
|
|
13967
|
+
return c.json({ error: "loopback JSON requests only" }, 403);
|
|
13968
|
+
const body = await c.req.json().catch(() => ({}));
|
|
13969
|
+
const message = typeof body.message === "string" ? body.message : "";
|
|
13970
|
+
if (message.length > 500)
|
|
13971
|
+
return c.json({ error: "message must be at most 500 characters" }, 400);
|
|
13972
|
+
try {
|
|
13973
|
+
const meta = createCheckpoint(repoDir, message);
|
|
13974
|
+
audit3(db, { kind: "checkpoint.create", target: String(meta.id), result: "ok" });
|
|
13975
|
+
return c.json(meta, 201);
|
|
13976
|
+
} catch (e) {
|
|
13977
|
+
audit3(db, { kind: "checkpoint.create", result: "error", detail: e.message });
|
|
13978
|
+
return c.json({ error: "could not create the checkpoint" }, 500);
|
|
13979
|
+
}
|
|
13980
|
+
});
|
|
13981
|
+
act.post("/v1/checkpoints/:id/rollback", (c) => {
|
|
13982
|
+
if (!ctx.local(c))
|
|
13983
|
+
return c.json({ error: "loopback JSON requests only" }, 403);
|
|
13984
|
+
const id = c.req.param("id");
|
|
13985
|
+
if (!validCheckpointId(id)) {
|
|
13986
|
+
audit3(db, { kind: "checkpoint.rollback", target: id.slice(0, 128), result: "refused", detail: "invalid id" });
|
|
13987
|
+
return c.json({ error: "invalid checkpoint id" }, 400);
|
|
13988
|
+
}
|
|
13989
|
+
const r = rollbackCheckpoint(repoDir, id);
|
|
13990
|
+
if (!r) {
|
|
13991
|
+
audit3(db, { kind: "checkpoint.rollback", target: id, result: "refused", detail: "not found" });
|
|
13992
|
+
return c.json({ error: "checkpoint not found" }, 404);
|
|
13993
|
+
}
|
|
13994
|
+
audit3(db, { kind: "checkpoint.rollback", target: id, result: r.errors.length ? "partial" : "ok", detail: `restored ${r.restored}; pre-rollback snapshot ${r.pre_rollback_snapshot}` });
|
|
13995
|
+
return c.json({ ...r, message: `Restored ${r.restored} item(s) from ${id}. Prior state saved as ${r.pre_rollback_snapshot} (undo this rollback by restoring it).` });
|
|
13996
|
+
});
|
|
13997
|
+
}
|
|
13998
|
+
|
|
13479
13999
|
// src/server/routes/config.ts
|
|
13480
14000
|
import { createHash as createHash5, randomBytes } from "crypto";
|
|
13481
|
-
import { closeSync as closeSync3, fchmodSync, fsyncSync, lstatSync, openSync as openSync3, readFileSync as
|
|
13482
|
-
import { basename as basename3, dirname as dirname2, join as
|
|
14001
|
+
import { closeSync as closeSync3, fchmodSync, fsyncSync, lstatSync as lstatSync2, openSync as openSync3, readFileSync as readFileSync9, realpathSync as realpathSync5, renameSync as renameSync2, statSync as statSync6, unlinkSync, writeSync as writeSync2 } from "fs";
|
|
14002
|
+
import { basename as basename3, dirname as dirname2, join as join11, sep as sep3 } from "path";
|
|
13483
14003
|
|
|
13484
14004
|
// node_modules/yaml/dist/index.js
|
|
13485
14005
|
var composer = require_composer();
|
|
@@ -13527,22 +14047,11 @@ var $stringify = publicApi.stringify;
|
|
|
13527
14047
|
var $visit = visit.visit;
|
|
13528
14048
|
var $visitAsync = visit.visitAsync;
|
|
13529
14049
|
|
|
13530
|
-
// src/server/audit.ts
|
|
13531
|
-
var clip = (s, n2) => s === undefined ? null : s.slice(0, n2);
|
|
13532
|
-
function audit3(db, e) {
|
|
13533
|
-
try {
|
|
13534
|
-
db.insert(actions).values({ ts: new Date().toISOString(), actor: e.actor ?? "local", kind: e.kind, target: clip(e.target, 500), result: e.result, detail: clip(e.detail, 2000) }).run();
|
|
13535
|
-
return true;
|
|
13536
|
-
} catch {
|
|
13537
|
-
return false;
|
|
13538
|
-
}
|
|
13539
|
-
}
|
|
13540
|
-
|
|
13541
14050
|
// src/server/spawn.ts
|
|
13542
14051
|
import { spawn } from "child_process";
|
|
13543
|
-
import { existsSync as
|
|
14052
|
+
import { existsSync as existsSync6, readFileSync as readFileSync8, statSync as statSync5 } from "fs";
|
|
13544
14053
|
import { homedir as homedir3 } from "os";
|
|
13545
|
-
import { basename as basename2, join as
|
|
14054
|
+
import { basename as basename2, join as join10, resolve as resolve2 } from "path";
|
|
13546
14055
|
var ISSUE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,99}\/[A-Za-z0-9._-]{1,100}#[1-9][0-9]{0,8}$/;
|
|
13547
14056
|
var TASK = /^[A-Za-z0-9][A-Za-z0-9 .,_/#@+=-]{0,499}$/;
|
|
13548
14057
|
var hasDotDot = (t) => t.split(/[\\/ ]/).some((seg) => seg.startsWith(".."));
|
|
@@ -13558,8 +14067,8 @@ var BUDGET = /^(?:[1-9][0-9]{0,5}|0)(?:\.[0-9]{1,2})?$/;
|
|
|
13558
14067
|
var WORKSPACE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
|
|
13559
14068
|
function registryRepos2(env = process.env) {
|
|
13560
14069
|
try {
|
|
13561
|
-
const j = JSON.parse(
|
|
13562
|
-
return Object.values(j.projects ?? {}).map((p) => typeof p?.path === "string" ? p.path : "").filter((p) => p &&
|
|
14070
|
+
const j = JSON.parse(readFileSync8(join10(env.HOME || homedir3(), ".loki", "dashboard", "projects.json"), "utf8"));
|
|
14071
|
+
return Object.values(j.projects ?? {}).map((p) => typeof p?.path === "string" ? p.path : "").filter((p) => p && existsSync6(p) && statSync5(p).isDirectory());
|
|
13563
14072
|
} catch {
|
|
13564
14073
|
return [];
|
|
13565
14074
|
}
|
|
@@ -13635,7 +14144,7 @@ function originOk(origin) {
|
|
|
13635
14144
|
return false;
|
|
13636
14145
|
}
|
|
13637
14146
|
}
|
|
13638
|
-
function
|
|
14147
|
+
function mount5(ctx) {
|
|
13639
14148
|
const { act, db, repoDir, local } = ctx;
|
|
13640
14149
|
const inflight = new Set;
|
|
13641
14150
|
const handler = async (c) => {
|
|
@@ -13803,19 +14312,19 @@ var loki_yaml_schema_default = {
|
|
|
13803
14312
|
// src/server/routes/config.ts
|
|
13804
14313
|
var MAX_BODY = 1e5;
|
|
13805
14314
|
var MAX_FILE = 1e6;
|
|
13806
|
-
var
|
|
14315
|
+
var isObj4 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
13807
14316
|
function validateConfig(value, s = loki_yaml_schema_default, path = "") {
|
|
13808
14317
|
const at = path || "(root)";
|
|
13809
14318
|
const errs = [];
|
|
13810
14319
|
const join22 = (k) => path ? `${path}.${k}` : k;
|
|
13811
14320
|
if (s.type === "object") {
|
|
13812
|
-
if (!
|
|
14321
|
+
if (!isObj4(value))
|
|
13813
14322
|
return [`${at}: must be an object`];
|
|
13814
14323
|
for (const [k, v] of Object.entries(value)) {
|
|
13815
14324
|
const sub = Object.hasOwn(s.properties ?? {}, k) ? s.properties[k] : undefined;
|
|
13816
14325
|
if (sub)
|
|
13817
14326
|
errs.push(...validateConfig(v, sub, join22(k)));
|
|
13818
|
-
else if (
|
|
14327
|
+
else if (isObj4(s.additionalProperties))
|
|
13819
14328
|
errs.push(...validateConfig(v, s.additionalProperties, join22(k)));
|
|
13820
14329
|
else if (s.additionalProperties === false)
|
|
13821
14330
|
errs.push(`${join22(k)}: unknown key`);
|
|
@@ -13884,7 +14393,7 @@ function findSecrets(v, path = "") {
|
|
|
13884
14393
|
return looksLikeSecret(v) ? [path || "(root)"] : [];
|
|
13885
14394
|
if (Array.isArray(v))
|
|
13886
14395
|
return v.flatMap((x, i) => findSecrets(x, `${path}[${i}]`));
|
|
13887
|
-
if (
|
|
14396
|
+
if (isObj4(v)) {
|
|
13888
14397
|
return Object.entries(v).flatMap(([k, x]) => [
|
|
13889
14398
|
...looksLikeSecret(k) ? [`${path ? `${path}.` : ""}(key)`] : [],
|
|
13890
14399
|
...findSecrets(x, path ? `${path}.${k}` : k)
|
|
@@ -13893,10 +14402,10 @@ function findSecrets(v, path = "") {
|
|
|
13893
14402
|
return [];
|
|
13894
14403
|
}
|
|
13895
14404
|
var sha = (b) => createHash5("sha256").update(b).digest("hex");
|
|
13896
|
-
var canon = (v) => Array.isArray(v) ? v.map(canon) :
|
|
14405
|
+
var canon = (v) => Array.isArray(v) ? v.map(canon) : isObj4(v) ? Object.fromEntries(Object.keys(v).sort().map((k) => [k, canon(v[k])])) : v;
|
|
13897
14406
|
var deepEq = (a, b) => JSON.stringify(canon(a)) === JSON.stringify(canon(b));
|
|
13898
14407
|
function sync(doc, path, prev, next) {
|
|
13899
|
-
if (
|
|
14408
|
+
if (isObj4(next) && isObj4(prev)) {
|
|
13900
14409
|
for (const k of Object.keys(prev))
|
|
13901
14410
|
if (!Object.hasOwn(next, k))
|
|
13902
14411
|
doc.deleteIn([...path, k]);
|
|
@@ -13922,10 +14431,10 @@ function tooDeep(v, max = MAX_DEPTH) {
|
|
|
13922
14431
|
}
|
|
13923
14432
|
return false;
|
|
13924
14433
|
}
|
|
13925
|
-
var hasShell = (w) =>
|
|
14434
|
+
var hasShell = (w) => isObj4(w) && (isObj4(w.integration) && ("command" in w.integration) || Array.isArray(w.repos) && w.repos.some((r) => isObj4(r) && ("setup" in r)));
|
|
13926
14435
|
function shellLocked(prev, next) {
|
|
13927
|
-
const pw =
|
|
13928
|
-
const nw =
|
|
14436
|
+
const pw = isObj4(prev) && isObj4(prev.workspaces) ? prev.workspaces : {};
|
|
14437
|
+
const nw = isObj4(next) && isObj4(next.workspaces) ? next.workspaces : {};
|
|
13929
14438
|
for (const name of new Set([...Object.keys(pw), ...Object.keys(nw)])) {
|
|
13930
14439
|
const a = Object.hasOwn(pw, name) ? pw[name] : undefined;
|
|
13931
14440
|
const b = Object.hasOwn(nw, name) ? nw[name] : undefined;
|
|
@@ -13935,16 +14444,16 @@ function shellLocked(prev, next) {
|
|
|
13935
14444
|
return false;
|
|
13936
14445
|
}
|
|
13937
14446
|
function traversalPaths(cfg, prev = {}) {
|
|
13938
|
-
const pw =
|
|
14447
|
+
const pw = isObj4(prev) && isObj4(prev.workspaces) ? prev.workspaces : {};
|
|
13939
14448
|
const out = [];
|
|
13940
|
-
const ws =
|
|
14449
|
+
const ws = isObj4(cfg) && isObj4(cfg.workspaces) ? cfg.workspaces : {};
|
|
13941
14450
|
for (const [name, w] of Object.entries(ws)) {
|
|
13942
|
-
if (!
|
|
14451
|
+
if (!isObj4(w) || !Array.isArray(w.repos))
|
|
13943
14452
|
continue;
|
|
13944
14453
|
w.repos.forEach((r, i) => {
|
|
13945
|
-
if (
|
|
13946
|
-
const old = Object.hasOwn(pw, name) &&
|
|
13947
|
-
if (!(
|
|
14454
|
+
if (isObj4(r) && typeof r.path === "string" && r.path.split(/[\\/]+/).includes("..")) {
|
|
14455
|
+
const old = Object.hasOwn(pw, name) && isObj4(pw[name]) && Array.isArray(pw[name].repos) ? pw[name].repos[i] : undefined;
|
|
14456
|
+
if (!(isObj4(old) && old.path === r.path))
|
|
13948
14457
|
out.push(`workspaces.${name}.repos[${i}].path`);
|
|
13949
14458
|
}
|
|
13950
14459
|
});
|
|
@@ -13958,7 +14467,7 @@ function mergeKeyPaths(v) {
|
|
|
13958
14467
|
const [x, p] = stack.pop();
|
|
13959
14468
|
if (Array.isArray(x))
|
|
13960
14469
|
x.forEach((y, i) => stack.push([y, `${p}[${i}]`]));
|
|
13961
|
-
else if (
|
|
14470
|
+
else if (isObj4(x))
|
|
13962
14471
|
for (const [k, y] of Object.entries(x)) {
|
|
13963
14472
|
if (k === "<<")
|
|
13964
14473
|
out.push(p || "(root)");
|
|
@@ -14003,27 +14512,27 @@ function renderVerified(doc, next, quote = needsQuote) {
|
|
|
14003
14512
|
function resolveConfigFile(repoDir) {
|
|
14004
14513
|
let root;
|
|
14005
14514
|
try {
|
|
14006
|
-
root =
|
|
14515
|
+
root = realpathSync5(repoDir);
|
|
14007
14516
|
} catch {
|
|
14008
14517
|
return { ok: false, status: 400, error: "repo directory not found" };
|
|
14009
14518
|
}
|
|
14010
|
-
const p =
|
|
14519
|
+
const p = join11(root, "loki.yaml");
|
|
14011
14520
|
let st;
|
|
14012
14521
|
try {
|
|
14013
|
-
st =
|
|
14522
|
+
st = lstatSync2(p);
|
|
14014
14523
|
} catch {
|
|
14015
14524
|
return { ok: true, file: p, exists: false };
|
|
14016
14525
|
}
|
|
14017
14526
|
if (st.isSymbolicLink()) {
|
|
14018
14527
|
let real2;
|
|
14019
14528
|
try {
|
|
14020
|
-
real2 =
|
|
14529
|
+
real2 = realpathSync5(p);
|
|
14021
14530
|
} catch {
|
|
14022
14531
|
return { ok: false, status: 400, error: "loki.yaml is a broken symlink" };
|
|
14023
14532
|
}
|
|
14024
|
-
if (!real2.startsWith(root +
|
|
14533
|
+
if (!real2.startsWith(root + sep3))
|
|
14025
14534
|
return { ok: false, status: 400, error: "loki.yaml is a symlink that resolves outside the repo" };
|
|
14026
|
-
if (!
|
|
14535
|
+
if (!statSync6(real2).isFile())
|
|
14027
14536
|
return { ok: false, status: 422, error: "loki.yaml is not a regular file" };
|
|
14028
14537
|
return { ok: true, file: real2, exists: true };
|
|
14029
14538
|
}
|
|
@@ -14032,7 +14541,7 @@ function resolveConfigFile(repoDir) {
|
|
|
14032
14541
|
return { ok: true, file: p, exists: true };
|
|
14033
14542
|
}
|
|
14034
14543
|
function atomicWrite(file, text2, mode) {
|
|
14035
|
-
const tmp =
|
|
14544
|
+
const tmp = join11(dirname2(file), `.${basename3(file)}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`);
|
|
14036
14545
|
const fd = openSync3(tmp, "wx", mode);
|
|
14037
14546
|
try {
|
|
14038
14547
|
writeSync2(fd, text2);
|
|
@@ -14057,7 +14566,7 @@ function atomicWrite(file, text2, mode) {
|
|
|
14057
14566
|
}
|
|
14058
14567
|
var etagOf = (raw2) => `"${sha(raw2)}"`;
|
|
14059
14568
|
var norm = (h) => h ? h.trim().replace(/^W\//, "").replace(/^"|"$/g, "") : null;
|
|
14060
|
-
function
|
|
14569
|
+
function mount6(ctx) {
|
|
14061
14570
|
const { act, db, repoDir, local } = ctx;
|
|
14062
14571
|
const deny = (c, kind, status, error, extra = {}) => {
|
|
14063
14572
|
audit3(db, { kind, target: "loki.yaml", result: status === 409 ? "conflict" : status === 500 ? "error" : "refused", detail: error });
|
|
@@ -14072,9 +14581,9 @@ function mount4(ctx) {
|
|
|
14072
14581
|
return deny(c, "config.read", r.status, r.error);
|
|
14073
14582
|
let raw2 = "";
|
|
14074
14583
|
if (r.exists) {
|
|
14075
|
-
if (
|
|
14584
|
+
if (statSync6(r.file).size > MAX_FILE)
|
|
14076
14585
|
return deny(c, "config.read", 413, "loki.yaml is too large");
|
|
14077
|
-
raw2 =
|
|
14586
|
+
raw2 = readFileSync9(r.file, "utf8");
|
|
14078
14587
|
}
|
|
14079
14588
|
const doc = $parseDocument(raw2);
|
|
14080
14589
|
let config = {};
|
|
@@ -14117,13 +14626,13 @@ function mount4(ctx) {
|
|
|
14117
14626
|
}
|
|
14118
14627
|
if (tooDeep(body))
|
|
14119
14628
|
return refuse(422, "config is nested too deeply");
|
|
14120
|
-
const next =
|
|
14121
|
-
if (!
|
|
14629
|
+
const next = isObj4(body) ? body.config : undefined;
|
|
14630
|
+
if (!isObj4(next))
|
|
14122
14631
|
return refuse(400, "body must be {config: object}");
|
|
14123
14632
|
const merge = mergeKeyPaths(next);
|
|
14124
14633
|
if (merge.length)
|
|
14125
14634
|
return refuse(422, "the YAML merge key << is not allowed", { paths: merge });
|
|
14126
|
-
const badNames = Object.keys(
|
|
14635
|
+
const badNames = Object.keys(isObj4(next.workspaces) ? next.workspaces : {}).filter((n2) => !/^[A-Za-z0-9_.-]+$/.test(n2) || n2 === "." || n2 === "..");
|
|
14127
14636
|
if (badNames.length)
|
|
14128
14637
|
return refuse(422, "workspace names may only use letters, digits, _ . and -", { paths: badNames.map((_n, i) => `workspaces.(name ${i + 1})`) });
|
|
14129
14638
|
const secrets = findSecrets(next);
|
|
@@ -14137,9 +14646,9 @@ function mount4(ctx) {
|
|
|
14137
14646
|
return refuse(r.status, r.error);
|
|
14138
14647
|
let raw2 = "";
|
|
14139
14648
|
if (r.exists) {
|
|
14140
|
-
if (
|
|
14649
|
+
if (statSync6(r.file).size > MAX_FILE)
|
|
14141
14650
|
return refuse(400, "loki.yaml is too large");
|
|
14142
|
-
raw2 =
|
|
14651
|
+
raw2 = readFileSync9(r.file, "utf8");
|
|
14143
14652
|
}
|
|
14144
14653
|
if (!(ifMatch === "*" ? r.exists : sha(raw2) === ifMatch))
|
|
14145
14654
|
return refuse(409, "loki.yaml changed since it was read", { etag: etagOf(raw2) });
|
|
@@ -14164,7 +14673,7 @@ function mount4(ctx) {
|
|
|
14164
14673
|
return refuse(400, "could not render the config safely");
|
|
14165
14674
|
const changed = [...new Set([...Object.keys(prev), ...Object.keys(next)])].filter((k) => !deepEq(prev[k], next[k]));
|
|
14166
14675
|
try {
|
|
14167
|
-
const mode = r.exists ?
|
|
14676
|
+
const mode = r.exists ? statSync6(r.file).mode & 511 : 420;
|
|
14168
14677
|
if (r.exists)
|
|
14169
14678
|
atomicWrite(`${r.file}.bak`, raw2, 384);
|
|
14170
14679
|
atomicWrite(r.file, out, mode);
|
|
@@ -14182,19 +14691,19 @@ function mount4(ctx) {
|
|
|
14182
14691
|
}
|
|
14183
14692
|
|
|
14184
14693
|
// src/server/routes/control.ts
|
|
14185
|
-
import { existsSync as
|
|
14186
|
-
import { join as
|
|
14694
|
+
import { existsSync as existsSync7, realpathSync as realpathSync6 } from "fs";
|
|
14695
|
+
import { join as join13, resolve as resolve3, sep as sep4 } from "path";
|
|
14187
14696
|
|
|
14188
14697
|
// ../../loki-ts/src/util/run_pid.ts
|
|
14189
|
-
import { execFileSync } from "child_process";
|
|
14190
|
-
import { chmodSync as chmodSync2, readFileSync as
|
|
14191
|
-
import { join as
|
|
14192
|
-
var runPidPath = (runDir) =>
|
|
14698
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
14699
|
+
import { chmodSync as chmodSync2, readFileSync as readFileSync10, renameSync as renameSync3, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
14700
|
+
import { join as join12 } from "path";
|
|
14701
|
+
var runPidPath = (runDir) => join12(runDir, "run.pid");
|
|
14193
14702
|
function processStartTime(pid) {
|
|
14194
14703
|
if (!Number.isInteger(pid) || pid <= 1)
|
|
14195
14704
|
return null;
|
|
14196
14705
|
try {
|
|
14197
|
-
const out =
|
|
14706
|
+
const out = execFileSync2("ps", ["-o", "lstart=", "-p", String(pid)], { encoding: "utf8", timeout: 5000, env: { ...process.env, LC_ALL: "C" }, stdio: ["ignore", "pipe", "ignore"] });
|
|
14198
14707
|
return out.trim().replace(/\s+/g, " ") || null;
|
|
14199
14708
|
} catch {
|
|
14200
14709
|
return null;
|
|
@@ -14202,7 +14711,7 @@ function processStartTime(pid) {
|
|
|
14202
14711
|
}
|
|
14203
14712
|
function readRunPid(runDir) {
|
|
14204
14713
|
try {
|
|
14205
|
-
const j = JSON.parse(
|
|
14714
|
+
const j = JSON.parse(readFileSync10(runPidPath(runDir), "utf8"));
|
|
14206
14715
|
if (!Number.isInteger(j.pid) || j.pid <= 1 || typeof j.start !== "string" || typeof j.run_id !== "string" || !Array.isArray(j.argv))
|
|
14207
14716
|
return null;
|
|
14208
14717
|
return { pid: j.pid, start: j.start, argv: j.argv.map(String), run_id: j.run_id };
|
|
@@ -14229,7 +14738,7 @@ function verifyRunPid(runDir, runId, startOf = processStartTime) {
|
|
|
14229
14738
|
// src/server/routes/control.ts
|
|
14230
14739
|
var ID3 = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
14231
14740
|
var busy = new Set;
|
|
14232
|
-
function
|
|
14741
|
+
function mount7(ctx) {
|
|
14233
14742
|
const { act, db } = ctx;
|
|
14234
14743
|
const bin = ctx.startBin ?? "loki", spawnIt = ctx.spawnImpl ?? spawnStart, answerDir = ctx.answerDir ?? defaultAnswerDir();
|
|
14235
14744
|
const guarded = (kind, handler) => act.post(`/v1/runs/:source/:run/${kind}`, async (c) => {
|
|
@@ -14245,14 +14754,14 @@ function mount5(ctx) {
|
|
|
14245
14754
|
const repoRow = db.select().from(localRepos).where(eq(localRepos.sourceId, source)).get();
|
|
14246
14755
|
let repo = "";
|
|
14247
14756
|
try {
|
|
14248
|
-
repo = repoRow ?
|
|
14757
|
+
repo = repoRow ? realpathSync6(repoRow.realpath) : "";
|
|
14249
14758
|
} catch {
|
|
14250
14759
|
repo = "";
|
|
14251
14760
|
}
|
|
14252
14761
|
if (!repo)
|
|
14253
14762
|
return done(404, "refused", { error: "this run's repo is not known on this machine" });
|
|
14254
|
-
const runsRoot =
|
|
14255
|
-
if (!runDir.startsWith(runsRoot +
|
|
14763
|
+
const runsRoot = join13(repo, ".loki", "runs"), runDir = resolve3(runsRoot, id);
|
|
14764
|
+
if (!runDir.startsWith(runsRoot + sep4))
|
|
14256
14765
|
return done(400, "refused", { error: "invalid run id" }, "path escape");
|
|
14257
14766
|
return handler({ source, run: id, repo, runDir }, done);
|
|
14258
14767
|
});
|
|
@@ -14299,7 +14808,7 @@ function mount5(ctx) {
|
|
|
14299
14808
|
return done(404, "refused", { error: "run not found" });
|
|
14300
14809
|
if (!blockedQuestion(loadEvents(db, a.source, a.run), row.verdict))
|
|
14301
14810
|
return done(409, "refused", { error: "run is not BLOCKED, so there is nothing to resume" });
|
|
14302
|
-
if (!
|
|
14811
|
+
if (!existsSync7(join13(answerDir, a.source, `${a.run}.answer.txt`)))
|
|
14303
14812
|
return done(409, "refused", { error: "no answer recorded for this run; answer it first" });
|
|
14304
14813
|
return launch(a, [bin, "answer", a.run], done);
|
|
14305
14814
|
});
|
|
@@ -14326,333 +14835,63 @@ var add = (a, r) => {
|
|
|
14326
14835
|
var dimVal = (d, r) => d === "day" ? r.startedAt ? r.startedAt.slice(0, 10) : NONE : d === "model" ? r.model ?? NONE : d === "provider" ? r.provider ?? NONE : r.originRepo ?? NONE;
|
|
14327
14836
|
var round = (n2) => Math.round(n2 * 1e6) / 1e6;
|
|
14328
14837
|
var out = (a) => ({ ...a.key, runs: a.runs, measured_runs: a.measured_runs, partial_runs: a.partial_runs, unmeasured_runs: a.unmeasured_runs, measured_usd: round(a.measured_usd), partial_usd: round(a.partial_usd), input_tokens: a.input_tokens, output_tokens: a.output_tokens });
|
|
14329
|
-
function
|
|
14838
|
+
function mount8(ctx) {
|
|
14330
14839
|
ctx.app.get("/v1/stats/cost", (c) => {
|
|
14331
14840
|
const dims = (c.req.query("group") ?? "day").split(",").map((s) => s.trim()).filter(Boolean);
|
|
14332
14841
|
if (dims.length === 0 || dims.some((d) => !COST_DIMS.includes(d))) {
|
|
14333
14842
|
return c.json({ error: `group must be a comma list of ${COST_DIMS.join(", ")}` }, 400);
|
|
14334
14843
|
}
|
|
14335
|
-
const since = c.req.query("since");
|
|
14336
|
-
if (since !== undefined && Number.isNaN(Date.parse(since)))
|
|
14337
|
-
return c.json({ error: "since must be an ISO date" }, 400);
|
|
14338
|
-
const q = ctx.db.select().from(runs);
|
|
14339
|
-
const all = since ? q.where(gte(runs.startedAt, since)).all() : q.all();
|
|
14340
|
-
const groups = new Map;
|
|
14341
|
-
const total = mk({});
|
|
14342
|
-
let
|
|
14343
|
-
for (const r of all) {
|
|
14344
|
-
const key = {};
|
|
14345
|
-
for (const d of dims)
|
|
14346
|
-
key[d] = dimVal(d, r);
|
|
14347
|
-
const id = dims.map((d) => key[d]).join("\x00");
|
|
14348
|
-
let g = groups.get(id);
|
|
14349
|
-
if (!g) {
|
|
14350
|
-
g = mk(key);
|
|
14351
|
-
groups.set(id, g);
|
|
14352
|
-
}
|
|
14353
|
-
add(g, r);
|
|
14354
|
-
add(total, r);
|
|
14355
|
-
if (r.measuredSessions > 0)
|
|
14356
|
-
|
|
14357
|
-
else
|
|
14358
|
-
unpriced++;
|
|
14359
|
-
}
|
|
14360
|
-
const rows = [...groups.values()].map(out).sort((a, b) => {
|
|
14361
|
-
for (const d of dims) {
|
|
14362
|
-
const x = a[d], y = b[d];
|
|
14363
|
-
if (x !== y)
|
|
14364
|
-
return d === "day" ? y.localeCompare(x) : x.localeCompare(y);
|
|
14365
|
-
}
|
|
14366
|
-
return 0;
|
|
14367
|
-
});
|
|
14368
|
-
const { runs: n2, ...rest } = out(total);
|
|
14369
|
-
return c.json({
|
|
14370
|
-
group: dims,
|
|
14371
|
-
since: since ?? null,
|
|
14372
|
-
rows,
|
|
14373
|
-
totals: { runs: n2, ...rest },
|
|
14374
|
-
budget: { api_key_default_cap_usd: DEFAULT_API_KEY_CAP_USD, subscription_cap: null, api_key_runs: priced, subscription_runs: unpriced }
|
|
14375
|
-
});
|
|
14376
|
-
});
|
|
14377
|
-
}
|
|
14378
|
-
|
|
14379
|
-
// src/server/routes/cost_ledger.ts
|
|
14380
|
-
import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync9, statSync as statSync5 } from "fs";
|
|
14381
|
-
import { join as join12 } from "path";
|
|
14382
|
-
var isNum = (v) => typeof v === "number" && Number.isFinite(v);
|
|
14383
|
-
var isObj3 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
14384
|
-
var r6 = (n2) => Math.round(n2 * 1e6) / 1e6;
|
|
14385
|
-
var MEASURED_FIELDS = ["cost_usd", "input_tokens", "output_tokens", "cache_read_tokens", "cache_creation_tokens"];
|
|
14386
|
-
var BUDGET_WARN_FRACTION = 0.8;
|
|
14387
|
-
var NOT_MEASURED = "not measured";
|
|
14388
|
-
function readJson(path) {
|
|
14389
|
-
try {
|
|
14390
|
-
return JSON.parse(readFileSync9(path, "utf8"));
|
|
14391
|
-
} catch {
|
|
14392
|
-
return null;
|
|
14393
|
-
}
|
|
14394
|
-
}
|
|
14395
|
-
function recordIsMeasured(rec, skip) {
|
|
14396
|
-
if (!isObj3(rec))
|
|
14397
|
-
return false;
|
|
14398
|
-
return MEASURED_FIELDS.some((k) => k !== skip && isNum(rec[k]) && rec[k] !== 0);
|
|
14399
|
-
}
|
|
14400
|
-
var DEFAULT_PRICING = {
|
|
14401
|
-
fable: { input: 10, output: 50 },
|
|
14402
|
-
"claude-fable-5": { input: 10, output: 50 },
|
|
14403
|
-
opus: { input: 5, output: 25 },
|
|
14404
|
-
sonnet: { input: 2, output: 10 },
|
|
14405
|
-
haiku: { input: 1, output: 5 },
|
|
14406
|
-
"gpt-5.3-codex": { input: 1.5, output: 12 },
|
|
14407
|
-
"gpt-5.6-sol": { input: 2.5, output: 20 },
|
|
14408
|
-
"gpt-5.6-terra": { input: 1.5, output: 12 },
|
|
14409
|
-
"gpt-5.6-luna": { input: 0.5, output: 4 }
|
|
14410
|
-
};
|
|
14411
|
-
function pricing(lokiDir) {
|
|
14412
|
-
const d = readJson(join12(lokiDir, "pricing.json"));
|
|
14413
|
-
const models = isObj3(d) && isObj3(d.models) ? d.models : {};
|
|
14414
|
-
return { ...DEFAULT_PRICING, ...models };
|
|
14415
|
-
}
|
|
14416
|
-
function modelCost(table, model, inp, out2, cr = 0, cw = 0) {
|
|
14417
|
-
const p = table[model.toLowerCase()] ?? table.sonnet ?? {};
|
|
14418
|
-
const ir = isNum(p.input) ? p.input : 3;
|
|
14419
|
-
const or2 = isNum(p.output) ? p.output : 15;
|
|
14420
|
-
const crr = isNum(p.cache_read) ? p.cache_read : ir * 0.1;
|
|
14421
|
-
const cwr = isNum(p.cache_write) ? p.cache_write : ir * 1.25;
|
|
14422
|
-
return r6((inp * ir + out2 * or2 + cr * crr + cw * cwr) / 1e6);
|
|
14423
|
-
}
|
|
14424
|
-
function iterationRecords(lokiDir) {
|
|
14425
|
-
const dir = join12(lokiDir, "metrics", "efficiency");
|
|
14426
|
-
let names = [];
|
|
14427
|
-
try {
|
|
14428
|
-
names = readdirSync3(dir).filter((n2) => /^iteration-.*\.json$/.test(n2)).sort();
|
|
14429
|
-
} catch {
|
|
14430
|
-
return [];
|
|
14431
|
-
}
|
|
14432
|
-
const out2 = [];
|
|
14433
|
-
for (const n2 of names) {
|
|
14434
|
-
const d = readJson(join12(dir, n2));
|
|
14435
|
-
if (isObj3(d))
|
|
14436
|
-
out2.push(d);
|
|
14437
|
-
}
|
|
14438
|
-
return out2;
|
|
14439
|
-
}
|
|
14440
|
-
var num0 = (v) => isNum(v) ? v : 0;
|
|
14441
|
-
var priced = (table, d) => d.cost_usd === null || d.cost_usd === undefined ? modelCost(table, String(d.model ?? "sonnet").toLowerCase(), num0(d.input_tokens), num0(d.output_tokens), num0(d.cache_read_tokens), num0(d.cache_creation_tokens)) : num0(d.cost_usd);
|
|
14442
|
-
function costSnapshot(lokiDir) {
|
|
14443
|
-
const table = pricing(lokiDir);
|
|
14444
|
-
let tIn = 0, tOut = 0, tCr = 0, tCw = 0, est = 0;
|
|
14445
|
-
let recorded = false, unknownUsd = false;
|
|
14446
|
-
const byPhase = {}, byModel = {};
|
|
14447
|
-
for (const d of iterationRecords(lokiDir)) {
|
|
14448
|
-
const measured = recordIsMeasured(d);
|
|
14449
|
-
if (measured)
|
|
14450
|
-
recorded = true;
|
|
14451
|
-
const tokMeasured = recordIsMeasured(d, "cost_usd");
|
|
14452
|
-
const inp = num0(d.input_tokens), out2 = num0(d.output_tokens), cr = num0(d.cache_read_tokens), cw = num0(d.cache_creation_tokens);
|
|
14453
|
-
const model = String(d.model ?? "sonnet").toLowerCase();
|
|
14454
|
-
const phase = String(d.phase ?? "unknown");
|
|
14455
|
-
tIn += inp;
|
|
14456
|
-
tOut += out2;
|
|
14457
|
-
tCr += cr;
|
|
14458
|
-
tCw += cw;
|
|
14459
|
-
const cost = priced(table, d);
|
|
14460
|
-
est += cost;
|
|
14461
|
-
for (const [bucket, name] of [[byPhase, phase], [byModel, model]]) {
|
|
14462
|
-
const b = bucket[name] ??= { input_tokens: 0, output_tokens: 0, cost_usd: 0, measured: false, tokens_measured: false };
|
|
14463
|
-
b.input_tokens += inp;
|
|
14464
|
-
b.output_tokens += out2;
|
|
14465
|
-
b.cost_usd += cost;
|
|
14466
|
-
b.measured ||= measured;
|
|
14467
|
-
b.tokens_measured ||= tokMeasured;
|
|
14468
|
-
}
|
|
14469
|
-
}
|
|
14470
|
-
if (tIn === 0 && tOut === 0) {
|
|
14471
|
-
const ctx = readJson(join12(lokiDir, "context", "tracking.json"));
|
|
14472
|
-
const totals = isObj3(ctx) && isObj3(ctx.totals) ? ctx.totals : {};
|
|
14473
|
-
tIn = num0(totals.total_input);
|
|
14474
|
-
tOut = num0(totals.total_output);
|
|
14475
|
-
if (tIn > 0 || tOut > 0) {
|
|
14476
|
-
recorded = true;
|
|
14477
|
-
if (isNum(totals.total_cost_usd))
|
|
14478
|
-
est = totals.total_cost_usd;
|
|
14479
|
-
else
|
|
14480
|
-
unknownUsd = true;
|
|
14481
|
-
const model = String(isObj3(ctx) && ctx.provider || "unknown").toLowerCase();
|
|
14482
|
-
const per = isObj3(ctx) && Array.isArray(ctx.per_iteration) ? ctx.per_iteration : [];
|
|
14483
|
-
for (const it of per) {
|
|
14484
|
-
if (!isObj3(it))
|
|
14485
|
-
continue;
|
|
14486
|
-
const b = byModel[model] ??= { input_tokens: 0, output_tokens: 0, cost_usd: 0, measured: false, tokens_measured: true };
|
|
14487
|
-
b.input_tokens += num0(it.input_tokens);
|
|
14488
|
-
b.output_tokens += num0(it.output_tokens);
|
|
14489
|
-
if (isNum(it.cost_usd)) {
|
|
14490
|
-
b.cost_usd += it.cost_usd;
|
|
14491
|
-
b.measured = true;
|
|
14492
|
-
}
|
|
14493
|
-
}
|
|
14494
|
-
}
|
|
14495
|
-
}
|
|
14496
|
-
let limit = null, used = null, remaining = null;
|
|
14497
|
-
const bd = readJson(join12(lokiDir, "metrics", "budget.json"));
|
|
14498
|
-
if (isObj3(bd) && isNum(bd.limit)) {
|
|
14499
|
-
limit = bd.limit;
|
|
14500
|
-
if (recorded && !unknownUsd) {
|
|
14501
|
-
used = est;
|
|
14502
|
-
remaining = Math.max(0, limit - used);
|
|
14503
|
-
}
|
|
14504
|
-
}
|
|
14505
|
-
const readIn = tIn + tCr;
|
|
14506
|
-
const fmt = (m) => Object.fromEntries(Object.entries(m).map(([k, v]) => [k, {
|
|
14507
|
-
input_tokens: v.tokens_measured ? v.input_tokens : null,
|
|
14508
|
-
output_tokens: v.tokens_measured ? v.output_tokens : null,
|
|
14509
|
-
cost_usd: v.measured ? r6(v.cost_usd) : null
|
|
14510
|
-
}]));
|
|
14511
|
-
return {
|
|
14512
|
-
total_input_tokens: recorded ? tIn : null,
|
|
14513
|
-
total_output_tokens: recorded ? tOut : null,
|
|
14514
|
-
total_cache_read_tokens: recorded ? tCr : null,
|
|
14515
|
-
total_cache_creation_tokens: recorded ? tCw : null,
|
|
14516
|
-
total_tokens: recorded ? tIn + tOut + tCr + tCw : null,
|
|
14517
|
-
cache_hit_ratio: readIn > 0 ? Math.round(tCr / readIn * 1e4) / 1e4 : null,
|
|
14518
|
-
estimated_cost_usd: recorded && !unknownUsd ? r6(est) : null,
|
|
14519
|
-
cost_recorded: recorded,
|
|
14520
|
-
by_phase: fmt(byPhase),
|
|
14521
|
-
by_model: fmt(byModel),
|
|
14522
|
-
budget_limit: limit,
|
|
14523
|
-
budget_used: limit !== null && used !== null ? r6(used) : null,
|
|
14524
|
-
budget_remaining: remaining !== null ? r6(remaining) : null,
|
|
14525
|
-
...recorded ? {} : { not_measured: NOT_MEASURED }
|
|
14526
|
-
};
|
|
14527
|
-
}
|
|
14528
|
-
function budgetStatus(used, limit) {
|
|
14529
|
-
if (limit === null || limit <= 0)
|
|
14530
|
-
return "none";
|
|
14531
|
-
if (used === null)
|
|
14532
|
-
return "unknown";
|
|
14533
|
-
if (used >= limit)
|
|
14534
|
-
return "exceeded";
|
|
14535
|
-
return used >= BUDGET_WARN_FRACTION * limit ? "warn" : "ok";
|
|
14536
|
-
}
|
|
14537
|
-
function budgetSnapshot(lokiDir) {
|
|
14538
|
-
const table = pricing(lokiDir);
|
|
14539
|
-
let total = 0, recorded = false, unmeasured = 0;
|
|
14540
|
-
for (const d of iterationRecords(lokiDir)) {
|
|
14541
|
-
if (recordIsMeasured(d))
|
|
14542
|
-
recorded = true;
|
|
14543
|
-
else
|
|
14544
|
-
unmeasured++;
|
|
14545
|
-
total += priced(table, d);
|
|
14546
|
-
}
|
|
14547
|
-
let limit = null;
|
|
14548
|
-
const bd = readJson(join12(lokiDir, "metrics", "budget.json"));
|
|
14549
|
-
if (isObj3(bd)) {
|
|
14550
|
-
const l = bd.limit || bd.budget_limit;
|
|
14551
|
-
limit = isNum(l) ? l : null;
|
|
14552
|
-
}
|
|
14553
|
-
if (limit === null && process.env.LOKI_BUDGET_LIMIT) {
|
|
14554
|
-
const e = Number(process.env.LOKI_BUDGET_LIMIT);
|
|
14555
|
-
if (Number.isFinite(e))
|
|
14556
|
-
limit = e;
|
|
14557
|
-
}
|
|
14558
|
-
const used = recorded ? r6(total) : null;
|
|
14559
|
-
const ok = limit !== null && limit > 0 && used !== null;
|
|
14560
|
-
const status = budgetStatus(used, limit);
|
|
14561
|
-
return {
|
|
14562
|
-
limit,
|
|
14563
|
-
used,
|
|
14564
|
-
remaining: ok ? r6(Math.max(0, limit - used)) : null,
|
|
14565
|
-
percent_used: ok ? Math.round(used / limit * 1e4) / 100 : null,
|
|
14566
|
-
status,
|
|
14567
|
-
warn_threshold_percent: Math.round(BUDGET_WARN_FRACTION * 100),
|
|
14568
|
-
exceeded: status === "exceeded",
|
|
14569
|
-
partial: recorded && unmeasured > 0
|
|
14570
|
-
};
|
|
14571
|
-
}
|
|
14572
|
-
function costTimeline(lokiDir) {
|
|
14573
|
-
const table = pricing(lokiDir);
|
|
14574
|
-
const recs = iterationRecords(lokiDir).map((d, i) => ({ d, i })).sort((a, b) => num0(Number(a.d.iteration)) - num0(Number(b.d.iteration)) || a.i - b.i).map((x) => x.d);
|
|
14575
|
-
const iterations = [];
|
|
14576
|
-
let cum = 0, recorded = false, unmeasured = 0;
|
|
14577
|
-
for (const d of recs) {
|
|
14578
|
-
const measured = recordIsMeasured(d);
|
|
14579
|
-
if (measured)
|
|
14580
|
-
recorded = true;
|
|
14581
|
-
else
|
|
14582
|
-
unmeasured++;
|
|
14583
|
-
const model = String(d.model ?? "sonnet").toLowerCase();
|
|
14584
|
-
cum += priced(table, d);
|
|
14585
|
-
iterations.push({
|
|
14586
|
-
iteration: d.iteration ?? null,
|
|
14587
|
-
timestamp: d.timestamp ?? null,
|
|
14588
|
-
model: d.model ? model : null,
|
|
14589
|
-
phase: d.phase ?? "unknown",
|
|
14590
|
-
provider: d.provider ?? null,
|
|
14591
|
-
input_tokens: measured ? num0(d.input_tokens) : null,
|
|
14592
|
-
output_tokens: measured ? num0(d.output_tokens) : null,
|
|
14593
|
-
cost_usd: measured ? r6(priced(table, d)) : null,
|
|
14594
|
-
cumulative_usd: measured ? r6(cum) : null
|
|
14595
|
-
});
|
|
14596
|
-
}
|
|
14597
|
-
const runs2 = [];
|
|
14598
|
-
let projectTotal = 0, runsMeasured = 0;
|
|
14599
|
-
const proofs = join12(lokiDir, "proofs");
|
|
14600
|
-
let entries = [];
|
|
14601
|
-
try {
|
|
14602
|
-
entries = readdirSync3(proofs).sort();
|
|
14603
|
-
} catch {}
|
|
14604
|
-
for (const e of entries) {
|
|
14605
|
-
try {
|
|
14606
|
-
if (!statSync5(join12(proofs, e)).isDirectory())
|
|
14607
|
-
continue;
|
|
14608
|
-
} catch {
|
|
14609
|
-
continue;
|
|
14610
|
-
}
|
|
14611
|
-
const p = readJson(join12(proofs, e, "proof.json"));
|
|
14612
|
-
if (!isObj3(p))
|
|
14613
|
-
continue;
|
|
14614
|
-
const cost = isObj3(p.cost) ? p.cost : {};
|
|
14615
|
-
const usd = isNum(cost.usd) ? cost.usd : null;
|
|
14616
|
-
if (usd !== null) {
|
|
14617
|
-
projectTotal += usd;
|
|
14618
|
-
runsMeasured++;
|
|
14619
|
-
}
|
|
14620
|
-
runs2.push({
|
|
14621
|
-
run_id: p.run_id ?? e,
|
|
14622
|
-
generated_at: p.generated_at ?? null,
|
|
14623
|
-
model: isObj3(p.provider) ? p.provider.model ?? null : null,
|
|
14624
|
-
cost_usd: usd !== null ? r6(usd) : null,
|
|
14625
|
-
cost_partial: cost.cost_partial === true,
|
|
14626
|
-
files_changed: isObj3(p.files_changed) ? p.files_changed.count ?? null : null,
|
|
14627
|
-
final_verdict: isObj3(p.council) ? p.council.final_verdict ?? null : null
|
|
14844
|
+
const since = c.req.query("since");
|
|
14845
|
+
if (since !== undefined && Number.isNaN(Date.parse(since)))
|
|
14846
|
+
return c.json({ error: "since must be an ISO date" }, 400);
|
|
14847
|
+
const q = ctx.db.select().from(runs);
|
|
14848
|
+
const all = since ? q.where(gte(runs.startedAt, since)).all() : q.all();
|
|
14849
|
+
const groups = new Map;
|
|
14850
|
+
const total = mk({});
|
|
14851
|
+
let priced2 = 0, unpriced = 0;
|
|
14852
|
+
for (const r of all) {
|
|
14853
|
+
const key = {};
|
|
14854
|
+
for (const d of dims)
|
|
14855
|
+
key[d] = dimVal(d, r);
|
|
14856
|
+
const id = dims.map((d) => key[d]).join("\x00");
|
|
14857
|
+
let g = groups.get(id);
|
|
14858
|
+
if (!g) {
|
|
14859
|
+
g = mk(key);
|
|
14860
|
+
groups.set(id, g);
|
|
14861
|
+
}
|
|
14862
|
+
add(g, r);
|
|
14863
|
+
add(total, r);
|
|
14864
|
+
if (r.measuredSessions > 0)
|
|
14865
|
+
priced2++;
|
|
14866
|
+
else
|
|
14867
|
+
unpriced++;
|
|
14868
|
+
}
|
|
14869
|
+
const rows = [...groups.values()].map(out).sort((a, b) => {
|
|
14870
|
+
for (const d of dims) {
|
|
14871
|
+
const x = a[d], y = b[d];
|
|
14872
|
+
if (x !== y)
|
|
14873
|
+
return d === "day" ? y.localeCompare(x) : x.localeCompare(y);
|
|
14874
|
+
}
|
|
14875
|
+
return 0;
|
|
14628
14876
|
});
|
|
14629
|
-
|
|
14630
|
-
|
|
14631
|
-
|
|
14632
|
-
|
|
14633
|
-
|
|
14634
|
-
|
|
14635
|
-
|
|
14636
|
-
|
|
14637
|
-
|
|
14638
|
-
...recorded || runsMeasured ? {} : { not_measured: NOT_MEASURED }
|
|
14639
|
-
};
|
|
14640
|
-
}
|
|
14641
|
-
var lokiDirOf = (repoDir) => join12(repoDir, ".loki");
|
|
14642
|
-
var lokiDirExists = (repoDir) => existsSync6(lokiDirOf(repoDir));
|
|
14643
|
-
function mount7(ctx) {
|
|
14644
|
-
const deny = { error: "loopback only without a token" };
|
|
14645
|
-
ctx.app.get("/v1/cost/snapshot", (c) => probeAllowed(ctx, c) ? c.json(costSnapshot(lokiDirOf(ctx.repoDir))) : c.json(deny, 403));
|
|
14646
|
-
ctx.app.get("/v1/cost/timeline", (c) => probeAllowed(ctx, c) ? c.json(costTimeline(lokiDirOf(ctx.repoDir))) : c.json(deny, 403));
|
|
14877
|
+
const { runs: n2, ...rest } = out(total);
|
|
14878
|
+
return c.json({
|
|
14879
|
+
group: dims,
|
|
14880
|
+
since: since ?? null,
|
|
14881
|
+
rows,
|
|
14882
|
+
totals: { runs: n2, ...rest },
|
|
14883
|
+
budget: { api_key_default_cap_usd: DEFAULT_API_KEY_CAP_USD, subscription_cap: null, api_key_runs: priced2, subscription_runs: unpriced }
|
|
14884
|
+
});
|
|
14885
|
+
});
|
|
14647
14886
|
}
|
|
14648
14887
|
|
|
14649
14888
|
// src/server/routes/fleet.ts
|
|
14650
|
-
import { readdirSync as
|
|
14889
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync11 } from "fs";
|
|
14651
14890
|
import { homedir as homedir4 } from "os";
|
|
14652
|
-
import { basename as basename4, join as
|
|
14891
|
+
import { basename as basename4, join as join14 } from "path";
|
|
14653
14892
|
var isNum2 = (v) => typeof v === "number" && Number.isFinite(v);
|
|
14654
|
-
var
|
|
14655
|
-
var registryFile = () =>
|
|
14893
|
+
var isObj5 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
14894
|
+
var registryFile = () => join14(process.env.HOME ?? homedir4(), ".loki", "dashboard", "projects.json");
|
|
14656
14895
|
function pidAlive(pid) {
|
|
14657
14896
|
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0)
|
|
14658
14897
|
return false;
|
|
@@ -14667,34 +14906,34 @@ function projectSnapshot(path) {
|
|
|
14667
14906
|
const snap = { phase: "", iteration: null, cost_usd: null, started_at: null, ended_at: null };
|
|
14668
14907
|
if (!path)
|
|
14669
14908
|
return snap;
|
|
14670
|
-
const loki =
|
|
14671
|
-
const st = readJson(
|
|
14672
|
-
if (
|
|
14909
|
+
const loki = join14(path, ".loki");
|
|
14910
|
+
const st = readJson(join14(loki, "dashboard-state.json"));
|
|
14911
|
+
if (isObj5(st)) {
|
|
14673
14912
|
snap.phase = typeof st.phase === "string" ? st.phase : "";
|
|
14674
14913
|
snap.iteration = Number.isInteger(st.iteration) ? st.iteration : null;
|
|
14675
14914
|
}
|
|
14676
|
-
const ss = readJson(
|
|
14677
|
-
if (
|
|
14915
|
+
const ss = readJson(join14(loki, "session.json"));
|
|
14916
|
+
if (isObj5(ss)) {
|
|
14678
14917
|
const a = ss.startedAt || ss.started_at, b = ss.endedAt || ss.ended_at;
|
|
14679
14918
|
snap.started_at = typeof a === "string" ? a : null;
|
|
14680
14919
|
snap.ended_at = typeof b === "string" ? b : null;
|
|
14681
14920
|
}
|
|
14682
14921
|
let cost = 0, found = false;
|
|
14683
|
-
const eff =
|
|
14922
|
+
const eff = join14(loki, "metrics", "efficiency");
|
|
14684
14923
|
let names = [];
|
|
14685
14924
|
try {
|
|
14686
|
-
names =
|
|
14925
|
+
names = readdirSync5(eff).filter((n2) => n2.endsWith(".json"));
|
|
14687
14926
|
} catch {}
|
|
14688
14927
|
for (const n2 of names) {
|
|
14689
|
-
const d = readJson(
|
|
14690
|
-
if (
|
|
14928
|
+
const d = readJson(join14(eff, n2));
|
|
14929
|
+
if (isObj5(d) && isNum2(d.cost_usd) && recordIsMeasured(d)) {
|
|
14691
14930
|
cost += d.cost_usd;
|
|
14692
14931
|
found = true;
|
|
14693
14932
|
}
|
|
14694
14933
|
}
|
|
14695
14934
|
if (!found) {
|
|
14696
|
-
const t = readJson(
|
|
14697
|
-
const totals =
|
|
14935
|
+
const t = readJson(join14(loki, "context", "tracking.json"));
|
|
14936
|
+
const totals = isObj5(t) && isObj5(t.totals) ? t.totals : null;
|
|
14698
14937
|
if (totals && isNum2(totals.total_cost_usd) && recordIsMeasured({ cost_usd: totals.total_cost_usd, input_tokens: totals.total_input, output_tokens: totals.total_output })) {
|
|
14699
14938
|
cost = totals.total_cost_usd;
|
|
14700
14939
|
found = true;
|
|
@@ -14706,9 +14945,9 @@ function projectSnapshot(path) {
|
|
|
14706
14945
|
function fleetRuns(includeInactive = true, file = registryFile()) {
|
|
14707
14946
|
let projects = [];
|
|
14708
14947
|
try {
|
|
14709
|
-
const reg = JSON.parse(
|
|
14710
|
-
if (
|
|
14711
|
-
projects = Object.values(reg.projects).filter(
|
|
14948
|
+
const reg = JSON.parse(readFileSync11(file, "utf8"));
|
|
14949
|
+
if (isObj5(reg) && isObj5(reg.projects))
|
|
14950
|
+
projects = Object.values(reg.projects).filter(isObj5);
|
|
14712
14951
|
} catch {
|
|
14713
14952
|
return [];
|
|
14714
14953
|
}
|
|
@@ -14759,16 +14998,294 @@ function fleetSummary(includeInactive = true, file = registryFile()) {
|
|
|
14759
14998
|
};
|
|
14760
14999
|
}
|
|
14761
15000
|
var parseInactive = (v) => !["false", "0", "no"].includes((v ?? "true").toLowerCase());
|
|
14762
|
-
function
|
|
15001
|
+
function mount9(ctx) {
|
|
14763
15002
|
const deny = { error: "loopback only without a token" };
|
|
14764
15003
|
ctx.app.get("/v1/fleet/runs", (c) => probeAllowed(ctx, c) ? c.json(fleetRuns(parseInactive(c.req.query("include_inactive"))).map(({ path: _p, ...r }) => r)) : c.json(deny, 403));
|
|
14765
15004
|
ctx.app.get("/v1/fleet/summary", (c) => probeAllowed(ctx, c) ? c.json(fleetSummary(parseInactive(c.req.query("include_inactive")))) : c.json(deny, 403));
|
|
14766
15005
|
}
|
|
14767
15006
|
|
|
15007
|
+
// src/server/routes/memory.ts
|
|
15008
|
+
import { existsSync as existsSync8, readFileSync as readFileSync12, readdirSync as readdirSync6, realpathSync as realpathSync7, statSync as statSync7 } from "fs";
|
|
15009
|
+
import { join as join15, sep as sep5 } from "path";
|
|
15010
|
+
var isObj6 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
15011
|
+
var nonEmpty = (v) => !(v === undefined || v === null || v === "" || Array.isArray(v) && v.length === 0 || isObj6(v) && Object.keys(v).length === 0);
|
|
15012
|
+
var focus = { dir: null };
|
|
15013
|
+
var lokiFor = (repoDir) => lokiDirOf(focus.dir ?? repoDir);
|
|
15014
|
+
function mutationAllowed(c) {
|
|
15015
|
+
if (!peerIsLoopback(c) || !isLoopbackHost(c.req.header("host")))
|
|
15016
|
+
return false;
|
|
15017
|
+
if (!(c.req.header("content-type") ?? "").toLowerCase().startsWith("application/json"))
|
|
15018
|
+
return false;
|
|
15019
|
+
const origin = c.req.header("origin");
|
|
15020
|
+
if (origin === undefined)
|
|
15021
|
+
return true;
|
|
15022
|
+
try {
|
|
15023
|
+
return new URL(origin).host.toLowerCase() === (c.req.header("host") ?? "").toLowerCase();
|
|
15024
|
+
} catch {
|
|
15025
|
+
return false;
|
|
15026
|
+
}
|
|
15027
|
+
}
|
|
15028
|
+
function jsonFiles(dir, cap = Infinity) {
|
|
15029
|
+
const out2 = [];
|
|
15030
|
+
const walk = (d) => {
|
|
15031
|
+
let ents = [];
|
|
15032
|
+
try {
|
|
15033
|
+
ents = readdirSync6(d, { withFileTypes: true });
|
|
15034
|
+
} catch {
|
|
15035
|
+
return;
|
|
15036
|
+
}
|
|
15037
|
+
for (const e of ents.sort((a, b) => a.name < b.name ? -1 : 1)) {
|
|
15038
|
+
if (out2.length >= cap)
|
|
15039
|
+
return;
|
|
15040
|
+
const p = join15(d, e.name);
|
|
15041
|
+
if (e.isDirectory())
|
|
15042
|
+
walk(p);
|
|
15043
|
+
else if (e.isFile() && e.name.endsWith(".json"))
|
|
15044
|
+
out2.push(p);
|
|
15045
|
+
}
|
|
15046
|
+
};
|
|
15047
|
+
walk(dir);
|
|
15048
|
+
return out2;
|
|
15049
|
+
}
|
|
15050
|
+
function focusInfo(repoDir) {
|
|
15051
|
+
return { project_dir: focus.dir, loki_dir: lokiFor(repoDir) };
|
|
15052
|
+
}
|
|
15053
|
+
function setFocus(repoDir, raw2, registry = registryFile()) {
|
|
15054
|
+
const s = typeof raw2 === "string" ? raw2.trim() : "";
|
|
15055
|
+
if (!s)
|
|
15056
|
+
return { ok: false, error: "project_dir must not be empty" };
|
|
15057
|
+
if (!s.startsWith("/") || s.split(/[\\/]/).includes(".."))
|
|
15058
|
+
return { ok: false, error: "project_dir must be an absolute path without .. segments" };
|
|
15059
|
+
let real2 = "";
|
|
15060
|
+
try {
|
|
15061
|
+
real2 = realpathSync7(s);
|
|
15062
|
+
if (!statSync7(real2).isDirectory())
|
|
15063
|
+
throw new Error("not a dir");
|
|
15064
|
+
} catch {
|
|
15065
|
+
return { ok: false, error: `Directory does not exist: ${s}` };
|
|
15066
|
+
}
|
|
15067
|
+
if (!existsSync8(join15(real2, ".loki")) || !statSync7(join15(real2, ".loki")).isDirectory())
|
|
15068
|
+
return { ok: false, error: `Directory does not contain a .loki/ subdirectory: ${s}` };
|
|
15069
|
+
const allowed = new Set;
|
|
15070
|
+
try {
|
|
15071
|
+
allowed.add(realpathSync7(repoDir));
|
|
15072
|
+
} catch {}
|
|
15073
|
+
const reg = readJson(registry);
|
|
15074
|
+
if (isObj6(reg) && isObj6(reg.projects))
|
|
15075
|
+
for (const p of Object.values(reg.projects)) {
|
|
15076
|
+
if (isObj6(p) && typeof p.path === "string") {
|
|
15077
|
+
try {
|
|
15078
|
+
allowed.add(realpathSync7(p.path));
|
|
15079
|
+
} catch {}
|
|
15080
|
+
}
|
|
15081
|
+
}
|
|
15082
|
+
if (!allowed.has(real2))
|
|
15083
|
+
return { ok: false, error: "project_dir is not this checkout or a registered project" };
|
|
15084
|
+
focus.dir = real2;
|
|
15085
|
+
return { ok: true };
|
|
15086
|
+
}
|
|
15087
|
+
var GROUPS = [["pending", "pending"], ["inProgress", "in_progress"], ["review", "review"], ["completed", "done"], ["failed", "done"]];
|
|
15088
|
+
var QUEUES = [["pending.json", "pending"], ["in-progress.json", "in_progress"], ["completed.json", "done"], ["failed.json", "done"], ["dead-letter.json", "done"]];
|
|
15089
|
+
var RANK = { done: 3, in_progress: 2, review: 1, pending: 0 };
|
|
15090
|
+
function tasksList(loki, status) {
|
|
15091
|
+
const all = [];
|
|
15092
|
+
const st = readJson(join15(loki, "dashboard-state.json"));
|
|
15093
|
+
const groups = isObj6(st) && isObj6(st.tasks) ? st.tasks : {};
|
|
15094
|
+
for (const [key, mapped] of GROUPS) {
|
|
15095
|
+
const g = groups[key];
|
|
15096
|
+
if (!Array.isArray(g))
|
|
15097
|
+
continue;
|
|
15098
|
+
g.forEach((t, i) => {
|
|
15099
|
+
if (!isObj6(t))
|
|
15100
|
+
return;
|
|
15101
|
+
const payload = isObj6(t.payload) ? t.payload : {};
|
|
15102
|
+
const e = { id: t.id ?? `${key}-${i}`, title: t.title ?? payload.action ?? t.type ?? "Task", description: t.description ?? payload.description ?? "", status: mapped, priority: t.priority ?? payload.priority ?? "medium", type: t.type ?? "task", position: i };
|
|
15103
|
+
for (const f of ["acceptance_criteria", "notes", "logs", "user_story", "project", "source", "specification", "provider", "startedAt", "full_content", "completedAt", "phase", "exitCode"])
|
|
15104
|
+
if (nonEmpty(t[f]))
|
|
15105
|
+
e[f] = t[f];
|
|
15106
|
+
if (t.exitCode === 0)
|
|
15107
|
+
e.exitCode = 0;
|
|
15108
|
+
if (key === "completed" || key === "failed")
|
|
15109
|
+
e._terminal_outcome = key;
|
|
15110
|
+
all.push(e);
|
|
15111
|
+
});
|
|
15112
|
+
}
|
|
15113
|
+
for (const [file, qs] of QUEUES) {
|
|
15114
|
+
const raw2 = readJson(join15(loki, "queue", file));
|
|
15115
|
+
const items = isObj6(raw2) ? raw2.tasks : raw2;
|
|
15116
|
+
if (!Array.isArray(items))
|
|
15117
|
+
continue;
|
|
15118
|
+
const term = file === "completed.json" ? "completed" : file === "failed.json" || file === "dead-letter.json" ? "failed" : null;
|
|
15119
|
+
items.forEach((it, i) => {
|
|
15120
|
+
if (!isObj6(it))
|
|
15121
|
+
return;
|
|
15122
|
+
const id = it.id ?? `q-${qs}-${i}`;
|
|
15123
|
+
if (term === null && all.some((t) => t.id === id))
|
|
15124
|
+
return;
|
|
15125
|
+
const e = { id, title: it.title ?? it.action ?? "Task", description: it.description ?? "", status: qs, priority: it.priority ?? "medium", type: it.type ?? "task", position: i };
|
|
15126
|
+
for (const f of ["acceptance_criteria", "user_story", "project", "source", "provider", "startedAt", "completedAt", "phase"])
|
|
15127
|
+
if (nonEmpty(it[f]))
|
|
15128
|
+
e[f] = it[f];
|
|
15129
|
+
for (const f of ["notes", "logs"])
|
|
15130
|
+
if (Array.isArray(it[f]))
|
|
15131
|
+
e[f] = it[f];
|
|
15132
|
+
if (Number.isInteger(it.exitCode))
|
|
15133
|
+
e.exitCode = it.exitCode;
|
|
15134
|
+
if (term)
|
|
15135
|
+
e._terminal_outcome = term;
|
|
15136
|
+
all.push(e);
|
|
15137
|
+
});
|
|
15138
|
+
}
|
|
15139
|
+
const auth = (t) => [RANK[t.status] ?? -1, t._terminal_outcome === "failed" ? 1 : 0];
|
|
15140
|
+
const better = (a, b) => {
|
|
15141
|
+
const x = auth(a), y = auth(b);
|
|
15142
|
+
return x[0] !== y[0] ? x[0] > y[0] : x[1] > y[1];
|
|
15143
|
+
};
|
|
15144
|
+
const byId = new Map, order2 = [];
|
|
15145
|
+
for (const t of all) {
|
|
15146
|
+
if (t.id === undefined || t.id === null) {
|
|
15147
|
+
order2.push(t);
|
|
15148
|
+
continue;
|
|
15149
|
+
}
|
|
15150
|
+
const prev = byId.get(t.id);
|
|
15151
|
+
if (!prev) {
|
|
15152
|
+
byId.set(t.id, t);
|
|
15153
|
+
order2.push({ ref: t.id });
|
|
15154
|
+
} else if (better(t, prev))
|
|
15155
|
+
byId.set(t.id, t);
|
|
15156
|
+
}
|
|
15157
|
+
const out2 = order2.map((o) => ("ref" in o) ? byId.get(o.ref) : o);
|
|
15158
|
+
for (const t of out2) {
|
|
15159
|
+
if (t.type === "iteration" && !t.description) {
|
|
15160
|
+
const ec = t.exitCode, failed = t._terminal_outcome === "failed";
|
|
15161
|
+
const o = Number.isInteger(ec) && ec === 0 && !failed ? "completed cleanly (exit 0)" : Number.isInteger(ec) && ec !== 0 ? `failed (exit ${ec})` : failed ? "failed" : t._terminal_outcome === "completed" ? "completed" : t.status === "done" ? "finished" : null;
|
|
15162
|
+
if (o)
|
|
15163
|
+
t.description = `Iteration ${o}${t.provider ? `, built by ${t.provider}` : ""}.`;
|
|
15164
|
+
}
|
|
15165
|
+
}
|
|
15166
|
+
return status ? out2.filter((t) => t.status === status) : out2;
|
|
15167
|
+
}
|
|
15168
|
+
var memDir = (loki, sub) => join15(loki, "memory", sub);
|
|
15169
|
+
function memoryEpisodes(loki, limit = 50) {
|
|
15170
|
+
const eps = jsonFiles(memDir(loki, "episodic")).map(readJson).filter(isObj6);
|
|
15171
|
+
eps.sort((a, b) => String(b.timestamp ?? "").localeCompare(String(a.timestamp ?? "")));
|
|
15172
|
+
return eps.slice(0, limit);
|
|
15173
|
+
}
|
|
15174
|
+
function memoryEpisode(loki, id) {
|
|
15175
|
+
for (const f of jsonFiles(memDir(loki, "episodic"))) {
|
|
15176
|
+
const d = readJson(f);
|
|
15177
|
+
if (isObj6(d) && (d.id === id || f.split(sep5).pop() === `${id}.json`))
|
|
15178
|
+
return d;
|
|
15179
|
+
}
|
|
15180
|
+
return null;
|
|
15181
|
+
}
|
|
15182
|
+
function memoryPatterns(loki) {
|
|
15183
|
+
const d = readJson(join15(memDir(loki, "semantic"), "patterns.json"));
|
|
15184
|
+
const list = Array.isArray(d) ? d : isObj6(d) && Array.isArray(d.patterns) ? d.patterns : [];
|
|
15185
|
+
return list.filter(isObj6);
|
|
15186
|
+
}
|
|
15187
|
+
var memorySkills = (loki) => jsonFiles(memDir(loki, "skills")).map(readJson).filter(isObj6);
|
|
15188
|
+
function memoryEconomics(loki) {
|
|
15189
|
+
const rawV = readJson(join15(loki, "memory", "token_economics.json"));
|
|
15190
|
+
const raw2 = isObj6(rawV) ? rawV : {};
|
|
15191
|
+
const m = isObj6(raw2.metrics) ? raw2.metrics : {};
|
|
15192
|
+
const n2 = (v) => typeof v === "number" && Number.isFinite(v) ? Math.trunc(v) : 0;
|
|
15193
|
+
const hits = n2(m.cache_hits), misses = n2(m.cache_misses), total = hits + misses;
|
|
15194
|
+
const recorded = Object.keys(m).length > 0;
|
|
15195
|
+
const disc = recorded ? n2(m.discovery_tokens) : null, read = recorded ? n2(m.read_tokens) : null;
|
|
15196
|
+
const cands = [];
|
|
15197
|
+
for (const kind of ["episodic", "semantic"]) {
|
|
15198
|
+
for (const f of jsonFiles(memDir(loki, kind), 300)) {
|
|
15199
|
+
const d = readJson(f);
|
|
15200
|
+
if (!isObj6(d))
|
|
15201
|
+
continue;
|
|
15202
|
+
const ctx = isObj6(d.context) ? d.context : {};
|
|
15203
|
+
cands.push({ id: d.id ?? "", kind, access_count: n2(d.access_count), importance: Number(d.importance) || 0, summary: String(d.summary || d.pattern || ctx.goal || "").slice(0, 160) });
|
|
15204
|
+
}
|
|
15205
|
+
}
|
|
15206
|
+
cands.sort((a, b) => b.access_count - a.access_count || b.importance - a.importance);
|
|
15207
|
+
return {
|
|
15208
|
+
session_id: raw2.session_id ?? null,
|
|
15209
|
+
discovery_tokens: disc,
|
|
15210
|
+
read_tokens: read,
|
|
15211
|
+
total_tokens: recorded ? disc + read : null,
|
|
15212
|
+
cache_hits: hits,
|
|
15213
|
+
cache_misses: misses,
|
|
15214
|
+
hit_rate: total > 0 ? Math.round(hits / total * 1e4) / 1e4 : null,
|
|
15215
|
+
ratio: raw2.ratio ?? null,
|
|
15216
|
+
savings_percent: raw2.savings_percent ?? null,
|
|
15217
|
+
top_patterns: cands.slice(0, 10).filter((p) => p.access_count > 0),
|
|
15218
|
+
discoveryTokens: disc,
|
|
15219
|
+
readTokens: read,
|
|
15220
|
+
savingsPercent: raw2.savings_percent ?? null,
|
|
15221
|
+
raw: raw2
|
|
15222
|
+
};
|
|
15223
|
+
}
|
|
15224
|
+
function contextTracking(loki) {
|
|
15225
|
+
const f = join15(loki, "context", "tracking.json");
|
|
15226
|
+
if (!existsSync8(f)) {
|
|
15227
|
+
return { status: 200, body: {
|
|
15228
|
+
session_id: null,
|
|
15229
|
+
updated_at: null,
|
|
15230
|
+
current: { input_tokens: null, output_tokens: null, cache_read_tokens: null, cache_creation_tokens: null, total_tokens: null, context_window_pct: null, estimated_cost_usd: null },
|
|
15231
|
+
compactions: [],
|
|
15232
|
+
per_iteration: [],
|
|
15233
|
+
totals: { total_input: null, total_output: null, total_cost_usd: null, compaction_count: null, iterations_tracked: null }
|
|
15234
|
+
} };
|
|
15235
|
+
}
|
|
15236
|
+
try {
|
|
15237
|
+
return { status: 200, body: JSON.parse(readFileSync12(f, "utf8")) };
|
|
15238
|
+
} catch {
|
|
15239
|
+
return { status: 500, body: { error: "Failed to read context tracking data" } };
|
|
15240
|
+
}
|
|
15241
|
+
}
|
|
15242
|
+
function mount10(ctx) {
|
|
15243
|
+
const deny = { error: "loopback only without a token" };
|
|
15244
|
+
const read = (path, fn) => ctx.app.get(path, (c) => probeAllowed(ctx, c) ? fn(c, lokiFor(ctx.repoDir)) : c.json(deny, 403));
|
|
15245
|
+
read("/v1/tasks", (c, l) => {
|
|
15246
|
+
if (c.req.query("project_id") !== undefined)
|
|
15247
|
+
return c.json({ error: "project_id filter is not supported by the Control Plane" }, 400);
|
|
15248
|
+
return c.json(tasksList(l, c.req.query("status")));
|
|
15249
|
+
});
|
|
15250
|
+
read("/v1/memory/episodes", (c, l) => {
|
|
15251
|
+
const lim = c.req.query("limit") === undefined ? 50 : Number(c.req.query("limit"));
|
|
15252
|
+
return Number.isInteger(lim) && lim >= 1 && lim <= 1000 ? c.json(memoryEpisodes(l, lim)) : c.json({ error: "limit must be 1..1000" }, 422);
|
|
15253
|
+
});
|
|
15254
|
+
read("/v1/memory/episodes/:id", (c, l) => {
|
|
15255
|
+
const e = memoryEpisode(l, c.req.param("id"));
|
|
15256
|
+
return e ? c.json(e) : c.json({ error: "Episode not found" }, 404);
|
|
15257
|
+
});
|
|
15258
|
+
read("/v1/memory/patterns", (c, l) => c.json(memoryPatterns(l)));
|
|
15259
|
+
read("/v1/memory/patterns/:id", (c, l) => {
|
|
15260
|
+
const p = memoryPatterns(l).find((x) => x.id === c.req.param("id"));
|
|
15261
|
+
return p ? c.json(p) : c.json({ error: "Pattern not found" }, 404);
|
|
15262
|
+
});
|
|
15263
|
+
read("/v1/memory/skills", (c, l) => c.json(memorySkills(l)));
|
|
15264
|
+
read("/v1/memory/economics", (c, l) => c.json(memoryEconomics(l)));
|
|
15265
|
+
read("/v1/context", (c, l) => {
|
|
15266
|
+
const r = contextTracking(l);
|
|
15267
|
+
return c.json(r.body, r.status);
|
|
15268
|
+
});
|
|
15269
|
+
read("/v1/focus", (c) => c.json(focusInfo(ctx.repoDir)));
|
|
15270
|
+
ctx.act.post("/v1/focus", async (c) => {
|
|
15271
|
+
if (!mutationAllowed(c))
|
|
15272
|
+
return c.json({ error: "loopback JSON requests only" }, 403);
|
|
15273
|
+
const body = await c.req.json().catch(() => null);
|
|
15274
|
+
const r = setFocus(ctx.repoDir, isObj6(body) ? body.project_dir : undefined);
|
|
15275
|
+
return r.ok ? c.json(focusInfo(ctx.repoDir)) : c.json({ error: r.error }, 400);
|
|
15276
|
+
});
|
|
15277
|
+
ctx.act.delete("/v1/focus", (c) => {
|
|
15278
|
+
if (!mutationAllowed(c))
|
|
15279
|
+
return c.json({ error: "loopback JSON requests only" }, 403);
|
|
15280
|
+
focus.dir = null;
|
|
15281
|
+
return c.json(focusInfo(ctx.repoDir));
|
|
15282
|
+
});
|
|
15283
|
+
}
|
|
15284
|
+
|
|
14768
15285
|
// src/server/routes/metrics.ts
|
|
14769
|
-
import { readdirSync as
|
|
14770
|
-
import { join as
|
|
14771
|
-
var
|
|
15286
|
+
import { readdirSync as readdirSync7, readFileSync as readFileSync13, statSync as statSync8 } from "fs";
|
|
15287
|
+
import { join as join16 } from "path";
|
|
15288
|
+
var isObj7 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
14772
15289
|
var arr = (v) => Array.isArray(v) ? v : [];
|
|
14773
15290
|
function buildMetricsText(repoDir, now = Date.now()) {
|
|
14774
15291
|
if (!lokiDirExists(repoDir))
|
|
@@ -14784,8 +15301,8 @@ function buildMetricsText(repoDir, now = Date.now()) {
|
|
|
14784
15301
|
L.push(...samples);
|
|
14785
15302
|
L.push("");
|
|
14786
15303
|
};
|
|
14787
|
-
const stRaw = readJson(
|
|
14788
|
-
const state =
|
|
15304
|
+
const stRaw = readJson(join16(loki, "dashboard-state.json"));
|
|
15305
|
+
const state = isObj7(stRaw) ? stRaw : null;
|
|
14789
15306
|
let status = 0;
|
|
14790
15307
|
if (state?.mode === "paused")
|
|
14791
15308
|
status = 2;
|
|
@@ -14793,7 +15310,7 @@ function buildMetricsText(repoDir, now = Date.now()) {
|
|
|
14793
15310
|
status = 1;
|
|
14794
15311
|
else {
|
|
14795
15312
|
try {
|
|
14796
|
-
if (pidAlive(parseInt(
|
|
15313
|
+
if (pidAlive(parseInt(readFileSync13(join16(loki, "loki.pid"), "utf8").trim(), 10)))
|
|
14797
15314
|
status = 1;
|
|
14798
15315
|
} catch {}
|
|
14799
15316
|
}
|
|
@@ -14801,7 +15318,7 @@ function buildMetricsText(repoDir, now = Date.now()) {
|
|
|
14801
15318
|
gauge("loki_iteration_current", "Current iteration number", "gauge", typeof state?.iteration === "number" ? [`loki_iteration_current ${state.iteration}`] : null);
|
|
14802
15319
|
const maxIt = Number.parseInt(process.env.LOKI_MAX_ITERATIONS ?? "", 10);
|
|
14803
15320
|
gauge("loki_iteration_max", "Maximum configured iterations", "gauge", [`loki_iteration_max ${Number.isFinite(maxIt) ? maxIt : 1000}`]);
|
|
14804
|
-
const tasks = state &&
|
|
15321
|
+
const tasks = state && isObj7(state.tasks) ? state.tasks : null;
|
|
14805
15322
|
gauge("loki_tasks_total", "Number of tasks by status", "gauge", tasks ? [
|
|
14806
15323
|
`loki_tasks_total{status="pending"} ${arr(tasks.pending).length}`,
|
|
14807
15324
|
`loki_tasks_total{status="in_progress"} ${arr(tasks.inProgress).length}`,
|
|
@@ -14809,19 +15326,19 @@ function buildMetricsText(repoDir, now = Date.now()) {
|
|
|
14809
15326
|
`loki_tasks_total{status="failed"} ${arr(tasks.failed).length}`
|
|
14810
15327
|
] : null);
|
|
14811
15328
|
let agents = null;
|
|
14812
|
-
const af = readJson(
|
|
15329
|
+
const af = readJson(join16(loki, "state", "agents.json"));
|
|
14813
15330
|
if (Array.isArray(af) && af.length)
|
|
14814
15331
|
agents = af;
|
|
14815
15332
|
else if (state && Array.isArray(state.agents) && state.agents.length)
|
|
14816
15333
|
agents = state.agents;
|
|
14817
|
-
const active = (agents ?? []).filter((a) =>
|
|
15334
|
+
const active = (agents ?? []).filter((a) => isObj7(a) && a.status === "active").length;
|
|
14818
15335
|
gauge("loki_agents_active", "Number of currently active agents", "gauge", agents ? [`loki_agents_active ${active}`] : null);
|
|
14819
15336
|
gauge("loki_agents_total", "Total number of agents registered", "gauge", agents ? [`loki_agents_total ${agents.length}`] : null);
|
|
14820
15337
|
const cost = costSnapshot(loki);
|
|
14821
15338
|
gauge("loki_cost_usd", "Estimated total cost in USD", "gauge", cost.estimated_cost_usd !== null ? [`loki_cost_usd ${cost.estimated_cost_usd}`] : null);
|
|
14822
15339
|
let events2 = null;
|
|
14823
15340
|
try {
|
|
14824
|
-
events2 =
|
|
15341
|
+
events2 = readFileSync13(join16(loki, "events.jsonl"), "utf8").split(`
|
|
14825
15342
|
`).filter((l) => l.trim()).length;
|
|
14826
15343
|
} catch {}
|
|
14827
15344
|
gauge("loki_events_total", "Total number of events recorded", "counter", events2 !== null ? [`loki_events_total ${events2}`] : null);
|
|
@@ -14833,23 +15350,23 @@ function buildMetricsText(repoDir, now = Date.now()) {
|
|
|
14833
15350
|
}
|
|
14834
15351
|
gauge("loki_uptime_seconds", "Seconds since session started", "gauge", uptime !== null ? [`loki_uptime_seconds ${uptime}`] : null);
|
|
14835
15352
|
let total = 0, ver = 0, gaps = 0, notver = 0, unk = 0, att = 0, sig = 0;
|
|
14836
|
-
const pdir =
|
|
15353
|
+
const pdir = join16(loki, "proofs");
|
|
14837
15354
|
let entries = [];
|
|
14838
15355
|
try {
|
|
14839
|
-
entries =
|
|
15356
|
+
entries = readdirSync7(pdir).sort();
|
|
14840
15357
|
} catch {}
|
|
14841
15358
|
for (const e of entries) {
|
|
14842
15359
|
try {
|
|
14843
|
-
if (!
|
|
15360
|
+
if (!statSync8(join16(pdir, e)).isDirectory())
|
|
14844
15361
|
continue;
|
|
14845
15362
|
} catch {
|
|
14846
15363
|
continue;
|
|
14847
15364
|
}
|
|
14848
|
-
const d = readJson(
|
|
14849
|
-
if (!
|
|
15365
|
+
const d = readJson(join16(pdir, e, "proof.json"));
|
|
15366
|
+
if (!isObj7(d))
|
|
14850
15367
|
continue;
|
|
14851
15368
|
total++;
|
|
14852
|
-
const head =
|
|
15369
|
+
const head = isObj7(d.honesty) ? d.honesty.headline : null;
|
|
14853
15370
|
if (head === "VERIFIED")
|
|
14854
15371
|
ver++;
|
|
14855
15372
|
else if (head === "VERIFIED WITH GAPS")
|
|
@@ -14858,7 +15375,7 @@ function buildMetricsText(repoDir, now = Date.now()) {
|
|
|
14858
15375
|
notver++;
|
|
14859
15376
|
else
|
|
14860
15377
|
unk++;
|
|
14861
|
-
if (
|
|
15378
|
+
if (isObj7(d.verification)) {
|
|
14862
15379
|
if (d.verification.attestation)
|
|
14863
15380
|
att++;
|
|
14864
15381
|
if (d.verification.gpg_signature)
|
|
@@ -14880,7 +15397,7 @@ function buildMetricsText(repoDir, now = Date.now()) {
|
|
|
14880
15397
|
`) + `
|
|
14881
15398
|
`;
|
|
14882
15399
|
}
|
|
14883
|
-
function
|
|
15400
|
+
function mount11(ctx) {
|
|
14884
15401
|
ctx.app.get("/v1/metrics", (c) => {
|
|
14885
15402
|
if (!probeAllowed(ctx, c))
|
|
14886
15403
|
return c.json({ error: "loopback only without a token" }, 403);
|
|
@@ -14888,14 +15405,216 @@ function mount9(ctx) {
|
|
|
14888
15405
|
});
|
|
14889
15406
|
}
|
|
14890
15407
|
|
|
15408
|
+
// src/server/routes/session_control.ts
|
|
15409
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync4, readdirSync as readdirSync8, readFileSync as readFileSync14, unlinkSync as unlinkSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
15410
|
+
import { dirname as dirname3, join as join17 } from "path";
|
|
15411
|
+
var isObj8 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
15412
|
+
var ACTIONS = ["pause", "resume", "stop", "council-review"];
|
|
15413
|
+
var isAction = (v) => ACTIONS.includes(v);
|
|
15414
|
+
function sameOrigin(c) {
|
|
15415
|
+
if (c.req.header("sec-fetch-site") === "cross-site")
|
|
15416
|
+
return false;
|
|
15417
|
+
const origin = c.req.header("origin");
|
|
15418
|
+
if (origin === undefined)
|
|
15419
|
+
return true;
|
|
15420
|
+
try {
|
|
15421
|
+
return new URL(origin).host.toLowerCase() === (c.req.header("host") ?? "").toLowerCase();
|
|
15422
|
+
} catch {
|
|
15423
|
+
return false;
|
|
15424
|
+
}
|
|
15425
|
+
}
|
|
15426
|
+
var readJson2 = (p) => {
|
|
15427
|
+
try {
|
|
15428
|
+
return JSON.parse(readFileSync14(p, "utf8"));
|
|
15429
|
+
} catch {
|
|
15430
|
+
return null;
|
|
15431
|
+
}
|
|
15432
|
+
};
|
|
15433
|
+
function runnerPid(loki) {
|
|
15434
|
+
try {
|
|
15435
|
+
const pid = parseInt(readFileSync14(join17(loki, "loki.pid"), "utf8").trim(), 10);
|
|
15436
|
+
return pidAlive(pid) ? pid : null;
|
|
15437
|
+
} catch {
|
|
15438
|
+
return null;
|
|
15439
|
+
}
|
|
15440
|
+
}
|
|
15441
|
+
function applyControl(repoDir, action) {
|
|
15442
|
+
const loki = lokiDirOf(repoDir);
|
|
15443
|
+
const running = runnerPid(loki) !== null;
|
|
15444
|
+
const put = (rel) => {
|
|
15445
|
+
const f = join17(loki, rel);
|
|
15446
|
+
mkdirSync4(dirname3(f), { recursive: true });
|
|
15447
|
+
writeFileSync5(f, new Date().toISOString());
|
|
15448
|
+
};
|
|
15449
|
+
if (action === "council-review") {
|
|
15450
|
+
put(join17("signals", "COUNCIL_REVIEW_REQUESTED"));
|
|
15451
|
+
return { status: 200, body: { success: true, message: "Council review requested" } };
|
|
15452
|
+
}
|
|
15453
|
+
if (action === "resume") {
|
|
15454
|
+
for (const n2 of ["PAUSE", "STOP"]) {
|
|
15455
|
+
try {
|
|
15456
|
+
unlinkSync2(join17(loki, n2));
|
|
15457
|
+
} catch {}
|
|
15458
|
+
}
|
|
15459
|
+
return running ? { status: 200, body: { success: true, message: "Session resumed", process_verified: true } } : { status: 503, body: { success: false, message: "Session process is not running; the signal files were cleared" } };
|
|
15460
|
+
}
|
|
15461
|
+
put(action === "pause" ? "PAUSE" : "STOP");
|
|
15462
|
+
if (!running)
|
|
15463
|
+
return { status: 503, body: { success: false, message: `Session process is not running; the ${action} signal may have no effect` } };
|
|
15464
|
+
return { status: 200, body: { success: true, message: action === "pause" ? "Session paused" : "Stop requested; the runner exits at its next check", process_verified: true } };
|
|
15465
|
+
}
|
|
15466
|
+
function runControl(db, repoDir, action, c, via) {
|
|
15467
|
+
if (!sameOrigin(c)) {
|
|
15468
|
+
audit3(db, { kind: `session.${action}`, target: via, result: "refused", detail: "origin not allowed" });
|
|
15469
|
+
return c.json({ error: "origin not allowed" }, 403);
|
|
15470
|
+
}
|
|
15471
|
+
const r = applyControl(repoDir, action);
|
|
15472
|
+
audit3(db, { kind: `session.${action}`, target: via, result: r.status === 200 ? "ok" : "unverified" });
|
|
15473
|
+
return c.json(r.body, r.status);
|
|
15474
|
+
}
|
|
15475
|
+
function councilState(loki) {
|
|
15476
|
+
const f = join17(loki, "council", "state.json");
|
|
15477
|
+
if (!existsSync9(f))
|
|
15478
|
+
return { enabled: false, total_votes: 0, verdicts: [] };
|
|
15479
|
+
const d = readJson2(f);
|
|
15480
|
+
return isObj8(d) ? d : { enabled: null, total_votes: null, verdicts: null, error: "unreadable_state" };
|
|
15481
|
+
}
|
|
15482
|
+
function councilVerdicts(loki, limit = 20) {
|
|
15483
|
+
const st = readJson2(join17(loki, "council", "state.json"));
|
|
15484
|
+
const verdicts = isObj8(st) && Array.isArray(st.verdicts) ? st.verdicts : [];
|
|
15485
|
+
const votes = join17(loki, "council", "votes");
|
|
15486
|
+
const details = [];
|
|
15487
|
+
let names = [];
|
|
15488
|
+
try {
|
|
15489
|
+
names = readdirSync8(votes, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort().reverse();
|
|
15490
|
+
} catch {}
|
|
15491
|
+
const text2 = (p) => {
|
|
15492
|
+
try {
|
|
15493
|
+
return readFileSync14(p, "utf8");
|
|
15494
|
+
} catch {
|
|
15495
|
+
return null;
|
|
15496
|
+
}
|
|
15497
|
+
};
|
|
15498
|
+
for (const n2 of names.slice(0, limit)) {
|
|
15499
|
+
const dir = join17(votes, n2), d = { iteration: n2 };
|
|
15500
|
+
const ev = text2(join17(dir, "evidence.md"));
|
|
15501
|
+
if (ev !== null)
|
|
15502
|
+
d.evidence_preview = ev.slice(0, 500);
|
|
15503
|
+
let files = [];
|
|
15504
|
+
try {
|
|
15505
|
+
files = readdirSync8(dir).filter((f) => /^member-.*\.txt$/.test(f)).sort();
|
|
15506
|
+
} catch {}
|
|
15507
|
+
d.members = files.flatMap((f) => {
|
|
15508
|
+
const t = text2(join17(dir, f));
|
|
15509
|
+
return t === null ? [] : [{ member: f.replace(/\.txt$/, ""), content: t.trim() }];
|
|
15510
|
+
});
|
|
15511
|
+
const con = text2(join17(dir, "contrarian.txt"));
|
|
15512
|
+
if (con !== null)
|
|
15513
|
+
d.contrarian = con.trim();
|
|
15514
|
+
details.push(d);
|
|
15515
|
+
}
|
|
15516
|
+
return { verdicts, details };
|
|
15517
|
+
}
|
|
15518
|
+
function councilConvergence(loki) {
|
|
15519
|
+
const dataPoints = [];
|
|
15520
|
+
let raw2 = "";
|
|
15521
|
+
try {
|
|
15522
|
+
raw2 = readFileSync14(join17(loki, "council", "convergence.log"), "utf8");
|
|
15523
|
+
} catch {}
|
|
15524
|
+
for (const line2 of raw2.trim().split(`
|
|
15525
|
+
`)) {
|
|
15526
|
+
const p = line2.split("|");
|
|
15527
|
+
if (p.length < 5)
|
|
15528
|
+
continue;
|
|
15529
|
+
const n2 = p.slice(1, 5).map((x) => /^-?\d+$/.test(x.trim()) ? Number(x) : NaN);
|
|
15530
|
+
if (n2.some(Number.isNaN))
|
|
15531
|
+
continue;
|
|
15532
|
+
dataPoints.push({ timestamp: p[0], iteration: n2[0], files_changed: n2[1], no_change_streak: n2[2], done_signals: n2[3] });
|
|
15533
|
+
}
|
|
15534
|
+
return { dataPoints };
|
|
15535
|
+
}
|
|
15536
|
+
function councilReport(loki) {
|
|
15537
|
+
try {
|
|
15538
|
+
return { report: readFileSync14(join17(loki, "council", "report.md"), "utf8") };
|
|
15539
|
+
} catch {
|
|
15540
|
+
return { report: null };
|
|
15541
|
+
}
|
|
15542
|
+
}
|
|
15543
|
+
function councilTranscripts(loki, q) {
|
|
15544
|
+
const limit = Math.min(Math.max(q.limit ?? 20, 1), 200);
|
|
15545
|
+
let since = null;
|
|
15546
|
+
if (q.since) {
|
|
15547
|
+
since = Date.parse(q.since);
|
|
15548
|
+
if (Number.isNaN(since))
|
|
15549
|
+
return { error: "Invalid 'since' timestamp format; expected ISO8601" };
|
|
15550
|
+
}
|
|
15551
|
+
const dir = join17(loki, "council", "transcripts");
|
|
15552
|
+
let names = [];
|
|
15553
|
+
try {
|
|
15554
|
+
names = readdirSync8(dir).filter((f) => /^iter-.*\.json$/.test(f)).sort().reverse();
|
|
15555
|
+
} catch {
|
|
15556
|
+
return { transcripts: [], total: 0, latest_id: null };
|
|
15557
|
+
}
|
|
15558
|
+
const out2 = [];
|
|
15559
|
+
for (const n2 of names) {
|
|
15560
|
+
const rec = readJson2(join17(dir, n2));
|
|
15561
|
+
if (!isObj8(rec) || typeof rec.iteration_id !== "string")
|
|
15562
|
+
continue;
|
|
15563
|
+
if (since !== null) {
|
|
15564
|
+
const t = Date.parse(String(rec.timestamp ?? ""));
|
|
15565
|
+
if (Number.isNaN(t) || t <= since)
|
|
15566
|
+
continue;
|
|
15567
|
+
}
|
|
15568
|
+
if (q.iter_min !== undefined && Number(rec.iteration ?? 0) < q.iter_min)
|
|
15569
|
+
continue;
|
|
15570
|
+
out2.push(rec);
|
|
15571
|
+
if (out2.length >= limit)
|
|
15572
|
+
break;
|
|
15573
|
+
}
|
|
15574
|
+
return { transcripts: out2, total: out2.length, latest_id: out2.length ? out2[0].iteration_id : null };
|
|
15575
|
+
}
|
|
15576
|
+
var intQ = (v, max) => v === undefined ? undefined : /^\d+$/.test(v) && Number(v) <= max ? Number(v) : null;
|
|
15577
|
+
function mount12(ctx) {
|
|
15578
|
+
const deny = { error: "loopback only without a token" };
|
|
15579
|
+
const loki = () => lokiDirOf(ctx.repoDir);
|
|
15580
|
+
ctx.act.post("/v1/control/:action", (c) => {
|
|
15581
|
+
if (!ctx.local(c))
|
|
15582
|
+
return c.json({ error: "loopback JSON requests only" }, 403);
|
|
15583
|
+
const action = c.req.param("action");
|
|
15584
|
+
if (!isAction(action))
|
|
15585
|
+
return c.json({ error: `action must be one of ${ACTIONS.join(", ")}` }, 400);
|
|
15586
|
+
return runControl(ctx.db, ctx.repoDir, action, c, "v1");
|
|
15587
|
+
});
|
|
15588
|
+
const read = (path, f) => ctx.app.get(path, (c) => {
|
|
15589
|
+
if (!probeAllowed(ctx, c))
|
|
15590
|
+
return c.json(deny, 403);
|
|
15591
|
+
const r = f(c);
|
|
15592
|
+
return r instanceof Response ? r : c.json(r);
|
|
15593
|
+
});
|
|
15594
|
+
read("/v1/council/state", () => councilState(loki()));
|
|
15595
|
+
read("/v1/council/verdicts", (c) => {
|
|
15596
|
+
const n2 = intQ(c.req.query("limit"), 1000);
|
|
15597
|
+
return n2 === null || n2 === 0 ? c.json({ error: "limit must be 1..1000" }, 422) : councilVerdicts(loki(), n2);
|
|
15598
|
+
});
|
|
15599
|
+
read("/v1/council/convergence", () => councilConvergence(loki()));
|
|
15600
|
+
read("/v1/council/report", () => councilReport(loki()));
|
|
15601
|
+
read("/v1/council/transcripts", (c) => {
|
|
15602
|
+
const limit = intQ(c.req.query("limit"), 200), iterMin = intQ(c.req.query("iter_min"), Number.MAX_SAFE_INTEGER);
|
|
15603
|
+
if (limit === null || limit === 0 || iterMin === null)
|
|
15604
|
+
return c.json({ error: "limit must be 1..200 and iter_min a non-negative integer" }, 422);
|
|
15605
|
+
const r = councilTranscripts(loki(), { limit, since: c.req.query("since"), iter_min: iterMin });
|
|
15606
|
+
return "error" in r ? c.json(r, 400) : r;
|
|
15607
|
+
});
|
|
15608
|
+
}
|
|
15609
|
+
|
|
14891
15610
|
// src/server/routes/integrations.ts
|
|
14892
15611
|
import { spawn as spawn2 } from "child_process";
|
|
14893
|
-
import { existsSync as
|
|
14894
|
-
import { delimiter, join as
|
|
15612
|
+
import { existsSync as existsSync10, readFileSync as readFileSync15, statSync as statSync9 } from "fs";
|
|
15613
|
+
import { delimiter, join as join18 } from "path";
|
|
14895
15614
|
var GH_TIMEOUT_MS = 3000;
|
|
14896
15615
|
var isSet = (n2) => !!n2 && (process.env[n2] ?? "") !== "";
|
|
14897
15616
|
function ghAuthed(pathVar = process.env.PATH ?? "", timeoutMs = GH_TIMEOUT_MS) {
|
|
14898
|
-
const exe = pathVar.split(delimiter).filter(Boolean).map((d) =>
|
|
15617
|
+
const exe = pathVar.split(delimiter).filter(Boolean).map((d) => join18(d, "gh")).find((p) => existsSync10(p));
|
|
14899
15618
|
if (!exe)
|
|
14900
15619
|
return Promise.resolve(null);
|
|
14901
15620
|
return new Promise((resolve4) => {
|
|
@@ -14927,9 +15646,9 @@ function ghAuthed(pathVar = process.env.PATH ?? "", timeoutMs = GH_TIMEOUT_MS) {
|
|
|
14927
15646
|
function configuredName(repoDir, path) {
|
|
14928
15647
|
try {
|
|
14929
15648
|
const r = resolveConfigFile(repoDir);
|
|
14930
|
-
if (!r.ok || !r.exists ||
|
|
15649
|
+
if (!r.ok || !r.exists || statSync9(r.file).size > 1e6)
|
|
14931
15650
|
return null;
|
|
14932
|
-
const doc = $parseDocument(
|
|
15651
|
+
const doc = $parseDocument(readFileSync15(r.file, "utf8"));
|
|
14933
15652
|
if (doc.errors.length)
|
|
14934
15653
|
return null;
|
|
14935
15654
|
const v = doc.getIn(path);
|
|
@@ -14940,10 +15659,10 @@ function configuredName(repoDir, path) {
|
|
|
14940
15659
|
}
|
|
14941
15660
|
function mcpServers(repoDir) {
|
|
14942
15661
|
try {
|
|
14943
|
-
const p =
|
|
14944
|
-
if (!
|
|
15662
|
+
const p = join18(repoDir, ".mcp.json");
|
|
15663
|
+
if (!existsSync10(p) || statSync9(p).size > 1e6)
|
|
14945
15664
|
return null;
|
|
14946
|
-
const j = JSON.parse(
|
|
15665
|
+
const j = JSON.parse(readFileSync15(p, "utf8"));
|
|
14947
15666
|
return j.mcpServers && typeof j.mcpServers === "object" ? Object.keys(j.mcpServers).length : null;
|
|
14948
15667
|
} catch {
|
|
14949
15668
|
return null;
|
|
@@ -15010,7 +15729,7 @@ async function listIntegrations(repoDir, gh = () => ghAuthed()) {
|
|
|
15010
15729
|
});
|
|
15011
15730
|
return rows;
|
|
15012
15731
|
}
|
|
15013
|
-
function
|
|
15732
|
+
function mount13(ctx) {
|
|
15014
15733
|
const cached = ttlCache();
|
|
15015
15734
|
ctx.app.get("/v1/integrations", async (c) => {
|
|
15016
15735
|
if (!probeAllowed(ctx, c))
|
|
@@ -15021,7 +15740,7 @@ function mount10(ctx) {
|
|
|
15021
15740
|
|
|
15022
15741
|
// src/server/routes/merge_risk.ts
|
|
15023
15742
|
import { spawn as spawn3 } from "child_process";
|
|
15024
|
-
import { realpathSync as
|
|
15743
|
+
import { realpathSync as realpathSync8 } from "fs";
|
|
15025
15744
|
import { basename as basename5, resolve as resolve4 } from "path";
|
|
15026
15745
|
var PR = /^[0-9]{1,7}$/;
|
|
15027
15746
|
var REF = /^[A-Za-z0-9._/][A-Za-z0-9._/-]{0,99}$/;
|
|
@@ -15113,14 +15832,14 @@ function parseRisk(out2) {
|
|
|
15113
15832
|
return null;
|
|
15114
15833
|
}
|
|
15115
15834
|
}
|
|
15116
|
-
function
|
|
15835
|
+
function mount14(ctx) {
|
|
15117
15836
|
const { act, db } = ctx;
|
|
15118
15837
|
const bin = () => ctx.startBin ?? process.env["LOKI_BIN"] ?? "loki";
|
|
15119
15838
|
const busy2 = new Set;
|
|
15120
15839
|
const active = { run: 0, queue: 0, risk: 0 };
|
|
15121
15840
|
const real2 = (p) => {
|
|
15122
15841
|
try {
|
|
15123
|
-
return
|
|
15842
|
+
return realpathSync8(p);
|
|
15124
15843
|
} catch {
|
|
15125
15844
|
return null;
|
|
15126
15845
|
}
|
|
@@ -15317,7 +16036,7 @@ function deriveNotifications(db) {
|
|
|
15317
16036
|
}
|
|
15318
16037
|
return out2.sort((a, b) => a.ts < b.ts ? 1 : a.ts > b.ts ? -1 : a.id < b.id ? -1 : 1);
|
|
15319
16038
|
}
|
|
15320
|
-
function
|
|
16039
|
+
function mount15(ctx) {
|
|
15321
16040
|
ctx.app.get("/v1/notifications", (c) => {
|
|
15322
16041
|
const all = deriveNotifications(ctx.db);
|
|
15323
16042
|
const limit = Math.min(Math.max(Number.parseInt(c.req.query("limit") ?? "50", 10) || 50, 1), 200);
|
|
@@ -15331,8 +16050,8 @@ function mount12(ctx) {
|
|
|
15331
16050
|
|
|
15332
16051
|
// src/server/routes/providers.ts
|
|
15333
16052
|
import { spawn as spawn4 } from "child_process";
|
|
15334
|
-
import { existsSync as
|
|
15335
|
-
import { delimiter as delimiter2, join as
|
|
16053
|
+
import { existsSync as existsSync11, readFileSync as readFileSync16 } from "fs";
|
|
16054
|
+
import { delimiter as delimiter2, join as join19 } from "path";
|
|
15336
16055
|
var ORDER = ["claude", "codex", "cline", "aider", "opencode"];
|
|
15337
16056
|
var DEPRECATED = ["gemini"];
|
|
15338
16057
|
var AUTH_ENV = {
|
|
@@ -15348,8 +16067,8 @@ function findOnPath(bin, pathVar) {
|
|
|
15348
16067
|
for (const d of pathVar.split(delimiter2)) {
|
|
15349
16068
|
if (!d)
|
|
15350
16069
|
continue;
|
|
15351
|
-
const p =
|
|
15352
|
-
if (
|
|
16070
|
+
const p = join19(d, bin);
|
|
16071
|
+
if (existsSync11(p))
|
|
15353
16072
|
return p;
|
|
15354
16073
|
}
|
|
15355
16074
|
return null;
|
|
@@ -15393,14 +16112,14 @@ function probeCli(bin, timeoutMs, pathVar = process.env.PATH ?? "") {
|
|
|
15393
16112
|
});
|
|
15394
16113
|
}
|
|
15395
16114
|
function providersDir() {
|
|
15396
|
-
const cands = [process.env.LOKI_PROVIDERS_DIR,
|
|
15397
|
-
return cands.find((d) => !!d &&
|
|
16115
|
+
const cands = [process.env.LOKI_PROVIDERS_DIR, join19(import.meta.dir, "../../../../../providers"), join19(import.meta.dir, "../../../providers")];
|
|
16116
|
+
return cands.find((d) => !!d && existsSync11(join19(d, "model_catalog.json"))) ?? null;
|
|
15398
16117
|
}
|
|
15399
16118
|
function loadCatalog(dir) {
|
|
15400
16119
|
if (!dir)
|
|
15401
16120
|
return { providers: {}, updated: null };
|
|
15402
16121
|
try {
|
|
15403
|
-
const j = JSON.parse(
|
|
16122
|
+
const j = JSON.parse(readFileSync16(join19(dir, "model_catalog.json"), "utf8"));
|
|
15404
16123
|
return { providers: j.providers ?? {}, updated: typeof j.updated === "string" ? j.updated : null };
|
|
15405
16124
|
} catch {
|
|
15406
16125
|
return { providers: {}, updated: null };
|
|
@@ -15431,7 +16150,7 @@ async function listProviders(timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
|
15431
16150
|
}));
|
|
15432
16151
|
return { providers: rows, catalog_updated: cat.updated, probe_timeout_ms: timeoutMs };
|
|
15433
16152
|
}
|
|
15434
|
-
function
|
|
16153
|
+
function mount16(ctx) {
|
|
15435
16154
|
const cached = ttlCache();
|
|
15436
16155
|
ctx.app.get("/v1/providers", async (c) => {
|
|
15437
16156
|
if (!probeAllowed(ctx, c))
|
|
@@ -15504,7 +16223,7 @@ function computeStats(db, since) {
|
|
|
15504
16223
|
receipts: { total, signed }
|
|
15505
16224
|
};
|
|
15506
16225
|
}
|
|
15507
|
-
function
|
|
16226
|
+
function mount17(ctx) {
|
|
15508
16227
|
ctx.app.get("/v1/stats", (c) => {
|
|
15509
16228
|
const since = c.req.query("since") ?? null;
|
|
15510
16229
|
if (since !== null && (!/^\d{4}-\d{2}-\d{2}/.test(since) || Number.isNaN(Date.parse(since))))
|
|
@@ -15518,7 +16237,7 @@ var num = (v, d) => {
|
|
|
15518
16237
|
const n2 = Number(v);
|
|
15519
16238
|
return Number.isFinite(n2) && n2 > 0 ? n2 : d;
|
|
15520
16239
|
};
|
|
15521
|
-
function
|
|
16240
|
+
function mount18(ctx) {
|
|
15522
16241
|
const pollMs = num(process.env.LOKI_CONTROL_STREAM_POLL_MS, 1000);
|
|
15523
16242
|
const heartbeatMs = num(process.env.LOKI_CONTROL_STREAM_HEARTBEAT_MS, 15000);
|
|
15524
16243
|
const maxStreams = num(process.env.LOKI_CONTROL_STREAM_MAX, 32);
|
|
@@ -15636,27 +16355,27 @@ data: ${JSON.stringify({ source: k.slice(0, i), run: k.slice(i + 1) })}
|
|
|
15636
16355
|
}
|
|
15637
16356
|
|
|
15638
16357
|
// src/server/routes/verify.ts
|
|
15639
|
-
import { readFileSync as
|
|
16358
|
+
import { readFileSync as readFileSync25, realpathSync as realpathSync11 } from "fs";
|
|
15640
16359
|
import { createPublicKey as createPublicKey5 } from "crypto";
|
|
15641
|
-
import { join as
|
|
16360
|
+
import { join as join28, sep as sep9 } from "path";
|
|
15642
16361
|
|
|
15643
16362
|
// ../../loki-ts/src/engine10/verify_cmd.ts
|
|
15644
|
-
import { existsSync as
|
|
15645
|
-
import { dirname as
|
|
16363
|
+
import { existsSync as existsSync17, readdirSync as readdirSync12, readFileSync as readFileSync24, statSync as statSync12 } from "fs";
|
|
16364
|
+
import { dirname as dirname12, join as join27 } from "path";
|
|
15646
16365
|
import { createHash as createHash10, createPublicKey as createPublicKey4, verify as verify3 } from "crypto";
|
|
15647
16366
|
|
|
15648
16367
|
// ../../loki-ts/src/util/paths.ts
|
|
15649
|
-
import { resolve as resolve5, dirname as
|
|
16368
|
+
import { resolve as resolve5, dirname as dirname4 } from "path";
|
|
15650
16369
|
import { fileURLToPath } from "url";
|
|
15651
|
-
import { existsSync as
|
|
15652
|
-
var HERE =
|
|
16370
|
+
import { existsSync as existsSync12 } from "fs";
|
|
16371
|
+
var HERE = dirname4(fileURLToPath(import.meta.url));
|
|
15653
16372
|
function findRepoRoot() {
|
|
15654
16373
|
let dir = HERE;
|
|
15655
16374
|
for (let i = 0;i < 6; i++) {
|
|
15656
|
-
if (
|
|
16375
|
+
if (existsSync12(resolve5(dir, "VERSION")) && existsSync12(resolve5(dir, "autonomy/run.sh"))) {
|
|
15657
16376
|
return dir;
|
|
15658
16377
|
}
|
|
15659
|
-
const parent =
|
|
16378
|
+
const parent = dirname4(dir);
|
|
15660
16379
|
if (parent === dir)
|
|
15661
16380
|
break;
|
|
15662
16381
|
dir = parent;
|
|
@@ -15667,15 +16386,15 @@ var REPO_ROOT = findRepoRoot();
|
|
|
15667
16386
|
|
|
15668
16387
|
// ../../loki-ts/src/engine10/stages/seal.ts
|
|
15669
16388
|
import { createHash as createHash9, randomBytes as randomBytes3, createPrivateKey, createPublicKey as createPublicKey2, generateKeyPairSync, sign } from "crypto";
|
|
15670
|
-
import { chmodSync as chmodSync4, existsSync as
|
|
16389
|
+
import { chmodSync as chmodSync4, existsSync as existsSync16, readdirSync as readdirSync11, linkSync, mkdirSync as mkdirSync8, readFileSync as readFileSync22, statSync as statSync11, unlinkSync as unlinkSync4, writeFileSync as writeFileSync9 } from "fs";
|
|
15671
16390
|
import { homedir as homedir5 } from "os";
|
|
15672
|
-
import { basename as basename9, dirname as
|
|
16391
|
+
import { basename as basename9, dirname as dirname11, join as join26 } from "path";
|
|
15673
16392
|
|
|
15674
16393
|
// ../../loki-ts/src/e10ext/assert_delta.ts
|
|
15675
|
-
import { execFileSync as
|
|
15676
|
-
import { mkdtempSync, readFileSync as
|
|
16394
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
16395
|
+
import { mkdtempSync, readFileSync as readFileSync17, rmSync as rmSync3 } from "fs";
|
|
15677
16396
|
import { tmpdir } from "os";
|
|
15678
|
-
import { join as
|
|
16397
|
+
import { join as join20 } from "path";
|
|
15679
16398
|
import { basename as basename6 } from "path";
|
|
15680
16399
|
var WEAKENED = { verdict: "weakened", changes: [] };
|
|
15681
16400
|
var PY = String.raw`
|
|
@@ -15834,8 +16553,8 @@ function classifyAssertDelta(inp) {
|
|
|
15834
16553
|
return WEAKENED;
|
|
15835
16554
|
let cwd = "";
|
|
15836
16555
|
try {
|
|
15837
|
-
cwd = mkdtempSync(
|
|
15838
|
-
const raw2 =
|
|
16556
|
+
cwd = mkdtempSync(join20(tmpdir(), "loki-ad-"));
|
|
16557
|
+
const raw2 = execFileSync3("python3", ["-I", "-c", PY], {
|
|
15839
16558
|
input: JSON.stringify({ path: inp.path, base: inp.base, head: inp.head, task: inp.task }),
|
|
15840
16559
|
cwd,
|
|
15841
16560
|
env: process.env,
|
|
@@ -15857,15 +16576,15 @@ function classifyAssertDelta(inp) {
|
|
|
15857
16576
|
return WEAKENED;
|
|
15858
16577
|
} finally {
|
|
15859
16578
|
if (cwd)
|
|
15860
|
-
|
|
16579
|
+
rmSync3(cwd, { recursive: true, force: true });
|
|
15861
16580
|
}
|
|
15862
16581
|
}
|
|
15863
16582
|
function assertDeltaNotes(repoDir, baseSha, headRef, path, task, baseCounts, headCounts) {
|
|
15864
16583
|
if (!baseCounts || !headCounts)
|
|
15865
16584
|
return null;
|
|
15866
16585
|
try {
|
|
15867
|
-
const show = (ref) =>
|
|
15868
|
-
const head = headRef ? show(headRef) :
|
|
16586
|
+
const show = (ref) => execFileSync3("git", ["show", `${ref}:${path}`], { cwd: repoDir, encoding: "utf8", env: process.env, stdio: ["ignore", "pipe", "ignore"] });
|
|
16587
|
+
const head = headRef ? show(headRef) : readFileSync17(join20(repoDir, path), "utf8");
|
|
15869
16588
|
const r = classifyTestEdit(path, show(baseSha), head, task, baseCounts, headCounts);
|
|
15870
16589
|
return r[0]?.kind === "literal-only" ? r.map((e) => `assertion value changed (not shown to be required by the task): ${e.file}:${e.line} ${e.old} -> ${e.new}`) : null;
|
|
15871
16590
|
} catch {
|
|
@@ -15881,21 +16600,21 @@ function classifyTestEdit(file, oldText, newText, taskText, baseCounts, headCoun
|
|
|
15881
16600
|
}
|
|
15882
16601
|
|
|
15883
16602
|
// ../../loki-ts/src/e10ext/discard.ts
|
|
15884
|
-
import { execFileSync as
|
|
15885
|
-
import { chmodSync as chmodSync3, closeSync as closeSync4, lstatSync as
|
|
15886
|
-
import { dirname as
|
|
16603
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
16604
|
+
import { chmodSync as chmodSync3, closeSync as closeSync4, lstatSync as lstatSync3, mkdirSync as mkdirSync5, openSync as openSync4, readFileSync as readFileSync18, realpathSync as realpathSync9, rmdirSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
16605
|
+
import { dirname as dirname5, join as join21, relative, sep as sep6 } from "path";
|
|
15887
16606
|
function safeRestore(repoDir, f, want, mode) {
|
|
15888
|
-
const root =
|
|
16607
|
+
const root = realpathSync9(repoDir), fp = join21(root, f), rel = relative(root, dirname5(fp));
|
|
15889
16608
|
if (rel.startsWith(".."))
|
|
15890
16609
|
throw new Error("outside repo");
|
|
15891
16610
|
let cur = root;
|
|
15892
|
-
for (const part of rel === "" ? [] : rel.split(
|
|
15893
|
-
cur =
|
|
16611
|
+
for (const part of rel === "" ? [] : rel.split(sep6)) {
|
|
16612
|
+
cur = join21(cur, part);
|
|
15894
16613
|
let st2;
|
|
15895
16614
|
try {
|
|
15896
|
-
st2 =
|
|
16615
|
+
st2 = lstatSync3(cur);
|
|
15897
16616
|
} catch {
|
|
15898
|
-
|
|
16617
|
+
mkdirSync5(cur);
|
|
15899
16618
|
continue;
|
|
15900
16619
|
}
|
|
15901
16620
|
if (!st2.isDirectory())
|
|
@@ -15903,11 +16622,11 @@ function safeRestore(repoDir, f, want, mode) {
|
|
|
15903
16622
|
}
|
|
15904
16623
|
let st;
|
|
15905
16624
|
try {
|
|
15906
|
-
st =
|
|
16625
|
+
st = lstatSync3(fp);
|
|
15907
16626
|
} catch {
|
|
15908
16627
|
st = null;
|
|
15909
16628
|
}
|
|
15910
|
-
if (st?.isFile() &&
|
|
16629
|
+
if (st?.isFile() && readFileSync18(fp).equals(want)) {
|
|
15911
16630
|
chmodSync3(fp, mode);
|
|
15912
16631
|
return;
|
|
15913
16632
|
}
|
|
@@ -15915,11 +16634,11 @@ function safeRestore(repoDir, f, want, mode) {
|
|
|
15915
16634
|
if (st.isDirectory())
|
|
15916
16635
|
rmdirSync(fp);
|
|
15917
16636
|
else
|
|
15918
|
-
|
|
16637
|
+
unlinkSync3(fp);
|
|
15919
16638
|
}
|
|
15920
16639
|
const fd = openSync4(fp, "wx", mode);
|
|
15921
16640
|
try {
|
|
15922
|
-
|
|
16641
|
+
writeFileSync6(fd, want);
|
|
15923
16642
|
} finally {
|
|
15924
16643
|
closeSync4(fd);
|
|
15925
16644
|
}
|
|
@@ -15939,15 +16658,15 @@ async function discardIfSatisfied(git, base, o, staged, keep, repoDir) {
|
|
|
15939
16658
|
return { status: "failed", data: {}, reason: "already-satisfied discard failed" };
|
|
15940
16659
|
const notProven = [];
|
|
15941
16660
|
for (const [f, v] of Object.entries(o.intake?.preexisting_untracked_blobs ?? {})) {
|
|
15942
|
-
const fp =
|
|
16661
|
+
const fp = join21(repoDir, f);
|
|
15943
16662
|
try {
|
|
15944
16663
|
if (v.startsWith("!")) {
|
|
15945
|
-
const st =
|
|
16664
|
+
const st = lstatSync3(fp);
|
|
15946
16665
|
if (`!${st.size}:${st.mtimeMs}:${st.ctimeMs}` !== v)
|
|
15947
16666
|
throw new Error("changed, not snapshotted");
|
|
15948
16667
|
continue;
|
|
15949
16668
|
}
|
|
15950
|
-
const [sha2, mode] = v.split(" "), want =
|
|
16669
|
+
const [sha2, mode] = v.split(" "), want = execFileSync4("git", ["cat-file", "blob", sha2], { cwd: repoDir, env: process.env, maxBuffer: 1 << 28 });
|
|
15951
16670
|
safeRestore(repoDir, f, want, parseInt(mode ?? "644", 8));
|
|
15952
16671
|
} catch {
|
|
15953
16672
|
notProven.push(f);
|
|
@@ -15958,11 +16677,11 @@ async function discardIfSatisfied(git, base, o, staged, keep, repoDir) {
|
|
|
15958
16677
|
try {
|
|
15959
16678
|
let m = 420;
|
|
15960
16679
|
try {
|
|
15961
|
-
const l =
|
|
16680
|
+
const l = lstatSync3(join21(repoDir, f));
|
|
15962
16681
|
if (l.isFile())
|
|
15963
16682
|
m = l.mode & 511;
|
|
15964
16683
|
} catch {}
|
|
15965
|
-
safeRestore(repoDir, f,
|
|
16684
|
+
safeRestore(repoDir, f, execFileSync4("git", ["cat-file", "blob", pre[f]], { cwd: repoDir, env: process.env, maxBuffer: 1 << 28 }), m);
|
|
15966
16685
|
} catch {
|
|
15967
16686
|
notProven.push(f);
|
|
15968
16687
|
}
|
|
@@ -15971,11 +16690,11 @@ async function discardIfSatisfied(git, base, o, staged, keep, repoDir) {
|
|
|
15971
16690
|
}
|
|
15972
16691
|
|
|
15973
16692
|
// ../../loki-ts/src/e10ext/commit_filter.ts
|
|
15974
|
-
import { dirname as
|
|
16693
|
+
import { dirname as dirname6 } from "path";
|
|
15975
16694
|
|
|
15976
16695
|
// ../../loki-ts/src/e10ext/preexisting_dirty.ts
|
|
15977
|
-
import { execFileSync as
|
|
15978
|
-
var blob2 = (repoDir, path, env = process.env) =>
|
|
16696
|
+
import { execFileSync as execFileSync5 } from "child_process";
|
|
16697
|
+
var blob2 = (repoDir, path, env = process.env) => execFileSync5("git", ["hash-object", "-w", "--no-filters", "--", path], { cwd: repoDir, encoding: "utf8", env, stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
15979
16698
|
function untouchedSinceIntake(repoDir, recorded, env = process.env) {
|
|
15980
16699
|
if (!recorded || typeof recorded !== "object")
|
|
15981
16700
|
return [];
|
|
@@ -15991,14 +16710,14 @@ function untouchedSinceIntake(repoDir, recorded, env = process.env) {
|
|
|
15991
16710
|
// ../../loki-ts/src/e10ext/commit_filter.ts
|
|
15992
16711
|
var parseStaged = (out2) => out2.split("\x00").reduce((a, t, i, all) => i % 2 === 0 && t ? [...a, { st: t, f: all[i + 1] }] : a, []);
|
|
15993
16712
|
function dropSet(repoDir, staged, preexistingDirty, env = process.env) {
|
|
15994
|
-
return staged.concat(untouchedSinceIntake(repoDir, preexistingDirty, env).map((f) => ({ st: "L", f }))).filter(({ st, f }) => f.startsWith(".loki/") || st === "L" || /(^|\/)loki_wall_[^/]*$/.test(f) || st === "A" && !staged.some(({ f: m }) => /(^|\/)(package\.json|pyproject\.toml|requirements\.txt|Cargo\.toml|go\.mod)$/.test(m) &&
|
|
16713
|
+
return staged.concat(untouchedSinceIntake(repoDir, preexistingDirty, env).map((f) => ({ st: "L", f }))).filter(({ st, f }) => f.startsWith(".loki/") || st === "L" || /(^|\/)loki_wall_[^/]*$/.test(f) || st === "A" && !staged.some(({ f: m }) => /(^|\/)(package\.json|pyproject\.toml|requirements\.txt|Cargo\.toml|go\.mod)$/.test(m) && dirname6(m) === dirname6(f)) && /(^|\/)(package-lock\.json|yarn\.lock|pnpm-lock\.yaml|bun\.lockb?|poetry\.lock|Cargo\.lock|go\.sum)$/.test(f));
|
|
15995
16714
|
}
|
|
15996
16715
|
|
|
15997
16716
|
// ../../loki-ts/src/e10ext/scope.ts
|
|
15998
|
-
import { basename as basename8, dirname as
|
|
16717
|
+
import { basename as basename8, dirname as dirname7 } from "path";
|
|
15999
16718
|
|
|
16000
16719
|
// ../../loki-ts/src/engine10/testmap.ts
|
|
16001
|
-
import { basename as basename7, extname, join as
|
|
16720
|
+
import { basename as basename7, extname, join as join22, relative as relative2, sep as sep7 } from "path";
|
|
16002
16721
|
var SKIP_DIRS = new Set([
|
|
16003
16722
|
".git",
|
|
16004
16723
|
"node_modules",
|
|
@@ -16023,14 +16742,14 @@ function isTestFile(rel) {
|
|
|
16023
16742
|
}
|
|
16024
16743
|
|
|
16025
16744
|
// ../../loki-ts/src/features/speed/unit_mode.ts
|
|
16026
|
-
import { closeSync as closeSync5, constants, fstatSync as fstatSync3, lstatSync as
|
|
16745
|
+
import { closeSync as closeSync5, constants, fstatSync as fstatSync3, lstatSync as lstatSync4, openSync as openSync5, readSync as readSync3, realpathSync as realpathSync10 } from "fs";
|
|
16027
16746
|
var MAX_SPEC_BYTES = 1024 * 1024;
|
|
16028
16747
|
var MAX_TOKEN_BUDGET = 1e9;
|
|
16029
16748
|
var norm2 = (p) => p.replace(/^\.\//, "");
|
|
16030
16749
|
var CTRL = /[\x00-\x1f\x7f]/;
|
|
16031
16750
|
var strs = (v) => Array.isArray(v) && v.every((x) => typeof x === "string" && !CTRL.test(x)) ? v.map(norm2) : null;
|
|
16032
16751
|
function readBounded(path) {
|
|
16033
|
-
const l =
|
|
16752
|
+
const l = lstatSync4(path);
|
|
16034
16753
|
if (l.isSymbolicLink() || !l.isFile() || l.size > MAX_SPEC_BYTES)
|
|
16035
16754
|
return null;
|
|
16036
16755
|
const fd = openSync5(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
@@ -16110,8 +16829,8 @@ function outsideScope(o, staged) {
|
|
|
16110
16829
|
const text2 = `${plan}
|
|
16111
16830
|
${typeof o.intake?.task === "string" ? o.intake.task : ""}
|
|
16112
16831
|
${contractText(o)}`, pre = o.intake?.preexisting_dirty ?? {};
|
|
16113
|
-
const toks = surfaceTokens(text2), plannedDirs = new Set(rel.map((r) =>
|
|
16114
|
-
return staged.filter(({ st, f }) => st !== "A" && !isTestFile(f) && !Object.hasOwn(pre, f) && !rel.includes(f) && !mentions(text2, f) && !mentions(text2, basename8(f)) && !plannedDirs.has(
|
|
16832
|
+
const toks = surfaceTokens(text2), plannedDirs = new Set(rel.map((r) => dirname7(r)).filter((d) => d !== "."));
|
|
16833
|
+
return staged.filter(({ st, f }) => st !== "A" && !isTestFile(f) && !Object.hasOwn(pre, f) && !rel.includes(f) && !mentions(text2, f) && !mentions(text2, basename8(f)) && !plannedDirs.has(dirname7(f)) && !dirSegs(f).some((d) => toks.has(d))).map(({ f }) => f);
|
|
16115
16834
|
}
|
|
16116
16835
|
function flagOutsideScope(o, staged) {
|
|
16117
16836
|
const unit = unitFlags(staged, process.env, o.intake?.preexisting_dirty ?? {});
|
|
@@ -16193,9 +16912,9 @@ async function run(argv, opts = {}) {
|
|
|
16193
16912
|
// ../../loki-ts/src/features/visual_evidence.ts
|
|
16194
16913
|
import { spawn as spawn5 } from "child_process";
|
|
16195
16914
|
import { createHash as createHash6, randomBytes as randomBytes2 } from "crypto";
|
|
16196
|
-
import { existsSync as
|
|
16915
|
+
import { existsSync as existsSync13, lstatSync as lstatSync5, mkdirSync as mkdirSync6, readdirSync as readdirSync9, readFileSync as readFileSync19, writeFileSync as writeFileSync7 } from "fs";
|
|
16197
16916
|
import { createServer } from "net";
|
|
16198
|
-
import { dirname as
|
|
16917
|
+
import { dirname as dirname8, isAbsolute, join as join23, normalize, relative as relative3, sep as sep8 } from "path";
|
|
16199
16918
|
var visualEvidenceEnabled = (env = process.env) => env["LOKI_VISUAL_EVIDENCE"] !== "0";
|
|
16200
16919
|
var PAGE_RE = /^(?:.*\/)?(?:app|pages|src|public)\/.*\.(?:html|jsx|tsx|vue|svelte)$/;
|
|
16201
16920
|
var isPageFile = (p) => PAGE_RE.test(p);
|
|
@@ -16205,12 +16924,12 @@ function routeFor(file) {
|
|
|
16205
16924
|
return `/${r}`.replace(/\/+/g, "/");
|
|
16206
16925
|
}
|
|
16207
16926
|
var screenName = (route) => (route === "/" ? "index" : route.slice(1).replace(/[^A-Za-z0-9._-]+/g, "_")) || "index";
|
|
16208
|
-
var sha256File = (p) => createHash6("sha256").update(
|
|
16927
|
+
var sha256File = (p) => createHash6("sha256").update(readFileSync19(p)).digest("hex");
|
|
16209
16928
|
function hashScreens(runDir, rels) {
|
|
16210
16929
|
const out2 = [];
|
|
16211
16930
|
for (const rel of rels) {
|
|
16212
16931
|
try {
|
|
16213
|
-
out2.push({ path: rel, sha256: sha256File(
|
|
16932
|
+
out2.push({ path: rel, sha256: sha256File(join23(runDir, rel)) });
|
|
16214
16933
|
} catch {}
|
|
16215
16934
|
}
|
|
16216
16935
|
return out2;
|
|
@@ -16223,14 +16942,14 @@ function checkScreens(runDir, screens) {
|
|
|
16223
16942
|
return "evidence_screens entry is malformed";
|
|
16224
16943
|
if (isAbsolute(s.path) || normalize(s.path).startsWith(".."))
|
|
16225
16944
|
return `evidence screenshot path escapes the evidence dir: ${s.path}`;
|
|
16226
|
-
const file =
|
|
16945
|
+
const file = join23(runDir, s.path);
|
|
16227
16946
|
let cur = runDir;
|
|
16228
|
-
for (const seg of normalize(s.path).split(
|
|
16229
|
-
cur =
|
|
16947
|
+
for (const seg of normalize(s.path).split(sep8)) {
|
|
16948
|
+
cur = join23(cur, seg);
|
|
16230
16949
|
if (isSymlink(cur))
|
|
16231
16950
|
return `evidence screenshot path is a symlink: ${s.path}`;
|
|
16232
16951
|
}
|
|
16233
|
-
if (!
|
|
16952
|
+
if (!existsSync13(file))
|
|
16234
16953
|
return `evidence screenshot is missing: ${s.path}`;
|
|
16235
16954
|
if (!isRegularFile(file))
|
|
16236
16955
|
return `evidence screenshot is not a regular file: ${s.path}`;
|
|
@@ -16272,16 +16991,16 @@ async function waitUp(url, ms, signal) {
|
|
|
16272
16991
|
return false;
|
|
16273
16992
|
}
|
|
16274
16993
|
var playwrightBin = (repoDir) => {
|
|
16275
|
-
const bin =
|
|
16276
|
-
return
|
|
16994
|
+
const bin = join23(repoDir, "node_modules", ".bin", "playwright");
|
|
16995
|
+
return existsSync13(bin) ? bin : null;
|
|
16277
16996
|
};
|
|
16278
16997
|
function openapiPaths(repoDir) {
|
|
16279
16998
|
for (const f of ["openapi.json", "openapi.yaml", "openapi.yml", "swagger.json"]) {
|
|
16280
|
-
const p =
|
|
16281
|
-
if (!
|
|
16999
|
+
const p = join23(repoDir, f);
|
|
17000
|
+
if (!existsSync13(p))
|
|
16282
17001
|
continue;
|
|
16283
17002
|
try {
|
|
16284
|
-
const txt =
|
|
17003
|
+
const txt = readFileSync19(p, "utf8");
|
|
16285
17004
|
const paths = f.endsWith(".json") ? Object.keys(JSON.parse(txt).paths ?? {}) : [...txt.matchAll(/^ {2}(\/[^\s:]*):\s*$/gm)].map((m) => m[1]);
|
|
16286
17005
|
return paths.filter((x) => !x.includes("{")).slice(0, 10);
|
|
16287
17006
|
} catch {
|
|
@@ -16291,8 +17010,8 @@ function openapiPaths(repoDir) {
|
|
|
16291
17010
|
return [];
|
|
16292
17011
|
}
|
|
16293
17012
|
var playwrightTestPkg = (repoDir) => {
|
|
16294
|
-
const d =
|
|
16295
|
-
return
|
|
17013
|
+
const d = join23(repoDir, "node_modules", "@playwright", "test");
|
|
17014
|
+
return existsSync13(join23(d, "package.json")) ? d : null;
|
|
16296
17015
|
};
|
|
16297
17016
|
var e2eSpecSource = (pkgDir, base, routes) => `const { test } = require(${JSON.stringify(pkgDir)});
|
|
16298
17017
|
test("loki e2e walkthrough", async ({ page }) => {
|
|
@@ -16304,24 +17023,24 @@ async function recordE2eMedia(repoDir, runDir, relDir, base, routes, ms, signal)
|
|
|
16304
17023
|
if (!pw || !pkg)
|
|
16305
17024
|
return { rels: [], skipped: "playwright e2e video and trace skipped: @playwright/test is not installed in the repo" };
|
|
16306
17025
|
try {
|
|
16307
|
-
const dir =
|
|
16308
|
-
|
|
16309
|
-
|
|
16310
|
-
|
|
17026
|
+
const dir = join23(runDir, relDir, "e2e");
|
|
17027
|
+
mkdirSync6(dir, { recursive: true });
|
|
17028
|
+
writeFileSync7(join23(dir, "walk.spec.cjs"), e2eSpecSource(pkg, base, routes));
|
|
17029
|
+
writeFileSync7(join23(dir, "playwright.config.cjs"), `module.exports = { testDir: ".", testMatch: "walk.spec.cjs", outputDir: "./out", reporter: "null", use: { video: "on", trace: "on" } };
|
|
16311
17030
|
`);
|
|
16312
|
-
if (!await shoot(pw, ["test", "--config",
|
|
17031
|
+
if (!await shoot(pw, ["test", "--config", join23(dir, "playwright.config.cjs")], repoDir, ms, signal))
|
|
16313
17032
|
return { rels: [], skipped: "playwright e2e video and trace skipped: the run failed or timed out" };
|
|
16314
17033
|
const rels = [];
|
|
16315
17034
|
const walk = (d) => {
|
|
16316
|
-
for (const e of
|
|
16317
|
-
const full =
|
|
17035
|
+
for (const e of readdirSync9(d, { withFileTypes: true })) {
|
|
17036
|
+
const full = join23(d, e.name);
|
|
16318
17037
|
if (e.isDirectory())
|
|
16319
17038
|
walk(full);
|
|
16320
17039
|
else if (e.isFile() && /\.(webm|zip)$/.test(e.name))
|
|
16321
|
-
rels.push(
|
|
17040
|
+
rels.push(join23(relDir, "e2e", relative3(dir, full)));
|
|
16322
17041
|
}
|
|
16323
17042
|
};
|
|
16324
|
-
walk(
|
|
17043
|
+
walk(join23(dir, "out"));
|
|
16325
17044
|
return rels.length > 0 ? { rels, skipped: null } : { rels, skipped: "playwright e2e video and trace skipped: no video or trace was produced" };
|
|
16326
17045
|
} catch (e) {
|
|
16327
17046
|
return { rels: [], skipped: `playwright e2e video and trace skipped: ${String(e?.message ?? e).slice(0, 120)}` };
|
|
@@ -16359,14 +17078,14 @@ function shoot(pw, args, cwd, ms, signal) {
|
|
|
16359
17078
|
}
|
|
16360
17079
|
var isSymlink = (p) => {
|
|
16361
17080
|
try {
|
|
16362
|
-
return
|
|
17081
|
+
return lstatSync5(p).isSymbolicLink();
|
|
16363
17082
|
} catch {
|
|
16364
17083
|
return false;
|
|
16365
17084
|
}
|
|
16366
17085
|
};
|
|
16367
17086
|
var isRegularFile = (p) => {
|
|
16368
17087
|
try {
|
|
16369
|
-
return
|
|
17088
|
+
return lstatSync5(p).isFile();
|
|
16370
17089
|
} catch {
|
|
16371
17090
|
return false;
|
|
16372
17091
|
}
|
|
@@ -16414,10 +17133,10 @@ async function captureVisualEvidence(repoDir, runDir, changed, opts = {}) {
|
|
|
16414
17133
|
try {
|
|
16415
17134
|
if (!visualEvidenceEnabled(opts.env))
|
|
16416
17135
|
return skip("LOKI_VISUAL_EVIDENCE is 0");
|
|
16417
|
-
const pkgPath =
|
|
16418
|
-
if (!
|
|
17136
|
+
const pkgPath = join23(repoDir, "package.json");
|
|
17137
|
+
if (!existsSync13(pkgPath))
|
|
16419
17138
|
return skip("no package.json");
|
|
16420
|
-
const script = pickScript(JSON.parse(
|
|
17139
|
+
const script = pickScript(JSON.parse(readFileSync19(pkgPath, "utf8")));
|
|
16421
17140
|
if (!script)
|
|
16422
17141
|
return skip("no dev, preview or start script");
|
|
16423
17142
|
const pages = changed.filter(isPageFile);
|
|
@@ -16442,20 +17161,20 @@ async function captureVisualEvidence(repoDir, runDir, changed, opts = {}) {
|
|
|
16442
17161
|
}
|
|
16443
17162
|
if (!await waitUp(base, left(), signal))
|
|
16444
17163
|
return skip(aborted() ? "aborted" : `${script} server did not answer within ${Math.round(budget / 1000)}s`);
|
|
16445
|
-
const evRoot =
|
|
17164
|
+
const evRoot = join23(runDir, "evidence");
|
|
16446
17165
|
if (isSymlink(evRoot))
|
|
16447
17166
|
return skip("evidence dir is a symlink");
|
|
16448
|
-
|
|
17167
|
+
mkdirSync6(evRoot, { recursive: true });
|
|
16449
17168
|
if (pages.length > 0) {
|
|
16450
|
-
const rel0 =
|
|
16451
|
-
|
|
17169
|
+
const rel0 = join23("evidence", randomBytes2(6).toString("hex"));
|
|
17170
|
+
mkdirSync6(join23(runDir, rel0));
|
|
16452
17171
|
const rels = [];
|
|
16453
17172
|
for (const route of [...new Set(pages.map(routeFor))].slice(0, opts.maxRoutes ?? MAX_ROUTES)) {
|
|
16454
17173
|
if (aborted() || left() <= 0)
|
|
16455
17174
|
break;
|
|
16456
|
-
const rel =
|
|
16457
|
-
const ok = await shoot(pw, ["screenshot", `${base}${route}`,
|
|
16458
|
-
if (ok && !aborted() && !isSymlink(
|
|
17175
|
+
const rel = join23(rel0, `${screenName(route)}.png`);
|
|
17176
|
+
const ok = await shoot(pw, ["screenshot", `${base}${route}`, join23(runDir, rel)], repoDir, left(), signal);
|
|
17177
|
+
if (ok && !aborted() && !isSymlink(join23(runDir, rel)) && existsSync13(join23(runDir, rel)))
|
|
16459
17178
|
rels.push(rel);
|
|
16460
17179
|
}
|
|
16461
17180
|
if (aborted())
|
|
@@ -16480,9 +17199,9 @@ async function captureVisualEvidence(repoDir, runDir, changed, opts = {}) {
|
|
|
16480
17199
|
}
|
|
16481
17200
|
if (aborted())
|
|
16482
17201
|
return skip("aborted");
|
|
16483
|
-
const hdir =
|
|
16484
|
-
|
|
16485
|
-
|
|
17202
|
+
const hdir = join23(evRoot, randomBytes2(6).toString("hex"));
|
|
17203
|
+
mkdirSync6(hdir);
|
|
17204
|
+
writeFileSync7(join23(hdir, "http.json"), JSON.stringify(rows, null, 2) + `
|
|
16486
17205
|
`);
|
|
16487
17206
|
return { screens: [], http: true, skipped: null };
|
|
16488
17207
|
} catch (e) {
|
|
@@ -16505,7 +17224,7 @@ async function sealEvidence(repoDir, runDir, o, notProven, signal, emit) {
|
|
|
16505
17224
|
return ev.screens.length > 0 ? { evidence_screens: ev.screens } : {};
|
|
16506
17225
|
}
|
|
16507
17226
|
function receiptScreensProblem(receiptPath, receipt) {
|
|
16508
|
-
return receipt["evidence_screens"] === undefined ? null : checkScreens(
|
|
17227
|
+
return receipt["evidence_screens"] === undefined ? null : checkScreens(dirname8(receiptPath), receipt["evidence_screens"]);
|
|
16509
17228
|
}
|
|
16510
17229
|
|
|
16511
17230
|
// ../../loki-ts/src/engine10/types.ts
|
|
@@ -16523,8 +17242,8 @@ var STAGE_BUDGETS = {
|
|
|
16523
17242
|
};
|
|
16524
17243
|
|
|
16525
17244
|
// ../../loki-ts/src/features/contract.ts
|
|
16526
|
-
import { existsSync as
|
|
16527
|
-
import { dirname as
|
|
17245
|
+
import { existsSync as existsSync14, lstatSync as lstatSync6, mkdirSync as mkdirSync7, readFileSync as readFileSync20, writeFileSync as writeFileSync8 } from "fs";
|
|
17246
|
+
import { dirname as dirname9, join as join24 } from "path";
|
|
16528
17247
|
import { createHash as createHash7 } from "crypto";
|
|
16529
17248
|
var MAX_CRITERIA = 50;
|
|
16530
17249
|
var MAX_TEXT = 500;
|
|
@@ -16559,14 +17278,14 @@ function untracedLines(trace) {
|
|
|
16559
17278
|
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)}`);
|
|
16560
17279
|
}
|
|
16561
17280
|
function contractPath(repoDir) {
|
|
16562
|
-
return
|
|
17281
|
+
return join24(repoDir, ".loki", "contract.json");
|
|
16563
17282
|
}
|
|
16564
17283
|
var MAX_CONTRACT_BYTES = 1024 * 1024;
|
|
16565
17284
|
function readContract(repoDir) {
|
|
16566
17285
|
const p = contractPath(repoDir);
|
|
16567
17286
|
let st;
|
|
16568
17287
|
try {
|
|
16569
|
-
st =
|
|
17288
|
+
st = lstatSync6(p);
|
|
16570
17289
|
} catch {
|
|
16571
17290
|
return { contract: null, notes: [], sha256: null };
|
|
16572
17291
|
}
|
|
@@ -16576,7 +17295,7 @@ function readContract(repoDir) {
|
|
|
16576
17295
|
return { contract: null, notes: ["contract unreadable: too large"], sha256: null };
|
|
16577
17296
|
let raw2;
|
|
16578
17297
|
try {
|
|
16579
|
-
raw2 =
|
|
17298
|
+
raw2 = readFileSync20(p);
|
|
16580
17299
|
} catch {
|
|
16581
17300
|
return { contract: null, notes: ["contract unreadable: read failed"], sha256: null };
|
|
16582
17301
|
}
|
|
@@ -16611,7 +17330,7 @@ function sealContract(repoDir, body, rawDiff, checks, env = process.env, snap) {
|
|
|
16611
17330
|
const live = readContract(repoDir);
|
|
16612
17331
|
let exists2 = true;
|
|
16613
17332
|
try {
|
|
16614
|
-
|
|
17333
|
+
lstatSync6(contractPath(repoDir));
|
|
16615
17334
|
} catch {
|
|
16616
17335
|
exists2 = false;
|
|
16617
17336
|
}
|
|
@@ -16640,32 +17359,32 @@ function sealContract(repoDir, body, rawDiff, checks, env = process.env, snap) {
|
|
|
16640
17359
|
|
|
16641
17360
|
// ../../loki-ts/src/features/speed/seal_group.ts
|
|
16642
17361
|
import { createHash as createHash8 } from "crypto";
|
|
16643
|
-
import { existsSync as
|
|
16644
|
-
import { dirname as
|
|
17362
|
+
import { existsSync as existsSync15, readdirSync as readdirSync10, readFileSync as readFileSync21, statSync as statSync10 } from "fs";
|
|
17363
|
+
import { dirname as dirname10, join as join25 } from "path";
|
|
16645
17364
|
var ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
16646
17365
|
var PASSING = new Set(["VERIFIED", "ALREADY_SATISFIED"]);
|
|
16647
17366
|
var sha2 = (b) => createHash8("sha256").update(b).digest("hex");
|
|
16648
|
-
var
|
|
17367
|
+
var isObj9 = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
16649
17368
|
var text2 = (v, cap) => typeof v === "string" ? v.replace(/[\x00-\x1f\x7f]+/g, " ").trim().slice(0, cap) : "";
|
|
16650
17369
|
var num2 = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : 0;
|
|
16651
|
-
var unitsDir = (runDir) =>
|
|
16652
|
-
var
|
|
17370
|
+
var unitsDir = (runDir) => join25(runDir, "group", "units");
|
|
17371
|
+
var readJson3 = (p) => {
|
|
16653
17372
|
try {
|
|
16654
|
-
return JSON.parse(
|
|
17373
|
+
return JSON.parse(readFileSync21(p, "utf8"));
|
|
16655
17374
|
} catch {
|
|
16656
17375
|
return;
|
|
16657
17376
|
}
|
|
16658
17377
|
};
|
|
16659
17378
|
var dirNames = (d) => {
|
|
16660
17379
|
try {
|
|
16661
|
-
return
|
|
17380
|
+
return readdirSync10(d).filter((n2) => !n2.startsWith(".")).sort();
|
|
16662
17381
|
} catch {
|
|
16663
17382
|
return null;
|
|
16664
17383
|
}
|
|
16665
17384
|
};
|
|
16666
17385
|
function sealGroup(runDir, rh) {
|
|
16667
|
-
const manifestPath =
|
|
16668
|
-
if (!
|
|
17386
|
+
const manifestPath = join25(runDir, "group", "manifest.json");
|
|
17387
|
+
if (!existsSync15(manifestPath))
|
|
16669
17388
|
return { section: undefined, notProven: [], problems: 0, allUnitsPass: true };
|
|
16670
17389
|
const notProven = [];
|
|
16671
17390
|
let problems = 0;
|
|
@@ -16673,9 +17392,9 @@ function sealGroup(runDir, rh) {
|
|
|
16673
17392
|
problems++;
|
|
16674
17393
|
notProven.push(`group: ${m2}`);
|
|
16675
17394
|
};
|
|
16676
|
-
const m =
|
|
16677
|
-
const list =
|
|
16678
|
-
if (!
|
|
17395
|
+
const m = readJson3(manifestPath);
|
|
17396
|
+
const list = isObj9(m) && Array.isArray(m["units"]) ? m["units"] : [];
|
|
17397
|
+
if (!isObj9(m) || !ID_RE.test(String(m["group_id"] ?? "")) || list.length === 0) {
|
|
16679
17398
|
fail("manifest missing, unreadable or empty");
|
|
16680
17399
|
return { section: undefined, notProven, problems, allUnitsPass: false };
|
|
16681
17400
|
}
|
|
@@ -16683,21 +17402,21 @@ function sealGroup(runDir, rh) {
|
|
|
16683
17402
|
const seen = new Set, seenLower = new Set;
|
|
16684
17403
|
let allUnitsPass = true;
|
|
16685
17404
|
list.forEach((raw2, index2) => {
|
|
16686
|
-
const u =
|
|
17405
|
+
const u = isObj9(raw2) ? raw2 : {};
|
|
16687
17406
|
const id = String(u["unit_id"] ?? "");
|
|
16688
17407
|
if (!ID_RE.test(id) || seen.has(id) || seenLower.has(id.toLowerCase()))
|
|
16689
17408
|
return fail(`unit ${index2} has an invalid, duplicate or case-colliding id`);
|
|
16690
17409
|
seen.add(id);
|
|
16691
17410
|
seenLower.add(id.toLowerCase());
|
|
16692
|
-
const dir =
|
|
17411
|
+
const dir = join25(unitsDir(runDir), id), rp = join25(dir, "receipt.json"), ep = join25(dir, "events.jsonl");
|
|
16693
17412
|
let bytes;
|
|
16694
17413
|
try {
|
|
16695
|
-
bytes =
|
|
17414
|
+
bytes = readFileSync21(rp);
|
|
16696
17415
|
} catch {
|
|
16697
17416
|
return fail(`unit ${id} sub-receipt missing`);
|
|
16698
17417
|
}
|
|
16699
|
-
const sub =
|
|
16700
|
-
if (!
|
|
17418
|
+
const sub = readJson3(rp);
|
|
17419
|
+
if (!isObj9(sub) || typeof sub["receipt_sha256"] !== "string" || rh(sub) !== sub["receipt_sha256"])
|
|
16701
17420
|
return fail(`unit ${id} sub-receipt unreadable or its hash does not recompute`);
|
|
16702
17421
|
const verdict = String(sub["verdict"] ?? "");
|
|
16703
17422
|
if (!PASSING.has(verdict)) {
|
|
@@ -16709,7 +17428,7 @@ function sealGroup(runDir, rh) {
|
|
|
16709
17428
|
unit_id: id,
|
|
16710
17429
|
sub_receipt_sha256: sha2(bytes),
|
|
16711
17430
|
receipt_sha256: sub["receipt_sha256"],
|
|
16712
|
-
events_file_sha256:
|
|
17431
|
+
events_file_sha256: existsSync15(ep) ? sha2(readFileSync21(ep)) : null,
|
|
16713
17432
|
verdict,
|
|
16714
17433
|
goal: text2(u["goal"], 200),
|
|
16715
17434
|
files: (Array.isArray(u["files"]) ? u["files"] : []).map((f) => text2(f, 200)).filter(Boolean).slice(0, 50),
|
|
@@ -16729,16 +17448,16 @@ function capGroupVerdict(v, g) {
|
|
|
16729
17448
|
return !g.allUnitsPass && (v === "VERIFIED" || v === "ALREADY_SATISFIED") ? "PARTIAL" : v;
|
|
16730
17449
|
}
|
|
16731
17450
|
async function verifyGroup(receiptPath, receipt, rh, verifyOne, deps = {}) {
|
|
16732
|
-
const runDir =
|
|
16733
|
-
const signed =
|
|
17451
|
+
const runDir = dirname10(receiptPath);
|
|
17452
|
+
const signed = isObj9(receipt["verification"]) && typeof receipt["verification"]["jwt"] === "string";
|
|
16734
17453
|
const bad = (reason) => ({ verdict: "TAMPERED", reason: `group: ${reason}` });
|
|
16735
17454
|
const g = receipt["group"];
|
|
16736
17455
|
if (g === undefined)
|
|
16737
|
-
return
|
|
16738
|
-
if (!
|
|
17456
|
+
return existsSync15(join25(runDir, "group", "manifest.json")) ? bad("group directory present but the receipt has no group section") : null;
|
|
17457
|
+
if (!isObj9(g) || !ID_RE.test(String(g["group_id"] ?? "")) || !Array.isArray(g["units"]) || g["units"].length === 0)
|
|
16739
17458
|
return bad("group section malformed");
|
|
16740
17459
|
const entries = g["units"];
|
|
16741
|
-
const ids = entries.map((e) =>
|
|
17460
|
+
const ids = entries.map((e) => isObj9(e) ? String(e["unit_id"] ?? "") : "");
|
|
16742
17461
|
if (ids.some((id) => !ID_RE.test(id)) || new Set(ids.map((i) => i.toLowerCase())).size !== ids.length)
|
|
16743
17462
|
return bad("unit ids invalid, duplicated or case-colliding");
|
|
16744
17463
|
const onDisk = dirNames(unitsDir(runDir));
|
|
@@ -16746,24 +17465,24 @@ async function verifyGroup(receiptPath, receipt, rh, verifyOne, deps = {}) {
|
|
|
16746
17465
|
return bad("unit directories do not match the receipt (missing or extra unit)");
|
|
16747
17466
|
let unchecked = null;
|
|
16748
17467
|
for (let i = 0;i < entries.length; i++) {
|
|
16749
|
-
const e = entries[i], id = ids[i], dir =
|
|
17468
|
+
const e = entries[i], id = ids[i], dir = join25(unitsDir(runDir), id), rp = join25(dir, "receipt.json"), ep = join25(dir, "events.jsonl");
|
|
16750
17469
|
if (e["index"] !== i)
|
|
16751
17470
|
return bad(`unit ${id} is out of order (index ${String(e["index"])} at position ${i})`);
|
|
16752
17471
|
let bytes;
|
|
16753
17472
|
try {
|
|
16754
|
-
if (!
|
|
17473
|
+
if (!statSync10(dir).isDirectory())
|
|
16755
17474
|
return bad(`unit ${id} is not a directory`);
|
|
16756
|
-
bytes =
|
|
17475
|
+
bytes = readFileSync21(rp);
|
|
16757
17476
|
} catch {
|
|
16758
17477
|
return bad(`unit ${id} sub-receipt missing`);
|
|
16759
17478
|
}
|
|
16760
17479
|
if (sha2(bytes) !== e["sub_receipt_sha256"])
|
|
16761
17480
|
return bad(`unit ${id} sub-receipt does not match its recorded hash`);
|
|
16762
|
-
const evSha =
|
|
17481
|
+
const evSha = existsSync15(ep) ? sha2(readFileSync21(ep)) : null;
|
|
16763
17482
|
if (evSha !== (e["events_file_sha256"] ?? null))
|
|
16764
17483
|
return bad(`unit ${id} events.jsonl does not match its recorded hash`);
|
|
16765
|
-
const sub =
|
|
16766
|
-
if (!
|
|
17484
|
+
const sub = readJson3(rp);
|
|
17485
|
+
if (!isObj9(sub) || sub["receipt_sha256"] !== e["receipt_sha256"] || rh(sub) !== e["receipt_sha256"])
|
|
16767
17486
|
return bad(`unit ${id} sub-receipt hash does not recompute`);
|
|
16768
17487
|
if (String(sub["verdict"] ?? "") !== e["verdict"])
|
|
16769
17488
|
return bad(`unit ${id} verdict differs from the combined receipt`);
|
|
@@ -16783,7 +17502,7 @@ var DEEP_NOT_PROVEN = ["full suite", "app boot", "council", "security scan"];
|
|
|
16783
17502
|
var SIGNING_UNAVAILABLE = "receipt signing unavailable (no usable signing key: invalid key or unwritable ~/.loki/keys)";
|
|
16784
17503
|
var EXCLUDE_LOKI = ":(exclude).loki";
|
|
16785
17504
|
var sha2562 = (s) => createHash9("sha256").update(s).digest("hex");
|
|
16786
|
-
var
|
|
17505
|
+
var str3 = (v) => typeof v === "string" && v !== "" ? v : null;
|
|
16787
17506
|
var strs2 = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string" && x !== "") : [];
|
|
16788
17507
|
function sanitizeReason(s) {
|
|
16789
17508
|
const collapsed = s.replace(/[\x00-\x1f\x7f]+/g, " ").trim();
|
|
@@ -16814,30 +17533,30 @@ function loadSigningKey(generate = true) {
|
|
|
16814
17533
|
let pem = inline ?? "";
|
|
16815
17534
|
if (!inline) {
|
|
16816
17535
|
const given = process.env["LOKI_RECEIPT_SIGNING_KEY_FILE"]?.trim();
|
|
16817
|
-
const file = given ||
|
|
17536
|
+
const file = given || join26(process.env["HOME"] || homedir5(), ".loki", "keys", RECEIPT_SIGNER_BASENAME);
|
|
16818
17537
|
try {
|
|
16819
|
-
pem =
|
|
17538
|
+
pem = readFileSync22(file);
|
|
16820
17539
|
if (!given) {
|
|
16821
|
-
for (const [f, m] of [[file, 384], [
|
|
16822
|
-
if (
|
|
17540
|
+
for (const [f, m] of [[file, 384], [dirname11(file), 448]])
|
|
17541
|
+
if (statSync11(f).mode & 63)
|
|
16823
17542
|
chmodSync4(f, m);
|
|
16824
17543
|
}
|
|
16825
17544
|
} catch (e) {
|
|
16826
17545
|
if (e.code !== "ENOENT" || !generate)
|
|
16827
17546
|
return null;
|
|
16828
|
-
|
|
16829
|
-
|
|
17547
|
+
mkdirSync8(dirname11(dirname11(file)), { recursive: true, mode: 448 });
|
|
17548
|
+
mkdirSync8(dirname11(file), { recursive: true, mode: 448 });
|
|
16830
17549
|
const tmp = `${file}.${randomBytes3(6).toString("hex")}.tmp`;
|
|
16831
|
-
|
|
17550
|
+
writeFileSync9(tmp, generateKeyPairSync("ed25519").privateKey.export({ type: "pkcs8", format: "pem" }), { flag: "wx", mode: 384 });
|
|
16832
17551
|
try {
|
|
16833
17552
|
linkSync(tmp, file);
|
|
16834
17553
|
} catch (l) {
|
|
16835
17554
|
if (l.code !== "EEXIST")
|
|
16836
17555
|
throw l;
|
|
16837
17556
|
} finally {
|
|
16838
|
-
|
|
17557
|
+
unlinkSync4(tmp);
|
|
16839
17558
|
}
|
|
16840
|
-
pem =
|
|
17559
|
+
pem = readFileSync22(file);
|
|
16841
17560
|
}
|
|
16842
17561
|
}
|
|
16843
17562
|
const k = createPrivateKey(pem);
|
|
@@ -16879,7 +17598,7 @@ var commitStage = {
|
|
|
16879
17598
|
const dropped = new Set(drop.map(({ f }) => f)), notes = flagOutsideScope(ctx.outputs(), staged.filter(({ f }) => !dropped.has(f)));
|
|
16880
17599
|
if ((await git(ctx, ["diff", "--cached", "--quiet"])).code === 0)
|
|
16881
17600
|
return { status: "completed", data: { committed: false, scope_notes: notes } };
|
|
16882
|
-
const title = (
|
|
17601
|
+
const title = (str3(ctx.outputs().intake?.title) ?? `run ${ctx.runId}`).split(`
|
|
16883
17602
|
`)[0].slice(0, 72);
|
|
16884
17603
|
const c = await git(ctx, ["commit", "-q", "-m", `loki: ${title}`, "-m", `Loki-Run: ${ctx.runId}`]);
|
|
16885
17604
|
if (c.code !== 0)
|
|
@@ -16964,7 +17683,7 @@ var sealStage = {
|
|
|
16964
17683
|
if (wallNotRun > 0) {
|
|
16965
17684
|
try {
|
|
16966
17685
|
const kept = new Set((Array.isArray(o.wall?.files) ? o.wall.files : []).map((f) => basename9(String(f.path))));
|
|
16967
|
-
for (const n2 of
|
|
17686
|
+
for (const n2 of readdirSync11(join26(ctx.runDir, "wall")).sort())
|
|
16968
17687
|
if (!kept.has(n2))
|
|
16969
17688
|
notProven.add(`wall test discarded: ${n2} (not_run)`);
|
|
16970
17689
|
} catch {}
|
|
@@ -16983,7 +17702,7 @@ var sealStage = {
|
|
|
16983
17702
|
if (!vl.length)
|
|
16984
17703
|
continue;
|
|
16985
17704
|
const tc = tcs?.[t];
|
|
16986
|
-
const mine = assertDeltaNotes(ctx.repoDir, ctx.baseSha, head, t,
|
|
17705
|
+
const mine = assertDeltaNotes(ctx.repoDir, ctx.baseSha, head, t, str3(o.intake?.task) ?? "", cnt(tc?.b), cnt(tc?.h));
|
|
16987
17706
|
if (!mine || mine.length !== vl.length || mine.some((n2) => !vl.includes(n2)))
|
|
16988
17707
|
for (const v of vl)
|
|
16989
17708
|
dropped.add(v);
|
|
@@ -17009,7 +17728,7 @@ var sealStage = {
|
|
|
17009
17728
|
const source = o.intake?.source;
|
|
17010
17729
|
if (source !== "text" && source !== "issue")
|
|
17011
17730
|
notProven.add("task source not recorded by intake");
|
|
17012
|
-
const repo =
|
|
17731
|
+
const repo = str3(o.intake?.repo);
|
|
17013
17732
|
if (repo === null)
|
|
17014
17733
|
notProven.add("repo not recorded by intake");
|
|
17015
17734
|
if (typeof o.intake?.resumed !== "boolean")
|
|
@@ -17024,7 +17743,7 @@ var sealStage = {
|
|
|
17024
17743
|
const cost = ctx.cost.read(ctx.repoDir, iterIds);
|
|
17025
17744
|
if (cost.unmetered)
|
|
17026
17745
|
notProven.add("cost unmetered (CLI invoker; recorded as 0)");
|
|
17027
|
-
const eventsPath =
|
|
17746
|
+
const eventsPath = join26(ctx.runDir, "events.jsonl");
|
|
17028
17747
|
const wallFiles = Array.isArray(o.wall?.files) ? o.wall.files : [];
|
|
17029
17748
|
const wallPassed = typeof o.verify?.wall_passed === "boolean" ? o.verify.wall_passed : null;
|
|
17030
17749
|
if (wallFiles.length > 0 && wallPassed === null)
|
|
@@ -17032,7 +17751,7 @@ var sealStage = {
|
|
|
17032
17751
|
const body = {
|
|
17033
17752
|
schema: "loki.v10.receipt/1",
|
|
17034
17753
|
run_id: ctx.runId,
|
|
17035
|
-
task: { source: source === "issue" ? "issue" : "text", sha256:
|
|
17754
|
+
task: { source: source === "issue" ? "issue" : "text", sha256: str3(o.intake?.task_sha256) ?? "" },
|
|
17036
17755
|
repo: repo ?? "",
|
|
17037
17756
|
base_sha: ctx.baseSha,
|
|
17038
17757
|
head_sha: head,
|
|
@@ -17043,7 +17762,7 @@ var sealStage = {
|
|
|
17043
17762
|
not_proven: [],
|
|
17044
17763
|
verdict,
|
|
17045
17764
|
...grp.section ? { group: grp.section } : {},
|
|
17046
|
-
...
|
|
17765
|
+
...str3(o.implement?.spec_conflict_reason) !== null ? { spec_conflict_reason: sanitizeReason(str3(o.implement?.spec_conflict_reason)) } : {},
|
|
17047
17766
|
evidence: strs2(o.intake?.evidence),
|
|
17048
17767
|
...o.intake?.preexisting_dirty ? { pre_existing_dirty: Object.keys(o.intake.preexisting_dirty) } : {},
|
|
17049
17768
|
cost: {
|
|
@@ -17053,13 +17772,14 @@ var sealStage = {
|
|
|
17053
17772
|
measured_sessions: cost.measuredCount ?? 0,
|
|
17054
17773
|
total_sessions: cost.totalCount ?? 0,
|
|
17055
17774
|
partial_usd: cost.partialUsd ?? 0,
|
|
17056
|
-
...cost.unmetered ? { source: "cli-invoker-unmetered" } : {}
|
|
17775
|
+
...cost.unmetered ? { source: "cli-invoker-unmetered" } : {},
|
|
17776
|
+
...Array.isArray(o.fix?.fix_rounds) ? { fix_rounds: o.fix.fix_rounds } : {}
|
|
17057
17777
|
},
|
|
17058
17778
|
time: { wall_s: Object.values(stages).reduce((a, b) => a + (b ?? 0), 0), stages },
|
|
17059
17779
|
provider: ctx.provider,
|
|
17060
17780
|
model: ctx.model,
|
|
17061
17781
|
resumed: o.intake?.resumed === true,
|
|
17062
|
-
events_sha256: sha2562(
|
|
17782
|
+
events_sha256: sha2562(existsSync16(eventsPath) ? readFileSync22(eventsPath) : ""),
|
|
17063
17783
|
...await sealEvidence(ctx.repoDir, ctx.runDir, o, notProven, signal, ctx.emit),
|
|
17064
17784
|
log_seal: true
|
|
17065
17785
|
};
|
|
@@ -17073,11 +17793,11 @@ var sealStage = {
|
|
|
17073
17793
|
hash = receiptSha256(body);
|
|
17074
17794
|
}
|
|
17075
17795
|
const receipt = { ...body, receipt_sha256: hash, verification: { jwt: sig.jwt, kid: sig.kid } };
|
|
17076
|
-
|
|
17077
|
-
const path =
|
|
17078
|
-
|
|
17796
|
+
mkdirSync8(ctx.runDir, { recursive: true });
|
|
17797
|
+
const path = join26(ctx.runDir, "receipt.json");
|
|
17798
|
+
writeFileSync9(path, JSON.stringify(receipt, null, 2) + `
|
|
17079
17799
|
`);
|
|
17080
|
-
|
|
17800
|
+
writeFileSync9(join26(ctx.runDir, "receipt.md"), renderReceiptMd(receipt));
|
|
17081
17801
|
const signed = sig.jwt !== null;
|
|
17082
17802
|
const data = { receipt_path: path, receipt_sha256: hash, signed, kid: sig.kid, verdict, not_proven: receipt.not_proven };
|
|
17083
17803
|
ctx.emit("receipt.sealed", "seal", { path, receipt_sha256: hash, signed, kid: sig.kid, verdict, not_proven: receipt.not_proven });
|
|
@@ -17087,7 +17807,7 @@ var sealStage = {
|
|
|
17087
17807
|
|
|
17088
17808
|
// ../../loki-ts/src/features/receipt_dsse.ts
|
|
17089
17809
|
import { createPublicKey as createPublicKey3, sign as sign2, verify as verify2 } from "crypto";
|
|
17090
|
-
import { readFileSync as
|
|
17810
|
+
import { readFileSync as readFileSync23 } from "fs";
|
|
17091
17811
|
var DSSE_PAYLOAD_TYPE = "application/vnd.in-toto+json";
|
|
17092
17812
|
var STATEMENT_TYPE = "https://in-toto.io/Statement/v1";
|
|
17093
17813
|
var PREDICATE_TYPE = "https://autonomi.dev/loki/receipt/v10";
|
|
@@ -17168,7 +17888,7 @@ function verifyDsseReceipt(env, d) {
|
|
|
17168
17888
|
}
|
|
17169
17889
|
function runIdGuard(receiptPath, runId) {
|
|
17170
17890
|
try {
|
|
17171
|
-
return envelopeRunIdProblem(JSON.parse(
|
|
17891
|
+
return envelopeRunIdProblem(JSON.parse(readFileSync23(receiptPath, "utf8")), runId);
|
|
17172
17892
|
} catch {
|
|
17173
17893
|
return null;
|
|
17174
17894
|
}
|
|
@@ -17182,7 +17902,7 @@ function pubFor(kid, given) {
|
|
|
17182
17902
|
const active = loadSigningKey(false);
|
|
17183
17903
|
const pubs = (process.env["LOKI_RECEIPT_RETIRED_PUBKEYS"] ?? "").split(":").map((f) => f.trim()).filter(Boolean).flatMap((f) => {
|
|
17184
17904
|
try {
|
|
17185
|
-
return [createPublicKey4(
|
|
17905
|
+
return [createPublicKey4(readFileSync24(f))];
|
|
17186
17906
|
} catch {
|
|
17187
17907
|
return [];
|
|
17188
17908
|
}
|
|
@@ -17214,10 +17934,10 @@ function checkEventLog(receiptPath, receipt) {
|
|
|
17214
17934
|
const bound = receipt["events_sha256"];
|
|
17215
17935
|
if (typeof bound !== "string" || bound === createHash10("sha256").digest("hex"))
|
|
17216
17936
|
return null;
|
|
17217
|
-
const path =
|
|
17218
|
-
if (!
|
|
17937
|
+
const path = join27(dirname12(receiptPath), "events.jsonl");
|
|
17938
|
+
if (!existsSync17(path))
|
|
17219
17939
|
return "events.jsonl is missing";
|
|
17220
|
-
const raw2 =
|
|
17940
|
+
const raw2 = readFileSync24(path, "utf8"), events2 = readEvents(path), lines = raw2.split(`
|
|
17221
17941
|
`).filter((l) => l.trim() !== "");
|
|
17222
17942
|
if (events2.length !== lines.length || events2.some((e, i) => e.seq !== i))
|
|
17223
17943
|
return "events.jsonl has a forged, edited or missing line";
|
|
@@ -17233,7 +17953,7 @@ function checkEventLog(receiptPath, receipt) {
|
|
|
17233
17953
|
return h.digest("hex") === bound ? null : "events.jsonl does not match the hash recorded at seal";
|
|
17234
17954
|
}
|
|
17235
17955
|
function checkLogSeal(receiptPath, hash, kid, given) {
|
|
17236
|
-
const path =
|
|
17956
|
+
const path = join27(dirname12(receiptPath), "events.jsonl"), events2 = readEvents(path), done = events2.findIndex((e) => e.type === "run.completed");
|
|
17237
17957
|
if (!events2.some((e) => e.type === "receipt.sealed" && e.data["receipt_sha256"] === hash))
|
|
17238
17958
|
return { verdict: "TAMPERED", reason: "events.jsonl has no receipt.sealed event for this receipt" };
|
|
17239
17959
|
if (done < 0)
|
|
@@ -17241,7 +17961,7 @@ function checkLogSeal(receiptPath, hash, kid, given) {
|
|
|
17241
17961
|
const seal = events2[done + 1], d = seal?.type === "log.sealed" ? seal.data : null;
|
|
17242
17962
|
if (!d)
|
|
17243
17963
|
return { verdict: "TAMPERED", reason: "events.jsonl has no signed log.sealed line after run.completed" };
|
|
17244
|
-
const lines =
|
|
17964
|
+
const lines = readFileSync24(path, "utf8").split(`
|
|
17245
17965
|
`).filter((l) => l.trim() !== ""), sha3 = createHash10("sha256").update(lines.slice(0, done + 1).join(`
|
|
17246
17966
|
`) + `
|
|
17247
17967
|
`).digest("hex");
|
|
@@ -17251,11 +17971,11 @@ function checkLogSeal(receiptPath, hash, kid, given) {
|
|
|
17251
17971
|
return null;
|
|
17252
17972
|
}
|
|
17253
17973
|
async function verifyReceipt(receiptPath, deps = {}) {
|
|
17254
|
-
if (!
|
|
17974
|
+
if (!existsSync17(receiptPath))
|
|
17255
17975
|
return { verdict: "UNCHECKED", reasons: [`receipt not found: ${receiptPath}`] };
|
|
17256
17976
|
let receipt;
|
|
17257
17977
|
try {
|
|
17258
|
-
receipt = JSON.parse((deps.read ?? ((p) =>
|
|
17978
|
+
receipt = JSON.parse((deps.read ?? ((p) => readFileSync24(p, "utf8")))(receiptPath));
|
|
17259
17979
|
} catch {
|
|
17260
17980
|
return { verdict: "UNCHECKED", reasons: ["receipt.json is not valid JSON"] };
|
|
17261
17981
|
}
|
|
@@ -17299,7 +18019,7 @@ async function verifyReceipt(receiptPath, deps = {}) {
|
|
|
17299
18019
|
// src/server/routes/verify.ts
|
|
17300
18020
|
var ID4 = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
17301
18021
|
var safeId2 = (s) => ID4.test(s) && !s.includes("..");
|
|
17302
|
-
function
|
|
18022
|
+
function mount19(ctx) {
|
|
17303
18023
|
const { act, db } = ctx;
|
|
17304
18024
|
act.post("/v1/runs/:source/:run/verify", async (c) => {
|
|
17305
18025
|
const source = c.req.param("source"), run2 = c.req.param("run");
|
|
@@ -17320,15 +18040,15 @@ function mount16(ctx) {
|
|
|
17320
18040
|
return done(404, "receipt not available", "not_found");
|
|
17321
18041
|
let runDir, file;
|
|
17322
18042
|
try {
|
|
17323
|
-
const root =
|
|
17324
|
-
runDir =
|
|
17325
|
-
if (runDir !==
|
|
18043
|
+
const root = realpathSync11(join28(repo.p, ".loki", "runs"));
|
|
18044
|
+
runDir = realpathSync11(join28(root, run2));
|
|
18045
|
+
if (runDir !== join28(root, run2))
|
|
17326
18046
|
return done(404, "receipt not found", "not_found");
|
|
17327
|
-
file =
|
|
18047
|
+
file = realpathSync11(join28(runDir, "receipt.json"));
|
|
17328
18048
|
} catch {
|
|
17329
18049
|
return done(404, "receipt not found", "not_found");
|
|
17330
18050
|
}
|
|
17331
|
-
if (!file.startsWith(runDir +
|
|
18051
|
+
if (!file.startsWith(runDir + sep9))
|
|
17332
18052
|
return done(404, "receipt not found", "not_found");
|
|
17333
18053
|
let integrity, reasons, sha3 = null, outcome = "UNREADABLE";
|
|
17334
18054
|
try {
|
|
@@ -17343,7 +18063,7 @@ function mount16(ctx) {
|
|
|
17343
18063
|
sha3 = r.receiptSha256 ?? null;
|
|
17344
18064
|
}
|
|
17345
18065
|
try {
|
|
17346
|
-
outcome = outcomeOf(JSON.parse(
|
|
18066
|
+
outcome = outcomeOf(JSON.parse(readFileSync25(file, "utf8")));
|
|
17347
18067
|
} catch {
|
|
17348
18068
|
outcome = "UNREADABLE";
|
|
17349
18069
|
}
|
|
@@ -17378,7 +18098,7 @@ function mount16(ctx) {
|
|
|
17378
18098
|
}
|
|
17379
18099
|
|
|
17380
18100
|
// src/server/routes/index.ts
|
|
17381
|
-
var routeModules = [mount,
|
|
18101
|
+
var routeModules = [mount, mount18, mount5, mount7, mount17, mount8, mount3, mount9, mount11, mount4, mount10, mount12, mount6, mount16, mount19, mount13, mount15, mount2, mount14];
|
|
17382
18102
|
function registerRoutes(ctx) {
|
|
17383
18103
|
mountRepos(ctx.act, ctx.db, ctx.peerIsLoopback);
|
|
17384
18104
|
for (const m of routeModules)
|
|
@@ -17405,8 +18125,8 @@ function ttlCache(ttlMs = PROBE_CACHE_MS, now = Date.now) {
|
|
|
17405
18125
|
}
|
|
17406
18126
|
|
|
17407
18127
|
// src/server/legacy/shim.ts
|
|
17408
|
-
import { existsSync as
|
|
17409
|
-
import { join as
|
|
18128
|
+
import { existsSync as existsSync18, readFileSync as readFileSync26 } from "fs";
|
|
18129
|
+
import { join as join29 } from "path";
|
|
17410
18130
|
|
|
17411
18131
|
// src/server/legacy/routes.ts
|
|
17412
18132
|
var LEGACY_ROUTES = [
|
|
@@ -17414,11 +18134,11 @@ var LEGACY_ROUTES = [
|
|
|
17414
18134
|
{ method: "GET", path: "/docs", action: "410", open: true, migrate: "" },
|
|
17415
18135
|
{ method: "GET", path: "/docs/oauth2-redirect", action: "410", open: true, migrate: "" },
|
|
17416
18136
|
{ method: "GET", path: "/redoc", action: "410", open: true, migrate: "" },
|
|
17417
|
-
{ method: "POST", path: "/api/v2/tenants", action: "
|
|
17418
|
-
{ method: "GET", path: "/api/v2/tenants", action: "
|
|
17419
|
-
{ method: "GET", path: "/api/v2/tenants/{tenant_id}", action: "
|
|
18137
|
+
{ method: "POST", path: "/api/v2/tenants", action: "410", open: false, migrate: "no tenants: local single user" },
|
|
18138
|
+
{ method: "GET", path: "/api/v2/tenants", action: "410", open: false, migrate: "no tenants: local single user" },
|
|
18139
|
+
{ method: "GET", path: "/api/v2/tenants/{tenant_id}", action: "410", open: false, migrate: "no tenants: local single user" },
|
|
17420
18140
|
{ method: "PUT", path: "/api/v2/tenants/{tenant_id}", action: "410", open: false, migrate: "" },
|
|
17421
|
-
{ method: "DELETE", path: "/api/v2/tenants/{tenant_id}", action: "
|
|
18141
|
+
{ method: "DELETE", path: "/api/v2/tenants/{tenant_id}", action: "410", open: false, migrate: "no tenants: local single user" },
|
|
17422
18142
|
{ method: "GET", path: "/api/v2/tenants/{tenant_id}/projects", action: "410", open: false, migrate: "" },
|
|
17423
18143
|
{ method: "POST", path: "/api/v2/runs", action: "501", open: false, migrate: "" },
|
|
17424
18144
|
{ method: "GET", path: "/api/v2/runs", action: "map", open: false, migrate: "/v1/runs" },
|
|
@@ -17426,12 +18146,12 @@ var LEGACY_ROUTES = [
|
|
|
17426
18146
|
{ method: "POST", path: "/api/v2/runs/{run_id}/cancel", action: "501", open: false, migrate: "" },
|
|
17427
18147
|
{ method: "POST", path: "/api/v2/runs/{run_id}/replay", action: "501", open: false, migrate: "" },
|
|
17428
18148
|
{ method: "GET", path: "/api/v2/runs/{run_id}/timeline", action: "map", open: false, migrate: "/v1/runs/:id" },
|
|
17429
|
-
{ method: "POST", path: "/api/v2/api-keys", action: "
|
|
17430
|
-
{ method: "GET", path: "/api/v2/api-keys", action: "
|
|
18149
|
+
{ method: "POST", path: "/api/v2/api-keys", action: "410", open: false, migrate: "CP bearer token set by env (LOKI_CONTROL_TOKEN)" },
|
|
18150
|
+
{ method: "GET", path: "/api/v2/api-keys", action: "410", open: false, migrate: "CP bearer token set by env (LOKI_CONTROL_TOKEN)" },
|
|
17431
18151
|
{ method: "GET", path: "/api/v2/api-keys/{identifier}", action: "410", open: false, migrate: "" },
|
|
17432
18152
|
{ method: "PUT", path: "/api/v2/api-keys/{identifier}", action: "410", open: false, migrate: "" },
|
|
17433
|
-
{ method: "DELETE", path: "/api/v2/api-keys/{identifier}", action: "
|
|
17434
|
-
{ method: "POST", path: "/api/v2/api-keys/{identifier}/rotate", action: "
|
|
18153
|
+
{ method: "DELETE", path: "/api/v2/api-keys/{identifier}", action: "410", open: false, migrate: "CP bearer token set by env (LOKI_CONTROL_TOKEN)" },
|
|
18154
|
+
{ method: "POST", path: "/api/v2/api-keys/{identifier}/rotate", action: "410", open: false, migrate: "CP bearer token set by env (LOKI_CONTROL_TOKEN)" },
|
|
17435
18155
|
{ method: "GET", path: "/api/v2/policies", action: "410", open: false, migrate: "" },
|
|
17436
18156
|
{ method: "PUT", path: "/api/v2/policies", action: "410", open: false, migrate: "" },
|
|
17437
18157
|
{ method: "POST", path: "/api/v2/policies/evaluate", action: "410", open: false, migrate: "" },
|
|
@@ -17467,31 +18187,31 @@ var LEGACY_ROUTES = [
|
|
|
17467
18187
|
{ method: "GET", path: "/api/projects/{project_id}", action: "501", open: false, migrate: "" },
|
|
17468
18188
|
{ method: "PUT", path: "/api/projects/{project_id}", action: "501", open: false, migrate: "" },
|
|
17469
18189
|
{ method: "DELETE", path: "/api/projects/{project_id}", action: "501", open: false, migrate: "" },
|
|
17470
|
-
{ method: "GET", path: "/api/tasks", action: "
|
|
17471
|
-
{ method: "POST", path: "/api/tasks", action: "
|
|
17472
|
-
{ method: "GET", path: "/api/tasks/{task_id}", action: "
|
|
17473
|
-
{ method: "PUT", path: "/api/tasks/{task_id}", action: "
|
|
17474
|
-
{ method: "DELETE", path: "/api/tasks/{task_id}", action: "
|
|
17475
|
-
{ method: "POST", path: "/api/tasks/{task_id}/move", action: "
|
|
17476
|
-
{ method: "WS", path: "/ws", action: "
|
|
18190
|
+
{ method: "GET", path: "/api/tasks", action: "map", open: false, migrate: "/v1/tasks" },
|
|
18191
|
+
{ method: "POST", path: "/api/tasks", action: "410", open: false, migrate: "no v10 task queue; read-only GET /v1/tasks" },
|
|
18192
|
+
{ method: "GET", path: "/api/tasks/{task_id}", action: "410", open: false, migrate: "no v10 task queue; read-only GET /v1/tasks" },
|
|
18193
|
+
{ method: "PUT", path: "/api/tasks/{task_id}", action: "410", open: false, migrate: "no v10 task queue; read-only GET /v1/tasks" },
|
|
18194
|
+
{ method: "DELETE", path: "/api/tasks/{task_id}", action: "410", open: false, migrate: "no v10 task queue; read-only GET /v1/tasks" },
|
|
18195
|
+
{ method: "POST", path: "/api/tasks/{task_id}/move", action: "410", open: false, migrate: "no v10 task queue; read-only GET /v1/tasks" },
|
|
18196
|
+
{ method: "WS", path: "/ws", action: "410", open: true, migrate: "SSE at GET /v1/stream" },
|
|
17477
18197
|
{ method: "GET", path: "/api/registry/projects", action: "410", open: false, migrate: "" },
|
|
17478
|
-
{ method: "POST", path: "/api/registry/projects", action: "
|
|
18198
|
+
{ method: "POST", path: "/api/registry/projects", action: "410", open: false, migrate: "repos are discovered locally; GET /v1/repos" },
|
|
17479
18199
|
{ method: "GET", path: "/api/registry/projects/{identifier}", action: "410", open: false, migrate: "" },
|
|
17480
18200
|
{ method: "DELETE", path: "/api/registry/projects/{identifier}", action: "410", open: false, migrate: "" },
|
|
17481
18201
|
{ method: "GET", path: "/api/registry/projects/{identifier}/health", action: "410", open: false, migrate: "" },
|
|
17482
18202
|
{ method: "POST", path: "/api/registry/projects/{identifier}/access", action: "410", open: false, migrate: "" },
|
|
17483
18203
|
{ method: "GET", path: "/api/registry/discover", action: "410", open: false, migrate: "" },
|
|
17484
|
-
{ method: "POST", path: "/api/registry/sync", action: "
|
|
18204
|
+
{ method: "POST", path: "/api/registry/sync", action: "410", open: false, migrate: "repos are discovered locally; GET /v1/repos" },
|
|
17485
18205
|
{ method: "GET", path: "/api/registry/tasks", action: "410", open: false, migrate: "" },
|
|
17486
18206
|
{ method: "GET", path: "/api/registry/learnings", action: "410", open: false, migrate: "" },
|
|
17487
18207
|
{ method: "GET", path: "/api/fleet/runs", action: "map", open: false, migrate: "/v1/fleet/runs" },
|
|
17488
18208
|
{ method: "GET", path: "/api/fleet/summary", action: "map", open: false, migrate: "/v1/fleet/summary" },
|
|
17489
18209
|
{ method: "GET", path: "/api/fleet/runs/{identifier}", action: "410", open: false, migrate: "" },
|
|
17490
|
-
{ method: "POST", path: "/api/focus", action: "
|
|
18210
|
+
{ method: "POST", path: "/api/focus", action: "map", open: false, migrate: "/v1/focus" },
|
|
17491
18211
|
{ method: "GET", path: "/api/focus", action: "410", open: false, migrate: "" },
|
|
17492
18212
|
{ method: "DELETE", path: "/api/focus", action: "410", open: false, migrate: "" },
|
|
17493
18213
|
{ method: "GET", path: "/api/session/model", action: "410", open: false, migrate: "" },
|
|
17494
|
-
{ method: "POST", path: "/api/session/model", action: "
|
|
18214
|
+
{ method: "POST", path: "/api/session/model", action: "410", open: false, migrate: "no mid-run model switch; start a new run with another model (POST /v1/runs)" },
|
|
17495
18215
|
{ method: "GET", path: "/api/running-projects", action: "410", open: false, migrate: "" },
|
|
17496
18216
|
{ method: "POST", path: "/api/control/start", action: "501", open: false, migrate: "" },
|
|
17497
18217
|
{ method: "GET", path: "/api/control/builds/{execution_id}", action: "410", open: false, migrate: "" },
|
|
@@ -17505,19 +18225,19 @@ var LEGACY_ROUTES = [
|
|
|
17505
18225
|
{ method: "POST", path: "/api/enterprise/tokens", action: "410", open: false, migrate: "" },
|
|
17506
18226
|
{ method: "GET", path: "/api/enterprise/tokens", action: "410", open: false, migrate: "" },
|
|
17507
18227
|
{ method: "DELETE", path: "/api/enterprise/tokens/{identifier}", action: "410", open: false, migrate: "" },
|
|
17508
|
-
{ method: "GET", path: "/api/enterprise/audit", action: "
|
|
17509
|
-
{ method: "GET", path: "/api/enterprise/audit/summary", action: "
|
|
18228
|
+
{ method: "GET", path: "/api/enterprise/audit", action: "map", open: false, migrate: "/v1/audit" },
|
|
18229
|
+
{ method: "GET", path: "/api/enterprise/audit/summary", action: "map", open: false, migrate: "/v1/audit/summary" },
|
|
17510
18230
|
{ method: "GET", path: "/api/compliance", action: "410", open: false, migrate: "" },
|
|
17511
18231
|
{ method: "GET", path: "/api/memory/summary", action: "410", open: false, migrate: "" },
|
|
17512
|
-
{ method: "GET", path: "/api/memory/episodes", action: "
|
|
17513
|
-
{ method: "GET", path: "/api/memory/episodes/{episode_id}", action: "
|
|
17514
|
-
{ method: "GET", path: "/api/memory/patterns", action: "
|
|
17515
|
-
{ method: "GET", path: "/api/memory/patterns/{pattern_id}", action: "
|
|
17516
|
-
{ method: "GET", path: "/api/memory/skills", action: "
|
|
18232
|
+
{ method: "GET", path: "/api/memory/episodes", action: "map", open: false, migrate: "/v1/memory/episodes" },
|
|
18233
|
+
{ method: "GET", path: "/api/memory/episodes/{episode_id}", action: "map", open: false, migrate: "/v1/memory/episodes/{id}" },
|
|
18234
|
+
{ method: "GET", path: "/api/memory/patterns", action: "map", open: false, migrate: "/v1/memory/patterns" },
|
|
18235
|
+
{ method: "GET", path: "/api/memory/patterns/{pattern_id}", action: "map", open: false, migrate: "/v1/memory/patterns/{id}" },
|
|
18236
|
+
{ method: "GET", path: "/api/memory/skills", action: "map", open: false, migrate: "/v1/memory/skills" },
|
|
17517
18237
|
{ method: "GET", path: "/api/memory/skills/{skill_id}", action: "410", open: false, migrate: "" },
|
|
17518
|
-
{ method: "GET", path: "/api/memory/economics", action: "
|
|
17519
|
-
{ method: "POST", path: "/api/memory/consolidate", action: "
|
|
17520
|
-
{ method: "POST", path: "/api/memory/retrieve", action: "
|
|
18238
|
+
{ method: "GET", path: "/api/memory/economics", action: "map", open: false, migrate: "/v1/memory/economics" },
|
|
18239
|
+
{ method: "POST", path: "/api/memory/consolidate", action: "410", open: false, migrate: "read-only GET /v1/memory/*" },
|
|
18240
|
+
{ method: "POST", path: "/api/memory/retrieve", action: "410", open: false, migrate: "read-only GET /v1/memory/*" },
|
|
17521
18241
|
{ method: "GET", path: "/api/memory/index", action: "410", open: false, migrate: "" },
|
|
17522
18242
|
{ method: "GET", path: "/api/memory/timeline", action: "410", open: false, migrate: "" },
|
|
17523
18243
|
{ method: "GET", path: "/api/memory/files", action: "410", open: false, migrate: "" },
|
|
@@ -17528,41 +18248,41 @@ var LEGACY_ROUTES = [
|
|
|
17528
18248
|
{ method: "GET", path: "/api/learning/trends", action: "410", open: false, migrate: "" },
|
|
17529
18249
|
{ method: "GET", path: "/api/learning/signals", action: "410", open: false, migrate: "" },
|
|
17530
18250
|
{ method: "GET", path: "/api/learning/aggregation", action: "410", open: false, migrate: "" },
|
|
17531
|
-
{ method: "POST", path: "/api/learning/aggregate", action: "
|
|
18251
|
+
{ method: "POST", path: "/api/learning/aggregate", action: "410", open: false, migrate: "read-only GET /v1/memory/patterns" },
|
|
17532
18252
|
{ method: "GET", path: "/api/learning/preferences", action: "410", open: false, migrate: "" },
|
|
17533
18253
|
{ method: "GET", path: "/api/learning/errors", action: "410", open: false, migrate: "" },
|
|
17534
18254
|
{ method: "GET", path: "/api/learning/success", action: "410", open: false, migrate: "" },
|
|
17535
18255
|
{ method: "GET", path: "/api/learning/tools", action: "410", open: false, migrate: "" },
|
|
17536
|
-
{ method: "POST", path: "/api/control/pause", action: "
|
|
17537
|
-
{ method: "POST", path: "/api/control/resume", action: "
|
|
17538
|
-
{ method: "POST", path: "/api/control/stop", action: "
|
|
18256
|
+
{ method: "POST", path: "/api/control/pause", action: "map", open: false, migrate: "/v1/control/pause" },
|
|
18257
|
+
{ method: "POST", path: "/api/control/resume", action: "map", open: false, migrate: "/v1/control/resume" },
|
|
18258
|
+
{ method: "POST", path: "/api/control/stop", action: "map", open: false, migrate: "/v1/control/stop" },
|
|
17539
18259
|
{ method: "GET", path: "/api/cost", action: "map", open: false, migrate: "/v1/cost/snapshot" },
|
|
17540
18260
|
{ method: "GET", path: "/api/budget", action: "501", open: false, migrate: "" },
|
|
17541
18261
|
{ method: "GET", path: "/api/cost/timeline", action: "map", open: false, migrate: "/v1/cost/timeline" },
|
|
17542
18262
|
{ method: "GET", path: "/api/trust/trajectory", action: "410", open: false, migrate: "" },
|
|
17543
18263
|
{ method: "GET", path: "/api/gate-policy", action: "410", open: false, migrate: "" },
|
|
17544
18264
|
{ method: "GET", path: "/api/pricing", action: "410", open: false, migrate: "" },
|
|
17545
|
-
{ method: "GET", path: "/api/council/state", action: "
|
|
17546
|
-
{ method: "GET", path: "/api/council/verdicts", action: "
|
|
17547
|
-
{ method: "GET", path: "/api/council/convergence", action: "
|
|
17548
|
-
{ method: "GET", path: "/api/council/report", action: "
|
|
17549
|
-
{ method: "POST", path: "/api/council/force-review", action: "
|
|
17550
|
-
{ method: "GET", path: "/api/council/transcripts", action: "
|
|
18265
|
+
{ method: "GET", path: "/api/council/state", action: "map", open: false, migrate: "/v1/council/state" },
|
|
18266
|
+
{ method: "GET", path: "/api/council/verdicts", action: "map", open: false, migrate: "/v1/council/verdicts" },
|
|
18267
|
+
{ method: "GET", path: "/api/council/convergence", action: "map", open: false, migrate: "/v1/council/convergence" },
|
|
18268
|
+
{ method: "GET", path: "/api/council/report", action: "map", open: false, migrate: "/v1/council/report" },
|
|
18269
|
+
{ method: "POST", path: "/api/council/force-review", action: "map", open: false, migrate: "/v1/control/council-review" },
|
|
18270
|
+
{ method: "GET", path: "/api/council/transcripts", action: "map", open: false, migrate: "/v1/council/transcripts" },
|
|
17551
18271
|
{ method: "GET", path: "/api/council/transcripts/{iteration_id}", action: "410", open: false, migrate: "" },
|
|
17552
|
-
{ method: "GET", path: "/api/context", action: "
|
|
18272
|
+
{ method: "GET", path: "/api/context", action: "map", open: false, migrate: "/v1/context" },
|
|
17553
18273
|
{ method: "GET", path: "/api/notifications", action: "410", open: false, migrate: "" },
|
|
17554
18274
|
{ method: "GET", path: "/api/notifications/triggers", action: "410", open: false, migrate: "" },
|
|
17555
|
-
{ method: "PUT", path: "/api/notifications/triggers", action: "
|
|
18275
|
+
{ method: "PUT", path: "/api/notifications/triggers", action: "410", open: false, migrate: "GET /v1/notifications (derived from events)" },
|
|
17556
18276
|
{ method: "POST", path: "/api/notifications/{notification_id}/acknowledge", action: "410", open: false, migrate: "" },
|
|
17557
18277
|
{ method: "POST", path: "/api/notifications/{notification_id}/unacknowledge", action: "410", open: false, migrate: "" },
|
|
17558
|
-
{ method: "GET", path: "/api/checkpoints", action: "
|
|
17559
|
-
{ method: "GET", path: "/api/checkpoints/{checkpoint_id}", action: "
|
|
17560
|
-
{ method: "POST", path: "/api/checkpoints", action: "
|
|
17561
|
-
{ method: "POST", path: "/api/checkpoints/{checkpoint_id}/rollback", action: "
|
|
18278
|
+
{ method: "GET", path: "/api/checkpoints", action: "map", open: false, migrate: "/v1/checkpoints" },
|
|
18279
|
+
{ method: "GET", path: "/api/checkpoints/{checkpoint_id}", action: "map", open: false, migrate: "/v1/checkpoints/{id}" },
|
|
18280
|
+
{ method: "POST", path: "/api/checkpoints", action: "map", open: false, migrate: "/v1/checkpoints" },
|
|
18281
|
+
{ method: "POST", path: "/api/checkpoints/{checkpoint_id}/rollback", action: "map", open: false, migrate: "/v1/checkpoints/{id}/rollback" },
|
|
17562
18282
|
{ method: "GET", path: "/api/agents", action: "410", open: false, migrate: "" },
|
|
17563
|
-
{ method: "POST", path: "/api/agents/{agent_id}/kill", action: "
|
|
17564
|
-
{ method: "POST", path: "/api/agents/{agent_id}/pause", action: "
|
|
17565
|
-
{ method: "POST", path: "/api/agents/{agent_id}/resume", action: "
|
|
18283
|
+
{ method: "POST", path: "/api/agents/{agent_id}/kill", action: "410", open: false, migrate: "POST /v1/control/stop" },
|
|
18284
|
+
{ method: "POST", path: "/api/agents/{agent_id}/pause", action: "410", open: false, migrate: "POST /v1/control/stop (v10 has no pause)" },
|
|
18285
|
+
{ method: "POST", path: "/api/agents/{agent_id}/resume", action: "410", open: false, migrate: "POST /v1/control/resume" },
|
|
17566
18286
|
{ method: "GET", path: "/api/logs", action: "410", open: false, migrate: "" },
|
|
17567
18287
|
{ method: "POST", path: "/api/collab/join", action: "410", open: false, migrate: "" },
|
|
17568
18288
|
{ method: "POST", path: "/api/collab/leave", action: "410", open: false, migrate: "" },
|
|
@@ -17591,19 +18311,19 @@ var LEGACY_ROUTES = [
|
|
|
17591
18311
|
{ method: "GET", path: "/api/checklist/summary", action: "410", open: false, migrate: "" },
|
|
17592
18312
|
{ method: "GET", path: "/api/prd-observations", action: "410", open: false, migrate: "" },
|
|
17593
18313
|
{ method: "GET", path: "/api/checklist/waivers", action: "410", open: false, migrate: "" },
|
|
17594
|
-
{ method: "POST", path: "/api/checklist/waivers", action: "
|
|
17595
|
-
{ method: "DELETE", path: "/api/checklist/waivers/{item_id}", action: "
|
|
18314
|
+
{ method: "POST", path: "/api/checklist/waivers", action: "410", open: false, migrate: "plan.json and receipt claims in GET /v1/runs/:id" },
|
|
18315
|
+
{ method: "DELETE", path: "/api/checklist/waivers/{item_id}", action: "410", open: false, migrate: "plan.json and receipt claims in GET /v1/runs/:id" },
|
|
17596
18316
|
{ method: "GET", path: "/api/council/gate", action: "501", open: false, migrate: "" },
|
|
17597
18317
|
{ method: "GET", path: "/api/app-runner/status", action: "410", open: false, migrate: "" },
|
|
17598
18318
|
{ method: "GET", path: "/api/app-runner/logs", action: "410", open: false, migrate: "" },
|
|
17599
18319
|
{ method: "GET", path: "/api/app-runner/errors", action: "410", open: false, migrate: "" },
|
|
17600
|
-
{ method: "POST", path: "/api/control/app-restart", action: "
|
|
17601
|
-
{ method: "POST", path: "/api/control/app-stop", action: "
|
|
18320
|
+
{ method: "POST", path: "/api/control/app-restart", action: "410", open: false, migrate: "evidence in GET /v1/runs/:source/:run/artifact/*" },
|
|
18321
|
+
{ method: "POST", path: "/api/control/app-stop", action: "410", open: false, migrate: "evidence in GET /v1/runs/:source/:run/artifact/*" },
|
|
17602
18322
|
{ method: "GET", path: "/api/playwright/results", action: "410", open: false, migrate: "" },
|
|
17603
18323
|
{ method: "GET", path: "/api/playwright/screenshot", action: "410", open: false, migrate: "" },
|
|
17604
18324
|
{ method: "GET", path: "/api/failures", action: "410", open: false, migrate: "" },
|
|
17605
18325
|
{ method: "GET", path: "/api/prompt-versions", action: "410", open: false, migrate: "" },
|
|
17606
|
-
{ method: "POST", path: "/api/prompt-optimize", action: "
|
|
18326
|
+
{ method: "POST", path: "/api/prompt-optimize", action: "410", open: false, migrate: "verify stage: POST /v1/runs/:source/:run/verify" },
|
|
17607
18327
|
{ method: "GET", path: "/api/activity", action: "501", open: false, migrate: "" },
|
|
17608
18328
|
{ method: "GET", path: "/api/session-diff", action: "410", open: false, migrate: "" },
|
|
17609
18329
|
{ method: "POST", path: "/api/activity", action: "410", open: false, migrate: "" },
|
|
@@ -17612,8 +18332,8 @@ var LEGACY_ROUTES = [
|
|
|
17612
18332
|
{ method: "GET", path: "/", action: "308", open: false, migrate: "/" },
|
|
17613
18333
|
{ method: "GET", path: "/api/quality-score", action: "410", open: false, migrate: "" },
|
|
17614
18334
|
{ method: "GET", path: "/api/quality-score/history", action: "410", open: false, migrate: "" },
|
|
17615
|
-
{ method: "POST", path: "/api/quality-scan", action: "
|
|
17616
|
-
{ method: "GET", path: "/api/quality-report", action: "
|
|
18335
|
+
{ method: "POST", path: "/api/quality-scan", action: "410", open: false, migrate: "verify stage: POST /v1/runs/:source/:run/verify" },
|
|
18336
|
+
{ method: "GET", path: "/api/quality-report", action: "410", open: false, migrate: "verify stage: POST /v1/runs/:source/:run/verify" },
|
|
17617
18337
|
{ method: "GET", path: "/api/migration/list", action: "501", open: false, migrate: "" },
|
|
17618
18338
|
{ method: "POST", path: "/api/migration/start", action: "410", open: false, migrate: "" },
|
|
17619
18339
|
{ method: "GET", path: "/api/migration/{migration_id}/status", action: "501", open: false, migrate: "" },
|
|
@@ -17622,8 +18342,8 @@ var LEGACY_ROUTES = [
|
|
|
17622
18342
|
{ method: "GET", path: "/api/migration/{migration_id}/seams", action: "410", open: false, migrate: "" },
|
|
17623
18343
|
{ method: "POST", path: "/api/migration/{migration_id}/advance", action: "410", open: false, migrate: "" },
|
|
17624
18344
|
{ method: "POST", path: "/api/migration/{migration_id}/start-phase", action: "410", open: false, migrate: "" },
|
|
17625
|
-
{ method: "GET", path: "/api/managed/events", action: "
|
|
17626
|
-
{ method: "GET", path: "/api/managed/status", action: "
|
|
18345
|
+
{ method: "GET", path: "/api/managed/events", action: "410", open: false, migrate: "managed memory dropped; GET /v1/runs/:source/:run/events" },
|
|
18346
|
+
{ method: "GET", path: "/api/managed/status", action: "410", open: false, migrate: "managed memory dropped; GET /v1/runs" },
|
|
17627
18347
|
{ method: "GET", path: "/api/managed/memory_versions/{memory_id}", action: "410", open: false, migrate: "" },
|
|
17628
18348
|
{ method: "GET", path: "/api/findings/{iteration}", action: "410", open: false, migrate: "" },
|
|
17629
18349
|
{ method: "GET", path: "/api/quality/architecture", action: "410", open: false, migrate: "" },
|
|
@@ -17639,9 +18359,101 @@ var LEGACY_ROUTES = [
|
|
|
17639
18359
|
{ method: "GET", path: "/api/spec/history", action: "410", open: false, migrate: "" },
|
|
17640
18360
|
{ method: "GET", path: "/api/wiki", action: "410", open: false, migrate: "" },
|
|
17641
18361
|
{ method: "GET", path: "/api/wiki/{section}", action: "410", open: false, migrate: "" },
|
|
17642
|
-
{ method: "POST", path: "/api/wiki/ask", action: "
|
|
18362
|
+
{ method: "POST", path: "/api/wiki/ask", action: "410", open: false, migrate: "wiki deleted; no replacement" }
|
|
17643
18363
|
];
|
|
17644
18364
|
|
|
18365
|
+
// src/server/legacy/routes-audit.ts
|
|
18366
|
+
var BAD_ID = "Invalid checkpoint_id: must be 1-128 chars of alphanumeric, hyphens, and underscores";
|
|
18367
|
+
function auditCheckpointMapped(db, repoDir, auditList) {
|
|
18368
|
+
const local = (c) => peerIsLoopback(c) && isLoopbackHost(c.req.header("host")) && (c.req.header("content-type") ?? "").toLowerCase().startsWith("application/json");
|
|
18369
|
+
const refuse = (c) => c.json({ detail: "loopback JSON requests only" }, 403);
|
|
18370
|
+
return {
|
|
18371
|
+
"GET /api/enterprise/audit": (c) => {
|
|
18372
|
+
const r = auditList(c);
|
|
18373
|
+
return r.err ?? c.json(r.rows);
|
|
18374
|
+
},
|
|
18375
|
+
"GET /api/enterprise/audit/summary": (c) => c.json(auditSummary(db, parseDays(c.req.query("days")))),
|
|
18376
|
+
"GET /api/checkpoints": (c) => c.json(listCheckpoints(repoDir, parseLimit(c.req.query("limit")))),
|
|
18377
|
+
"GET /api/checkpoints/{checkpoint_id}": (c) => {
|
|
18378
|
+
const id = c.req.param("checkpoint_id") ?? "";
|
|
18379
|
+
if (!validCheckpointId(id))
|
|
18380
|
+
return c.json({ detail: BAD_ID }, 400);
|
|
18381
|
+
const m = getCheckpoint(repoDir, id);
|
|
18382
|
+
return m ? c.json(m) : c.json({ detail: "Checkpoint not found" }, 404);
|
|
18383
|
+
},
|
|
18384
|
+
"POST /api/checkpoints": async (c) => {
|
|
18385
|
+
if (!local(c))
|
|
18386
|
+
return refuse(c);
|
|
18387
|
+
const body = await c.req.json().catch(() => ({}));
|
|
18388
|
+
const message = typeof body?.message === "string" ? body.message : "";
|
|
18389
|
+
if (message.length > 500)
|
|
18390
|
+
return c.json({ detail: "message must be at most 500 characters" }, 422);
|
|
18391
|
+
try {
|
|
18392
|
+
const meta = createCheckpoint(repoDir, message);
|
|
18393
|
+
audit3(db, { kind: "checkpoint.create", target: String(meta.id), result: "ok", detail: "legacy route" });
|
|
18394
|
+
return c.json(meta, 201);
|
|
18395
|
+
} catch (e) {
|
|
18396
|
+
audit3(db, { kind: "checkpoint.create", result: "error", detail: e.message });
|
|
18397
|
+
return c.json({ detail: "could not create the checkpoint" }, 500);
|
|
18398
|
+
}
|
|
18399
|
+
},
|
|
18400
|
+
"POST /api/checkpoints/{checkpoint_id}/rollback": (c) => {
|
|
18401
|
+
if (!local(c))
|
|
18402
|
+
return refuse(c);
|
|
18403
|
+
const id = c.req.param("checkpoint_id") ?? "";
|
|
18404
|
+
if (!validCheckpointId(id)) {
|
|
18405
|
+
audit3(db, { kind: "checkpoint.rollback", target: id.slice(0, 128), result: "refused", detail: "invalid id (legacy route)" });
|
|
18406
|
+
return c.json({ detail: BAD_ID }, 400);
|
|
18407
|
+
}
|
|
18408
|
+
const r = rollbackCheckpoint(repoDir, id);
|
|
18409
|
+
if (!r) {
|
|
18410
|
+
audit3(db, { kind: "checkpoint.rollback", target: id, result: "refused", detail: "not found (legacy route)" });
|
|
18411
|
+
return c.json({ detail: "Checkpoint not found" }, 404);
|
|
18412
|
+
}
|
|
18413
|
+
audit3(db, { kind: "checkpoint.rollback", target: id, result: r.errors.length ? "partial" : "ok", detail: `restored ${r.restored}; pre-rollback snapshot ${r.pre_rollback_snapshot} (legacy route)` });
|
|
18414
|
+
return c.json({ ...r, message: `Restored ${r.restored} item(s) from ${id}. Prior state saved as ${r.pre_rollback_snapshot} (undo this rollback by restoring it).` });
|
|
18415
|
+
}
|
|
18416
|
+
};
|
|
18417
|
+
}
|
|
18418
|
+
|
|
18419
|
+
// src/server/legacy/routes-memory.ts
|
|
18420
|
+
function memoryMapped(repoDir) {
|
|
18421
|
+
const loki = () => lokiFor(repoDir);
|
|
18422
|
+
return {
|
|
18423
|
+
"GET /api/tasks": (c) => {
|
|
18424
|
+
if (c.req.query("project_id") !== undefined)
|
|
18425
|
+
return c.json({ detail: "project_id filter is not supported by the Control Plane" }, 400);
|
|
18426
|
+
return c.json(tasksList(loki(), c.req.query("status")));
|
|
18427
|
+
},
|
|
18428
|
+
"GET /api/memory/episodes": (c) => {
|
|
18429
|
+
const lim = c.req.query("limit") === undefined ? 50 : Number(c.req.query("limit"));
|
|
18430
|
+
return Number.isInteger(lim) && lim >= 1 && lim <= 1000 ? c.json(memoryEpisodes(loki(), lim)) : c.json({ detail: "limit must be 1..1000" }, 422);
|
|
18431
|
+
},
|
|
18432
|
+
"GET /api/memory/episodes/{episode_id}": (c) => {
|
|
18433
|
+
const e = memoryEpisode(loki(), c.req.param("episode_id") ?? "");
|
|
18434
|
+
return e ? c.json(e) : c.json({ detail: "Episode not found" }, 404);
|
|
18435
|
+
},
|
|
18436
|
+
"GET /api/memory/patterns": (c) => c.json(memoryPatterns(loki())),
|
|
18437
|
+
"GET /api/memory/patterns/{pattern_id}": (c) => {
|
|
18438
|
+
const p = memoryPatterns(loki()).find((x) => x.id === c.req.param("pattern_id"));
|
|
18439
|
+
return p ? c.json(p) : c.json({ detail: "Pattern not found" }, 404);
|
|
18440
|
+
},
|
|
18441
|
+
"GET /api/memory/skills": (c) => c.json(memorySkills(loki())),
|
|
18442
|
+
"GET /api/memory/economics": (c) => c.json(memoryEconomics(loki())),
|
|
18443
|
+
"GET /api/context": (c) => {
|
|
18444
|
+
const r = contextTracking(loki());
|
|
18445
|
+
return c.json(r.body, r.status);
|
|
18446
|
+
},
|
|
18447
|
+
"POST /api/focus": async (c) => {
|
|
18448
|
+
if (!mutationAllowed(c))
|
|
18449
|
+
return c.json({ detail: "loopback JSON requests only" }, 403);
|
|
18450
|
+
const body = await c.req.json().catch(() => null);
|
|
18451
|
+
const r = setFocus(repoDir, typeof body === "object" && body !== null ? body.project_dir : undefined);
|
|
18452
|
+
return r.ok ? c.json(focusInfo(repoDir)) : c.json({ detail: r.error }, 400);
|
|
18453
|
+
}
|
|
18454
|
+
};
|
|
18455
|
+
}
|
|
18456
|
+
|
|
17645
18457
|
// src/server/legacy/routes-metrics.ts
|
|
17646
18458
|
function metricsMapped(repoDir) {
|
|
17647
18459
|
const inactive = (c) => parseInactive(c.req.query("include_inactive"));
|
|
@@ -17654,6 +18466,31 @@ function metricsMapped(repoDir) {
|
|
|
17654
18466
|
};
|
|
17655
18467
|
}
|
|
17656
18468
|
|
|
18469
|
+
// src/server/legacy/routes-control.ts
|
|
18470
|
+
function controlMapped(db, repoDir) {
|
|
18471
|
+
const loki = () => lokiDirOf(repoDir);
|
|
18472
|
+
return {
|
|
18473
|
+
"POST /api/control/pause": (c) => runControl(db, repoDir, "pause", c, "legacy"),
|
|
18474
|
+
"POST /api/control/resume": (c) => runControl(db, repoDir, "resume", c, "legacy"),
|
|
18475
|
+
"POST /api/control/stop": (c) => runControl(db, repoDir, "stop", c, "legacy"),
|
|
18476
|
+
"POST /api/council/force-review": (c) => runControl(db, repoDir, "council-review", c, "legacy"),
|
|
18477
|
+
"GET /api/council/state": (c) => c.json(councilState(loki())),
|
|
18478
|
+
"GET /api/council/verdicts": (c) => {
|
|
18479
|
+
const n2 = intQ(c.req.query("limit"), 1000);
|
|
18480
|
+
return n2 === null || n2 === 0 ? c.json({ detail: "limit must be 1..1000" }, 422) : c.json(councilVerdicts(loki(), n2));
|
|
18481
|
+
},
|
|
18482
|
+
"GET /api/council/convergence": (c) => c.json(councilConvergence(loki())),
|
|
18483
|
+
"GET /api/council/report": (c) => c.json(councilReport(loki())),
|
|
18484
|
+
"GET /api/council/transcripts": (c) => {
|
|
18485
|
+
const limit = intQ(c.req.query("limit"), 200), iterMin = intQ(c.req.query("iter_min"), Number.MAX_SAFE_INTEGER);
|
|
18486
|
+
if (limit === null || limit === 0 || iterMin === null)
|
|
18487
|
+
return c.json({ detail: "limit must be 1..200 and iter_min a non-negative integer" }, 422);
|
|
18488
|
+
const r = councilTranscripts(loki(), { limit, since: c.req.query("since"), iter_min: iterMin });
|
|
18489
|
+
return "error" in r ? c.json({ detail: r.error }, 400) : c.json(r);
|
|
18490
|
+
}
|
|
18491
|
+
};
|
|
18492
|
+
}
|
|
18493
|
+
|
|
17657
18494
|
// src/server/legacy/shim.ts
|
|
17658
18495
|
var flagOn = (v) => ["true", "1", "yes"].includes((v ?? "").toLowerCase());
|
|
17659
18496
|
var legacyAuthEnabled = (env) => flagOn(env.LOKI_ENTERPRISE_AUTH) || !!(env.LOKI_OIDC_ISSUER && env.LOKI_OIDC_CLIENT_ID);
|
|
@@ -17661,10 +18498,10 @@ var logged = new Set;
|
|
|
17661
18498
|
var toHono = (p) => p.replace(/\{([A-Za-z_]+):path\}/g, ":$1{.+}").replace(/\{([A-Za-z_]+)\}/g, ":$1");
|
|
17662
18499
|
var NOT_SUPPORTED = "not yet supported by the Control Plane";
|
|
17663
18500
|
var repoVersion = () => {
|
|
17664
|
-
for (const f of [
|
|
18501
|
+
for (const f of [join29(import.meta.dir, "../../../../../VERSION")]) {
|
|
17665
18502
|
try {
|
|
17666
|
-
if (
|
|
17667
|
-
return
|
|
18503
|
+
if (existsSync18(f))
|
|
18504
|
+
return readFileSync26(f, "utf8").trim();
|
|
17668
18505
|
} catch {}
|
|
17669
18506
|
}
|
|
17670
18507
|
return "unknown";
|
|
@@ -17792,7 +18629,10 @@ function legacyShim(opts) {
|
|
|
17792
18629
|
const r = auditList(c);
|
|
17793
18630
|
return r.err ?? c.json(r.rows);
|
|
17794
18631
|
},
|
|
17795
|
-
...metricsMapped(opts.repoDir ?? process.cwd())
|
|
18632
|
+
...metricsMapped(opts.repoDir ?? process.cwd()),
|
|
18633
|
+
...auditCheckpointMapped(db, opts.repoDir ?? process.cwd(), auditList),
|
|
18634
|
+
...memoryMapped(opts.repoDir ?? process.cwd()),
|
|
18635
|
+
...controlMapped(db, opts.repoDir ?? process.cwd())
|
|
17796
18636
|
};
|
|
17797
18637
|
app.all("/lab/api/*", (c) => {
|
|
17798
18638
|
const d = denied(c);
|
|
@@ -17817,7 +18657,7 @@ function legacyShim(opts) {
|
|
|
17817
18657
|
if (r.action === "308")
|
|
17818
18658
|
return c.redirect(`${uiBase}/`, 308);
|
|
17819
18659
|
if (r.action === "410")
|
|
17820
|
-
return c.json({ error: "gone", detail: "This legacy dashboard route was retired; nothing consumes it.", ...body }, 410);
|
|
18660
|
+
return c.json({ error: "gone", detail: "This legacy dashboard route was retired; nothing consumes it.", ...r.migrate ? { replacement: r.migrate } : {}, ...body }, 410);
|
|
17821
18661
|
return c.json({ error: NOT_SUPPORTED, detail: "The legacy dashboard route exists, but the Control Plane has no data or mechanism for it yet.", ...body }, 501);
|
|
17822
18662
|
};
|
|
17823
18663
|
const m = r.method === "WS" || r.method === "MOUNT" ? "GET" : r.method;
|
|
@@ -17843,7 +18683,7 @@ function legacyShim(opts) {
|
|
|
17843
18683
|
|
|
17844
18684
|
// src/server/app.ts
|
|
17845
18685
|
var MAX_BODY2 = 1e6;
|
|
17846
|
-
var defaultUiDir = () => [
|
|
18686
|
+
var defaultUiDir = () => [join30(import.meta.dir, "../../ui/dist"), join30(import.meta.dir, "../ui/dist")].find((d) => existsSync19(join30(d, "index.html")));
|
|
17847
18687
|
function createApp(opts) {
|
|
17848
18688
|
const uiDir = opts.uiDir ?? defaultUiDir();
|
|
17849
18689
|
const { db, sqlite } = openDb(opts.dbPath);
|
|
@@ -17968,11 +18808,11 @@ function createApp(opts) {
|
|
|
17968
18808
|
return c.json({ error: "not found" }, 404);
|
|
17969
18809
|
const root = resolve6(uiDir);
|
|
17970
18810
|
const f = resolve6(root, `.${p}`);
|
|
17971
|
-
if (f.startsWith(root +
|
|
18811
|
+
if (f.startsWith(root + sep10) && existsSync19(f) && statSync13(f).isFile())
|
|
17972
18812
|
return new Response(Bun.file(f));
|
|
17973
18813
|
if (/\.[a-z0-9]+$/i.test(p))
|
|
17974
18814
|
return c.json({ error: "not found" }, 404);
|
|
17975
|
-
return new Response(Bun.file(
|
|
18815
|
+
return new Response(Bun.file(join30(root, "index.html")));
|
|
17976
18816
|
});
|
|
17977
18817
|
return { app, db, close: () => sqlite.close() };
|
|
17978
18818
|
}
|