synabun 1.3.0 → 2.0.0-beta

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 (1153) hide show
  1. package/CHANGELOG.md +197 -662
  2. package/LICENSE-COMMERCIAL.md +33 -0
  3. package/NOTICE +17 -0
  4. package/README.md +175 -853
  5. package/THIRD-PARTY-LICENSES.md +131 -0
  6. package/benchmarks/browser-live-results.json +79 -0
  7. package/benchmarks/browser-v2-results.json +258 -0
  8. package/benchmarks/fixtures/browser/README.md +57 -0
  9. package/benchmarks/fixtures/browser/page-state.json +132 -0
  10. package/benchmarks/fixtures/browser/social.json +92 -0
  11. package/benchmarks/fixtures/browser/target.json +319 -0
  12. package/benchmarks/fixtures/desktop/README.md +58 -0
  13. package/benchmarks/fixtures/desktop/abstention.json +59 -0
  14. package/benchmarks/fixtures/desktop/adversarial.json +22 -0
  15. package/benchmarks/fixtures/desktop/forbidden.json +93 -0
  16. package/benchmarks/fixtures/desktop/generate.mjs +1023 -0
  17. package/benchmarks/fixtures/desktop/target.json +136 -0
  18. package/bin/synabun.js +30 -0
  19. package/docs/browser-v2.md +109 -0
  20. package/docs/hook-integrity.md +89 -0
  21. package/docs/judgments.md +182 -0
  22. package/docs/style-guide.md +246 -0
  23. package/docs/whatsapp.md +325 -0
  24. package/hooks/claude-code/post-plan.mjs +613 -391
  25. package/hooks/claude-code/post-remember.mjs +246 -122
  26. package/hooks/claude-code/pre-compact.mjs +171 -110
  27. package/hooks/claude-code/pre-task.mjs +133 -0
  28. package/hooks/claude-code/pre-websearch.mjs +15 -4
  29. package/hooks/claude-code/prompt-origin.mjs +94 -0
  30. package/hooks/claude-code/prompt-submit.mjs +1244 -861
  31. package/hooks/claude-code/session-start.mjs +114 -156
  32. package/hooks/claude-code/shared.mjs +1228 -368
  33. package/hooks/claude-code/state.mjs +167 -0
  34. package/hooks/claude-code/stop.mjs +515 -140
  35. package/hooks/claude-code/subagent-start.mjs +154 -0
  36. package/hooks/claude-code/subagent-stop.mjs +102 -0
  37. package/hooks/claude-code/task-gate.mjs +150 -0
  38. package/lib/claude-hooks.js +84 -0
  39. package/lib/claude-usage-audit.js +141 -0
  40. package/lib/client-config-repair.js +144 -0
  41. package/lib/data-home-migration.js +498 -0
  42. package/lib/first-launch-protection.js +240 -0
  43. package/lib/paths.d.ts +21 -0
  44. package/lib/paths.js +103 -0
  45. package/mcp-server/dist/config.d.ts +22 -0
  46. package/mcp-server/dist/config.d.ts.map +1 -0
  47. package/mcp-server/dist/config.js +109 -0
  48. package/mcp-server/dist/config.js.map +1 -0
  49. package/mcp-server/dist/http.d.ts +30 -0
  50. package/mcp-server/dist/http.d.ts.map +1 -0
  51. package/mcp-server/dist/http.js +276 -0
  52. package/mcp-server/dist/http.js.map +1 -0
  53. package/mcp-server/dist/index.d.ts +48 -0
  54. package/mcp-server/dist/index.d.ts.map +1 -0
  55. package/mcp-server/dist/index.js +538 -0
  56. package/mcp-server/dist/index.js.map +1 -0
  57. package/mcp-server/dist/preload-http.d.ts +3 -0
  58. package/mcp-server/dist/preload-http.d.ts.map +1 -0
  59. package/mcp-server/dist/preload-http.js +28 -0
  60. package/mcp-server/dist/preload-http.js.map +1 -0
  61. package/mcp-server/dist/preload.d.ts +3 -0
  62. package/mcp-server/dist/preload.d.ts.map +1 -0
  63. package/mcp-server/dist/preload.js +32 -0
  64. package/mcp-server/dist/preload.js.map +1 -0
  65. package/mcp-server/dist/scripts/migrate-qdrant-to-sqlite.d.ts +19 -0
  66. package/mcp-server/dist/scripts/migrate-qdrant-to-sqlite.d.ts.map +1 -0
  67. package/mcp-server/dist/scripts/migrate-qdrant-to-sqlite.js +403 -0
  68. package/mcp-server/dist/scripts/migrate-qdrant-to-sqlite.js.map +1 -0
  69. package/mcp-server/dist/scripts/setup-embeddings.d.ts +3 -0
  70. package/mcp-server/dist/scripts/setup-embeddings.d.ts.map +1 -0
  71. package/mcp-server/dist/scripts/setup-embeddings.js +15 -0
  72. package/mcp-server/dist/scripts/setup-embeddings.js.map +1 -0
  73. package/mcp-server/dist/services/assistant-judgments.d.ts +79 -0
  74. package/mcp-server/dist/services/assistant-judgments.d.ts.map +1 -0
  75. package/mcp-server/dist/services/assistant-judgments.js +128 -0
  76. package/mcp-server/dist/services/assistant-judgments.js.map +1 -0
  77. package/mcp-server/dist/services/browser-assist-gate.d.ts +314 -0
  78. package/mcp-server/dist/services/browser-assist-gate.d.ts.map +1 -0
  79. package/mcp-server/dist/services/browser-assist-gate.js +344 -0
  80. package/mcp-server/dist/services/browser-assist-gate.js.map +1 -0
  81. package/mcp-server/dist/services/browser-assist.d.ts +104 -0
  82. package/mcp-server/dist/services/browser-assist.d.ts.map +1 -0
  83. package/mcp-server/dist/services/browser-assist.js +282 -0
  84. package/mcp-server/dist/services/browser-assist.js.map +1 -0
  85. package/mcp-server/dist/services/browser-bench.d.ts +772 -0
  86. package/mcp-server/dist/services/browser-bench.d.ts.map +1 -0
  87. package/mcp-server/dist/services/browser-bench.js +424 -0
  88. package/mcp-server/dist/services/browser-bench.js.map +1 -0
  89. package/mcp-server/dist/services/browser-judgments.d.ts +196 -0
  90. package/mcp-server/dist/services/browser-judgments.d.ts.map +1 -0
  91. package/mcp-server/dist/services/browser-judgments.js +363 -0
  92. package/mcp-server/dist/services/browser-judgments.js.map +1 -0
  93. package/mcp-server/dist/services/browser-risk.d.ts +166 -0
  94. package/mcp-server/dist/services/browser-risk.d.ts.map +1 -0
  95. package/mcp-server/dist/services/browser-risk.js +458 -0
  96. package/mcp-server/dist/services/browser-risk.js.map +1 -0
  97. package/mcp-server/dist/services/categories.d.ts +61 -0
  98. package/mcp-server/dist/services/categories.d.ts.map +1 -0
  99. package/mcp-server/dist/services/categories.js +377 -0
  100. package/mcp-server/dist/services/categories.js.map +1 -0
  101. package/mcp-server/dist/services/codex-config-heal.d.ts +49 -0
  102. package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -0
  103. package/mcp-server/dist/services/codex-config-heal.js +346 -0
  104. package/mcp-server/dist/services/codex-config-heal.js.map +1 -0
  105. package/mcp-server/dist/services/desktop-assist-gate.d.ts +198 -0
  106. package/mcp-server/dist/services/desktop-assist-gate.d.ts.map +1 -0
  107. package/mcp-server/dist/services/desktop-assist-gate.js +202 -0
  108. package/mcp-server/dist/services/desktop-assist-gate.js.map +1 -0
  109. package/mcp-server/dist/services/desktop-assist.d.ts +104 -0
  110. package/mcp-server/dist/services/desktop-assist.d.ts.map +1 -0
  111. package/mcp-server/dist/services/desktop-assist.js +266 -0
  112. package/mcp-server/dist/services/desktop-assist.js.map +1 -0
  113. package/mcp-server/dist/services/desktop-bench.d.ts +553 -0
  114. package/mcp-server/dist/services/desktop-bench.d.ts.map +1 -0
  115. package/mcp-server/dist/services/desktop-bench.js +311 -0
  116. package/mcp-server/dist/services/desktop-bench.js.map +1 -0
  117. package/mcp-server/dist/services/desktop-client.d.ts +23 -0
  118. package/mcp-server/dist/services/desktop-client.d.ts.map +1 -0
  119. package/mcp-server/dist/services/desktop-client.js +79 -0
  120. package/mcp-server/dist/services/desktop-client.js.map +1 -0
  121. package/mcp-server/dist/services/desktop-judgments.d.ts +125 -0
  122. package/mcp-server/dist/services/desktop-judgments.d.ts.map +1 -0
  123. package/mcp-server/dist/services/desktop-judgments.js +177 -0
  124. package/mcp-server/dist/services/desktop-judgments.js.map +1 -0
  125. package/mcp-server/dist/services/desktop-risk.d.ts +283 -0
  126. package/mcp-server/dist/services/desktop-risk.d.ts.map +1 -0
  127. package/mcp-server/dist/services/desktop-risk.js +605 -0
  128. package/mcp-server/dist/services/desktop-risk.js.map +1 -0
  129. package/mcp-server/dist/services/discord.d.ts +112 -0
  130. package/mcp-server/dist/services/discord.d.ts.map +1 -0
  131. package/mcp-server/dist/services/discord.js +477 -0
  132. package/mcp-server/dist/services/discord.js.map +1 -0
  133. package/mcp-server/dist/services/edit-stale.d.ts +81 -0
  134. package/mcp-server/dist/services/edit-stale.d.ts.map +1 -0
  135. package/mcp-server/dist/services/edit-stale.js +358 -0
  136. package/mcp-server/dist/services/edit-stale.js.map +1 -0
  137. package/mcp-server/dist/services/embedding-service.d.ts +16 -0
  138. package/mcp-server/dist/services/embedding-service.d.ts.map +1 -0
  139. package/mcp-server/dist/services/embedding-service.js +126 -0
  140. package/mcp-server/dist/services/embedding-service.js.map +1 -0
  141. package/mcp-server/dist/services/embedding-worker.d.ts +2 -0
  142. package/mcp-server/dist/services/embedding-worker.d.ts.map +1 -0
  143. package/mcp-server/dist/services/embedding-worker.js +52 -0
  144. package/mcp-server/dist/services/embedding-worker.js.map +1 -0
  145. package/mcp-server/dist/services/fb-regions.d.ts +71 -0
  146. package/mcp-server/dist/services/fb-regions.d.ts.map +1 -0
  147. package/mcp-server/dist/services/fb-regions.js +211 -0
  148. package/mcp-server/dist/services/fb-regions.js.map +1 -0
  149. package/mcp-server/dist/services/file-checksums.d.ts +22 -0
  150. package/mcp-server/dist/services/file-checksums.d.ts.map +1 -0
  151. package/mcp-server/dist/services/file-checksums.js +55 -0
  152. package/mcp-server/dist/services/file-checksums.js.map +1 -0
  153. package/mcp-server/dist/services/file-excerpt.d.ts +24 -0
  154. package/mcp-server/dist/services/file-excerpt.d.ts.map +1 -0
  155. package/mcp-server/dist/services/file-excerpt.js +65 -0
  156. package/mcp-server/dist/services/file-excerpt.js.map +1 -0
  157. package/mcp-server/dist/services/identity.d.ts +124 -0
  158. package/mcp-server/dist/services/identity.d.ts.map +1 -0
  159. package/mcp-server/dist/services/identity.js +241 -0
  160. package/mcp-server/dist/services/identity.js.map +1 -0
  161. package/mcp-server/dist/services/local-embeddings.d.ts +2 -0
  162. package/mcp-server/dist/services/local-embeddings.d.ts.map +1 -0
  163. package/mcp-server/dist/services/local-embeddings.js +3 -0
  164. package/mcp-server/dist/services/local-embeddings.js.map +1 -0
  165. package/mcp-server/dist/services/memory-client.d.ts +4 -0
  166. package/mcp-server/dist/services/memory-client.d.ts.map +1 -0
  167. package/mcp-server/dist/services/memory-client.js +27 -0
  168. package/mcp-server/dist/services/memory-client.js.map +1 -0
  169. package/mcp-server/dist/services/memory-judge-backfill.d.ts +135 -0
  170. package/mcp-server/dist/services/memory-judge-backfill.d.ts.map +1 -0
  171. package/mcp-server/dist/services/memory-judge-backfill.js +510 -0
  172. package/mcp-server/dist/services/memory-judge-backfill.js.map +1 -0
  173. package/mcp-server/dist/services/memory-judgments.d.ts +362 -0
  174. package/mcp-server/dist/services/memory-judgments.d.ts.map +1 -0
  175. package/mcp-server/dist/services/memory-judgments.js +674 -0
  176. package/mcp-server/dist/services/memory-judgments.js.map +1 -0
  177. package/mcp-server/dist/services/memory-maintenance.d.ts +108 -0
  178. package/mcp-server/dist/services/memory-maintenance.d.ts.map +1 -0
  179. package/mcp-server/dist/services/memory-maintenance.js +403 -0
  180. package/mcp-server/dist/services/memory-maintenance.js.map +1 -0
  181. package/mcp-server/dist/services/memory-retrieval.d.ts +66 -0
  182. package/mcp-server/dist/services/memory-retrieval.d.ts.map +1 -0
  183. package/mcp-server/dist/services/memory-retrieval.js +412 -0
  184. package/mcp-server/dist/services/memory-retrieval.js.map +1 -0
  185. package/mcp-server/dist/services/memory-schema.d.ts +16 -0
  186. package/mcp-server/dist/services/memory-schema.d.ts.map +1 -0
  187. package/mcp-server/dist/services/memory-schema.js +282 -0
  188. package/mcp-server/dist/services/memory-schema.js.map +1 -0
  189. package/mcp-server/dist/services/memory-sessions.d.ts +24 -0
  190. package/mcp-server/dist/services/memory-sessions.d.ts.map +1 -0
  191. package/mcp-server/dist/services/memory-sessions.js +110 -0
  192. package/mcp-server/dist/services/memory-sessions.js.map +1 -0
  193. package/mcp-server/dist/services/memory-triage.d.ts +60 -0
  194. package/mcp-server/dist/services/memory-triage.d.ts.map +1 -0
  195. package/mcp-server/dist/services/memory-triage.js +189 -0
  196. package/mcp-server/dist/services/memory-triage.js.map +1 -0
  197. package/mcp-server/dist/services/memory-verification.d.ts +24 -0
  198. package/mcp-server/dist/services/memory-verification.d.ts.map +1 -0
  199. package/mcp-server/dist/services/memory-verification.js +92 -0
  200. package/mcp-server/dist/services/memory-verification.js.map +1 -0
  201. package/mcp-server/dist/services/memory-writer.d.ts +26 -0
  202. package/mcp-server/dist/services/memory-writer.d.ts.map +1 -0
  203. package/mcp-server/dist/services/memory-writer.js +126 -0
  204. package/mcp-server/dist/services/memory-writer.js.map +1 -0
  205. package/mcp-server/dist/services/neural-interface.d.ts +439 -0
  206. package/mcp-server/dist/services/neural-interface.d.ts.map +1 -0
  207. package/mcp-server/dist/services/neural-interface.js +1135 -0
  208. package/mcp-server/dist/services/neural-interface.js.map +1 -0
  209. package/mcp-server/dist/services/profiles.d.ts +120 -0
  210. package/mcp-server/dist/services/profiles.d.ts.map +1 -0
  211. package/mcp-server/dist/services/profiles.js +387 -0
  212. package/mcp-server/dist/services/profiles.js.map +1 -0
  213. package/mcp-server/dist/services/secret-gate.d.ts +29 -0
  214. package/mcp-server/dist/services/secret-gate.d.ts.map +1 -0
  215. package/mcp-server/dist/services/secret-gate.js +43 -0
  216. package/mcp-server/dist/services/secret-gate.js.map +1 -0
  217. package/mcp-server/dist/services/server-instructions.d.ts +10 -0
  218. package/mcp-server/dist/services/server-instructions.d.ts.map +1 -0
  219. package/mcp-server/dist/services/server-instructions.js +101 -0
  220. package/mcp-server/dist/services/server-instructions.js.map +1 -0
  221. package/mcp-server/dist/services/sqlite.d.ts +235 -0
  222. package/mcp-server/dist/services/sqlite.d.ts.map +1 -0
  223. package/mcp-server/dist/services/sqlite.js +1185 -0
  224. package/mcp-server/dist/services/sqlite.js.map +1 -0
  225. package/mcp-server/dist/services/typesafe-bench.d.ts +99 -0
  226. package/mcp-server/dist/services/typesafe-bench.d.ts.map +1 -0
  227. package/mcp-server/dist/services/typesafe-bench.js +218 -0
  228. package/mcp-server/dist/services/typesafe-bench.js.map +1 -0
  229. package/mcp-server/dist/services/typesafe-config.d.ts +342 -0
  230. package/mcp-server/dist/services/typesafe-config.d.ts.map +1 -0
  231. package/mcp-server/dist/services/typesafe-config.js +746 -0
  232. package/mcp-server/dist/services/typesafe-config.js.map +1 -0
  233. package/mcp-server/dist/services/typesafe.d.ts +213 -0
  234. package/mcp-server/dist/services/typesafe.d.ts.map +1 -0
  235. package/mcp-server/dist/services/typesafe.js +330 -0
  236. package/mcp-server/dist/services/typesafe.js.map +1 -0
  237. package/mcp-server/dist/services/vector-cache.d.ts +68 -0
  238. package/mcp-server/dist/services/vector-cache.d.ts.map +1 -0
  239. package/mcp-server/dist/services/vector-cache.js +271 -0
  240. package/mcp-server/dist/services/vector-cache.js.map +1 -0
  241. package/mcp-server/dist/services/whiteboard-geometry.d.ts +305 -0
  242. package/mcp-server/dist/services/whiteboard-geometry.d.ts.map +1 -0
  243. package/mcp-server/dist/services/whiteboard-geometry.js +666 -0
  244. package/mcp-server/dist/services/whiteboard-geometry.js.map +1 -0
  245. package/mcp-server/dist/tools/agents.d.ts +212 -0
  246. package/mcp-server/dist/tools/agents.d.ts.map +1 -0
  247. package/mcp-server/dist/tools/agents.js +407 -0
  248. package/mcp-server/dist/tools/agents.js.map +1 -0
  249. package/mcp-server/dist/tools/bluesky.d.ts +39 -0
  250. package/mcp-server/dist/tools/bluesky.d.ts.map +1 -0
  251. package/mcp-server/dist/tools/bluesky.js +1144 -0
  252. package/mcp-server/dist/tools/bluesky.js.map +1 -0
  253. package/mcp-server/dist/tools/browser-advanced.d.ts +58 -0
  254. package/mcp-server/dist/tools/browser-advanced.d.ts.map +1 -0
  255. package/mcp-server/dist/tools/browser-advanced.js +106 -0
  256. package/mcp-server/dist/tools/browser-advanced.js.map +1 -0
  257. package/mcp-server/dist/tools/browser-batch.d.ts +99 -0
  258. package/mcp-server/dist/tools/browser-batch.d.ts.map +1 -0
  259. package/mcp-server/dist/tools/browser-batch.js +187 -0
  260. package/mcp-server/dist/tools/browser-batch.js.map +1 -0
  261. package/mcp-server/dist/tools/browser-cheatsheet.d.ts +21 -0
  262. package/mcp-server/dist/tools/browser-cheatsheet.d.ts.map +1 -0
  263. package/mcp-server/dist/tools/browser-cheatsheet.js +144 -0
  264. package/mcp-server/dist/tools/browser-cheatsheet.js.map +1 -0
  265. package/mcp-server/dist/tools/browser-interact.d.ts +239 -0
  266. package/mcp-server/dist/tools/browser-interact.d.ts.map +1 -0
  267. package/mcp-server/dist/tools/browser-interact.js +300 -0
  268. package/mcp-server/dist/tools/browser-interact.js.map +1 -0
  269. package/mcp-server/dist/tools/browser-navigate.d.ts +90 -0
  270. package/mcp-server/dist/tools/browser-navigate.d.ts.map +1 -0
  271. package/mcp-server/dist/tools/browser-navigate.js +142 -0
  272. package/mcp-server/dist/tools/browser-navigate.js.map +1 -0
  273. package/mcp-server/dist/tools/browser-observe.d.ts +561 -0
  274. package/mcp-server/dist/tools/browser-observe.d.ts.map +1 -0
  275. package/mcp-server/dist/tools/browser-observe.js +2009 -0
  276. package/mcp-server/dist/tools/browser-observe.js.map +1 -0
  277. package/mcp-server/dist/tools/browser.d.ts +34 -0
  278. package/mcp-server/dist/tools/browser.d.ts.map +1 -0
  279. package/mcp-server/dist/tools/browser.js +112 -0
  280. package/mcp-server/dist/tools/browser.js.map +1 -0
  281. package/mcp-server/dist/tools/card-tools.d.ts +82 -0
  282. package/mcp-server/dist/tools/card-tools.d.ts.map +1 -0
  283. package/mcp-server/dist/tools/card-tools.js +137 -0
  284. package/mcp-server/dist/tools/card-tools.js.map +1 -0
  285. package/mcp-server/dist/tools/card.d.ts +8 -0
  286. package/mcp-server/dist/tools/card.d.ts.map +1 -0
  287. package/mcp-server/dist/tools/card.js +16 -0
  288. package/mcp-server/dist/tools/card.js.map +1 -0
  289. package/mcp-server/dist/tools/category.d.ts +32 -0
  290. package/mcp-server/dist/tools/category.d.ts.map +1 -0
  291. package/mcp-server/dist/tools/category.js +290 -0
  292. package/mcp-server/dist/tools/category.js.map +1 -0
  293. package/mcp-server/dist/tools/choice.d.ts +160 -0
  294. package/mcp-server/dist/tools/choice.d.ts.map +1 -0
  295. package/mcp-server/dist/tools/choice.js +80 -0
  296. package/mcp-server/dist/tools/choice.js.map +1 -0
  297. package/mcp-server/dist/tools/computer.d.ts +71 -0
  298. package/mcp-server/dist/tools/computer.d.ts.map +1 -0
  299. package/mcp-server/dist/tools/computer.js +174 -0
  300. package/mcp-server/dist/tools/computer.js.map +1 -0
  301. package/mcp-server/dist/tools/discord-channel.d.ts +44 -0
  302. package/mcp-server/dist/tools/discord-channel.d.ts.map +1 -0
  303. package/mcp-server/dist/tools/discord-channel.js +236 -0
  304. package/mcp-server/dist/tools/discord-channel.js.map +1 -0
  305. package/mcp-server/dist/tools/discord-guild.d.ts +20 -0
  306. package/mcp-server/dist/tools/discord-guild.d.ts.map +1 -0
  307. package/mcp-server/dist/tools/discord-guild.js +138 -0
  308. package/mcp-server/dist/tools/discord-guild.js.map +1 -0
  309. package/mcp-server/dist/tools/discord-member.d.ts +26 -0
  310. package/mcp-server/dist/tools/discord-member.d.ts.map +1 -0
  311. package/mcp-server/dist/tools/discord-member.js +122 -0
  312. package/mcp-server/dist/tools/discord-member.js.map +1 -0
  313. package/mcp-server/dist/tools/discord-message.d.ts +36 -0
  314. package/mcp-server/dist/tools/discord-message.d.ts.map +1 -0
  315. package/mcp-server/dist/tools/discord-message.js +184 -0
  316. package/mcp-server/dist/tools/discord-message.js.map +1 -0
  317. package/mcp-server/dist/tools/discord-onboarding.d.ts +32 -0
  318. package/mcp-server/dist/tools/discord-onboarding.d.ts.map +1 -0
  319. package/mcp-server/dist/tools/discord-onboarding.js +179 -0
  320. package/mcp-server/dist/tools/discord-onboarding.js.map +1 -0
  321. package/mcp-server/dist/tools/discord-role.d.ts +34 -0
  322. package/mcp-server/dist/tools/discord-role.d.ts.map +1 -0
  323. package/mcp-server/dist/tools/discord-role.js +178 -0
  324. package/mcp-server/dist/tools/discord-role.js.map +1 -0
  325. package/mcp-server/dist/tools/discord-thread.d.ts +28 -0
  326. package/mcp-server/dist/tools/discord-thread.d.ts.map +1 -0
  327. package/mcp-server/dist/tools/discord-thread.js +88 -0
  328. package/mcp-server/dist/tools/discord-thread.js.map +1 -0
  329. package/mcp-server/dist/tools/discord-webhook.d.ts +32 -0
  330. package/mcp-server/dist/tools/discord-webhook.d.ts.map +1 -0
  331. package/mcp-server/dist/tools/discord-webhook.js +154 -0
  332. package/mcp-server/dist/tools/discord-webhook.js.map +1 -0
  333. package/mcp-server/dist/tools/discord.d.ts +7 -0
  334. package/mcp-server/dist/tools/discord.d.ts.map +1 -0
  335. package/mcp-server/dist/tools/discord.js +25 -0
  336. package/mcp-server/dist/tools/discord.js.map +1 -0
  337. package/mcp-server/dist/tools/fb-groups.d.ts +98 -0
  338. package/mcp-server/dist/tools/fb-groups.d.ts.map +1 -0
  339. package/mcp-server/dist/tools/fb-groups.js +369 -0
  340. package/mcp-server/dist/tools/fb-groups.js.map +1 -0
  341. package/mcp-server/dist/tools/forget.d.ts +14 -0
  342. package/mcp-server/dist/tools/forget.d.ts.map +1 -0
  343. package/mcp-server/dist/tools/forget.js +26 -0
  344. package/mcp-server/dist/tools/forget.js.map +1 -0
  345. package/mcp-server/dist/tools/git-tools.d.ts +24 -0
  346. package/mcp-server/dist/tools/git-tools.d.ts.map +1 -0
  347. package/mcp-server/dist/tools/git-tools.js +107 -0
  348. package/mcp-server/dist/tools/git-tools.js.map +1 -0
  349. package/mcp-server/dist/tools/git.d.ts +7 -0
  350. package/mcp-server/dist/tools/git.d.ts.map +1 -0
  351. package/mcp-server/dist/tools/git.js +11 -0
  352. package/mcp-server/dist/tools/git.js.map +1 -0
  353. package/mcp-server/dist/tools/gsc/experience.d.ts +135 -0
  354. package/mcp-server/dist/tools/gsc/experience.d.ts.map +1 -0
  355. package/mcp-server/dist/tools/gsc/experience.js +351 -0
  356. package/mcp-server/dist/tools/gsc/experience.js.map +1 -0
  357. package/mcp-server/dist/tools/gsc/helpers.d.ts +78 -0
  358. package/mcp-server/dist/tools/gsc/helpers.d.ts.map +1 -0
  359. package/mcp-server/dist/tools/gsc/helpers.js +258 -0
  360. package/mcp-server/dist/tools/gsc/helpers.js.map +1 -0
  361. package/mcp-server/dist/tools/gsc/indexing.d.ts +121 -0
  362. package/mcp-server/dist/tools/gsc/indexing.d.ts.map +1 -0
  363. package/mcp-server/dist/tools/gsc/indexing.js +357 -0
  364. package/mcp-server/dist/tools/gsc/indexing.js.map +1 -0
  365. package/mcp-server/dist/tools/gsc/inspect.d.ts +78 -0
  366. package/mcp-server/dist/tools/gsc/inspect.d.ts.map +1 -0
  367. package/mcp-server/dist/tools/gsc/inspect.js +321 -0
  368. package/mcp-server/dist/tools/gsc/inspect.js.map +1 -0
  369. package/mcp-server/dist/tools/gsc/navigate.d.ts +44 -0
  370. package/mcp-server/dist/tools/gsc/navigate.d.ts.map +1 -0
  371. package/mcp-server/dist/tools/gsc/navigate.js +197 -0
  372. package/mcp-server/dist/tools/gsc/navigate.js.map +1 -0
  373. package/mcp-server/dist/tools/gsc/performance.d.ts +84 -0
  374. package/mcp-server/dist/tools/gsc/performance.d.ts.map +1 -0
  375. package/mcp-server/dist/tools/gsc/performance.js +274 -0
  376. package/mcp-server/dist/tools/gsc/performance.js.map +1 -0
  377. package/mcp-server/dist/tools/gsc/settings.d.ts +153 -0
  378. package/mcp-server/dist/tools/gsc/settings.d.ts.map +1 -0
  379. package/mcp-server/dist/tools/gsc/settings.js +425 -0
  380. package/mcp-server/dist/tools/gsc/settings.js.map +1 -0
  381. package/mcp-server/dist/tools/gsc.d.ts +7 -0
  382. package/mcp-server/dist/tools/gsc.d.ts.map +1 -0
  383. package/mcp-server/dist/tools/gsc.js +51 -0
  384. package/mcp-server/dist/tools/gsc.js.map +1 -0
  385. package/mcp-server/dist/tools/image-tools.d.ts +24 -0
  386. package/mcp-server/dist/tools/image-tools.d.ts.map +1 -0
  387. package/mcp-server/dist/tools/image-tools.js +80 -0
  388. package/mcp-server/dist/tools/image-tools.js.map +1 -0
  389. package/mcp-server/dist/tools/image.d.ts +10 -0
  390. package/mcp-server/dist/tools/image.d.ts.map +1 -0
  391. package/mcp-server/dist/tools/image.js +14 -0
  392. package/mcp-server/dist/tools/image.js.map +1 -0
  393. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts +97 -0
  394. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts.map +1 -0
  395. package/mcp-server/dist/tools/leonardo-browser-tools.js +228 -0
  396. package/mcp-server/dist/tools/leonardo-browser-tools.js.map +1 -0
  397. package/mcp-server/dist/tools/leonardo.d.ts +13 -0
  398. package/mcp-server/dist/tools/leonardo.d.ts.map +1 -0
  399. package/mcp-server/dist/tools/leonardo.js +21 -0
  400. package/mcp-server/dist/tools/leonardo.js.map +1 -0
  401. package/mcp-server/dist/tools/loop.d.ts +30 -0
  402. package/mcp-server/dist/tools/loop.d.ts.map +1 -0
  403. package/mcp-server/dist/tools/loop.js +401 -0
  404. package/mcp-server/dist/tools/loop.js.map +1 -0
  405. package/mcp-server/dist/tools/memories.d.ts +56 -0
  406. package/mcp-server/dist/tools/memories.d.ts.map +1 -0
  407. package/mcp-server/dist/tools/memories.js +171 -0
  408. package/mcp-server/dist/tools/memories.js.map +1 -0
  409. package/mcp-server/dist/tools/morelogin.d.ts +24 -0
  410. package/mcp-server/dist/tools/morelogin.d.ts.map +1 -0
  411. package/mcp-server/dist/tools/morelogin.js +79 -0
  412. package/mcp-server/dist/tools/morelogin.js.map +1 -0
  413. package/mcp-server/dist/tools/profile.d.ts +17 -0
  414. package/mcp-server/dist/tools/profile.d.ts.map +1 -0
  415. package/mcp-server/dist/tools/profile.js +114 -0
  416. package/mcp-server/dist/tools/profile.js.map +1 -0
  417. package/mcp-server/dist/tools/recall.d.ts +71 -0
  418. package/mcp-server/dist/tools/recall.d.ts.map +1 -0
  419. package/mcp-server/dist/tools/recall.js +147 -0
  420. package/mcp-server/dist/tools/recall.js.map +1 -0
  421. package/mcp-server/dist/tools/reflect.d.ts +56 -0
  422. package/mcp-server/dist/tools/reflect.d.ts.map +1 -0
  423. package/mcp-server/dist/tools/reflect.js +157 -0
  424. package/mcp-server/dist/tools/reflect.js.map +1 -0
  425. package/mcp-server/dist/tools/remember.d.ts +62 -0
  426. package/mcp-server/dist/tools/remember.d.ts.map +1 -0
  427. package/mcp-server/dist/tools/remember.js +201 -0
  428. package/mcp-server/dist/tools/remember.js.map +1 -0
  429. package/mcp-server/dist/tools/response.d.ts +23 -0
  430. package/mcp-server/dist/tools/response.d.ts.map +1 -0
  431. package/mcp-server/dist/tools/response.js +24 -0
  432. package/mcp-server/dist/tools/response.js.map +1 -0
  433. package/mcp-server/dist/tools/restore.d.ts +14 -0
  434. package/mcp-server/dist/tools/restore.d.ts.map +1 -0
  435. package/mcp-server/dist/tools/restore.js +23 -0
  436. package/mcp-server/dist/tools/restore.js.map +1 -0
  437. package/mcp-server/dist/tools/style-guide-tools.d.ts +51 -0
  438. package/mcp-server/dist/tools/style-guide-tools.d.ts.map +1 -0
  439. package/mcp-server/dist/tools/style-guide-tools.js +231 -0
  440. package/mcp-server/dist/tools/style-guide-tools.js.map +1 -0
  441. package/mcp-server/dist/tools/style-guide.d.ts +8 -0
  442. package/mcp-server/dist/tools/style-guide.d.ts.map +1 -0
  443. package/mcp-server/dist/tools/style-guide.js +12 -0
  444. package/mcp-server/dist/tools/style-guide.js.map +1 -0
  445. package/mcp-server/dist/tools/sync.d.ts +20 -0
  446. package/mcp-server/dist/tools/sync.d.ts.map +1 -0
  447. package/mcp-server/dist/tools/sync.js +156 -0
  448. package/mcp-server/dist/tools/sync.js.map +1 -0
  449. package/mcp-server/dist/tools/tictactoe-tools.d.ts +18 -0
  450. package/mcp-server/dist/tools/tictactoe-tools.d.ts.map +1 -0
  451. package/mcp-server/dist/tools/tictactoe-tools.js +78 -0
  452. package/mcp-server/dist/tools/tictactoe-tools.js.map +1 -0
  453. package/mcp-server/dist/tools/tictactoe.d.ts +7 -0
  454. package/mcp-server/dist/tools/tictactoe.d.ts.map +1 -0
  455. package/mcp-server/dist/tools/tictactoe.js +11 -0
  456. package/mcp-server/dist/tools/tictactoe.js.map +1 -0
  457. package/mcp-server/dist/tools/utils.d.ts +19 -0
  458. package/mcp-server/dist/tools/utils.d.ts.map +1 -0
  459. package/mcp-server/dist/tools/utils.js +54 -0
  460. package/mcp-server/dist/tools/utils.js.map +1 -0
  461. package/mcp-server/dist/tools/whiteboard-format.d.ts +88 -0
  462. package/mcp-server/dist/tools/whiteboard-format.d.ts.map +1 -0
  463. package/mcp-server/dist/tools/whiteboard-format.js +337 -0
  464. package/mcp-server/dist/tools/whiteboard-format.js.map +1 -0
  465. package/mcp-server/dist/tools/whiteboard-tools.d.ts +326 -0
  466. package/mcp-server/dist/tools/whiteboard-tools.d.ts.map +1 -0
  467. package/mcp-server/dist/tools/whiteboard-tools.js +222 -0
  468. package/mcp-server/dist/tools/whiteboard-tools.js.map +1 -0
  469. package/mcp-server/dist/tools/whiteboard.d.ts +8 -0
  470. package/mcp-server/dist/tools/whiteboard.d.ts.map +1 -0
  471. package/mcp-server/dist/tools/whiteboard.js +16 -0
  472. package/mcp-server/dist/tools/whiteboard.js.map +1 -0
  473. package/mcp-server/dist/tools/youtube/analytics.d.ts +46 -0
  474. package/mcp-server/dist/tools/youtube/analytics.d.ts.map +1 -0
  475. package/mcp-server/dist/tools/youtube/analytics.js +83 -0
  476. package/mcp-server/dist/tools/youtube/analytics.js.map +1 -0
  477. package/mcp-server/dist/tools/youtube/dedup.d.ts +35 -0
  478. package/mcp-server/dist/tools/youtube/dedup.d.ts.map +1 -0
  479. package/mcp-server/dist/tools/youtube/dedup.js +56 -0
  480. package/mcp-server/dist/tools/youtube/dedup.js.map +1 -0
  481. package/mcp-server/dist/tools/youtube/download.d.ts +31 -0
  482. package/mcp-server/dist/tools/youtube/download.d.ts.map +1 -0
  483. package/mcp-server/dist/tools/youtube/download.js +30 -0
  484. package/mcp-server/dist/tools/youtube/download.js.map +1 -0
  485. package/mcp-server/dist/tools/youtube/helpers.d.ts +66 -0
  486. package/mcp-server/dist/tools/youtube/helpers.d.ts.map +1 -0
  487. package/mcp-server/dist/tools/youtube/helpers.js +143 -0
  488. package/mcp-server/dist/tools/youtube/helpers.js.map +1 -0
  489. package/mcp-server/dist/tools/youtube/media.d.ts +43 -0
  490. package/mcp-server/dist/tools/youtube/media.d.ts.map +1 -0
  491. package/mcp-server/dist/tools/youtube/media.js +97 -0
  492. package/mcp-server/dist/tools/youtube/media.js.map +1 -0
  493. package/mcp-server/dist/tools/youtube/metadata.d.ts +33 -0
  494. package/mcp-server/dist/tools/youtube/metadata.d.ts.map +1 -0
  495. package/mcp-server/dist/tools/youtube/metadata.js +106 -0
  496. package/mcp-server/dist/tools/youtube/metadata.js.map +1 -0
  497. package/mcp-server/dist/tools/youtube/navigate.d.ts +30 -0
  498. package/mcp-server/dist/tools/youtube/navigate.d.ts.map +1 -0
  499. package/mcp-server/dist/tools/youtube/navigate.js +71 -0
  500. package/mcp-server/dist/tools/youtube/navigate.js.map +1 -0
  501. package/mcp-server/dist/tools/youtube/playlist.d.ts +29 -0
  502. package/mcp-server/dist/tools/youtube/playlist.d.ts.map +1 -0
  503. package/mcp-server/dist/tools/youtube/playlist.js +128 -0
  504. package/mcp-server/dist/tools/youtube/playlist.js.map +1 -0
  505. package/mcp-server/dist/tools/youtube/schedule.d.ts +31 -0
  506. package/mcp-server/dist/tools/youtube/schedule.d.ts.map +1 -0
  507. package/mcp-server/dist/tools/youtube/schedule.js +147 -0
  508. package/mcp-server/dist/tools/youtube/schedule.js.map +1 -0
  509. package/mcp-server/dist/tools/youtube/source.d.ts +36 -0
  510. package/mcp-server/dist/tools/youtube/source.d.ts.map +1 -0
  511. package/mcp-server/dist/tools/youtube/source.js +85 -0
  512. package/mcp-server/dist/tools/youtube/source.js.map +1 -0
  513. package/mcp-server/dist/tools/youtube/upload.d.ts +53 -0
  514. package/mcp-server/dist/tools/youtube/upload.d.ts.map +1 -0
  515. package/mcp-server/dist/tools/youtube/upload.js +255 -0
  516. package/mcp-server/dist/tools/youtube/upload.js.map +1 -0
  517. package/mcp-server/dist/tools/youtube.d.ts +12 -0
  518. package/mcp-server/dist/tools/youtube.d.ts.map +1 -0
  519. package/mcp-server/dist/tools/youtube.js +38 -0
  520. package/mcp-server/dist/tools/youtube.js.map +1 -0
  521. package/mcp-server/dist/tui.d.ts +3 -0
  522. package/mcp-server/dist/tui.d.ts.map +1 -0
  523. package/mcp-server/dist/tui.js +806 -0
  524. package/mcp-server/dist/tui.js.map +1 -0
  525. package/mcp-server/dist/types.d.ts +77 -0
  526. package/mcp-server/dist/types.d.ts.map +1 -0
  527. package/mcp-server/dist/types.js +2 -0
  528. package/mcp-server/dist/types.js.map +1 -0
  529. package/mcp-server/package-lock.json +4798 -0
  530. package/mcp-server/package.json +55 -38
  531. package/mcp-server/run.mjs +12 -2
  532. package/mcp-server/src/config.ts +48 -3
  533. package/mcp-server/src/http.ts +296 -93
  534. package/mcp-server/src/index.ts +546 -203
  535. package/mcp-server/src/preload-http.ts +19 -18
  536. package/mcp-server/src/preload.ts +19 -18
  537. package/mcp-server/src/scripts/setup-embeddings.ts +9 -0
  538. package/mcp-server/src/services/assistant-judgments.ts +157 -0
  539. package/mcp-server/src/services/browser-assist-gate.ts +383 -0
  540. package/mcp-server/src/services/browser-assist.ts +267 -0
  541. package/mcp-server/src/services/browser-bench.ts +477 -0
  542. package/mcp-server/src/services/browser-judgments.ts +414 -0
  543. package/mcp-server/src/services/browser-risk.ts +484 -0
  544. package/mcp-server/src/services/categories.ts +36 -19
  545. package/mcp-server/src/services/codex-config-heal.ts +403 -0
  546. package/mcp-server/src/services/desktop-assist-gate.ts +251 -0
  547. package/mcp-server/src/services/desktop-assist.ts +308 -0
  548. package/mcp-server/src/services/desktop-bench.ts +372 -0
  549. package/mcp-server/src/services/desktop-client.ts +76 -0
  550. package/mcp-server/src/services/desktop-judgments.ts +218 -0
  551. package/mcp-server/src/services/desktop-risk.ts +743 -0
  552. package/mcp-server/src/services/edit-stale.ts +299 -0
  553. package/mcp-server/src/services/embedding-service.ts +108 -0
  554. package/mcp-server/src/services/embedding-worker.ts +46 -0
  555. package/mcp-server/src/services/fb-regions.ts +221 -0
  556. package/mcp-server/src/services/file-checksums.ts +26 -9
  557. package/mcp-server/src/services/file-excerpt.ts +59 -0
  558. package/mcp-server/src/services/identity.ts +291 -0
  559. package/mcp-server/src/services/local-embeddings.ts +2 -77
  560. package/mcp-server/src/services/memory-client.ts +23 -0
  561. package/mcp-server/src/services/memory-judge-backfill.ts +462 -0
  562. package/mcp-server/src/services/memory-judgments.ts +901 -0
  563. package/mcp-server/src/services/memory-maintenance.ts +360 -0
  564. package/mcp-server/src/services/memory-retrieval.ts +349 -0
  565. package/mcp-server/src/services/memory-schema.ts +280 -0
  566. package/mcp-server/src/services/memory-sessions.ts +87 -0
  567. package/mcp-server/src/services/memory-triage.ts +182 -0
  568. package/mcp-server/src/services/memory-verification.ts +68 -0
  569. package/mcp-server/src/services/memory-writer.ts +125 -0
  570. package/mcp-server/src/services/neural-interface.ts +1217 -73
  571. package/mcp-server/src/services/profiles.ts +428 -0
  572. package/mcp-server/src/services/secret-gate.ts +52 -0
  573. package/mcp-server/src/services/server-instructions.ts +122 -0
  574. package/mcp-server/src/services/sqlite.ts +1456 -823
  575. package/mcp-server/src/services/typesafe-bench.ts +247 -0
  576. package/mcp-server/src/services/typesafe-config.ts +790 -0
  577. package/mcp-server/src/services/typesafe.ts +390 -0
  578. package/mcp-server/src/services/vector-cache.ts +255 -0
  579. package/mcp-server/src/services/whiteboard-geometry.ts +711 -0
  580. package/mcp-server/src/tools/agents.ts +539 -0
  581. package/mcp-server/src/tools/bluesky.ts +1186 -0
  582. package/mcp-server/src/tools/browser-advanced.ts +15 -6
  583. package/mcp-server/src/tools/browser-batch.ts +178 -0
  584. package/mcp-server/src/tools/browser-cheatsheet.ts +155 -0
  585. package/mcp-server/src/tools/browser-interact.ts +362 -255
  586. package/mcp-server/src/tools/browser-navigate.ts +164 -77
  587. package/mcp-server/src/tools/browser-observe.ts +2222 -1330
  588. package/mcp-server/src/tools/browser.ts +181 -107
  589. package/mcp-server/src/tools/card.ts +7 -5
  590. package/mcp-server/src/tools/choice.ts +103 -0
  591. package/mcp-server/src/tools/computer.ts +190 -0
  592. package/mcp-server/src/tools/discord.ts +10 -23
  593. package/mcp-server/src/tools/fb-groups.ts +387 -0
  594. package/mcp-server/src/tools/forget.ts +1 -1
  595. package/mcp-server/src/tools/git.ts +3 -1
  596. package/mcp-server/src/tools/gsc/experience.ts +440 -0
  597. package/mcp-server/src/tools/gsc/helpers.ts +288 -0
  598. package/mcp-server/src/tools/gsc/indexing.ts +495 -0
  599. package/mcp-server/src/tools/gsc/inspect.ts +391 -0
  600. package/mcp-server/src/tools/gsc/navigate.ts +236 -0
  601. package/mcp-server/src/tools/gsc/performance.ts +352 -0
  602. package/mcp-server/src/tools/gsc/settings.ts +564 -0
  603. package/mcp-server/src/tools/gsc.ts +95 -0
  604. package/mcp-server/src/tools/image.ts +3 -1
  605. package/mcp-server/src/tools/leonardo-browser-tools.ts +47 -37
  606. package/mcp-server/src/tools/leonardo.ts +7 -5
  607. package/mcp-server/src/tools/loop.ts +118 -25
  608. package/mcp-server/src/tools/memories.ts +65 -5
  609. package/mcp-server/src/tools/morelogin.ts +81 -0
  610. package/mcp-server/src/tools/profile.ts +122 -0
  611. package/mcp-server/src/tools/recall.ts +105 -261
  612. package/mcp-server/src/tools/reflect.ts +171 -145
  613. package/mcp-server/src/tools/remember.ts +223 -115
  614. package/mcp-server/src/tools/style-guide-tools.ts +250 -0
  615. package/mcp-server/src/tools/style-guide.ts +16 -0
  616. package/mcp-server/src/tools/sync.ts +66 -9
  617. package/mcp-server/src/tools/tictactoe.ts +3 -1
  618. package/mcp-server/src/tools/utils.ts +22 -0
  619. package/mcp-server/src/tools/whiteboard-format.ts +394 -0
  620. package/mcp-server/src/tools/whiteboard-tools.ts +191 -183
  621. package/mcp-server/src/tools/whiteboard.ts +7 -5
  622. package/mcp-server/src/tools/youtube/analytics.ts +106 -0
  623. package/mcp-server/src/tools/youtube/dedup.ts +57 -0
  624. package/mcp-server/src/tools/youtube/download.ts +41 -0
  625. package/mcp-server/src/tools/youtube/helpers.ts +183 -0
  626. package/mcp-server/src/tools/youtube/media.ts +109 -0
  627. package/mcp-server/src/tools/youtube/metadata.ts +112 -0
  628. package/mcp-server/src/tools/youtube/navigate.ts +81 -0
  629. package/mcp-server/src/tools/youtube/playlist.ts +134 -0
  630. package/mcp-server/src/tools/youtube/schedule.ts +158 -0
  631. package/mcp-server/src/tools/youtube/source.ts +101 -0
  632. package/mcp-server/src/tools/youtube/upload.ts +296 -0
  633. package/mcp-server/src/tools/youtube.ts +47 -0
  634. package/mcp-server/src/tui.ts +8 -9
  635. package/neural-interface/games/TicTacToe/1.svg +1 -0
  636. package/neural-interface/games/TicTacToe/2.svg +1 -0
  637. package/neural-interface/games/TicTacToe/3.svg +1 -0
  638. package/neural-interface/games/TicTacToe/4.svg +1 -0
  639. package/neural-interface/games/TicTacToe/5.svg +1 -0
  640. package/neural-interface/games/TicTacToe/6.svg +1 -0
  641. package/neural-interface/games/TicTacToe/7.svg +1 -0
  642. package/neural-interface/games/TicTacToe/8.svg +1 -0
  643. package/neural-interface/games/TicTacToe/9.svg +1 -0
  644. package/neural-interface/games/TicTacToe/Board.svg +1 -0
  645. package/neural-interface/games/TicTacToe/Circle.svg +1 -0
  646. package/neural-interface/games/TicTacToe/Cross.svg +1 -0
  647. package/neural-interface/i18n/en.json +6983 -0
  648. package/neural-interface/i18n/pt-BR.json +6987 -0
  649. package/neural-interface/lib/assistant-api.js +1005 -0
  650. package/neural-interface/lib/assistant-attachments.js +121 -0
  651. package/neural-interface/lib/assistant-brains/claude.js +207 -0
  652. package/neural-interface/lib/assistant-brains/codex-browser-policy-hook.mjs +43 -0
  653. package/neural-interface/lib/assistant-brains/codex-route-gate-hook.mjs +56 -0
  654. package/neural-interface/lib/assistant-brains/codex.js +213 -0
  655. package/neural-interface/lib/assistant-brains/opencode-browser-policy.js +47 -0
  656. package/neural-interface/lib/assistant-brains/opencode-route-gate.js +62 -0
  657. package/neural-interface/lib/assistant-brains/opencode.js +320 -0
  658. package/neural-interface/lib/assistant-budget.js +318 -0
  659. package/neural-interface/lib/assistant-catalog.js +731 -0
  660. package/neural-interface/lib/assistant-clarify.js +664 -0
  661. package/neural-interface/lib/assistant-config.js +232 -0
  662. package/neural-interface/lib/assistant-dispatch.js +2744 -0
  663. package/neural-interface/lib/assistant-envelope.js +579 -0
  664. package/neural-interface/lib/assistant-evidence.js +236 -0
  665. package/neural-interface/lib/assistant-hidden-models.js +272 -0
  666. package/neural-interface/lib/assistant-jev.js +117 -0
  667. package/neural-interface/lib/assistant-judgments.js +111 -0
  668. package/neural-interface/lib/assistant-media.js +335 -0
  669. package/neural-interface/lib/assistant-memory.js +398 -0
  670. package/neural-interface/lib/assistant-persona.js +444 -0
  671. package/neural-interface/lib/assistant-plan-permissions.js +448 -0
  672. package/neural-interface/lib/assistant-playbooks/design.md +87 -0
  673. package/neural-interface/lib/assistant-playbooks.js +72 -0
  674. package/neural-interface/lib/assistant-pricing.js +159 -0
  675. package/neural-interface/lib/assistant-route-gate-codex.js +163 -0
  676. package/neural-interface/lib/assistant-route-gate.js +212 -0
  677. package/neural-interface/lib/assistant-router.js +1324 -0
  678. package/neural-interface/lib/assistant-runtime.js +3337 -0
  679. package/neural-interface/lib/assistant-task-prompt.js +472 -0
  680. package/neural-interface/lib/assistant-usage.js +1688 -0
  681. package/neural-interface/lib/augmented-path.js +50 -0
  682. package/neural-interface/lib/automation-storage-migration.js +54 -0
  683. package/neural-interface/lib/backup-service.js +851 -0
  684. package/neural-interface/lib/backup-zip-reader.js +262 -0
  685. package/neural-interface/lib/browser-assist-api.js +41 -0
  686. package/neural-interface/lib/browser-console.js +176 -0
  687. package/neural-interface/lib/browser-execution.js +241 -0
  688. package/neural-interface/lib/browser-extraction.js +191 -0
  689. package/neural-interface/lib/browser-screenshot.js +399 -0
  690. package/neural-interface/lib/browser-semantic-context.js +639 -0
  691. package/neural-interface/lib/browser-snapshots.js +192 -0
  692. package/neural-interface/lib/browser-tab-recovery.js +119 -0
  693. package/neural-interface/lib/browser-targets.js +70 -0
  694. package/neural-interface/lib/browser-tool-policy.js +926 -0
  695. package/neural-interface/lib/claude-accounts.js +230 -0
  696. package/neural-interface/lib/claude-agent-bridge.js +3296 -0
  697. package/neural-interface/lib/claude-bypass-policy.js +99 -0
  698. package/neural-interface/lib/claude-history.js +429 -0
  699. package/neural-interface/lib/claude-model-catalog.js +483 -0
  700. package/neural-interface/lib/claude-panel-requests.js +165 -0
  701. package/neural-interface/lib/claude-panel-session.js +272 -0
  702. package/neural-interface/lib/claude-permission-rules.js +153 -0
  703. package/neural-interface/lib/claude-session-cleanup.js +71 -0
  704. package/neural-interface/lib/claude-session-list.js +82 -0
  705. package/neural-interface/lib/claude-session-ops.js +140 -0
  706. package/neural-interface/lib/claude-settings-view.js +51 -0
  707. package/neural-interface/lib/claude-skin-connect.js +79 -0
  708. package/neural-interface/lib/claude-stall-policy.js +7 -0
  709. package/neural-interface/lib/claude-temporary.js +435 -0
  710. package/neural-interface/lib/cli-update-planner.js +312 -0
  711. package/neural-interface/lib/codex-capabilities.d.ts +41 -0
  712. package/neural-interface/lib/codex-capabilities.js +148 -0
  713. package/neural-interface/lib/codex-collaboration-mode.js +17 -0
  714. package/neural-interface/lib/codex-compat-rpc.js +241 -0
  715. package/neural-interface/lib/codex-mcp-approvals.js +56 -0
  716. package/neural-interface/lib/codex-mcp-apps.js +47 -0
  717. package/neural-interface/lib/codex-model-context.js +202 -0
  718. package/neural-interface/lib/codex-plan-store.js +71 -0
  719. package/neural-interface/lib/codex-request-journal.js +242 -0
  720. package/neural-interface/lib/codex-runtime-path.js +213 -0
  721. package/neural-interface/lib/codex-session-catalog.js +231 -0
  722. package/neural-interface/lib/codex-session-settings.js +46 -0
  723. package/neural-interface/lib/codex-sidepanel-permissions.js +110 -0
  724. package/neural-interface/lib/codex-thread-history.js +165 -0
  725. package/neural-interface/lib/codex-user-verification.js +34 -0
  726. package/neural-interface/lib/codex-writer-lifecycle.js +152 -0
  727. package/neural-interface/lib/confined-fs.js +256 -0
  728. package/neural-interface/lib/db.js +922 -0
  729. package/neural-interface/lib/design-md.js +9 -0
  730. package/neural-interface/lib/desktop/api.js +92 -0
  731. package/neural-interface/lib/desktop/audit.js +62 -0
  732. package/neural-interface/lib/desktop/build.js +423 -0
  733. package/neural-interface/lib/desktop/config.js +606 -0
  734. package/neural-interface/lib/desktop/coords.js +76 -0
  735. package/neural-interface/lib/desktop/fake-helper.js +1192 -0
  736. package/neural-interface/lib/desktop/frames.js +77 -0
  737. package/neural-interface/lib/desktop/grants.js +84 -0
  738. package/neural-interface/lib/desktop/guards.js +170 -0
  739. package/neural-interface/lib/desktop/helper/SynabunDesktop.swift +3056 -0
  740. package/neural-interface/lib/desktop/lease.js +51 -0
  741. package/neural-interface/lib/desktop/manager.js +545 -0
  742. package/neural-interface/lib/desktop/press-contexts.js +103 -0
  743. package/neural-interface/lib/desktop/protocol.js +495 -0
  744. package/neural-interface/lib/desktop/service.js +1202 -0
  745. package/neural-interface/lib/effort-levels.js +148 -0
  746. package/neural-interface/lib/event-loop-monitor.js +67 -0
  747. package/neural-interface/lib/exec-loop-command.js +209 -0
  748. package/neural-interface/lib/folder-picker.ps1 +77 -0
  749. package/neural-interface/lib/http-guards.js +287 -0
  750. package/neural-interface/lib/jsonl-lines.js +94 -0
  751. package/neural-interface/lib/loop-logger.js +127 -0
  752. package/neural-interface/lib/mcp-client-config.js +133 -0
  753. package/neural-interface/lib/mcp-installer.js +487 -0
  754. package/neural-interface/lib/mcp-profile-refresh-coordinator.js +78 -0
  755. package/neural-interface/lib/mcp-profile-servers.js +120 -0
  756. package/neural-interface/lib/memo-cache.js +84 -0
  757. package/neural-interface/lib/memory-api.js +758 -0
  758. package/neural-interface/lib/memory-map-api.js +1150 -0
  759. package/neural-interface/lib/memory-map-layout.js +872 -0
  760. package/neural-interface/lib/memory-map-worker.js +165 -0
  761. package/neural-interface/lib/memory-session-capture.js +81 -0
  762. package/neural-interface/lib/memory-timers.js +62 -0
  763. package/neural-interface/lib/morelogin.js +301 -0
  764. package/neural-interface/lib/native-binary-runtime.js +650 -0
  765. package/neural-interface/lib/native-loop-goal.js +33 -0
  766. package/neural-interface/lib/native-loop-providers.js +1016 -0
  767. package/neural-interface/lib/native-loop-runtime.js +747 -0
  768. package/neural-interface/lib/opencode-hidden-models.js +113 -0
  769. package/neural-interface/lib/opencode-history.js +253 -0
  770. package/neural-interface/lib/opencode-runtime-config.js +58 -0
  771. package/neural-interface/lib/opencode-v2-client.js +486 -0
  772. package/neural-interface/lib/opencode-v2-ws-requests.js +955 -0
  773. package/neural-interface/lib/path-confine.js +213 -0
  774. package/neural-interface/lib/plugin-installer.js +264 -0
  775. package/neural-interface/lib/project-storage.js +766 -0
  776. package/neural-interface/lib/pty-host/core.js +603 -0
  777. package/neural-interface/lib/pty-host/flow.js +43 -0
  778. package/neural-interface/lib/pty-host/host.js +47 -0
  779. package/neural-interface/lib/pty-host/manager.js +360 -0
  780. package/neural-interface/lib/pty-host/protocol.js +81 -0
  781. package/neural-interface/lib/pty-host/snapshot.js +133 -0
  782. package/neural-interface/lib/remote-policy.js +582 -0
  783. package/neural-interface/lib/rulesets/api.js +175 -0
  784. package/neural-interface/lib/rulesets/fs-safe.js +245 -0
  785. package/neural-interface/lib/rulesets/installer.js +1453 -0
  786. package/neural-interface/lib/rulesets/json-edit.js +186 -0
  787. package/neural-interface/lib/rulesets/lock.js +170 -0
  788. package/neural-interface/lib/rulesets/managed-text.js +314 -0
  789. package/neural-interface/lib/rulesets/normalise.js +24 -0
  790. package/neural-interface/lib/rulesets/render.js +84 -0
  791. package/neural-interface/lib/schedule-launch-intent.js +40 -0
  792. package/neural-interface/lib/session-cache-builder.js +524 -0
  793. package/neural-interface/lib/session-chunker.js +344 -0
  794. package/neural-interface/lib/session-indexer.js +327 -0
  795. package/neural-interface/lib/session-title-generator.js +227 -0
  796. package/neural-interface/lib/skill-prompt-bridge.js +11 -0
  797. package/neural-interface/lib/style-guide/api.js +375 -0
  798. package/neural-interface/lib/style-guide/color.js +352 -0
  799. package/neural-interface/lib/style-guide/export-css.js +99 -0
  800. package/neural-interface/lib/style-guide/export-dtcg.js +209 -0
  801. package/neural-interface/lib/style-guide/export-tailwind.js +102 -0
  802. package/neural-interface/lib/style-guide/fonts.json +227 -0
  803. package/neural-interface/lib/style-guide/import.js +1268 -0
  804. package/neural-interface/lib/style-guide/pointers.js +103 -0
  805. package/neural-interface/lib/style-guide/presets.js +232 -0
  806. package/neural-interface/lib/style-guide/render-design-md.js +319 -0
  807. package/neural-interface/lib/style-guide/schema.js +848 -0
  808. package/neural-interface/lib/style-guide/security.js +43 -0
  809. package/neural-interface/lib/style-guide/store.js +491 -0
  810. package/neural-interface/lib/style-guide/summary.js +106 -0
  811. package/neural-interface/lib/style-guide/tokens.js +170 -0
  812. package/neural-interface/lib/synabun-update-plan.js +305 -0
  813. package/neural-interface/lib/synabun-version.js +179 -0
  814. package/neural-interface/lib/system-restore.js +99 -0
  815. package/neural-interface/lib/terminal-env.js +86 -0
  816. package/neural-interface/lib/ui-blob-store.js +106 -0
  817. package/neural-interface/lib/vector-cache.js +2 -0
  818. package/neural-interface/lib/virtual-socket.js +60 -0
  819. package/neural-interface/lib/whatsapp/api.js +146 -0
  820. package/neural-interface/lib/whatsapp/auth-store.js +389 -0
  821. package/neural-interface/lib/whatsapp/baileys-adapter.js +457 -0
  822. package/neural-interface/lib/whatsapp/brain.js +128 -0
  823. package/neural-interface/lib/whatsapp/bridge.js +1499 -0
  824. package/neural-interface/lib/whatsapp/cards.js +519 -0
  825. package/neural-interface/lib/whatsapp/commands.js +92 -0
  826. package/neural-interface/lib/whatsapp/config.js +291 -0
  827. package/neural-interface/lib/whatsapp/connector/entry.mjs +25 -0
  828. package/neural-interface/lib/whatsapp/connector/manifest.json +6 -0
  829. package/neural-interface/lib/whatsapp/connector/npm-shrinkwrap.json +717 -0
  830. package/neural-interface/lib/whatsapp/connector/package.json +8 -0
  831. package/neural-interface/lib/whatsapp/fake-baileys.js +444 -0
  832. package/neural-interface/lib/whatsapp/format.js +552 -0
  833. package/neural-interface/lib/whatsapp/host-core.js +1225 -0
  834. package/neural-interface/lib/whatsapp/host.js +119 -0
  835. package/neural-interface/lib/whatsapp/identity.js +288 -0
  836. package/neural-interface/lib/whatsapp/inbound.js +149 -0
  837. package/neural-interface/lib/whatsapp/installer.js +562 -0
  838. package/neural-interface/lib/whatsapp/manager.js +638 -0
  839. package/neural-interface/lib/whatsapp/paths.js +81 -0
  840. package/neural-interface/lib/whatsapp/protocol.js +327 -0
  841. package/neural-interface/lib/whatsapp/qr-svg.js +57 -0
  842. package/neural-interface/lib/whatsapp/redact.js +130 -0
  843. package/neural-interface/lib/whatsapp/service.js +1305 -0
  844. package/neural-interface/lib/whatsapp/vendor/qrcodegen.js +848 -0
  845. package/neural-interface/package-lock.json +4682 -0
  846. package/neural-interface/package.json +58 -27
  847. package/neural-interface/public/apple-touch-icon.png +0 -0
  848. package/neural-interface/public/category-logos/synabun.png +0 -0
  849. package/neural-interface/public/claude.svg +1 -0
  850. package/neural-interface/public/favicon-16x16.png +0 -0
  851. package/neural-interface/public/favicon-32x32.png +0 -0
  852. package/neural-interface/public/icon-192.png +0 -0
  853. package/neural-interface/public/icon-512.png +0 -0
  854. package/neural-interface/public/index.html +32 -182
  855. package/neural-interface/public/index2d.html +2 -2
  856. package/neural-interface/public/logo-synabun.png +0 -0
  857. package/neural-interface/public/logoHD.png +0 -0
  858. package/neural-interface/public/mcp.svg +4 -0
  859. package/neural-interface/public/offline.html +335 -299
  860. package/neural-interface/public/onboarding.html +692 -155
  861. package/neural-interface/public/opencode-logo-dark-square.svg +1 -0
  862. package/neural-interface/public/shared/agent-runtime-options.js +596 -0
  863. package/neural-interface/public/shared/api.js +339 -28
  864. package/neural-interface/public/shared/assistant/asst-accounts.js +318 -0
  865. package/neural-interface/public/shared/assistant/asst-agents-dock.js +296 -0
  866. package/neural-interface/public/shared/assistant/asst-api.js +270 -0
  867. package/neural-interface/public/shared/assistant/asst-brain-picker.js +744 -0
  868. package/neural-interface/public/shared/assistant/asst-budget.js +333 -0
  869. package/neural-interface/public/shared/assistant/asst-composer.js +656 -0
  870. package/neural-interface/public/shared/assistant/asst-computer.js +803 -0
  871. package/neural-interface/public/shared/assistant/asst-control.js +1018 -0
  872. package/neural-interface/public/shared/assistant/asst-hosts.js +262 -0
  873. package/neural-interface/public/shared/assistant/asst-mascot.js +311 -0
  874. package/neural-interface/public/shared/assistant/asst-menu.js +375 -0
  875. package/neural-interface/public/shared/assistant/asst-panel.js +1845 -0
  876. package/neural-interface/public/shared/assistant/asst-rack-styles.js +589 -0
  877. package/neural-interface/public/shared/assistant/asst-render.js +3296 -0
  878. package/neural-interface/public/shared/assistant/asst-route.js +1653 -0
  879. package/neural-interface/public/shared/assistant/asst-roving.js +75 -0
  880. package/neural-interface/public/shared/assistant/asst-sanitize.js +117 -0
  881. package/neural-interface/public/shared/assistant/asst-sidepanel-styles.js +488 -0
  882. package/neural-interface/public/shared/assistant/asst-sidepanel.js +675 -0
  883. package/neural-interface/public/shared/assistant/asst-slash.js +258 -0
  884. package/neural-interface/public/shared/assistant/asst-state.js +727 -0
  885. package/neural-interface/public/shared/assistant/asst-styles.js +1684 -0
  886. package/neural-interface/public/shared/assistant/asst-tool-kinds.js +1017 -0
  887. package/neural-interface/public/shared/assistant/asst-usage.js +444 -0
  888. package/neural-interface/public/shared/assistant/asst-ws.js +175 -0
  889. package/neural-interface/public/shared/automation-context.js +75 -0
  890. package/neural-interface/public/shared/blob-store.js +99 -0
  891. package/neural-interface/public/shared/cdx/cdx-capabilities.js +118 -0
  892. package/neural-interface/public/shared/cdx/cdx-cards.js +37 -0
  893. package/neural-interface/public/shared/cdx/cdx-commands.js +171 -0
  894. package/neural-interface/public/shared/cdx/cdx-context-menu.js +402 -0
  895. package/neural-interface/public/shared/cdx/cdx-context-model.js +220 -0
  896. package/neural-interface/public/shared/cdx/cdx-features.js +408 -0
  897. package/neural-interface/public/shared/cdx/cdx-icons.js +89 -0
  898. package/neural-interface/public/shared/cdx/cdx-mcp-app-host.js +414 -0
  899. package/neural-interface/public/shared/cdx/cdx-mcp-app-proxy.html +6 -0
  900. package/neural-interface/public/shared/cdx/cdx-mcp-app-proxy.js +56 -0
  901. package/neural-interface/public/shared/cdx/cdx-mcp-app.js +178 -0
  902. package/neural-interface/public/shared/cdx/cdx-model-catalog.js +120 -0
  903. package/neural-interface/public/shared/cdx/cdx-output-model.js +261 -0
  904. package/neural-interface/public/shared/cdx/cdx-output.js +382 -0
  905. package/neural-interface/public/shared/cdx/cdx-panel.js +1372 -0
  906. package/neural-interface/public/shared/cdx/cdx-plan.js +266 -0
  907. package/neural-interface/public/shared/cdx/cdx-protocol.js +682 -0
  908. package/neural-interface/public/shared/cdx/cdx-render.js +3381 -0
  909. package/neural-interface/public/shared/cdx/cdx-requests.js +1136 -0
  910. package/neural-interface/public/shared/cdx/cdx-styles.js +3251 -0
  911. package/neural-interface/public/shared/cdx/cdx-tabs.js +7992 -0
  912. package/neural-interface/public/shared/cdx/cdx-ws.js +363 -0
  913. package/neural-interface/public/shared/cli-status.js +169 -0
  914. package/neural-interface/public/shared/constants.js +22 -5
  915. package/neural-interface/public/shared/cp/cp-agents.js +186 -0
  916. package/neural-interface/public/shared/cp/cp-bash.js +252 -0
  917. package/neural-interface/public/shared/cp/cp-compose.js +159 -0
  918. package/neural-interface/public/shared/cp/cp-context-menu.js +309 -0
  919. package/neural-interface/public/shared/cp/cp-context-model.js +157 -0
  920. package/neural-interface/public/shared/cp/cp-context-window.js +80 -0
  921. package/neural-interface/public/shared/cp/cp-ctx.js +11 -0
  922. package/neural-interface/public/shared/cp/cp-diff.js +255 -0
  923. package/neural-interface/public/shared/cp/cp-event-rows.js +637 -0
  924. package/neural-interface/public/shared/cp/cp-events.js +846 -0
  925. package/neural-interface/public/shared/cp/cp-markdown.js +104 -0
  926. package/neural-interface/public/shared/cp/cp-permission-model.js +905 -0
  927. package/neural-interface/public/shared/cp/cp-permissions.js +626 -0
  928. package/neural-interface/public/shared/cp/cp-rehydrate.js +113 -0
  929. package/neural-interface/public/shared/cp/cp-restore.js +224 -0
  930. package/neural-interface/public/shared/cp/cp-session-model.js +338 -0
  931. package/neural-interface/public/shared/cp/cp-session-settings.js +254 -0
  932. package/neural-interface/public/shared/cp/cp-sessions.js +227 -0
  933. package/neural-interface/public/shared/cp/cp-statusline.js +153 -0
  934. package/neural-interface/public/shared/cp/cp-styles.js +349 -0
  935. package/neural-interface/public/shared/cp/cp-tasks-model.js +282 -0
  936. package/neural-interface/public/shared/cp/cp-tasks.js +107 -0
  937. package/neural-interface/public/shared/cp/cp-temporary-view.js +56 -0
  938. package/neural-interface/public/shared/cp/cp-temporary.js +231 -0
  939. package/neural-interface/public/shared/cp/cp-tool-cards.js +326 -0
  940. package/neural-interface/public/shared/cp/cp-tool-results.js +618 -0
  941. package/neural-interface/public/shared/cp/cp-usage-model.js +124 -0
  942. package/neural-interface/public/shared/graph-activity.js +68 -0
  943. package/neural-interface/public/shared/html-shell.js +107 -12
  944. package/neural-interface/public/shared/i18n.js +132 -34
  945. package/neural-interface/public/shared/node-positions-store.js +44 -0
  946. package/neural-interface/public/shared/ocp-hidden-models.js +116 -0
  947. package/neural-interface/public/shared/ocp-v2/ocp-v2-approvals.js +355 -0
  948. package/neural-interface/public/shared/ocp-v2/ocp-v2-autoaccept-button.js +35 -0
  949. package/neural-interface/public/shared/ocp-v2/ocp-v2-automation-ownership.js +72 -0
  950. package/neural-interface/public/shared/ocp-v2/ocp-v2-binding.js +280 -0
  951. package/neural-interface/public/shared/ocp-v2/ocp-v2-caps.js +67 -0
  952. package/neural-interface/public/shared/ocp-v2/ocp-v2-changes-logic.js +179 -0
  953. package/neural-interface/public/shared/ocp-v2/ocp-v2-changes.js +164 -0
  954. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +725 -0
  955. package/neural-interface/public/shared/ocp-v2/ocp-v2-compact-button.js +75 -0
  956. package/neural-interface/public/shared/ocp-v2/ocp-v2-composer-logic.js +1204 -0
  957. package/neural-interface/public/shared/ocp-v2/ocp-v2-confirm-logic.js +117 -0
  958. package/neural-interface/public/shared/ocp-v2/ocp-v2-confirm.js +59 -0
  959. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-gauge.js +252 -0
  960. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-menu.js +478 -0
  961. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-model.js +179 -0
  962. package/neural-interface/public/shared/ocp-v2/ocp-v2-events.js +233 -0
  963. package/neural-interface/public/shared/ocp-v2/ocp-v2-icons.js +30 -0
  964. package/neural-interface/public/shared/ocp-v2/ocp-v2-manager.js +1065 -0
  965. package/neural-interface/public/shared/ocp-v2/ocp-v2-mention-hints.js +182 -0
  966. package/neural-interface/public/shared/ocp-v2/ocp-v2-modelpicker.js +655 -0
  967. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +2636 -0
  968. package/neural-interface/public/shared/ocp-v2/ocp-v2-plan.js +601 -0
  969. package/neural-interface/public/shared/ocp-v2/ocp-v2-profile-inheritance.js +78 -0
  970. package/neural-interface/public/shared/ocp-v2/ocp-v2-projectbar.js +649 -0
  971. package/neural-interface/public/shared/ocp-v2/ocp-v2-providers.js +35 -0
  972. package/neural-interface/public/shared/ocp-v2/ocp-v2-rehydrate.js +357 -0
  973. package/neural-interface/public/shared/ocp-v2/ocp-v2-render-logic.js +189 -0
  974. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +1869 -0
  975. package/neural-interface/public/shared/ocp-v2/ocp-v2-select.js +42 -0
  976. package/neural-interface/public/shared/ocp-v2/ocp-v2-send-logic.js +117 -0
  977. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +1714 -0
  978. package/neural-interface/public/shared/ocp-v2/ocp-v2-session-actions.js +468 -0
  979. package/neural-interface/public/shared/ocp-v2/ocp-v2-session-widgets.js +63 -0
  980. package/neural-interface/public/shared/ocp-v2/ocp-v2-sessions-logic.js +366 -0
  981. package/neural-interface/public/shared/ocp-v2/ocp-v2-settings-link.js +19 -0
  982. package/neural-interface/public/shared/ocp-v2/ocp-v2-slash-hints.js +343 -0
  983. package/neural-interface/public/shared/ocp-v2/ocp-v2-state.js +746 -0
  984. package/neural-interface/public/shared/ocp-v2/ocp-v2-status-logic.js +347 -0
  985. package/neural-interface/public/shared/ocp-v2/ocp-v2-status.js +338 -0
  986. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +3543 -0
  987. package/neural-interface/public/shared/ocp-v2/ocp-v2-subagents-logic.js +482 -0
  988. package/neural-interface/public/shared/ocp-v2/ocp-v2-tools-logic.js +351 -0
  989. package/neural-interface/public/shared/ocp-v2/ocp-v2-tools.js +168 -0
  990. package/neural-interface/public/shared/ocp-v2/ocp-v2-variantpicker.js +315 -0
  991. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +545 -0
  992. package/neural-interface/public/shared/provider-icons.js +30 -0
  993. package/neural-interface/public/shared/session-title.js +100 -0
  994. package/neural-interface/public/shared/settings/settings-ia.js +389 -0
  995. package/neural-interface/public/shared/settings/settings-kit.js +234 -0
  996. package/neural-interface/public/shared/settings/settings-search.js +463 -0
  997. package/neural-interface/public/shared/settings/settings-shell.js +570 -0
  998. package/neural-interface/public/shared/state.js +5 -0
  999. package/neural-interface/public/shared/storage.js +50 -7
  1000. package/neural-interface/public/shared/styleguide/sg-agents.js +42 -0
  1001. package/neural-interface/public/shared/styleguide/sg-brand.js +4 -0
  1002. package/neural-interface/public/shared/styleguide/sg-color-client.js +388 -0
  1003. package/neural-interface/public/shared/styleguide/sg-colors.js +148 -0
  1004. package/neural-interface/public/shared/styleguide/sg-components.js +35 -0
  1005. package/neural-interface/public/shared/styleguide/sg-history.js +41 -0
  1006. package/neural-interface/public/shared/styleguide/sg-icons-logo.js +38 -0
  1007. package/neural-interface/public/shared/styleguide/sg-imagery.js +4 -0
  1008. package/neural-interface/public/shared/styleguide/sg-import-export.js +47 -0
  1009. package/neural-interface/public/shared/styleguide/sg-kit.js +281 -0
  1010. package/neural-interface/public/shared/styleguide/sg-labels.js +318 -0
  1011. package/neural-interface/public/shared/styleguide/sg-layout.js +4 -0
  1012. package/neural-interface/public/shared/styleguide/sg-motion.js +5 -0
  1013. package/neural-interface/public/shared/styleguide/sg-panel.js +229 -0
  1014. package/neural-interface/public/shared/styleguide/sg-preview.js +138 -0
  1015. package/neural-interface/public/shared/styleguide/sg-rules.js +9 -0
  1016. package/neural-interface/public/shared/styleguide/sg-shape.js +24 -0
  1017. package/neural-interface/public/shared/styleguide/sg-state.js +132 -0
  1018. package/neural-interface/public/shared/styleguide/sg-typography.js +124 -0
  1019. package/neural-interface/public/shared/styles.css +7883 -1155
  1020. package/neural-interface/public/shared/synabun-mascot.js +1625 -0
  1021. package/neural-interface/public/shared/synabun-ticker.js +376 -0
  1022. package/neural-interface/public/shared/term-renderer.js +343 -0
  1023. package/neural-interface/public/shared/ui-assistant-panel.js +10 -0
  1024. package/neural-interface/public/shared/ui-assistant.js +77 -0
  1025. package/neural-interface/public/shared/ui-automation-studio.js +5105 -3573
  1026. package/neural-interface/public/shared/ui-claude-panel.js +11685 -6333
  1027. package/neural-interface/public/shared/ui-codex-panel.js +12 -0
  1028. package/neural-interface/public/shared/ui-cost-widget.js +55 -34
  1029. package/neural-interface/public/shared/ui-detail.js +77 -14
  1030. package/neural-interface/public/shared/ui-explorer.js +360 -181
  1031. package/neural-interface/public/shared/ui-file-explorer.js +485 -107
  1032. package/neural-interface/public/shared/ui-help.js +2 -2
  1033. package/neural-interface/public/shared/ui-image-gallery.js +45 -21
  1034. package/neural-interface/public/shared/ui-judgments.js +1346 -0
  1035. package/neural-interface/public/shared/ui-keybinds.js +3 -2
  1036. package/neural-interface/public/shared/ui-link.js +105 -51
  1037. package/neural-interface/public/shared/ui-loading.js +4 -4
  1038. package/neural-interface/public/shared/ui-memory-maintenance.js +111 -0
  1039. package/neural-interface/public/shared/ui-menubar.js +89 -4
  1040. package/neural-interface/public/shared/ui-native-loop-router-state.js +94 -0
  1041. package/neural-interface/public/shared/ui-native-loop-router.js +493 -0
  1042. package/neural-interface/public/shared/ui-native-window-id.js +7 -0
  1043. package/neural-interface/public/shared/ui-navbar.js +357 -222
  1044. package/neural-interface/public/shared/ui-notifications.js +58 -12
  1045. package/neural-interface/public/shared/ui-opencode-panel-v2.js +17 -0
  1046. package/neural-interface/public/shared/ui-panels.js +15 -7
  1047. package/neural-interface/public/shared/ui-resume.js +823 -653
  1048. package/neural-interface/public/shared/ui-schedule-queue.js +172 -0
  1049. package/neural-interface/public/shared/ui-schedules-studio.js +1882 -0
  1050. package/neural-interface/public/shared/ui-scroll-follow.js +194 -0
  1051. package/neural-interface/public/shared/ui-sessions.js +789 -34
  1052. package/neural-interface/public/shared/ui-settings.js +5793 -2190
  1053. package/neural-interface/public/shared/ui-sidepanel-layout.js +109 -0
  1054. package/neural-interface/public/shared/ui-sidepanel-runtime.js +19 -0
  1055. package/neural-interface/public/shared/ui-sidepanel-session-entry.js +40 -0
  1056. package/neural-interface/public/shared/ui-sidepanel-sessions.js +221 -0
  1057. package/neural-interface/public/shared/ui-sidepanel-state.js +131 -0
  1058. package/neural-interface/public/shared/ui-sidepanel-tray.js +547 -0
  1059. package/neural-interface/public/shared/ui-sidepanel-windows.js +387 -0
  1060. package/neural-interface/public/shared/ui-skills.js +18 -32
  1061. package/neural-interface/public/shared/ui-styleguide.js +35 -0
  1062. package/neural-interface/public/shared/ui-sync.js +87 -0
  1063. package/neural-interface/public/shared/ui-terminal.js +7877 -6385
  1064. package/neural-interface/public/shared/ui-tool-updates.js +292 -0
  1065. package/neural-interface/public/shared/ui-tooltip.js +156 -15
  1066. package/neural-interface/public/shared/ui-tutorial-draw.js +119 -683
  1067. package/neural-interface/public/shared/ui-tutorial-layout.js +135 -0
  1068. package/neural-interface/public/shared/ui-tutorial-steps.js +59 -2209
  1069. package/neural-interface/public/shared/ui-tutorial.css +102 -0
  1070. package/neural-interface/public/shared/ui-tutorial.js +557 -1076
  1071. package/neural-interface/public/shared/ui-update.js +420 -68
  1072. package/neural-interface/public/shared/ui-whatsapp.js +941 -0
  1073. package/neural-interface/public/shared/ui-whiteboard.js +1076 -233
  1074. package/neural-interface/public/shared/ui-workspaces.js +2 -1
  1075. package/neural-interface/public/shared/utils.js +58 -0
  1076. package/neural-interface/public/shared/vterm-buffer.js +15 -5
  1077. package/neural-interface/public/shared/whatsapp/wa-phone.js +65 -0
  1078. package/neural-interface/public/shared/whatsapp/wa-view.js +411 -0
  1079. package/neural-interface/public/sidepanel-session.html +18 -0
  1080. package/neural-interface/public/sw.js +8 -2
  1081. package/neural-interface/public/synabun.png +0 -0
  1082. package/neural-interface/public/variant/2d/graph.js +1707 -1635
  1083. package/neural-interface/public/variant/2d/lasso.js +2 -1
  1084. package/neural-interface/public/variant/2d/layout.js +595 -594
  1085. package/neural-interface/public/variant/2d/main.js +556 -521
  1086. package/neural-interface/public/variant/2d/settings-gfx.js +18 -24
  1087. package/neural-interface/public/variant/3d/main.js +516 -500
  1088. package/neural-interface/public/variant/3d/map-controls.js +144 -0
  1089. package/neural-interface/public/variant/3d/map-data.js +251 -0
  1090. package/neural-interface/public/variant/3d/map-labels.js +241 -0
  1091. package/neural-interface/public/variant/3d/map-pick.js +90 -0
  1092. package/neural-interface/public/variant/3d/map-renderer.js +521 -0
  1093. package/neural-interface/public/variant/3d/map-shaders.js +158 -0
  1094. package/neural-interface/public/variant/3d/map-view.js +618 -0
  1095. package/neural-interface/public/vendor/three/manifest.json +3 -0
  1096. package/neural-interface/public/vendor/three/three-0.169.0.min.js +3844 -0
  1097. package/neural-interface/public/vendor/xterm/manifest.json +3 -0
  1098. package/neural-interface/public/vendor/xterm/xterm-6.0.0.css +285 -0
  1099. package/neural-interface/public/vendor/xterm/xterm-6.0.0.min.js +192 -0
  1100. package/neural-interface/scripts/build-vendor.mjs +64 -0
  1101. package/neural-interface/scripts/codex-protocol-fixture.mjs +83 -0
  1102. package/neural-interface/scripts/rebuild-pty.js +37 -23
  1103. package/neural-interface/scripts/vendor-three-entry.js +24 -0
  1104. package/neural-interface/scripts/vendor-xterm-entry.js +8 -0
  1105. package/neural-interface/server.js +33948 -15000
  1106. package/neural-interface/skins/default/skin.css +90 -0
  1107. package/neural-interface/skins/default/skin.json +8 -0
  1108. package/neural-interface/templates/rulesets/README.md +9 -0
  1109. package/neural-interface/templates/rulesets/claude.md +3 -0
  1110. package/neural-interface/templates/rulesets/codex.md +2 -0
  1111. package/neural-interface/templates/rulesets/coexistence.md +6 -0
  1112. package/neural-interface/templates/rulesets/core.md +26 -0
  1113. package/neural-interface/templates/rulesets/cursor.md +2 -0
  1114. package/neural-interface/templates/rulesets/gemini.md +2 -0
  1115. package/neural-interface/templates/rulesets/legacy-hashes.json +227 -0
  1116. package/neural-interface/templates/rulesets/manifest.json +12 -0
  1117. package/neural-interface/templates/rulesets/opencode.md +2 -0
  1118. package/package.json +119 -53
  1119. package/postinstall.js +125 -26
  1120. package/preuninstall.js +187 -0
  1121. package/scripts/audit-claude-hook-usage.mjs +12 -0
  1122. package/scripts/audit-memory-duplicates.mjs +44 -0
  1123. package/scripts/measure-claude-hook-overhead.mjs +77 -0
  1124. package/scripts/recover-user-config.mjs +254 -0
  1125. package/scripts/restore-data-home-from-backup.mjs +239 -0
  1126. package/setup.js +804 -260
  1127. package/skills/leonardo/SKILL.md +19 -10
  1128. package/skills/leonardo/modules/image-prompter.md +18 -12
  1129. package/skills/leonardo/modules/video-prompter.md +19 -13
  1130. package/skills/synabun/SKILL.md +209 -185
  1131. package/skills/synabun/icon.png +0 -0
  1132. package/skills/synabun/modules/audit.md +380 -374
  1133. package/skills/synabun/modules/changelog.md +15 -6
  1134. package/skills/synabun/modules/idea.md +148 -144
  1135. package/skills/synabun/modules/memorize.md +13 -4
  1136. package/skills/synabun/modules/schedule.md +198 -0
  1137. package/skills/synabun/synabunicon.png +0 -0
  1138. package/updater.cmd +31 -0
  1139. package/updater.mjs +382 -0
  1140. package/updater.sh +29 -0
  1141. package/hooks/claude-code/test-user-learning.mjs +0 -531
  1142. package/neural-interface/public/claude-chat.css +0 -878
  1143. package/neural-interface/public/claude-chat.html +0 -92
  1144. package/neural-interface/public/claude-chat.js +0 -1168
  1145. package/neural-interface/public/index-legacy.html +0 -14677
  1146. package/neural-interface/public/index2d-legacy.html +0 -8193
  1147. package/neural-interface/public/shared/html-term-renderer.js +0 -1022
  1148. package/neural-interface/public/variant/3d/background.js +0 -884
  1149. package/neural-interface/public/variant/3d/camera.js +0 -661
  1150. package/neural-interface/public/variant/3d/gfx.js +0 -116
  1151. package/neural-interface/public/variant/3d/graph.js +0 -2399
  1152. package/neural-interface/public/variant/3d/settings-gfx.js +0 -228
  1153. package/neural-interface/templates/CLAUDE-template.md +0 -211
@@ -1,861 +1,1244 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * SynaBun UserPromptSubmit Hook for Claude Code
5
- *
6
- * Fires on every user message. Analyzes the prompt against tiered
7
- * trigger patterns and injects context-aware recall nudges:
8
- *
9
- * TIER 1 (MUST recall) — Past work, decisions, explicit memory references
10
- * TIER 2 (SHOULD recall) — Debugging, architecture, domain-specific knowledge
11
- * TIER 3 (CONSIDER recall) — New features, similarity, broad technical mentions
12
- *
13
- * Conversation recall triggers have highest priority (above all tiers).
14
- *
15
- * Lightweight — reads stdin, analyzes the prompt, outputs additionalContext
16
- * only when a recall-worthy signal is detected.
17
- */
18
-
19
- import { readFileSync, writeFileSync, appendFileSync, existsSync, mkdirSync, renameSync, readdirSync } from 'node:fs';
20
- import { dirname, join } from 'node:path';
21
- import { fileURLToPath } from 'node:url';
22
- import { detectProject } from './shared.mjs';
23
-
24
- const __dirname = dirname(fileURLToPath(import.meta.url));
25
- const DATA_DIR = join(__dirname, '..', '..', 'data');
26
- const HOOK_FEATURES_PATH = join(DATA_DIR, 'hook-features.json');
27
- const PENDING_REMEMBER_DIR = join(DATA_DIR, 'pending-remember');
28
- const LOOP_DIR = join(DATA_DIR, 'loop');
29
-
30
- function getHookFeatures() {
31
- try {
32
- if (!existsSync(HOOK_FEATURES_PATH)) return {};
33
- return JSON.parse(readFileSync(HOOK_FEATURES_PATH, 'utf-8'));
34
- } catch { return {}; }
35
- }
36
-
37
- function readStdin() {
38
- return new Promise((resolve) => {
39
- if (process.stdin.isTTY) return resolve('{}');
40
- let data = '';
41
- process.stdin.setEncoding('utf8');
42
- process.stdin.on('data', (chunk) => { data += chunk; });
43
- process.stdin.on('end', () => resolve(data));
44
- setTimeout(() => resolve(data || '{}'), 2000);
45
- });
46
- }
47
-
48
- // ── Loop helper functions ──────────────────────────────────────
49
-
50
- /**
51
- * Extract formatting/style rules from the task text into a separate block.
52
- * Matches lines containing prohibitions about dashes, emojis, spam, double posting, etc.
53
- */
54
- function extractFormattingRules(task) {
55
- if (!task) return '';
56
- const rules = [];
57
- let hasDashRule = false;
58
- for (const line of task.split('\n')) {
59
- const t = line.trim();
60
- if (!t) continue;
61
- if (/\b(do not|don'?t|never|must not|avoid)\b/i.test(t) &&
62
- /\b(dash|--|—|emoji|emote|emojis|spam|double post|over use)\b/i.test(t)) {
63
- rules.push(t.replace(/^[-*•]\s*/, '').replace(/^\d+\.\s*/, ''));
64
- if (/dash|--/i.test(t)) hasDashRule = true;
65
- }
66
- }
67
- // Strengthen dash rule with explicit variants
68
- if (hasDashRule) {
69
- rules.push('NEVER use double dashes (--), em dashes (\u2014), or en dashes (\u2013) in ANY text you write. Use commas, periods, or semicolons instead.');
70
- }
71
- if (rules.length === 0) return '';
72
- return rules.map(r => `- ${r}`).join('\n');
73
- }
74
-
75
- function buildBrowserNote(state) {
76
- if (!state.usesBrowser) return '';
77
- const lines = [
78
- '',
79
- '=== BROWSER ENFORCEMENT (MANDATORY) ===',
80
- 'This automation REQUIRES the SynaBun internal browser. You MUST:',
81
- ];
82
- // Pin to dedicated browser session if available (multi-automation isolation)
83
- if (state.browserSessionId) {
84
- lines.push(`YOUR BROWSER SESSION ID: ${state.browserSessionId}`);
85
- lines.push('Pass sessionId: "' + state.browserSessionId + '" to ALL browser tool calls (browser_navigate, browser_click, etc.).');
86
- }
87
- lines.push(
88
- '1. Call `browser_navigate` with your target URL to create or reuse a browser session',
89
- '2. Use ONLY SynaBun MCP browser tools: browser_navigate, browser_go_back, browser_go_forward, browser_reload, browser_click, browser_fill, browser_type, browser_hover, browser_select, browser_press, browser_scroll, browser_upload, browser_snapshot, browser_content, browser_screenshot, browser_evaluate, browser_wait, browser_session, browser_extract_tweets, browser_extract_fb_posts, browser_extract_tiktok_videos, browser_extract_tiktok_search, browser_extract_tiktok_studio, browser_extract_tiktok_profile, browser_extract_wa_chats, browser_extract_wa_messages, browser_extract_ig_feed, browser_extract_ig_profile, browser_extract_ig_post, browser_extract_ig_reels, browser_extract_ig_search, browser_extract_li_feed, browser_extract_li_profile, browser_extract_li_post, browser_extract_li_notifications, browser_extract_li_messages, browser_extract_li_search_people, browser_extract_li_network, browser_extract_li_jobs',
90
- '3. NEVER use Playwright plugin tools (mcp__plugin_playwright_*) — they launch a separate browser that the user cannot see',
91
- '4. NEVER use WebFetch or WebSearch tools for tasks that require visual browsing — use the SynaBun browser instead',
92
- '5. If the browser shows a login page, CAPTCHA, or any wall requiring human action: STOP immediately. Report the blocker to the user and WAIT. Do NOT abandon the browser and fall back to web search. The user can interact with the browser panel to resolve it.',
93
- 'A persistent Chrome profile is active with saved logins and cookies. The user can see and interact with the SynaBun browser panel.',
94
- '=== END BROWSER ENFORCEMENT ===',
95
- );
96
- return lines.join('\n');
97
- }
98
-
99
- function buildAutonomyBlock(blockerRule, sessionId) {
100
- const updateCall = sessionId
101
- ? `- After completing this iteration, call \`loop\` with action \`update\`, session_id \`${sessionId}\`, and a brief summary.`
102
- : '- After completing this iteration, call `loop` with action `update` and a brief summary.';
103
- return [
104
- '',
105
- '--- LOOP AUTONOMY MODE ---',
106
- 'IMPORTANT: IGNORE any GREETING DIRECTIVE or recall instructions from SessionStart. Do NOT output a greeting. Do NOT call recall. You are in an autonomous loop \u2014 execute the task below immediately.',
107
- '',
108
- 'Rules for this session:',
109
- '- Execute the task directly. Do NOT ask for confirmation or clarification.',
110
- '- Make reasonable assumptions and proceed. Do not hesitate.',
111
- '- Use all available tools (browser, memory, file system) as needed without asking.',
112
- '- Each iteration should produce concrete output or progress.',
113
- '- If something fails due to a technical issue, try an alternative approach.',
114
- updateCall,
115
- '- The server will automatically advance to the next iteration when you finish.',
116
- blockerRule || '',
117
- '--- END LOOP AUTONOMY ---',
118
- ].filter(Boolean).join('\n');
119
- }
120
-
121
- function buildJournalBlock(state) {
122
- const parts = [];
123
- if (state.progressSummary) {
124
- parts.push(`PROGRESS SO FAR: ${state.progressSummary}`);
125
- }
126
- const journal = Array.isArray(state.journal) ? state.journal : [];
127
- const recent = journal.slice(-3);
128
- if (recent.length > 0) {
129
- parts.push('RECENT ITERATIONS:');
130
- for (const entry of recent) {
131
- parts.push(` Iteration ${entry.iteration}: ${entry.summary}`);
132
- }
133
- }
134
- return parts.length > 0 ? parts.join('\n') : '';
135
- }
136
-
137
- /**
138
- * Find an active loop owned by this session (exact file name match only).
139
- * Does NOT scan other sessions' loop files — cross-session injection is
140
- * the source of loop leaks between concurrent Claude panels/TUI sessions.
141
- * Returns the loop state, or null if this session has no active loop.
142
- */
143
- function findActiveLoop(sessionId) {
144
- try {
145
- const exactPath = join(LOOP_DIR, `${sessionId}.json`);
146
- if (!existsSync(exactPath)) return null;
147
- const candidate = JSON.parse(readFileSync(exactPath, 'utf-8'));
148
- if (!candidate.active) return null;
149
- // Skip loops inactive for >45 minutes (stuck)
150
- const lastAct = new Date(candidate.lastIterationAt || candidate.startedAt || 0).getTime();
151
- if (Date.now() - lastAct > 45 * 60 * 1000) return null;
152
- return candidate;
153
- } catch { return null; }
154
- }
155
-
156
- // ============================================================
157
- // TIER 1 — MUST recall (high confidence: past work, decisions, explicit memory)
158
- // These indicate the user is referencing prior context that memory likely holds.
159
- // Threshold: >= 1 match fires. Nudge: mandatory.
160
- // ============================================================
161
-
162
- const TIER1_TRIGGERS = [
163
- // Explicit past work references
164
- /\b(last time|before we|previously|earlier we|remember when|we (did|had|tried|used|decided|chose))\b/i,
165
- /\b(why (did|do) we|what happened (to|with)|what was the)\b/i,
166
- /\b(history|context) (of|about|for|on)\b/i,
167
-
168
- // Decision recall
169
- /\bshould (we|i) (use|go with|pick|choose|switch|keep|change|stick with)\b/i,
170
- /\bwhat('s| is| was) the (best|right|correct|agreed|chosen) (approach|way|method|pattern)\b/i,
171
- /\bwhat did we (decide|agree|settle) on\b/i,
172
-
173
- // Explicit memory/recall references
174
- /\b(do you remember|check (your )?memory|what do you know about|recall what)\b/i,
175
- /\b(we (already|previously) (fixed|solved|handled|addressed|implemented))\b/i,
176
- ];
177
-
178
- // ============================================================
179
- // TIER 2 — SHOULD recall (medium confidence: debugging, architecture, domains)
180
- // These suggest memory might hold relevant context. Worth checking.
181
- // Threshold: >= 1 match fires. Nudge: strong suggestion.
182
- // ============================================================
183
-
184
- const TIER2_TRIGGERS = [
185
- // Debugging (likely to have past bug context)
186
- /\b(bug|error|broken|crash|not working|doesn't work|keeps? (failing|breaking))\b/i,
187
- /\b(debug|troubleshoot|investigate|diagnose|root cause)\b/i,
188
-
189
- // Architecture & structural changes
190
- /\b(refactor|restructur|migrat|upgrad|deprecat)\b/i,
191
- /\b(architect|redesign|rearchitect)\b/i,
192
-
193
- // Decision-making questions (broader than Tier 1)
194
- /\bwhat('s| is) the (best|right|correct|proper) way to\b/i,
195
- /\bhow (should|do) (we|i) (handle|approach|structure|organize)\b/i,
196
-
197
- // Specific technical domains that accumulate knowledge
198
- /\b(supabase|redis|upstash|sqlite)\b/i,
199
- /\b(auth(entication|orization)?|session handling|jwt|mfa)\b/i,
200
- /\b(cron job|ranking|price aggregat|deal(s)? (system|pipeline))\b/i,
201
- ];
202
-
203
- // ============================================================
204
- // TIER 3 — CONSIDER recall (lower confidence: new features, broad tech)
205
- // These MIGHT benefit from memory but often don't. Requires 2+ matches
206
- // from this tier, OR 1 Tier 3 + 1 Tier 2 to fire. Nudge: soft.
207
- // ============================================================
208
-
209
- const TIER3_TRIGGERS = [
210
- // New features (might conflict with past decisions)
211
- /\b(implement|integrate) (a |the |new )?\w+/i,
212
- /\bnew (feature|component|page|endpoint|hook|service)\b/i,
213
-
214
- // Building on existing patterns
215
- /\bsimilar to (the|what|how)\b/i,
216
- /\bsame (as|way|pattern|approach) (as |we )?\b/i,
217
- /\bconsistent with\b/i,
218
-
219
- // Broad technical domains
220
- /\b(database|cache|caching) (schema|strategy|layer|issue)\b/i,
221
- /\b(api|endpoint) (design|structure|pattern)\b/i,
222
- /\b(deploy|deployment|ci\/cd|pipeline) (strategy|process|config)\b/i,
223
- /\b(config|configuration) (for|of|pattern)\b/i,
224
- ];
225
-
226
- // ============================================================
227
- // CONVERSATION RECALL TRIGGERS (highest priority — above all tiers)
228
- // ============================================================
229
-
230
- const CONVERSATION_RECALL_TRIGGERS = [
231
- /\bremember that (conversation|session|chat|discussion|time)\b/i,
232
- /\bthat (conversation|session|chat) (about|where|when)\b/i,
233
- /\b(days?|weeks?) ago.*(conversation|session|worked on|discussed|implemented|built)/i,
234
- /\bcontinue (where we left off|that session|from last time|from yesterday)\b/i,
235
- /\bwhat did we (talk|discuss|work on|do|build|implement|fix) (last|yesterday|on|the other)/i,
236
- /\bfind that (session|conversation|chat) (where|about|when|from)\b/i,
237
- /\bpick up (where|from) (we|last|that)/i,
238
- /\b(yesterday|last week|other day).*(session|conversation|worked|discussed|implemented)/i,
239
- ];
240
-
241
- // ============================================================
242
- // SKIP PATTERNS — Messages that never need recall
243
- // ============================================================
244
-
245
- const SKIP_PATTERNS = [
246
- // Trivial confirmations
247
- /^(yes|no|ok|sure|thanks|ty|thank you|perfect|great|good|nice|cool|got it|yep|nope|nah)\b/i,
248
- // Continuation commands
249
- /^(do it|go ahead|proceed|continue|keep going|next|done|stop|cancel|abort|nevermind)\b/i,
250
- // Empty or whitespace
251
- /^\s*$/,
252
- // Slash commands (Claude Code handles these)
253
- /^\/\w+/,
254
- // Very short messages (< 8 chars, likely just a word)
255
- /^.{1,7}$/,
256
- // Direct file operations (no memory needed)
257
- /^(read|open|show|cat|look at|check) .+\.\w{1,5}$/i,
258
- // Run commands
259
- /^(run|execute|start|npm|node|git|pnpm|yarn|bun) /i,
260
- ];
261
-
262
- // ============================================================
263
- // NUDGE TEMPLATES
264
- // ============================================================
265
-
266
- const NUDGE = {
267
- pendingRemember: (editCount, files) => {
268
- const fileList = files.length > 0
269
- ? ` Files: ${files.slice(0, 5).join(', ')}${files.length > 5 ? ` (+${files.length - 5} more)` : ''}.`
270
- : '';
271
- const urgency = editCount >= 5
272
- ? `CRITICAL: ${editCount} file edits`
273
- : editCount >= 3
274
- ? `IMPORTANT: ${editCount} file edits`
275
- : `${editCount} file edit${editCount !== 1 ? 's' : ''}`;
276
- return [
277
- `SynaBun TASK BOUNDARY: ${urgency} from your previous work have NOT been stored in memory.`,
278
- `You MUST call \`remember\` for that completed work BEFORE starting this new task.${fileList}`,
279
- `Summarize what was done, why, and how — then proceed with the user's new request.`,
280
- ].join(' ');
281
- },
282
-
283
- conversation: [
284
- `The user is asking about a past conversation. Follow the Conversation Recall Workflow:`,
285
- `1. Calculate exact dates from relative references (e.g., "4 days ago" → compute the date).`,
286
- `2. Call \`recall\` with category \`conversations\` and include the topic + calculated date in your query.`,
287
- `3. Present matching conversations via AskUserQuestion with date, project, and summary for each.`,
288
- `4. After selection, offer recovery options: "Recover entire context and continue", "Continue with compacted summary", or "Other".`,
289
- ].join(' '),
290
-
291
- tier1: [
292
- `You MUST call \`recall\` before responding to this message.`,
293
- `The user is referencing past work, prior decisions, or historical context that your persistent memory likely holds.`,
294
- `Search for: the specific topic mentioned, related past decisions, known issues, or prior implementations.`,
295
- `Do NOT respond without checking memory first.`,
296
- ].join(' '),
297
-
298
- tier2: [
299
- `Before responding, call \`recall\` to check your persistent memory for relevant context.`,
300
- `This topic likely has prior knowledge stored — past bugs, architecture decisions, or domain-specific patterns.`,
301
- `Skip recall only if you already have full context from this session.`,
302
- ].join(' '),
303
-
304
- tier3: [
305
- `Consider calling \`recall\` to check if there's relevant prior context about this topic.`,
306
- `There may be past decisions or patterns worth reviewing before proceeding.`,
307
- ].join(' '),
308
-
309
- nonEnglish: [
310
- `The user's message is in a non-English language. Mentally translate their intent to evaluate if you should call \`recall\`.`,
311
- `Check if they are: referencing past work, asking about prior decisions, debugging an issue, or working in a domain where you have stored knowledge.`,
312
- `If any of those apply, call \`recall\` with an ENGLISH query that captures their intent (SynaBun memories are stored in English).`,
313
- `If it's a trivial or direct command, skip recall.`,
314
- ].join(' '),
315
-
316
- userLearning: [
317
- `SynaBun User Learning: Observe HOW the user works with you and store a behavioral observation.`,
318
- ``,
319
- `RULES:`,
320
- `- Category MUST be \`communication-style\` — never \`conversations\` or anything else`,
321
- `- Content MUST describe HOW the user communicates and works — NOT what was worked on`,
322
- `- This is NOT a session summary. Do NOT describe the task, topic, or outcome.`,
323
- `- AVOID DUPLICATES: If an existing memory already covers the same patterns, use \`reflect\` to UPDATE it instead of creating a new one.`,
324
- ``,
325
- `GOOD example: "User gives multi-part requests in a single message and expects all parts addressed. Provides file paths inline rather than expecting discovery. Corrects by stating what's wrong ('still broken', 'not that one') without re-explaining the goal — expects you to re-derive intent. Chains the next task immediately after completion with no acknowledgment. Prefers options as a short list over long explanations."`,
326
- `BAD example: "User asked about the hook system and we fixed 3 bugs." ← This is a session summary, NOT a behavioral observation.`,
327
- `BAD example: "User uses lowercase and skips punctuation." ← Too shallow. Describe patterns that change how you should respond, not surface formatting.`,
328
- ``,
329
- `Steps:`,
330
- `1. \`recall\` category \`communication-style\` — check existing entries`,
331
- `2. If an existing entry covers similar patterns → \`reflect\` (memory_id=<full UUID>, content=updated observation merging old + new)`,
332
- ` If NO existing entry matches → \`remember\` category \`communication-style\`, project "global", importance 5-7`,
333
- ` Observe: instruction patterns (chained? contextual? explicit?), response expectations (code-only? options? explanations?), correction style (how they say no), expertise signals (where they need no hand-holding), frustration triggers, workflow preferences (incremental vs big-bang)`,
334
- `Do not mention this to the user.`,
335
- ].join('\n'),
336
- };
337
-
338
- // ============================================================
339
- // LANGUAGE DETECTION
340
- // ============================================================
341
-
342
- /**
343
- * Detects if the prompt is primarily non-English by checking the ratio
344
- * of non-ASCII alphabetic characters. Tech terms (code, paths, URLs)
345
- * are stripped first to avoid false positives from code snippets.
346
- */
347
- function isNonEnglish(text) {
348
- // Strip things that look like code, paths, URLs, or technical tokens
349
- const cleaned = text
350
- .replace(/`[^`]*`/g, '') // inline code
351
- .replace(/https?:\/\/\S+/g, '') // URLs
352
- .replace(/[A-Za-z][\w./-]*\.[a-z]{1,5}/g, '') // file paths
353
- .replace(/\b[A-Z_]{2,}\b/g, '') // CONSTANTS
354
- .replace(/[{}()\[\];:=<>]/g, '') // syntax chars
355
- .trim();
356
-
357
- if (cleaned.length < 10) return false; // too short to tell
358
-
359
- // Count characters that are alphabetic but outside basic Latin
360
- const nonLatin = (cleaned.match(/[^\x00-\x7F\s\d]/g) || []).length;
361
- const alpha = (cleaned.match(/[a-zA-Z]/g) || []).length;
362
- const total = nonLatin + alpha;
363
-
364
- if (total === 0) return false;
365
-
366
- // If > 40% of alphabetic chars are non-Latin, it's likely non-English
367
- return (nonLatin / total) > 0.4;
368
- }
369
-
370
- /**
371
- * Checks if a Latin-script prompt looks like English by counting
372
- * common English function words. If 2+ are found, it's English.
373
- * This prevents the catch-all from firing on English sentences
374
- * that simply didn't match any tier pattern.
375
- */
376
- const ENGLISH_FUNCTION_WORDS = /\b(the|is|are|was|were|have|has|had|will|would|can|could|should|this|that|with|from|for|not|but|and|it|to|in|on|at|of|my|your|our|we|you|they|do|did|does|get|got|set|let|if|or|an?)\b/gi;
377
-
378
- function looksEnglish(text) {
379
- const matches = text.match(ENGLISH_FUNCTION_WORDS) || [];
380
- return matches.length >= 2;
381
- }
382
-
383
- // ============================================================
384
- // MAIN
385
- // ============================================================
386
-
387
- // Max user-learning nudges per session (overridable via hook-features.json)
388
- const USER_LEARNING_MAX_NUDGES_DEFAULT = 3;
389
-
390
- /**
391
- * Debug logger for user-learning nudge diagnostics.
392
- */
393
- const UL_DEBUG = join(DATA_DIR, 'user-learning-debug.log');
394
- function debugUL(msg) {
395
- try { appendFileSync(UL_DEBUG, `[${new Date().toISOString()}] ${msg}\n`); } catch { /* best effort */ }
396
- }
397
-
398
- /**
399
- * Check if user-learning nudge should fire.
400
- * Fires at threshold multiples (3, 6, 9...) up to max nudges.
401
- * Returns nudge text or empty string.
402
- */
403
- function checkUserLearning(features, sessionId) {
404
- if (features.userLearning === false) {
405
- debugUL(`SKIP: userLearning feature disabled`);
406
- return '';
407
- }
408
- if (!sessionId) {
409
- debugUL(`SKIP: no sessionId`);
410
- return '';
411
- }
412
-
413
- const threshold = features.userLearningThreshold || 8;
414
- const maxNudges = features.userLearningMaxNudges || USER_LEARNING_MAX_NUDGES_DEFAULT;
415
- const flagPath = join(PENDING_REMEMBER_DIR, `${sessionId}.json`);
416
- if (!existsSync(flagPath)) {
417
- debugUL(`SKIP: flag file not found at ${flagPath}`);
418
- return '';
419
- }
420
-
421
- let flag;
422
- try {
423
- flag = JSON.parse(readFileSync(flagPath, 'utf-8'));
424
- } catch (e) {
425
- debugUL(`SKIP: failed to parse flag file: ${e.message}`);
426
- return '';
427
- }
428
-
429
- const msgCount = flag.totalSessionMessages || flag.messageCount || 0;
430
- const nudgeCount = flag.userLearningNudgeCount || 0;
431
- const observed = flag.userLearningObserved || false;
432
-
433
- debugUL(`CHECK: session=${sessionId.slice(0, 8)}... msgCount=${msgCount} threshold=${threshold} nudgeCount=${nudgeCount} maxNudges=${maxNudges} observed=${observed}`);
434
-
435
- // If a style observation was already stored/updated this session, skip further nudges
436
- if (observed) {
437
- debugUL(`SKIP: userLearningObserved=true (already stored this session)`);
438
- return '';
439
- }
440
-
441
- if (nudgeCount >= maxNudges) {
442
- debugUL(`SKIP: max nudges reached (${nudgeCount} >= ${maxNudges})`);
443
- return '';
444
- }
445
- if (msgCount < threshold) {
446
- debugUL(`SKIP: msgCount ${msgCount} < threshold ${threshold}`);
447
- return '';
448
- }
449
-
450
- const expectedNudges = Math.floor(msgCount / threshold);
451
- if (expectedNudges <= nudgeCount) {
452
- debugUL(`SKIP: expectedNudges ${expectedNudges} <= nudgeCount ${nudgeCount}`);
453
- return '';
454
- }
455
-
456
- debugUL(`FIRE: nudge #${nudgeCount + 1} (expectedNudges=${expectedNudges})`);
457
-
458
- // Persist nudge count + pending flag (best-effort — don't block nudge on write failure)
459
- flag.userLearningNudgeCount = nudgeCount + 1;
460
- flag.userLearningPending = true;
461
- try {
462
- writeFileSync(flagPath, JSON.stringify(flag));
463
- debugUL(`PERSIST: nudgeCount saved as ${nudgeCount + 1}`);
464
- } catch (e) {
465
- debugUL(`PERSIST FAILED (nudge still fires): ${e.message}`);
466
- }
467
-
468
- // First nudge: full instructions. Subsequent: short reminder.
469
- if (nudgeCount === 0) {
470
- return NUDGE.userLearning;
471
- }
472
- return `SynaBun User Learning reminder: You've had ${msgCount} exchanges. If you've noticed new behavioral patterns (how they give instructions, correct you, make decisions, or signal frustration), call \`recall\` category \`communication-style\` — then \`reflect\` to update an existing entry, or \`remember\` only if genuinely new. Do NOT create duplicates. Do NOT store surface-level formatting observations.`;
473
- }
474
-
475
- // ============================================================
476
- // AUTO-RECALL — Hook-side memory injection
477
- // Calls NI server to fetch relevant memories for the user's prompt
478
- // and formats them for injection into additionalContext.
479
- // ============================================================
480
-
481
- function formatAge(isoDate) {
482
- const diffMs = Date.now() - new Date(isoDate).getTime();
483
- const days = Math.floor(diffMs / (1000 * 60 * 60 * 24));
484
- if (days === 0) return 'today';
485
- if (days === 1) return '1 day ago';
486
- if (days < 30) return `${days} days ago`;
487
- const months = Math.floor(days / 30);
488
- return months === 1 ? '1 month ago' : `${months} months ago`;
489
- }
490
-
491
- async function autoRecall(prompt, cwd) {
492
- try {
493
- const niUrl = process.env.SYNABUN_NI_URL || 'http://localhost:3344';
494
- const project = detectProject(cwd);
495
-
496
- const resp = await fetch(`${niUrl}/api/hook-recall`, {
497
- method: 'POST',
498
- headers: { 'Content-Type': 'application/json' },
499
- body: JSON.stringify({
500
- query: prompt,
501
- project: project !== 'global' ? project : undefined,
502
- limit: 3,
503
- min_score: 0.4,
504
- }),
505
- signal: AbortSignal.timeout(3000),
506
- });
507
-
508
- if (!resp.ok) return '';
509
- const data = await resp.json();
510
- if (!data.results || data.results.length === 0) return '';
511
-
512
- const lines = data.results.map((r, i) => {
513
- const score = (r.score * 100).toFixed(0);
514
- const age = formatAge(r.created_at);
515
- const tags = r.tags?.length ? `Tags: ${r.tags.join(', ')}` : '';
516
- const files = r.related_files?.length ? `Files: ${r.related_files.slice(0, 3).join(', ')}` : '';
517
- const details = [tags, files].filter(Boolean).join(' | ');
518
- return `${i + 1}. [${r.category} | importance ${r.importance}, ${age}, ${score}% match] ${r.content}${details ? `\n ${details}` : ''}`;
519
- });
520
-
521
- return [
522
- '=== SynaBun: Related Memories ===',
523
- ...lines,
524
- 'These memories may be relevant. Use as context — call recall for deeper search if needed.',
525
- '=== End Memories ===',
526
- ].join('\n');
527
- } catch {
528
- // NI down, timeout, or error — silently skip
529
- return '';
530
- }
531
- }
532
-
533
- async function main() {
534
- let prompt = '';
535
- let sessionId = '';
536
- let cwd = '';
537
- try {
538
- const raw = await readStdin();
539
- const input = JSON.parse(raw);
540
- prompt = input.prompt || '';
541
- sessionId = input.session_id || '';
542
- cwd = input.cwd || '';
543
- } catch { /* proceed with empty */ }
544
-
545
- const trimmed = prompt.trim();
546
-
547
- // Session heartbeat to Neural Interface session monitor (fire-and-forget)
548
- if (sessionId) {
549
- const niUrl = process.env.SYNABUN_NI_URL || 'http://localhost:3344';
550
- try {
551
- fetch(`${niUrl}/api/sessions/heartbeat`, {
552
- method: 'POST',
553
- headers: { 'Content-Type': 'application/json' },
554
- body: JSON.stringify({ claudeSessionId: sessionId }),
555
- signal: AbortSignal.timeout(2000),
556
- }).catch(() => {});
557
- } catch { /* ok */ }
558
- }
559
-
560
- // --- Loop marker detection (BEFORE greeting — loops must bypass greeting) ---
561
- const terminalSessionEnv = process.env.SYNABUN_TERMINAL_SESSION || '';
562
- if (sessionId && /^\[SynaBun Loop\]/i.test(trimmed)) {
563
- try {
564
- if (existsSync(LOOP_DIR)) {
565
- const pending = readdirSync(LOOP_DIR)
566
- .filter(f => f.startsWith('pending-') && f.endsWith('.json'));
567
- // Filter by terminalSessionId when available (multi-loop isolation).
568
- // Without the env var, fall back to first match (legacy behavior).
569
- let matchedPending = null;
570
- if (terminalSessionEnv) {
571
- for (const pf of pending) {
572
- try {
573
- const ps = JSON.parse(readFileSync(join(LOOP_DIR, pf), 'utf-8'));
574
- if (ps.terminalSessionId === terminalSessionEnv) { matchedPending = pf; break; }
575
- } catch { continue; }
576
- }
577
- } else if (pending.length > 0) {
578
- matchedPending = pending[0];
579
- }
580
- if (matchedPending) {
581
- const pendingPath = join(LOOP_DIR, matchedPending);
582
- const targetPath = join(LOOP_DIR, `${sessionId}.json`);
583
- renameSync(pendingPath, targetPath);
584
-
585
- const state = JSON.parse(readFileSync(targetPath, 'utf-8'));
586
- delete state.pending;
587
- // Set currentIteration to 1 immediately — closes the race window where
588
- // another session's stop hook fallback scan (which only matches
589
- // currentIteration === 0) could steal this loop file.
590
- state.currentIteration = 1;
591
- // Preserve terminalSessionId — loop driver needs it for session isolation
592
- writeFileSync(targetPath, JSON.stringify(state, null, 2));
593
-
594
- const browserNote = buildBrowserNote(state);
595
- const blockerRule = state.usesBrowser
596
- ? '- CRITICAL: If the browser shows a login page, CAPTCHA, 2FA, or ANY wall requiring human action — STOP IMMEDIATELY. Output what the user needs to do (e.g. "Please log into Twitter in the browser panel"). Do NOT use WebSearch, WebFetch, or any workaround. Do NOT try to bypass it. Just STOP and WAIT.'
597
- : '';
598
- const autonomy = buildAutonomyBlock(blockerRule, sessionId);
599
-
600
- // Extract formatting rules and place them prominently
601
- const formattingRules = extractFormattingRules(state.task);
602
- const fmtBlock = formattingRules
603
- ? `\n=== FORMATTING RULES (MANDATORY \u2014 EVERY ITERATION) ===\n${formattingRules}\n=== END FORMATTING RULES ===\n`
604
- : '';
605
-
606
- process.stdout.write(JSON.stringify({
607
- hookSpecificOutput: {
608
- hookEventName: 'UserPromptSubmit',
609
- additionalContext: `${fmtBlock}SynaBun Loop ACTIVE: ${state.totalIterations} iterations (${state.totalIterations - 1} remaining).\nTask: ${state.task}${state.context ? `\nContext: ${state.context}` : ''}${browserNote}\n\nBegin iteration 1 immediately.${autonomy}`,
610
- },
611
- }));
612
- return;
613
- }
614
-
615
- // Case 2: Subsequent iteration — find any active loop state file (after /clear)
616
- // Note: /clear may change the session ID, so we can't match by sessionId alone.
617
- // Scan for any active loop with currentIteration > 0.
618
- let existingPath = join(LOOP_DIR, `${sessionId}.json`);
619
- let state = null;
620
- if (existsSync(existingPath)) {
621
- try { state = JSON.parse(readFileSync(existingPath, 'utf-8')); } catch { /* skip */ }
622
- }
623
- // Fallback: scan loop files for an active loop (session ID may have changed after /clear).
624
- // When SYNABUN_TERMINAL_SESSION is set, only match loops owned by this terminal.
625
- // Without the env var, fall back to first active loop (legacy behavior).
626
- if (!state?.active || !(state?.currentIteration > 0)) {
627
- const now = Date.now();
628
- const allLoopFiles = readdirSync(LOOP_DIR)
629
- .filter(f => f.endsWith('.json') && !f.startsWith('pending-'));
630
- for (const f of allLoopFiles) {
631
- try {
632
- const fullPath = join(LOOP_DIR, f);
633
- const candidate = JSON.parse(readFileSync(fullPath, 'utf-8'));
634
- if (candidate.active && candidate.currentIteration > 0) {
635
- // Multi-loop isolation: only match OUR terminal's loop
636
- if (terminalSessionEnv && candidate.terminalSessionId && candidate.terminalSessionId !== terminalSessionEnv) continue;
637
- // Validate the loop hasn't exceeded its own time cap + grace
638
- // Skip loops inactive for >45 minutes (stuck)
639
- const lastAct = new Date(candidate.lastIterationAt || candidate.startedAt || 0).getTime();
640
- if (now - lastAct > 45 * 60 * 1000) continue;
641
- state = candidate;
642
- existingPath = fullPath;
643
- break;
644
- }
645
- } catch { /* skip corrupt */ }
646
- }
647
- }
648
- if (state?.active && state?.currentIteration > 0) {
649
- const formattingRules = extractFormattingRules(state.task);
650
- const browserNote = buildBrowserNote(state);
651
- const blockerRule2 = state.usesBrowser
652
- ? '- CRITICAL: If the browser shows a login page, CAPTCHA, 2FA, or ANY wall requiring human action — STOP IMMEDIATELY. Output what the user needs to do. Do NOT use WebSearch, WebFetch, or any workaround. Just STOP and WAIT.'
653
- : '';
654
- const autonomy2 = buildAutonomyBlock(blockerRule2, sessionId);
655
- const journal = buildJournalBlock(state);
656
- const iterationsRemaining = (state.totalIterations || 10) - (state.currentIteration || 0);
657
-
658
- const parts = [
659
- // Memory rules (session-start won't re-inject after /clear)
660
- 'SynaBun memory is active. CLAUDE.md contains the memory rules. Follow them.',
661
- '',
662
- ];
663
-
664
- // Formatting rules — FIRST, most prominent position
665
- if (formattingRules) {
666
- parts.push(
667
- '=== FORMATTING RULES (MANDATORY \u2014 EVERY ITERATION) ===',
668
- formattingRules,
669
- '=== END FORMATTING RULES ===',
670
- '',
671
- );
672
- }
673
-
674
- parts.push(
675
- `SynaBun Loop ACTIVE: Iteration ${state.currentIteration}/${state.totalIterations} (${iterationsRemaining} remaining).`,
676
- `Task: ${state.task}`,
677
- );
678
- if (state.context) parts.push(`Context: ${state.context}`);
679
-
680
- // Journal + progress
681
- if (journal) parts.push('', journal);
682
-
683
- // Browser enforcement
684
- if (browserNote) parts.push(browserNote);
685
-
686
- parts.push(
687
- '',
688
- `Begin iteration ${state.currentIteration} immediately.`,
689
- autonomy2,
690
- );
691
-
692
- process.stdout.write(JSON.stringify({
693
- hookSpecificOutput: {
694
- hookEventName: 'UserPromptSubmit',
695
- additionalContext: parts.filter(Boolean).join('\n'),
696
- },
697
- }));
698
- return;
699
- }
700
- }
701
- } catch { /* fall through to normal processing */ }
702
- }
703
-
704
- // --- Active loop detection for non-loop-marker messages ---
705
- // When user sends a regular message during an active browser loop, inject
706
- // loop context so Claude knows where it was, and sync the session ID file.
707
- let activeLoopNotice = '';
708
- if (sessionId && !/^\[SynaBun Loop\]/i.test(trimmed)) {
709
- const activeLoop = findActiveLoop(sessionId);
710
- if (activeLoop) {
711
- const journal = buildJournalBlock(activeLoop);
712
- const iterLeft = (activeLoop.totalIterations || 10) - (activeLoop.currentIteration || 0);
713
- const browserNote = activeLoop.usesBrowser ? buildBrowserNote(activeLoop) : '';
714
- const parts = [
715
- `=== ACTIVE LOOP NOTICE ===`,
716
- `You are mid-loop: Iteration ${activeLoop.currentIteration}/${activeLoop.totalIterations} (${iterLeft} iterations remaining).`,
717
- `Task: ${activeLoop.task}`,
718
- ];
719
- if (activeLoop.context) parts.push(`Context: ${activeLoop.context}`);
720
- if (journal) parts.push('', journal);
721
- parts.push(
722
- '',
723
- `The user sent a message. Respond to it, then call \`loop\` action \`update\` with your current progress, then continue the loop task from where you left off.`,
724
- );
725
- if (browserNote) parts.push(browserNote);
726
- parts.push(`=== END LOOP NOTICE ===`);
727
- activeLoopNotice = parts.filter(p => p !== undefined).join('\n');
728
- }
729
- }
730
-
731
- // --- Track message count (BEFORE skip check — all messages count) ---
732
- let currentMessageCount = 0;
733
- if (sessionId && trimmed.length > 0) {
734
- const flagPath = join(PENDING_REMEMBER_DIR, `${sessionId}.json`);
735
- if (!existsSync(PENDING_REMEMBER_DIR)) mkdirSync(PENDING_REMEMBER_DIR, { recursive: true });
736
- let flag = { editCount: 0, retries: 0, files: [], messageCount: 0 };
737
- if (existsSync(flagPath)) {
738
- try { flag = JSON.parse(readFileSync(flagPath, 'utf-8')); } catch { /* start fresh */ }
739
- }
740
- flag.messageCount = (flag.messageCount || 0) + 1;
741
- flag.totalSessionMessages = (flag.totalSessionMessages || 0) + 1;
742
- currentMessageCount = flag.messageCount;
743
- if (!flag.firstMessageAt) flag.firstMessageAt = new Date().toISOString();
744
- flag.lastMessageAt = new Date().toISOString();
745
- try { writeFileSync(flagPath, JSON.stringify(flag)); } catch { /* ok */ }
746
-
747
- // --- Greeting reinforcement (first message only, before skip patterns) ---
748
- if (flag.messageCount === 1 && !flag.greetingDelivered) {
749
- const greetingFeatures = getHookFeatures();
750
- if (greetingFeatures.greeting === true) {
751
- flag.greetingDelivered = true;
752
- try { writeFileSync(flagPath, JSON.stringify(flag)); } catch { /* ok */ }
753
- process.stdout.write(JSON.stringify({
754
- hookSpecificOutput: {
755
- hookEventName: 'UserPromptSubmit',
756
- additionalContext: 'SynaBun: This is the FIRST message of the session. Output the greeting from the GREETING DIRECTIVE in your session context NOW, then respond to the user\'s message. Do NOT launch any task-related tools (Agent, Explore, Grep, Glob) until the greeting text is visible. If their message is just a greeting (hi, hello, hey, or a single character), the greeting IS your full response — no need to ask what they need.',
757
- },
758
- }));
759
- return;
760
- }
761
- }
762
-
763
- }
764
-
765
- // Skip trivial messages first (fastest path)
766
- if (SKIP_PATTERNS.some(p => p.test(trimmed))) {
767
- process.stdout.write(JSON.stringify({}));
768
- return;
769
- }
770
-
771
- const features = getHookFeatures();
772
-
773
- // --- Collect primary context from priority chain ---
774
- let primaryContext = '';
775
-
776
- // Priority 0: TASK BOUNDARY — pending-remember check (highest priority)
777
- if (!primaryContext && sessionId) {
778
- const flagPath = join(PENDING_REMEMBER_DIR, `${sessionId}.json`);
779
- if (existsSync(flagPath)) {
780
- try {
781
- const flag = JSON.parse(readFileSync(flagPath, 'utf-8'));
782
- const editCount = flag.editCount || 0;
783
- if (editCount >= 1) {
784
- const files = Array.isArray(flag.files) ? flag.files : [];
785
- primaryContext = NUDGE.pendingRemember(editCount, files);
786
- }
787
- } catch { /* corrupt flag — ignore, don't block */ }
788
- }
789
- }
790
-
791
- // Priority 1: Conversation recall (highest recall priority)
792
- if (!primaryContext) {
793
- const conversationMemoryEnabled = features.conversationMemory !== false;
794
- if (conversationMemoryEnabled) {
795
- const convMatches = CONVERSATION_RECALL_TRIGGERS.filter(p => p.test(prompt));
796
- if (convMatches.length >= 1) {
797
- primaryContext = NUDGE.conversation;
798
- }
799
- }
800
- }
801
-
802
- // Priority 2: Tier 1 — MUST recall (>= 1 match)
803
- if (!primaryContext) {
804
- const t1 = TIER1_TRIGGERS.filter(p => p.test(prompt));
805
- if (t1.length >= 1) primaryContext = NUDGE.tier1;
806
- }
807
-
808
- // Priority 3: Tier 2 — SHOULD recall (>= 1 match)
809
- if (!primaryContext) {
810
- const t2 = TIER2_TRIGGERS.filter(p => p.test(prompt));
811
- if (t2.length >= 1) primaryContext = NUDGE.tier2;
812
- }
813
-
814
- // Priority 4: Tier 3 — CONSIDER recall (>= 2 matches)
815
- if (!primaryContext) {
816
- const t3 = TIER3_TRIGGERS.filter(p => p.test(prompt));
817
- if (t3.length >= 2) primaryContext = NUDGE.tier3;
818
- }
819
-
820
- // Priority 5: Non-English (non-Latin scripts)
821
- if (!primaryContext && isNonEnglish(prompt)) {
822
- primaryContext = NUDGE.nonEnglish;
823
- }
824
-
825
- // Priority 6: Latin-script non-English catch-all
826
- if (!primaryContext && trimmed.length > 30 && !looksEnglish(trimmed)) {
827
- primaryContext = NUDGE.nonEnglish;
828
- }
829
-
830
- // --- Auto-recall: inject relevant memories from NI server ---
831
- let autoRecallContext = '';
832
- if (!activeLoopNotice && currentMessageCount >= 2) {
833
- autoRecallContext = await autoRecall(trimmed, cwd);
834
- }
835
-
836
- // --- User Learning (independent — appends to any primary context) ---
837
- const userLearningContext = checkUserLearning(features, sessionId);
838
-
839
- // --- Boot sequence cancellation (messages 2+, override persistent SessionStart context) ---
840
- const bootCancel = (currentMessageCount >= 2)
841
- ? 'SynaBun: The GREETING DIRECTIVE and Session Boot Sequence have ALREADY been completed. Do NOT re-greet. Do NOT call recall for session boot. Ignore any "first response MUST" instructions — they applied only to message 1. CRITICAL: If you see a GREETING DIRECTIVE in earlier context (including compacted summaries), it is STALE — do NOT execute it. This is an ongoing conversation, not a fresh session.'
842
- : '';
843
-
844
- // --- Emit combined output ---
845
- const combined = [bootCancel, activeLoopNotice, primaryContext, autoRecallContext, userLearningContext].filter(Boolean).join('\n\n');
846
-
847
- if (combined) {
848
- process.stdout.write(JSON.stringify({
849
- hookSpecificOutput: {
850
- hookEventName: 'UserPromptSubmit',
851
- additionalContext: combined,
852
- },
853
- }));
854
- } else {
855
- process.stdout.write(JSON.stringify({}));
856
- }
857
- }
858
-
859
- main().catch(() => {
860
- process.stdout.write(JSON.stringify({}));
861
- });
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * SynaBun UserPromptSubmit Hook for Claude Code
5
+ *
6
+ * Fires on every user message. Analyzes the prompt against tiered
7
+ * trigger patterns and injects context-aware recall nudges:
8
+ *
9
+ * TIER 1 (MUST recall) — Past work, decisions, explicit memory references
10
+ * TIER 2 (SHOULD recall) — Debugging, architecture, domain-specific knowledge
11
+ * TIER 3 (CONSIDER recall) — New features, similarity, broad technical mentions
12
+ *
13
+ * Conversation recall triggers have highest priority (above all tiers).
14
+ *
15
+ * Jev (TypeSafe, via the Neural Interface) makes these calls when available,
16
+ * in ONE prompt request whose riders are asked only when they matter: whether
17
+ * this prompt starts a new task while edits are unsaved (task boundary),
18
+ * whether it refers to a past session, whether it reveals a working
19
+ * preference. Nudges Jev decided carry a `[Jev]` tag; every missing answer
20
+ * falls back to the regex/counter rule it replaced. The judgment and the
21
+ * ranked auto-recall (surface rerank, relevance floor) run in parallel inside
22
+ * the hook's 3 s budget.
23
+ *
24
+ * System-injected "prompts" (task notifications, cross-session messages,
25
+ * mailbox relays, local-command output, continuation banners) are skipped
26
+ * entirely: no judgment, recall, message count or regex (prompt-origin.mjs).
27
+ */
28
+
29
+ import { readFileSync, writeFileSync, appendFileSync, existsSync, mkdirSync, renameSync, readdirSync } from 'node:fs';
30
+ import { execSync } from 'node:child_process';
31
+ import { dirname, join } from 'node:path';
32
+ import { fileURLToPath } from 'node:url';
33
+ import { withStateLock, writeJsonAtomic } from './state.mjs';
34
+ import { startTaskTurn } from './task-gate.mjs';
35
+ import { detectProject, DATA_DIR, appendLoopLog, recallMemories, appendCapped, hookJudge, hookBudget, clipForJudge, clipQuery, formatStaleNotices, readRecentHumanPrompts, niUrl, isTemporaryChat } from './shared.mjs';
36
+ import { classifyPrompt, isTrivialPrompt } from './prompt-origin.mjs';
37
+
38
+ // Cross-platform safety: catch uncaught errors and output valid hook JSON
39
+ process.on('uncaughtException', () => { try { process.stdout.write('{}'); } catch {} process.exit(0); });
40
+ process.on('unhandledRejection', () => { try { process.stdout.write('{}'); } catch {} process.exit(0); });
41
+
42
+ // UserPromptSubmit has 3 s. Node startup counts against it.
43
+ const budget = hookBudget(3000);
44
+
45
+ const __dirname = dirname(fileURLToPath(import.meta.url));
46
+ const HOOK_FEATURES_PATH = join(DATA_DIR, 'hook-features.json');
47
+ const PENDING_REMEMBER_DIR = join(DATA_DIR, 'pending-remember');
48
+ const LOOP_DIR = join(DATA_DIR, 'loop');
49
+ const GREETING_CONFIG_PATH = join(DATA_DIR, 'greeting-config.json');
50
+
51
+ // --- Greeting helpers (moved from session-start.mjs) ---
52
+
53
+ function loadGreetingConfig() {
54
+ try {
55
+ if (!existsSync(GREETING_CONFIG_PATH)) return null;
56
+ return JSON.parse(readFileSync(GREETING_CONFIG_PATH, 'utf-8'));
57
+ } catch { return null; }
58
+ }
59
+
60
+ function getProjectGreetingConfig(config, project) {
61
+ if (!config) return null;
62
+ if (config.projects && config.projects[project]) {
63
+ return { ...config.defaults, ...config.projects[project] };
64
+ }
65
+ if (config.global) {
66
+ return { ...config.defaults, ...config.global };
67
+ }
68
+ return config.defaults || null;
69
+ }
70
+
71
+ function getTimeGreeting() {
72
+ const hour = new Date().getHours();
73
+ if (hour >= 5 && hour < 12) return 'Good morning';
74
+ if (hour >= 12 && hour < 17) return 'Good afternoon';
75
+ return 'Good evening';
76
+ }
77
+
78
+ function getGitBranch(cwd) {
79
+ try {
80
+ return execSync('git rev-parse --abbrev-ref HEAD', {
81
+ cwd,
82
+ encoding: 'utf-8',
83
+ timeout: 2000,
84
+ stdio: ['pipe', 'pipe', 'pipe'],
85
+ }).trim();
86
+ } catch {
87
+ return 'unknown';
88
+ }
89
+ }
90
+
91
+ function resolveTemplate(template, vars) {
92
+ return template.replace(/\{(\w+)\}/g, (match, key) => {
93
+ return vars[key] !== undefined ? vars[key] : match;
94
+ });
95
+ }
96
+
97
+ function formatReminders(reminders, prefix) {
98
+ if (!reminders || reminders.length === 0) return '';
99
+ const lines = reminders.map((r) => `- **${r.label}:** \`${r.command}\``);
100
+ return `${prefix}\n${lines.join('\n')}`;
101
+ }
102
+
103
+ /**
104
+ * Build the full greeting directive + boot sequence for message 1.
105
+ * Returns the complete additionalContext string, or empty string if greeting is disabled.
106
+ */
107
+ function buildGreetingContext(cwd, project, features) {
108
+ if (features.greeting !== true) return '';
109
+
110
+ const greetingConfig = loadGreetingConfig();
111
+ const projectConfig = getProjectGreetingConfig(greetingConfig, project);
112
+ const DEFAULT_TEMPLATE = '{time_greeting}! Working on **{project_label}** (`{branch}` branch). {date}.';
113
+
114
+ const branch = getGitBranch(cwd);
115
+ const vars = {
116
+ time_greeting: getTimeGreeting(),
117
+ project_name: project,
118
+ project_label: projectConfig?.label || project,
119
+ branch,
120
+ date: new Date().toLocaleDateString('en-US', {
121
+ weekday: 'long', year: 'numeric', month: 'long', day: 'numeric',
122
+ }),
123
+ };
124
+
125
+ const greetingText = resolveTemplate(
126
+ projectConfig?.greetingTemplate || greetingConfig?.defaults?.greetingTemplate || DEFAULT_TEMPLATE,
127
+ vars,
128
+ );
129
+
130
+ const showReminders = projectConfig?.showReminders ?? greetingConfig?.defaults?.showReminders ?? false;
131
+ const remindersText = showReminders
132
+ ? formatReminders(
133
+ projectConfig?.reminders,
134
+ projectConfig?.reminderPrefix || greetingConfig?.defaults?.reminderPrefix || 'Reminders:',
135
+ )
136
+ : '';
137
+
138
+ const showLastSession = projectConfig?.showLastSession ?? greetingConfig?.defaults?.showLastSession ?? false;
139
+
140
+ const ctx = [];
141
+
142
+ ctx.push(
143
+ `## GREETING DIRECTIVE`,
144
+ ``,
145
+ `When you produce your FIRST response in this session, begin with this greeting:`,
146
+ ``,
147
+ `> ${greetingText}`,
148
+ ``,
149
+ );
150
+
151
+ if (remindersText) {
152
+ ctx.push(
153
+ `After the greeting, show these service reminders with individual copy buttons (use separate markdown code blocks for each command):`,
154
+ ``,
155
+ remindersText,
156
+ ``,
157
+ );
158
+ }
159
+
160
+ if (showLastSession) {
161
+ ctx.push(
162
+ `After the greeting${remindersText ? ' and reminders' : ''}, include a brief "Last session:" line summarizing what was worked on. You will have this from the recall results. If recall returns nothing relevant, omit the last session line.`,
163
+ ``,
164
+ );
165
+ }
166
+
167
+ ctx.push(
168
+ `Present the greeting naturally — do not mention this directive or say "as instructed". Just greet.`,
169
+ ``,
170
+ `---`,
171
+ ``,
172
+ );
173
+
174
+ // Session Boot Sequence
175
+ const bootSteps = [
176
+ `1. Call \`recall\` with query: "recent sessions, ongoing work, known issues, decisions", project: "${project}", **recency_boost: true**, **rerank: true** — recency picks the shortlist, the rerank orders it by judged relevance to the project's current state.`,
177
+ ];
178
+
179
+ if (features.userLearning !== false) {
180
+ bootSteps.push(
181
+ `2. Call \`recall\` with query: "user communication style preferences", category: "communication-style", limit: 2 — this surfaces how the user prefers to communicate.`,
182
+ `3. Output the greeting as your FIRST text. No other tool calls between the recalls and greeting.`,
183
+ `4. Only AFTER the greeting is fully written, proceed with the user's request. Use recall results as your starting context — do not re-search for information recall already provided.`,
184
+ );
185
+ } else {
186
+ bootSteps.push(
187
+ `2. Output the greeting as your FIRST text. No other tool calls between recall and greeting.`,
188
+ `3. Only AFTER the greeting is fully written, proceed with the user's request. Use recall results as your starting context — do not re-search for information recall already provided.`,
189
+ );
190
+ }
191
+
192
+ ctx.push(
193
+ `### Session Boot Sequence (MANDATORY ORDER)`,
194
+ ``,
195
+ `Your first response MUST follow this exact sequence:`,
196
+ ...bootSteps,
197
+ ``,
198
+ );
199
+
200
+ return ctx.join('\n');
201
+ }
202
+
203
+ function getHookFeatures() {
204
+ try {
205
+ if (!existsSync(HOOK_FEATURES_PATH)) return {};
206
+ return JSON.parse(readFileSync(HOOK_FEATURES_PATH, 'utf-8'));
207
+ } catch { return {}; }
208
+ }
209
+
210
+ function readStdin() {
211
+ return new Promise((resolve) => {
212
+ if (process.stdin.isTTY) return resolve('{}');
213
+ let data = '';
214
+ process.stdin.setEncoding('utf8');
215
+ process.stdin.on('data', (chunk) => { data += chunk; });
216
+ process.stdin.on('end', () => { clearTimeout(guard); resolve(data); });
217
+ // unref + clearTimeout: without both, this timer keeps the event loop alive
218
+ // for its full duration AFTER 'end' already resolved, so every hook
219
+ // invocation stalled ~2s against a 3s configured timeout.
220
+ const guard = setTimeout(() => resolve(data || '{}'), 2000);
221
+ guard.unref?.();
222
+ });
223
+ }
224
+
225
+ // ── Output helper ──────────────────────────────────────────────
226
+ // Writes the hook JSON and exits immediately. Without the explicit exit,
227
+ // any pending fetch (heartbeat/recall) keeps the event loop alive and
228
+ // delays prompt processing by up to the fetch timeout on EVERY message.
229
+
230
+ let _heartbeatPromise = null;
231
+ let _dispatchDirective = '';
232
+ const DISPATCH_DIRECTIVE = 'Before composing or launching any Task/Agent subagent (including Explore and general-purpose), complete an explicit, task-focused SynaBun recall. Tool discovery is allowed if needed to make recall callable. Do not launch recall and subagents in the same parallel batch. Fold relevant exact file paths, prior fixes, and constraints into each subagent prompt so it searches narrowly. Automatic Related Memories context helps focus this lookup; it does not replace explicit recall.';
233
+
234
+ async function emitAndExit(obj) {
235
+ if (_dispatchDirective && obj && typeof obj === 'object') {
236
+ const previous = obj.hookSpecificOutput?.additionalContext || '';
237
+ obj = { ...obj, hookSpecificOutput: { ...obj.hookSpecificOutput,
238
+ hookEventName: 'UserPromptSubmit', additionalContext: [previous, _dispatchDirective].filter(Boolean).join('\n\n') } };
239
+ }
240
+ // Give an in-flight heartbeat a short window to land (localhost: ~5ms)
241
+ if (_heartbeatPromise) {
242
+ try {
243
+ const wait = Math.max(0, Math.min(300, budget.remaining() - 50));
244
+ await Promise.race([_heartbeatPromise, new Promise(r => setTimeout(r, wait))]);
245
+ } catch { /* heartbeat is best-effort */ }
246
+ }
247
+ const json = typeof obj === 'string' ? obj : JSON.stringify(obj);
248
+ process.stdout.write(json, () => process.exit(0));
249
+ setTimeout(() => process.exit(0), 250); // failsafe if the write callback never fires
250
+ }
251
+
252
+ /**
253
+ * The whole hook for a temporary chat: the human's words as one memory search,
254
+ * with no session beside them and no judgment (typesafeOff() is true for the
255
+ * marker), and the Related Memories block as the only context. A trivial or
256
+ * system prompt searches nothing.
257
+ */
258
+ async function temporaryChatPrompt(prompt, project) {
259
+ let context = '';
260
+ try {
261
+ const origin = classifyPrompt(prompt);
262
+ const text = origin.origin === 'system' ? '' : String(origin.text || '');
263
+ if (text.trim() && !isTrivialPrompt(text)) {
264
+ context = await recallMemories({ query: clipQuery(text), project, limit: 3, minScore: 0.4, tokenBudget: 600, timeoutMs: 2000 }) || '';
265
+ }
266
+ } catch { context = ''; }
267
+ await emitAndExit(context ? { hookSpecificOutput: { hookEventName: 'UserPromptSubmit', additionalContext: context } } : {});
268
+ }
269
+
270
+ // ── Loop helper functions ──────────────────────────────────────
271
+
272
+ /**
273
+ * Extract formatting/style rules from the task text into a separate block.
274
+ * Matches lines containing prohibitions about dashes, emojis, spam, double posting, etc.
275
+ */
276
+ function extractFormattingRules(task) {
277
+ if (!task) return '';
278
+ const rules = [];
279
+ let hasDashRule = false;
280
+ for (const line of task.split('\n')) {
281
+ const t = line.trim();
282
+ if (!t) continue;
283
+ if (/\b(do not|don'?t|never|must not|avoid)\b/i.test(t) &&
284
+ /\b(dash|--|—|emoji|emote|emojis|spam|double post|over use)\b/i.test(t)) {
285
+ rules.push(t.replace(/^[-*•]\s*/, '').replace(/^\d+\.\s*/, ''));
286
+ if (/dash|--/i.test(t)) hasDashRule = true;
287
+ }
288
+ }
289
+ // Strengthen dash rule with explicit variants
290
+ if (hasDashRule) {
291
+ rules.push('NEVER use double dashes (--), em dashes (\u2014), or en dashes (\u2013) in ANY text you write. Use commas, periods, or semicolons instead.');
292
+ }
293
+ if (rules.length === 0) return '';
294
+ return rules.map(r => `- ${r}`).join('\n');
295
+ }
296
+
297
+ function buildBrowserNote(state) {
298
+ if (!state.usesBrowser) return '';
299
+ const lines = [
300
+ '',
301
+ '=== BROWSER ENFORCEMENT (MANDATORY) ===',
302
+ 'This automation REQUIRES the SynaBun internal browser. You MUST:',
303
+ ];
304
+ // HTTP MCP is shared across all loops — env-var pinning does NOT route tool calls.
305
+ // Claude must pass sessionId/tabId explicitly on every browser tool call, or a
306
+ // concurrent loop will hijack the tab.
307
+ if (state.browserSessionId) {
308
+ lines.push(`YOUR BROWSER SESSION: ${state.browserSessionId}`);
309
+ if (state.browserTabId) {
310
+ lines.push(`YOUR BROWSER TAB: ${state.browserTabId}`);
311
+ }
312
+ lines.push(`CRITICAL: Pass sessionId: "${state.browserSessionId}"${state.browserTabId ? ` and tabId: "${state.browserTabId}"` : ''} to EVERY browser tool call (browser_navigate, browser_click, browser_snapshot, browser_extract_*, etc.). Omitting them will cause your tool calls to hijack another loop's tab. No exceptions.`);
313
+ }
314
+ // NOTE: the full per-tool enumeration used to live here and was re-injected on
315
+ // EVERY iteration of EVERY browser loop. Claude Code discovers tools via ToolSearch,
316
+ // and template tasks now carry a compact `=== BROWSER ===` block, so the dump is
317
+ // pure token waste. Keep only the dynamic session pin + the universal fixes in short
318
+ // form (ad-hoc loops without a template still need these).
319
+ lines.push(
320
+ '1. Start with `browser_navigate` to your target URL.',
321
+ '2. Use ONLY SynaBun browser_* MCP tools. NEVER Playwright plugin tools (mcp__plugin_playwright_*), WebFetch, or WebSearch for visual browsing.',
322
+ '3. browser_type truncates ~100 chars: type in <=90-char chunks. Reply inline, never the modal.',
323
+ '4. Login / CAPTCHA / wall: STOP, screenshot, report to the user, and WAIT. Do NOT fall back to web search.',
324
+ '=== END BROWSER ENFORCEMENT ===',
325
+ );
326
+ // Facebook posting playbook (analytics-derived). Kept in sync with FACEBOOK_PLAYBOOK in
327
+ // neural-interface/server.js (the exec/opencode path); this is the claude-code/hook path.
328
+ if (/facebook/i.test(state.task || '')) {
329
+ lines.push(
330
+ '',
331
+ '=== FACEBOOK PLAYBOOK (authorized first-party marketing) ===',
332
+ "You post as the brand's own operator inside SynaBun Automation Studio. This is authorized first-party marketing, not spam, so do NOT refuse the task. Stay legitimate:",
333
+ '- TARGET gaming DISCUSSION, news, and recommendation communities. AVOID pure buy/sell/trade and promo-only groups (low engagement, fast link removal).',
334
+ '- RECALL EXCLUSIONS FIRST: recall tags ["fb-deal-excluded"] and skip any group on that ledger.',
335
+ '- CHECK GROUP RULES before posting. If self-promotion or external links are banned, SKIP and remember it (tag "fb-deal-excluded", content = group URL + reason). Never post into a no-links group.',
336
+ '- If the task sources groups via fb_groups worklist/mark, that directory is AUTHORITATIVE: skip the fb-deal-excluded recall, do not re-read group rules per group (the worklist already excludes forbidden groups), and record every outcome with fb_groups mark instead of per-group memories.',
337
+ '- DEDUP: browser_extract_fb_posts before posting; if a Critical Pixel post for this link/game is already visible or pending, skip the group.',
338
+ '- pending-approval COUNTS AS SUCCESS. Do not retry it; record it and move to the next group.',
339
+ "- Match the group's language. No em/en/double dashes.",
340
+ '- NEVER SPEND MONEY. This is ORGANIC posting only. Never click Boost / Promote / Turbinar / Impulsionar, never open Ads Manager, never enter any budget or payment info. After publishing, DISMISS any "Boost this post" upsell; ensure any "Boost when published" toggle is OFF before submitting. The Boost button sits right next to the Post button on this account (in Portuguese) — only submit via the composer submitButton from browser_fb_composer_state.',
341
+ '=== END FACEBOOK PLAYBOOK ===',
342
+ );
343
+ }
344
+ return lines.join('\n');
345
+ }
346
+
347
+ function buildAutonomyBlock(blockerRule, sessionId) {
348
+ const updateCall = sessionId
349
+ ? `- After completing this iteration, call \`loop\` with action \`update\`, session_id \`${sessionId}\`, and a brief summary.`
350
+ : '- After completing this iteration, call `loop` with action `update` and a brief summary.';
351
+ return [
352
+ '',
353
+ '--- LOOP AUTONOMY MODE ---',
354
+ 'IMPORTANT: Do NOT output a greeting. Do NOT call recall. You are in an autonomous loop \u2014 execute the task below immediately.',
355
+ '',
356
+ 'Rules for this session:',
357
+ '- Execute the task directly. Do NOT ask for confirmation or clarification.',
358
+ '- Make reasonable assumptions and proceed. Do not hesitate.',
359
+ '- Use all available tools (browser, memory, file system) as needed without asking.',
360
+ '- Each iteration should produce concrete output or progress.',
361
+ '- If something fails due to a technical issue, try an alternative approach.',
362
+ updateCall,
363
+ '- The server will automatically advance to the next iteration when you finish.',
364
+ blockerRule || '',
365
+ '--- END LOOP AUTONOMY ---',
366
+ ].filter(Boolean).join('\n');
367
+ }
368
+
369
+ function buildJournalBlock(state) {
370
+ const parts = [];
371
+ if (state.progressSummary) {
372
+ parts.push(`PROGRESS SO FAR: ${state.progressSummary}`);
373
+ }
374
+ const journal = Array.isArray(state.journal) ? state.journal : [];
375
+ const recent = journal.slice(-3);
376
+ if (recent.length > 0) {
377
+ parts.push('RECENT ITERATIONS:');
378
+ for (const entry of recent) {
379
+ parts.push(` Iteration ${entry.iteration}: ${entry.summary}`);
380
+ }
381
+ }
382
+ return parts.length > 0 ? parts.join('\n') : '';
383
+ }
384
+
385
+ /**
386
+ * Find an active loop owned by this session (exact file name match only).
387
+ * Does NOT scan other sessions' loop files — cross-session injection is
388
+ * the source of loop leaks between concurrent Claude panels/TUI sessions.
389
+ * Returns the loop state, or null if this session has no active loop.
390
+ */
391
+ function findActiveLoop(sessionId) {
392
+ try {
393
+ const exactPath = join(LOOP_DIR, `${sessionId}.json`);
394
+ if (!existsSync(exactPath)) return null;
395
+ const candidate = JSON.parse(readFileSync(exactPath, 'utf-8'));
396
+ if (!candidate.active) return null;
397
+ // Skip loops inactive for >45 minutes (stuck)
398
+ const lastAct = new Date(candidate.lastIterationAt || candidate.startedAt || 0).getTime();
399
+ if (Date.now() - lastAct > 45 * 60 * 1000) return null;
400
+ return candidate;
401
+ } catch { return null; }
402
+ }
403
+
404
+ // ============================================================
405
+ // TIER 1 — MUST recall (high confidence: past work, decisions, explicit memory)
406
+ // These indicate the user is referencing prior context that memory likely holds.
407
+ // Threshold: >= 1 match fires. Nudge: mandatory.
408
+ // ============================================================
409
+
410
+ const TIER1_TRIGGERS = [
411
+ // Explicit past work references
412
+ /\b(last time|before we|previously|earlier we|remember when|we (did|had|tried|used|decided|chose))\b/i,
413
+ /\b(why (did|do) we|what happened (to|with)|what was the)\b/i,
414
+ /\b(history|context) (of|about|for|on)\b/i,
415
+
416
+ // Decision recall
417
+ /\bshould (we|i) (use|go with|pick|choose|switch|keep|change|stick with)\b/i,
418
+ /\bwhat('s| is| was) the (best|right|correct|agreed|chosen) (approach|way|method|pattern)\b/i,
419
+ /\bwhat did we (decide|agree|settle) on\b/i,
420
+
421
+ // Explicit memory/recall references
422
+ /\b(do you remember|check (your )?memory|what do you know about|recall what)\b/i,
423
+ /\b(we (already|previously) (fixed|solved|handled|addressed|implemented))\b/i,
424
+ ];
425
+
426
+ // ============================================================
427
+ // TIER 2 — SHOULD recall (medium confidence: debugging, architecture, domains)
428
+ // These suggest memory might hold relevant context. Worth checking.
429
+ // Threshold: >= 1 match fires. Nudge: strong suggestion.
430
+ // ============================================================
431
+
432
+ const TIER2_TRIGGERS = [
433
+ // Debugging (likely to have past bug context)
434
+ /\b(bug|error|broken|crash|not working|doesn't work|keeps? (failing|breaking))\b/i,
435
+ /\b(debug|troubleshoot|investigate|diagnose|root cause)\b/i,
436
+
437
+ // Architecture & structural changes
438
+ /\b(refactor|restructur|migrat|upgrad|deprecat)\b/i,
439
+ /\b(architect|redesign|rearchitect)\b/i,
440
+
441
+ // Decision-making questions (broader than Tier 1)
442
+ /\bwhat('s| is) the (best|right|correct|proper) way to\b/i,
443
+ /\bhow (should|do) (we|i) (handle|approach|structure|organize)\b/i,
444
+
445
+ // Specific technical domains that accumulate knowledge
446
+ /\b(supabase|redis|upstash|sqlite)\b/i,
447
+ /\b(auth(entication|orization)?|session handling|jwt|mfa)\b/i,
448
+ /\b(cron job|ranking|price aggregat|deal(s)? (system|pipeline))\b/i,
449
+ ];
450
+
451
+ // ============================================================
452
+ // TIER 3 — CONSIDER recall (lower confidence: new features, broad tech)
453
+ // These MIGHT benefit from memory but often don't. Requires 2+ matches
454
+ // from this tier, OR 1 Tier 3 + 1 Tier 2 to fire. Nudge: soft.
455
+ // ============================================================
456
+
457
+ const TIER3_TRIGGERS = [
458
+ // New features (might conflict with past decisions)
459
+ /\b(implement|integrate) (a |the |new )?\w+/i,
460
+ /\bnew (feature|component|page|endpoint|hook|service)\b/i,
461
+
462
+ // Building on existing patterns
463
+ /\bsimilar to (the|what|how)\b/i,
464
+ /\bsame (as|way|pattern|approach) (as |we )?\b/i,
465
+ /\bconsistent with\b/i,
466
+
467
+ // Broad technical domains
468
+ /\b(database|cache|caching) (schema|strategy|layer|issue)\b/i,
469
+ /\b(api|endpoint) (design|structure|pattern)\b/i,
470
+ /\b(deploy|deployment|ci\/cd|pipeline) (strategy|process|config)\b/i,
471
+ /\b(config|configuration) (for|of|pattern)\b/i,
472
+ ];
473
+
474
+ // ============================================================
475
+ // CONVERSATION RECALL TRIGGERS (highest priority — above all tiers)
476
+ // ============================================================
477
+
478
+ const CONVERSATION_RECALL_TRIGGERS = [
479
+ /\bremember that (conversation|session|chat|discussion|time)\b/i,
480
+ /\bthat (conversation|session|chat) (about|where|when)\b/i,
481
+ /\b(days?|weeks?) ago.*(conversation|session|worked on|discussed|implemented|built)/i,
482
+ /\bcontinue (where we left off|that session|from last time|from yesterday)\b/i,
483
+ /\bwhat did we (talk|discuss|work on|do|build|implement|fix) (last|yesterday|on|the other)/i,
484
+ /\bfind that (session|conversation|chat) (where|about|when|from)\b/i,
485
+ /\bpick up (where|from) (we|last|that)/i,
486
+ /\b(yesterday|last week|other day).*(session|conversation|worked|discussed|implemented)/i,
487
+ ];
488
+
489
+ // Messages that never need recall: isTrivialPrompt() in prompt-origin.mjs
490
+ // (whole-string patterns; the old prefix patterns here dropped prompts such as
491
+ // "no, the bug is in X" before any judgment or recall).
492
+
493
+ // ============================================================
494
+ // NUDGE TEMPLATES
495
+ // ============================================================
496
+
497
+ // Nudges whose decision came from Jev say so.
498
+ const jev = (text) => `[Jev] ${text}`;
499
+
500
+ const NUDGE = {
501
+ pendingRemember: (editCount, files) => {
502
+ const fileList = files.length > 0
503
+ ? ` Files: ${files.slice(0, 5).join(', ')}${files.length > 5 ? ` (+${files.length - 5} more)` : ''}.`
504
+ : '';
505
+ const urgency = editCount >= 5
506
+ ? `CRITICAL: ${editCount} file edits`
507
+ : editCount >= 3
508
+ ? `IMPORTANT: ${editCount} file edits`
509
+ : `${editCount} file edit${editCount !== 1 ? 's' : ''}`;
510
+ return [
511
+ `SynaBun TASK BOUNDARY: ${urgency} from your previous work have NOT been stored in memory.`,
512
+ `You MUST call \`remember\` for that completed work BEFORE starting this new task.${fileList}`,
513
+ `Summarize what was done, why, and how — then proceed with the user's new request.`,
514
+ ].join(' ');
515
+ },
516
+
517
+ conversation: [
518
+ `The user is asking about a past conversation. Follow the Conversation Recall Workflow:`,
519
+ `1. Calculate exact dates from relative references (e.g., "4 days ago" → compute the date).`,
520
+ `2. Call \`recall\` with category \`conversations\` and include the topic + calculated date in your query.`,
521
+ `3. Present matching conversations via AskUserQuestion with date, project, and summary for each.`,
522
+ `4. After selection, offer recovery options: "Recover entire context and continue", "Continue with compacted summary", or "Other".`,
523
+ ].join(' '),
524
+
525
+ tier1: [
526
+ `You MUST call \`recall\` before responding or making any task-related tool call, including launching Task/Agent/Explore subagents. Tool discovery needed to make recall callable is allowed.`,
527
+ `The user is referencing past work, prior decisions, or historical context that your persistent memory likely holds.`,
528
+ `Search for: the specific topic mentioned, related past decisions, known issues, or prior implementations.`,
529
+ `Do NOT respond without checking memory first.`,
530
+ ].join(' '),
531
+
532
+ tier2: [
533
+ `Before responding or making any task-related tool call, including launching Task/Agent/Explore subagents, call \`recall\` for relevant context. Tool discovery needed to make recall callable is allowed.`,
534
+ `This topic likely has prior knowledge stored — past bugs, architecture decisions, or domain-specific patterns.`,
535
+ `Skip recall only if you already have full context from this session.`,
536
+ ].join(' '),
537
+
538
+ tier3: [
539
+ `Consider calling \`recall\` to check if there's relevant prior context about this topic.`,
540
+ `There may be past decisions or patterns worth reviewing before proceeding.`,
541
+ ].join(' '),
542
+
543
+ nonEnglish: [
544
+ `The user's message is in a non-English language. Mentally translate their intent to evaluate if you should call \`recall\`.`,
545
+ `Check if they are: referencing past work, asking about prior decisions, debugging an issue, or working in a domain where you have stored knowledge.`,
546
+ `If any of those apply, call \`recall\` with an ENGLISH query that captures their intent (SynaBun memories are stored in English).`,
547
+ `If it's a trivial or direct command, skip recall.`,
548
+ ].join(' '),
549
+
550
+ userLearning: [
551
+ `SynaBun User Learning: Observe HOW the user works with you and store a behavioral observation.`,
552
+ ``,
553
+ `RULES:`,
554
+ `- Category MUST be \`communication-style\` — never \`conversations\` or anything else`,
555
+ `- Content MUST describe HOW the user communicates and works — NOT what was worked on`,
556
+ `- This is NOT a session summary. Do NOT describe the task, topic, or outcome.`,
557
+ `- AVOID DUPLICATES: If an existing memory already covers the same patterns, use \`reflect\` to UPDATE it instead of creating a new one.`,
558
+ ``,
559
+ `GOOD example: "User gives multi-part requests in a single message and expects all parts addressed. Provides file paths inline rather than expecting discovery. Corrects by stating what's wrong ('still broken', 'not that one') without re-explaining the goal — expects you to re-derive intent. Chains the next task immediately after completion with no acknowledgment. Prefers options as a short list over long explanations."`,
560
+ `BAD example: "User asked about the hook system and we fixed 3 bugs." ← This is a session summary, NOT a behavioral observation.`,
561
+ `BAD example: "User uses lowercase and skips punctuation." ← Too shallow. Describe patterns that change how you should respond, not surface formatting.`,
562
+ ``,
563
+ `Steps:`,
564
+ `1. \`recall\` category \`communication-style\` — check existing entries`,
565
+ `2. If an existing entry covers similar patterns → \`reflect\` (memory_id=<full UUID>, content=updated observation merging old + new)`,
566
+ ` If NO existing entry matches → \`remember\` category \`communication-style\`, project "global", importance 5-7`,
567
+ ` Observe: instruction patterns (chained? contextual? explicit?), response expectations (code-only? options? explanations?), correction style (how they say no), expertise signals (where they need no hand-holding), frustration triggers, workflow preferences (incremental vs big-bang)`,
568
+ `Do not mention this to the user.`,
569
+ ].join('\n'),
570
+ };
571
+
572
+ // ============================================================
573
+ // LANGUAGE DETECTION
574
+ // ============================================================
575
+
576
+ /**
577
+ * Detects if the prompt is primarily non-English by checking the ratio
578
+ * of non-ASCII alphabetic characters. Tech terms (code, paths, URLs)
579
+ * are stripped first to avoid false positives from code snippets.
580
+ */
581
+ function isNonEnglish(text) {
582
+ // Strip things that look like code, paths, URLs, or technical tokens
583
+ const cleaned = text
584
+ .replace(/`[^`]*`/g, '') // inline code
585
+ .replace(/https?:\/\/\S+/g, '') // URLs
586
+ .replace(/[A-Za-z][\w./-]*\.[a-z]{1,5}/g, '') // file paths
587
+ .replace(/\b[A-Z_]{2,}\b/g, '') // CONSTANTS
588
+ .replace(/[{}()\[\];:=<>]/g, '') // syntax chars
589
+ .trim();
590
+
591
+ if (cleaned.length < 10) return false; // too short to tell
592
+
593
+ // Count characters that are alphabetic but outside basic Latin
594
+ const nonLatin = (cleaned.match(/[^\x00-\x7F\s\d]/g) || []).length;
595
+ const alpha = (cleaned.match(/[a-zA-Z]/g) || []).length;
596
+ const total = nonLatin + alpha;
597
+
598
+ if (total === 0) return false;
599
+
600
+ // If > 40% of alphabetic chars are non-Latin, it's likely non-English
601
+ return (nonLatin / total) > 0.4;
602
+ }
603
+
604
+ /**
605
+ * Checks if a Latin-script prompt looks like English by counting
606
+ * common English function words. If 2+ are found, it's English.
607
+ * This prevents the catch-all from firing on English sentences
608
+ * that simply didn't match any tier pattern.
609
+ */
610
+ const ENGLISH_FUNCTION_WORDS = /\b(the|is|are|was|were|have|has|had|will|would|can|could|should|this|that|with|from|for|not|but|and|it|to|in|on|at|of|my|your|our|we|you|they|do|did|does|get|got|set|let|if|or|an?)\b/gi;
611
+
612
+ function looksEnglish(text) {
613
+ const matches = text.match(ENGLISH_FUNCTION_WORDS) || [];
614
+ return matches.length >= 2;
615
+ }
616
+
617
+ // ============================================================
618
+ // MAIN
619
+ // ============================================================
620
+
621
+ // Max user-learning nudges per session (overridable via hook-features.json)
622
+ const USER_LEARNING_MAX_NUDGES_DEFAULT = 3;
623
+ // Human messages required between two user-learning nudges.
624
+ const USER_LEARNING_MIN_GAP = 3;
625
+
626
+ /**
627
+ * Debug logger for user-learning nudge diagnostics.
628
+ */
629
+ const UL_DEBUG = join(DATA_DIR, 'user-learning-debug.log');
630
+ function debugUL(msg) {
631
+ try { appendCapped(UL_DEBUG, `[${new Date().toISOString()}] ${msg}\n`); } catch { /* best effort */ }
632
+ }
633
+
634
+ /**
635
+ * Whether a user-learning nudge may fire at all right now, whatever decides
636
+ * it. `total` counts human messages this session (system prompts never count).
637
+ */
638
+ function userLearningGate(features, sessionId, flag) {
639
+ if (features.userLearning === false) return { ok: false, reason: 'userLearning feature disabled' };
640
+ if (!sessionId) return { ok: false, reason: 'no sessionId' };
641
+ if (!flag || typeof flag !== 'object') return { ok: false, reason: 'no flag' };
642
+ const total = flag.totalSessionMessages || flag.messageCount || 0;
643
+ const nudgeCount = flag.userLearningNudgeCount || 0;
644
+ const maxNudges = features.userLearningMaxNudges || USER_LEARNING_MAX_NUDGES_DEFAULT;
645
+ const threshold = features.userLearningThreshold || 8;
646
+ const lastAt = Number.isFinite(flag.userLearningLastNudgeAt) ? flag.userLearningLastNudgeAt : null;
647
+ const state = { total, nudgeCount, maxNudges, threshold, lastAt, sinceLast: total - (lastAt ?? 0) };
648
+ // If a style observation was already stored/updated this session, skip further nudges
649
+ if (flag.userLearningObserved) return { ok: false, reason: 'userLearningObserved=true (already stored this session)', ...state };
650
+ if (nudgeCount >= maxNudges) return { ok: false, reason: `max nudges reached (${nudgeCount} >= ${maxNudges})`, ...state };
651
+ return { ok: true, ...state };
652
+ }
653
+
654
+ /**
655
+ * Decide, record and return a user-learning nudge (call under the state lock).
656
+ * reveals === true Jev saw a working preference in this prompt: nudge when
657
+ * ≥3 human messages passed since the last nudge
658
+ * reveals === false Jev saw none: no nudge
659
+ * reveals === null not judged: nudge at threshold multiples (3, 6, 9…)
660
+ * Returns nudge text or empty string.
661
+ */
662
+ function checkUserLearning(features, sessionId, reveals = null) {
663
+ if (features.userLearning === false) {
664
+ debugUL(`SKIP: userLearning feature disabled`);
665
+ return '';
666
+ }
667
+ if (!sessionId) {
668
+ debugUL(`SKIP: no sessionId`);
669
+ return '';
670
+ }
671
+ const flagPath = join(PENDING_REMEMBER_DIR, `${sessionId}.json`);
672
+ if (!existsSync(flagPath)) {
673
+ debugUL(`SKIP: flag file not found at ${flagPath}`);
674
+ return '';
675
+ }
676
+
677
+ let flag;
678
+ try {
679
+ flag = JSON.parse(readFileSync(flagPath, 'utf-8'));
680
+ } catch (e) {
681
+ debugUL(`SKIP: failed to parse flag file: ${e.message}`);
682
+ return '';
683
+ }
684
+
685
+ const gate = userLearningGate(features, sessionId, flag);
686
+ debugUL(`CHECK: session=${sessionId.slice(0, 8)}... msgCount=${gate.total} threshold=${gate.threshold} nudgeCount=${gate.nudgeCount} maxNudges=${gate.maxNudges} observed=${!!flag.userLearningObserved} judged=${reveals}`);
687
+ if (!gate.ok) {
688
+ debugUL(`SKIP: ${gate.reason}`);
689
+ return '';
690
+ }
691
+ if (reveals === false) {
692
+ debugUL(`SKIP: judged: this prompt reveals no working preference`);
693
+ return '';
694
+ }
695
+ if (reveals === true) {
696
+ if (gate.sinceLast < USER_LEARNING_MIN_GAP) {
697
+ debugUL(`SKIP: judged preference, but only ${gate.sinceLast} messages since the last nudge`);
698
+ return '';
699
+ }
700
+ } else {
701
+ if (gate.total < gate.threshold) {
702
+ debugUL(`SKIP: msgCount ${gate.total} < threshold ${gate.threshold}`);
703
+ return '';
704
+ }
705
+ const expectedNudges = Math.floor(gate.total / gate.threshold);
706
+ if (expectedNudges <= gate.nudgeCount) {
707
+ debugUL(`SKIP: expectedNudges ${expectedNudges} <= nudgeCount ${gate.nudgeCount}`);
708
+ return '';
709
+ }
710
+ if (gate.lastAt !== null && gate.sinceLast < USER_LEARNING_MIN_GAP) {
711
+ debugUL(`SKIP: only ${gate.sinceLast} messages since the last nudge`);
712
+ return '';
713
+ }
714
+ }
715
+
716
+ debugUL(`FIRE: nudge #${gate.nudgeCount + 1} (${reveals === true ? 'judged' : 'count'})`);
717
+
718
+ // Persist nudge count + pending flag (best-effort — don't block nudge on write failure)
719
+ flag.userLearningNudgeCount = gate.nudgeCount + 1;
720
+ flag.userLearningPending = true;
721
+ flag.userLearningLastNudgeAt = gate.total;
722
+ try {
723
+ writeJsonAtomic(flagPath, flag);
724
+ debugUL(`PERSIST: nudgeCount saved as ${gate.nudgeCount + 1}`);
725
+ } catch (e) {
726
+ debugUL(`PERSIST FAILED (nudge still fires): ${e.message}`);
727
+ }
728
+
729
+ // First nudge: full instructions. Subsequent: short reminder.
730
+ const text = gate.nudgeCount === 0
731
+ ? NUDGE.userLearning
732
+ : `SynaBun User Learning reminder: You've had ${gate.total} exchanges. If you've noticed new behavioral patterns (how they give instructions, correct you, make decisions, or signal frustration), call \`recall\` category \`communication-style\` — then \`reflect\` to update an existing entry, or \`remember\` only if genuinely new. Do NOT create duplicates. Do NOT store surface-level formatting observations.`;
733
+ return reveals === true ? jev(text) : text;
734
+ }
735
+
736
+ // ============================================================
737
+ // AUTO-RECALL — Hook-side memory injection
738
+ // Calls NI server to fetch relevant memories for the user's prompt
739
+ // and formats them for injection into additionalContext.
740
+ // ============================================================
741
+
742
+ function readContextGeneration(sessionId) {
743
+ if (!sessionId || !/^[a-zA-Z0-9_-]+$/.test(sessionId)) return undefined;
744
+ try { return JSON.parse(readFileSync(join(DATA_DIR, 'memory-context', sessionId + '.json'), 'utf8')).generation; } catch { return undefined; }
745
+ }
746
+
747
+ function readFlag(sessionId) {
748
+ if (!/^[a-zA-Z0-9_-]+$/.test(sessionId || '')) return null;
749
+ try { return JSON.parse(readFileSync(join(PENDING_REMEMBER_DIR, `${sessionId}.json`), 'utf-8')); } catch { return null; }
750
+ }
751
+
752
+ // The previous human prompt, for the task-boundary question. The transcript
753
+ // may or may not already hold the current prompt, so skip an exact repeat.
754
+ function previousHumanPrompt(transcriptPath, current) {
755
+ try {
756
+ const now = String(current || '').trim();
757
+ return readRecentHumanPrompts(transcriptPath, 2).find((p) => p.trim() !== now) || '';
758
+ } catch { return ''; }
759
+ }
760
+
761
+ // Fallback for the task boundary when it was not judged: unsaved edits from
762
+ // a turn that reached Stop. When the edits are newer than the last Stop, the
763
+ // turn was interrupted (Claude Code runs no Stop after an interrupt) and this
764
+ // prompt is most likely a correction of that same work.
765
+ function stopCameAfterEdits(flag) {
766
+ const stop = Date.parse(flag?.lastStopAt || '');
767
+ const edit = Date.parse(flag?.lastEditAt || '');
768
+ return Number.isFinite(stop) && Number.isFinite(edit) && stop >= edit;
769
+ }
770
+
771
+ async function main() {
772
+ let prompt = '';
773
+ let sessionId = '';
774
+ let cwd = '';
775
+ let input = {};
776
+ try {
777
+ const raw = await readStdin();
778
+ input = JSON.parse(raw);
779
+ prompt = typeof input.prompt === 'string' ? input.prompt : '';
780
+ sessionId = input.session_id || '';
781
+ cwd = input.cwd || '';
782
+ } catch { /* proceed with empty */ }
783
+
784
+ const trimmed = prompt.trim();
785
+ const project = detectProject(cwd);
786
+
787
+ // A temporary chat (see isTemporaryChat): related memories are still read
788
+ // into the prompt. Nothing is counted, flagged, judged or gated, and the
789
+ // session is named to nobody.
790
+ if (isTemporaryChat()) { await temporaryChatPrompt(prompt, project); return; }
791
+
792
+ // Reset before greeting, trivial-message, and loop early returns.
793
+ try { startTaskTurn(input); } catch { /* gate state is best-effort */ }
794
+ if (!input.agent_id && /^[a-zA-Z0-9_-]+$/.test(sessionId) && typeof input.prompt === 'string'
795
+ && getHookFeatures().taskRecallGate !== false) _dispatchDirective = DISPATCH_DIRECTIVE;
796
+
797
+ // Session heartbeat to Neural Interface session monitor (best-effort).
798
+ // Stored so emitAndExit() can give it a short window to land — the old
799
+ // fire-and-forget version held the process open for up to 2s per prompt.
800
+ if (sessionId) {
801
+ try {
802
+ _heartbeatPromise = fetch(`${niUrl()}/api/sessions/heartbeat`, {
803
+ method: 'POST',
804
+ headers: { 'Content-Type': 'application/json' },
805
+ body: JSON.stringify({ claudeSessionId: sessionId }),
806
+ signal: AbortSignal.timeout(600),
807
+ }).catch(() => {});
808
+ } catch { /* ok */ }
809
+ }
810
+
811
+ // --- Loop marker detection (BEFORE greeting — loops must bypass greeting) ---
812
+ const terminalSessionEnv = process.env.SYNABUN_TERMINAL_SESSION || '';
813
+ if (terminalSessionEnv) {
814
+ appendLoopLog(terminalSessionEnv, 'prompt-submit:enter', 'UserPromptSubmit fired inside loop terminal', {
815
+ sessionId, isLoopMarker: /^\[SynaBun Loop\]/i.test(trimmed), promptPreview: trimmed.slice(0, 200),
816
+ });
817
+ }
818
+ if (sessionId && /^\[SynaBun Loop\]/i.test(trimmed)) {
819
+ try {
820
+ if (existsSync(LOOP_DIR)) {
821
+ const pending = readdirSync(LOOP_DIR)
822
+ .filter(f => f.startsWith('pending-') && f.endsWith('.json'));
823
+ // STRICT multi-loop isolation: only claim a pending file whose
824
+ // terminalSessionId matches our SYNABUN_TERMINAL_SESSION env.
825
+ // Legacy "match first pending" fallback was removed — it silently
826
+ // stole loops across concurrent sessions (sidepanel plan ↔ scheduled
827
+ // CLI loop in same cwd). Loop spawners MUST set the env var.
828
+ // Legacy pending files without terminalSessionId are still claimable
829
+ // when this session also has no env (ancient manual-loop compat).
830
+ let matchedPending = null;
831
+ for (const pf of pending) {
832
+ try {
833
+ const ps = JSON.parse(readFileSync(join(LOOP_DIR, pf), 'utf-8'));
834
+ if (terminalSessionEnv) {
835
+ if (ps.terminalSessionId === terminalSessionEnv) { matchedPending = pf; break; }
836
+ } else {
837
+ if (!ps.terminalSessionId) { matchedPending = pf; break; }
838
+ }
839
+ } catch { continue; }
840
+ }
841
+ if (matchedPending) {
842
+ const pendingPath = join(LOOP_DIR, matchedPending);
843
+ const targetPath = join(LOOP_DIR, `${sessionId}.json`);
844
+ renameSync(pendingPath, targetPath);
845
+ appendLoopLog(terminalSessionEnv, 'prompt-submit:claim', 'pending loop renamed to active', { from: matchedPending, to: `${sessionId}.json` });
846
+
847
+ const state = JSON.parse(readFileSync(targetPath, 'utf-8'));
848
+ delete state.pending;
849
+ // Set currentIteration to 1 immediately — closes the race window where
850
+ // another session's stop hook fallback scan (which only matches
851
+ // currentIteration === 0) could steal this loop file.
852
+ state.currentIteration = 1;
853
+ // Preserve terminalSessionId — loop driver needs it for session isolation
854
+ writeJsonAtomic(targetPath, state);
855
+ appendLoopLog(terminalSessionEnv, 'prompt-submit:inject', 'iteration 1 context built — emitting additionalContext', { task: state.task?.slice(0, 200), totalIterations: state.totalIterations, usesBrowser: !!state.usesBrowser });
856
+
857
+ const browserNote = buildBrowserNote(state);
858
+ const blockerRule = state.usesBrowser
859
+ ? '- CRITICAL: If the browser shows a login page, CAPTCHA, 2FA, or ANY wall requiring human action — STOP IMMEDIATELY. Output what the user needs to do (e.g. "Please log into Twitter in the browser panel"). Do NOT use WebSearch, WebFetch, or any workaround. Do NOT try to bypass it. Just STOP and WAIT.'
860
+ : '';
861
+ const autonomy = buildAutonomyBlock(blockerRule, sessionId);
862
+
863
+ // Extract formatting rules and place them prominently
864
+ const formattingRules = extractFormattingRules(state.task);
865
+ const fmtBlock = formattingRules
866
+ ? `\n=== FORMATTING RULES (MANDATORY \u2014 EVERY ITERATION) ===\n${formattingRules}\n=== END FORMATTING RULES ===\n`
867
+ : '';
868
+
869
+ await emitAndExit({
870
+ hookSpecificOutput: {
871
+ hookEventName: 'UserPromptSubmit',
872
+ additionalContext: `${fmtBlock}SynaBun Loop ACTIVE: ${state.totalIterations} iterations (${state.totalIterations - 1} remaining).\nTask: ${state.task}${state.context ? `\nContext: ${state.context}` : ''}${browserNote}\n\nBegin iteration 1 immediately.${autonomy}`,
873
+ },
874
+ });
875
+ return;
876
+ }
877
+
878
+ // Case 2: Subsequent iteration — find any active loop state file (after /clear)
879
+ // Note: /clear may change the session ID, so we can't match by sessionId alone.
880
+ // Scan for any active loop with currentIteration > 0.
881
+ let existingPath = join(LOOP_DIR, `${sessionId}.json`);
882
+ let state = null;
883
+ if (existsSync(existingPath)) {
884
+ try { state = JSON.parse(readFileSync(existingPath, 'utf-8')); } catch { /* skip */ }
885
+ }
886
+ // Fallback: scan loop files for an active loop (session ID may have changed after /clear).
887
+ // STRICT isolation: only match our terminal's loop.
888
+ // - env set → require exact terminalSessionId match
889
+ // - env unset → only match loops WITHOUT terminalSessionId (legacy manual loops)
890
+ // Previously the filter was disabled when env was empty, letting any
891
+ // Claude session grab any active loop in the same LOOP_DIR.
892
+ if (!state?.active || !(state?.currentIteration > 0)) {
893
+ const now = Date.now();
894
+ const allLoopFiles = readdirSync(LOOP_DIR)
895
+ .filter(f => f.endsWith('.json') && !f.startsWith('pending-'));
896
+ for (const f of allLoopFiles) {
897
+ try {
898
+ const fullPath = join(LOOP_DIR, f);
899
+ const candidate = JSON.parse(readFileSync(fullPath, 'utf-8'));
900
+ if (candidate.active && candidate.currentIteration > 0) {
901
+ // Multi-loop isolation
902
+ if (terminalSessionEnv) {
903
+ if (candidate.terminalSessionId !== terminalSessionEnv) continue;
904
+ } else {
905
+ if (candidate.terminalSessionId) continue;
906
+ }
907
+ // Validate the loop hasn't exceeded its own time cap + grace
908
+ // Skip loops inactive for >45 minutes (stuck)
909
+ const lastAct = new Date(candidate.lastIterationAt || candidate.startedAt || 0).getTime();
910
+ if (now - lastAct > 45 * 60 * 1000) continue;
911
+ state = candidate;
912
+ existingPath = fullPath;
913
+ break;
914
+ }
915
+ } catch { /* skip corrupt */ }
916
+ }
917
+ }
918
+ if (state?.active && state?.currentIteration > 0) {
919
+ appendLoopLog(terminalSessionEnv, 'prompt-submit:inject', 'subsequent iteration context built', { iter: state.currentIteration, total: state.totalIterations, usesBrowser: !!state.usesBrowser });
920
+ const formattingRules = extractFormattingRules(state.task);
921
+ const browserNote = buildBrowserNote(state);
922
+ const blockerRule2 = state.usesBrowser
923
+ ? '- CRITICAL: If the browser shows a login page, CAPTCHA, 2FA, or ANY wall requiring human action — STOP IMMEDIATELY. Output what the user needs to do. Do NOT use WebSearch, WebFetch, or any workaround. Just STOP and WAIT.'
924
+ : '';
925
+ const autonomy2 = buildAutonomyBlock(blockerRule2, sessionId);
926
+ const journal = buildJournalBlock(state);
927
+ const iterationsRemaining = (state.totalIterations || 10) - (state.currentIteration || 0);
928
+
929
+ const parts = [
930
+ // Memory rules (session-start won't re-inject after /clear)
931
+ 'SynaBun memory is active. Follow the SynaBun rules in your instructions.',
932
+ '',
933
+ ];
934
+
935
+ // Formatting rules — FIRST, most prominent position
936
+ if (formattingRules) {
937
+ parts.push(
938
+ '=== FORMATTING RULES (MANDATORY \u2014 EVERY ITERATION) ===',
939
+ formattingRules,
940
+ '=== END FORMATTING RULES ===',
941
+ '',
942
+ );
943
+ }
944
+
945
+ parts.push(
946
+ `SynaBun Loop ACTIVE: Iteration ${state.currentIteration}/${state.totalIterations} (${iterationsRemaining} remaining).`,
947
+ `Task: ${state.task}`,
948
+ );
949
+ if (state.context) parts.push(`Context: ${state.context}`);
950
+
951
+ // Journal + progress
952
+ if (journal) parts.push('', journal);
953
+
954
+ // Browser enforcement
955
+ if (browserNote) parts.push(browserNote);
956
+
957
+ parts.push(
958
+ '',
959
+ `Begin iteration ${state.currentIteration} immediately.`,
960
+ autonomy2,
961
+ );
962
+
963
+ await emitAndExit({
964
+ hookSpecificOutput: {
965
+ hookEventName: 'UserPromptSubmit',
966
+ additionalContext: parts.filter(Boolean).join('\n'),
967
+ },
968
+ });
969
+ return;
970
+ }
971
+ }
972
+ } catch { /* fall through to normal processing */ }
973
+ }
974
+
975
+ // Native sidepanel loops receive the complete iteration prompt directly from
976
+ // the server-owned SDK runtime. Bypass the legacy marker claim, greeting, and
977
+ // recall paths; those would duplicate context and can make unattended runs
978
+ // greet or wait instead of executing.
979
+ if (terminalSessionEnv && !/^\[SynaBun Loop\]/i.test(trimmed)) {
980
+ try {
981
+ const nativePath = join(LOOP_DIR, `${terminalSessionEnv}.json`);
982
+ if (existsSync(nativePath)) {
983
+ const nativeLoop = JSON.parse(readFileSync(nativePath, 'utf-8'));
984
+ if (nativeLoop.active && nativeLoop.driverType === 'native'
985
+ && nativeLoop.terminalSessionId === terminalSessionEnv) {
986
+ appendLoopLog(terminalSessionEnv, 'prompt-submit:native', 'native loop prompt accepted without legacy hook injection', {
987
+ sessionId,
988
+ iteration: nativeLoop.currentIteration || 0,
989
+ });
990
+ await emitAndExit({
991
+ hookSpecificOutput: {
992
+ hookEventName: 'UserPromptSubmit',
993
+ additionalContext: 'Native SynaBun loop: execute the complete server-provided iteration prompt immediately. Do not greet, ask questions, enter plan mode, or wait for interactive input.',
994
+ },
995
+ });
996
+ return;
997
+ }
998
+ }
999
+ } catch { /* fall through to ordinary hook behavior */ }
1000
+ }
1001
+
1002
+ // --- Who wrote this "prompt"? ---
1003
+ // Task notifications, cross-session/teammate messages, mailbox relays,
1004
+ // local-command output and continuation banners are not the human: no
1005
+ // judgment, no recall, no message count, no regex. The task turn was
1006
+ // already started above and emitAndExit still appends the dispatch
1007
+ // directive, exactly as before.
1008
+ const origin = classifyPrompt(prompt);
1009
+ if (origin.origin === 'system') {
1010
+ await emitAndExit({});
1011
+ return;
1012
+ }
1013
+ // The assistant brain recalls in-process (assistant-memory.js); running here too injected every memory twice and paid Jev twice.
1014
+ if (process.env.SYNABUN_ASSISTANT_SESSION) {
1015
+ await emitAndExit({});
1016
+ return;
1017
+ }
1018
+ // The human's own words, with IDE/hook wrappers removed.
1019
+ const text = origin.text;
1020
+
1021
+ // --- Active loop detection for non-loop-marker messages ---
1022
+ // When user sends a regular message during an active browser loop, inject
1023
+ // loop context so Claude knows where it was, and sync the session ID file.
1024
+ let activeLoopNotice = '';
1025
+ if (sessionId && !/^\[SynaBun Loop\]/i.test(trimmed)) {
1026
+ const activeLoop = findActiveLoop(sessionId);
1027
+ if (activeLoop) {
1028
+ const journal = buildJournalBlock(activeLoop);
1029
+ const iterLeft = (activeLoop.totalIterations || 10) - (activeLoop.currentIteration || 0);
1030
+ const browserNote = activeLoop.usesBrowser ? buildBrowserNote(activeLoop) : '';
1031
+ const parts = [
1032
+ `=== ACTIVE LOOP NOTICE ===`,
1033
+ `You are mid-loop: Iteration ${activeLoop.currentIteration}/${activeLoop.totalIterations} (${iterLeft} iterations remaining).`,
1034
+ `Task: ${activeLoop.task}`,
1035
+ ];
1036
+ if (activeLoop.context) parts.push(`Context: ${activeLoop.context}`);
1037
+ if (journal) parts.push('', journal);
1038
+ parts.push(
1039
+ '',
1040
+ `The user sent a message. Respond to it, then call \`loop\` action \`update\` with your current progress, then continue the loop task from where you left off.`,
1041
+ );
1042
+ if (browserNote) parts.push(browserNote);
1043
+ parts.push(`=== END LOOP NOTICE ===`);
1044
+ activeLoopNotice = parts.filter(p => p !== undefined).join('\n');
1045
+ }
1046
+ }
1047
+
1048
+ // --- Track message count (BEFORE the trivial check — every human message counts) ---
1049
+ let currentMessageCount = 0;
1050
+ let greetingContext = "";
1051
+ let flag = null; // snapshot after this message was counted
1052
+ if (/^[a-zA-Z0-9_-]+$/.test(sessionId) && text.length > 0) {
1053
+ try {
1054
+ withStateLock(() => {
1055
+ const flagPath = join(PENDING_REMEMBER_DIR, `${sessionId}.json`);
1056
+ if (!existsSync(PENDING_REMEMBER_DIR)) mkdirSync(PENDING_REMEMBER_DIR, { recursive: true });
1057
+ let current = { editCount: 0, retries: 0, files: [], messageCount: 0 };
1058
+ if (existsSync(flagPath)) {
1059
+ try { current = JSON.parse(readFileSync(flagPath, 'utf-8')); } catch { /* start fresh */ }
1060
+ }
1061
+ current.messageCount = (current.messageCount || 0) + 1;
1062
+ current.totalSessionMessages = (current.totalSessionMessages || 0) + 1;
1063
+ currentMessageCount = current.messageCount;
1064
+ if (!current.firstMessageAt) current.firstMessageAt = new Date().toISOString();
1065
+ current.lastMessageAt = new Date().toISOString();
1066
+ try { writeJsonAtomic(flagPath, current); } catch { /* ok */ }
1067
+
1068
+ // --- Greeting injection (first message only) ---
1069
+ // The full greeting directive + boot sequence is built HERE (not in session-start)
1070
+ // so it only appears in context for message 1 and never persists.
1071
+ if (current.messageCount === 1 && !current.greetingDelivered) {
1072
+ const greetingFeatures = getHookFeatures();
1073
+ const greetingCtx = buildGreetingContext(cwd, project, greetingFeatures);
1074
+ if (greetingCtx) {
1075
+ current.greetingDelivered = true;
1076
+ try { writeJsonAtomic(flagPath, current); } catch { /* ok */ }
1077
+ greetingContext = greetingCtx;
1078
+ }
1079
+ }
1080
+ flag = { ...current };
1081
+ }, { timeoutMs: 500 });
1082
+ } catch { flag = readFlag(sessionId); /* contended: judge with what is on disk */ }
1083
+ }
1084
+
1085
+ if (greetingContext) {
1086
+ await emitAndExit({ hookSpecificOutput: { hookEventName: 'UserPromptSubmit',
1087
+ additionalContext: greetingContext + '\nIf the user message is just a greeting, the greeting is your full response.' } });
1088
+ return;
1089
+ }
1090
+
1091
+ // Skip trivial messages first (fastest path). Whole-string patterns only.
1092
+ if (isTrivialPrompt(text)) {
1093
+ await emitAndExit({});
1094
+ return;
1095
+ }
1096
+
1097
+ const features = getHookFeatures();
1098
+
1099
+ // --- What to ask Jev (one request; riders only when they can matter) ---
1100
+ const editCount = Number(flag?.editCount) || 0;
1101
+ const files = Array.isArray(flag?.files) ? flag.files.filter((f) => typeof f === 'string') : [];
1102
+ const hasUnsaved = !!sessionId && editCount >= 1;
1103
+ const conversationOn = features.conversationMemory !== false;
1104
+ const ulGate = userLearningGate(features, sessionId, flag);
1105
+ const ask = {};
1106
+ if (hasUnsaved) ask.new_task = true;
1107
+ if (conversationOn) ask.past_session = true;
1108
+ if (ulGate.ok && ulGate.sinceLast >= USER_LEARNING_MIN_GAP) ask.reveals_preference = true;
1109
+ let unsavedWork;
1110
+ if (ask.new_task) {
1111
+ unsavedWork = { files: files.slice(0, 10), edit_count: editCount };
1112
+ const previous = previousHumanPrompt(input.transcript_path, text);
1113
+ if (previous) unsavedWork.previous_prompt = previous.slice(0, 1000);
1114
+ }
1115
+
1116
+ // The judgment and the ranked recall run side by side, each bounded by what
1117
+ // is left of the 3 s budget (typical ≈1 s, worst case ≈2.2 s).
1118
+ const callMs = Math.min(2000, budget.callTimeout(250));
1119
+ const judgmentRequest = hookJudge('prompt', {
1120
+ text: clipForJudge(text), project,
1121
+ session_id: sessionId || undefined, cwd: cwd || undefined,
1122
+ ask: Object.keys(ask).length ? ask : undefined,
1123
+ unsaved_work: unsavedWork,
1124
+ }, { timeoutMs: callMs });
1125
+
1126
+ // When greeting is enabled, message 1 gets recall via the boot sequence in buildGreetingContext().
1127
+ // When greeting is disabled, message 1 has no recall at all — so fire auto-recall on message 1 too.
1128
+ const greetingEnabled = features.greeting === true;
1129
+ const autoRecallMinMessage = greetingEnabled ? 2 : 1;
1130
+ const staleShown = Array.isArray(flag?.staleShown) ? flag.staleShown.filter((id) => typeof id === 'string') : [];
1131
+ const recallRequest = (!activeLoopNotice && currentMessageCount >= autoRecallMinMessage)
1132
+ ? recallMemories({
1133
+ query: clipQuery(text), project, sessionId, contextGeneration: readContextGeneration(sessionId),
1134
+ limit: 3, minScore: 0.4, tokenBudget: 600, timeoutMs: callMs, budgetMs: callMs,
1135
+ // Jev ranks the shortlist and the floor drops what it judged irrelevant.
1136
+ // Memories are never dropped here after the fact: the server has already
1137
+ // recorded them as delivered in the injection ledger.
1138
+ surface: 'rerank', floor: true,
1139
+ // Edit-time stale verdicts ride along; the ones the Stop hook already
1140
+ // showed are acknowledged instead of shown twice.
1141
+ wantStale: true, staleAck: staleShown,
1142
+ returnMeta: true,
1143
+ })
1144
+ : Promise.resolve(null);
1145
+
1146
+ const [judged, recall] = await Promise.all([judgmentRequest, recallRequest]);
1147
+
1148
+ // --- Collect primary context from priority chain ---
1149
+ let primaryContext = '';
1150
+ let primaryKind = '';
1151
+
1152
+ // Priority 0: TASK BOUNDARY — unsaved edits and a new task.
1153
+ // newTask true → nudge ([Jev]); false → none (a correction or follow-up)
1154
+ // not judged → nudge only if a Stop came after the edits
1155
+ if (hasUnsaved) {
1156
+ if (judged.newTask === true) {
1157
+ primaryContext = jev(NUDGE.pendingRemember(editCount, files));
1158
+ primaryKind = 'boundary';
1159
+ } else if (typeof judged.newTask !== 'boolean' && stopCameAfterEdits(flag)) {
1160
+ primaryContext = NUDGE.pendingRemember(editCount, files);
1161
+ primaryKind = 'boundary';
1162
+ }
1163
+ }
1164
+
1165
+ // Priority 1: Conversation recall. The judgment wins; the English-only
1166
+ // regexes are the fallback.
1167
+ if (!primaryContext && conversationOn) {
1168
+ if (typeof judged.pastSession === 'boolean') {
1169
+ if (judged.pastSession) { primaryContext = jev(NUDGE.conversation); primaryKind = 'conversation'; }
1170
+ } else if (CONVERSATION_RECALL_TRIGGERS.some(p => p.test(text))) {
1171
+ primaryContext = NUDGE.conversation;
1172
+ primaryKind = 'conversation';
1173
+ }
1174
+ }
1175
+
1176
+ // Priorities 2-6: would searching memory first change the answer?
1177
+ //
1178
+ // One judgment replaces the Tier1/2/3 regex ladder and both non-English
1179
+ // catch-alls. The ladder could only match phrasings someone thought to
1180
+ // enumerate, and the language tests were proxies for "this prompt is in a
1181
+ // language our triggers don't cover" — a judgment reads intent directly, in
1182
+ // any language, so the separate language priorities are no longer needed.
1183
+ // The regex ladder stays as the fallback when the judgment is unavailable.
1184
+ if (!primaryContext) {
1185
+ const { urgency } = judged;
1186
+ if (urgency === 'must') { primaryContext = jev(NUDGE.tier1); primaryKind = 'tier1'; }
1187
+ else if (urgency === 'should') { primaryContext = jev(NUDGE.tier2); primaryKind = 'tier2'; }
1188
+ else if (urgency === 'consider') { primaryContext = jev(NUDGE.tier3); primaryKind = 'tier3'; }
1189
+ else if (!urgency) {
1190
+ if (TIER1_TRIGGERS.some(p => p.test(text))) { primaryContext = NUDGE.tier1; primaryKind = 'tier1'; }
1191
+ else if (TIER2_TRIGGERS.some(p => p.test(text))) { primaryContext = NUDGE.tier2; primaryKind = 'tier2'; }
1192
+ else if (TIER3_TRIGGERS.filter(p => p.test(text)).length >= 2) { primaryContext = NUDGE.tier3; primaryKind = 'tier3'; }
1193
+ else if (isNonEnglish(text)) { primaryContext = NUDGE.nonEnglish; primaryKind = 'nonEnglish'; }
1194
+ else if (text.length > 30 && !looksEnglish(text)) { primaryContext = NUDGE.nonEnglish; primaryKind = 'nonEnglish'; }
1195
+ }
1196
+ }
1197
+
1198
+ // --- Auto-recall: the memories injected from the NI server ---
1199
+ const autoRecallContext = recall?.context || '';
1200
+ if (autoRecallContext && ['tier1', 'tier2', 'tier3'].includes(primaryKind)) {
1201
+ primaryContext = 'Relevant SynaBun context is available in the Related Memories block. Use it to focus your explicit task recall before composing subagent prompts, then include the exact file paths, prior fixes, and constraints in each prompt.';
1202
+ }
1203
+
1204
+ // --- Stale memories flagged by edit-time checks (not already shown at Stop) ---
1205
+ const alreadyShown = new Set(staleShown);
1206
+ const staleContext = formatStaleNotices((recall?.stale || []).filter((v) => !alreadyShown.has(v.memory_id)));
1207
+
1208
+ // --- User Learning (independent — appends to any primary context) ---
1209
+ const reveals = typeof judged.revealsPreference === 'boolean' ? judged.revealsPreference : null;
1210
+ let userLearningContext = "";
1211
+ try {
1212
+ userLearningContext = withStateLock(() => {
1213
+ // The Stop hook's stale ids were acknowledged by the recall above.
1214
+ if (recall?.responded && staleShown.length) {
1215
+ const current = readFlag(sessionId);
1216
+ if (current && Array.isArray(current.staleShown)) {
1217
+ current.staleShown = current.staleShown.filter((id) => !alreadyShown.has(id));
1218
+ try { writeJsonAtomic(join(PENDING_REMEMBER_DIR, `${sessionId}.json`), current); } catch { /* ok */ }
1219
+ }
1220
+ }
1221
+ return checkUserLearning(features, sessionId, reveals);
1222
+ }, { timeoutMs: Math.max(50, Math.min(500, budget.remaining() - 100)) });
1223
+ } catch { /* best effort */ }
1224
+
1225
+ // --- Emit combined output ---
1226
+ // NOTE: No bootCancel needed — greeting directive is only injected on message 1
1227
+ // via buildGreetingContext(), so it never persists in session context.
1228
+ const combined = [activeLoopNotice, primaryContext, autoRecallContext, staleContext, userLearningContext].filter(Boolean).join('\n\n');
1229
+
1230
+ if (combined) {
1231
+ await emitAndExit({
1232
+ hookSpecificOutput: {
1233
+ hookEventName: 'UserPromptSubmit',
1234
+ additionalContext: combined,
1235
+ },
1236
+ });
1237
+ } else {
1238
+ await emitAndExit({});
1239
+ }
1240
+ }
1241
+
1242
+ main().catch(() => {
1243
+ emitAndExit({});
1244
+ });