@tekmidian/pai 0.36.1 → 0.37.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 (157) hide show
  1. package/dist/{auto-route-Byf8ENXj.mjs → auto-route-DM7GhJ8y.mjs} +2 -2
  2. package/dist/{auto-route-Byf8ENXj.mjs.map → auto-route-DM7GhJ8y.mjs.map} +1 -1
  3. package/dist/cli/index.mjs +5 -4
  4. package/dist/cli/index.mjs.map +1 -1
  5. package/dist/cli/program.d.mts.map +1 -1
  6. package/dist/cli/program.mjs +5 -4
  7. package/dist/{clusters-wZgTCYCB.mjs → clusters-Do4tEGyc.mjs} +1 -1
  8. package/dist/{clusters-wZgTCYCB.mjs.map → clusters-Do4tEGyc.mjs.map} +1 -1
  9. package/dist/{context-handover-cache-PtNvj_8D.mjs → context-handover-cache-BpUojjsi.mjs} +2 -2
  10. package/dist/{context-handover-cache-PtNvj_8D.mjs.map → context-handover-cache-BpUojjsi.mjs.map} +1 -1
  11. package/dist/daemon/index.mjs +8 -8
  12. package/dist/{daemon-BZ93KRBo.mjs → daemon-CGg1VCbA.mjs} +25 -25
  13. package/dist/{daemon-BZ93KRBo.mjs.map → daemon-CGg1VCbA.mjs.map} +1 -1
  14. package/dist/daemon-DsGGiIJM.mjs +20 -0
  15. package/dist/daemon-mcp/index.mjs +428 -1
  16. package/dist/daemon-mcp/index.mjs.map +1 -1
  17. package/dist/{detector--Gg5JRN5.mjs → detector-CMap-9vw.mjs} +1 -1
  18. package/dist/{detector--Gg5JRN5.mjs.map → detector-CMap-9vw.mjs.map} +1 -1
  19. package/dist/detector-DtLExmHN.mjs +5 -0
  20. package/dist/factory-BXzqRYVZ.mjs +3 -0
  21. package/dist/{factory-Bsp7xOpO.mjs → factory-DD2T33C9.mjs} +5 -5
  22. package/dist/{factory-Bsp7xOpO.mjs.map → factory-DD2T33C9.mjs.map} +1 -1
  23. package/dist/hooks/context-compression-hook.mjs +164 -14
  24. package/dist/hooks/context-compression-hook.mjs.map +4 -4
  25. package/dist/hooks/initialize-session.mjs +6 -0
  26. package/dist/hooks/initialize-session.mjs.map +3 -3
  27. package/dist/hooks/load-core-context.mjs +6 -0
  28. package/dist/hooks/load-core-context.mjs.map +3 -3
  29. package/dist/hooks/load-project-context.mjs +6 -0
  30. package/dist/hooks/load-project-context.mjs.map +3 -3
  31. package/dist/hooks/post-compact-inject.mjs +48 -7
  32. package/dist/hooks/post-compact-inject.mjs.map +4 -4
  33. package/dist/hooks/route-agents-to-worker.mjs +456 -0
  34. package/dist/hooks/route-agents-to-worker.mjs.map +7 -0
  35. package/dist/hooks/status-line.mjs +400 -0
  36. package/dist/hooks/status-line.mjs.map +7 -0
  37. package/dist/hooks/stop-hook.mjs +6 -0
  38. package/dist/hooks/stop-hook.mjs.map +3 -3
  39. package/dist/hooks/subagent-stop-hook.mjs +6 -0
  40. package/dist/hooks/subagent-stop-hook.mjs.map +3 -3
  41. package/dist/hooks/sync-todo-to-md.mjs +6 -0
  42. package/dist/hooks/sync-todo-to-md.mjs.map +3 -3
  43. package/dist/hooks/update-tab-on-action.mjs +6 -0
  44. package/dist/hooks/update-tab-on-action.mjs.map +3 -3
  45. package/dist/hooks/update-tab-titles.mjs +6 -0
  46. package/dist/hooks/update-tab-titles.mjs.map +3 -3
  47. package/dist/hooks/worker-proxy.mjs +796 -0
  48. package/dist/hooks/worker-proxy.mjs.map +7 -0
  49. package/dist/hooks/worker-status-line.mjs +483 -0
  50. package/dist/hooks/worker-status-line.mjs.map +7 -0
  51. package/dist/{indexer-backend-nQZuEx6N.mjs → indexer-backend-Cox9BCo-.mjs} +1 -1
  52. package/dist/{indexer-backend-nQZuEx6N.mjs.map → indexer-backend-Cox9BCo-.mjs.map} +1 -1
  53. package/dist/{kg-entity-DbOMPdF9.mjs → kg-entity-r8duqhi9.mjs} +1 -1
  54. package/dist/{kg-entity-DbOMPdF9.mjs.map → kg-entity-r8duqhi9.mjs.map} +1 -1
  55. package/dist/{latent-ideas-Bn6A5-5P.mjs → latent-ideas-BC1oINZ-.mjs} +2 -2
  56. package/dist/{latent-ideas-Bn6A5-5P.mjs.map → latent-ideas-BC1oINZ-.mjs.map} +1 -1
  57. package/dist/{link-boost-fYjUnxCN.mjs → link-boost-QFLrJwD6.mjs} +1 -1
  58. package/dist/{link-boost-fYjUnxCN.mjs.map → link-boost-QFLrJwD6.mjs.map} +1 -1
  59. package/dist/{main-resolver-BAbhKpeX.mjs → main-resolver-DlaLOFBA.mjs} +1 -1
  60. package/dist/{main-resolver-BAbhKpeX.mjs.map → main-resolver-DlaLOFBA.mjs.map} +1 -1
  61. package/dist/{main-resolver-Dxh444GO.mjs → main-resolver-IhZo4pI0.mjs} +1 -1
  62. package/dist/{neighborhood-DpaEM991.mjs → neighborhood-D9MJ1c8f.mjs} +1 -1
  63. package/dist/{neighborhood-DpaEM991.mjs.map → neighborhood-D9MJ1c8f.mjs.map} +1 -1
  64. package/dist/{note-context-DrcY4cWm.mjs → note-context-d1wT_-GA.mjs} +1 -1
  65. package/dist/{note-context-DrcY4cWm.mjs.map → note-context-d1wT_-GA.mjs.map} +1 -1
  66. package/dist/{postgres-BVme6qX0.mjs → postgres--BjPtLa0.mjs} +1 -1
  67. package/dist/{postgres-BVme6qX0.mjs.map → postgres--BjPtLa0.mjs.map} +1 -1
  68. package/dist/{program-C-fUghPv.mjs → program-CEIHn_Ma.mjs} +693 -101
  69. package/dist/program-CEIHn_Ma.mjs.map +1 -0
  70. package/dist/providers-sXcK5bDZ.mjs +3067 -0
  71. package/dist/providers-sXcK5bDZ.mjs.map +1 -0
  72. package/dist/{query-feedback-C1T6kS18.mjs → query-feedback-BUJxgw5B.mjs} +1 -1
  73. package/dist/{query-feedback-C1T6kS18.mjs.map → query-feedback-BUJxgw5B.mjs.map} +1 -1
  74. package/dist/query-feedback-DhyLOe5S.mjs +3 -0
  75. package/dist/router-1zi8jiNF.mjs +3 -0
  76. package/dist/{router-CsDm7HvK.mjs → router-DK_sLsUL.mjs} +1 -1
  77. package/dist/{router-CsDm7HvK.mjs.map → router-DK_sLsUL.mjs.map} +1 -1
  78. package/dist/skills/Worker/SKILL.md +41 -0
  79. package/dist/{sources-D8ZdNfvK.mjs → sources-Bi7--33T.mjs} +1 -1
  80. package/dist/{sources-D8ZdNfvK.mjs.map → sources-Bi7--33T.mjs.map} +1 -1
  81. package/dist/{sqlite-D1IaR8Am.mjs → sqlite-DtaL1glm.mjs} +1 -1
  82. package/dist/{sqlite-D1IaR8Am.mjs.map → sqlite-DtaL1glm.mjs.map} +1 -1
  83. package/dist/{state-qtmrBWCm.mjs → state-8Hm9E4tW.mjs} +1 -1
  84. package/dist/{state-WaXhLr6R.mjs → state-BY2L6-vX.mjs} +1 -1
  85. package/dist/{state-WaXhLr6R.mjs.map → state-BY2L6-vX.mjs.map} +1 -1
  86. package/dist/{themes-XPkj_bfP.mjs → themes-BN0a2duq.mjs} +1 -1
  87. package/dist/{themes-XPkj_bfP.mjs.map → themes-BN0a2duq.mjs.map} +1 -1
  88. package/dist/{tools-DEt6YPfc.mjs → tools-CGPqpU3A.mjs} +1 -1
  89. package/dist/{tools-ceiy7ANX.mjs → tools-y2bJpKom.mjs} +14 -14
  90. package/dist/{tools-ceiy7ANX.mjs.map → tools-y2bJpKom.mjs.map} +1 -1
  91. package/dist/{trace-DfyGmMG_.mjs → trace-h23JCcFD.mjs} +1 -1
  92. package/dist/{trace-DfyGmMG_.mjs.map → trace-h23JCcFD.mjs.map} +1 -1
  93. package/dist/{vault-indexer-CFvlPUMB.mjs → vault-indexer-DgsPjMgs.mjs} +1 -1
  94. package/dist/{vault-indexer-CFvlPUMB.mjs.map → vault-indexer-DgsPjMgs.mjs.map} +1 -1
  95. package/dist/{work-queue-worker-B8W8_3Rn.mjs → work-queue-worker-Dva_v_pI.mjs} +4 -4
  96. package/dist/{work-queue-worker-B8W8_3Rn.mjs.map → work-queue-worker-Dva_v_pI.mjs.map} +1 -1
  97. package/dist/{work-queue-worker-HN2Ufg-L.mjs → work-queue-worker-gsKd2LJa.mjs} +4 -4
  98. package/dist/{zettelkasten-CvjmMghT.mjs → zettelkasten-vo7psPdT.mjs} +3 -3
  99. package/dist/{zettelkasten-CvjmMghT.mjs.map → zettelkasten-vo7psPdT.mjs.map} +1 -1
  100. package/docs/commands/README.md +28 -0
  101. package/docs/commands/backup.md +1 -1
  102. package/docs/commands/clear-names.md +1 -1
  103. package/docs/commands/daemon.md +1 -1
  104. package/docs/commands/db.md +1 -1
  105. package/docs/commands/end.md +1 -1
  106. package/docs/commands/help.md +1 -1
  107. package/docs/commands/identity.md +1 -1
  108. package/docs/commands/kg.md +1 -1
  109. package/docs/commands/mcp.md +1 -1
  110. package/docs/commands/memory.md +1 -1
  111. package/docs/commands/notify.md +1 -1
  112. package/docs/commands/observation.md +1 -1
  113. package/docs/commands/obsidian.md +1 -1
  114. package/docs/commands/pause.md +1 -1
  115. package/docs/commands/project.md +1 -1
  116. package/docs/commands/projects.md +1 -1
  117. package/docs/commands/registry.md +1 -1
  118. package/docs/commands/restore.md +1 -1
  119. package/docs/commands/session.md +1 -1
  120. package/docs/commands/sessions.md +1 -1
  121. package/docs/commands/setup.md +1 -1
  122. package/docs/commands/shell-init.md +1 -1
  123. package/docs/commands/skill.md +1 -1
  124. package/docs/commands/task.md +1 -1
  125. package/docs/commands/topic.md +1 -1
  126. package/docs/commands/update.md +1 -1
  127. package/docs/commands/worker.md +361 -0
  128. package/docs/commands/zettel.md +1 -1
  129. package/docs/worker.md +303 -0
  130. package/package.json +1 -1
  131. package/scripts/build-hooks.mjs +46 -1
  132. package/src/hooks/pre-compact.sh +1 -0
  133. package/src/hooks/session-autosave.sh +1 -0
  134. package/src/hooks/session-stop.sh +1 -0
  135. package/src/hooks/ts/lib/context-fill.test.ts +104 -0
  136. package/src/hooks/ts/lib/context-fill.ts +73 -0
  137. package/src/hooks/ts/lib/handover-evidence.ts +227 -0
  138. package/src/hooks/ts/lib/worker-session.test.ts +24 -0
  139. package/src/hooks/ts/lib/worker-session.ts +23 -0
  140. package/src/hooks/ts/post-tool-use/sync-todo-to-md.ts +2 -0
  141. package/src/hooks/ts/post-tool-use/update-tab-on-action.ts +2 -0
  142. package/src/hooks/ts/pre-compact/context-compression-hook.ts +17 -0
  143. package/src/hooks/ts/pre-tool-use/route-agents-to-worker.ts +128 -0
  144. package/src/hooks/ts/session-start/initialize-session.ts +2 -0
  145. package/src/hooks/ts/session-start/load-core-context.ts +2 -0
  146. package/src/hooks/ts/session-start/load-project-context.ts +2 -0
  147. package/src/hooks/ts/session-start/post-compact-inject.ts +13 -0
  148. package/src/hooks/ts/stop/stop-hook.ts +2 -0
  149. package/src/hooks/ts/subagent-stop/subagent-stop-hook.ts +2 -0
  150. package/src/hooks/ts/user-prompt/update-tab-titles.ts +2 -0
  151. package/statusline-command.sh +17 -0
  152. package/dist/daemon-DRdoA489.mjs +0 -20
  153. package/dist/detector-DGAk1iBR.mjs +0 -5
  154. package/dist/factory-CrokPMk2.mjs +0 -3
  155. package/dist/program-C-fUghPv.mjs.map +0 -1
  156. package/dist/query-feedback-BBMBp96K.mjs +0 -3
  157. package/dist/router-BMkOb62X.mjs +0 -3
@@ -1 +1 @@
1
- {"version":3,"file":"query-feedback-C1T6kS18.mjs","names":[],"sources":["../src/zettelkasten/query-feedback.ts"],"sourcesContent":["/**\n * Query feedback loop — persist search queries and results as markdown files.\n *\n * When memory_search or zettel_converse returns results, the query + result\n * metadata is saved to ~/.config/pai/queries/ as a markdown file with YAML\n * frontmatter. The daemon indexer picks these up on the next cycle and indexes\n * them into federation.db, creating a self-reinforcing feedback loop: past\n * queries become searchable context for future queries.\n */\n\nimport { existsSync, mkdirSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\n\nexport interface QueryRecord {\n /** The original query string. */\n query: string;\n /** Timestamp of the query. */\n timestamp: number;\n /** Tool that produced the result: 'memory_search' | 'zettel_converse'. */\n source: string;\n /** Slugs of the top result paths (for linking back). */\n sourceSlugs: string[];\n /** Preview of the answer/result (first 500 chars). */\n answerPreview: string;\n /** Number of results returned. */\n resultCount: number;\n}\n\nconst QUERIES_DIR = join(homedir(), \".config\", \"pai\", \"queries\");\n\n/**\n * Ensure the queries directory exists.\n */\nfunction ensureQueriesDir(): void {\n if (!existsSync(QUERIES_DIR)) {\n mkdirSync(QUERIES_DIR, { recursive: true });\n }\n}\n\n/**\n * Generate a filename-safe slug from a query string.\n */\nfunction querySlug(query: string, timestamp: number): string {\n const slug = query\n .toLowerCase()\n .replace(/[^a-z0-9\\s]/g, \"\")\n .replace(/\\s+/g, \"-\")\n .slice(0, 60);\n const ts = new Date(timestamp).toISOString().slice(0, 10);\n const shortHash = timestamp.toString(36).slice(-4);\n return `${ts}-${slug}-${shortHash}`;\n}\n\n/**\n * Save a query + result record as a markdown file with YAML frontmatter.\n *\n * The file is written to ~/.config/pai/queries/ and will be picked up by\n * the daemon indexer on the next cycle.\n */\nexport function saveQueryResult(record: QueryRecord): string | null {\n try {\n ensureQueriesDir();\n\n const filename = querySlug(record.query, record.timestamp) + \".md\";\n const filepath = join(QUERIES_DIR, filename);\n\n // Don't overwrite if the exact file already exists\n if (existsSync(filepath)) return filepath;\n\n const frontmatter = [\n \"---\",\n `query: \"${record.query.replace(/\"/g, '\\\\\"')}\"`,\n `timestamp: ${new Date(record.timestamp).toISOString()}`,\n `source: ${record.source}`,\n `result_count: ${record.resultCount}`,\n `source_slugs:`,\n ...record.sourceSlugs.map((s) => ` - \"${s}\"`),\n \"---\",\n ].join(\"\\n\");\n\n const body = [\n \"\",\n `# Query: ${record.query}`,\n \"\",\n `**Source:** ${record.source} `,\n `**Date:** ${new Date(record.timestamp).toISOString().slice(0, 19).replace(\"T\", \" \")} `,\n `**Results:** ${record.resultCount}`,\n \"\",\n \"## Answer Preview\",\n \"\",\n record.answerPreview,\n \"\",\n \"## Source Paths\",\n \"\",\n ...record.sourceSlugs.map((s) => `- \\`${s}\\``),\n \"\",\n ].join(\"\\n\");\n\n writeFileSync(filepath, frontmatter + body, \"utf8\");\n return filepath;\n } catch {\n // Non-critical — don't crash the parent tool if query logging fails\n return null;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AA6BA,MAAM,cAAc,KAAK,SAAS,EAAE,WAAW,OAAO,UAAU;;;;AAKhE,SAAS,mBAAyB;AAChC,KAAI,CAAC,WAAW,YAAY,CAC1B,WAAU,aAAa,EAAE,WAAW,MAAM,CAAC;;;;;AAO/C,SAAS,UAAU,OAAe,WAA2B;CAC3D,MAAM,OAAO,MACV,aAAa,CACb,QAAQ,gBAAgB,GAAG,CAC3B,QAAQ,QAAQ,IAAI,CACpB,MAAM,GAAG,GAAG;AAGf,QAAO,GAFI,IAAI,KAAK,UAAU,CAAC,aAAa,CAAC,MAAM,GAAG,GAAG,CAE5C,GAAG,KAAK,GADH,UAAU,SAAS,GAAG,CAAC,MAAM,GAAG;;;;;;;;AAUpD,SAAgB,gBAAgB,QAAoC;AAClE,KAAI;AACF,oBAAkB;EAGlB,MAAM,WAAW,KAAK,aADL,UAAU,OAAO,OAAO,OAAO,UAAU,GAAG,MACjB;AAG5C,MAAI,WAAW,SAAS,CAAE,QAAO;AA+BjC,gBAAc,UA7BM;GAClB;GACA,WAAW,OAAO,MAAM,QAAQ,MAAM,OAAM,CAAC;GAC7C,cAAc,IAAI,KAAK,OAAO,UAAU,CAAC,aAAa;GACtD,WAAW,OAAO;GAClB,iBAAiB,OAAO;GACxB;GACA,GAAG,OAAO,YAAY,KAAK,MAAM,QAAQ,EAAE,GAAG;GAC9C;GACD,CAAC,KAAK,KAAK,GAEC;GACX;GACA,YAAY,OAAO;GACnB;GACA,eAAe,OAAO,OAAO;GAC7B,aAAa,IAAI,KAAK,OAAO,UAAU,CAAC,aAAa,CAAC,MAAM,GAAG,GAAG,CAAC,QAAQ,KAAK,IAAI,CAAC;GACrF,gBAAgB,OAAO;GACvB;GACA;GACA;GACA,OAAO;GACP;GACA;GACA;GACA,GAAG,OAAO,YAAY,KAAK,MAAM,OAAO,EAAE,IAAI;GAC9C;GACD,CAAC,KAAK,KAAK,EAEgC,OAAO;AACnD,SAAO;SACD;AAEN,SAAO"}
1
+ {"version":3,"file":"query-feedback-BUJxgw5B.mjs","names":[],"sources":["../src/zettelkasten/query-feedback.ts"],"sourcesContent":["/**\n * Query feedback loop — persist search queries and results as markdown files.\n *\n * When memory_search or zettel_converse returns results, the query + result\n * metadata is saved to ~/.config/pai/queries/ as a markdown file with YAML\n * frontmatter. The daemon indexer picks these up on the next cycle and indexes\n * them into federation.db, creating a self-reinforcing feedback loop: past\n * queries become searchable context for future queries.\n */\n\nimport { existsSync, mkdirSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\n\nexport interface QueryRecord {\n /** The original query string. */\n query: string;\n /** Timestamp of the query. */\n timestamp: number;\n /** Tool that produced the result: 'memory_search' | 'zettel_converse'. */\n source: string;\n /** Slugs of the top result paths (for linking back). */\n sourceSlugs: string[];\n /** Preview of the answer/result (first 500 chars). */\n answerPreview: string;\n /** Number of results returned. */\n resultCount: number;\n}\n\nconst QUERIES_DIR = join(homedir(), \".config\", \"pai\", \"queries\");\n\n/**\n * Ensure the queries directory exists.\n */\nfunction ensureQueriesDir(): void {\n if (!existsSync(QUERIES_DIR)) {\n mkdirSync(QUERIES_DIR, { recursive: true });\n }\n}\n\n/**\n * Generate a filename-safe slug from a query string.\n */\nfunction querySlug(query: string, timestamp: number): string {\n const slug = query\n .toLowerCase()\n .replace(/[^a-z0-9\\s]/g, \"\")\n .replace(/\\s+/g, \"-\")\n .slice(0, 60);\n const ts = new Date(timestamp).toISOString().slice(0, 10);\n const shortHash = timestamp.toString(36).slice(-4);\n return `${ts}-${slug}-${shortHash}`;\n}\n\n/**\n * Save a query + result record as a markdown file with YAML frontmatter.\n *\n * The file is written to ~/.config/pai/queries/ and will be picked up by\n * the daemon indexer on the next cycle.\n */\nexport function saveQueryResult(record: QueryRecord): string | null {\n try {\n ensureQueriesDir();\n\n const filename = querySlug(record.query, record.timestamp) + \".md\";\n const filepath = join(QUERIES_DIR, filename);\n\n // Don't overwrite if the exact file already exists\n if (existsSync(filepath)) return filepath;\n\n const frontmatter = [\n \"---\",\n `query: \"${record.query.replace(/\"/g, '\\\\\"')}\"`,\n `timestamp: ${new Date(record.timestamp).toISOString()}`,\n `source: ${record.source}`,\n `result_count: ${record.resultCount}`,\n `source_slugs:`,\n ...record.sourceSlugs.map((s) => ` - \"${s}\"`),\n \"---\",\n ].join(\"\\n\");\n\n const body = [\n \"\",\n `# Query: ${record.query}`,\n \"\",\n `**Source:** ${record.source} `,\n `**Date:** ${new Date(record.timestamp).toISOString().slice(0, 19).replace(\"T\", \" \")} `,\n `**Results:** ${record.resultCount}`,\n \"\",\n \"## Answer Preview\",\n \"\",\n record.answerPreview,\n \"\",\n \"## Source Paths\",\n \"\",\n ...record.sourceSlugs.map((s) => `- \\`${s}\\``),\n \"\",\n ].join(\"\\n\");\n\n writeFileSync(filepath, frontmatter + body, \"utf8\");\n return filepath;\n } catch {\n // Non-critical — don't crash the parent tool if query logging fails\n return null;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AA6BA,MAAM,cAAc,KAAK,SAAS,EAAE,WAAW,OAAO,UAAU;;;;AAKhE,SAAS,mBAAyB;AAChC,KAAI,CAAC,WAAW,YAAY,CAC1B,WAAU,aAAa,EAAE,WAAW,MAAM,CAAC;;;;;AAO/C,SAAS,UAAU,OAAe,WAA2B;CAC3D,MAAM,OAAO,MACV,aAAa,CACb,QAAQ,gBAAgB,GAAG,CAC3B,QAAQ,QAAQ,IAAI,CACpB,MAAM,GAAG,GAAG;AAGf,QAAO,GAFI,IAAI,KAAK,UAAU,CAAC,aAAa,CAAC,MAAM,GAAG,GAAG,CAE5C,GAAG,KAAK,GADH,UAAU,SAAS,GAAG,CAAC,MAAM,GAAG;;;;;;;;AAUpD,SAAgB,gBAAgB,QAAoC;AAClE,KAAI;AACF,oBAAkB;EAGlB,MAAM,WAAW,KAAK,aADL,UAAU,OAAO,OAAO,OAAO,UAAU,GAAG,MACjB;AAG5C,MAAI,WAAW,SAAS,CAAE,QAAO;AA+BjC,gBAAc,UA7BM;GAClB;GACA,WAAW,OAAO,MAAM,QAAQ,MAAM,OAAM,CAAC;GAC7C,cAAc,IAAI,KAAK,OAAO,UAAU,CAAC,aAAa;GACtD,WAAW,OAAO;GAClB,iBAAiB,OAAO;GACxB;GACA,GAAG,OAAO,YAAY,KAAK,MAAM,QAAQ,EAAE,GAAG;GAC9C;GACD,CAAC,KAAK,KAAK,GAEC;GACX;GACA,YAAY,OAAO;GACnB;GACA,eAAe,OAAO,OAAO;GAC7B,aAAa,IAAI,KAAK,OAAO,UAAU,CAAC,aAAa,CAAC,MAAM,GAAG,GAAG,CAAC,QAAQ,KAAK,IAAI,CAAC;GACrF,gBAAgB,OAAO;GACvB;GACA;GACA;GACA,OAAO;GACP;GACA;GACA;GACA,GAAG,OAAO,YAAY,KAAK,MAAM,OAAO,EAAE,IAAI;GAC9C;GACD,CAAC,KAAK,KAAK,EAEgC,OAAO;AACnD,SAAO;SACD;AAEN,SAAO"}
@@ -0,0 +1,3 @@
1
+ import { t as saveQueryResult } from "./query-feedback-BUJxgw5B.mjs";
2
+
3
+ export { saveQueryResult };
@@ -0,0 +1,3 @@
1
+ import { t as routeNotification } from "./router-DK_sLsUL.mjs";
2
+
3
+ export { routeNotification };
@@ -246,4 +246,4 @@ async function routeNotification(payload, config) {
246
246
 
247
247
  //#endregion
248
248
  export { routeNotification as t };
249
- //# sourceMappingURL=router-CsDm7HvK.mjs.map
249
+ //# sourceMappingURL=router-DK_sLsUL.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"router-CsDm7HvK.mjs","names":[],"sources":["../src/notifications/providers/ntfy.ts","../src/notifications/providers/whatsapp.ts","../src/notifications/providers/macos.ts","../src/notifications/providers/cli.ts","../src/notifications/router.ts"],"sourcesContent":["/**\n * ntfy.ts — ntfy.sh notification provider\n *\n * Sends notifications to a configured ntfy.sh topic via HTTP.\n */\n\nimport type {\n NotificationProvider,\n NotificationPayload,\n NotificationConfig,\n} from \"../types.js\";\n\nexport class NtfyProvider implements NotificationProvider {\n readonly channelId = \"ntfy\" as const;\n\n async send(\n payload: NotificationPayload,\n config: NotificationConfig\n ): Promise<boolean> {\n const cfg = config.channels.ntfy;\n if (!cfg.enabled || !cfg.url) return false;\n\n try {\n const headers: Record<string, string> = {\n \"Content-Type\": \"text/plain; charset=utf-8\",\n };\n\n if (payload.title) {\n headers[\"Title\"] = payload.title;\n }\n\n if (cfg.priority && cfg.priority !== \"default\") {\n headers[\"Priority\"] = cfg.priority;\n }\n\n const response = await fetch(cfg.url, {\n method: \"POST\",\n headers,\n body: payload.message,\n });\n\n return response.ok;\n } catch {\n return false;\n }\n }\n}\n","/**\n * whatsapp.ts — WhatsApp notification provider (via the AIBroker hub)\n *\n * Sends through AIBroker's hub socket, which proxies to the whazaa adapter.\n *\n * It used to dial /tmp/whazaa.sock and call a method named `whatsapp_send`.\n * Both were wrong, and had been since AIBroker became the runtime hub and\n * adapters became thin transports: Whazaa no longer owns an IPC socket of its\n * own (it registers with the hub, currently at /tmp/whazaa-watcher.sock, which\n * is the hub's business and not ours), and `whatsapp_send` is an MCP TOOL name\n * — the adapter itself takes `send` and `tts`.\n *\n * So every WhatsApp notification failed. Silently, because a failed channel\n * writes one line to stderr and the router has no fallback: on 2026-08-04 four\n * task-bus escalations about a job that had not run for nine hours reached\n * /tmp/pai-scheduler.log and nowhere else, while the user had no idea.\n *\n * Routing through the hub rather than at the adapter directly is deliberate:\n * the hub knows where its adapters are, and that is exactly the knowledge whose\n * absence broke this. Connect-per-call, no persistent state.\n */\n\nimport { connect } from \"node:net\";\nimport { randomUUID } from \"node:crypto\";\nimport type {\n NotificationProvider,\n NotificationPayload,\n NotificationConfig,\n} from \"../types.js\";\n\n/**\n * AIBroker's hub socket.\n *\n * Hardcoded rather than imported: PAI must not depend on AIBroker. This is a\n * protocol constant between them, like the agent mark in tasks/poller.ts.\n */\nconst HUB_SOCKET = \"/tmp/aibroker.sock\";\nconst HUB_TIMEOUT_MS = 10_000;\n\n/**\n * Send a single IPC call to the hub.\n * Returns true on success, false if the hub is not available or errors.\n */\nfunction callHub(\n method: string,\n params: Record<string, unknown>\n): Promise<boolean> {\n return new Promise((resolve) => {\n let done = false;\n let buffer = \"\";\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n function finish(ok: boolean): void {\n if (done) return;\n done = true;\n if (timer) { clearTimeout(timer); timer = null; }\n try { socket?.destroy(); } catch { /* ignore */ }\n resolve(ok);\n }\n\n const socket = connect(HUB_SOCKET, () => {\n const request = {\n jsonrpc: \"2.0\",\n id: randomUUID(),\n method,\n params,\n };\n socket.write(JSON.stringify(request) + \"\\n\");\n });\n\n socket.on(\"data\", (chunk: Buffer) => {\n buffer += chunk.toString();\n const nl = buffer.indexOf(\"\\n\");\n if (nl === -1) return;\n try {\n const resp = JSON.parse(buffer.slice(0, nl)) as { error?: unknown; ok?: boolean };\n // The hub reports failure as {ok:false,error}; JSON-RPC reports {error}.\n // Checking only `error` counted an {ok:false} reply as a delivered\n // notification, which is the one mistake this file must never make.\n finish(!resp.error && resp.ok !== false);\n } catch {\n finish(false);\n }\n });\n\n socket.on(\"error\", () => finish(false));\n socket.on(\"end\", () => finish(false));\n\n timer = setTimeout(() => finish(false), HUB_TIMEOUT_MS);\n });\n}\n\nexport class WhatsAppProvider implements NotificationProvider {\n readonly channelId = \"whatsapp\" as const;\n\n async send(\n payload: NotificationPayload,\n config: NotificationConfig\n ): Promise<boolean> {\n const cfg = config.channels.whatsapp;\n if (!cfg.enabled) return false;\n\n const isVoiceMode = config.mode === \"voice\" || config.channels.voice.enabled;\n const asVoice = isVoiceMode && config.mode === \"voice\";\n\n // The adapter's own vocabulary is `send` and `tts`, reached through the\n // hub's `adapter_call`. `whatsapp_send` — what this used to ask for — is the\n // name of the MCP TOOL that wraps it, and the adapter has never answered to\n // it. The two vocabularies are easy to confuse because the MCP tool exists\n // and works; it just is not this interface.\n const inner: Record<string, unknown> = asVoice\n ? { text: payload.message, voice: config.channels.voice.voiceName ?? \"bm_george\" }\n : { message: payload.message };\n\n if (cfg.recipient) {\n // `send` addresses by `recipient`, `tts` by `jid` — same destination,\n // different key, per the adapter's interface.\n inner[asVoice ? \"jid\" : \"recipient\"] = cfg.recipient;\n }\n\n return callHub(\"adapter_call\", {\n adapter: \"whazaa\",\n method: asVoice ? \"tts\" : \"send\",\n params: inner,\n });\n }\n}\n","/**\n * macos.ts — macOS notification provider\n *\n * Uses the `osascript` command to display a macOS system notification.\n * Non-blocking: spawns the process and returns success without waiting.\n */\n\nimport { spawn } from \"node:child_process\";\nimport type {\n NotificationProvider,\n NotificationPayload,\n NotificationConfig,\n} from \"../types.js\";\n\nexport class MacOsProvider implements NotificationProvider {\n readonly channelId = \"macos\" as const;\n\n async send(\n payload: NotificationPayload,\n config: NotificationConfig\n ): Promise<boolean> {\n const cfg = config.channels.macos;\n if (!cfg.enabled) return false;\n\n try {\n const title = payload.title ?? \"PAI\";\n // Escape single quotes in title and message for AppleScript\n const safeTitle = title.replace(/'/g, \"\\\\'\");\n const safeMessage = payload.message.replace(/'/g, \"\\\\'\");\n\n const script = `display notification \"${safeMessage}\" with title \"${safeTitle}\"`;\n\n return new Promise((resolve) => {\n const child = spawn(\"osascript\", [\"-e\", script], {\n detached: true,\n stdio: \"ignore\",\n });\n child.unref();\n\n // Give the process a moment to start, then assume success.\n // osascript is always present on macOS.\n child.on(\"error\", () => resolve(false));\n\n // Resolve after a short timeout — osascript exits quickly\n setTimeout(() => resolve(true), 200);\n });\n } catch {\n return false;\n }\n }\n}\n","/**\n * cli.ts — CLI notification provider\n *\n * Writes notifications to the PAI daemon log (stderr).\n * Always succeeds — it's the fallback channel.\n */\n\nimport type {\n NotificationProvider,\n NotificationPayload,\n NotificationConfig,\n} from \"../types.js\";\n\nexport class CliProvider implements NotificationProvider {\n readonly channelId = \"cli\" as const;\n\n async send(\n payload: NotificationPayload,\n _config: NotificationConfig\n ): Promise<boolean> {\n const prefix = `[pai-notify:${payload.event}]`;\n const title = payload.title ? ` ${payload.title}:` : \"\";\n process.stderr.write(`${prefix}${title} ${payload.message}\\n`);\n return true;\n }\n}\n","/**\n * router.ts — Notification router\n *\n * Routes notification events to the appropriate channels based on the\n * current mode and per-event routing config.\n *\n * Channel providers are instantiated lazily and cached.\n */\n\nimport type {\n NotificationPayload,\n NotificationConfig,\n NotificationProvider,\n ChannelId,\n SendResult,\n NotificationMode,\n} from \"./types.js\";\nimport { NtfyProvider } from \"./providers/ntfy.js\";\nimport { WhatsAppProvider } from \"./providers/whatsapp.js\";\nimport { MacOsProvider } from \"./providers/macos.js\";\nimport { CliProvider } from \"./providers/cli.js\";\n\n// ---------------------------------------------------------------------------\n// Provider registry (singletons — stateless, safe to reuse)\n// ---------------------------------------------------------------------------\n\nconst PROVIDERS: Record<ChannelId, NotificationProvider> = {\n ntfy: new NtfyProvider(),\n whatsapp: new WhatsAppProvider(),\n macos: new MacOsProvider(),\n voice: new WhatsAppProvider(), // Voice uses WhatsApp TTS; handled in WhatsAppProvider\n cli: new CliProvider(),\n};\n\n// ---------------------------------------------------------------------------\n// Channel resolution\n// ---------------------------------------------------------------------------\n\n/**\n * Given the current config, resolve which channels should receive a\n * notification for the given event type.\n *\n * Mode overrides:\n * \"off\" → no channels\n * \"auto\" → use routing table, filtered by enabled channels\n * \"voice\" → whatsapp (TTS enabled in provider)\n * \"whatsapp\" → whatsapp\n * \"ntfy\" → ntfy\n * \"macos\" → macos\n * \"cli\" → cli\n */\nfunction resolveChannels(\n config: NotificationConfig,\n event: NotificationPayload[\"event\"]\n): ChannelId[] {\n const { mode, channels, routing } = config;\n\n if (mode === \"off\") return [];\n\n // Non-auto modes: force a single channel\n const modeToChannel: Partial<Record<NotificationMode, ChannelId>> = {\n voice: \"whatsapp\", // WhatsAppProvider checks mode === \"voice\" for TTS\n whatsapp: \"whatsapp\",\n ntfy: \"ntfy\",\n macos: \"macos\",\n cli: \"cli\",\n };\n\n if (mode !== \"auto\") {\n const ch = modeToChannel[mode];\n if (!ch) return [];\n // Check the channel is enabled\n const cfg = channels[ch];\n if (cfg && !cfg.enabled) return [ch]; // Still send — mode override bypasses enabled check\n return [ch];\n }\n\n // Auto mode: use routing table, filter to enabled channels\n const candidates = routing[event] ?? [];\n return candidates.filter((ch) => {\n const cfg = channels[ch];\n // \"voice\" channel is virtual — it overlaps with whatsapp.\n // Skip \"voice\" as an independent channel; voice is handled by checking config.mode.\n if (ch === \"voice\") return false;\n return cfg?.enabled === true;\n });\n}\n\n// ---------------------------------------------------------------------------\n// Router\n// ---------------------------------------------------------------------------\n\n/**\n * Route a notification to the appropriate channels.\n *\n * Sends to all resolved channels in parallel.\n * Individual channel failures are non-fatal and logged to stderr.\n *\n * @param payload The notification to send\n * @param config The current notification config (from daemon state)\n */\nexport async function routeNotification(\n payload: NotificationPayload,\n config: NotificationConfig\n): Promise<SendResult> {\n const channels = resolveChannels(config, payload.event);\n\n if (channels.length === 0) {\n return {\n channelsAttempted: [],\n channelsSucceeded: [],\n channelsFailed: [],\n mode: config.mode,\n };\n }\n\n const results = await Promise.allSettled(\n channels.map(async (ch) => {\n const provider = PROVIDERS[ch];\n const ok = await provider.send(payload, config);\n if (!ok) {\n process.stderr.write(\n `[pai-notify] Channel ${ch} failed for event ${payload.event}\\n`\n );\n }\n return { ch, ok };\n })\n );\n\n const succeeded: ChannelId[] = [];\n const failed: ChannelId[] = [];\n\n for (const r of results) {\n if (r.status === \"fulfilled\") {\n if (r.value.ok) {\n succeeded.push(r.value.ch);\n } else {\n failed.push(r.value.ch);\n }\n } else {\n // Provider threw — treat as failure\n failed.push(channels[results.indexOf(r)]);\n }\n }\n\n return {\n channelsAttempted: channels,\n channelsSucceeded: succeeded,\n channelsFailed: failed,\n mode: config.mode,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"router-DK_sLsUL.mjs","names":[],"sources":["../src/notifications/providers/ntfy.ts","../src/notifications/providers/whatsapp.ts","../src/notifications/providers/macos.ts","../src/notifications/providers/cli.ts","../src/notifications/router.ts"],"sourcesContent":["/**\n * ntfy.ts — ntfy.sh notification provider\n *\n * Sends notifications to a configured ntfy.sh topic via HTTP.\n */\n\nimport type {\n NotificationProvider,\n NotificationPayload,\n NotificationConfig,\n} from \"../types.js\";\n\nexport class NtfyProvider implements NotificationProvider {\n readonly channelId = \"ntfy\" as const;\n\n async send(\n payload: NotificationPayload,\n config: NotificationConfig\n ): Promise<boolean> {\n const cfg = config.channels.ntfy;\n if (!cfg.enabled || !cfg.url) return false;\n\n try {\n const headers: Record<string, string> = {\n \"Content-Type\": \"text/plain; charset=utf-8\",\n };\n\n if (payload.title) {\n headers[\"Title\"] = payload.title;\n }\n\n if (cfg.priority && cfg.priority !== \"default\") {\n headers[\"Priority\"] = cfg.priority;\n }\n\n const response = await fetch(cfg.url, {\n method: \"POST\",\n headers,\n body: payload.message,\n });\n\n return response.ok;\n } catch {\n return false;\n }\n }\n}\n","/**\n * whatsapp.ts — WhatsApp notification provider (via the AIBroker hub)\n *\n * Sends through AIBroker's hub socket, which proxies to the whazaa adapter.\n *\n * It used to dial /tmp/whazaa.sock and call a method named `whatsapp_send`.\n * Both were wrong, and had been since AIBroker became the runtime hub and\n * adapters became thin transports: Whazaa no longer owns an IPC socket of its\n * own (it registers with the hub, currently at /tmp/whazaa-watcher.sock, which\n * is the hub's business and not ours), and `whatsapp_send` is an MCP TOOL name\n * — the adapter itself takes `send` and `tts`.\n *\n * So every WhatsApp notification failed. Silently, because a failed channel\n * writes one line to stderr and the router has no fallback: on 2026-08-04 four\n * task-bus escalations about a job that had not run for nine hours reached\n * /tmp/pai-scheduler.log and nowhere else, while the user had no idea.\n *\n * Routing through the hub rather than at the adapter directly is deliberate:\n * the hub knows where its adapters are, and that is exactly the knowledge whose\n * absence broke this. Connect-per-call, no persistent state.\n */\n\nimport { connect } from \"node:net\";\nimport { randomUUID } from \"node:crypto\";\nimport type {\n NotificationProvider,\n NotificationPayload,\n NotificationConfig,\n} from \"../types.js\";\n\n/**\n * AIBroker's hub socket.\n *\n * Hardcoded rather than imported: PAI must not depend on AIBroker. This is a\n * protocol constant between them, like the agent mark in tasks/poller.ts.\n */\nconst HUB_SOCKET = \"/tmp/aibroker.sock\";\nconst HUB_TIMEOUT_MS = 10_000;\n\n/**\n * Send a single IPC call to the hub.\n * Returns true on success, false if the hub is not available or errors.\n */\nfunction callHub(\n method: string,\n params: Record<string, unknown>\n): Promise<boolean> {\n return new Promise((resolve) => {\n let done = false;\n let buffer = \"\";\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n function finish(ok: boolean): void {\n if (done) return;\n done = true;\n if (timer) { clearTimeout(timer); timer = null; }\n try { socket?.destroy(); } catch { /* ignore */ }\n resolve(ok);\n }\n\n const socket = connect(HUB_SOCKET, () => {\n const request = {\n jsonrpc: \"2.0\",\n id: randomUUID(),\n method,\n params,\n };\n socket.write(JSON.stringify(request) + \"\\n\");\n });\n\n socket.on(\"data\", (chunk: Buffer) => {\n buffer += chunk.toString();\n const nl = buffer.indexOf(\"\\n\");\n if (nl === -1) return;\n try {\n const resp = JSON.parse(buffer.slice(0, nl)) as { error?: unknown; ok?: boolean };\n // The hub reports failure as {ok:false,error}; JSON-RPC reports {error}.\n // Checking only `error` counted an {ok:false} reply as a delivered\n // notification, which is the one mistake this file must never make.\n finish(!resp.error && resp.ok !== false);\n } catch {\n finish(false);\n }\n });\n\n socket.on(\"error\", () => finish(false));\n socket.on(\"end\", () => finish(false));\n\n timer = setTimeout(() => finish(false), HUB_TIMEOUT_MS);\n });\n}\n\nexport class WhatsAppProvider implements NotificationProvider {\n readonly channelId = \"whatsapp\" as const;\n\n async send(\n payload: NotificationPayload,\n config: NotificationConfig\n ): Promise<boolean> {\n const cfg = config.channels.whatsapp;\n if (!cfg.enabled) return false;\n\n const isVoiceMode = config.mode === \"voice\" || config.channels.voice.enabled;\n const asVoice = isVoiceMode && config.mode === \"voice\";\n\n // The adapter's own vocabulary is `send` and `tts`, reached through the\n // hub's `adapter_call`. `whatsapp_send` — what this used to ask for — is the\n // name of the MCP TOOL that wraps it, and the adapter has never answered to\n // it. The two vocabularies are easy to confuse because the MCP tool exists\n // and works; it just is not this interface.\n const inner: Record<string, unknown> = asVoice\n ? { text: payload.message, voice: config.channels.voice.voiceName ?? \"bm_george\" }\n : { message: payload.message };\n\n if (cfg.recipient) {\n // `send` addresses by `recipient`, `tts` by `jid` — same destination,\n // different key, per the adapter's interface.\n inner[asVoice ? \"jid\" : \"recipient\"] = cfg.recipient;\n }\n\n return callHub(\"adapter_call\", {\n adapter: \"whazaa\",\n method: asVoice ? \"tts\" : \"send\",\n params: inner,\n });\n }\n}\n","/**\n * macos.ts — macOS notification provider\n *\n * Uses the `osascript` command to display a macOS system notification.\n * Non-blocking: spawns the process and returns success without waiting.\n */\n\nimport { spawn } from \"node:child_process\";\nimport type {\n NotificationProvider,\n NotificationPayload,\n NotificationConfig,\n} from \"../types.js\";\n\nexport class MacOsProvider implements NotificationProvider {\n readonly channelId = \"macos\" as const;\n\n async send(\n payload: NotificationPayload,\n config: NotificationConfig\n ): Promise<boolean> {\n const cfg = config.channels.macos;\n if (!cfg.enabled) return false;\n\n try {\n const title = payload.title ?? \"PAI\";\n // Escape single quotes in title and message for AppleScript\n const safeTitle = title.replace(/'/g, \"\\\\'\");\n const safeMessage = payload.message.replace(/'/g, \"\\\\'\");\n\n const script = `display notification \"${safeMessage}\" with title \"${safeTitle}\"`;\n\n return new Promise((resolve) => {\n const child = spawn(\"osascript\", [\"-e\", script], {\n detached: true,\n stdio: \"ignore\",\n });\n child.unref();\n\n // Give the process a moment to start, then assume success.\n // osascript is always present on macOS.\n child.on(\"error\", () => resolve(false));\n\n // Resolve after a short timeout — osascript exits quickly\n setTimeout(() => resolve(true), 200);\n });\n } catch {\n return false;\n }\n }\n}\n","/**\n * cli.ts — CLI notification provider\n *\n * Writes notifications to the PAI daemon log (stderr).\n * Always succeeds — it's the fallback channel.\n */\n\nimport type {\n NotificationProvider,\n NotificationPayload,\n NotificationConfig,\n} from \"../types.js\";\n\nexport class CliProvider implements NotificationProvider {\n readonly channelId = \"cli\" as const;\n\n async send(\n payload: NotificationPayload,\n _config: NotificationConfig\n ): Promise<boolean> {\n const prefix = `[pai-notify:${payload.event}]`;\n const title = payload.title ? ` ${payload.title}:` : \"\";\n process.stderr.write(`${prefix}${title} ${payload.message}\\n`);\n return true;\n }\n}\n","/**\n * router.ts — Notification router\n *\n * Routes notification events to the appropriate channels based on the\n * current mode and per-event routing config.\n *\n * Channel providers are instantiated lazily and cached.\n */\n\nimport type {\n NotificationPayload,\n NotificationConfig,\n NotificationProvider,\n ChannelId,\n SendResult,\n NotificationMode,\n} from \"./types.js\";\nimport { NtfyProvider } from \"./providers/ntfy.js\";\nimport { WhatsAppProvider } from \"./providers/whatsapp.js\";\nimport { MacOsProvider } from \"./providers/macos.js\";\nimport { CliProvider } from \"./providers/cli.js\";\n\n// ---------------------------------------------------------------------------\n// Provider registry (singletons — stateless, safe to reuse)\n// ---------------------------------------------------------------------------\n\nconst PROVIDERS: Record<ChannelId, NotificationProvider> = {\n ntfy: new NtfyProvider(),\n whatsapp: new WhatsAppProvider(),\n macos: new MacOsProvider(),\n voice: new WhatsAppProvider(), // Voice uses WhatsApp TTS; handled in WhatsAppProvider\n cli: new CliProvider(),\n};\n\n// ---------------------------------------------------------------------------\n// Channel resolution\n// ---------------------------------------------------------------------------\n\n/**\n * Given the current config, resolve which channels should receive a\n * notification for the given event type.\n *\n * Mode overrides:\n * \"off\" → no channels\n * \"auto\" → use routing table, filtered by enabled channels\n * \"voice\" → whatsapp (TTS enabled in provider)\n * \"whatsapp\" → whatsapp\n * \"ntfy\" → ntfy\n * \"macos\" → macos\n * \"cli\" → cli\n */\nfunction resolveChannels(\n config: NotificationConfig,\n event: NotificationPayload[\"event\"]\n): ChannelId[] {\n const { mode, channels, routing } = config;\n\n if (mode === \"off\") return [];\n\n // Non-auto modes: force a single channel\n const modeToChannel: Partial<Record<NotificationMode, ChannelId>> = {\n voice: \"whatsapp\", // WhatsAppProvider checks mode === \"voice\" for TTS\n whatsapp: \"whatsapp\",\n ntfy: \"ntfy\",\n macos: \"macos\",\n cli: \"cli\",\n };\n\n if (mode !== \"auto\") {\n const ch = modeToChannel[mode];\n if (!ch) return [];\n // Check the channel is enabled\n const cfg = channels[ch];\n if (cfg && !cfg.enabled) return [ch]; // Still send — mode override bypasses enabled check\n return [ch];\n }\n\n // Auto mode: use routing table, filter to enabled channels\n const candidates = routing[event] ?? [];\n return candidates.filter((ch) => {\n const cfg = channels[ch];\n // \"voice\" channel is virtual — it overlaps with whatsapp.\n // Skip \"voice\" as an independent channel; voice is handled by checking config.mode.\n if (ch === \"voice\") return false;\n return cfg?.enabled === true;\n });\n}\n\n// ---------------------------------------------------------------------------\n// Router\n// ---------------------------------------------------------------------------\n\n/**\n * Route a notification to the appropriate channels.\n *\n * Sends to all resolved channels in parallel.\n * Individual channel failures are non-fatal and logged to stderr.\n *\n * @param payload The notification to send\n * @param config The current notification config (from daemon state)\n */\nexport async function routeNotification(\n payload: NotificationPayload,\n config: NotificationConfig\n): Promise<SendResult> {\n const channels = resolveChannels(config, payload.event);\n\n if (channels.length === 0) {\n return {\n channelsAttempted: [],\n channelsSucceeded: [],\n channelsFailed: [],\n mode: config.mode,\n };\n }\n\n const results = await Promise.allSettled(\n channels.map(async (ch) => {\n const provider = PROVIDERS[ch];\n const ok = await provider.send(payload, config);\n if (!ok) {\n process.stderr.write(\n `[pai-notify] Channel ${ch} failed for event ${payload.event}\\n`\n );\n }\n return { ch, ok };\n })\n );\n\n const succeeded: ChannelId[] = [];\n const failed: ChannelId[] = [];\n\n for (const r of results) {\n if (r.status === \"fulfilled\") {\n if (r.value.ok) {\n succeeded.push(r.value.ch);\n } else {\n failed.push(r.value.ch);\n }\n } else {\n // Provider threw — treat as failure\n failed.push(channels[results.indexOf(r)]);\n }\n }\n\n return {\n channelsAttempted: channels,\n channelsSucceeded: succeeded,\n channelsFailed: failed,\n mode: config.mode,\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ ---
2
+ name: Worker
3
+ description: "Run, watch and route subagent workers on configured providers. USE WHEN delegating subagent work, checking on running workers, or managing worker providers."
4
+ ---
5
+
6
+ ## Worker Skill
7
+
8
+ USE WHEN delegating subagent work, checking on running workers, or managing worker providers.
9
+
10
+ ### Delegating
11
+
12
+ The Agent tool is denied when workers are on — delegate with Bash instead:
13
+
14
+ ```
15
+ pai worker run --label "short task label" --role research \\
16
+ -p '<full, self-contained task spec>' \\
17
+ --allowedTools 'Read,Edit,Write,Bash,Grep,Glob' --output-format json
18
+ ```
19
+
20
+ - Always run in background (run_in_background: true) and with a timeout.
21
+ - Roles: implement (default quality), research (web: add WebSearch,WebFetch), spotcheck (cheap/fast model).
22
+ - The answer is in the `result` field of the JSON it prints. Review the diff yourself.
23
+ - `--no-pane` suppresses the iTerm follow pane; panes open automatically otherwise.
24
+ - Panes run under the `pai-worker` dynamic profile (default profile's font
25
+ family at `workers.pane.fontSize`, default 13); `pai worker pane <id> --check`
26
+ reports the pane plus that profile's path, existence and font.
27
+
28
+ ### Watching
29
+
30
+ - `worker_ps` — running + last finished workers.
31
+ - `worker_replay` with id — the transcript of one worker.
32
+ - CLI equivalents: `pai worker ps`, `pai worker follow <id>`.
33
+
34
+ ### Routing
35
+
36
+ - `worker_status` — on/off, active provider, providers, run tally.
37
+ - `worker_providers` — list/add/remove/use/enable/disable/test. Adding needs
38
+ name, base_url, model and a key (key file path, or a raw key which is
39
+ parked in ~/.config/pai/keys/<name>, mode 0600).
40
+ - `worker_roles` — list/set/unset role → provider[/fast].
41
+ - `worker_toggle` — off makes Agent subagents run on Anthropic again.
@@ -133,4 +133,4 @@ function tail(s, n) {
133
133
 
134
134
  //#endregion
135
135
  export { cmdMemorySources };
136
- //# sourceMappingURL=sources-D8ZdNfvK.mjs.map
136
+ //# sourceMappingURL=sources-Bi7--33T.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"sources-D8ZdNfvK.mjs","names":[],"sources":["../src/cli/commands/memory/sources.ts"],"sourcesContent":["/**\n * `pai memory sources` — what the indexer is actually taking in.\n *\n * The index had grown to ~1.5M chunks against a vault of ~2,700 notes, and\n * nothing in the CLI could show why. `memory status` refuses to report at all\n * when the backend is not SQLite, and `daemon status` gives three totals with no\n * composition — so answering \"what is it indexing, and why is it re-indexing\"\n * meant hand-writing aggregate SQL against the container.\n *\n * The cause, when it was finally measured, was visible in one breakdown: the\n * vault indexer follows symlinks out of the vault into cloud-synced trees, and\n * those trees have mtimes rewritten by the sync client on content that has not\n * changed. Each rewrite re-chunks the file, and re-chunking assigns new chunk\n * ids, which discards the embeddings — so the embedder was re-doing work\n * indefinitely while never catching up.\n *\n * Hence the four sections below. Each one exists because it was needed:\n * composition — the vault dwarfing everything else is the first clue\n * roots — where content enters from, which is how symlink leakage shows\n * heaviest — single files contributing thousands of chunks (attachments)\n * churn — chunks rewritten per day, which is what distinguishes a\n * backlog that will finish from a treadmill that never will\n */\n\nimport { ok, warn, dim, bold, header, renderTable } from \"../../utils.js\";\nimport type { StorageBackend } from \"../../../storage/interface.js\";\n\ninterface Row {\n label: string;\n chunks: number;\n embedded: number;\n}\n\ninterface ChurnRow {\n day: string;\n chunks: number;\n embedded: number;\n}\n\n/** A backend that can answer aggregate SQL. Both concrete backends can. */\ninterface Queryable {\n backendType: \"sqlite\" | \"postgres\";\n getPool?: () => { query: (sql: string) => Promise<{ rows: unknown[] }> };\n getSqliteDb?: () => { prepare: (sql: string) => { all: () => unknown[] } };\n}\n\n/**\n * Run one aggregate query against whichever backend is configured.\n *\n * The two dialects differ only in the epoch conversion, so the SQL is passed in\n * per-dialect rather than abstracted — an abstraction over four reporting\n * queries would cost more than it saves.\n */\nasync function queryRows(\n backend: Queryable,\n pgSql: string,\n sqliteSql: string\n): Promise<Record<string, unknown>[]> {\n if (backend.backendType === \"postgres\" && backend.getPool) {\n const res = await backend.getPool().query(pgSql);\n return res.rows as Record<string, unknown>[];\n }\n if (backend.getSqliteDb) {\n return backend.getSqliteDb().prepare(sqliteSql).all() as Record<string, unknown>[];\n }\n return [];\n}\n\nconst num = (v: unknown): number => Number(v ?? 0);\nconst pct = (part: number, whole: number): string =>\n whole === 0 ? \"—\" : `${Math.round((part / whole) * 100)}%`;\n\n/** Group the leading path segments, which is where content enters the index. */\nexport function rootOf(path: string, depth = 2): string {\n const parts = path.split(\"/\").filter(Boolean);\n if (parts.length <= depth) return path;\n return parts.slice(0, depth).join(\"/\") + \"/…\";\n}\n\nexport async function cmdMemorySources(\n backend: StorageBackend,\n opts: { limit?: number } = {}\n): Promise<void> {\n const q = backend as unknown as Queryable;\n const limit = opts.limit ?? 8;\n\n // ---- composition -------------------------------------------------------\n const comp = (await queryRows(\n q,\n `SELECT source, tier, COUNT(*) AS chunks, COUNT(embedding) AS embedded\n FROM pai_chunks GROUP BY source, tier ORDER BY COUNT(*) DESC`,\n `SELECT source, tier, COUNT(*) AS chunks,\n SUM(CASE WHEN embedding IS NOT NULL THEN 1 ELSE 0 END) AS embedded\n FROM memory_chunks GROUP BY source, tier ORDER BY COUNT(*) DESC`\n )) as Array<{ source: string; tier: string; chunks: unknown; embedded: unknown }>;\n\n if (comp.length === 0) {\n console.log();\n console.log(warn(` Nothing indexed yet, or the backend is unreachable.`));\n console.log(dim(` Backend: ${q.backendType}`));\n console.log();\n return;\n }\n\n const totalChunks = comp.reduce((s, r) => s + num(r.chunks), 0);\n const totalEmbedded = comp.reduce((s, r) => s + num(r.embedded), 0);\n\n console.log();\n console.log(header(`What the indexer has taken in`));\n console.log();\n console.log(\n ` ${bold(totalChunks.toLocaleString())} chunks ` +\n `${totalEmbedded.toLocaleString()} embedded (${pct(totalEmbedded, totalChunks)}) ` +\n dim(`backend: ${q.backendType}`)\n );\n console.log();\n\n console.log(\n renderTable(\n [\"source / tier\", \"chunks\", \"share\", \"embedded\"],\n comp.map((r) => [\n `${r.source} / ${r.tier}`,\n num(r.chunks).toLocaleString(),\n pct(num(r.chunks), totalChunks),\n `${num(r.embedded).toLocaleString()} (${pct(num(r.embedded), num(r.chunks))})`,\n ])\n )\n );\n\n // ---- where it enters from ---------------------------------------------\n const paths = (await queryRows(\n q,\n `SELECT path, COUNT(*) AS chunks FROM pai_chunks GROUP BY path`,\n `SELECT path, COUNT(*) AS chunks FROM memory_chunks GROUP BY path`\n )) as Array<{ path: string; chunks: unknown }>;\n\n const byRoot = new Map<string, number>();\n for (const r of paths) {\n const k = rootOf(String(r.path ?? \"\"));\n byRoot.set(k, (byRoot.get(k) ?? 0) + num(r.chunks));\n }\n const roots = [...byRoot.entries()].sort((a, b) => b[1] - a[1]).slice(0, limit);\n\n console.log();\n console.log(header(`Where it comes from`));\n console.log(dim(` A root you did not expect here is usually a symlink leading out of the vault.`));\n console.log();\n console.log(\n renderTable(\n [\"root\", \"chunks\", \"share\"],\n roots.map(([k, v]) => [k, v.toLocaleString(), pct(v, totalChunks)])\n )\n );\n\n // ---- heaviest single files -------------------------------------------\n const heaviest = [...paths]\n .sort((a, b) => num(b.chunks) - num(a.chunks))\n .slice(0, limit);\n\n console.log();\n console.log(header(`Heaviest single files`));\n console.log();\n console.log(\n renderTable(\n [\"chunks\", \"path\"],\n heaviest.map((r) => [num(r.chunks).toLocaleString(), tail(String(r.path ?? \"\"), 62)])\n )\n );\n\n // ---- churn ------------------------------------------------------------\n // The section that distinguishes a backlog from a treadmill. A day with a\n // large chunk count and a small embedded count means those chunks were\n // rewritten and their embeddings thrown away.\n const churn = (await queryRows(\n q,\n `SELECT to_char(to_timestamp(updated_at/1000),'YYYY-MM-DD') AS day,\n COUNT(*) AS chunks, COUNT(embedding) AS embedded\n FROM pai_chunks GROUP BY day ORDER BY day DESC LIMIT 10`,\n `SELECT date(updated_at/1000,'unixepoch') AS day, COUNT(*) AS chunks,\n SUM(CASE WHEN embedding IS NOT NULL THEN 1 ELSE 0 END) AS embedded\n FROM memory_chunks GROUP BY day ORDER BY day DESC LIMIT 10`\n )) as unknown as ChurnRow[];\n\n console.log();\n console.log(header(`Rewritten per day`));\n console.log(\n dim(` Many chunks with few embedded means they were re-chunked and their`)\n );\n console.log(dim(` embeddings discarded — work the embedder has to redo.`));\n console.log();\n console.log(\n renderTable(\n [\"day\", \"chunks touched\", \"of those embedded\"],\n churn.map((r) => [\n String(r.day),\n num(r.chunks).toLocaleString(),\n `${num(r.embedded).toLocaleString()} (${pct(num(r.embedded), num(r.chunks))})`,\n ])\n )\n );\n\n const missing = totalChunks - totalEmbedded;\n console.log();\n if (missing > 0) {\n console.log(` ${bold(missing.toLocaleString())} chunks still need embedding.`);\n } else {\n console.log(ok(` Everything indexed is embedded.`));\n }\n console.log();\n}\n\n/** Keep the informative end of a long path. */\nfunction tail(s: string, n: number): string {\n return s.length <= n ? s : \"…\" + s.slice(-(n - 1));\n}\n\n"],"mappings":"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"}
1
+ {"version":3,"file":"sources-Bi7--33T.mjs","names":[],"sources":["../src/cli/commands/memory/sources.ts"],"sourcesContent":["/**\n * `pai memory sources` — what the indexer is actually taking in.\n *\n * The index had grown to ~1.5M chunks against a vault of ~2,700 notes, and\n * nothing in the CLI could show why. `memory status` refuses to report at all\n * when the backend is not SQLite, and `daemon status` gives three totals with no\n * composition — so answering \"what is it indexing, and why is it re-indexing\"\n * meant hand-writing aggregate SQL against the container.\n *\n * The cause, when it was finally measured, was visible in one breakdown: the\n * vault indexer follows symlinks out of the vault into cloud-synced trees, and\n * those trees have mtimes rewritten by the sync client on content that has not\n * changed. Each rewrite re-chunks the file, and re-chunking assigns new chunk\n * ids, which discards the embeddings — so the embedder was re-doing work\n * indefinitely while never catching up.\n *\n * Hence the four sections below. Each one exists because it was needed:\n * composition — the vault dwarfing everything else is the first clue\n * roots — where content enters from, which is how symlink leakage shows\n * heaviest — single files contributing thousands of chunks (attachments)\n * churn — chunks rewritten per day, which is what distinguishes a\n * backlog that will finish from a treadmill that never will\n */\n\nimport { ok, warn, dim, bold, header, renderTable } from \"../../utils.js\";\nimport type { StorageBackend } from \"../../../storage/interface.js\";\n\ninterface Row {\n label: string;\n chunks: number;\n embedded: number;\n}\n\ninterface ChurnRow {\n day: string;\n chunks: number;\n embedded: number;\n}\n\n/** A backend that can answer aggregate SQL. Both concrete backends can. */\ninterface Queryable {\n backendType: \"sqlite\" | \"postgres\";\n getPool?: () => { query: (sql: string) => Promise<{ rows: unknown[] }> };\n getSqliteDb?: () => { prepare: (sql: string) => { all: () => unknown[] } };\n}\n\n/**\n * Run one aggregate query against whichever backend is configured.\n *\n * The two dialects differ only in the epoch conversion, so the SQL is passed in\n * per-dialect rather than abstracted — an abstraction over four reporting\n * queries would cost more than it saves.\n */\nasync function queryRows(\n backend: Queryable,\n pgSql: string,\n sqliteSql: string\n): Promise<Record<string, unknown>[]> {\n if (backend.backendType === \"postgres\" && backend.getPool) {\n const res = await backend.getPool().query(pgSql);\n return res.rows as Record<string, unknown>[];\n }\n if (backend.getSqliteDb) {\n return backend.getSqliteDb().prepare(sqliteSql).all() as Record<string, unknown>[];\n }\n return [];\n}\n\nconst num = (v: unknown): number => Number(v ?? 0);\nconst pct = (part: number, whole: number): string =>\n whole === 0 ? \"—\" : `${Math.round((part / whole) * 100)}%`;\n\n/** Group the leading path segments, which is where content enters the index. */\nexport function rootOf(path: string, depth = 2): string {\n const parts = path.split(\"/\").filter(Boolean);\n if (parts.length <= depth) return path;\n return parts.slice(0, depth).join(\"/\") + \"/…\";\n}\n\nexport async function cmdMemorySources(\n backend: StorageBackend,\n opts: { limit?: number } = {}\n): Promise<void> {\n const q = backend as unknown as Queryable;\n const limit = opts.limit ?? 8;\n\n // ---- composition -------------------------------------------------------\n const comp = (await queryRows(\n q,\n `SELECT source, tier, COUNT(*) AS chunks, COUNT(embedding) AS embedded\n FROM pai_chunks GROUP BY source, tier ORDER BY COUNT(*) DESC`,\n `SELECT source, tier, COUNT(*) AS chunks,\n SUM(CASE WHEN embedding IS NOT NULL THEN 1 ELSE 0 END) AS embedded\n FROM memory_chunks GROUP BY source, tier ORDER BY COUNT(*) DESC`\n )) as Array<{ source: string; tier: string; chunks: unknown; embedded: unknown }>;\n\n if (comp.length === 0) {\n console.log();\n console.log(warn(` Nothing indexed yet, or the backend is unreachable.`));\n console.log(dim(` Backend: ${q.backendType}`));\n console.log();\n return;\n }\n\n const totalChunks = comp.reduce((s, r) => s + num(r.chunks), 0);\n const totalEmbedded = comp.reduce((s, r) => s + num(r.embedded), 0);\n\n console.log();\n console.log(header(`What the indexer has taken in`));\n console.log();\n console.log(\n ` ${bold(totalChunks.toLocaleString())} chunks ` +\n `${totalEmbedded.toLocaleString()} embedded (${pct(totalEmbedded, totalChunks)}) ` +\n dim(`backend: ${q.backendType}`)\n );\n console.log();\n\n console.log(\n renderTable(\n [\"source / tier\", \"chunks\", \"share\", \"embedded\"],\n comp.map((r) => [\n `${r.source} / ${r.tier}`,\n num(r.chunks).toLocaleString(),\n pct(num(r.chunks), totalChunks),\n `${num(r.embedded).toLocaleString()} (${pct(num(r.embedded), num(r.chunks))})`,\n ])\n )\n );\n\n // ---- where it enters from ---------------------------------------------\n const paths = (await queryRows(\n q,\n `SELECT path, COUNT(*) AS chunks FROM pai_chunks GROUP BY path`,\n `SELECT path, COUNT(*) AS chunks FROM memory_chunks GROUP BY path`\n )) as Array<{ path: string; chunks: unknown }>;\n\n const byRoot = new Map<string, number>();\n for (const r of paths) {\n const k = rootOf(String(r.path ?? \"\"));\n byRoot.set(k, (byRoot.get(k) ?? 0) + num(r.chunks));\n }\n const roots = [...byRoot.entries()].sort((a, b) => b[1] - a[1]).slice(0, limit);\n\n console.log();\n console.log(header(`Where it comes from`));\n console.log(dim(` A root you did not expect here is usually a symlink leading out of the vault.`));\n console.log();\n console.log(\n renderTable(\n [\"root\", \"chunks\", \"share\"],\n roots.map(([k, v]) => [k, v.toLocaleString(), pct(v, totalChunks)])\n )\n );\n\n // ---- heaviest single files -------------------------------------------\n const heaviest = [...paths]\n .sort((a, b) => num(b.chunks) - num(a.chunks))\n .slice(0, limit);\n\n console.log();\n console.log(header(`Heaviest single files`));\n console.log();\n console.log(\n renderTable(\n [\"chunks\", \"path\"],\n heaviest.map((r) => [num(r.chunks).toLocaleString(), tail(String(r.path ?? \"\"), 62)])\n )\n );\n\n // ---- churn ------------------------------------------------------------\n // The section that distinguishes a backlog from a treadmill. A day with a\n // large chunk count and a small embedded count means those chunks were\n // rewritten and their embeddings thrown away.\n const churn = (await queryRows(\n q,\n `SELECT to_char(to_timestamp(updated_at/1000),'YYYY-MM-DD') AS day,\n COUNT(*) AS chunks, COUNT(embedding) AS embedded\n FROM pai_chunks GROUP BY day ORDER BY day DESC LIMIT 10`,\n `SELECT date(updated_at/1000,'unixepoch') AS day, COUNT(*) AS chunks,\n SUM(CASE WHEN embedding IS NOT NULL THEN 1 ELSE 0 END) AS embedded\n FROM memory_chunks GROUP BY day ORDER BY day DESC LIMIT 10`\n )) as unknown as ChurnRow[];\n\n console.log();\n console.log(header(`Rewritten per day`));\n console.log(\n dim(` Many chunks with few embedded means they were re-chunked and their`)\n );\n console.log(dim(` embeddings discarded — work the embedder has to redo.`));\n console.log();\n console.log(\n renderTable(\n [\"day\", \"chunks touched\", \"of those embedded\"],\n churn.map((r) => [\n String(r.day),\n num(r.chunks).toLocaleString(),\n `${num(r.embedded).toLocaleString()} (${pct(num(r.embedded), num(r.chunks))})`,\n ])\n )\n );\n\n const missing = totalChunks - totalEmbedded;\n console.log();\n if (missing > 0) {\n console.log(` ${bold(missing.toLocaleString())} chunks still need embedding.`);\n } else {\n console.log(ok(` Everything indexed is embedded.`));\n }\n console.log();\n}\n\n/** Keep the informative end of a long path. */\nfunction tail(s: string, n: number): string {\n return s.length <= n ? s : \"…\" + s.slice(-(n - 1));\n}\n\n"],"mappings":"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"}
@@ -268,4 +268,4 @@ var SQLiteBackend = class {
268
268
 
269
269
  //#endregion
270
270
  export { SQLiteBackend };
271
- //# sourceMappingURL=sqlite-D1IaR8Am.mjs.map
271
+ //# sourceMappingURL=sqlite-DtaL1glm.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite-D1IaR8Am.mjs","names":[],"sources":["../src/storage/sqlite.ts"],"sourcesContent":["/**\n * SQLiteBackend — wraps the existing better-sqlite3 federation.db\n * behind the StorageBackend interface.\n *\n * This is a thin adapter. The heavy lifting is all in the existing\n * memory/indexer.ts and memory/search.ts code; we just provide a\n * backend-agnostic surface so the daemon and tools can call either\n * SQLite or Postgres transparently.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport type { StorageBackend, ChunkRow, FileRow, FederationStats } from \"./interface.js\";\nimport type { SearchResult, SearchOptions } from \"../memory/search.js\";\nimport { searchMemory, searchMemorySemantic } from \"../memory/search.js\";\n\nexport class SQLiteBackend implements StorageBackend {\n readonly backendType = \"sqlite\" as const;\n\n private db: Database;\n\n constructor(db: Database) {\n this.db = db;\n }\n\n /**\n * Expose the raw better-sqlite3 Database handle.\n * Used by the daemon to pass to indexAll() which still uses the synchronous API directly.\n */\n getRawDb(): Database {\n return this.db;\n }\n\n /**\n * Alias for getRawDb() — used by the dispatcher for operations that require\n * direct SQLite access (e.g. memory_feedback, touchChunksLastAccessed).\n */\n getSqliteDb(): Database {\n return this.db;\n }\n\n // -------------------------------------------------------------------------\n // Lifecycle\n // -------------------------------------------------------------------------\n\n async close(): Promise<void> {\n try {\n this.db.close();\n } catch {\n // ignore\n }\n }\n\n async getStats(): Promise<FederationStats> {\n const files = (\n this.db.prepare(\"SELECT COUNT(*) AS n FROM memory_files\").get() as { n: number }\n ).n;\n const chunks = (\n this.db.prepare(\"SELECT COUNT(*) AS n FROM memory_chunks\").get() as { n: number }\n ).n;\n return { files, chunks };\n }\n\n // -------------------------------------------------------------------------\n // File tracking\n // -------------------------------------------------------------------------\n\n async getFileHash(projectId: number, path: string): Promise<string | undefined> {\n const row = this.db\n .prepare(\"SELECT hash FROM memory_files WHERE project_id = ? AND path = ?\")\n .get(projectId, path) as { hash: string } | undefined;\n return row?.hash;\n }\n\n async upsertFile(file: FileRow): Promise<void> {\n this.db\n .prepare(\n `INSERT INTO memory_files (project_id, path, source, tier, hash, mtime, size)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(project_id, path) DO UPDATE SET\n source = excluded.source,\n tier = excluded.tier,\n hash = excluded.hash,\n mtime = excluded.mtime,\n size = excluded.size`\n )\n .run(file.projectId, file.path, file.source, file.tier, file.hash, file.mtime, file.size);\n }\n\n // -------------------------------------------------------------------------\n // Chunk management\n // -------------------------------------------------------------------------\n\n async getChunkIds(projectId: number, path: string): Promise<string[]> {\n const rows = this.db\n .prepare(\"SELECT id FROM memory_chunks WHERE project_id = ? AND path = ?\")\n .all(projectId, path) as Array<{ id: string }>;\n return rows.map((r) => r.id);\n }\n\n async deleteChunksForFile(projectId: number, path: string): Promise<void> {\n const ids = await this.getChunkIds(projectId, path);\n const deleteFts = this.db.prepare(\"DELETE FROM memory_fts WHERE id = ?\");\n const deleteChunks = this.db.prepare(\n \"DELETE FROM memory_chunks WHERE project_id = ? AND path = ?\"\n );\n this.db.transaction(() => {\n for (const id of ids) {\n deleteFts.run(id);\n }\n deleteChunks.run(projectId, path);\n })();\n }\n\n async insertChunks(chunks: ChunkRow[]): Promise<void> {\n if (chunks.length === 0) return;\n\n const insertChunk = this.db.prepare(\n `INSERT INTO memory_chunks (id, project_id, source, tier, path, start_line, end_line, hash, text, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`\n );\n const insertFts = this.db.prepare(\n `INSERT INTO memory_fts (text, id, project_id, path, source, tier, start_line, end_line)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)`\n );\n\n this.db.transaction(() => {\n for (const c of chunks) {\n insertChunk.run(\n c.id,\n c.projectId,\n c.source,\n c.tier,\n c.path,\n c.startLine,\n c.endLine,\n c.hash,\n c.text,\n c.updatedAt\n );\n insertFts.run(\n c.text,\n c.id,\n c.projectId,\n c.path,\n c.source,\n c.tier,\n c.startLine,\n c.endLine\n );\n }\n })();\n }\n\n async getDistinctChunkPaths(projectId: number): Promise<string[]> {\n const rows = this.db\n .prepare(\"SELECT DISTINCT path FROM memory_chunks WHERE project_id = ?\")\n .all(projectId) as Array<{ path: string }>;\n return rows.map((r) => r.path);\n }\n\n async deletePaths(projectId: number, paths: string[]): Promise<void> {\n if (paths.length === 0) return;\n const deleteFts = this.db.prepare(\"DELETE FROM memory_fts WHERE id = ?\");\n const deleteChunks = this.db.prepare(\n \"DELETE FROM memory_chunks WHERE project_id = ? AND path = ?\"\n );\n const deleteFile = this.db.prepare(\n \"DELETE FROM memory_files WHERE project_id = ? AND path = ?\"\n );\n this.db.transaction(() => {\n for (const path of paths) {\n const ids = this.db\n .prepare(\"SELECT id FROM memory_chunks WHERE project_id = ? AND path = ?\")\n .all(projectId, path) as Array<{ id: string }>;\n for (const { id } of ids) {\n deleteFts.run(id);\n }\n deleteChunks.run(projectId, path);\n deleteFile.run(projectId, path);\n }\n })();\n }\n\n async getUnembeddedChunkIds(projectId?: number, limit?: number): Promise<Array<{ id: string; text: string; project_id: number; path: string }>> {\n const conditions = [\"embedding IS NULL\"];\n const params: (string | number)[] = [];\n\n if (projectId !== undefined) {\n conditions.push(\"project_id = ?\");\n params.push(projectId);\n }\n\n const where = \"WHERE \" + conditions.join(\" AND \");\n // Prioritize real knowledge notes over PAI session/job-search noise.\n // CASE expression assigns lower priority numbers to knowledge paths.\n const rows = this.db\n .prepare(`SELECT id, text, project_id, path FROM memory_chunks ${where}\n ORDER BY CASE\n WHEN path LIKE '🧠 Ideaverse/%' THEN 0\n WHEN path LIKE 'Z - Zettelkasten/%' THEN 0\n WHEN path LIKE '💼 Business/%' THEN 0\n WHEN path LIKE '📆 Meetings/%' THEN 1\n WHEN path LIKE '💡 Insights/%' THEN 1\n WHEN path LIKE '👨‍💻 People/%' THEN 1\n WHEN path LIKE 'University/%' THEN 1\n WHEN path LIKE 'Copilot/%' THEN 1\n WHEN path LIKE '🗓️ Daily Notes/%' THEN 2\n WHEN path LIKE 'PAI/%' THEN 3\n WHEN path LIKE '09-job-search/%' THEN 4\n WHEN path LIKE 'seriousletter/%' THEN 4\n WHEN path LIKE 'Attachments/%' THEN 5\n ELSE 2\n END, id${limit !== undefined ? \" LIMIT ?\" : \"\"}`)\n .all(...params, ...(limit !== undefined ? [limit] : [])) as Array<{ id: string; text: string; project_id: number; path: string }>;\n return rows;\n }\n\n async updateEmbedding(chunkId: string, embedding: Buffer): Promise<void> {\n this.db\n .prepare(\"UPDATE memory_chunks SET embedding = ? WHERE id = ?\")\n .run(embedding, chunkId);\n }\n\n // -------------------------------------------------------------------------\n // Search\n // -------------------------------------------------------------------------\n\n async searchKeyword(query: string, opts?: SearchOptions): Promise<SearchResult[]> {\n return searchMemory(this.db, query, opts);\n }\n\n async searchSemantic(queryEmbedding: Float32Array, opts?: SearchOptions): Promise<SearchResult[]> {\n return searchMemorySemantic(this.db, queryEmbedding, opts);\n }\n\n // -------------------------------------------------------------------------\n // Vault operations — not supported on SQLite backend (use Postgres)\n // -------------------------------------------------------------------------\n\n private vaultNotSupported(): never {\n throw new Error(\"Vault operations require the Postgres backend\");\n }\n\n async upsertVaultFile(): Promise<void> { this.vaultNotSupported(); }\n async deleteVaultFile(): Promise<void> { this.vaultNotSupported(); }\n async getVaultFile(): Promise<null> { this.vaultNotSupported(); }\n async getVaultFileByInode(): Promise<null> { this.vaultNotSupported(); }\n async getAllVaultFiles(): Promise<never[]> { this.vaultNotSupported(); }\n async getRecentVaultFiles(): Promise<never[]> { this.vaultNotSupported(); }\n async countVaultFiles(): Promise<number> { this.vaultNotSupported(); }\n async upsertVaultAliases(): Promise<void> { this.vaultNotSupported(); }\n async deleteVaultAliases(): Promise<void> { this.vaultNotSupported(); }\n async replaceLinksForSources(): Promise<void> { this.vaultNotSupported(); }\n async getLinksFromSource(): Promise<never[]> { this.vaultNotSupported(); }\n async getLinksToTarget(): Promise<never[]> { this.vaultNotSupported(); }\n async getVaultLinkGraph(): Promise<never[]> { this.vaultNotSupported(); }\n async upsertVaultHealth(): Promise<void> { this.vaultNotSupported(); }\n async getVaultHealth(): Promise<null> { this.vaultNotSupported(); }\n async getOrphans(): Promise<never[]> { this.vaultNotSupported(); }\n async getDeadLinks(): Promise<never[]> { this.vaultNotSupported(); }\n async upsertNameIndex(): Promise<void> { this.vaultNotSupported(); }\n async replaceNameIndex(): Promise<void> { this.vaultNotSupported(); }\n async resolveVaultName(): Promise<never[]> { this.vaultNotSupported(); }\n async searchVaultNameIndex(): Promise<never[]> { this.vaultNotSupported(); }\n async getVaultFilesByPaths(): Promise<never[]> { this.vaultNotSupported(); }\n async getVaultFilesByPathsAfter(): Promise<never[]> { this.vaultNotSupported(); }\n async getVaultLinksFromPaths(): Promise<never[]> { this.vaultNotSupported(); }\n async getChunksWithEmbeddings(): Promise<never[]> { this.vaultNotSupported(); }\n async getChunksForPath(): Promise<never[]> { this.vaultNotSupported(); }\n async searchChunksByText(): Promise<never[]> { this.vaultNotSupported(); }\n async countVaultFilesWithPrefix(): Promise<number> { this.vaultNotSupported(); }\n async countVaultFilesAfter(): Promise<number> { this.vaultNotSupported(); }\n async countVaultLinksWithPrefix(): Promise<number> { this.vaultNotSupported(); }\n async countVaultLinksAfter(): Promise<number> { this.vaultNotSupported(); }\n async getDeadLinksWithLineNumbers(): Promise<never[]> { this.vaultNotSupported(); }\n async getDeadLinksWithPrefix(): Promise<never[]> { this.vaultNotSupported(); }\n async getDeadLinksAfter(): Promise<never[]> { this.vaultNotSupported(); }\n async getOrphansWithPrefix(): Promise<never[]> { this.vaultNotSupported(); }\n async getOrphansAfter(): Promise<never[]> { this.vaultNotSupported(); }\n async getLowConnectivity(): Promise<never[]> { this.vaultNotSupported(); }\n async getLowConnectivityWithPrefix(): Promise<never[]> { this.vaultNotSupported(); }\n async getLowConnectivityAfter(): Promise<never[]> { this.vaultNotSupported(); }\n async getAllVaultFilePaths(): Promise<never[]> { this.vaultNotSupported(); }\n async getVaultFilePathsWithPrefix(): Promise<never[]> { this.vaultNotSupported(); }\n async getVaultFilePathsAfter(): Promise<never[]> { this.vaultNotSupported(); }\n async getVaultLinkEdges(): Promise<never[]> { this.vaultNotSupported(); }\n async getVaultLinkEdgesWithPrefix(): Promise<never[]> { this.vaultNotSupported(); }\n async getVaultLinkEdgesAfter(): Promise<never[]> { this.vaultNotSupported(); }\n async getVaultAlias(): Promise<null> { this.vaultNotSupported(); }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"sqlite-DtaL1glm.mjs","names":[],"sources":["../src/storage/sqlite.ts"],"sourcesContent":["/**\n * SQLiteBackend — wraps the existing better-sqlite3 federation.db\n * behind the StorageBackend interface.\n *\n * This is a thin adapter. The heavy lifting is all in the existing\n * memory/indexer.ts and memory/search.ts code; we just provide a\n * backend-agnostic surface so the daemon and tools can call either\n * SQLite or Postgres transparently.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport type { StorageBackend, ChunkRow, FileRow, FederationStats } from \"./interface.js\";\nimport type { SearchResult, SearchOptions } from \"../memory/search.js\";\nimport { searchMemory, searchMemorySemantic } from \"../memory/search.js\";\n\nexport class SQLiteBackend implements StorageBackend {\n readonly backendType = \"sqlite\" as const;\n\n private db: Database;\n\n constructor(db: Database) {\n this.db = db;\n }\n\n /**\n * Expose the raw better-sqlite3 Database handle.\n * Used by the daemon to pass to indexAll() which still uses the synchronous API directly.\n */\n getRawDb(): Database {\n return this.db;\n }\n\n /**\n * Alias for getRawDb() — used by the dispatcher for operations that require\n * direct SQLite access (e.g. memory_feedback, touchChunksLastAccessed).\n */\n getSqliteDb(): Database {\n return this.db;\n }\n\n // -------------------------------------------------------------------------\n // Lifecycle\n // -------------------------------------------------------------------------\n\n async close(): Promise<void> {\n try {\n this.db.close();\n } catch {\n // ignore\n }\n }\n\n async getStats(): Promise<FederationStats> {\n const files = (\n this.db.prepare(\"SELECT COUNT(*) AS n FROM memory_files\").get() as { n: number }\n ).n;\n const chunks = (\n this.db.prepare(\"SELECT COUNT(*) AS n FROM memory_chunks\").get() as { n: number }\n ).n;\n return { files, chunks };\n }\n\n // -------------------------------------------------------------------------\n // File tracking\n // -------------------------------------------------------------------------\n\n async getFileHash(projectId: number, path: string): Promise<string | undefined> {\n const row = this.db\n .prepare(\"SELECT hash FROM memory_files WHERE project_id = ? AND path = ?\")\n .get(projectId, path) as { hash: string } | undefined;\n return row?.hash;\n }\n\n async upsertFile(file: FileRow): Promise<void> {\n this.db\n .prepare(\n `INSERT INTO memory_files (project_id, path, source, tier, hash, mtime, size)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(project_id, path) DO UPDATE SET\n source = excluded.source,\n tier = excluded.tier,\n hash = excluded.hash,\n mtime = excluded.mtime,\n size = excluded.size`\n )\n .run(file.projectId, file.path, file.source, file.tier, file.hash, file.mtime, file.size);\n }\n\n // -------------------------------------------------------------------------\n // Chunk management\n // -------------------------------------------------------------------------\n\n async getChunkIds(projectId: number, path: string): Promise<string[]> {\n const rows = this.db\n .prepare(\"SELECT id FROM memory_chunks WHERE project_id = ? AND path = ?\")\n .all(projectId, path) as Array<{ id: string }>;\n return rows.map((r) => r.id);\n }\n\n async deleteChunksForFile(projectId: number, path: string): Promise<void> {\n const ids = await this.getChunkIds(projectId, path);\n const deleteFts = this.db.prepare(\"DELETE FROM memory_fts WHERE id = ?\");\n const deleteChunks = this.db.prepare(\n \"DELETE FROM memory_chunks WHERE project_id = ? AND path = ?\"\n );\n this.db.transaction(() => {\n for (const id of ids) {\n deleteFts.run(id);\n }\n deleteChunks.run(projectId, path);\n })();\n }\n\n async insertChunks(chunks: ChunkRow[]): Promise<void> {\n if (chunks.length === 0) return;\n\n const insertChunk = this.db.prepare(\n `INSERT INTO memory_chunks (id, project_id, source, tier, path, start_line, end_line, hash, text, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`\n );\n const insertFts = this.db.prepare(\n `INSERT INTO memory_fts (text, id, project_id, path, source, tier, start_line, end_line)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)`\n );\n\n this.db.transaction(() => {\n for (const c of chunks) {\n insertChunk.run(\n c.id,\n c.projectId,\n c.source,\n c.tier,\n c.path,\n c.startLine,\n c.endLine,\n c.hash,\n c.text,\n c.updatedAt\n );\n insertFts.run(\n c.text,\n c.id,\n c.projectId,\n c.path,\n c.source,\n c.tier,\n c.startLine,\n c.endLine\n );\n }\n })();\n }\n\n async getDistinctChunkPaths(projectId: number): Promise<string[]> {\n const rows = this.db\n .prepare(\"SELECT DISTINCT path FROM memory_chunks WHERE project_id = ?\")\n .all(projectId) as Array<{ path: string }>;\n return rows.map((r) => r.path);\n }\n\n async deletePaths(projectId: number, paths: string[]): Promise<void> {\n if (paths.length === 0) return;\n const deleteFts = this.db.prepare(\"DELETE FROM memory_fts WHERE id = ?\");\n const deleteChunks = this.db.prepare(\n \"DELETE FROM memory_chunks WHERE project_id = ? AND path = ?\"\n );\n const deleteFile = this.db.prepare(\n \"DELETE FROM memory_files WHERE project_id = ? AND path = ?\"\n );\n this.db.transaction(() => {\n for (const path of paths) {\n const ids = this.db\n .prepare(\"SELECT id FROM memory_chunks WHERE project_id = ? AND path = ?\")\n .all(projectId, path) as Array<{ id: string }>;\n for (const { id } of ids) {\n deleteFts.run(id);\n }\n deleteChunks.run(projectId, path);\n deleteFile.run(projectId, path);\n }\n })();\n }\n\n async getUnembeddedChunkIds(projectId?: number, limit?: number): Promise<Array<{ id: string; text: string; project_id: number; path: string }>> {\n const conditions = [\"embedding IS NULL\"];\n const params: (string | number)[] = [];\n\n if (projectId !== undefined) {\n conditions.push(\"project_id = ?\");\n params.push(projectId);\n }\n\n const where = \"WHERE \" + conditions.join(\" AND \");\n // Prioritize real knowledge notes over PAI session/job-search noise.\n // CASE expression assigns lower priority numbers to knowledge paths.\n const rows = this.db\n .prepare(`SELECT id, text, project_id, path FROM memory_chunks ${where}\n ORDER BY CASE\n WHEN path LIKE '🧠 Ideaverse/%' THEN 0\n WHEN path LIKE 'Z - Zettelkasten/%' THEN 0\n WHEN path LIKE '💼 Business/%' THEN 0\n WHEN path LIKE '📆 Meetings/%' THEN 1\n WHEN path LIKE '💡 Insights/%' THEN 1\n WHEN path LIKE '👨‍💻 People/%' THEN 1\n WHEN path LIKE 'University/%' THEN 1\n WHEN path LIKE 'Copilot/%' THEN 1\n WHEN path LIKE '🗓️ Daily Notes/%' THEN 2\n WHEN path LIKE 'PAI/%' THEN 3\n WHEN path LIKE '09-job-search/%' THEN 4\n WHEN path LIKE 'seriousletter/%' THEN 4\n WHEN path LIKE 'Attachments/%' THEN 5\n ELSE 2\n END, id${limit !== undefined ? \" LIMIT ?\" : \"\"}`)\n .all(...params, ...(limit !== undefined ? [limit] : [])) as Array<{ id: string; text: string; project_id: number; path: string }>;\n return rows;\n }\n\n async updateEmbedding(chunkId: string, embedding: Buffer): Promise<void> {\n this.db\n .prepare(\"UPDATE memory_chunks SET embedding = ? WHERE id = ?\")\n .run(embedding, chunkId);\n }\n\n // -------------------------------------------------------------------------\n // Search\n // -------------------------------------------------------------------------\n\n async searchKeyword(query: string, opts?: SearchOptions): Promise<SearchResult[]> {\n return searchMemory(this.db, query, opts);\n }\n\n async searchSemantic(queryEmbedding: Float32Array, opts?: SearchOptions): Promise<SearchResult[]> {\n return searchMemorySemantic(this.db, queryEmbedding, opts);\n }\n\n // -------------------------------------------------------------------------\n // Vault operations — not supported on SQLite backend (use Postgres)\n // -------------------------------------------------------------------------\n\n private vaultNotSupported(): never {\n throw new Error(\"Vault operations require the Postgres backend\");\n }\n\n async upsertVaultFile(): Promise<void> { this.vaultNotSupported(); }\n async deleteVaultFile(): Promise<void> { this.vaultNotSupported(); }\n async getVaultFile(): Promise<null> { this.vaultNotSupported(); }\n async getVaultFileByInode(): Promise<null> { this.vaultNotSupported(); }\n async getAllVaultFiles(): Promise<never[]> { this.vaultNotSupported(); }\n async getRecentVaultFiles(): Promise<never[]> { this.vaultNotSupported(); }\n async countVaultFiles(): Promise<number> { this.vaultNotSupported(); }\n async upsertVaultAliases(): Promise<void> { this.vaultNotSupported(); }\n async deleteVaultAliases(): Promise<void> { this.vaultNotSupported(); }\n async replaceLinksForSources(): Promise<void> { this.vaultNotSupported(); }\n async getLinksFromSource(): Promise<never[]> { this.vaultNotSupported(); }\n async getLinksToTarget(): Promise<never[]> { this.vaultNotSupported(); }\n async getVaultLinkGraph(): Promise<never[]> { this.vaultNotSupported(); }\n async upsertVaultHealth(): Promise<void> { this.vaultNotSupported(); }\n async getVaultHealth(): Promise<null> { this.vaultNotSupported(); }\n async getOrphans(): Promise<never[]> { this.vaultNotSupported(); }\n async getDeadLinks(): Promise<never[]> { this.vaultNotSupported(); }\n async upsertNameIndex(): Promise<void> { this.vaultNotSupported(); }\n async replaceNameIndex(): Promise<void> { this.vaultNotSupported(); }\n async resolveVaultName(): Promise<never[]> { this.vaultNotSupported(); }\n async searchVaultNameIndex(): Promise<never[]> { this.vaultNotSupported(); }\n async getVaultFilesByPaths(): Promise<never[]> { this.vaultNotSupported(); }\n async getVaultFilesByPathsAfter(): Promise<never[]> { this.vaultNotSupported(); }\n async getVaultLinksFromPaths(): Promise<never[]> { this.vaultNotSupported(); }\n async getChunksWithEmbeddings(): Promise<never[]> { this.vaultNotSupported(); }\n async getChunksForPath(): Promise<never[]> { this.vaultNotSupported(); }\n async searchChunksByText(): Promise<never[]> { this.vaultNotSupported(); }\n async countVaultFilesWithPrefix(): Promise<number> { this.vaultNotSupported(); }\n async countVaultFilesAfter(): Promise<number> { this.vaultNotSupported(); }\n async countVaultLinksWithPrefix(): Promise<number> { this.vaultNotSupported(); }\n async countVaultLinksAfter(): Promise<number> { this.vaultNotSupported(); }\n async getDeadLinksWithLineNumbers(): Promise<never[]> { this.vaultNotSupported(); }\n async getDeadLinksWithPrefix(): Promise<never[]> { this.vaultNotSupported(); }\n async getDeadLinksAfter(): Promise<never[]> { this.vaultNotSupported(); }\n async getOrphansWithPrefix(): Promise<never[]> { this.vaultNotSupported(); }\n async getOrphansAfter(): Promise<never[]> { this.vaultNotSupported(); }\n async getLowConnectivity(): Promise<never[]> { this.vaultNotSupported(); }\n async getLowConnectivityWithPrefix(): Promise<never[]> { this.vaultNotSupported(); }\n async getLowConnectivityAfter(): Promise<never[]> { this.vaultNotSupported(); }\n async getAllVaultFilePaths(): Promise<never[]> { this.vaultNotSupported(); }\n async getVaultFilePathsWithPrefix(): Promise<never[]> { this.vaultNotSupported(); }\n async getVaultFilePathsAfter(): Promise<never[]> { this.vaultNotSupported(); }\n async getVaultLinkEdges(): Promise<never[]> { this.vaultNotSupported(); }\n async getVaultLinkEdgesWithPrefix(): Promise<never[]> { this.vaultNotSupported(); }\n async getVaultLinkEdgesAfter(): Promise<never[]> { this.vaultNotSupported(); }\n async getVaultAlias(): Promise<null> { this.vaultNotSupported(); }\n}\n"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
- import { C as setStorageBackend, D as storageBackend, E as startTime, O as vaultIndexInProgress, S as setStartTime, T as shutdownRequested, _ as setLastIndexTime, a as indexSchedulerTimer, b as setRegistryDb, c as lastVaultIndexTime, d as setDaemonConfig, f as setEmbedInProgress, g as setLastEmbedTime, h as setIndexSchedulerTimer, i as indexInProgress, l as notificationConfig, m as setIndexInProgress, n as embedInProgress, o as lastEmbedTime, p as setEmbedSchedulerTimer, r as embedSchedulerTimer, s as lastIndexTime, t as daemonConfig, u as registryDb, v as setLastVaultIndexTime, w as setVaultIndexInProgress, x as setShutdownRequested, y as setNotificationConfig } from "./state-WaXhLr6R.mjs";
1
+ import { C as setStorageBackend, D as storageBackend, E as startTime, O as vaultIndexInProgress, S as setStartTime, T as shutdownRequested, _ as setLastIndexTime, a as indexSchedulerTimer, b as setRegistryDb, c as lastVaultIndexTime, d as setDaemonConfig, f as setEmbedInProgress, g as setLastEmbedTime, h as setIndexSchedulerTimer, i as indexInProgress, l as notificationConfig, m as setIndexInProgress, n as embedInProgress, o as lastEmbedTime, p as setEmbedSchedulerTimer, r as embedSchedulerTimer, s as lastIndexTime, t as daemonConfig, u as registryDb, v as setLastVaultIndexTime, w as setVaultIndexInProgress, x as setShutdownRequested, y as setNotificationConfig } from "./state-BY2L6-vX.mjs";
2
2
 
3
3
  export { lastVaultIndexTime, notificationConfig };
@@ -67,4 +67,4 @@ function setLastVaultIndexTime(v) {
67
67
 
68
68
  //#endregion
69
69
  export { setStorageBackend as C, storageBackend as D, startTime as E, vaultIndexInProgress as O, setStartTime as S, shutdownRequested as T, setLastIndexTime as _, indexSchedulerTimer as a, setRegistryDb as b, lastVaultIndexTime as c, setDaemonConfig as d, setEmbedInProgress as f, setLastEmbedTime as g, setIndexSchedulerTimer as h, indexInProgress as i, notificationConfig as l, setIndexInProgress as m, embedInProgress as n, lastEmbedTime as o, setEmbedSchedulerTimer as p, embedSchedulerTimer as r, lastIndexTime as s, daemonConfig as t, registryDb as u, setLastVaultIndexTime as v, setVaultIndexInProgress as w, setShutdownRequested as x, setNotificationConfig as y };
70
- //# sourceMappingURL=state-WaXhLr6R.mjs.map
70
+ //# sourceMappingURL=state-BY2L6-vX.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"state-WaXhLr6R.mjs","names":[],"sources":["../src/daemon/daemon/state.ts"],"sourcesContent":["/**\n * Shared mutable daemon state — module-level singletons used across all daemon sub-modules.\n */\n\nimport { openRegistry } from \"../../registry/db.js\";\nimport type { StorageBackend } from \"../../storage/interface.js\";\nimport type { NotificationConfig } from \"../../notifications/types.js\";\nimport type { PaiDaemonConfig } from \"../config.js\";\n\n// ---------------------------------------------------------------------------\n// Core singletons (assigned at startup by serve())\n// ---------------------------------------------------------------------------\n\nexport let registryDb: ReturnType<typeof openRegistry>;\nexport let storageBackend: StorageBackend;\nexport let daemonConfig: PaiDaemonConfig;\nexport let startTime = Date.now();\n\n// ---------------------------------------------------------------------------\n// Scheduler state\n// ---------------------------------------------------------------------------\n\n/** True while a project index pass is running. */\nexport let indexInProgress = false;\nexport let lastIndexTime = 0;\nexport let indexSchedulerTimer: ReturnType<typeof setInterval> | null = null;\n\n/** True while an embedding pass is running. */\nexport let embedInProgress = false;\nexport let lastEmbedTime = 0;\nexport let embedSchedulerTimer: ReturnType<typeof setInterval> | null = null;\n\n/** True while a vault index pass is running. */\nexport let vaultIndexInProgress = false;\nexport let lastVaultIndexTime = 0;\n\n// ---------------------------------------------------------------------------\n// Notification state\n// ---------------------------------------------------------------------------\n\n/** Mutable notification config — loaded from disk at startup, patchable at runtime. */\nexport let notificationConfig: NotificationConfig;\n\n// ---------------------------------------------------------------------------\n// Graceful shutdown flag\n// ---------------------------------------------------------------------------\n\n/**\n * Set to true when a SIGTERM/SIGINT is received so that long-running loops\n * (embed, index) can detect the signal and exit their inner loops before the\n * pool/backend is closed.\n */\nexport let shutdownRequested = false;\n\n// ---------------------------------------------------------------------------\n// Setters (TypeScript requires assignment functions for exported `let` vars\n// that need to be mutated across module boundaries)\n// ---------------------------------------------------------------------------\n\nexport function setRegistryDb(db: ReturnType<typeof openRegistry>): void { registryDb = db; }\nexport function setStorageBackend(b: StorageBackend): void { storageBackend = b; }\nexport function setDaemonConfig(c: PaiDaemonConfig): void { daemonConfig = c; }\nexport function setStartTime(t: number): void { startTime = t; }\nexport function setNotificationConfig(c: NotificationConfig): void { notificationConfig = c; }\nexport function setShutdownRequested(v: boolean): void { shutdownRequested = v; }\nexport function setIndexInProgress(v: boolean): void { indexInProgress = v; }\nexport function setLastIndexTime(v: number): void { lastIndexTime = v; }\nexport function setIndexSchedulerTimer(v: ReturnType<typeof setInterval> | null): void { indexSchedulerTimer = v; }\nexport function setEmbedInProgress(v: boolean): void { embedInProgress = v; }\nexport function setLastEmbedTime(v: number): void { lastEmbedTime = v; }\nexport function setEmbedSchedulerTimer(v: ReturnType<typeof setInterval> | null): void { embedSchedulerTimer = v; }\nexport function setVaultIndexInProgress(v: boolean): void { vaultIndexInProgress = v; }\nexport function setLastVaultIndexTime(v: number): void { lastVaultIndexTime = v; }\n"],"mappings":";AAaA,IAAW;AACX,IAAW;AACX,IAAW;AACX,IAAW,YAAY,KAAK,KAAK;;AAOjC,IAAW,kBAAkB;AAC7B,IAAW,gBAAgB;AAC3B,IAAW,sBAA6D;;AAGxE,IAAW,kBAAkB;AAC7B,IAAW,gBAAgB;AAC3B,IAAW,sBAA6D;;AAGxE,IAAW,uBAAuB;AAClC,IAAW,qBAAqB;;AAOhC,IAAW;;;;;;AAWX,IAAW,oBAAoB;AAO/B,SAAgB,cAAc,IAA2C;AAAE,cAAa;;AACxF,SAAgB,kBAAkB,GAAyB;AAAE,kBAAiB;;AAC9E,SAAgB,gBAAgB,GAA0B;AAAE,gBAAe;;AAC3E,SAAgB,aAAa,GAAiB;AAAE,aAAY;;AAC5D,SAAgB,sBAAsB,GAA6B;AAAE,sBAAqB;;AAC1F,SAAgB,qBAAqB,GAAkB;AAAE,qBAAoB;;AAC7E,SAAgB,mBAAmB,GAAkB;AAAE,mBAAkB;;AACzE,SAAgB,iBAAiB,GAAiB;AAAE,iBAAgB;;AACpE,SAAgB,uBAAuB,GAAgD;AAAE,uBAAsB;;AAC/G,SAAgB,mBAAmB,GAAkB;AAAE,mBAAkB;;AACzE,SAAgB,iBAAiB,GAAiB;AAAE,iBAAgB;;AACpE,SAAgB,uBAAuB,GAAgD;AAAE,uBAAsB;;AAC/G,SAAgB,wBAAwB,GAAkB;AAAE,wBAAuB;;AACnF,SAAgB,sBAAsB,GAAiB;AAAE,sBAAqB"}
1
+ {"version":3,"file":"state-BY2L6-vX.mjs","names":[],"sources":["../src/daemon/daemon/state.ts"],"sourcesContent":["/**\n * Shared mutable daemon state — module-level singletons used across all daemon sub-modules.\n */\n\nimport { openRegistry } from \"../../registry/db.js\";\nimport type { StorageBackend } from \"../../storage/interface.js\";\nimport type { NotificationConfig } from \"../../notifications/types.js\";\nimport type { PaiDaemonConfig } from \"../config.js\";\n\n// ---------------------------------------------------------------------------\n// Core singletons (assigned at startup by serve())\n// ---------------------------------------------------------------------------\n\nexport let registryDb: ReturnType<typeof openRegistry>;\nexport let storageBackend: StorageBackend;\nexport let daemonConfig: PaiDaemonConfig;\nexport let startTime = Date.now();\n\n// ---------------------------------------------------------------------------\n// Scheduler state\n// ---------------------------------------------------------------------------\n\n/** True while a project index pass is running. */\nexport let indexInProgress = false;\nexport let lastIndexTime = 0;\nexport let indexSchedulerTimer: ReturnType<typeof setInterval> | null = null;\n\n/** True while an embedding pass is running. */\nexport let embedInProgress = false;\nexport let lastEmbedTime = 0;\nexport let embedSchedulerTimer: ReturnType<typeof setInterval> | null = null;\n\n/** True while a vault index pass is running. */\nexport let vaultIndexInProgress = false;\nexport let lastVaultIndexTime = 0;\n\n// ---------------------------------------------------------------------------\n// Notification state\n// ---------------------------------------------------------------------------\n\n/** Mutable notification config — loaded from disk at startup, patchable at runtime. */\nexport let notificationConfig: NotificationConfig;\n\n// ---------------------------------------------------------------------------\n// Graceful shutdown flag\n// ---------------------------------------------------------------------------\n\n/**\n * Set to true when a SIGTERM/SIGINT is received so that long-running loops\n * (embed, index) can detect the signal and exit their inner loops before the\n * pool/backend is closed.\n */\nexport let shutdownRequested = false;\n\n// ---------------------------------------------------------------------------\n// Setters (TypeScript requires assignment functions for exported `let` vars\n// that need to be mutated across module boundaries)\n// ---------------------------------------------------------------------------\n\nexport function setRegistryDb(db: ReturnType<typeof openRegistry>): void { registryDb = db; }\nexport function setStorageBackend(b: StorageBackend): void { storageBackend = b; }\nexport function setDaemonConfig(c: PaiDaemonConfig): void { daemonConfig = c; }\nexport function setStartTime(t: number): void { startTime = t; }\nexport function setNotificationConfig(c: NotificationConfig): void { notificationConfig = c; }\nexport function setShutdownRequested(v: boolean): void { shutdownRequested = v; }\nexport function setIndexInProgress(v: boolean): void { indexInProgress = v; }\nexport function setLastIndexTime(v: number): void { lastIndexTime = v; }\nexport function setIndexSchedulerTimer(v: ReturnType<typeof setInterval> | null): void { indexSchedulerTimer = v; }\nexport function setEmbedInProgress(v: boolean): void { embedInProgress = v; }\nexport function setLastEmbedTime(v: number): void { lastEmbedTime = v; }\nexport function setEmbedSchedulerTimer(v: ReturnType<typeof setInterval> | null): void { embedSchedulerTimer = v; }\nexport function setVaultIndexInProgress(v: boolean): void { vaultIndexInProgress = v; }\nexport function setLastVaultIndexTime(v: number): void { lastVaultIndexTime = v; }\n"],"mappings":";AAaA,IAAW;AACX,IAAW;AACX,IAAW;AACX,IAAW,YAAY,KAAK,KAAK;;AAOjC,IAAW,kBAAkB;AAC7B,IAAW,gBAAgB;AAC3B,IAAW,sBAA6D;;AAGxE,IAAW,kBAAkB;AAC7B,IAAW,gBAAgB;AAC3B,IAAW,sBAA6D;;AAGxE,IAAW,uBAAuB;AAClC,IAAW,qBAAqB;;AAOhC,IAAW;;;;;;AAWX,IAAW,oBAAoB;AAO/B,SAAgB,cAAc,IAA2C;AAAE,cAAa;;AACxF,SAAgB,kBAAkB,GAAyB;AAAE,kBAAiB;;AAC9E,SAAgB,gBAAgB,GAA0B;AAAE,gBAAe;;AAC3E,SAAgB,aAAa,GAAiB;AAAE,aAAY;;AAC5D,SAAgB,sBAAsB,GAA6B;AAAE,sBAAqB;;AAC1F,SAAgB,qBAAqB,GAAkB;AAAE,qBAAoB;;AAC7E,SAAgB,mBAAmB,GAAkB;AAAE,mBAAkB;;AACzE,SAAgB,iBAAiB,GAAiB;AAAE,iBAAgB;;AACpE,SAAgB,uBAAuB,GAAgD;AAAE,uBAAsB;;AAC/G,SAAgB,mBAAmB,GAAkB;AAAE,mBAAkB;;AACzE,SAAgB,iBAAiB,GAAiB;AAAE,iBAAgB;;AACpE,SAAgB,uBAAuB,GAAgD;AAAE,uBAAsB;;AAC/G,SAAgB,wBAAwB,GAAkB;AAAE,wBAAuB;;AACnF,SAAgB,sBAAsB,GAAiB;AAAE,sBAAqB"}
@@ -145,4 +145,4 @@ async function zettelThemes(backend, opts) {
145
145
 
146
146
  //#endregion
147
147
  export { zettelThemes as t };
148
- //# sourceMappingURL=themes-XPkj_bfP.mjs.map
148
+ //# sourceMappingURL=themes-BN0a2duq.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"themes-XPkj_bfP.mjs","names":[],"sources":["../src/zettelkasten/themes.ts"],"sourcesContent":["import type { StorageBackend } from \"../storage/interface.js\";\nimport { deserializeEmbedding, cosineSimilarity } from \"../memory/embeddings.js\";\nimport { STOP_WORDS } from \"../utils/stop-words.js\";\n\nexport interface ThemeOptions {\n vaultProjectId: number;\n lookbackDays?: number;\n minClusterSize?: number;\n maxThemes?: number;\n similarityThreshold?: number;\n}\n\nexport interface ThemeCluster {\n id: number;\n label: string;\n notes: Array<{\n path: string;\n title: string | null;\n }>;\n size: number;\n folderDiversity: number;\n avgRecency: number;\n linkedRatio: number;\n suggestIndexNote: boolean;\n}\n\nexport interface ThemeResult {\n themes: ThemeCluster[];\n totalNotesAnalyzed: number;\n timeWindow: { from: number; to: number };\n}\n\nconst MAX_CHUNKS = 5000;\n\n// STOP_WORDS imported from utils/stop-words.ts\n\nfunction getTopFolder(vaultPath: string): string {\n const parts = vaultPath.split(\"/\");\n return parts.length > 1 ? parts[0] : \"\";\n}\n\nfunction generateLabel(titles: Array<string | null>): string {\n const wordCounts = new Map<string, number>();\n for (const title of titles) {\n if (!title) continue;\n const words = title\n .toLowerCase()\n .replace(/[^a-z0-9\\s]/g, \" \")\n .split(/\\s+/)\n .filter((w) => w.length > 2 && !STOP_WORDS.has(w));\n for (const word of words) {\n wordCounts.set(word, (wordCounts.get(word) ?? 0) + 1);\n }\n }\n const sorted = [...wordCounts.entries()].sort((a, b) => b[1] - a[1]);\n return sorted\n .slice(0, 3)\n .map(([w]) => w)\n .join(\" / \");\n}\n\nasync function computeLinkedRatio(backend: StorageBackend, paths: string[]): Promise<number> {\n if (paths.length < 2) return 0;\n const totalPairs = (paths.length * (paths.length - 1)) / 2;\n const pathSet = new Set(paths);\n let linkedPairs = 0;\n\n for (const path of paths) {\n const links = await backend.getLinksFromSource(path);\n for (const link of links) {\n if (link.targetPath && pathSet.has(link.targetPath)) {\n linkedPairs++;\n }\n }\n }\n\n // Each bidirectional pair might be counted once per direction; divide by 2 to normalize\n const uniquePairs = linkedPairs / 2;\n return Math.min(1, uniquePairs / totalPairs);\n}\n\ntype ClusterNode = {\n paths: string[];\n titles: Array<string | null>;\n indexedAts: number[];\n centroid: Float32Array;\n};\n\nfunction averageEmbeddings(embeddings: Float32Array[]): Float32Array {\n if (embeddings.length === 0) return new Float32Array(0);\n const dim = embeddings[0].length;\n const sum = new Float32Array(dim);\n for (const vec of embeddings) {\n for (let i = 0; i < dim; i++) {\n sum[i] += vec[i];\n }\n }\n const avg = new Float32Array(dim);\n for (let i = 0; i < dim; i++) {\n avg[i] = sum[i] / embeddings.length;\n }\n return avg;\n}\n\n/**\n * Detect emerging themes in recently-modified notes using agglomerative single-linkage\n * clustering of note-level embeddings.\n */\nexport async function zettelThemes(\n backend: StorageBackend,\n opts: ThemeOptions,\n): Promise<ThemeResult> {\n const lookbackDays = opts.lookbackDays ?? 30;\n const minClusterSize = opts.minClusterSize ?? 3;\n const maxThemes = opts.maxThemes ?? 10;\n const similarityThreshold = opts.similarityThreshold ?? 0.65;\n\n const now = Date.now();\n const from = now - lookbackDays * 86400000;\n\n // Step 1: get recent notes\n const recentFiles = await backend.getRecentVaultFiles(from);\n const recentNotes = recentFiles.map(f => ({ vault_path: f.vaultPath, title: f.title, indexed_at: f.indexedAt }));\n\n // Step 2: get file-level embeddings from memory_chunks\n const chunkRows = await backend.getChunksWithEmbeddings(opts.vaultProjectId, MAX_CHUNKS);\n\n const embeddingsByPath = new Map<string, Float32Array[]>();\n for (const row of chunkRows) {\n const vec = deserializeEmbedding(row.embedding);\n const arr = embeddingsByPath.get(row.path);\n if (!arr) {\n embeddingsByPath.set(row.path, [vec]);\n } else {\n arr.push(vec);\n }\n }\n\n const fileEmbeddings = new Map<string, Float32Array>();\n for (const [path, vecs] of embeddingsByPath) {\n fileEmbeddings.set(path, averageEmbeddings(vecs));\n }\n\n // Step 3: build initial clusters — only include notes that have embeddings\n const clusters: ClusterNode[] = [];\n for (const note of recentNotes) {\n const embedding = fileEmbeddings.get(note.vault_path);\n if (!embedding) continue;\n clusters.push({\n paths: [note.vault_path],\n titles: [note.title],\n indexedAts: [note.indexed_at],\n centroid: embedding,\n });\n }\n\n const totalNotesAnalyzed = clusters.length;\n\n // Step 4: agglomerative single-linkage clustering\n // Stop when no two clusters have similarity >= threshold\n let merged = true;\n while (merged && clusters.length > 1) {\n merged = false;\n let bestSim = similarityThreshold;\n let bestI = -1;\n let bestJ = -1;\n\n for (let i = 0; i < clusters.length; i++) {\n for (let j = i + 1; j < clusters.length; j++) {\n const sim = cosineSimilarity(clusters[i].centroid, clusters[j].centroid);\n if (sim > bestSim) {\n bestSim = sim;\n bestI = i;\n bestJ = j;\n }\n }\n }\n\n if (bestI === -1) break;\n\n // Merge cluster j into cluster i\n const ci = clusters[bestI];\n const cj = clusters[bestJ];\n const mergedPaths = [...ci.paths, ...cj.paths];\n const mergedTitles = [...ci.titles, ...cj.titles];\n const mergedIndexedAts = [...ci.indexedAts, ...cj.indexedAts];\n\n // Recompute centroid from averaged embeddings of all member paths\n const memberEmbeddings: Float32Array[] = [];\n for (const p of mergedPaths) {\n const emb = fileEmbeddings.get(p);\n if (emb) memberEmbeddings.push(emb);\n }\n\n clusters[bestI] = {\n paths: mergedPaths,\n titles: mergedTitles,\n indexedAts: mergedIndexedAts,\n centroid: averageEmbeddings(memberEmbeddings),\n };\n\n clusters.splice(bestJ, 1);\n merged = true;\n }\n\n // Step 5: filter and annotate clusters\n const themes: ThemeCluster[] = [];\n let clusterIndex = 0;\n\n for (const cluster of clusters) {\n if (cluster.paths.length < minClusterSize) continue;\n\n const label = generateLabel(cluster.titles) || `Theme ${clusterIndex + 1}`;\n const avgRecency =\n cluster.indexedAts.reduce((sum, t) => sum + t, 0) / cluster.indexedAts.length;\n\n const uniqueFolders = new Set(cluster.paths.map(getTopFolder));\n const folderDiversity = uniqueFolders.size / cluster.paths.length;\n\n const linkedRatio = await computeLinkedRatio(backend, cluster.paths);\n const suggestIndexNote = linkedRatio < 0.3 && cluster.paths.length >= 5;\n\n themes.push({\n id: clusterIndex++,\n label,\n notes: cluster.paths.map((path, idx) => ({\n path,\n title: cluster.titles[idx],\n })),\n size: cluster.paths.length,\n folderDiversity,\n avgRecency,\n linkedRatio,\n suggestIndexNote,\n });\n }\n\n // Step 6: rank by size * folderDiversity * recency_ratio\n themes.sort(\n (a, b) =>\n b.size * b.folderDiversity * (b.avgRecency / now) -\n a.size * a.folderDiversity * (a.avgRecency / now),\n );\n\n return {\n themes: themes.slice(0, maxThemes),\n totalNotesAnalyzed,\n timeWindow: { from, to: now },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"themes-BN0a2duq.mjs","names":[],"sources":["../src/zettelkasten/themes.ts"],"sourcesContent":["import type { StorageBackend } from \"../storage/interface.js\";\nimport { deserializeEmbedding, cosineSimilarity } from \"../memory/embeddings.js\";\nimport { STOP_WORDS } from \"../utils/stop-words.js\";\n\nexport interface ThemeOptions {\n vaultProjectId: number;\n lookbackDays?: number;\n minClusterSize?: number;\n maxThemes?: number;\n similarityThreshold?: number;\n}\n\nexport interface ThemeCluster {\n id: number;\n label: string;\n notes: Array<{\n path: string;\n title: string | null;\n }>;\n size: number;\n folderDiversity: number;\n avgRecency: number;\n linkedRatio: number;\n suggestIndexNote: boolean;\n}\n\nexport interface ThemeResult {\n themes: ThemeCluster[];\n totalNotesAnalyzed: number;\n timeWindow: { from: number; to: number };\n}\n\nconst MAX_CHUNKS = 5000;\n\n// STOP_WORDS imported from utils/stop-words.ts\n\nfunction getTopFolder(vaultPath: string): string {\n const parts = vaultPath.split(\"/\");\n return parts.length > 1 ? parts[0] : \"\";\n}\n\nfunction generateLabel(titles: Array<string | null>): string {\n const wordCounts = new Map<string, number>();\n for (const title of titles) {\n if (!title) continue;\n const words = title\n .toLowerCase()\n .replace(/[^a-z0-9\\s]/g, \" \")\n .split(/\\s+/)\n .filter((w) => w.length > 2 && !STOP_WORDS.has(w));\n for (const word of words) {\n wordCounts.set(word, (wordCounts.get(word) ?? 0) + 1);\n }\n }\n const sorted = [...wordCounts.entries()].sort((a, b) => b[1] - a[1]);\n return sorted\n .slice(0, 3)\n .map(([w]) => w)\n .join(\" / \");\n}\n\nasync function computeLinkedRatio(backend: StorageBackend, paths: string[]): Promise<number> {\n if (paths.length < 2) return 0;\n const totalPairs = (paths.length * (paths.length - 1)) / 2;\n const pathSet = new Set(paths);\n let linkedPairs = 0;\n\n for (const path of paths) {\n const links = await backend.getLinksFromSource(path);\n for (const link of links) {\n if (link.targetPath && pathSet.has(link.targetPath)) {\n linkedPairs++;\n }\n }\n }\n\n // Each bidirectional pair might be counted once per direction; divide by 2 to normalize\n const uniquePairs = linkedPairs / 2;\n return Math.min(1, uniquePairs / totalPairs);\n}\n\ntype ClusterNode = {\n paths: string[];\n titles: Array<string | null>;\n indexedAts: number[];\n centroid: Float32Array;\n};\n\nfunction averageEmbeddings(embeddings: Float32Array[]): Float32Array {\n if (embeddings.length === 0) return new Float32Array(0);\n const dim = embeddings[0].length;\n const sum = new Float32Array(dim);\n for (const vec of embeddings) {\n for (let i = 0; i < dim; i++) {\n sum[i] += vec[i];\n }\n }\n const avg = new Float32Array(dim);\n for (let i = 0; i < dim; i++) {\n avg[i] = sum[i] / embeddings.length;\n }\n return avg;\n}\n\n/**\n * Detect emerging themes in recently-modified notes using agglomerative single-linkage\n * clustering of note-level embeddings.\n */\nexport async function zettelThemes(\n backend: StorageBackend,\n opts: ThemeOptions,\n): Promise<ThemeResult> {\n const lookbackDays = opts.lookbackDays ?? 30;\n const minClusterSize = opts.minClusterSize ?? 3;\n const maxThemes = opts.maxThemes ?? 10;\n const similarityThreshold = opts.similarityThreshold ?? 0.65;\n\n const now = Date.now();\n const from = now - lookbackDays * 86400000;\n\n // Step 1: get recent notes\n const recentFiles = await backend.getRecentVaultFiles(from);\n const recentNotes = recentFiles.map(f => ({ vault_path: f.vaultPath, title: f.title, indexed_at: f.indexedAt }));\n\n // Step 2: get file-level embeddings from memory_chunks\n const chunkRows = await backend.getChunksWithEmbeddings(opts.vaultProjectId, MAX_CHUNKS);\n\n const embeddingsByPath = new Map<string, Float32Array[]>();\n for (const row of chunkRows) {\n const vec = deserializeEmbedding(row.embedding);\n const arr = embeddingsByPath.get(row.path);\n if (!arr) {\n embeddingsByPath.set(row.path, [vec]);\n } else {\n arr.push(vec);\n }\n }\n\n const fileEmbeddings = new Map<string, Float32Array>();\n for (const [path, vecs] of embeddingsByPath) {\n fileEmbeddings.set(path, averageEmbeddings(vecs));\n }\n\n // Step 3: build initial clusters — only include notes that have embeddings\n const clusters: ClusterNode[] = [];\n for (const note of recentNotes) {\n const embedding = fileEmbeddings.get(note.vault_path);\n if (!embedding) continue;\n clusters.push({\n paths: [note.vault_path],\n titles: [note.title],\n indexedAts: [note.indexed_at],\n centroid: embedding,\n });\n }\n\n const totalNotesAnalyzed = clusters.length;\n\n // Step 4: agglomerative single-linkage clustering\n // Stop when no two clusters have similarity >= threshold\n let merged = true;\n while (merged && clusters.length > 1) {\n merged = false;\n let bestSim = similarityThreshold;\n let bestI = -1;\n let bestJ = -1;\n\n for (let i = 0; i < clusters.length; i++) {\n for (let j = i + 1; j < clusters.length; j++) {\n const sim = cosineSimilarity(clusters[i].centroid, clusters[j].centroid);\n if (sim > bestSim) {\n bestSim = sim;\n bestI = i;\n bestJ = j;\n }\n }\n }\n\n if (bestI === -1) break;\n\n // Merge cluster j into cluster i\n const ci = clusters[bestI];\n const cj = clusters[bestJ];\n const mergedPaths = [...ci.paths, ...cj.paths];\n const mergedTitles = [...ci.titles, ...cj.titles];\n const mergedIndexedAts = [...ci.indexedAts, ...cj.indexedAts];\n\n // Recompute centroid from averaged embeddings of all member paths\n const memberEmbeddings: Float32Array[] = [];\n for (const p of mergedPaths) {\n const emb = fileEmbeddings.get(p);\n if (emb) memberEmbeddings.push(emb);\n }\n\n clusters[bestI] = {\n paths: mergedPaths,\n titles: mergedTitles,\n indexedAts: mergedIndexedAts,\n centroid: averageEmbeddings(memberEmbeddings),\n };\n\n clusters.splice(bestJ, 1);\n merged = true;\n }\n\n // Step 5: filter and annotate clusters\n const themes: ThemeCluster[] = [];\n let clusterIndex = 0;\n\n for (const cluster of clusters) {\n if (cluster.paths.length < minClusterSize) continue;\n\n const label = generateLabel(cluster.titles) || `Theme ${clusterIndex + 1}`;\n const avgRecency =\n cluster.indexedAts.reduce((sum, t) => sum + t, 0) / cluster.indexedAts.length;\n\n const uniqueFolders = new Set(cluster.paths.map(getTopFolder));\n const folderDiversity = uniqueFolders.size / cluster.paths.length;\n\n const linkedRatio = await computeLinkedRatio(backend, cluster.paths);\n const suggestIndexNote = linkedRatio < 0.3 && cluster.paths.length >= 5;\n\n themes.push({\n id: clusterIndex++,\n label,\n notes: cluster.paths.map((path, idx) => ({\n path,\n title: cluster.titles[idx],\n })),\n size: cluster.paths.length,\n folderDiversity,\n avgRecency,\n linkedRatio,\n suggestIndexNote,\n });\n }\n\n // Step 6: rank by size * folderDiversity * recency_ratio\n themes.sort(\n (a, b) =>\n b.size * b.folderDiversity * (b.avgRecency / now) -\n a.size * a.folderDiversity * (a.avgRecency / now),\n );\n\n return {\n themes: themes.slice(0, maxThemes),\n totalNotesAnalyzed,\n timeWindow: { from, to: now },\n };\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import "./embeddings-DOLZnT1X.mjs";
2
2
  import "./search-Rpk1cSBC.mjs";
3
- import { A as formatProject, C as toolProjectInfo, D as toolMemoryGet, E as combineHybridResults, O as toolMemorySearch, S as toolProjectHealth, T as toolProjectTodo, _ as toolZettelThemes, a as toolMemoryWakeup, b as toolSessionRoute, c as toolKgInvalidate, d as toolZettelConverse, f as toolZettelExplore, g as toolZettelSurprise, h as toolZettelSuggest, i as toolMemoryTaxonomy, j as lookupProjectId, k as detectProjectFromPath, l as toolKgQuery, m as toolZettelHealth, n as toolMemoryFeedback, o as toolKgAdd, p as toolZettelGodNotes, r as toolMemoryTunnels, s as toolKgContradictions, t as toolMemoryKgSearch, u as toolZettelCommunities, v as toolRegistrySearch, w as toolProjectList, x as toolProjectDetect, y as toolSessionList } from "./tools-ceiy7ANX.mjs";
3
+ import { A as formatProject, C as toolProjectInfo, D as toolMemoryGet, E as combineHybridResults, O as toolMemorySearch, S as toolProjectHealth, T as toolProjectTodo, _ as toolZettelThemes, a as toolMemoryWakeup, b as toolSessionRoute, c as toolKgInvalidate, d as toolZettelConverse, f as toolZettelExplore, g as toolZettelSurprise, h as toolZettelSuggest, i as toolMemoryTaxonomy, j as lookupProjectId, k as detectProjectFromPath, l as toolKgQuery, m as toolZettelHealth, n as toolMemoryFeedback, o as toolKgAdd, p as toolZettelGodNotes, r as toolMemoryTunnels, s as toolKgContradictions, t as toolMemoryKgSearch, u as toolZettelCommunities, v as toolRegistrySearch, w as toolProjectList, x as toolProjectDetect, y as toolSessionList } from "./tools-y2bJpKom.mjs";
4
4
 
5
5
  export { toolKgAdd, toolKgContradictions, toolKgInvalidate, toolKgQuery, toolMemoryTunnels, toolZettelCommunities, toolZettelConverse, toolZettelExplore, toolZettelGodNotes, toolZettelHealth, toolZettelSuggest, toolZettelSurprise, toolZettelThemes };
@@ -2,7 +2,7 @@ import { n as cosineSimilarity } from "./embeddings-DOLZnT1X.mjs";
2
2
  import { t as STOP_WORDS } from "./stop-words-Hfu8u22w.mjs";
3
3
  import { c as touchChunksLastAccessed, i as populateSlugs, o as searchMemoryHybrid } from "./search-Rpk1cSBC.mjs";
4
4
  import { r as formatDetectionJson, t as detectProject } from "./detect-CdaA48EI.mjs";
5
- import { a as kgContradictions, i as kgAdd, n as updateEntityFeedbackWeight, o as kgInvalidate, s as kgQuery, t as listKgEntities } from "./kg-entity-DbOMPdF9.mjs";
5
+ import { a as kgContradictions, i as kgAdd, n as updateEntityFeedbackWeight, o as kgInvalidate, s as kgQuery, t as listKgEntities } from "./kg-entity-r8duqhi9.mjs";
6
6
  import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
7
7
  import { homedir } from "node:os";
8
8
  import { basename, isAbsolute, join, resolve } from "node:path";
@@ -104,7 +104,7 @@ async function toolMemorySearch(registryDb, federation, params, searchDefaults)
104
104
  } else results = searchMemory(federation, params.query, searchOpts);
105
105
  }
106
106
  try {
107
- const { applyLinkBoost } = await import("./link-boost-fYjUnxCN.mjs");
107
+ const { applyLinkBoost } = await import("./link-boost-QFLrJwD6.mjs");
108
108
  const paths = [...new Set(results.map((r) => r.path))];
109
109
  if (isBackend(federation) && paths.length > 1) {
110
110
  const edges = [];
@@ -151,7 +151,7 @@ async function toolMemorySearch(registryDb, federation, params, searchDefaults)
151
151
  return `${header}\n${raw.length > snippetLength ? raw.slice(0, snippetLength) + "..." : raw}`;
152
152
  }).join(useCompact ? "\n" : "\n\n---\n\n");
153
153
  try {
154
- const { saveQueryResult } = await import("./query-feedback-BBMBp96K.mjs");
154
+ const { saveQueryResult } = await import("./query-feedback-DhyLOe5S.mjs");
155
155
  saveQueryResult({
156
156
  query: params.query,
157
157
  timestamp: Date.now(),
@@ -676,7 +676,7 @@ function toolSessionList(registryDb, params) {
676
676
  */
677
677
  async function toolSessionRoute(registryDb, federation, params) {
678
678
  try {
679
- const { autoRoute, formatAutoRouteJson } = await import("./auto-route-Byf8ENXj.mjs");
679
+ const { autoRoute, formatAutoRouteJson } = await import("./auto-route-DM7GhJ8y.mjs");
680
680
  const result = await autoRoute(registryDb, federation, params.cwd, params.context);
681
681
  if (!result) return { content: [{
682
682
  type: "text",
@@ -740,7 +740,7 @@ function toolRegistrySearch(registryDb, params) {
740
740
  //#region src/mcp/tools/zettel.ts
741
741
  async function toolZettelExplore(backend, params) {
742
742
  try {
743
- const { zettelExplore } = await import("./zettelkasten-CvjmMghT.mjs");
743
+ const { zettelExplore } = await import("./zettelkasten-vo7psPdT.mjs");
744
744
  const result = await zettelExplore(backend, {
745
745
  startNote: params.start_note,
746
746
  depth: params.depth,
@@ -763,7 +763,7 @@ async function toolZettelExplore(backend, params) {
763
763
  }
764
764
  async function toolZettelHealth(backend, params) {
765
765
  try {
766
- const { zettelHealth } = await import("./zettelkasten-CvjmMghT.mjs");
766
+ const { zettelHealth } = await import("./zettelkasten-vo7psPdT.mjs");
767
767
  const result = await zettelHealth(backend, {
768
768
  scope: params.scope,
769
769
  projectPath: params.project_path,
@@ -786,7 +786,7 @@ async function toolZettelHealth(backend, params) {
786
786
  }
787
787
  async function toolZettelSurprise(backend, params) {
788
788
  try {
789
- const { zettelSurprise } = await import("./zettelkasten-CvjmMghT.mjs");
789
+ const { zettelSurprise } = await import("./zettelkasten-vo7psPdT.mjs");
790
790
  const results = await zettelSurprise(backend, {
791
791
  referencePath: params.reference_path,
792
792
  vaultProjectId: params.vault_project_id,
@@ -810,7 +810,7 @@ async function toolZettelSurprise(backend, params) {
810
810
  }
811
811
  async function toolZettelSuggest(backend, params) {
812
812
  try {
813
- const { zettelSuggest } = await import("./zettelkasten-CvjmMghT.mjs");
813
+ const { zettelSuggest } = await import("./zettelkasten-vo7psPdT.mjs");
814
814
  const results = await zettelSuggest(backend, {
815
815
  notePath: params.note_path,
816
816
  vaultProjectId: params.vault_project_id,
@@ -833,7 +833,7 @@ async function toolZettelSuggest(backend, params) {
833
833
  }
834
834
  async function toolZettelConverse(backend, params) {
835
835
  try {
836
- const { zettelConverse } = await import("./zettelkasten-CvjmMghT.mjs");
836
+ const { zettelConverse } = await import("./zettelkasten-vo7psPdT.mjs");
837
837
  const result = await zettelConverse(backend, {
838
838
  question: params.question,
839
839
  vaultProjectId: params.vault_project_id,
@@ -841,7 +841,7 @@ async function toolZettelConverse(backend, params) {
841
841
  limit: params.limit
842
842
  });
843
843
  try {
844
- const { saveQueryResult } = await import("./query-feedback-BBMBp96K.mjs");
844
+ const { saveQueryResult } = await import("./query-feedback-DhyLOe5S.mjs");
845
845
  saveQueryResult({
846
846
  query: params.question,
847
847
  timestamp: Date.now(),
@@ -869,7 +869,7 @@ async function toolZettelConverse(backend, params) {
869
869
  }
870
870
  async function toolZettelThemes(backend, params) {
871
871
  try {
872
- const { zettelThemes } = await import("./zettelkasten-CvjmMghT.mjs");
872
+ const { zettelThemes } = await import("./zettelkasten-vo7psPdT.mjs");
873
873
  const result = await zettelThemes(backend, {
874
874
  vaultProjectId: params.vault_project_id,
875
875
  lookbackDays: params.lookback_days,
@@ -893,7 +893,7 @@ async function toolZettelThemes(backend, params) {
893
893
  }
894
894
  async function toolZettelGodNotes(backend, params) {
895
895
  try {
896
- const { zettelGodNotes } = await import("./zettelkasten-CvjmMghT.mjs");
896
+ const { zettelGodNotes } = await import("./zettelkasten-vo7psPdT.mjs");
897
897
  const result = await zettelGodNotes(backend, {
898
898
  limit: params.limit,
899
899
  minInbound: params.min_inbound
@@ -914,7 +914,7 @@ async function toolZettelGodNotes(backend, params) {
914
914
  }
915
915
  async function toolZettelCommunities(backend, params) {
916
916
  try {
917
- const { zettelCommunities } = await import("./zettelkasten-CvjmMghT.mjs");
917
+ const { zettelCommunities } = await import("./zettelkasten-vo7psPdT.mjs");
918
918
  const result = await zettelCommunities(backend, {
919
919
  minSize: params.min_size,
920
920
  maxCommunities: params.max_communities,
@@ -1914,4 +1914,4 @@ async function toolMemoryKgSearch(federationDb, pool, params) {
1914
1914
 
1915
1915
  //#endregion
1916
1916
  export { formatProject as A, toolProjectInfo as C, toolMemoryGet as D, combineHybridResults as E, toolMemorySearch as O, toolProjectHealth as S, toolProjectTodo as T, toolZettelThemes as _, toolMemoryWakeup as a, toolSessionRoute as b, toolKgInvalidate as c, toolZettelConverse as d, toolZettelExplore as f, toolZettelSurprise as g, toolZettelSuggest as h, toolMemoryTaxonomy as i, lookupProjectId as j, detectProjectFromPath as k, toolKgQuery as l, toolZettelHealth as m, toolMemoryFeedback as n, toolKgAdd as o, toolZettelGodNotes as p, toolMemoryTunnels as r, toolKgContradictions as s, toolMemoryKgSearch as t, toolZettelCommunities as u, toolRegistrySearch as v, toolProjectList as w, toolProjectDetect as x, toolSessionList as y };
1917
- //# sourceMappingURL=tools-ceiy7ANX.mjs.map
1917
+ //# sourceMappingURL=tools-y2bJpKom.mjs.map