loki-mode 11.8.1 → 11.10.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.
Files changed (90) hide show
  1. package/SKILL.md +2 -2
  2. package/VERSION +1 -1
  3. package/autonomy/lib/backlog.py +15 -3
  4. package/autonomy/lib/workspace.py +95 -4
  5. package/autonomy/loki +189 -108
  6. package/autonomy/trigger-server.py +302 -4
  7. package/bin/loki +1 -1
  8. package/completions/_loki +34 -1
  9. package/completions/loki.bash +10 -3
  10. package/dashboard/__init__.py +1 -1
  11. package/docs/ARCHITECTURE.md +0 -1
  12. package/docs/CLI-REFERENCE.md +1 -1
  13. package/docs/COMPONENTS.md +1 -1
  14. package/docs/DEPLOY-PLAN.md +13 -3
  15. package/docs/INSTALLATION.md +1 -1
  16. package/docs/WORKSPACES.md +1 -1
  17. package/docs/dev/architecture-reference.md +1 -1
  18. package/docs/sentry-intake.md +15 -1
  19. package/docs/v10/D61-SPEED.md +2 -0
  20. package/docs/v10/PROGRESS.md +25 -1
  21. package/loki-ts/dist/loki.js +1016 -946
  22. package/mcp/__init__.py +1 -1
  23. package/package.json +2 -2
  24. package/packages/control-plane/dist/ask-tools-server.js +13 -4
  25. package/packages/control-plane/dist/server.js +457 -279
  26. package/packages/control-plane/src/server/diffstat.ts +10 -2
  27. package/packages/control-plane/src/server/legacy/routes-control.ts +8 -4
  28. package/packages/control-plane/src/server/routes/checkpoints.ts +11 -2
  29. package/packages/control-plane/ui/dist/assets/index-u8cd6TRQ.js +190 -0
  30. package/packages/control-plane/ui/dist/index.html +1 -1
  31. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  32. package/web-app/dist/assets/{AdminPage-BnIg9ilA.js → AdminPage-D3PgG7SH.js} +1 -1
  33. package/web-app/dist/assets/{Avatar-0BTVsQ2A.js → Avatar-Ct77hvlG.js} +1 -1
  34. package/web-app/dist/assets/{Badge-Bbq8CXgn.js → Badge-Cg_gZaJF.js} +1 -1
  35. package/web-app/dist/assets/{Button-CKD4n0ld.js → Button-i9r2G84n.js} +1 -1
  36. package/web-app/dist/assets/{CockpitPage-DJhLNLAz.js → CockpitPage-C3UyLXyp.js} +1 -1
  37. package/web-app/dist/assets/{ComparePage-Di2akuII.js → ComparePage-B1dXHga_.js} +1 -1
  38. package/web-app/dist/assets/{ErrorBoundary-9lhsYG-z.js → ErrorBoundary-CQ_xREw0.js} +1 -1
  39. package/web-app/dist/assets/{EvidenceReceiptPanel-CQDGXvDr.js → EvidenceReceiptPanel-DwUxd_Xi.js} +1 -1
  40. package/web-app/dist/assets/{GitHubIssuesPanel-Y_w2k9Ej.js → GitHubIssuesPanel-C0jAnuAd.js} +1 -1
  41. package/web-app/dist/assets/{GitHubPRsPanel-pMSQedCv.js → GitHubPRsPanel-B4pNXJMz.js} +1 -1
  42. package/web-app/dist/assets/{HomePage-CIAkiGdt.js → HomePage-BC9UTS1l.js} +2 -2
  43. package/web-app/dist/assets/{LoginPage-BES4Pk6k.js → LoginPage-CnpdwK8F.js} +1 -1
  44. package/web-app/dist/assets/{MagicPage-B154fMhi.js → MagicPage-Bj0lZuNp.js} +1 -1
  45. package/web-app/dist/assets/{MetricsPage-BcNyaiUO.js → MetricsPage-yyw3VnfX.js} +1 -1
  46. package/web-app/dist/assets/{NotFoundPage-D28FqYxG.js → NotFoundPage-LMxkJP15.js} +1 -1
  47. package/web-app/dist/assets/{ProjectPage-C_O3u8Jx.js → ProjectPage-2x0MRkNu.js} +3 -3
  48. package/web-app/dist/assets/{ProjectsPage-uqdahuPA.js → ProjectsPage-A8J8o1gj.js} +1 -1
  49. package/web-app/dist/assets/{SettingsPage-DLvVA9OZ.js → SettingsPage-fwvOVcRb.js} +1 -1
  50. package/web-app/dist/assets/{ShowcasePage-BAZP8vsm.js → ShowcasePage-DqI4sUwk.js} +1 -1
  51. package/web-app/dist/assets/{SystemSettingsPage-DPYZkLWh.js → SystemSettingsPage-CyytfNo_.js} +1 -1
  52. package/web-app/dist/assets/{TeamsPage-BgSyb5Pd.js → TeamsPage-BTTe76W1.js} +1 -1
  53. package/web-app/dist/assets/{TemplatesPage-D_Jh5VU1.js → TemplatesPage-CIp3OTlL.js} +1 -1
  54. package/web-app/dist/assets/{TerminalOutput-4v8poDKb.js → TerminalOutput-dX5L4ky-.js} +1 -1
  55. package/web-app/dist/assets/{activity-CE2b6kdw.js → activity-uHXr2AaF.js} +1 -1
  56. package/web-app/dist/assets/{bell-CxJDlLSR.js → bell-D6RbxRMo.js} +1 -1
  57. package/web-app/dist/assets/{bot-C2WuK4zw.js → bot-D31w2Mu6.js} +1 -1
  58. package/web-app/dist/assets/{check-81epVldw.js → check-CoKJksbr.js} +1 -1
  59. package/web-app/dist/assets/{chevron-left-B6EXh_3C.js → chevron-left-Bjx7oX7s.js} +1 -1
  60. package/web-app/dist/assets/{circle-alert-0iqoo-iy.js → circle-alert-DOAh8Jmi.js} +1 -1
  61. package/web-app/dist/assets/{clock-CFrBpNUp.js → clock-DgEXPNWH.js} +1 -1
  62. package/web-app/dist/assets/{cloud-BBSFhUT_.js → cloud-0dXJFzBv.js} +1 -1
  63. package/web-app/dist/assets/{code-xml-BCU2V1rq.js → code-xml-DytQ_bq7.js} +1 -1
  64. package/web-app/dist/assets/{database-D2hvO52K.js → database-Qyjryb72.js} +1 -1
  65. package/web-app/dist/assets/{dollar-sign-CXFA9b6T.js → dollar-sign-BzhTVy1e.js} +1 -1
  66. package/web-app/dist/assets/{file-code-corner-CqTCEEfS.js → file-code-corner-DTdSOALw.js} +1 -1
  67. package/web-app/dist/assets/{file-plus-DYFbkGbs.js → file-plus-VdjOG1RK.js} +1 -1
  68. package/web-app/dist/assets/{globe-DpYHLobk.js → globe-BhxNOfyM.js} +1 -1
  69. package/web-app/dist/assets/{hammer-CZ97SEYo.js → hammer-MQzrw8ee.js} +1 -1
  70. package/web-app/dist/assets/{index-DzswgpRo.js → index-DaxrMbHf.js} +3 -3
  71. package/web-app/dist/assets/{layers-2Tq0edx4.js → layers-C7nusTP-.js} +1 -1
  72. package/web-app/dist/assets/{loader-circle-BFP4kUX-.js → loader-circle-B1qBkgOY.js} +1 -1
  73. package/web-app/dist/assets/{lock-DkKdZH4d.js → lock-BdkOxK2O.js} +1 -1
  74. package/web-app/dist/assets/{package-ChOGD8yf.js → package-RVyK96Zn.js} +1 -1
  75. package/web-app/dist/assets/{plus-hggB1o6b.js → plus-ntmwFRrp.js} +1 -1
  76. package/web-app/dist/assets/{refresh-cw-DYwVLOeF.js → refresh-cw-BJ39b_PP.js} +1 -1
  77. package/web-app/dist/assets/{rotate-ccw-CUdY506q.js → rotate-ccw-uPx_-3vz.js} +1 -1
  78. package/web-app/dist/assets/{scroll-text-Wvidz5uN.js → scroll-text-D9qvXkj-.js} +1 -1
  79. package/web-app/dist/assets/{server-O11QmkCN.js → server-gs4xmD8Y.js} +1 -1
  80. package/web-app/dist/assets/{shield-alert-DXXx27uV.js → shield-alert-O2_YffGI.js} +1 -1
  81. package/web-app/dist/assets/{thumbs-up-DF8ivs3A.js → thumbs-up-B3RVBhQx.js} +1 -1
  82. package/web-app/dist/assets/{trash-2-LA6PW0Nf.js → trash-2-DEUt7qhq.js} +1 -1
  83. package/web-app/dist/assets/{trending-down-Bw_vLKHT.js → trending-down-oyTns4UZ.js} +1 -1
  84. package/web-app/dist/assets/{trending-up-tX8motha.js → trending-up-CdpZK0Oc.js} +1 -1
  85. package/web-app/dist/assets/{upload-CxtTWJd2.js → upload-D9sbjjC8.js} +1 -1
  86. package/web-app/dist/assets/{usePolling-D-P5Iofq.js → usePolling-6ognrnyV.js} +1 -1
  87. package/web-app/dist/assets/{user-dKTcuUFq.js → user-CBKc8QoI.js} +1 -1
  88. package/web-app/dist/index.html +1 -1
  89. package/memory/rag_injector.py +0 -161
  90. package/packages/control-plane/ui/dist/assets/index-DbLzr87u.js +0 -190
@@ -22,6 +22,10 @@ const SHA = /^[0-9a-f]{40,64}$/;
22
22
  const MAX_RECEIPT_BYTES = 1024 * 1024;
23
23
  const GIT_TIMEOUT_MS = 5000;
24
24
  const cache = new Map<string, DiffStat>();
25
+ // A git failure (timeout, unknown object) is remembered per run and range for a short while so a list refresh does not respawn git for every row.
26
+ const FAIL_TTL_MS = 60_000;
27
+ const failed = new Map<string, number>();
28
+ export const diffStatGitRuns = { n: 0 };
25
29
 
26
30
  const gitEnv = (): NodeJS.ProcessEnv => ({ PATH: process.env.PATH ?? "", HOME: process.env.HOME ?? "", LC_ALL: "C", GIT_TERMINAL_PROMPT: "0", GIT_OPTIONAL_LOCKS: "0" });
27
31
 
@@ -49,7 +53,7 @@ function readReceipt(runDir: string): { base_sha?: unknown; head_sha?: unknown }
49
53
  } catch { return null; } finally { closeSync(fd); }
50
54
  }
51
55
 
52
- /** Null when the run is unknown, has no local repo or receipt, has no valid shas, or git cannot answer. Only successes are cached (a finished run's range never changes). */
56
+ /** Null when the run is unknown, has no local repo or receipt, has no valid shas, or git cannot answer. Successes are cached for good (a finished run's range never changes); git failures are cached for FAIL_TTL_MS. */
53
57
  export function diffStatFor(db: Db, source: string, run: string): DiffStat | null {
54
58
  if (!ID.test(source) || !ID.test(run) || source.includes("..") || run.includes("..")) return null;
55
59
  const key = `${source}/${run}`;
@@ -67,11 +71,15 @@ export function diffStatFor(db: Db, source: string, run: string): DiffStat | nul
67
71
  const r = readReceipt(runDir);
68
72
  const base = typeof r?.base_sha === "string" ? r.base_sha : "", head = typeof r?.head_sha === "string" ? r.head_sha : "";
69
73
  if (!SHA.test(base) || !SHA.test(head)) return null;
74
+ const failKey = `${key}:${base}:${head}`;
75
+ const failedAt = failed.get(failKey);
76
+ if (failedAt !== undefined && Date.now() - failedAt < FAIL_TTL_MS) return null;
70
77
  try {
78
+ diffStatGitRuns.n++;
71
79
  const out = execFileSync("git", ["-C", repoReal, "-c", "core.quotePath=false", "diff", "--numstat", "--no-renames", `${base}..${head}`, "--"], { env: gitEnv(), timeout: GIT_TIMEOUT_MS, maxBuffer: 8 * 1024 * 1024, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
72
80
  const files = parseNumstat(out);
73
81
  const stat: DiffStat = { base, head, files, added: files.reduce((n, f) => n + (f.added ?? 0), 0), removed: files.reduce((n, f) => n + (f.removed ?? 0), 0), source: "git" };
74
82
  cache.set(key, stat);
75
83
  return stat;
76
- } catch { return null; }
84
+ } catch { failed.set(failKey, Date.now()); return null; }
77
85
  }
@@ -1,17 +1,21 @@
1
1
  // CPE24-P4: legacy (port 57374) mappings for session control and the Completion Council. They share the /v1 implementation in
2
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
3
  import type { Context } from "hono";
4
+ import { isLoopbackHost, peerIsLoopback } from "../auth.ts";
4
5
  import type { Db } from "../../db/migrate.ts";
5
6
  import { councilConvergence, councilReport, councilState, councilTranscripts, councilVerdicts, intQ, runControl } from "../routes/session_control.ts";
6
7
  import { lokiDirOf } from "../routes/cost_ledger.ts";
7
8
 
8
9
  export function controlMapped(db: Db, repoDir: string): Record<string, (c: Context) => Response> {
9
10
  const loki = () => lokiDirOf(repoDir);
11
+ // State-changing legacy calls need a loopback peer AND a loopback Host, like /v1 (no JSON requirement: legacy callers send none).
12
+ const ctl = (action: Parameters<typeof runControl>[2]) => (c: Context) => (peerIsLoopback(c) && isLoopbackHost(c.req.header("host"))
13
+ ? runControl(db, repoDir, action, c, "legacy") : c.json({ error: "loopback only" }, 403));
10
14
  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
+ "POST /api/control/pause": ctl("pause"),
16
+ "POST /api/control/resume": ctl("resume"),
17
+ "POST /api/control/stop": ctl("stop"),
18
+ "POST /api/council/force-review": ctl("council-review"),
15
19
  "GET /api/council/state": (c) => c.json(councilState(loki())),
16
20
  "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
21
  "GET /api/council/convergence": (c) => c.json(councilConvergence(loki())),
@@ -17,6 +17,7 @@ const isObj = (v: unknown): v is Rec => typeof v === "object" && v !== null && !
17
17
  const str = (v: unknown): string | null => (typeof v === "string" && v !== "" ? v : null);
18
18
  const SAFE_ID = /^[A-Za-z0-9_-]{1,128}$/;
19
19
  const MAX_CHECKPOINTS = 50;
20
+ const ROLLBACK_ALLOW = new Set(["session.json", "dashboard-state.json", "CONTINUITY.md", "autonomy-state.json", "state", "queue"]);
20
21
 
21
22
  export const validCheckpointId = (id: string) => SAFE_ID.test(id);
22
23
  const storeOf = (repoDir: string) => join(repoDir, ".loki", "state", "checkpoints");
@@ -104,12 +105,20 @@ export function rollbackCheckpoint(repoDir: string, id: string, now = new Date()
104
105
  appendFileSync(join(store, "index.jsonl"), `${JSON.stringify(preMeta)}\n`);
105
106
  let restored = 0;
106
107
  const errors: string[] = [];
108
+ // Only the allowlisted names are restored; anything else in the checkpoint (a planted audit/, say) is ignored and reported.
109
+ const skipped = (src: string) => src === join(real, "state", "checkpoints");
107
110
  for (const e of readdirSync(real, { withFileTypes: true })) {
108
111
  if (e.name === "metadata.json" || e.name === "worktree-snapshot.txt") continue;
109
- const src = join(real, e.name);
112
+ if (!ROLLBACK_ALLOW.has(e.name)) { errors.push(`${e.name}: not in the rollback allowlist, skipped`); continue; }
113
+ const src = join(real, e.name), dst = join(loki, e.name);
110
114
  try {
111
115
  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") });
116
+ if (existsSync(dst) && lstatSync(dst).isSymbolicLink()) { errors.push(`${e.name}: destination is a symlink, skipped`); continue; }
117
+ cpSync(src, dst, { recursive: true, filter: (s) => {
118
+ if (skipped(s)) return false;
119
+ if (lstatSync(s).isSymbolicLink()) { errors.push(`${s.slice(real.length + 1)}: nested symlink skipped`); return false; }
120
+ return true;
121
+ } });
113
122
  restored++;
114
123
  } catch (err) { errors.push(`${e.name}: ${(err as Error).message}`); }
115
124
  }