openwolf-enhanced 1.16.5 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/dist/dashboard/assets/{AISuggestions-DF-LNNBU.js → AISuggestions-1R0pnlm9.js} +1 -1
  3. package/dist/dashboard/assets/{ActivityLog-DuQB_TaU.js → ActivityLog-Cet80S6D.js} +1 -1
  4. package/dist/dashboard/assets/{ActivityTimeline-jWDsjU7p.js → ActivityTimeline-CoCPhZRg.js} +1 -1
  5. package/dist/dashboard/assets/{AnatomyBrowser-BOfaZMLt.js → AnatomyBrowser-BK7WiuNN.js} +1 -1
  6. package/dist/dashboard/assets/{BugLog-Bb8Jdare.js → BugLog-DlOfPz4q.js} +1 -1
  7. package/dist/dashboard/assets/{CerebrumViewer-Clix3JOG.js → CerebrumViewer-C0gSBIxg.js} +1 -1
  8. package/dist/dashboard/assets/{CronStatus-ne4IxXs8.js → CronStatus-DMGKkek9.js} +1 -1
  9. package/dist/dashboard/assets/{DesignQC-COcZ38nI.js → DesignQC-BUjKJ4wE.js} +1 -1
  10. package/dist/dashboard/assets/{MemoryViewer-BYUkDs9k.js → MemoryViewer-CbFNeqWm.js} +1 -1
  11. package/dist/dashboard/assets/{NativeMemory-20gjdcAD.js → NativeMemory-DEAyhwK9.js} +1 -1
  12. package/dist/dashboard/assets/{ProjectOverview-BoadyvwS.js → ProjectOverview-CnxeetBi.js} +1 -1
  13. package/dist/dashboard/assets/{ProjectsAggregate-z6XtnlbZ.js → ProjectsAggregate-DG5Vxc2k.js} +1 -1
  14. package/dist/dashboard/assets/{StatusBadge-CBsSwOOc.js → StatusBadge-DelYV9XQ.js} +1 -1
  15. package/dist/dashboard/assets/{TokenUsage-n2PFPxqk.js → TokenUsage-Bvk7bQ1V.js} +1 -1
  16. package/dist/dashboard/assets/{index-DLkIxGHf.js → index-QzB3QJTS.js} +3 -3
  17. package/dist/dashboard/index.html +1 -1
  18. package/dist/src/cli/consolidate-cmd.js +14 -7
  19. package/dist/src/cli/consolidate-cmd.js.map +1 -1
  20. package/dist/src/cli/index.js +10 -0
  21. package/dist/src/cli/index.js.map +1 -1
  22. package/dist/src/cli/llm-cmd.js +82 -0
  23. package/dist/src/cli/llm-cmd.js.map +1 -0
  24. package/dist/src/daemon/cron-engine.js +191 -57
  25. package/dist/src/daemon/cron-engine.js.map +1 -1
  26. package/dist/src/daemon/llm-provider.js +64 -8
  27. package/dist/src/daemon/llm-provider.js.map +1 -1
  28. package/dist/src/daemon/wolf-daemon.js +4 -2
  29. package/dist/src/daemon/wolf-daemon.js.map +1 -1
  30. package/dist/src/utils/recall.js +4 -0
  31. package/dist/src/utils/recall.js.map +1 -1
  32. package/package.json +1 -1
@@ -7,7 +7,7 @@
7
7
  <link rel="preconnect" href="https://fonts.googleapis.com" />
8
8
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
9
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
10
- <script type="module" crossorigin src="./assets/index-DLkIxGHf.js"></script>
10
+ <script type="module" crossorigin src="./assets/index-QzB3QJTS.js"></script>
11
11
  <link rel="stylesheet" crossorigin href="./assets/index-Cr3yWM5G.css">
12
12
  </head>
13
13
  <body>
@@ -3,7 +3,7 @@ import * as path from "node:path";
3
3
  import { findProjectRoot } from "../scanner/project-root.js";
4
4
  import { findDuplicateEntries } from "../utils/maintenance.js";
5
5
  import { blocksFor } from "../utils/recall.js";
6
- import { resolveLlmConfig, callLlm } from "../daemon/llm-provider.js";
6
+ import { resolveLlmConfig, callLlm, requiresApiKey } from "../daemon/llm-provider.js";
7
7
  // Rewrite `content`: for each merge, replace block A's lines with the merged entry and delete block
8
8
  // B's lines. Pure + bottom-up so line indices don't shift. (1-based start/end from blocksFor.)
9
9
  export function applyConsolidations(content, merges) {
@@ -43,9 +43,9 @@ export async function consolidateCommand(opts) {
43
43
  console.log(`OpenWolf Consolidate — ${pairs.length} near-duplicate pair(s) in cerebrum.md (threshold ${threshold})\n`);
44
44
  // Resolve the LLM once (consolidation needs it). Clear guidance if unconfigured.
45
45
  const llm = resolveLlmConfig(wolfDir);
46
- const apiKey = process.env[llm.apiKeyEnv];
47
- if (!apiKey) {
48
- console.error(`${llm.apiKeyEnv} is not set — consolidation needs an LLM. Set it, or point openwolf.cron.llm_* at a provider (e.g. a free OpenAI-compatible one). Provider: ${llm.provider}/${llm.model}.`);
46
+ const apiKey = process.env[llm.apiKeyEnv] ?? "";
47
+ if (!apiKey && requiresApiKey(llm)) {
48
+ console.error(`${llm.apiKeyEnv} is not set — consolidation needs an LLM. Set it, or point openwolf.cron.llm_* at a provider (a free OpenAI-compatible one, or a local model server — LM Studio on http://localhost:1234/v1, Ollama on http://localhost:11434/v1 — which needs no key). Provider: ${llm.provider}/${llm.model}.`);
49
49
  process.exitCode = 1;
50
50
  return;
51
51
  }
@@ -60,7 +60,9 @@ export async function consolidateCommand(opts) {
60
60
  process.stdout.write(` ${Math.round(p.similarity * 100)}% lines ${p.aLine} ↔ ${p.bLine} … `);
61
61
  let merged = "";
62
62
  try {
63
- merged = (await callLlm(llm, apiKey, MERGE_PROMPT(a.text, b.text), { maxTokens: 900, timeoutMs: 60000 })).trim();
63
+ // 3000, not 900: the merge itself is short, but a reasoning model bills its thinking to the same
64
+ // budget and would otherwise return nothing — every merge then looked "implausible" and was skipped.
65
+ merged = (await callLlm(llm, apiKey, MERGE_PROMPT(a.text, b.text), { maxTokens: 3000, timeoutMs: 120000 })).trim();
64
66
  merged = merged.replace(/^```[\w]*\n?|\n?```$/g, "").trim(); // strip stray fences
65
67
  }
66
68
  catch (e) {
@@ -87,11 +89,16 @@ export async function consolidateCommand(opts) {
87
89
  console.log(`\n(dry run) ${merges.length} pair(s) would be merged. Run without --dry-run to apply.`);
88
90
  return;
89
91
  }
90
- // Back up, then rewrite.
92
+ // Back up, then rewrite. A failed backup is a HARD STOP, not best-effort: same failure class as
93
+ // bug-157 — if the safety net silently fails, the dangerous step must not proceed. No backup, no rewrite.
91
94
  try {
92
95
  fs.copyFileSync(cerebrumPath, cerebrumPath + ".bak-pre-consolidate");
93
96
  }
94
- catch { /* best-effort */ }
97
+ catch (e) {
98
+ console.error(`\nAborting: could not write backup cerebrum.md.bak-pre-consolidate (${e.message}). cerebrum.md left untouched.`);
99
+ process.exitCode = 1;
100
+ return;
101
+ }
95
102
  fs.writeFileSync(cerebrumPath, applyConsolidations(content, merges), "utf-8");
96
103
  console.log(`\nDone. Merged ${merges.length} pair(s) → cerebrum.md (backup: cerebrum.md.bak-pre-consolidate). Review the diff.`);
97
104
  }
@@ -1 +1 @@
1
- {"version":3,"file":"consolidate-cmd.js","sourceRoot":"","sources":["../../../src/cli/consolidate-cmd.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"consolidate-cmd.js","sourceRoot":"","sources":["../../../src/cli/consolidate-cmd.ts"],"names":[],"mappings":"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"}
@@ -153,6 +153,16 @@ export function createProgram() {
153
153
  const { consolidateCommand } = await import("./consolidate-cmd.js");
154
154
  await consolidateCommand(opts);
155
155
  });
156
+ // --- LLM command ---
157
+ program
158
+ .command("llm")
159
+ .description("Show (and test) the LLM the project's AI features call — incl. local models via Ollama/llama.cpp")
160
+ .option("--test", "Send a real prompt and time the round-trip")
161
+ .option("--prompt <text>", "Prompt to send with --test")
162
+ .action(async (opts) => {
163
+ const { llmCommand } = await import("./llm-cmd.js");
164
+ await llmCommand(opts);
165
+ });
156
166
  // --- Export command ---
157
167
  program
158
168
  .command("export <what>")
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/cli/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/cli/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ import * as path from "node:path";
2
+ import { findProjectRoot } from "../scanner/project-root.js";
3
+ import { resolveLlmConfig, callLlm, requiresApiKey, isLocalEndpoint } from "../daemon/llm-provider.js";
4
+ // The port is read back out of the configured base_url — hard-coding Ollama's 11434 sent LM Studio
5
+ // users (port 1234) to forward a port nothing was listening on.
6
+ function endpointPort(baseUrl) {
7
+ try {
8
+ const u = new URL(baseUrl);
9
+ return u.port || (u.protocol === "https:" ? "443" : "80");
10
+ }
11
+ catch {
12
+ return "1234";
13
+ }
14
+ }
15
+ // Turn a fetch/HTTP failure into the one line that actually tells the user what to do next.
16
+ export function explainLlmError(cfg, err) {
17
+ const msg = err.message || String(err);
18
+ const cause = err.cause?.code;
19
+ const port = endpointPort(cfg.baseUrl);
20
+ if (cause === "ECONNREFUSED" || /ECONNREFUSED|fetch failed|Empty reply|socket hang up/i.test(msg)) {
21
+ return isLocalEndpoint(cfg.baseUrl)
22
+ ? `Nothing is listening on ${cfg.baseUrl}. If the model runs on another machine, forward the port to this one:\n` +
23
+ ` ssh -N -R ${port}:localhost:${port} <user>@<this-host> (run on the machine with the model)\n` +
24
+ ` If the tunnel is already up, the model server itself is not running — LM Studio serves only while\n` +
25
+ ` the Developer tab's server toggle is "Running" (\`lms server start\`); Ollama serves via \`ollama serve\`.`
26
+ : `Cannot reach ${cfg.baseUrl} — ${msg}`;
27
+ }
28
+ if (/\b404\b/.test(msg) && isLocalEndpoint(cfg.baseUrl)) {
29
+ return `Endpoint answered 404 — the server is up but does not know the model "${cfg.model}".\n` +
30
+ ` Check the exact id it serves: curl ${cfg.baseUrl}/models\n` +
31
+ ` Then load it (LM Studio: load the model or enable JIT loading; Ollama: ollama pull ${cfg.model}).`;
32
+ }
33
+ if (/max_tokens/.test(msg)) {
34
+ return msg; // already the actionable reasoning-model explanation from parseLlmResponse
35
+ }
36
+ if (/\b401\b|\b403\b/.test(msg)) {
37
+ return `Rejected (auth). ${requiresApiKey(cfg) ? `Check that $${cfg.apiKeyEnv} holds a valid key.` : "The local server demands a key — set one in $" + cfg.apiKeyEnv + "."}`;
38
+ }
39
+ return msg;
40
+ }
41
+ export async function llmCommand(opts) {
42
+ const wolfDir = path.join(findProjectRoot(), ".wolf");
43
+ const cfg = resolveLlmConfig(wolfDir);
44
+ const keyed = !!process.env[cfg.apiKeyEnv];
45
+ const local = isLocalEndpoint(cfg.baseUrl);
46
+ console.log("OpenWolf LLM — used by cron AI tasks and `openwolf consolidate`\n");
47
+ console.log(` provider ${cfg.provider}${local ? " (local model server — no API key needed)" : ""}`);
48
+ console.log(` endpoint ${cfg.baseUrl}`);
49
+ console.log(` model ${cfg.model}`);
50
+ console.log(` api key ${keyed ? `$${cfg.apiKeyEnv} is set` : requiresApiKey(cfg) ? `$${cfg.apiKeyEnv} is NOT set — remote providers need one` : "not required"}`);
51
+ if (!opts.test) {
52
+ console.log("\nRun `openwolf llm --test` to send a real prompt and time the round-trip.");
53
+ return;
54
+ }
55
+ const prompt = opts.prompt || "Reply with exactly one word: OK";
56
+ console.log(`\n → ${JSON.stringify(prompt)}`);
57
+ const started = Date.now();
58
+ try {
59
+ // 2048, not 256: a reasoning model spends the budget on thinking before it answers, and 256 was
60
+ // gone before Qwen3 emitted a single character — the test then "passed" with an empty reply.
61
+ const reply = await callLlm(cfg, process.env[cfg.apiKeyEnv] ?? "", prompt, { maxTokens: 2048, timeoutMs: 120_000 });
62
+ const secs = (Date.now() - started) / 1000;
63
+ if (!reply) {
64
+ // An empty answer is a failure. Reporting ✓ on 0 chars is how this stayed hidden.
65
+ console.error(`\n ✗ the endpoint answered in ${secs.toFixed(1)}s but returned no text.\n` +
66
+ ` The server is reachable, so this is a model/response problem, not a connection one.`);
67
+ process.exitCode = 1;
68
+ return;
69
+ }
70
+ console.log(` ← ${reply.replace(/\n/g, "\n ")}`);
71
+ console.log(`\n ✓ ${secs.toFixed(1)}s round-trip (${reply.length} chars)`);
72
+ // A local 20B+ model on CPU answers at single-digit tokens/s — usable for a cron job that runs
73
+ // nightly, not for anything a human waits on. Say so instead of letting them find out in prod.
74
+ if (local && secs > 30)
75
+ console.log(` ⚠ that is slow — fine for nightly cron tasks, painful for interactive use.`);
76
+ }
77
+ catch (err) {
78
+ console.error(`\n ✗ ${explainLlmError(cfg, err)}`);
79
+ process.exitCode = 1;
80
+ }
81
+ }
82
+ //# sourceMappingURL=llm-cmd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm-cmd.js","sourceRoot":"","sources":["../../../src/cli/llm-cmd.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,cAAc,EAAE,eAAe,EAAkB,MAAM,2BAA2B,CAAC;AAQvH,mGAAmG;AACnG,gEAAgE;AAChE,SAAS,YAAY,CAAC,OAAe;IACnC,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;QAC3B,OAAO,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAC5D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,MAAM,CAAC;IAChB,CAAC;AACH,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,eAAe,CAAC,GAAc,EAAE,GAAU;IACxD,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC;IACvC,MAAM,KAAK,GAAI,GAAqC,CAAC,KAAK,EAAE,IAAI,CAAC;IACjE,MAAM,IAAI,GAAG,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACvC,IAAI,KAAK,KAAK,cAAc,IAAI,uDAAuD,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAClG,OAAO,eAAe,CAAC,GAAG,CAAC,OAAO,CAAC;YACjC,CAAC,CAAC,2BAA2B,GAAG,CAAC,OAAO,yEAAyE;gBAC/G,iBAAiB,IAAI,cAAc,IAAI,6DAA6D;gBACpG,uGAAuG;gBACvG,8GAA8G;YAChH,CAAC,CAAC,gBAAgB,GAAG,CAAC,OAAO,MAAM,GAAG,EAAE,CAAC;IAC7C,CAAC;IACD,IAAI,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,eAAe,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;QACxD,OAAO,yEAAyE,GAAG,CAAC,KAAK,MAAM;YAC7F,yCAAyC,GAAG,CAAC,OAAO,WAAW;YAC/D,wFAAwF,GAAG,CAAC,KAAK,IAAI,CAAC;IAC1G,CAAC;IACD,IAAI,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAC3B,OAAO,GAAG,CAAC,CAAC,2EAA2E;IACzF,CAAC;IACD,IAAI,iBAAiB,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAChC,OAAO,oBAAoB,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,eAAe,GAAG,CAAC,SAAS,qBAAqB,CAAC,CAAC,CAAC,+CAA+C,GAAG,GAAG,CAAC,SAAS,GAAG,GAAG,EAAE,CAAC;IAC/K,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAa;IAC5C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,OAAO,CAAC,CAAC;IACtD,MAAM,GAAG,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACtC,MAAM,KAAK,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAC3C,MAAM,KAAK,GAAG,eAAe,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAE3C,OAAO,CAAC,GAAG,CAAC,mEAAmE,CAAC,CAAC;IACjF,OAAO,CAAC,GAAG,CAAC,gBAAgB,GAAG,CAAC,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,4CAA4C,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACxG,OAAO,CAAC,GAAG,CAAC,gBAAgB,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;IAC3C,OAAO,CAAC,GAAG,CAAC,gBAAgB,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;IACzC,OAAO,CAAC,GAAG,CAAC,gBAAgB,KAAK,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,SAAS,SAAS,CAAC,CAAC,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,SAAS,yCAAyC,CAAC,CAAC,CAAC,cAAc,EAAE,CAAC,CAAC;IAEtK,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QACf,OAAO,CAAC,GAAG,CAAC,4EAA4E,CAAC,CAAC;QAC1F,OAAO;IACT,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,iCAAiC,CAAC;IAChE,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC/C,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC3B,IAAI,CAAC;QACH,gGAAgG;QAChG,6FAA6F;QAC7F,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC;QACpH,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC;QAC3C,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,kFAAkF;YAClF,OAAO,CAAC,KAAK,CAAC,kCAAkC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,2BAA2B;gBACxF,yFAAyF,CAAC,CAAC;YAC7F,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;YACrB,OAAO;QACT,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;QACrD,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,iBAAiB,KAAK,CAAC,MAAM,SAAS,CAAC,CAAC;QAC5E,+FAA+F;QAC/F,+FAA+F;QAC/F,IAAI,KAAK,IAAI,IAAI,GAAG,EAAE;YAAE,OAAO,CAAC,GAAG,CAAC,8EAA8E,CAAC,CAAC;IACtH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,SAAS,eAAe,CAAC,GAAG,EAAE,GAAY,CAAC,EAAE,CAAC,CAAC;QAC7D,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IACvB,CAAC;AACH,CAAC"}
@@ -4,13 +4,50 @@ import cron from "node-cron";
4
4
  import { readJSON, writeJSON, readText, writeText, withLock } from "../utils/fs-safe.js";
5
5
  import { scanProject } from "../scanner/anatomy-scanner.js";
6
6
  import { detectWaste } from "../tracker/waste-detector.js";
7
- import { resolveLlmConfig, callLlm } from "./llm-provider.js";
7
+ import { resolveLlmConfig, callLlmDetailed, requiresApiKey } from "./llm-provider.js";
8
+ /**
9
+ * Split text into chunks that fit a context window — at paragraph boundaries, never mid-sentence.
10
+ *
11
+ * The old code did `content.slice(-MAX)`: it kept the tail and threw the rest away. For a knowledge
12
+ * base that is not a size limit, it is data loss with extra steps — the model then "cleans" a quarter
13
+ * of the file and the other three quarters cease to exist. Splitting keeps every byte; the model just
14
+ * reads it in several passes.
15
+ */
16
+ export function splitForContext(text, maxBytes) {
17
+ if (Buffer.byteLength(text, "utf-8") <= maxBytes)
18
+ return [text];
19
+ const chunks = [];
20
+ let current = "";
21
+ for (const para of text.split(/\n\n+/)) {
22
+ const candidate = current ? `${current}\n\n${para}` : para;
23
+ if (current && Buffer.byteLength(candidate, "utf-8") > maxBytes) {
24
+ chunks.push(current);
25
+ current = para;
26
+ }
27
+ else {
28
+ current = candidate;
29
+ }
30
+ }
31
+ if (current)
32
+ chunks.push(current);
33
+ // A single paragraph bigger than the window still has to be cut somewhere — but it is the only
34
+ // case where anything is cut, and it is cut into pieces rather than thrown away.
35
+ return chunks.flatMap((c) => {
36
+ if (Buffer.byteLength(c, "utf-8") <= maxBytes)
37
+ return [c];
38
+ const out = [];
39
+ for (let i = 0; i < c.length; i += maxBytes)
40
+ out.push(c.slice(i, i + maxBytes));
41
+ return out;
42
+ });
43
+ }
8
44
  export class CronEngine {
9
45
  wolfDir;
10
46
  projectRoot;
11
47
  logger;
12
48
  broadcast;
13
49
  scheduledTasks = [];
50
+ retryTimers = new Set();
14
51
  failureCounts = new Map();
15
52
  constructor(wolfDir, projectRoot, logger, broadcast) {
16
53
  this.wolfDir = wolfDir;
@@ -41,6 +78,11 @@ export class CronEngine {
41
78
  task.stop();
42
79
  }
43
80
  this.scheduledTasks = [];
81
+ // Retries are scheduled with setTimeout, not node-cron, so task.stop() above doesn't touch them.
82
+ // Left alone, a pending retry fires after shutdown and runs against a daemon that thinks it stopped.
83
+ for (const timer of this.retryTimers)
84
+ clearTimeout(timer);
85
+ this.retryTimers.clear();
44
86
  }
45
87
  async runTask(taskId) {
46
88
  const manifest = this.readManifest();
@@ -68,21 +110,24 @@ export class CronEngine {
68
110
  const startTime = Date.now();
69
111
  this.logger.info(`Executing task: ${task.name}`);
70
112
  try {
71
- await this.runAction(task.action);
113
+ await this.runAction(task.action, task.id);
72
114
  const duration = Date.now() - startTime;
73
- // Log success
74
- const state = this.readState();
75
- state.execution_log.push({
76
- task_id: task.id,
77
- status: "success",
78
- timestamp: new Date().toISOString(),
79
- duration_ms: duration,
115
+ // Log success. Re-read under a lock and write immediately: a concurrent task (or its long LLM
116
+ // await) could otherwise read the same snapshot and clobber this entry on write — lost logs.
117
+ withLock(path.join(this.wolfDir, "cron-state.json"), () => {
118
+ const state = this.readState();
119
+ state.execution_log.push({
120
+ task_id: task.id,
121
+ status: "success",
122
+ timestamp: new Date().toISOString(),
123
+ duration_ms: duration,
124
+ });
125
+ // Keep last 100 entries
126
+ if (state.execution_log.length > 100) {
127
+ state.execution_log = state.execution_log.slice(-100);
128
+ }
129
+ this.writeState(state);
80
130
  });
81
- // Keep last 100 entries
82
- if (state.execution_log.length > 100) {
83
- state.execution_log = state.execution_log.slice(-100);
84
- }
85
- this.writeState(state);
86
131
  this.failureCounts.set(task.id, 0);
87
132
  this.broadcast({
88
133
  type: "cron_executed",
@@ -102,33 +147,38 @@ export class CronEngine {
102
147
  // Retry with backoff
103
148
  const delay = this.calculateDelay(task.retry.backoff, task.retry.base_delay_seconds, failures);
104
149
  this.logger.info(`Retrying ${task.name} in ${delay}ms`);
105
- setTimeout(() => {
150
+ const timer = setTimeout(() => {
151
+ this.retryTimers.delete(timer);
106
152
  this.executeTask(task).catch(() => { });
107
153
  }, delay);
154
+ this.retryTimers.add(timer);
108
155
  }
109
156
  else {
110
- // Dead letter or skip
111
- const state = this.readState();
112
- state.execution_log.push({
113
- task_id: task.id,
114
- status: "failed",
115
- timestamp: new Date().toISOString(),
116
- duration_ms: duration,
117
- error: errorMsg,
118
- });
119
- if (task.failsafe.dead_letter) {
120
- state.dead_letter_queue.push({
157
+ // Dead letter or skip. Same lock as the success path — read-modify-write under it so a
158
+ // concurrent task cannot overwrite this failure record with its own stale snapshot.
159
+ withLock(path.join(this.wolfDir, "cron-state.json"), () => {
160
+ const state = this.readState();
161
+ state.execution_log.push({
121
162
  task_id: task.id,
122
- error: errorMsg,
163
+ status: "failed",
123
164
  timestamp: new Date().toISOString(),
124
- attempts: failures,
165
+ duration_ms: duration,
166
+ error: errorMsg,
125
167
  });
126
- // Keep the dead-letter queue bounded (execution_log is already capped elsewhere).
127
- if (state.dead_letter_queue.length > 100) {
128
- state.dead_letter_queue = state.dead_letter_queue.slice(-100);
168
+ if (task.failsafe.dead_letter) {
169
+ state.dead_letter_queue.push({
170
+ task_id: task.id,
171
+ error: errorMsg,
172
+ timestamp: new Date().toISOString(),
173
+ attempts: failures,
174
+ });
175
+ // Keep the dead-letter queue bounded (execution_log is already capped elsewhere).
176
+ if (state.dead_letter_queue.length > 100) {
177
+ state.dead_letter_queue = state.dead_letter_queue.slice(-100);
178
+ }
129
179
  }
130
- }
131
- this.writeState(state);
180
+ this.writeState(state);
181
+ });
132
182
  this.failureCounts.set(task.id, 0);
133
183
  // Notify the dashboard that this task permanently failed (upstream #4, bug 1).
134
184
  this.broadcast({ type: "task_error", task_id: task.id, error: errorMsg });
@@ -152,7 +202,7 @@ export class CronEngine {
152
202
  return 0;
153
203
  }
154
204
  }
155
- async runAction(action) {
205
+ async runAction(action, taskId = "ai_task") {
156
206
  switch (action.type) {
157
207
  case "scan_project":
158
208
  scanProject(this.wolfDir, this.projectRoot);
@@ -164,7 +214,7 @@ export class CronEngine {
164
214
  this.generateTokenReport();
165
215
  break;
166
216
  case "ai_task":
167
- await this.runAiTask(action.params);
217
+ await this.runAiTask(action.params, taskId);
168
218
  break;
169
219
  default:
170
220
  throw new Error(`Unknown action type: ${action.type}`);
@@ -234,55 +284,139 @@ export class CronEngine {
234
284
  writeJSON(ledgerPath, ledger);
235
285
  });
236
286
  }
237
- async runAiTask(params) {
238
- // Cap each context file so one large file can't blow up the prompt.
239
- const MAX_CONTEXT_BYTES = 20 * 1024;
240
- const contextParts = [];
287
+ /**
288
+ * Run a model over the project's own knowledge files.
289
+ *
290
+ * Default output is `mode: "proposal"`: the model NEVER writes a canonical file. It reads, and it
291
+ * writes its answer to `.wolf/proposals/<task>-<stamp>.md`, which a human — or a stronger model in a
292
+ * session — reviews before anything is adopted. The local model does the legwork; authority over what
293
+ * is true stays with the reader.
294
+ *
295
+ * This is not caution for its own sake. With `mode: "overwrite"` (opt-in, still supported), this exact
296
+ * path was one Sunday away from replacing a 78 KB cerebrum.md with a 3.9 KB "cleaned" version — see
297
+ * writeCerebrumFromAi.
298
+ *
299
+ * Files larger than the model's context are SPLIT, not truncated. Cutting a knowledge base to its tail
300
+ * and asking for "the cleaned file" is how the 95%-loss bug happened; reading it in pieces means the
301
+ * model actually sees all of it, just not at once.
302
+ */
303
+ async runAiTask(params, taskId) {
304
+ const mode = params.mode ?? "proposal";
305
+ const chunkBytes = params.chunk_bytes ?? 20 * 1024;
241
306
  const rootPrefix = path.resolve(this.projectRoot) + path.sep;
307
+ const files = [];
242
308
  for (const file of params.context_files) {
243
309
  const filePath = path.resolve(this.projectRoot, file);
244
310
  // Reject paths that escape the project root (e.g. "../../etc/passwd") — the file
245
311
  // contents are fed to the model, so traversal would exfiltrate arbitrary files (#34).
246
312
  if (filePath !== path.resolve(this.projectRoot) && !filePath.startsWith(rootPrefix)) {
247
- contextParts.push(`--- ${file} --- (rejected: outside project root)`);
313
+ files.push({ name: file, chunks: ["(rejected: outside project root)"] });
248
314
  continue;
249
315
  }
250
316
  try {
251
- let content = fs.readFileSync(filePath, "utf-8");
252
- if (Buffer.byteLength(content, "utf-8") > MAX_CONTEXT_BYTES) {
253
- content = "...[truncated — showing most recent]\n" + content.slice(-MAX_CONTEXT_BYTES);
254
- }
255
- contextParts.push(`--- ${file} ---\n${content}`);
317
+ files.push({ name: file, chunks: splitForContext(fs.readFileSync(filePath, "utf-8"), chunkBytes) });
256
318
  }
257
319
  catch {
258
- contextParts.push(`--- ${file} --- (not found)`);
320
+ files.push({ name: file, chunks: ["(not found)"] });
259
321
  }
260
322
  }
261
- const fullPrompt = `${params.prompt}\n\n---\nContext:\n${contextParts.join("\n\n")}`;
262
323
  // A background daemon can't drive the interactive `claude` CLI (it delegates auth to the
263
324
  // desktop app and fails headless). Use a direct API key instead, with a clear error if it's
264
325
  // missing — the dashboard then shows a copy-able prompt to run inside a session (upstream #4, bug 2).
265
326
  // The provider/model/endpoint are config-driven (openwolf.cron.llm_*), defaulting to Anthropic.
266
327
  const llm = resolveLlmConfig(this.wolfDir);
267
- const apiKey = process.env[llm.apiKeyEnv];
268
- if (!apiKey) {
328
+ const apiKey = process.env[llm.apiKeyEnv] ?? "";
329
+ if (!apiKey && requiresApiKey(llm)) {
269
330
  throw new Error(`${llm.apiKeyEnv} is not set. AI tasks require a direct API key when running as a background daemon. ` +
270
- `Set it in your shell profile: export ${llm.apiKeyEnv}=…`);
331
+ `Set it in your shell profile: export ${llm.apiKeyEnv}=… — or point openwolf.cron.llm_base_url at a ` +
332
+ `local model server (LM Studio on http://localhost:1234/v1, Ollama on http://localhost:11434/v1), which needs no key.`);
271
333
  }
272
- let result = await callLlm(llm, apiKey, fullPrompt);
273
- const fenceMatch = result.match(/```[\w]*\n([\s\S]*?)\n```/);
274
- if (fenceMatch)
275
- result = fenceMatch[1].trim();
276
- // Write result to suggestions.json if it's JSON, otherwise treat as a cerebrum update.
334
+ const totalChunks = files.reduce((n, f) => n + f.chunks.length, 0);
335
+ const sections = [];
336
+ for (const f of files) {
337
+ for (const [i, chunk] of f.chunks.entries()) {
338
+ const part = f.chunks.length > 1 ? ` (part ${i + 1}/${f.chunks.length})` : "";
339
+ const fullPrompt = `${params.prompt}\n\n---\nContext — ${f.name}${part}:\n${chunk}`;
340
+ // 16k, not the 4k default: a reasoning model spends thousands of tokens thinking before the
341
+ // first character of its answer. Too small a budget does not fail loudly — it returns HALF a
342
+ // file, which still looks like a valid result to any writer downstream.
343
+ const { text, truncated } = await callLlmDetailed(llm, apiKey, fullPrompt, { maxTokens: 16000, timeoutMs: 600_000 });
344
+ if (truncated)
345
+ throw new Error(`The model hit max_tokens mid-answer on ${f.name}${part} — the result is an incomplete fragment, so it is discarded rather than used.`);
346
+ const fence = text.match(/```[\w]*\n([\s\S]*?)\n```/);
347
+ sections.push(`## ${f.name}${part}\n\n${(fence ? fence[1] : text).trim()}`);
348
+ }
349
+ }
350
+ const result = sections.map((s) => s.replace(/^## .*\n\n/, "")).join("\n\n").trim();
351
+ // JSON answers keep going to suggestions.json — that file is a proposal by definition, nothing
352
+ // canonical is at risk.
277
353
  try {
278
354
  const parsed = JSON.parse(result);
279
355
  writeJSON(path.join(this.wolfDir, "suggestions.json"), { generated_at: new Date().toISOString(), ...parsed });
356
+ return;
280
357
  }
281
- catch {
358
+ catch { /* not JSON — prose, handled below */ }
359
+ if (mode === "overwrite") {
282
360
  if (result.includes("## User Preferences") || result.includes("## Key Learnings") || result.includes("# Cerebrum")) {
283
- writeText(path.join(this.wolfDir, "cerebrum.md"), result);
361
+ this.writeCerebrumFromAi(result, totalChunks > files.length ? new Set(["cerebrum.md"]) : new Set());
284
362
  }
363
+ return;
364
+ }
365
+ this.writeProposal(taskId, llm.model, files, totalChunks, sections);
366
+ }
367
+ /** Park the model's output where it can be reviewed — never where it can overwrite something. */
368
+ writeProposal(taskId, model, files, totalChunks, sections) {
369
+ const dir = path.join(this.wolfDir, "proposals");
370
+ fs.mkdirSync(dir, { recursive: true });
371
+ const stamp = new Date().toISOString().slice(0, 19).replace(/[:T]/g, "-");
372
+ const file = path.join(dir, `${taskId}-${stamp}.md`);
373
+ const header = [
374
+ `# Proposal — ${taskId}`,
375
+ ``,
376
+ `> Written by \`${model}\` on ${new Date().toISOString()}. **Nothing was changed.**`,
377
+ `> Read this, keep what is right, and apply it by hand — or ask a session to.`,
378
+ `> Sources: ${files.map((f) => `\`${f.name}\`${f.chunks.length > 1 ? ` (${f.chunks.length} parts, read in full)` : ""}`).join(", ")}`,
379
+ totalChunks > files.length
380
+ ? `> The file did not fit the model's context, so it was **split and read in pieces** — not truncated. Each section below covers one piece.`
381
+ : ``,
382
+ ``,
383
+ `---`,
384
+ ``,
385
+ ].filter((l) => l !== undefined).join("\n");
386
+ fs.writeFileSync(file, header + sections.join("\n\n---\n\n") + "\n", "utf-8");
387
+ this.logger.info(`Proposal written: .wolf/proposals/${path.basename(file)} — review it; nothing was overwritten.`);
388
+ }
389
+ /**
390
+ * Overwrite cerebrum.md with a model's rewrite — but only when that is actually safe.
391
+ *
392
+ * This path destroyed a real project's knowledge base in a dry run: cerebrum.md was 78 KB, the
393
+ * context cap fed the model only the LAST 20 KB (74% missing), the prompt said "return the cleaned
394
+ * file content only", and the model dutifully returned a tidy 3.9 KB file. Writing it would have
395
+ * erased 95% of everything the project had learned — silently, at 03:00 on a Sunday, with no backup.
396
+ * It never fired only because ANTHROPIC_API_KEY was unset; pointing the task at a keyless local
397
+ * model removed that accidental safety net.
398
+ *
399
+ * Two hard rules now:
400
+ * 1. A file the model saw only a SLICE of is never rewritten from that slice.
401
+ * 2. Nothing overwrites cerebrum.md without a timestamped backup sitting next to it.
402
+ */
403
+ writeCerebrumFromAi(result, truncatedFiles) {
404
+ const cerebrumPath = path.join(this.wolfDir, "cerebrum.md");
405
+ if (truncatedFiles.has("cerebrum.md")) {
406
+ throw new Error(`Refusing to overwrite cerebrum.md: it is larger than the ${20}KB context cap, so the model only saw ` +
407
+ `its tail. Rewriting the whole file from a fragment would delete everything above the cut. ` +
408
+ `Either shrink cerebrum.md (\`openwolf consolidate\`) or change this task to emit JSON suggestions ` +
409
+ `instead of a full-file rewrite.`);
410
+ }
411
+ try {
412
+ const before = fs.readFileSync(cerebrumPath, "utf-8");
413
+ const stamp = new Date().toISOString().slice(0, 19).replace(/[:T]/g, "-");
414
+ const backupDir = path.join(this.wolfDir, "backups");
415
+ fs.mkdirSync(backupDir, { recursive: true });
416
+ fs.writeFileSync(path.join(backupDir, `cerebrum-${stamp}.md`), before, "utf-8");
285
417
  }
418
+ catch { /* no existing file to back up — first write is harmless */ }
419
+ writeText(cerebrumPath, result);
286
420
  }
287
421
  }
288
422
  //# sourceMappingURL=cron-engine.js.map