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
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// CPE24-P2: legacy (port 57374) mappings for audit and checkpoints. Reads share their implementation with routes/checkpoints.ts; the two POSTs
|
|
2
|
+
// keep the /v1 rule (loopback peer, loopback Host, JSON) even though the shim guard already passed, so a token alone never reaches a restore.
|
|
3
|
+
import type { Context } from "hono";
|
|
4
|
+
import type { Db } from "../../db/migrate.ts";
|
|
5
|
+
import { audit } from "../audit.ts";
|
|
6
|
+
import { isLoopbackHost, peerIsLoopback } from "../auth.ts";
|
|
7
|
+
import { auditSummary, createCheckpoint, getCheckpoint, listCheckpoints, parseDays, parseLimit, rollbackCheckpoint, validCheckpointId } from "../routes/checkpoints.ts";
|
|
8
|
+
|
|
9
|
+
type AuditList = (c: Context) => { err: Response; rows?: undefined } | { err?: undefined; rows: unknown[] };
|
|
10
|
+
|
|
11
|
+
const BAD_ID = "Invalid checkpoint_id: must be 1-128 chars of alphanumeric, hyphens, and underscores";
|
|
12
|
+
|
|
13
|
+
export function auditCheckpointMapped(db: Db, repoDir: string, auditList: AuditList): Record<string, (c: Context) => Response | Promise<Response>> {
|
|
14
|
+
const local = (c: Context) => peerIsLoopback(c) && isLoopbackHost(c.req.header("host")) && (c.req.header("content-type") ?? "").toLowerCase().startsWith("application/json");
|
|
15
|
+
const refuse = (c: Context) => c.json({ detail: "loopback JSON requests only" }, 403);
|
|
16
|
+
return {
|
|
17
|
+
"GET /api/enterprise/audit": (c) => { const r = auditList(c); return r.err ?? c.json(r.rows); },
|
|
18
|
+
"GET /api/enterprise/audit/summary": (c) => c.json(auditSummary(db, parseDays(c.req.query("days")))),
|
|
19
|
+
"GET /api/checkpoints": (c) => c.json(listCheckpoints(repoDir, parseLimit(c.req.query("limit")))),
|
|
20
|
+
"GET /api/checkpoints/{checkpoint_id}": (c) => {
|
|
21
|
+
const id = c.req.param("checkpoint_id") ?? "";
|
|
22
|
+
if (!validCheckpointId(id)) return c.json({ detail: BAD_ID }, 400);
|
|
23
|
+
const m = getCheckpoint(repoDir, id);
|
|
24
|
+
return m ? c.json(m) : c.json({ detail: "Checkpoint not found" }, 404);
|
|
25
|
+
},
|
|
26
|
+
"POST /api/checkpoints": async (c) => {
|
|
27
|
+
if (!local(c)) return refuse(c);
|
|
28
|
+
const body = (await c.req.json().catch(() => ({}))) as { message?: unknown };
|
|
29
|
+
const message = typeof body?.message === "string" ? body.message : "";
|
|
30
|
+
if (message.length > 500) return c.json({ detail: "message must be at most 500 characters" }, 422);
|
|
31
|
+
try {
|
|
32
|
+
const meta = createCheckpoint(repoDir, message);
|
|
33
|
+
audit(db, { kind: "checkpoint.create", target: String(meta.id), result: "ok", detail: "legacy route" });
|
|
34
|
+
return c.json(meta, 201);
|
|
35
|
+
} catch (e) {
|
|
36
|
+
audit(db, { kind: "checkpoint.create", result: "error", detail: (e as Error).message });
|
|
37
|
+
return c.json({ detail: "could not create the checkpoint" }, 500);
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"POST /api/checkpoints/{checkpoint_id}/rollback": (c) => {
|
|
41
|
+
if (!local(c)) return refuse(c);
|
|
42
|
+
const id = c.req.param("checkpoint_id") ?? "";
|
|
43
|
+
if (!validCheckpointId(id)) { audit(db, { kind: "checkpoint.rollback", target: id.slice(0, 128), result: "refused", detail: "invalid id (legacy route)" }); return c.json({ detail: BAD_ID }, 400); }
|
|
44
|
+
const r = rollbackCheckpoint(repoDir, id);
|
|
45
|
+
if (!r) { audit(db, { kind: "checkpoint.rollback", target: id, result: "refused", detail: "not found (legacy route)" }); return c.json({ detail: "Checkpoint not found" }, 404); }
|
|
46
|
+
audit(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)` });
|
|
47
|
+
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).` });
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// CPE24-P4: legacy (port 57374) mappings for session control and the Completion Council. They share the /v1 implementation in
|
|
2
|
+
// routes/session_control.ts (fixed action enum, signal files only, same-origin check, audit row). The shim guard (CP token or loopback peer) has already run.
|
|
3
|
+
import type { Context } from "hono";
|
|
4
|
+
import type { Db } from "../../db/migrate.ts";
|
|
5
|
+
import { councilConvergence, councilReport, councilState, councilTranscripts, councilVerdicts, intQ, runControl } from "../routes/session_control.ts";
|
|
6
|
+
import { lokiDirOf } from "../routes/cost_ledger.ts";
|
|
7
|
+
|
|
8
|
+
export function controlMapped(db: Db, repoDir: string): Record<string, (c: Context) => Response> {
|
|
9
|
+
const loki = () => lokiDirOf(repoDir);
|
|
10
|
+
return {
|
|
11
|
+
"POST /api/control/pause": (c) => runControl(db, repoDir, "pause", c, "legacy"),
|
|
12
|
+
"POST /api/control/resume": (c) => runControl(db, repoDir, "resume", c, "legacy"),
|
|
13
|
+
"POST /api/control/stop": (c) => runControl(db, repoDir, "stop", c, "legacy"),
|
|
14
|
+
"POST /api/council/force-review": (c) => runControl(db, repoDir, "council-review", c, "legacy"),
|
|
15
|
+
"GET /api/council/state": (c) => c.json(councilState(loki())),
|
|
16
|
+
"GET /api/council/verdicts": (c) => { const n = intQ(c.req.query("limit"), 1000); return n === null || n === 0 ? c.json({ detail: "limit must be 1..1000" }, 422) : c.json(councilVerdicts(loki(), n)); },
|
|
17
|
+
"GET /api/council/convergence": (c) => c.json(councilConvergence(loki())),
|
|
18
|
+
"GET /api/council/report": (c) => c.json(councilReport(loki())),
|
|
19
|
+
"GET /api/council/transcripts": (c) => {
|
|
20
|
+
const limit = intQ(c.req.query("limit"), 200), iterMin = intQ(c.req.query("iter_min"), Number.MAX_SAFE_INTEGER);
|
|
21
|
+
if (limit === null || limit === 0 || iterMin === null) return c.json({ detail: "limit must be 1..200 and iter_min a non-negative integer" }, 422);
|
|
22
|
+
const r = councilTranscripts(loki(), { limit, since: c.req.query("since"), iter_min: iterMin });
|
|
23
|
+
return "error" in r ? c.json({ detail: r.error }, 400) : c.json(r);
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// CPE24-P3: legacy (port 57374) mappings for memory, context, focus and tasks. Each reads the same source as its /v1 route (routes/memory.ts)
|
|
2
|
+
// and keeps the legacy response shape. The shim's guard (legacyGuard) has already run; POST /api/focus adds the same loopback, JSON and Origin checks as /v1/focus.
|
|
3
|
+
import type { Context } from "hono";
|
|
4
|
+
import { contextTracking, focusInfo, lokiFor, memoryEconomics, memoryEpisode, memoryEpisodes, memoryPatterns, memorySkills, mutationAllowed, setFocus, tasksList } from "../routes/memory.ts";
|
|
5
|
+
|
|
6
|
+
export function memoryMapped(repoDir: string): Record<string, (c: Context) => Response | Promise<Response>> {
|
|
7
|
+
const loki = () => lokiFor(repoDir);
|
|
8
|
+
return {
|
|
9
|
+
"GET /api/tasks": (c) => {
|
|
10
|
+
if (c.req.query("project_id") !== undefined) return c.json({ detail: "project_id filter is not supported by the Control Plane" }, 400);
|
|
11
|
+
return c.json(tasksList(loki(), c.req.query("status")));
|
|
12
|
+
},
|
|
13
|
+
"GET /api/memory/episodes": (c) => {
|
|
14
|
+
const lim = c.req.query("limit") === undefined ? 50 : Number(c.req.query("limit"));
|
|
15
|
+
return Number.isInteger(lim) && lim >= 1 && lim <= 1000 ? c.json(memoryEpisodes(loki(), lim)) : c.json({ detail: "limit must be 1..1000" }, 422);
|
|
16
|
+
},
|
|
17
|
+
"GET /api/memory/episodes/{episode_id}": (c) => { const e = memoryEpisode(loki(), c.req.param("episode_id") ?? ""); return e ? c.json(e) : c.json({ detail: "Episode not found" }, 404); },
|
|
18
|
+
"GET /api/memory/patterns": (c) => c.json(memoryPatterns(loki())),
|
|
19
|
+
"GET /api/memory/patterns/{pattern_id}": (c) => { const p = memoryPatterns(loki()).find((x) => x.id === c.req.param("pattern_id")); return p ? c.json(p) : c.json({ detail: "Pattern not found" }, 404); },
|
|
20
|
+
"GET /api/memory/skills": (c) => c.json(memorySkills(loki())),
|
|
21
|
+
"GET /api/memory/economics": (c) => c.json(memoryEconomics(loki())),
|
|
22
|
+
"GET /api/context": (c) => { const r = contextTracking(loki()); return c.json(r.body, r.status); },
|
|
23
|
+
"POST /api/focus": async (c) => {
|
|
24
|
+
if (!mutationAllowed(c)) return c.json({ detail: "loopback JSON requests only" }, 403);
|
|
25
|
+
const body = await c.req.json().catch(() => null);
|
|
26
|
+
const r = setFocus(repoDir, typeof body === "object" && body !== null ? (body as Record<string, unknown>).project_dir : undefined);
|
|
27
|
+
return r.ok ? c.json(focusInfo(repoDir)) : c.json({ detail: r.error }, 400);
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -8,11 +8,11 @@ export const LEGACY_ROUTES: readonly LegacyRoute[] = [
|
|
|
8
8
|
{ method: "GET", path: "/docs", action: "410", open: true, migrate: "" },
|
|
9
9
|
{ method: "GET", path: "/docs/oauth2-redirect", action: "410", open: true, migrate: "" },
|
|
10
10
|
{ method: "GET", path: "/redoc", action: "410", open: true, migrate: "" },
|
|
11
|
-
{ method: "POST", path: "/api/v2/tenants", action: "
|
|
12
|
-
{ method: "GET", path: "/api/v2/tenants", action: "
|
|
13
|
-
{ method: "GET", path: "/api/v2/tenants/{tenant_id}", action: "
|
|
11
|
+
{ method: "POST", path: "/api/v2/tenants", action: "410", open: false, migrate: "no tenants: local single user" },
|
|
12
|
+
{ method: "GET", path: "/api/v2/tenants", action: "410", open: false, migrate: "no tenants: local single user" },
|
|
13
|
+
{ method: "GET", path: "/api/v2/tenants/{tenant_id}", action: "410", open: false, migrate: "no tenants: local single user" },
|
|
14
14
|
{ method: "PUT", path: "/api/v2/tenants/{tenant_id}", action: "410", open: false, migrate: "" },
|
|
15
|
-
{ method: "DELETE", path: "/api/v2/tenants/{tenant_id}", action: "
|
|
15
|
+
{ method: "DELETE", path: "/api/v2/tenants/{tenant_id}", action: "410", open: false, migrate: "no tenants: local single user" },
|
|
16
16
|
{ method: "GET", path: "/api/v2/tenants/{tenant_id}/projects", action: "410", open: false, migrate: "" },
|
|
17
17
|
{ method: "POST", path: "/api/v2/runs", action: "501", open: false, migrate: "" },
|
|
18
18
|
{ method: "GET", path: "/api/v2/runs", action: "map", open: false, migrate: "/v1/runs" },
|
|
@@ -20,12 +20,12 @@ export const LEGACY_ROUTES: readonly LegacyRoute[] = [
|
|
|
20
20
|
{ method: "POST", path: "/api/v2/runs/{run_id}/cancel", action: "501", open: false, migrate: "" },
|
|
21
21
|
{ method: "POST", path: "/api/v2/runs/{run_id}/replay", action: "501", open: false, migrate: "" },
|
|
22
22
|
{ method: "GET", path: "/api/v2/runs/{run_id}/timeline", action: "map", open: false, migrate: "/v1/runs/:id" },
|
|
23
|
-
{ method: "POST", path: "/api/v2/api-keys", action: "
|
|
24
|
-
{ method: "GET", path: "/api/v2/api-keys", action: "
|
|
23
|
+
{ method: "POST", path: "/api/v2/api-keys", action: "410", open: false, migrate: "CP bearer token set by env (LOKI_CONTROL_TOKEN)" },
|
|
24
|
+
{ method: "GET", path: "/api/v2/api-keys", action: "410", open: false, migrate: "CP bearer token set by env (LOKI_CONTROL_TOKEN)" },
|
|
25
25
|
{ method: "GET", path: "/api/v2/api-keys/{identifier}", action: "410", open: false, migrate: "" },
|
|
26
26
|
{ method: "PUT", path: "/api/v2/api-keys/{identifier}", action: "410", open: false, migrate: "" },
|
|
27
|
-
{ method: "DELETE", path: "/api/v2/api-keys/{identifier}", action: "
|
|
28
|
-
{ method: "POST", path: "/api/v2/api-keys/{identifier}/rotate", action: "
|
|
27
|
+
{ method: "DELETE", path: "/api/v2/api-keys/{identifier}", action: "410", open: false, migrate: "CP bearer token set by env (LOKI_CONTROL_TOKEN)" },
|
|
28
|
+
{ method: "POST", path: "/api/v2/api-keys/{identifier}/rotate", action: "410", open: false, migrate: "CP bearer token set by env (LOKI_CONTROL_TOKEN)" },
|
|
29
29
|
{ method: "GET", path: "/api/v2/policies", action: "410", open: false, migrate: "" },
|
|
30
30
|
{ method: "PUT", path: "/api/v2/policies", action: "410", open: false, migrate: "" },
|
|
31
31
|
{ method: "POST", path: "/api/v2/policies/evaluate", action: "410", open: false, migrate: "" },
|
|
@@ -61,31 +61,31 @@ export const LEGACY_ROUTES: readonly LegacyRoute[] = [
|
|
|
61
61
|
{ method: "GET", path: "/api/projects/{project_id}", action: "501", open: false, migrate: "" },
|
|
62
62
|
{ method: "PUT", path: "/api/projects/{project_id}", action: "501", open: false, migrate: "" },
|
|
63
63
|
{ method: "DELETE", path: "/api/projects/{project_id}", action: "501", open: false, migrate: "" },
|
|
64
|
-
{ method: "GET", path: "/api/tasks", action: "
|
|
65
|
-
{ method: "POST", path: "/api/tasks", action: "
|
|
66
|
-
{ method: "GET", path: "/api/tasks/{task_id}", action: "
|
|
67
|
-
{ method: "PUT", path: "/api/tasks/{task_id}", action: "
|
|
68
|
-
{ method: "DELETE", path: "/api/tasks/{task_id}", action: "
|
|
69
|
-
{ method: "POST", path: "/api/tasks/{task_id}/move", action: "
|
|
70
|
-
{ method: "WS", path: "/ws", action: "
|
|
64
|
+
{ method: "GET", path: "/api/tasks", action: "map", open: false, migrate: "/v1/tasks" },
|
|
65
|
+
{ method: "POST", path: "/api/tasks", action: "410", open: false, migrate: "no v10 task queue; read-only GET /v1/tasks" },
|
|
66
|
+
{ method: "GET", path: "/api/tasks/{task_id}", action: "410", open: false, migrate: "no v10 task queue; read-only GET /v1/tasks" },
|
|
67
|
+
{ method: "PUT", path: "/api/tasks/{task_id}", action: "410", open: false, migrate: "no v10 task queue; read-only GET /v1/tasks" },
|
|
68
|
+
{ method: "DELETE", path: "/api/tasks/{task_id}", action: "410", open: false, migrate: "no v10 task queue; read-only GET /v1/tasks" },
|
|
69
|
+
{ method: "POST", path: "/api/tasks/{task_id}/move", action: "410", open: false, migrate: "no v10 task queue; read-only GET /v1/tasks" },
|
|
70
|
+
{ method: "WS", path: "/ws", action: "410", open: true, migrate: "SSE at GET /v1/stream" },
|
|
71
71
|
{ method: "GET", path: "/api/registry/projects", action: "410", open: false, migrate: "" },
|
|
72
|
-
{ method: "POST", path: "/api/registry/projects", action: "
|
|
72
|
+
{ method: "POST", path: "/api/registry/projects", action: "410", open: false, migrate: "repos are discovered locally; GET /v1/repos" },
|
|
73
73
|
{ method: "GET", path: "/api/registry/projects/{identifier}", action: "410", open: false, migrate: "" },
|
|
74
74
|
{ method: "DELETE", path: "/api/registry/projects/{identifier}", action: "410", open: false, migrate: "" },
|
|
75
75
|
{ method: "GET", path: "/api/registry/projects/{identifier}/health", action: "410", open: false, migrate: "" },
|
|
76
76
|
{ method: "POST", path: "/api/registry/projects/{identifier}/access", action: "410", open: false, migrate: "" },
|
|
77
77
|
{ method: "GET", path: "/api/registry/discover", action: "410", open: false, migrate: "" },
|
|
78
|
-
{ method: "POST", path: "/api/registry/sync", action: "
|
|
78
|
+
{ method: "POST", path: "/api/registry/sync", action: "410", open: false, migrate: "repos are discovered locally; GET /v1/repos" },
|
|
79
79
|
{ method: "GET", path: "/api/registry/tasks", action: "410", open: false, migrate: "" },
|
|
80
80
|
{ method: "GET", path: "/api/registry/learnings", action: "410", open: false, migrate: "" },
|
|
81
81
|
{ method: "GET", path: "/api/fleet/runs", action: "map", open: false, migrate: "/v1/fleet/runs" },
|
|
82
82
|
{ method: "GET", path: "/api/fleet/summary", action: "map", open: false, migrate: "/v1/fleet/summary" },
|
|
83
83
|
{ method: "GET", path: "/api/fleet/runs/{identifier}", action: "410", open: false, migrate: "" },
|
|
84
|
-
{ method: "POST", path: "/api/focus", action: "
|
|
84
|
+
{ method: "POST", path: "/api/focus", action: "map", open: false, migrate: "/v1/focus" },
|
|
85
85
|
{ method: "GET", path: "/api/focus", action: "410", open: false, migrate: "" },
|
|
86
86
|
{ method: "DELETE", path: "/api/focus", action: "410", open: false, migrate: "" },
|
|
87
87
|
{ method: "GET", path: "/api/session/model", action: "410", open: false, migrate: "" },
|
|
88
|
-
{ method: "POST", path: "/api/session/model", action: "
|
|
88
|
+
{ 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)" },
|
|
89
89
|
{ method: "GET", path: "/api/running-projects", action: "410", open: false, migrate: "" },
|
|
90
90
|
{ method: "POST", path: "/api/control/start", action: "501", open: false, migrate: "" },
|
|
91
91
|
{ method: "GET", path: "/api/control/builds/{execution_id}", action: "410", open: false, migrate: "" },
|
|
@@ -99,19 +99,19 @@ export const LEGACY_ROUTES: readonly LegacyRoute[] = [
|
|
|
99
99
|
{ method: "POST", path: "/api/enterprise/tokens", action: "410", open: false, migrate: "" },
|
|
100
100
|
{ method: "GET", path: "/api/enterprise/tokens", action: "410", open: false, migrate: "" },
|
|
101
101
|
{ method: "DELETE", path: "/api/enterprise/tokens/{identifier}", action: "410", open: false, migrate: "" },
|
|
102
|
-
{ method: "GET", path: "/api/enterprise/audit", action: "
|
|
103
|
-
{ method: "GET", path: "/api/enterprise/audit/summary", action: "
|
|
102
|
+
{ method: "GET", path: "/api/enterprise/audit", action: "map", open: false, migrate: "/v1/audit" },
|
|
103
|
+
{ method: "GET", path: "/api/enterprise/audit/summary", action: "map", open: false, migrate: "/v1/audit/summary" },
|
|
104
104
|
{ method: "GET", path: "/api/compliance", action: "410", open: false, migrate: "" },
|
|
105
105
|
{ method: "GET", path: "/api/memory/summary", action: "410", open: false, migrate: "" },
|
|
106
|
-
{ method: "GET", path: "/api/memory/episodes", action: "
|
|
107
|
-
{ method: "GET", path: "/api/memory/episodes/{episode_id}", action: "
|
|
108
|
-
{ method: "GET", path: "/api/memory/patterns", action: "
|
|
109
|
-
{ method: "GET", path: "/api/memory/patterns/{pattern_id}", action: "
|
|
110
|
-
{ method: "GET", path: "/api/memory/skills", action: "
|
|
106
|
+
{ method: "GET", path: "/api/memory/episodes", action: "map", open: false, migrate: "/v1/memory/episodes" },
|
|
107
|
+
{ method: "GET", path: "/api/memory/episodes/{episode_id}", action: "map", open: false, migrate: "/v1/memory/episodes/{id}" },
|
|
108
|
+
{ method: "GET", path: "/api/memory/patterns", action: "map", open: false, migrate: "/v1/memory/patterns" },
|
|
109
|
+
{ method: "GET", path: "/api/memory/patterns/{pattern_id}", action: "map", open: false, migrate: "/v1/memory/patterns/{id}" },
|
|
110
|
+
{ method: "GET", path: "/api/memory/skills", action: "map", open: false, migrate: "/v1/memory/skills" },
|
|
111
111
|
{ method: "GET", path: "/api/memory/skills/{skill_id}", action: "410", open: false, migrate: "" },
|
|
112
|
-
{ method: "GET", path: "/api/memory/economics", action: "
|
|
113
|
-
{ method: "POST", path: "/api/memory/consolidate", action: "
|
|
114
|
-
{ method: "POST", path: "/api/memory/retrieve", action: "
|
|
112
|
+
{ method: "GET", path: "/api/memory/economics", action: "map", open: false, migrate: "/v1/memory/economics" },
|
|
113
|
+
{ method: "POST", path: "/api/memory/consolidate", action: "410", open: false, migrate: "read-only GET /v1/memory/*" },
|
|
114
|
+
{ method: "POST", path: "/api/memory/retrieve", action: "410", open: false, migrate: "read-only GET /v1/memory/*" },
|
|
115
115
|
{ method: "GET", path: "/api/memory/index", action: "410", open: false, migrate: "" },
|
|
116
116
|
{ method: "GET", path: "/api/memory/timeline", action: "410", open: false, migrate: "" },
|
|
117
117
|
{ method: "GET", path: "/api/memory/files", action: "410", open: false, migrate: "" },
|
|
@@ -122,41 +122,41 @@ export const LEGACY_ROUTES: readonly LegacyRoute[] = [
|
|
|
122
122
|
{ method: "GET", path: "/api/learning/trends", action: "410", open: false, migrate: "" },
|
|
123
123
|
{ method: "GET", path: "/api/learning/signals", action: "410", open: false, migrate: "" },
|
|
124
124
|
{ method: "GET", path: "/api/learning/aggregation", action: "410", open: false, migrate: "" },
|
|
125
|
-
{ method: "POST", path: "/api/learning/aggregate", action: "
|
|
125
|
+
{ method: "POST", path: "/api/learning/aggregate", action: "410", open: false, migrate: "read-only GET /v1/memory/patterns" },
|
|
126
126
|
{ method: "GET", path: "/api/learning/preferences", action: "410", open: false, migrate: "" },
|
|
127
127
|
{ method: "GET", path: "/api/learning/errors", action: "410", open: false, migrate: "" },
|
|
128
128
|
{ method: "GET", path: "/api/learning/success", action: "410", open: false, migrate: "" },
|
|
129
129
|
{ method: "GET", path: "/api/learning/tools", action: "410", open: false, migrate: "" },
|
|
130
|
-
{ method: "POST", path: "/api/control/pause", action: "
|
|
131
|
-
{ method: "POST", path: "/api/control/resume", action: "
|
|
132
|
-
{ method: "POST", path: "/api/control/stop", action: "
|
|
130
|
+
{ method: "POST", path: "/api/control/pause", action: "map", open: false, migrate: "/v1/control/pause" },
|
|
131
|
+
{ method: "POST", path: "/api/control/resume", action: "map", open: false, migrate: "/v1/control/resume" },
|
|
132
|
+
{ method: "POST", path: "/api/control/stop", action: "map", open: false, migrate: "/v1/control/stop" },
|
|
133
133
|
{ method: "GET", path: "/api/cost", action: "map", open: false, migrate: "/v1/cost/snapshot" },
|
|
134
134
|
{ method: "GET", path: "/api/budget", action: "501", open: false, migrate: "" },
|
|
135
135
|
{ method: "GET", path: "/api/cost/timeline", action: "map", open: false, migrate: "/v1/cost/timeline" },
|
|
136
136
|
{ method: "GET", path: "/api/trust/trajectory", action: "410", open: false, migrate: "" },
|
|
137
137
|
{ method: "GET", path: "/api/gate-policy", action: "410", open: false, migrate: "" },
|
|
138
138
|
{ method: "GET", path: "/api/pricing", action: "410", open: false, migrate: "" },
|
|
139
|
-
{ method: "GET", path: "/api/council/state", action: "
|
|
140
|
-
{ method: "GET", path: "/api/council/verdicts", action: "
|
|
141
|
-
{ method: "GET", path: "/api/council/convergence", action: "
|
|
142
|
-
{ method: "GET", path: "/api/council/report", action: "
|
|
143
|
-
{ method: "POST", path: "/api/council/force-review", action: "
|
|
144
|
-
{ method: "GET", path: "/api/council/transcripts", action: "
|
|
139
|
+
{ method: "GET", path: "/api/council/state", action: "map", open: false, migrate: "/v1/council/state" },
|
|
140
|
+
{ method: "GET", path: "/api/council/verdicts", action: "map", open: false, migrate: "/v1/council/verdicts" },
|
|
141
|
+
{ method: "GET", path: "/api/council/convergence", action: "map", open: false, migrate: "/v1/council/convergence" },
|
|
142
|
+
{ method: "GET", path: "/api/council/report", action: "map", open: false, migrate: "/v1/council/report" },
|
|
143
|
+
{ method: "POST", path: "/api/council/force-review", action: "map", open: false, migrate: "/v1/control/council-review" },
|
|
144
|
+
{ method: "GET", path: "/api/council/transcripts", action: "map", open: false, migrate: "/v1/council/transcripts" },
|
|
145
145
|
{ method: "GET", path: "/api/council/transcripts/{iteration_id}", action: "410", open: false, migrate: "" },
|
|
146
|
-
{ method: "GET", path: "/api/context", action: "
|
|
146
|
+
{ method: "GET", path: "/api/context", action: "map", open: false, migrate: "/v1/context" },
|
|
147
147
|
{ method: "GET", path: "/api/notifications", action: "410", open: false, migrate: "" },
|
|
148
148
|
{ method: "GET", path: "/api/notifications/triggers", action: "410", open: false, migrate: "" },
|
|
149
|
-
{ method: "PUT", path: "/api/notifications/triggers", action: "
|
|
149
|
+
{ method: "PUT", path: "/api/notifications/triggers", action: "410", open: false, migrate: "GET /v1/notifications (derived from events)" },
|
|
150
150
|
{ method: "POST", path: "/api/notifications/{notification_id}/acknowledge", action: "410", open: false, migrate: "" },
|
|
151
151
|
{ method: "POST", path: "/api/notifications/{notification_id}/unacknowledge", action: "410", open: false, migrate: "" },
|
|
152
|
-
{ method: "GET", path: "/api/checkpoints", action: "
|
|
153
|
-
{ method: "GET", path: "/api/checkpoints/{checkpoint_id}", action: "
|
|
154
|
-
{ method: "POST", path: "/api/checkpoints", action: "
|
|
155
|
-
{ method: "POST", path: "/api/checkpoints/{checkpoint_id}/rollback", action: "
|
|
152
|
+
{ method: "GET", path: "/api/checkpoints", action: "map", open: false, migrate: "/v1/checkpoints" },
|
|
153
|
+
{ method: "GET", path: "/api/checkpoints/{checkpoint_id}", action: "map", open: false, migrate: "/v1/checkpoints/{id}" },
|
|
154
|
+
{ method: "POST", path: "/api/checkpoints", action: "map", open: false, migrate: "/v1/checkpoints" },
|
|
155
|
+
{ method: "POST", path: "/api/checkpoints/{checkpoint_id}/rollback", action: "map", open: false, migrate: "/v1/checkpoints/{id}/rollback" },
|
|
156
156
|
{ method: "GET", path: "/api/agents", action: "410", open: false, migrate: "" },
|
|
157
|
-
{ method: "POST", path: "/api/agents/{agent_id}/kill", action: "
|
|
158
|
-
{ method: "POST", path: "/api/agents/{agent_id}/pause", action: "
|
|
159
|
-
{ method: "POST", path: "/api/agents/{agent_id}/resume", action: "
|
|
157
|
+
{ method: "POST", path: "/api/agents/{agent_id}/kill", action: "410", open: false, migrate: "POST /v1/control/stop" },
|
|
158
|
+
{ method: "POST", path: "/api/agents/{agent_id}/pause", action: "410", open: false, migrate: "POST /v1/control/stop (v10 has no pause)" },
|
|
159
|
+
{ method: "POST", path: "/api/agents/{agent_id}/resume", action: "410", open: false, migrate: "POST /v1/control/resume" },
|
|
160
160
|
{ method: "GET", path: "/api/logs", action: "410", open: false, migrate: "" },
|
|
161
161
|
{ method: "POST", path: "/api/collab/join", action: "410", open: false, migrate: "" },
|
|
162
162
|
{ method: "POST", path: "/api/collab/leave", action: "410", open: false, migrate: "" },
|
|
@@ -185,19 +185,19 @@ export const LEGACY_ROUTES: readonly LegacyRoute[] = [
|
|
|
185
185
|
{ method: "GET", path: "/api/checklist/summary", action: "410", open: false, migrate: "" },
|
|
186
186
|
{ method: "GET", path: "/api/prd-observations", action: "410", open: false, migrate: "" },
|
|
187
187
|
{ method: "GET", path: "/api/checklist/waivers", action: "410", open: false, migrate: "" },
|
|
188
|
-
{ method: "POST", path: "/api/checklist/waivers", action: "
|
|
189
|
-
{ method: "DELETE", path: "/api/checklist/waivers/{item_id}", action: "
|
|
188
|
+
{ method: "POST", path: "/api/checklist/waivers", action: "410", open: false, migrate: "plan.json and receipt claims in GET /v1/runs/:id" },
|
|
189
|
+
{ method: "DELETE", path: "/api/checklist/waivers/{item_id}", action: "410", open: false, migrate: "plan.json and receipt claims in GET /v1/runs/:id" },
|
|
190
190
|
{ method: "GET", path: "/api/council/gate", action: "501", open: false, migrate: "" },
|
|
191
191
|
{ method: "GET", path: "/api/app-runner/status", action: "410", open: false, migrate: "" },
|
|
192
192
|
{ method: "GET", path: "/api/app-runner/logs", action: "410", open: false, migrate: "" },
|
|
193
193
|
{ method: "GET", path: "/api/app-runner/errors", action: "410", open: false, migrate: "" },
|
|
194
|
-
{ method: "POST", path: "/api/control/app-restart", action: "
|
|
195
|
-
{ method: "POST", path: "/api/control/app-stop", action: "
|
|
194
|
+
{ method: "POST", path: "/api/control/app-restart", action: "410", open: false, migrate: "evidence in GET /v1/runs/:source/:run/artifact/*" },
|
|
195
|
+
{ method: "POST", path: "/api/control/app-stop", action: "410", open: false, migrate: "evidence in GET /v1/runs/:source/:run/artifact/*" },
|
|
196
196
|
{ method: "GET", path: "/api/playwright/results", action: "410", open: false, migrate: "" },
|
|
197
197
|
{ method: "GET", path: "/api/playwright/screenshot", action: "410", open: false, migrate: "" },
|
|
198
198
|
{ method: "GET", path: "/api/failures", action: "410", open: false, migrate: "" },
|
|
199
199
|
{ method: "GET", path: "/api/prompt-versions", action: "410", open: false, migrate: "" },
|
|
200
|
-
{ method: "POST", path: "/api/prompt-optimize", action: "
|
|
200
|
+
{ method: "POST", path: "/api/prompt-optimize", action: "410", open: false, migrate: "verify stage: POST /v1/runs/:source/:run/verify" },
|
|
201
201
|
{ method: "GET", path: "/api/activity", action: "501", open: false, migrate: "" },
|
|
202
202
|
{ method: "GET", path: "/api/session-diff", action: "410", open: false, migrate: "" },
|
|
203
203
|
{ method: "POST", path: "/api/activity", action: "410", open: false, migrate: "" },
|
|
@@ -206,8 +206,8 @@ export const LEGACY_ROUTES: readonly LegacyRoute[] = [
|
|
|
206
206
|
{ method: "GET", path: "/", action: "308", open: false, migrate: "/" },
|
|
207
207
|
{ method: "GET", path: "/api/quality-score", action: "410", open: false, migrate: "" },
|
|
208
208
|
{ method: "GET", path: "/api/quality-score/history", action: "410", open: false, migrate: "" },
|
|
209
|
-
{ method: "POST", path: "/api/quality-scan", action: "
|
|
210
|
-
{ method: "GET", path: "/api/quality-report", action: "
|
|
209
|
+
{ method: "POST", path: "/api/quality-scan", action: "410", open: false, migrate: "verify stage: POST /v1/runs/:source/:run/verify" },
|
|
210
|
+
{ method: "GET", path: "/api/quality-report", action: "410", open: false, migrate: "verify stage: POST /v1/runs/:source/:run/verify" },
|
|
211
211
|
{ method: "GET", path: "/api/migration/list", action: "501", open: false, migrate: "" },
|
|
212
212
|
{ method: "POST", path: "/api/migration/start", action: "410", open: false, migrate: "" },
|
|
213
213
|
{ method: "GET", path: "/api/migration/{migration_id}/status", action: "501", open: false, migrate: "" },
|
|
@@ -216,8 +216,8 @@ export const LEGACY_ROUTES: readonly LegacyRoute[] = [
|
|
|
216
216
|
{ method: "GET", path: "/api/migration/{migration_id}/seams", action: "410", open: false, migrate: "" },
|
|
217
217
|
{ method: "POST", path: "/api/migration/{migration_id}/advance", action: "410", open: false, migrate: "" },
|
|
218
218
|
{ method: "POST", path: "/api/migration/{migration_id}/start-phase", action: "410", open: false, migrate: "" },
|
|
219
|
-
{ method: "GET", path: "/api/managed/events", action: "
|
|
220
|
-
{ method: "GET", path: "/api/managed/status", action: "
|
|
219
|
+
{ method: "GET", path: "/api/managed/events", action: "410", open: false, migrate: "managed memory dropped; GET /v1/runs/:source/:run/events" },
|
|
220
|
+
{ method: "GET", path: "/api/managed/status", action: "410", open: false, migrate: "managed memory dropped; GET /v1/runs" },
|
|
221
221
|
{ method: "GET", path: "/api/managed/memory_versions/{memory_id}", action: "410", open: false, migrate: "" },
|
|
222
222
|
{ method: "GET", path: "/api/findings/{iteration}", action: "410", open: false, migrate: "" },
|
|
223
223
|
{ method: "GET", path: "/api/quality/architecture", action: "410", open: false, migrate: "" },
|
|
@@ -233,5 +233,5 @@ export const LEGACY_ROUTES: readonly LegacyRoute[] = [
|
|
|
233
233
|
{ method: "GET", path: "/api/spec/history", action: "410", open: false, migrate: "" },
|
|
234
234
|
{ method: "GET", path: "/api/wiki", action: "410", open: false, migrate: "" },
|
|
235
235
|
{ method: "GET", path: "/api/wiki/{section}", action: "410", open: false, migrate: "" },
|
|
236
|
-
{ method: "POST", path: "/api/wiki/ask", action: "
|
|
236
|
+
{ method: "POST", path: "/api/wiki/ask", action: "410", open: false, migrate: "wiki deleted; no replacement" },
|
|
237
237
|
];
|
|
@@ -10,7 +10,10 @@ import { audit, runs } from "../../db/schema.ts";
|
|
|
10
10
|
import { peerIsLoopback, tokenMatches } from "../auth.ts";
|
|
11
11
|
import { liveInfo, listRuns, loadEvents, runDetail } from "../runs.ts";
|
|
12
12
|
import { LEGACY_ROUTES, type LegacyRoute } from "./routes.ts";
|
|
13
|
+
import { auditCheckpointMapped } from "./routes-audit.ts";
|
|
14
|
+
import { memoryMapped } from "./routes-memory.ts";
|
|
13
15
|
import { metricsMapped } from "./routes-metrics.ts";
|
|
16
|
+
import { controlMapped } from "./routes-control.ts";
|
|
14
17
|
|
|
15
18
|
export interface LegacyShimOpts {
|
|
16
19
|
db: Db;
|
|
@@ -102,7 +105,7 @@ export function legacyShim(opts: LegacyShimOpts) {
|
|
|
102
105
|
return { rows: queryAudit(db, { start_date: q.start_date, end_date: q.end_date, action: q.action, limit, offset }), limit, offset };
|
|
103
106
|
};
|
|
104
107
|
|
|
105
|
-
const mapped: Record<string, (c: Context) => Response
|
|
108
|
+
const mapped: Record<string, (c: Context) => Response | Promise<Response>> = {
|
|
106
109
|
"GET /.well-known/agent.json": (c) => c.json({
|
|
107
110
|
name: "Loki Mode", version, url: "https://www.autonomi.dev/",
|
|
108
111
|
description: "Multi-agent autonomous system by Autonomi. Served by the Loki Control Plane.",
|
|
@@ -152,6 +155,9 @@ export function legacyShim(opts: LegacyShimOpts) {
|
|
|
152
155
|
},
|
|
153
156
|
"GET /api/v2/audit": (c) => { const r = auditList(c); return r.err ?? c.json(r.rows); },
|
|
154
157
|
...metricsMapped(opts.repoDir ?? process.cwd()),
|
|
158
|
+
...auditCheckpointMapped(db, opts.repoDir ?? process.cwd(), auditList),
|
|
159
|
+
...memoryMapped(opts.repoDir ?? process.cwd()),
|
|
160
|
+
...controlMapped(db, opts.repoDir ?? process.cwd()),
|
|
155
161
|
};
|
|
156
162
|
|
|
157
163
|
// /lab/api/* is data, not a page: 501 JSON on every method (a 308 to the SPA would hand a client HTML). Registered before the /lab mount.
|
|
@@ -165,13 +171,13 @@ export function legacyShim(opts: LegacyShimOpts) {
|
|
|
165
171
|
// On the CP app itself "/" is the UI already; redirecting it would loop. Only a separately mounted shim (uiBase set) redirects it.
|
|
166
172
|
if (r.action === "308" && uiBase === "" && r.path === "/") continue;
|
|
167
173
|
const hp = toHono(r.path);
|
|
168
|
-
const handle = (c: Context): Response => {
|
|
174
|
+
const handle = (c: Context): Response | Promise<Response> => {
|
|
169
175
|
if (!r.open) { const d = denied(c); if (d) return d; }
|
|
170
176
|
once(r);
|
|
171
177
|
const body = { legacy_route: r.path, method: r.method };
|
|
172
|
-
if (r.action === "map") return (mapped[`${r.method} ${r.path}`] as (c: Context) => Response)(c);
|
|
178
|
+
if (r.action === "map") return (mapped[`${r.method} ${r.path}`] as (c: Context) => Response | Promise<Response>)(c);
|
|
173
179
|
if (r.action === "308") return c.redirect(`${uiBase}/`, 308);
|
|
174
|
-
if (r.action === "410") return c.json({ error: "gone", detail: "This legacy dashboard route was retired; nothing consumes it.", ...body }, 410);
|
|
180
|
+
if (r.action === "410") return c.json({ error: "gone", detail: "This legacy dashboard route was retired; nothing consumes it.", ...(r.migrate ? { replacement: r.migrate } : {}), ...body }, 410);
|
|
175
181
|
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);
|
|
176
182
|
};
|
|
177
183
|
const m = r.method === "WS" || r.method === "MOUNT" ? "GET" : r.method;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// CPE24-P2: checkpoints and audit summary, ported from dashboard/server.py (list/get/create/rollback checkpoint, get_audit_summary).
|
|
2
|
+
// Same store as legacy and run.sh: <repo>/.loki/state/checkpoints/{index.jsonl,<id>/metadata.json}. Audit data stays read-only: nothing here
|
|
3
|
+
// writes, edits or deletes an audit row or the hash chain. Create and rollback are state-changing, so they sit on `act` (loopback peer plus a
|
|
4
|
+
// loopback Host plus JSON, ctx.local) like every other POST route, and each is recorded in the actions log. Unmeasured values are null, never "" or 0.
|
|
5
|
+
import { execFileSync } from "node:child_process";
|
|
6
|
+
import { appendFileSync, copyFileSync, cpSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { join, sep } from "node:path";
|
|
8
|
+
import { gte, sql } from "drizzle-orm";
|
|
9
|
+
import type { Db } from "../../db/migrate.ts";
|
|
10
|
+
import { audit as auditTable } from "../../db/schema.ts";
|
|
11
|
+
import { audit } from "../audit.ts";
|
|
12
|
+
import { probeAllowed, type RouteCtx } from "./index.ts";
|
|
13
|
+
import { NOT_MEASURED } from "./cost_ledger.ts";
|
|
14
|
+
|
|
15
|
+
type Rec = Record<string, unknown>;
|
|
16
|
+
const isObj = (v: unknown): v is Rec => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
17
|
+
const str = (v: unknown): string | null => (typeof v === "string" && v !== "" ? v : null);
|
|
18
|
+
const SAFE_ID = /^[A-Za-z0-9_-]{1,128}$/;
|
|
19
|
+
const MAX_CHECKPOINTS = 50;
|
|
20
|
+
|
|
21
|
+
export const validCheckpointId = (id: string) => SAFE_ID.test(id);
|
|
22
|
+
const storeOf = (repoDir: string) => join(repoDir, ".loki", "state", "checkpoints");
|
|
23
|
+
const lokiOf = (repoDir: string) => join(repoDir, ".loki");
|
|
24
|
+
const readObj = (f: string): Rec | null => { try { const d = JSON.parse(readFileSync(f, "utf8")); return isObj(d) ? d : null; } catch { return null; } };
|
|
25
|
+
const stamp = (d: Date) => d.toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "").replace("T", "-");
|
|
26
|
+
|
|
27
|
+
export function listCheckpoints(repoDir: string, limit = 20) {
|
|
28
|
+
const store = storeOf(repoDir);
|
|
29
|
+
let lines: string[] = [];
|
|
30
|
+
try { lines = readFileSync(join(store, "index.jsonl"), "utf8").split("\n"); } catch { return []; }
|
|
31
|
+
const out: Rec[] = [];
|
|
32
|
+
for (const line of lines) {
|
|
33
|
+
if (!line.trim()) continue;
|
|
34
|
+
let raw: unknown;
|
|
35
|
+
try { raw = JSON.parse(line); } catch { continue; }
|
|
36
|
+
if (!isObj(raw)) continue;
|
|
37
|
+
const id = String(raw.id ?? "");
|
|
38
|
+
if (!validCheckpointId(id)) continue;
|
|
39
|
+
const it = raw.iter ?? raw.iteration;
|
|
40
|
+
const cp: Rec = {
|
|
41
|
+
id, created_at: str(raw.ts) ?? str(raw.created_at) ?? str(raw.timestamp), git_sha: str(raw.sha) ?? str(raw.git_sha),
|
|
42
|
+
message: str(raw.task) ?? str(raw.message) ?? str(raw.task_description), iteration: Number.isInteger(it) ? it : null,
|
|
43
|
+
};
|
|
44
|
+
const meta = readObj(join(store, id, "metadata.json"));
|
|
45
|
+
if (meta) {
|
|
46
|
+
cp.git_branch = str(meta.git_branch); cp.provider = str(meta.provider); cp.phase = str(meta.phase);
|
|
47
|
+
cp.message ??= str(meta.task_description); cp.git_sha ??= str(meta.git_sha); cp.created_at ??= str(meta.timestamp);
|
|
48
|
+
cp.files_count = countFiles(join(store, id));
|
|
49
|
+
}
|
|
50
|
+
out.push(cp);
|
|
51
|
+
}
|
|
52
|
+
return out.reverse().slice(0, limit);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function countFiles(dir: string): number {
|
|
56
|
+
let n = 0;
|
|
57
|
+
const walk = (d: string, top: boolean) => {
|
|
58
|
+
for (const e of readdirSync(d, { withFileTypes: true })) {
|
|
59
|
+
if (e.isDirectory()) walk(join(d, e.name), false);
|
|
60
|
+
else if (e.isFile() && !(top && e.name === "metadata.json")) n++;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
try { walk(dir, true); } catch { /* unreadable */ }
|
|
64
|
+
return n;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function getCheckpoint(repoDir: string, id: string): Rec | null {
|
|
68
|
+
return validCheckpointId(id) ? readObj(join(storeOf(repoDir), id, "metadata.json")) : null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const gitHead = (repoDir: string): string | null => {
|
|
72
|
+
try { return execFileSync("git", ["rev-parse", "HEAD"], { cwd: repoDir, timeout: 5000, stdio: ["ignore", "pipe", "ignore"] }).toString().trim() || null; } catch { return null; }
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export function createCheckpoint(repoDir: string, message: string, now = new Date()) {
|
|
76
|
+
const store = storeOf(repoDir), loki = lokiOf(repoDir);
|
|
77
|
+
mkdirSync(store, { recursive: true });
|
|
78
|
+
const id = `chk-${stamp(now)}`, dir = join(store, id);
|
|
79
|
+
mkdirSync(dir, { recursive: true });
|
|
80
|
+
for (const f of ["dashboard-state.json", "session.json"]) { try { if (existsSync(join(loki, f))) copyFileSync(join(loki, f), join(dir, f)); } catch { /* skip */ } }
|
|
81
|
+
try { if (existsSync(join(loki, "queue"))) cpSync(join(loki, "queue"), join(dir, "queue"), { recursive: true }); } catch { /* skip */ }
|
|
82
|
+
const meta = { id, created_at: now.toISOString(), git_sha: gitHead(repoDir), message: message || null, files: readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile()).map((e) => e.name) };
|
|
83
|
+
writeFileSync(join(dir, "metadata.json"), JSON.stringify(meta, null, 2));
|
|
84
|
+
appendFileSync(join(store, "index.jsonl"), `${JSON.stringify(meta)}\n`);
|
|
85
|
+
// Retention: only chk- directories are ever pruned, never a rollback snapshot or a foreign directory.
|
|
86
|
+
const mine = readdirSync(store, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name.startsWith("chk-")).map((e) => e.name).sort();
|
|
87
|
+
while (mine.length > MAX_CHECKPOINTS) rmSync(join(store, mine.shift() as string), { recursive: true, force: true });
|
|
88
|
+
return meta;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Restore .loki/ from a checkpoint after a forced pre-rollback snapshot (so the rollback can itself be undone). Never replaces .loki/state wholesale: the store lives in it. */
|
|
92
|
+
export function rollbackCheckpoint(repoDir: string, id: string, now = new Date()) {
|
|
93
|
+
const store = storeOf(repoDir), loki = lokiOf(repoDir);
|
|
94
|
+
let real = "", realStore = "";
|
|
95
|
+
try { real = realpathSync(join(store, id)); realStore = realpathSync(store); } catch { return null; }
|
|
96
|
+
if (!real.startsWith(`${realStore}${sep}`) || !statSync(real).isDirectory()) return null;
|
|
97
|
+
const preId = `rb-pre-${stamp(now)}`, pre = join(store, preId);
|
|
98
|
+
mkdirSync(pre, { recursive: true });
|
|
99
|
+
const notStore = (src: string) => src !== store; // state/ holds the store itself; never copy it into itself
|
|
100
|
+
for (const n of ["session.json", "dashboard-state.json", "CONTINUITY.md", "autonomy-state.json"]) { try { if (existsSync(join(loki, n))) copyFileSync(join(loki, n), join(pre, n)); } catch { /* skip */ } }
|
|
101
|
+
for (const d of ["state", "queue"]) { try { if (existsSync(join(loki, d))) cpSync(join(loki, d), join(pre, d), { recursive: true, filter: notStore }); } catch { /* skip */ } }
|
|
102
|
+
const preMeta = { id: preId, created_at: now.toISOString(), message: `pre-rollback snapshot (before restoring ${id})`, created_by: "control plane rollback" };
|
|
103
|
+
writeFileSync(join(pre, "metadata.json"), JSON.stringify(preMeta, null, 2));
|
|
104
|
+
appendFileSync(join(store, "index.jsonl"), `${JSON.stringify(preMeta)}\n`);
|
|
105
|
+
let restored = 0;
|
|
106
|
+
const errors: string[] = [];
|
|
107
|
+
for (const e of readdirSync(real, { withFileTypes: true })) {
|
|
108
|
+
if (e.name === "metadata.json" || e.name === "worktree-snapshot.txt") continue;
|
|
109
|
+
const src = join(real, e.name);
|
|
110
|
+
try {
|
|
111
|
+
if (lstatSync(src).isSymbolicLink()) { errors.push(`${e.name}: symlink skipped`); continue; }
|
|
112
|
+
cpSync(src, join(loki, e.name), { recursive: true, filter: (s) => s !== join(real, "state", "checkpoints") });
|
|
113
|
+
restored++;
|
|
114
|
+
} catch (err) { errors.push(`${e.name}: ${(err as Error).message}`); }
|
|
115
|
+
}
|
|
116
|
+
return { id, restored, pre_rollback_snapshot: preId, errors };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Read-only summary of the operator audit table over the last `days`. Fields the table does not record (success, resource type) are null. */
|
|
120
|
+
export function auditSummary(db: Db, days: number, now = Date.now()) {
|
|
121
|
+
const since = new Date(now - days * 86_400_000).toISOString();
|
|
122
|
+
const where = gte(auditTable.ts, since);
|
|
123
|
+
const total = db.select({ n: sql<number>`count(*)` }).from(auditTable).where(where).get()?.n ?? 0;
|
|
124
|
+
const by = (col: typeof auditTable.action | typeof auditTable.actor) => Object.fromEntries(
|
|
125
|
+
db.select({ k: col, n: sql<number>`count(*)` }).from(auditTable).where(where).groupBy(col).all().map((r) => [r.k, r.n]));
|
|
126
|
+
return {
|
|
127
|
+
period_days: days, total_events: total, by_action: by(auditTable.action), by_user: by(auditTable.actor),
|
|
128
|
+
successful_events: null, failed_events: null, by_resource_type: null, recent_failures: null, not_measured: NOT_MEASURED,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export const parseDays = (v: string | undefined) => { const n = Number.parseInt(v ?? "7", 10); return Number.isFinite(n) && n >= 1 && n <= 3650 ? n : 7; };
|
|
133
|
+
export const parseLimit = (v: string | undefined) => { const n = Number.parseInt(v ?? "20", 10); return Number.isFinite(n) ? Math.min(Math.max(n, 1), 200) : 20; };
|
|
134
|
+
|
|
135
|
+
export function mount(ctx: RouteCtx): void {
|
|
136
|
+
const deny = { error: "loopback only without a token" };
|
|
137
|
+
const { app, act, db, repoDir } = ctx;
|
|
138
|
+
app.get("/v1/checkpoints", (c) => (probeAllowed(ctx, c) ? c.json({ checkpoints: listCheckpoints(repoDir, parseLimit(c.req.query("limit"))) }) : c.json(deny, 403)));
|
|
139
|
+
app.get("/v1/checkpoints/:id", (c) => {
|
|
140
|
+
if (!probeAllowed(ctx, c)) return c.json(deny, 403);
|
|
141
|
+
const id = c.req.param("id");
|
|
142
|
+
if (!validCheckpointId(id)) return c.json({ error: "invalid checkpoint id" }, 400);
|
|
143
|
+
const m = getCheckpoint(repoDir, id);
|
|
144
|
+
return m ? c.json(m) : c.json({ error: "checkpoint not found" }, 404);
|
|
145
|
+
});
|
|
146
|
+
app.get("/v1/audit/summary", (c) => (probeAllowed(ctx, c) ? c.json(auditSummary(db, parseDays(c.req.query("days")))) : c.json(deny, 403)));
|
|
147
|
+
|
|
148
|
+
act.post("/v1/checkpoints", async (c) => {
|
|
149
|
+
if (!ctx.local(c)) return c.json({ error: "loopback JSON requests only" }, 403);
|
|
150
|
+
const body = (await c.req.json().catch(() => ({}))) as Rec;
|
|
151
|
+
const message = typeof body.message === "string" ? body.message : "";
|
|
152
|
+
if (message.length > 500) return c.json({ error: "message must be at most 500 characters" }, 400);
|
|
153
|
+
try {
|
|
154
|
+
const meta = createCheckpoint(repoDir, message);
|
|
155
|
+
audit(db, { kind: "checkpoint.create", target: String(meta.id), result: "ok" });
|
|
156
|
+
return c.json(meta, 201);
|
|
157
|
+
} catch (e) {
|
|
158
|
+
audit(db, { kind: "checkpoint.create", result: "error", detail: (e as Error).message });
|
|
159
|
+
return c.json({ error: "could not create the checkpoint" }, 500);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
act.post("/v1/checkpoints/:id/rollback", (c) => {
|
|
163
|
+
if (!ctx.local(c)) return c.json({ error: "loopback JSON requests only" }, 403);
|
|
164
|
+
const id = c.req.param("id");
|
|
165
|
+
if (!validCheckpointId(id)) { audit(db, { kind: "checkpoint.rollback", target: id.slice(0, 128), result: "refused", detail: "invalid id" }); return c.json({ error: "invalid checkpoint id" }, 400); }
|
|
166
|
+
const r = rollbackCheckpoint(repoDir, id);
|
|
167
|
+
if (!r) { audit(db, { kind: "checkpoint.rollback", target: id, result: "refused", detail: "not found" }); return c.json({ error: "checkpoint not found" }, 404); }
|
|
168
|
+
audit(db, { kind: "checkpoint.rollback", target: id, result: r.errors.length ? "partial" : "ok", detail: `restored ${r.restored}; pre-rollback snapshot ${r.pre_rollback_snapshot}` });
|
|
169
|
+
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).` });
|
|
170
|
+
});
|
|
171
|
+
}
|
|
@@ -7,12 +7,15 @@ import { isLoopbackHost } from "../auth.ts";
|
|
|
7
7
|
import { mountRepos } from "../repos.ts";
|
|
8
8
|
import { mount as artifacts } from "./artifacts.ts";
|
|
9
9
|
import { mount as audit } from "./audit.ts";
|
|
10
|
+
import { mount as checkpoints } from "./checkpoints.ts";
|
|
10
11
|
import { mount as config } from "./config.ts";
|
|
11
12
|
import { mount as control } from "./control.ts";
|
|
12
13
|
import { mount as cost } from "./cost.ts";
|
|
13
14
|
import { mount as costLedger } from "./cost_ledger.ts";
|
|
14
15
|
import { mount as fleet } from "./fleet.ts";
|
|
16
|
+
import { mount as memory } from "./memory.ts";
|
|
15
17
|
import { mount as metrics } from "./metrics.ts";
|
|
18
|
+
import { mount as sessionControl } from "./session_control.ts";
|
|
16
19
|
import { mount as integrations } from "./integrations.ts";
|
|
17
20
|
import { mount as mergeRisk } from "./merge_risk.ts";
|
|
18
21
|
import { mount as notify } from "./notify.ts";
|
|
@@ -40,7 +43,7 @@ export interface RouteCtx {
|
|
|
40
43
|
answerDir?: string;
|
|
41
44
|
}
|
|
42
45
|
|
|
43
|
-
export const routeModules: ReadonlyArray<(ctx: RouteCtx) => void> = [artifacts, stream, start, control, stats, cost, costLedger, fleet, metrics, config, providers, verify, integrations, notify, audit, mergeRisk];
|
|
46
|
+
export const routeModules: ReadonlyArray<(ctx: RouteCtx) => void> = [artifacts, stream, start, control, stats, cost, costLedger, fleet, metrics, checkpoints, memory, sessionControl, config, providers, verify, integrations, notify, audit, mergeRisk];
|
|
44
47
|
|
|
45
48
|
export function registerRoutes(ctx: RouteCtx): void {
|
|
46
49
|
mountRepos(ctx.act, ctx.db, ctx.peerIsLoopback);
|