@rubytech/create-maxy-code 0.1.232 → 0.1.233

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 (64) hide show
  1. package/dist/__tests__/rss-sampler-installer.test.js +27 -0
  2. package/dist/index.js +61 -0
  3. package/package.json +1 -1
  4. package/payload/platform/plugins/memory/PLUGIN.md +5 -1
  5. package/payload/platform/plugins/memory/mcp/dist/index.js +23 -7
  6. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  7. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-revision.test.js +16 -0
  8. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-revision.test.js.map +1 -1
  9. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/phase-backlinks.test.d.ts +2 -0
  10. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/phase-backlinks.test.d.ts.map +1 -0
  11. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/phase-backlinks.test.js +24 -0
  12. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/phase-backlinks.test.js.map +1 -0
  13. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/phase-tag-normalisation.test.d.ts +2 -0
  14. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/phase-tag-normalisation.test.d.ts.map +1 -0
  15. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/phase-tag-normalisation.test.js +51 -0
  16. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/phase-tag-normalisation.test.js.map +1 -0
  17. package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-revision.d.ts +31 -0
  18. package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-revision.d.ts.map +1 -1
  19. package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-revision.js.map +1 -1
  20. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/index.d.ts +7 -7
  21. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/index.js +7 -7
  22. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-backlinks.d.ts +15 -0
  23. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-backlinks.d.ts.map +1 -0
  24. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-backlinks.js +37 -0
  25. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-backlinks.js.map +1 -0
  26. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-tag-normalisation.d.ts +25 -0
  27. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-tag-normalisation.d.ts.map +1 -0
  28. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-tag-normalisation.js +90 -0
  29. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-tag-normalisation.js.map +1 -0
  30. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-derive-insights.test.js +117 -0
  31. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-derive-insights.test.js.map +1 -1
  32. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-inline-rewrite.test.d.ts +2 -0
  33. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-inline-rewrite.test.d.ts.map +1 -0
  34. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-inline-rewrite.test.js +66 -0
  35. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-inline-rewrite.test.js.map +1 -0
  36. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.d.ts +25 -0
  37. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.d.ts.map +1 -1
  38. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.js +26 -0
  39. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.js.map +1 -1
  40. package/payload/platform/plugins/memory/mcp/dist/tools/memory-dream-run.d.ts +6 -3
  41. package/payload/platform/plugins/memory/mcp/dist/tools/memory-dream-run.d.ts.map +1 -1
  42. package/payload/platform/plugins/memory/mcp/dist/tools/memory-dream-run.js +32 -5
  43. package/payload/platform/plugins/memory/mcp/dist/tools/memory-dream-run.js.map +1 -1
  44. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.d.ts +37 -0
  45. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.d.ts.map +1 -1
  46. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js +72 -0
  47. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js.map +1 -1
  48. package/payload/platform/plugins/scheduling/mcp/dist/index.js +1 -1
  49. package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -1
  50. package/payload/platform/scripts/rss-sampler.sh +58 -0
  51. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts +4 -0
  52. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  53. package/payload/platform/services/claude-session-manager/dist/http-server.js +4 -0
  54. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  55. package/payload/platform/services/claude-session-manager/dist/index.js +23 -0
  56. package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
  57. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +14 -2
  58. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
  59. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +27 -0
  60. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
  61. package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts +6 -0
  62. package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts.map +1 -1
  63. package/payload/platform/services/claude-session-manager/dist/rc-daemon.js +2 -0
  64. package/payload/platform/services/claude-session-manager/dist/rc-daemon.js.map +1 -1
@@ -0,0 +1,27 @@
1
+ // Static-grep contracts for Task 600 installer additions:
2
+ // swap provisioning and rss-sampler service registration.
3
+ //
4
+ // Why static-grep, not behaviour: the install branches spawn (sudo, systemctl,
5
+ // dphys-swapfile), so end-to-end reproduction belongs to the manual smoke path.
6
+ // The unit suite pins the branch points so a future refactor that removes them
7
+ // fails loudly here.
8
+ import test from "node:test";
9
+ import assert from "node:assert/strict";
10
+ import { readFileSync } from "node:fs";
11
+ import { fileURLToPath } from "node:url";
12
+ import { dirname, resolve } from "node:path";
13
+ const here = dirname(fileURLToPath(import.meta.url));
14
+ const INDEX_TS = resolve(here, "../../src/index.ts");
15
+ const SRC = readFileSync(INDEX_TS, "utf-8");
16
+ test("installSystemDeps Linux branch installs dphys-swapfile and writes CONF_SWAPSIZE=2048", () => {
17
+ assert.match(SRC, /apt-get.*install.*dphys-swapfile/s, "apt install dphys-swapfile must be present");
18
+ assert.match(SRC, /CONF_SWAPSIZE=2048/, "swap size must be 2048MB");
19
+ assert.match(SRC, /CONF_MAXSWAP=4096/, "swap max must be 4096MB");
20
+ assert.match(SRC, /dphys-swapfile.*restart|restart.*dphys-swapfile/s, "dphys-swapfile service must be restarted");
21
+ });
22
+ test("installService Linux branch writes rss-sampler user unit and enables it", () => {
23
+ assert.match(SRC, /rss-sampler\.service/, "rss-sampler.service unit must be written");
24
+ assert.match(SRC, /rss-sampler\.sh/, "rss-sampler.sh must appear in ExecStart");
25
+ assert.match(SRC, /rss-sampler.*enable|enable.*rss-sampler/s, "rss-sampler must be enabled");
26
+ assert.match(SRC, /rss-sampler installed/, "rss-sampler install must emit a logFile entry");
27
+ });
package/dist/index.js CHANGED
@@ -709,6 +709,42 @@ function installSystemDeps() {
709
709
  // so deployPayload's writeChromiumBinaryPathFile and installService's
710
710
  // buildMaxyUnitFile both have a real absolute path to thread through.
711
711
  ensureNonSnapChromium();
712
+ // Task 600 — provision swap so the OOM-killer has a buffer to work in
713
+ // instead of silently freezing the box. dphys-swapfile is the standard
714
+ // Raspberry Pi OS swap manager. CONF_MAXSWAP raised to 4096 so the
715
+ // operator can manually grow the swap file without reinstalling.
716
+ // Best-effort: a failure here warns but does not abort the install.
717
+ try {
718
+ console.log(" [privileged] apt-get install dphys-swapfile");
719
+ shell("apt-get", ["install", "-y", "dphys-swapfile"], { sudo: true, bestEffort: true });
720
+ const desiredSwapConf = "CONF_SWAPSIZE=2048\nCONF_MAXSWAP=4096\n";
721
+ const swapConfPath = "/etc/dphys-swapfile";
722
+ let swapCurrent = "";
723
+ if (existsSync(swapConfPath)) {
724
+ try {
725
+ swapCurrent = readFileSync(swapConfPath, "utf-8");
726
+ }
727
+ catch {
728
+ swapCurrent = "";
729
+ }
730
+ }
731
+ if (swapCurrent === desiredSwapConf) {
732
+ logFile(" swap: already configured (CONF_SWAPSIZE=2048 CONF_MAXSWAP=4096)");
733
+ }
734
+ else {
735
+ const swapTmpPath = `/tmp/dphys-swapfile.${process.pid}`;
736
+ writeFileSync(swapTmpPath, desiredSwapConf);
737
+ console.log(" [privileged] install /etc/dphys-swapfile (CONF_SWAPSIZE=2048 CONF_MAXSWAP=4096)");
738
+ shell("cp", [swapTmpPath, swapConfPath], { sudo: true });
739
+ spawnSync("rm", ["-f", swapTmpPath]);
740
+ console.log(" [privileged] systemctl restart dphys-swapfile");
741
+ shell("systemctl", ["restart", "dphys-swapfile"], { sudo: true, bestEffort: true });
742
+ logFile(" swap: configured CONF_SWAPSIZE=2048 CONF_MAXSWAP=4096 and restarted dphys-swapfile");
743
+ }
744
+ }
745
+ catch (err) {
746
+ console.error(` WARNING: failed to provision swap: ${err instanceof Error ? err.message : String(err)}`);
747
+ }
712
748
  // Hostname resolution — four sources, in priority order:
713
749
  // 1. --hostname flag (unconditional — the caller is the authority)
714
750
  // 2. OS detection on same-brand upgrade (service exists → keep whatever is currently set)
@@ -3084,6 +3120,28 @@ function installService() {
3084
3120
  const claudePtysSliceName = "claude-ptys.slice";
3085
3121
  writeFileSync(join(serviceDir, claudePtysSliceName), buildClaudePtysSliceUnitFile());
3086
3122
  logFile(` ${claudePtysSliceName}: Task 250 slice for claude-session-*.scope cohort`);
3123
+ // Task 600 — per-brand RSS sampler: a long-running user service that
3124
+ // periodically writes per-claude.exe RSS + aggregate RSS to disk.
3125
+ // Named per-brand (BRAND.hostname prefix) so two brands on the same
3126
+ // device each own their own sampler writing to their own log path.
3127
+ const rssSamplerUnitName = `${BRAND.hostname}-rss-sampler.service`;
3128
+ const rssSamplerScriptPath = resolve(INSTALL_DIR, "platform/scripts/rss-sampler.sh");
3129
+ const rssSamplerLogPath = join(persistDir, "rss-sampler.log");
3130
+ const rssSamplerUnit = `[Unit]
3131
+ Description=${BRAND.productName} RSS Sampler
3132
+
3133
+ [Service]
3134
+ Type=simple
3135
+ ExecStart=/bin/bash ${rssSamplerScriptPath}
3136
+ Environment=LOG_PATH=${rssSamplerLogPath}
3137
+ Restart=on-failure
3138
+ RestartSec=10
3139
+
3140
+ [Install]
3141
+ WantedBy=default.target
3142
+ `;
3143
+ writeFileSync(join(serviceDir, rssSamplerUnitName), rssSamplerUnit);
3144
+ logFile(` rss-sampler installed: ${rssSamplerUnitName} LOG_PATH=${rssSamplerLogPath}`);
3087
3145
  // the edge service: always-on front door that owns the public
3088
3146
  // port (PORT) and the VNC stack (Xtigervnc + websockify). Its lifecycle is
3089
3147
  // independent of the main brand service, so an in-place upgrade triggered
@@ -3294,11 +3352,14 @@ WantedBy=multi-user.target
3294
3352
  spawnSync("systemctl", ["--user", "enable", edgeUnitShort], { stdio: "inherit" });
3295
3353
  spawnSync("systemctl", ["--user", "enable", claudeSessionManagerUnitShort], { stdio: "inherit" });
3296
3354
  spawnSync("systemctl", ["--user", "enable", unitName], { stdio: "inherit" });
3355
+ spawnSync("systemctl", ["--user", "enable", `${BRAND.hostname}-rss-sampler`], { stdio: "inherit" });
3297
3356
  // edge first: binds public port + starts VNC stack. Then claude-session-manager
3298
3357
  // (so the main brand unit's Requires= is satisfied). Then main brand service.
3358
+ // rss-sampler is independent — start last, failure is non-blocking.
3299
3359
  spawnSync("systemctl", ["--user", "restart", edgeUnitShort], { stdio: "inherit" });
3300
3360
  spawnSync("systemctl", ["--user", "restart", claudeSessionManagerUnitShort], { stdio: "inherit" });
3301
3361
  spawnSync("systemctl", ["--user", "restart", unitName], { stdio: "inherit" });
3362
+ spawnSync("systemctl", ["--user", "restart", `${BRAND.hostname}-rss-sampler`], { stdio: "inherit" });
3302
3363
  // Session manager runs in its own systemd user unit and can boot-fail silently
3303
3364
  // (e.g. mcp-block-missing). The web server below comes up regardless, so the
3304
3365
  // install terminal would otherwise report success while every API call 503s.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-maxy-code",
3
- "version": "0.1.232",
3
+ "version": "0.1.233",
4
4
  "description": "Install Maxy — AI for Productive People",
5
5
  "bin": {
6
6
  "create-maxy-code": "./dist/index.js"
@@ -205,7 +205,7 @@ Graph hygiene is **agent-directed, case by case** — no autonomous rule engine,
205
205
 
206
206
  ## Dream-cycle maintenance — `memory-dream-run` / `memory-review-queue`
207
207
 
208
- `memory-dream-run` is the operator-initiated graph-hygiene sweep — four phases (orphan detection on `:Person`/`:Organization`/`:Concept`/`:Project`; stale compiled-truth scan on `compiledTruthUpdatedAt`; mark edges to `:Trashed` targets with `staleTarget: true`; LLM-bounded citation audit on `:TimelineEvent`). Each run writes a `:Report` (via `memory-report-write` from Task 332) summarising counts; per-phase log lines `[dream-cycle:phase-<n>]` join on the run id. Failure of any phase logs `[dream-cycle:phase-<n>:error]` and continues — subsequent phases still run. Pass `phases: [4]` to run only the citation audit (e.g. after a bulk ingest); `maxEventsPerRun` defaults to 200 to cap LLM cost, with a resume cursor stored as `:Report.lastEventCursor` so subsequent runs continue from the last processed event. `memory-review-queue` is the read-only surface for `:OrphanCandidate` and `:CitationProposal` queues — counts plus a small sample per queue. Phase 5 (backlinks) and phase 6 (tag consistency) are deferred to follow-up tasks. Task 327; absorbs Task 330.
208
+ `memory-dream-run` is the operator-initiated graph-hygiene sweep — seven phases: orphan detection on `:Person`/`:Organization`/`:Concept`/`:Project`; stale compiled-truth scan on `compiledTruthUpdatedAt`; mark edges to `:Trashed` targets with `staleTarget: true`; LLM-bounded citation audit on `:TimelineEvent`; prune compiled-truth revisions to newest 20 per entity; materialise persistent `:BACKLINKS` edges derived from `:MENTIONS`/`:REFERENCES` (making reverse-link queries O(1)); normalise `tags[]` array properties on `:KnowledgeDocument` nodes and merge `:DefinedTerm {category:'obsidian-tag'}` variants. Each run writes a `:Report` (via `memory-report-write`) summarising counts; per-phase log lines `[dream-cycle] phase=<n>` join on the run id. Failure of any phase logs `[dream-cycle:phase-<n>:error]` and continues. Pass `phases: [4]` to run only the citation audit (e.g. after a bulk ingest); `maxEventsPerRun` defaults to 200 for phase 4, with a resume cursor stored as `:Report.lastEventCursor`. `memory-review-queue` is the read-only surface for `:OrphanCandidate` and `:CitationProposal` queues — counts plus a small sample per queue. Task 327; absorbs Task 330; extended Task 596.
209
209
 
210
210
  ## Brain-capture diagnostic — `memory-brain-capture-recent`
211
211
 
@@ -286,3 +286,7 @@ After a successful `memory-ingest` call, the dispatching admin agent should:
286
286
  3. If the user provides additional keywords post-ingest, call `memory-update` on the `documentNodeId` with the merged array.
287
287
 
288
288
  Keywords support user-defined collections via naming convention (e.g., `["reports", "reports/quarterly", "reports/quarterly/q1-2026"]`).
289
+
290
+ **Inline compiled-truth reconciliation (opt-in).** Pass `inlineRewrite: true` to enable the edge-density gate. After all sections and edges are written, the writer counts MENTIONS and REFERENCES edges from the new document (KnowledgeDocument and its sections) to existing entities that carry `compiledTruth`. Entities with >= `inlineRewriteThreshold` (default 2) such edges qualify — unless their `compiledTruthUpdatedAt` is within the 7-day cooldown, in which case they land in `inlineRewriteDeferred` and remain candidates for the next dream-cycle. The result includes `inlineRewriteCandidates: [{ elementId, label, name, edgeCount }]`. Dispatch the `compiled-truth-rewriter` specialist for each entry before the skill returns; pass `nodeId = entry.elementId` and an optional `operatorHint` if the new source suggests a correction. Pass `forceRewrite: true` to bypass the cooldown. Diagnostic: `grep '[memory-ingest] op=compiled-truth-inline'` shows `entitiesChecked`, `entitiesRecompiled`, `entitiesDeferred`, and `reason` per ingest.
291
+
292
+ For the conversation-archive path, the equivalent signal is in `conversation-archive-derive-insights`: pass `inlineRewrite: true` and optionally `inlineRewriteThreshold` to receive `inlineRewriteSignals` (display name + mention count) in the per-chunk result. The skill accumulates signals across chunks; when a cumulative count hits threshold, resolve the entity via `memory-search` and dispatch the `compiled-truth-rewriter` specialist.
@@ -504,7 +504,9 @@ server.tool("conversation-archive-derive-insights", "Convert one chunk's agent-p
504
504
  claims: z
505
505
  .array(claimSchema)
506
506
  .describe("Claims the dispatched specialist emitted in-turn from the chunk body. Zero is valid — pass [] to record an empty-chunk pass."),
507
- }, async ({ archiveElementId, chunkElementId, claims }) => {
507
+ inlineRewrite: z.boolean().optional().describe("When true, count mention-claim subjects in this chunk and return those reaching inlineRewriteThreshold in inlineRewriteSignals. The calling skill accumulates signals across chunks; when an entity's cumulative count hits threshold, resolve it via memory-search and dispatch the compiled-truth-rewriter specialist."),
508
+ inlineRewriteThreshold: z.number().int().min(1).optional().describe("Minimum mention-claim count for a subject to appear in inlineRewriteSignals. Default: 2."),
509
+ }, async ({ archiveElementId, chunkElementId, claims, inlineRewrite, inlineRewriteThreshold }) => {
508
510
  if (!accountId)
509
511
  return refuseNoAccount("conversation-archive-derive-insights");
510
512
  try {
@@ -514,6 +516,8 @@ server.tool("conversation-archive-derive-insights", "Convert one chunk's agent-p
514
516
  chunkElementId,
515
517
  claims: claims,
516
518
  sessionId: resolveSessionId(),
519
+ inlineRewrite,
520
+ inlineRewriteThreshold,
517
521
  });
518
522
  const proposalLines = result.proposals
519
523
  .map((p, i) => {
@@ -532,7 +536,10 @@ server.tool("conversation-archive-derive-insights", "Convert one chunk's agent-p
532
536
  ].join("\n");
533
537
  })
534
538
  .join("\n\n");
535
- const header = `archiveElementId=${result.archiveElementId} chunkElementId=${result.chunkElementId} proposals=${result.proposals.length} rejectionsFiltered=${result.rejectionsFiltered} invalidClaims=${result.invalidClaims}`;
539
+ const signalsSuffix = result.inlineRewriteSignals && result.inlineRewriteSignals.length > 0
540
+ ? ` inlineRewriteSignals=${JSON.stringify(result.inlineRewriteSignals)}`
541
+ : "";
542
+ const header = `archiveElementId=${result.archiveElementId} chunkElementId=${result.chunkElementId} proposals=${result.proposals.length} rejectionsFiltered=${result.rejectionsFiltered} invalidClaims=${result.invalidClaims}${signalsSuffix}`;
536
543
  return {
537
544
  content: [
538
545
  {
@@ -1220,7 +1227,10 @@ if (!readOnly) {
1220
1227
  documentKeywords: z.array(z.string()).optional().describe("Classifier-extracted topic keywords."),
1221
1228
  userKeywords: z.array(z.string()).optional().describe("Keywords explicitly provided by the user. Merged additively with documentKeywords."),
1222
1229
  sessionId: z.string().optional().describe("Session UUID for provenance stamping. Optional override; the server falls back to the SESSION_ID env var set by the admin agent at MCP spawn. The agent only needs to pass this when calling outside the normal admin-session context."),
1223
- }, async ({ attachmentId, conversationIdentity, source, archiveSha256, archiveSourceFile, participantElementIds, lastIngestedMessageHash, lastIngestedMessageAt, archiveTitle, cleanupPriorChunks, sessionIndex, documentSummary, anchorNodeId, anchorLabel, sections, documentEdges, orphanCandidates, scope, sourceUrl, sourceType, documentKeywords, userKeywords, sessionId: sessionIdOverride }) => {
1230
+ inlineRewrite: z.boolean().optional().describe("When true, after writing all sections and edges, run the inline compiled-truth reconciliation gate. Counts MENTIONS/REFERENCES edges from this document (KnowledgeDocument + its sections) to existing entities that carry compiledTruth. Entities with >= inlineRewriteThreshold such edges (default 2) and compiledTruthUpdatedAt older than 7 days are returned in inlineRewriteCandidates. Dispatch the compiled-truth-rewriter specialist for each before the skill returns. Disabled by default."),
1231
+ forceRewrite: z.boolean().optional().describe("When inlineRewrite is true, bypass the 7-day per-entity cooldown on compiledTruthUpdatedAt. All threshold-qualifying entities are returned as inlineRewriteCandidates regardless of when their truth was last rewritten."),
1232
+ inlineRewriteThreshold: z.number().int().min(1).optional().describe("Minimum MENTIONS/REFERENCES edge count from this source to an entity for it to qualify as an inline-rewrite candidate. Default: 2."),
1233
+ }, async ({ attachmentId, conversationIdentity, source, archiveSha256, archiveSourceFile, participantElementIds, lastIngestedMessageHash, lastIngestedMessageAt, archiveTitle, cleanupPriorChunks, sessionIndex, documentSummary, anchorNodeId, anchorLabel, sections, documentEdges, orphanCandidates, scope, sourceUrl, sourceType, documentKeywords, userKeywords, sessionId: sessionIdOverride, inlineRewrite, forceRewrite, inlineRewriteThreshold }) => {
1224
1234
  if (!accountId)
1225
1235
  return refuseNoAccount("memory-ingest");
1226
1236
  try {
@@ -1249,6 +1259,9 @@ if (!readOnly) {
1249
1259
  documentKeywords,
1250
1260
  userKeywords,
1251
1261
  sessionId: resolveSessionId(sessionIdOverride),
1262
+ inlineRewrite,
1263
+ forceRewrite,
1264
+ inlineRewriteThreshold,
1252
1265
  });
1253
1266
  return {
1254
1267
  content: [{
@@ -1264,6 +1277,8 @@ if (!readOnly) {
1264
1277
  documentSummary: result.documentSummary,
1265
1278
  keywords: result.keywords,
1266
1279
  cleanedPriorChunks: result.cleanedPriorChunks,
1280
+ inlineRewriteCandidates: result.inlineRewriteCandidates,
1281
+ inlineRewriteDeferred: result.inlineRewriteDeferred,
1267
1282
  }),
1268
1283
  }],
1269
1284
  };
@@ -2494,13 +2509,14 @@ server.tool("session-retrospective-skip-rate", "Return the ratio of recent sessi
2494
2509
  // ===================================================================
2495
2510
  // Report MCP tools (Task 332) are registered earlier in this file.
2496
2511
  // Dream-run calls the same writer in-process for the per-run :Report.
2497
- server.tool("memory-dream-run", "Operator-initiated dream-cycle: graph-hygiene sweep across five phases " +
2512
+ server.tool("memory-dream-run", "Operator-initiated dream-cycle: graph-hygiene sweep across seven phases " +
2498
2513
  "(orphan detection, stale compiled-truth, edges to :Trashed targets, citation audit on :TimelineEvent, " +
2499
- "prune compiled-truth revisions to newest 20 per entity). " +
2514
+ "prune compiled-truth revisions, materialise :BACKLINKS edges, tag normalisation). " +
2500
2515
  "Writes a :Report node at the end. Failure of any phase logs and continues. " +
2501
- "Pass phases=[4] to run only the citation audit (e.g. after a bulk ingest), or phases=[5] for revision prune only.", {
2516
+ "Pass phases=[4] to run only the citation audit (e.g. after a bulk ingest), phases=[5] for revision prune only, " +
2517
+ "or phases=[6,7] for backlink/tag maintenance only.", {
2502
2518
  operatorId: z.string().optional().describe("Identifier of the operator who triggered the run; appears in the report and log line."),
2503
- phases: z.array(z.number().int().min(1).max(5)).optional().describe("Subset of phases to run. Default [1,2,3,4,5]. Order is fixed."),
2519
+ phases: z.array(z.number().int().min(1).max(7)).optional().describe("Subset of phases to run. Default [1,2,3,4,5,6,7]. Order is fixed."),
2504
2520
  maxEventsPerRun: z.number().int().positive().optional().describe("Phase 4 cap on :TimelineEvent rows scanned per run. Default 200."),
2505
2521
  }, async ({ operatorId, phases, maxEventsPerRun }) => {
2506
2522
  if (!accountId)