@rubytech/create-maxy-code 0.1.321 → 0.1.322

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 (46) hide show
  1. package/dist/index.js +82 -1
  2. package/package.json +1 -1
  3. package/payload/platform/lib/embed-client/dist/index.d.ts +2 -0
  4. package/payload/platform/lib/embed-client/dist/index.d.ts.map +1 -1
  5. package/payload/platform/lib/embed-client/dist/index.js +50 -10
  6. package/payload/platform/lib/embed-client/dist/index.js.map +1 -1
  7. package/payload/platform/lib/embed-client/src/index.ts +57 -9
  8. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +4 -1
  9. package/payload/platform/plugins/docs/references/deployment.md +1 -0
  10. package/payload/platform/plugins/docs/references/internals.md +2 -0
  11. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-body-server-sliced.test.js +41 -2
  12. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-body-server-sliced.test.js.map +1 -1
  13. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.d.ts.map +1 -1
  14. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js +34 -4
  15. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js.map +1 -1
  16. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  17. package/payload/platform/services/claude-session-manager/dist/http-server.js +75 -2
  18. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  19. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +13 -0
  20. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
  21. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +16 -0
  22. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
  23. package/payload/server/public/assets/{AdminLoginScreens-DKwWudsP.js → AdminLoginScreens-D1X4M9wX.js} +1 -1
  24. package/payload/server/public/assets/{AdminShell-BsPsXvJv.js → AdminShell-BGZ3d3gY.js} +1 -1
  25. package/payload/server/public/assets/{Checkbox-V5ubVZG1.js → Checkbox-DjANdKoK.js} +1 -1
  26. package/payload/server/public/assets/{OperatorConversations-DOGCHZhv.css → OperatorConversations-C-eFXT7w.css} +1 -1
  27. package/payload/server/public/assets/{admin-Cfqj5N4P.js → admin--FlkQOMu.js} +1 -1
  28. package/payload/server/public/assets/{browser-CtMwlpyW.js → browser-vowQc0TJ.js} +1 -1
  29. package/payload/server/public/assets/chat-Cw6rUWiC.js +1 -0
  30. package/payload/server/public/assets/{data-7_dN5vN5.js → data-9ArhwX1q.js} +1 -1
  31. package/payload/server/public/assets/{graph-Bez5QF37.js → graph-B-64Cq-y.js} +1 -1
  32. package/payload/server/public/assets/{graph-labels-BkbaBPIP.js → graph-labels-Dtd2ckXH.js} +1 -1
  33. package/payload/server/public/assets/{operator-F5KHg5WG.js → operator-Cqn1Ne60.js} +1 -1
  34. package/payload/server/public/assets/page-DQ5D6Dg9.js +30 -0
  35. package/payload/server/public/assets/{public-wy93nfmL.js → public-CrFepd06.js} +1 -1
  36. package/payload/server/public/browser.html +4 -4
  37. package/payload/server/public/chat.html +5 -5
  38. package/payload/server/public/data.html +4 -4
  39. package/payload/server/public/graph.html +6 -6
  40. package/payload/server/public/index.html +6 -6
  41. package/payload/server/public/operator.html +7 -7
  42. package/payload/server/public/public.html +5 -5
  43. package/payload/server/server.js +185 -12
  44. package/payload/server/public/assets/chat-Bm11OGsU.js +0 -1
  45. package/payload/server/public/assets/page-CExyEcvh.js +0 -30
  46. /package/payload/server/public/assets/{OperatorConversations-DPRRlS_z.js → OperatorConversations-BkitSIiT.js} +0 -0
package/dist/index.js CHANGED
@@ -20,7 +20,7 @@ import { classifyPortHolder } from "./preflight-port-classifier.js";
20
20
  import { parsePluginList, computeInstallActions, parseExternalPlugins, findUnregisteredResyncs, } from "./lib/plugin-install.js";
21
21
  import { findPremiumMcpDirs } from "./lib/premium-mcp-discover.js";
22
22
  import { pickBindDecision, mergeSmbConf, formatSambaMarker, SAMBA_ENABLE_UNITS, } from "./samba-provision.js";
23
- import { networkInterfaces, userInfo } from "node:os";
23
+ import { networkInterfaces, userInfo, cpus } from "node:os";
24
24
  const PAYLOAD_DIR = resolve(import.meta.dirname, "../payload");
25
25
  // Brand manifest — read from payload to derive all brand-specific installation values.
26
26
  // The bundler stamps brand.json into the payload at build time.
@@ -1557,6 +1557,84 @@ function ensureOllamaServing() {
1557
1557
  logFile(` Ollama serve timed out after 30s. Log tail:\n${serveLog}`);
1558
1558
  throw new Error(`Ollama server did not respond within 30s. Log: ${ollamaLog}\n${serveLog}`);
1559
1559
  }
1560
+ // Cap ollama's CPU so an embedding burst cannot thermally hard-reset a
1561
+ // passively-cooled Pi. The platform embeds a document's summary plus every
1562
+ // section body via one `/api/embed` POST; ollama's llama-server then runs
1563
+ // flat-out across every core for the whole batch with no idle gaps, and on a
1564
+ // passively-cooled Pi 5 the SoC climbs past its soft-temp limit until the board
1565
+ // reaches its thermal ceiling and hard-resets. A systemd `CPUQuota` drop-in on
1566
+ // `ollama.service` bounds llama-server to a fraction of the cores so peak
1567
+ // temperature plateaus below the limit regardless of batch shape or which
1568
+ // platform path issues the embeds — the caller-agnostic fix, inherited by every
1569
+ // install. Quota is half the cores (rounded down, min 1) so at least half stay
1570
+ // idle for the thermal budget: 200% on a 4-core Pi. Applied only when the ollama
1571
+ // system service exists (the normal Linux path, where ollama's install.sh
1572
+ // registers `ollama.service`); a detached `ollama serve` has no unit to cap.
1573
+ // ollama is one shared host service across co-resident brands, so the drop-in is
1574
+ // host-wide with a neutral filename — idempotent across brands and reinstalls,
1575
+ // and ollama restarts only when the drop-in content actually changes. Best-
1576
+ // effort: a failure warns, never aborts the install.
1577
+ function capOllamaServiceCpu() {
1578
+ try {
1579
+ const svcCheck = spawnSync("systemctl", ["cat", "ollama"], { stdio: "pipe", timeout: 5_000 });
1580
+ if (svcCheck.status !== 0) {
1581
+ logFile(" ollama CPU cap: skipped (no ollama system service to cap)");
1582
+ return;
1583
+ }
1584
+ const coreCount = Math.max(1, cpus().length);
1585
+ const quotaCores = Math.max(1, Math.floor(coreCount / 2));
1586
+ const quotaPct = quotaCores * 100;
1587
+ const desired = `[Service]\nCPUQuota=${quotaPct}%\n`;
1588
+ const dropinDir = "/etc/systemd/system/ollama.service.d";
1589
+ const dropinPath = `${dropinDir}/10-maxy-cpuquota.conf`;
1590
+ let current = "";
1591
+ if (existsSync(dropinPath)) {
1592
+ try {
1593
+ current = readFileSync(dropinPath, "utf-8");
1594
+ }
1595
+ catch {
1596
+ // Unreadable existing drop-in — treat as changed so we rewrite + restart.
1597
+ current = "";
1598
+ }
1599
+ }
1600
+ if (current === desired) {
1601
+ logFile(` ollama CPU cap: already active (${dropinPath} matches CPUQuota=${quotaPct}%)`);
1602
+ return;
1603
+ }
1604
+ const tmpPath = "/tmp/10-maxy-ollama-cpuquota.conf";
1605
+ writeFileSync(tmpPath, desired);
1606
+ console.log(` [privileged] install ollama CPUQuota=${quotaPct}% drop-in (${quotaCores}/${coreCount} cores)`);
1607
+ shell("mkdir", ["-p", dropinDir], { sudo: true });
1608
+ shell("cp", [tmpPath, dropinPath], { sudo: true });
1609
+ spawnSync("rm", ["-f", tmpPath]);
1610
+ spawnSync("sudo", ["systemctl", "daemon-reload"], { stdio: "inherit" });
1611
+ console.log(" [privileged] systemctl restart ollama");
1612
+ spawnSync("sudo", ["systemctl", "restart", "ollama"], { stdio: "inherit", timeout: 30_000 });
1613
+ // The restart briefly drops the API. Wait for ollama to serve again before
1614
+ // returning so the immediately-following ensureOllamaServing() probe finds
1615
+ // it up and does not race the restart by spawning a second detached
1616
+ // `ollama serve` that would contend for port 11434. Bounded poll; the drop-in
1617
+ // is already on disk either way, so a timeout still applies the cap on the
1618
+ // next ollama start.
1619
+ let served = false;
1620
+ for (let elapsed = 0; elapsed < 15; elapsed += 3) {
1621
+ if (spawnSync("curl", ["-sf", "http://localhost:11434/api/tags"], { stdio: "pipe", timeout: 5_000 }).status === 0) {
1622
+ served = true;
1623
+ break;
1624
+ }
1625
+ spawnSync("sleep", ["3"]);
1626
+ }
1627
+ if (served) {
1628
+ logFile(` ollama CPU cap: enabled (wrote ${dropinPath} CPUQuota=${quotaPct}%, ollama serving again under the cap)`);
1629
+ }
1630
+ else {
1631
+ logFile(` ollama CPU cap: drop-in written (${dropinPath} CPUQuota=${quotaPct}%); ollama not serving yet after restart — cap applies on its next start`);
1632
+ }
1633
+ }
1634
+ catch (err) {
1635
+ console.error(` WARNING: failed to cap ollama CPU: ${err instanceof Error ? err.message : String(err)}`);
1636
+ }
1637
+ }
1560
1638
  function installOllama(embedModel) {
1561
1639
  if (!commandExists("ollama")) {
1562
1640
  log("5", TOTAL, "Installing Ollama...");
@@ -1565,6 +1643,9 @@ function installOllama(embedModel) {
1565
1643
  else {
1566
1644
  log("5", TOTAL, "Ollama already installed.");
1567
1645
  }
1646
+ // Bound ollama's CPU before bringing the service up so the capped unit is what
1647
+ // serves embeds (and the restart below lands the new quota).
1648
+ capOllamaServiceCpu();
1568
1649
  ensureOllamaServing();
1569
1650
  console.log(` Pulling ${embedModel} embedding model...`);
1570
1651
  logFile(` Pulling embedding model: ${embedModel}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-maxy-code",
3
- "version": "0.1.321",
3
+ "version": "0.1.322",
4
4
  "description": "Install Maxy — AI for Productive People",
5
5
  "bin": {
6
6
  "create-maxy-code": "./dist/index.js"
@@ -1,4 +1,6 @@
1
1
  export declare const EMBED_INPUT_MAX_CHARS = 8000;
2
+ export declare const EMBED_SUBBATCH_SIZE: number;
3
+ export declare const EMBED_SUBBATCH_COOLDOWN_MS: number;
2
4
  export declare function embed(text: string): Promise<number[]>;
3
5
  export declare function embedBatch(texts: string[]): Promise<number[][]>;
4
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAU1C,wBAAsB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAa3D;AAED,wBAAsB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAarE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAmB1C,eAAO,MAAM,mBAAmB,QAA6D,CAAC;AAC9F,eAAO,MAAM,0BAA0B,QAGtC,CAAC;AA4BF,wBAAsB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAa3D;AAED,wBAAsB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAoBrE"}
@@ -6,7 +6,7 @@
6
6
  // and one POST shape across the platform (Task 636). fetch-only — no node:fs —
7
7
  // so it is safe to bundle into the ESM payload.
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.EMBED_INPUT_MAX_CHARS = void 0;
9
+ exports.EMBED_SUBBATCH_COOLDOWN_MS = exports.EMBED_SUBBATCH_SIZE = exports.EMBED_INPUT_MAX_CHARS = void 0;
10
10
  exports.embed = embed;
11
11
  exports.embedBatch = embedBatch;
12
12
  const OLLAMA_URL = process.env.OLLAMA_URL ?? "http://localhost:11434";
@@ -16,28 +16,49 @@ const EMBED_MODEL = process.env.EMBED_MODEL ?? "nomic-embed-text";
16
16
  // an oversized body embeds (truncated) instead of throwing and aborting the
17
17
  // caller.
18
18
  exports.EMBED_INPUT_MAX_CHARS = 8000;
19
+ // Batch pacing (Task 960). On a passively-cooled Pi, embedding a whole
20
+ // document's sections in one `/api/embed` POST pins ollama's llama-server
21
+ // across every core for the entire batch with no idle gaps — the SoC climbs
22
+ // past its soft-temp limit and the board thermally hard-resets. embedBatch
23
+ // splits an oversized input array into sub-batches and waits a cooldown between
24
+ // them so the SoC gets cooling gaps. Centralising here means every batch caller
25
+ // (memory-ingest, memory-archive-write, memory-reindex) inherits the pacing.
26
+ // The systemd CPUQuota cap on ollama.service is the load-bearing thermal fix;
27
+ // this pacing is defence-in-depth and faster recovery. Both knobs are
28
+ // env-overridable so an operator can tune them from on-device thermal
29
+ // measurement without a code change.
30
+ function envInt(name, def) {
31
+ const raw = process.env[name];
32
+ if (raw === undefined || raw === "")
33
+ return def;
34
+ const n = Number(raw);
35
+ return Number.isFinite(n) ? n : def;
36
+ }
37
+ exports.EMBED_SUBBATCH_SIZE = Math.max(1, Math.floor(envInt("EMBED_SUBBATCH_SIZE", 32)));
38
+ exports.EMBED_SUBBATCH_COOLDOWN_MS = Math.max(0, envInt("EMBED_SUBBATCH_COOLDOWN_MS", 1000));
19
39
  function capInput(text) {
20
40
  if (text.length <= exports.EMBED_INPUT_MAX_CHARS)
21
41
  return text;
22
42
  process.stderr.write(`[embed] op=truncate origBytes=${text.length} cappedBytes=${exports.EMBED_INPUT_MAX_CHARS}\n`);
23
43
  return text.slice(0, exports.EMBED_INPUT_MAX_CHARS);
24
44
  }
25
- async function embed(text) {
26
- const input = capInput(text);
45
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
46
+ // POST one already-capped sub-batch of inputs to the embed endpoint.
47
+ async function postBatch(inputs) {
27
48
  const res = await fetch(`${OLLAMA_URL}/api/embed`, {
28
49
  method: "POST",
29
50
  headers: { "Content-Type": "application/json" },
30
- body: JSON.stringify({ model: EMBED_MODEL, input }),
51
+ body: JSON.stringify({ model: EMBED_MODEL, input: inputs }),
31
52
  });
32
53
  if (!res.ok) {
33
54
  const body = await res.text();
34
- throw new Error(`Ollama embedding failed (${res.status}): ${body}`);
55
+ throw new Error(`Ollama batch embedding failed (${res.status}): ${body}`);
35
56
  }
36
57
  const data = (await res.json());
37
- return data.embeddings[0];
58
+ return data.embeddings;
38
59
  }
39
- async function embedBatch(texts) {
40
- const input = texts.map(capInput);
60
+ async function embed(text) {
61
+ const input = capInput(text);
41
62
  const res = await fetch(`${OLLAMA_URL}/api/embed`, {
42
63
  method: "POST",
43
64
  headers: { "Content-Type": "application/json" },
@@ -45,9 +66,28 @@ async function embedBatch(texts) {
45
66
  });
46
67
  if (!res.ok) {
47
68
  const body = await res.text();
48
- throw new Error(`Ollama batch embedding failed (${res.status}): ${body}`);
69
+ throw new Error(`Ollama embedding failed (${res.status}): ${body}`);
49
70
  }
50
71
  const data = (await res.json());
51
- return data.embeddings;
72
+ return data.embeddings[0];
73
+ }
74
+ async function embedBatch(texts) {
75
+ const inputs = texts.map(capInput);
76
+ // Common case — an array within the sub-batch size is one POST, unchanged.
77
+ if (inputs.length <= exports.EMBED_SUBBATCH_SIZE) {
78
+ return postBatch(inputs);
79
+ }
80
+ // Oversized: split into sub-batches, cooling between each (not after the
81
+ // last). Vectors are concatenated in input order so callers can keep their
82
+ // index-aligned `embeddings[i]` ↔ `texts[i]` contract.
83
+ const out = [];
84
+ for (let i = 0; i < inputs.length; i += exports.EMBED_SUBBATCH_SIZE) {
85
+ if (i > 0 && exports.EMBED_SUBBATCH_COOLDOWN_MS > 0) {
86
+ await sleep(exports.EMBED_SUBBATCH_COOLDOWN_MS);
87
+ }
88
+ const chunk = inputs.slice(i, i + exports.EMBED_SUBBATCH_SIZE);
89
+ out.push(...(await postBatch(chunk)));
90
+ }
91
+ return out;
52
92
  }
53
93
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,yEAAyE;AACzE,6EAA6E;AAC7E,2EAA2E;AAC3E,6EAA6E;AAC7E,+EAA+E;AAC/E,gDAAgD;;;AAmBhD,sBAaC;AAED,gCAaC;AA7CD,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,wBAAwB,CAAC;AACtE,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,kBAAkB,CAAC;AAElE,2EAA2E;AAC3E,gFAAgF;AAChF,4EAA4E;AAC5E,UAAU;AACG,QAAA,qBAAqB,GAAG,IAAI,CAAC;AAE1C,SAAS,QAAQ,CAAC,IAAY;IAC5B,IAAI,IAAI,CAAC,MAAM,IAAI,6BAAqB;QAAE,OAAO,IAAI,CAAC;IACtD,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,iCAAiC,IAAI,CAAC,MAAM,gBAAgB,6BAAqB,IAAI,CACtF,CAAC;IACF,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,6BAAqB,CAAC,CAAC;AAC9C,CAAC;AAEM,KAAK,UAAU,KAAK,CAAC,IAAY;IACtC,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC7B,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,UAAU,YAAY,EAAE;QACjD,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;KACpD,CAAC,CAAC;IACH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,4BAA4B,GAAG,CAAC,MAAM,MAAM,IAAI,EAAE,CAAC,CAAC;IACtE,CAAC;IACD,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAA+B,CAAC;IAC9D,OAAO,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;AAC5B,CAAC;AAEM,KAAK,UAAU,UAAU,CAAC,KAAe;IAC9C,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAClC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,UAAU,YAAY,EAAE;QACjD,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;KACpD,CAAC,CAAC;IACH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,kCAAkC,GAAG,CAAC,MAAM,MAAM,IAAI,EAAE,CAAC,CAAC;IAC5E,CAAC;IACD,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAA+B,CAAC;IAC9D,OAAO,IAAI,CAAC,UAAU,CAAC;AACzB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -14,6 +14,29 @@ const EMBED_MODEL = process.env.EMBED_MODEL ?? "nomic-embed-text";
14
14
  // caller.
15
15
  export const EMBED_INPUT_MAX_CHARS = 8000;
16
16
 
17
+ // Batch pacing (Task 960). On a passively-cooled Pi, embedding a whole
18
+ // document's sections in one `/api/embed` POST pins ollama's llama-server
19
+ // across every core for the entire batch with no idle gaps — the SoC climbs
20
+ // past its soft-temp limit and the board thermally hard-resets. embedBatch
21
+ // splits an oversized input array into sub-batches and waits a cooldown between
22
+ // them so the SoC gets cooling gaps. Centralising here means every batch caller
23
+ // (memory-ingest, memory-archive-write, memory-reindex) inherits the pacing.
24
+ // The systemd CPUQuota cap on ollama.service is the load-bearing thermal fix;
25
+ // this pacing is defence-in-depth and faster recovery. Both knobs are
26
+ // env-overridable so an operator can tune them from on-device thermal
27
+ // measurement without a code change.
28
+ function envInt(name: string, def: number): number {
29
+ const raw = process.env[name];
30
+ if (raw === undefined || raw === "") return def;
31
+ const n = Number(raw);
32
+ return Number.isFinite(n) ? n : def;
33
+ }
34
+ export const EMBED_SUBBATCH_SIZE = Math.max(1, Math.floor(envInt("EMBED_SUBBATCH_SIZE", 32)));
35
+ export const EMBED_SUBBATCH_COOLDOWN_MS = Math.max(
36
+ 0,
37
+ envInt("EMBED_SUBBATCH_COOLDOWN_MS", 1000),
38
+ );
39
+
17
40
  function capInput(text: string): string {
18
41
  if (text.length <= EMBED_INPUT_MAX_CHARS) return text;
19
42
  process.stderr.write(
@@ -22,23 +45,26 @@ function capInput(text: string): string {
22
45
  return text.slice(0, EMBED_INPUT_MAX_CHARS);
23
46
  }
24
47
 
25
- export async function embed(text: string): Promise<number[]> {
26
- const input = capInput(text);
48
+ const sleep = (ms: number): Promise<void> =>
49
+ new Promise((resolve) => setTimeout(resolve, ms));
50
+
51
+ // POST one already-capped sub-batch of inputs to the embed endpoint.
52
+ async function postBatch(inputs: string[]): Promise<number[][]> {
27
53
  const res = await fetch(`${OLLAMA_URL}/api/embed`, {
28
54
  method: "POST",
29
55
  headers: { "Content-Type": "application/json" },
30
- body: JSON.stringify({ model: EMBED_MODEL, input }),
56
+ body: JSON.stringify({ model: EMBED_MODEL, input: inputs }),
31
57
  });
32
58
  if (!res.ok) {
33
59
  const body = await res.text();
34
- throw new Error(`Ollama embedding failed (${res.status}): ${body}`);
60
+ throw new Error(`Ollama batch embedding failed (${res.status}): ${body}`);
35
61
  }
36
62
  const data = (await res.json()) as { embeddings: number[][] };
37
- return data.embeddings[0];
63
+ return data.embeddings;
38
64
  }
39
65
 
40
- export async function embedBatch(texts: string[]): Promise<number[][]> {
41
- const input = texts.map(capInput);
66
+ export async function embed(text: string): Promise<number[]> {
67
+ const input = capInput(text);
42
68
  const res = await fetch(`${OLLAMA_URL}/api/embed`, {
43
69
  method: "POST",
44
70
  headers: { "Content-Type": "application/json" },
@@ -46,8 +72,30 @@ export async function embedBatch(texts: string[]): Promise<number[][]> {
46
72
  });
47
73
  if (!res.ok) {
48
74
  const body = await res.text();
49
- throw new Error(`Ollama batch embedding failed (${res.status}): ${body}`);
75
+ throw new Error(`Ollama embedding failed (${res.status}): ${body}`);
50
76
  }
51
77
  const data = (await res.json()) as { embeddings: number[][] };
52
- return data.embeddings;
78
+ return data.embeddings[0];
79
+ }
80
+
81
+ export async function embedBatch(texts: string[]): Promise<number[][]> {
82
+ const inputs = texts.map(capInput);
83
+
84
+ // Common case — an array within the sub-batch size is one POST, unchanged.
85
+ if (inputs.length <= EMBED_SUBBATCH_SIZE) {
86
+ return postBatch(inputs);
87
+ }
88
+
89
+ // Oversized: split into sub-batches, cooling between each (not after the
90
+ // last). Vectors are concatenated in input order so callers can keep their
91
+ // index-aligned `embeddings[i]` ↔ `texts[i]` contract.
92
+ const out: number[][] = [];
93
+ for (let i = 0; i < inputs.length; i += EMBED_SUBBATCH_SIZE) {
94
+ if (i > 0 && EMBED_SUBBATCH_COOLDOWN_MS > 0) {
95
+ await sleep(EMBED_SUBBATCH_COOLDOWN_MS);
96
+ }
97
+ const chunk = inputs.slice(i, i + EMBED_SUBBATCH_SIZE);
98
+ out.push(...(await postBatch(chunk)));
99
+ }
100
+ return out;
53
101
  }
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: platform-architecture
3
3
  description: Use when grounding any documented-surface claim about what Maxy ships — plugins, skills, specialists, install/deploy flows, internals. This is the install catalogue, not evidence of what is enabled on the current account. For install state on this account, call `capabilities-here`; for documented surface, cite the `Source:` URL inline.
4
- content-hash: sha256:fe4862e501a7da76a576d077c3313fb611b3db8b5f9c52ac272b193d16b0d08b
4
+ content-hash: sha256:009036cb2953a53e5a5d85f5cdf3399d4a7dd12c40fa17e03ebcb46ebeec87a7
5
5
  brand: maxy-code
6
6
  product-name: Maxy
7
7
  ---
@@ -2995,6 +2995,8 @@ If Ollama is unavailable at write time, the node is stored without an embedding
2995
2995
 
2996
2996
  The `memory-reindex` tool backfills missing embeddings by iterating nodes where `embedding IS NULL`. It uses the bounded representation (the `summary` for document/archive labels, never the raw body), embeds in batches with a per-node fallback on batch error, and **skips and continues** past a failing node — keeping skipped nodes out of the re-fetch via an exclude set — instead of aborting the whole run. `embed()`/`embedBatch()` cap oversized input (`EMBED_INPUT_MAX_CHARS`) before the Ollama POST, so a large body embeds truncated rather than throwing. Per-node `[reindex] op=embedded|skip-error` lines and a terminal `[reindex] op=done processed=N skipped=M`.
2997
2997
 
2998
+ `embedBatch()` also paces large batches. A document's summary plus every section body in one `/api/embed` POST pins ollama's `llama-server` across all cores for the whole batch with no idle gaps; on a passively-cooled Pi the SoC climbs past its soft-temp limit and the board thermally hard-resets. `embedBatch` splits an input array longer than `EMBED_SUBBATCH_SIZE` (default 32) into sub-batches, waiting `EMBED_SUBBATCH_COOLDOWN_MS` (default 1000) between them so the SoC gets cooling gaps; vectors are concatenated in input order. Both knobs are env-overridable for on-device tuning. The load-bearing thermal protection is the installer's `CPUQuota` cap on `ollama.service` (every embed caller is bounded there regardless of batch shape); this pacing is defence-in-depth. The UI's file-index path uses its own single-input embed client in `platform/ui/app/lib/neo4j-store.ts` (one embed per file, not a batch), so it is covered by the CPU cap, not by `embedBatch` pacing.
2999
+
2998
3000
  ---
2999
3001
 
3000
3002
  ## Knowledge Document Hierarchy
@@ -3703,6 +3705,7 @@ A single Pi or laptop can host more than one brand (for example Maxy and Real Ag
3703
3705
  - **Brand-isolated Neo4j:** when a brand provisions a dedicated Neo4j instance (any port other than 7687), the installer stops and disables the apt-package's system `neo4j.service` after enabling the brand-dedicated unit, so only one Neo4j process holds the shared `/var/lib/neo4j/run/` PID file. The seed step receives the brand-correct `NEO4J_URI` and `NEO4J_PASSWORD` as explicit environment variables — the seed script no longer carries a `bolt://localhost:7687` default. A failed dedicated start aborts the install loudly with a journalctl tail; there is no silent fallback to the system instance. Stop/disable targets the literal `neo4j.service` only, so peer brands running their own `neo4j-{brand}.service` are unaffected.
3704
3706
  - **Peer-aware system-unit guard:** before stopping the system `neo4j.service`, the installer checks whether any other brand on the device still depends on it — that is, has `NEO4J_URI=bolt://localhost:7687` in its `~/.<peer>/.env`. If so, the system unit is left enabled and active, and the install log shows `[neo4j] system unit kept active — peer brand <name> depends on port 7687` instead of the usual `[neo4j] disabling system unit` line. This prevents a `create-realagent` install from disabling Maxy's database on a host where Maxy still uses the shared system instance (the earlier platform fixes reproducer on Neo's laptop, 2026-04-28). On single-brand hosts and on multi-brand hosts where every peer runs a dedicated port, behaviour is unchanged. The dedicated unit exports `NEO4J_HOME=<per-brand-data-dir>` alongside `NEO4J_CONF`, so `server.directories.run`, `server.directories.plugins`, and `server.directories.import` resolve per-brand — no collision with `/var/lib/neo4j/run/neo4j.pid`. The conf sed-overrides, mkdir-p, chown, and unit-write are idempotent and re-run on every install, so a host whose prior install left a broken unit recovers on retry.
3705
3707
  - **Shared:** both brands share the system Chromium/VNC stack, the Ollama model server, and the `cloudflared` command itself. Browser automation is serialised — one admin session at a time across both brands.
3708
+ - **Thermal cap on shared Ollama:** the installer writes a host-wide systemd `CPUQuota` drop-in (`/etc/systemd/system/ollama.service.d/10-maxy-cpuquota.conf`) bounding `ollama.service` to half the cores (200% on a 4-core Pi). An embedding burst — a document's summary plus every section body in one POST — otherwise pins `llama-server` across all cores with no idle gaps until a passively-cooled Pi crosses its soft-temp limit and hard-resets. The cap keeps peak temperature below the limit regardless of which brand or platform path issues the embeds. The drop-in has a neutral filename and is rewritten only when its content changes, so it is idempotent across brands and reinstalls. Applied only where the `ollama.service` system unit exists; a detached `ollama serve` has no unit to cap.
3706
3709
 
3707
3710
  To install a second brand on a device that already runs the first, just run the other installer. No flags needed for isolation:
3708
3711
 
@@ -253,6 +253,7 @@ A single Pi or laptop can host more than one brand (for example Maxy and Real Ag
253
253
  - **Brand-isolated Neo4j:** when a brand provisions a dedicated Neo4j instance (any port other than 7687), the installer stops and disables the apt-package's system `neo4j.service` after enabling the brand-dedicated unit, so only one Neo4j process holds the shared `/var/lib/neo4j/run/` PID file. The seed step receives the brand-correct `NEO4J_URI` and `NEO4J_PASSWORD` as explicit environment variables — the seed script no longer carries a `bolt://localhost:7687` default. A failed dedicated start aborts the install loudly with a journalctl tail; there is no silent fallback to the system instance. Stop/disable targets the literal `neo4j.service` only, so peer brands running their own `neo4j-{brand}.service` are unaffected.
254
254
  - **Peer-aware system-unit guard:** before stopping the system `neo4j.service`, the installer checks whether any other brand on the device still depends on it — that is, has `NEO4J_URI=bolt://localhost:7687` in its `~/.<peer>/.env`. If so, the system unit is left enabled and active, and the install log shows `[neo4j] system unit kept active — peer brand <name> depends on port 7687` instead of the usual `[neo4j] disabling system unit` line. This prevents a `create-realagent` install from disabling Maxy's database on a host where Maxy still uses the shared system instance (the earlier platform fixes reproducer on Neo's laptop, 2026-04-28). On single-brand hosts and on multi-brand hosts where every peer runs a dedicated port, behaviour is unchanged. The dedicated unit exports `NEO4J_HOME=<per-brand-data-dir>` alongside `NEO4J_CONF`, so `server.directories.run`, `server.directories.plugins`, and `server.directories.import` resolve per-brand — no collision with `/var/lib/neo4j/run/neo4j.pid`. The conf sed-overrides, mkdir-p, chown, and unit-write are idempotent and re-run on every install, so a host whose prior install left a broken unit recovers on retry.
255
255
  - **Shared:** both brands share the system Chromium/VNC stack, the Ollama model server, and the `cloudflared` command itself. Browser automation is serialised — one admin session at a time across both brands.
256
+ - **Thermal cap on shared Ollama:** the installer writes a host-wide systemd `CPUQuota` drop-in (`/etc/systemd/system/ollama.service.d/10-maxy-cpuquota.conf`) bounding `ollama.service` to half the cores (200% on a 4-core Pi). An embedding burst — a document's summary plus every section body in one POST — otherwise pins `llama-server` across all cores with no idle gaps until a passively-cooled Pi crosses its soft-temp limit and hard-resets. The cap keeps peak temperature below the limit regardless of which brand or platform path issues the embeds. The drop-in has a neutral filename and is rewritten only when its content changes, so it is idempotent across brands and reinstalls. Applied only where the `ollama.service` system unit exists; a detached `ollama serve` has no unit to cap.
256
257
 
257
258
  To install a second brand on a device that already runs the first, just run the other installer. No flags needed for isolation:
258
259
 
@@ -95,6 +95,8 @@ If Ollama is unavailable at write time, the node is stored without an embedding
95
95
 
96
96
  The `memory-reindex` tool backfills missing embeddings by iterating nodes where `embedding IS NULL`. It uses the bounded representation (the `summary` for document/archive labels, never the raw body), embeds in batches with a per-node fallback on batch error, and **skips and continues** past a failing node — keeping skipped nodes out of the re-fetch via an exclude set — instead of aborting the whole run. `embed()`/`embedBatch()` cap oversized input (`EMBED_INPUT_MAX_CHARS`) before the Ollama POST, so a large body embeds truncated rather than throwing. Per-node `[reindex] op=embedded|skip-error` lines and a terminal `[reindex] op=done processed=N skipped=M`.
97
97
 
98
+ `embedBatch()` also paces large batches. A document's summary plus every section body in one `/api/embed` POST pins ollama's `llama-server` across all cores for the whole batch with no idle gaps; on a passively-cooled Pi the SoC climbs past its soft-temp limit and the board thermally hard-resets. `embedBatch` splits an input array longer than `EMBED_SUBBATCH_SIZE` (default 32) into sub-batches, waiting `EMBED_SUBBATCH_COOLDOWN_MS` (default 1000) between them so the SoC gets cooling gaps; vectors are concatenated in input order. Both knobs are env-overridable for on-device tuning. The load-bearing thermal protection is the installer's `CPUQuota` cap on `ollama.service` (every embed caller is bounded there regardless of batch shape); this pacing is defence-in-depth. The UI's file-index path uses its own single-input embed client in `platform/ui/app/lib/neo4j-store.ts` (one embed per file, not a batch), so it is covered by the CPU cap, not by `embedBatch` pacing.
99
+
98
100
  ---
99
101
 
100
102
  ## Knowledge Document Hierarchy
@@ -43,11 +43,50 @@ describe("deriveSectionBodies", () => {
43
43
  const sections = [{ sourceStart: 7, sourceEnd: 3 }];
44
44
  expect(() => deriveSectionBodies(sections, cachedText, "att-rev")).toThrow(/reversed offsets.*sectionIndex=0.*sourceStart=7.*sourceEnd=3.*att-rev/);
45
45
  });
46
- it("(d) throws on out-of-bounds sourceEnd", () => {
46
+ it("(d) throws on a gross out-of-bounds sourceEnd (overrun beyond tolerance)", () => {
47
47
  const cachedText = "Short text."; // length 11
48
- const sections = [{ sourceStart: 0, sourceEnd: 100 }];
48
+ const sections = [{ sourceStart: 0, sourceEnd: 100 }]; // overrun 89 >> 32
49
49
  expect(() => deriveSectionBodies(sections, cachedText, "att-oob")).toThrow(/out-of-bounds sourceEnd.*sectionIndex=0.*sourceEnd=100.*textLength=11/);
50
50
  });
51
+ it("(d-boundary) returns the full slice when sourceEnd === textLength (no clamp, no log)", () => {
52
+ const cachedText = "Exactly to the end."; // length 19
53
+ const sections = [{ sourceStart: 0, sourceEnd: 19 }];
54
+ const result = deriveSectionBodies(sections, cachedText, "att-edge");
55
+ expect(result).toEqual(["Exactly to the end."]);
56
+ expect(logSpy).not.toHaveBeenCalled();
57
+ });
58
+ it("(d-clamp) clamps a small end-of-text overrun to textLength and logs attributably", () => {
59
+ const cachedText = "0123456789"; // length 10
60
+ const sections = [{ sourceStart: 2, sourceEnd: 13 }]; // overrun 3, start in-bounds
61
+ const result = deriveSectionBodies(sections, cachedText, "att-clamp");
62
+ expect(result).toEqual(["23456789"]); // slice(2, 10)
63
+ expect(logSpy).toHaveBeenCalledTimes(1);
64
+ expect(logSpy.mock.calls[0][0]).toContain("clamped out-of-bounds sourceEnd");
65
+ expect(logSpy.mock.calls[0][0]).toContain("sectionIndex=0");
66
+ expect(logSpy.mock.calls[0][0]).toContain("sourceStart=2");
67
+ expect(logSpy.mock.calls[0][0]).toContain("sourceEnd=13");
68
+ expect(logSpy.mock.calls[0][0]).toContain("textLength=10");
69
+ expect(logSpy.mock.calls[0][0]).toContain("overrun=3");
70
+ expect(logSpy.mock.calls[0][0]).toContain("att-clamp");
71
+ });
72
+ it("(d-clamp-edge) clamps an overrun exactly at the tolerance (32)", () => {
73
+ const cachedText = "0123456789"; // length 10
74
+ const sections = [{ sourceStart: 0, sourceEnd: 42 }]; // overrun exactly 32
75
+ const result = deriveSectionBodies(sections, cachedText, "att-clamp-edge");
76
+ expect(result).toEqual(["0123456789"]);
77
+ expect(logSpy).toHaveBeenCalledTimes(1);
78
+ expect(logSpy.mock.calls[0][0]).toContain("clamped out-of-bounds sourceEnd");
79
+ });
80
+ it("(d-overrun) throws when the overrun is one past the tolerance", () => {
81
+ const cachedText = "0123456789"; // length 10
82
+ const sections = [{ sourceStart: 0, sourceEnd: 43 }]; // overrun 33 > 32
83
+ expect(() => deriveSectionBodies(sections, cachedText, "att-over")).toThrow(/out-of-bounds sourceEnd.*sectionIndex=0.*sourceEnd=43.*textLength=10.*overrun=33/);
84
+ });
85
+ it("(d-start-past-eof) throws when sourceStart is past end-of-text even within tolerance", () => {
86
+ const cachedText = "0123456789"; // length 10
87
+ const sections = [{ sourceStart: 12, sourceEnd: 12 }]; // start past EOF, end overrun 2
88
+ expect(() => deriveSectionBodies(sections, cachedText, "att-start-oob")).toThrow(/sourceStart past end-of-text.*sectionIndex=0.*sourceStart=12.*textLength=10/);
89
+ });
51
90
  it("(d2) throws on negative sourceStart", () => {
52
91
  const cachedText = "Some text.";
53
92
  const sections = [{ sourceStart: -1, sourceEnd: 5 }];
@@ -1 +1 @@
1
- {"version":3,"file":"memory-ingest-body-server-sliced.test.js","sourceRoot":"","sources":["../../../src/tools/__tests__/memory-ingest-body-server-sliced.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"memory-ingest-body-server-sliced.test.js","sourceRoot":"","sources":["../../../src/tools/__tests__/memory-ingest-body-server-sliced.test.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"memory-ingest.d.ts","sourceRoot":"","sources":["../../src/tools/memory-ingest.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"memory-ingest.d.ts","sourceRoot":"","sources":["../../src/tools/memory-ingest.ts"],"names":[],"mappings":"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"}
@@ -55,6 +55,14 @@ import { isAllowedTypedEdge, isTypedEntityEdgeType } from "../lib/typed-edge-sch
55
55
  const PREVIEW_LENGTH = 150;
56
56
  const PROVENANCE_AGENT = "document-ingest";
57
57
  const PROVENANCE_SOURCE = "document";
58
+ // Max characters a section's `sourceEnd` may exceed the cached text length and
59
+ // still be treated as a benign end-of-text overshoot (clamped, not thrown).
60
+ // The classifier (librarian LLM) emits offsets in-turn; its final section's
61
+ // `sourceEnd` estimate routinely lands a few characters past the trimmed
62
+ // document end (trailing whitespace `extractCache` removed + LLM rounding).
63
+ // That overrun is content-independent and small. A larger overrun means the
64
+ // classifier lost positional track — that stays a loud, attributable throw.
65
+ const SECTION_BODY_OVERRUN_TOLERANCE = 32;
58
66
  // ---------------------------------------------------------------------------
59
67
  // Section-body derivation. Shared by both ingest paths (Task 465 doc path,
60
68
  // Task 468 conversation-archive path).
@@ -89,9 +97,31 @@ export function deriveSectionBodies(sections, cachedText, sourceKey = "unknown")
89
97
  throw new Error(`[memory-ingest:derive] reversed offsets: sectionIndex=${i} ` +
90
98
  `sourceStart=${sourceStart} sourceEnd=${sourceEnd} sourceKey=${sourceKey}`);
91
99
  }
100
+ // The classifier's offsets are LLM estimates. Its final section's
101
+ // `sourceEnd` can land a few chars past the trimmed document end — a
102
+ // benign end-of-text overshoot. Clamp that to `textLength` (recovering the
103
+ // section's true content, start→EOF) with a loud, attributable adjustment.
104
+ // A start past end-of-text, or an overrun beyond the tolerance, means the
105
+ // classifier lost positional track: throw, so the ingest fails loudly
106
+ // rather than writing a bloated or truncated body.
107
+ let effectiveEnd = sourceEnd;
92
108
  if (sourceEnd > textLength) {
93
- throw new Error(`[memory-ingest:derive] out-of-bounds sourceEnd: sectionIndex=${i} ` +
94
- `sourceEnd=${sourceEnd} textLength=${textLength} sourceKey=${sourceKey}`);
109
+ const overrun = sourceEnd - textLength;
110
+ if (sourceStart <= textLength && overrun <= SECTION_BODY_OVERRUN_TOLERANCE) {
111
+ console.error(`[memory-ingest:derive] clamped out-of-bounds sourceEnd: sectionIndex=${i} ` +
112
+ `sourceStart=${sourceStart} sourceEnd=${sourceEnd} textLength=${textLength} ` +
113
+ `overrun=${overrun} sourceKey=${sourceKey}`);
114
+ effectiveEnd = textLength;
115
+ }
116
+ else if (sourceStart > textLength) {
117
+ throw new Error(`[memory-ingest:derive] sourceStart past end-of-text: sectionIndex=${i} ` +
118
+ `sourceStart=${sourceStart} textLength=${textLength} sourceKey=${sourceKey}`);
119
+ }
120
+ else {
121
+ throw new Error(`[memory-ingest:derive] out-of-bounds sourceEnd: sectionIndex=${i} ` +
122
+ `sourceEnd=${sourceEnd} textLength=${textLength} overrun=${overrun} ` +
123
+ `tolerance=${SECTION_BODY_OVERRUN_TOLERANCE} sourceKey=${sourceKey}`);
124
+ }
95
125
  }
96
126
  if (agentBody !== undefined) {
97
127
  const agentBodyPreview = JSON.stringify(agentBody.slice(0, 120));
@@ -99,12 +129,12 @@ export function deriveSectionBodies(sections, cachedText, sourceKey = "unknown")
99
129
  `agentBodyLength=${agentBody.length} ` +
100
130
  `agentBodyPreview=${agentBodyPreview} sourceKey=${sourceKey}`);
101
131
  }
102
- if (sourceStart === sourceEnd) {
132
+ if (sourceStart === effectiveEnd) {
103
133
  console.error(`[memory-ingest:derive] zero-width section: sectionIndex=${i} ` +
104
134
  `offset=${sourceStart} sourceKey=${sourceKey}`);
105
135
  return "";
106
136
  }
107
- return cachedText.slice(sourceStart, sourceEnd);
137
+ return cachedText.slice(sourceStart, effectiveEnd);
108
138
  });
109
139
  }
110
140
  /** Identity-kind anchor edge writer set (UserProfile → Section:Kind). */