opensquid 0.5.432 → 0.5.447

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 (440) hide show
  1. package/README.md +1 -0
  2. package/dist/functions/arm_scope.d.ts +27 -0
  3. package/dist/functions/arm_scope.d.ts.map +1 -0
  4. package/dist/functions/arm_scope.js +52 -0
  5. package/dist/functions/arm_scope.js.map +1 -0
  6. package/dist/functions/event.d.ts.map +1 -1
  7. package/dist/functions/event.js +18 -2
  8. package/dist/functions/event.js.map +1 -1
  9. package/dist/functions/index.d.ts +4 -0
  10. package/dist/functions/index.d.ts.map +1 -1
  11. package/dist/functions/index.js +4 -0
  12. package/dist/functions/index.js.map +1 -1
  13. package/dist/functions/inject_context.d.ts +18 -0
  14. package/dist/functions/inject_context.d.ts.map +1 -0
  15. package/dist/functions/inject_context.js +16 -0
  16. package/dist/functions/inject_context.js.map +1 -0
  17. package/dist/functions/procedure_pre_inject.d.ts +23 -0
  18. package/dist/functions/procedure_pre_inject.d.ts.map +1 -0
  19. package/dist/functions/procedure_pre_inject.js +49 -0
  20. package/dist/functions/procedure_pre_inject.js.map +1 -0
  21. package/dist/functions/registry.d.ts +6 -0
  22. package/dist/functions/registry.d.ts.map +1 -1
  23. package/dist/functions/registry.js.map +1 -1
  24. package/dist/functions/rubric_pre_inject.d.ts.map +1 -1
  25. package/dist/functions/rubric_pre_inject.js +2 -1
  26. package/dist/functions/rubric_pre_inject.js.map +1 -1
  27. package/dist/functions/set_request_type.d.ts +11 -0
  28. package/dist/functions/set_request_type.d.ts.map +1 -0
  29. package/dist/functions/set_request_type.js +34 -0
  30. package/dist/functions/set_request_type.js.map +1 -0
  31. package/dist/functions/shell_parse.d.ts +1 -0
  32. package/dist/functions/shell_parse.d.ts.map +1 -1
  33. package/dist/functions/shell_parse.js +22 -4
  34. package/dist/functions/shell_parse.js.map +1 -1
  35. package/dist/packs/loader.d.ts.map +1 -1
  36. package/dist/packs/loader.js +26 -0
  37. package/dist/packs/loader.js.map +1 -1
  38. package/dist/runtime/bootstrap.d.ts.map +1 -1
  39. package/dist/runtime/bootstrap.js +6 -0
  40. package/dist/runtime/bootstrap.js.map +1 -1
  41. package/dist/runtime/fsm_state.d.ts +6 -0
  42. package/dist/runtime/fsm_state.d.ts.map +1 -1
  43. package/dist/runtime/fsm_state.js +14 -0
  44. package/dist/runtime/fsm_state.js.map +1 -1
  45. package/dist/runtime/handoff/render.d.ts +5 -4
  46. package/dist/runtime/handoff/render.d.ts.map +1 -1
  47. package/dist/runtime/handoff/render.js +7 -7
  48. package/dist/runtime/handoff/render.js.map +1 -1
  49. package/dist/runtime/handoff/stranded_scoping.d.ts +21 -0
  50. package/dist/runtime/handoff/stranded_scoping.d.ts.map +1 -0
  51. package/dist/runtime/handoff/stranded_scoping.js +39 -0
  52. package/dist/runtime/handoff/stranded_scoping.js.map +1 -0
  53. package/dist/runtime/hooks/active_task_mirror.js +0 -0
  54. package/dist/runtime/hooks/apply_patch.js +0 -0
  55. package/dist/runtime/hooks/dispatch.d.ts.map +1 -1
  56. package/dist/runtime/hooks/dispatch.js +3 -0
  57. package/dist/runtime/hooks/dispatch.js.map +1 -1
  58. package/dist/runtime/hooks/hook_output.js +0 -0
  59. package/dist/runtime/hooks/memory_reconcile.js +0 -0
  60. package/dist/runtime/hooks/new_project_detect.js +0 -0
  61. package/dist/runtime/hooks/profession_resolver.js +0 -0
  62. package/dist/runtime/hooks/scope_intent.js +0 -0
  63. package/dist/runtime/hooks/session-start.js +17 -0
  64. package/dist/runtime/hooks/session-start.js.map +1 -1
  65. package/dist/runtime/hooks/session_id.js +0 -0
  66. package/dist/runtime/hooks/session_liveness.js +0 -0
  67. package/dist/runtime/hooks/stop_drive.js +0 -0
  68. package/dist/runtime/hooks/stop_stream.js +0 -0
  69. package/dist/runtime/hooks/subagent_guard.js +0 -0
  70. package/dist/runtime/hooks/transcript.js +0 -0
  71. package/dist/runtime/hooks/transcript_tasks.js +0 -0
  72. package/dist/runtime/hooks/user-prompt-submit.d.ts.map +1 -1
  73. package/dist/runtime/hooks/user-prompt-submit.js +22 -1
  74. package/dist/runtime/hooks/user-prompt-submit.js.map +1 -1
  75. package/dist/runtime/ralph/orchestrator.d.ts.map +1 -1
  76. package/dist/runtime/ralph/orchestrator.js +2 -1
  77. package/dist/runtime/ralph/orchestrator.js.map +1 -1
  78. package/dist/runtime/request_type.d.ts +33 -0
  79. package/dist/runtime/request_type.d.ts.map +1 -0
  80. package/dist/runtime/request_type.js +38 -0
  81. package/dist/runtime/request_type.js.map +1 -0
  82. package/dist/runtime/session_state.d.ts +11 -0
  83. package/dist/runtime/session_state.d.ts.map +1 -1
  84. package/dist/runtime/session_state.js +30 -0
  85. package/dist/runtime/session_state.js.map +1 -1
  86. package/dist/runtime/types.d.ts +5 -0
  87. package/dist/runtime/types.d.ts.map +1 -1
  88. package/dist/runtime/types.js +3 -0
  89. package/dist/runtime/types.js.map +1 -1
  90. package/dist/setup/cli/limits_state.d.ts.map +1 -1
  91. package/dist/setup/cli/limits_state.js +6 -40
  92. package/dist/setup/cli/limits_state.js.map +1 -1
  93. package/dist/setup/cli/pack_walk.d.ts +32 -0
  94. package/dist/setup/cli/pack_walk.d.ts.map +1 -0
  95. package/dist/setup/cli/pack_walk.js +76 -0
  96. package/dist/setup/cli/pack_walk.js.map +1 -0
  97. package/dist/setup/cli/permissions_state.d.ts.map +1 -1
  98. package/dist/setup/cli/permissions_state.js +6 -37
  99. package/dist/setup/cli/permissions_state.js.map +1 -1
  100. package/dist/setup/cli/triggers_state.d.ts.map +1 -1
  101. package/dist/setup/cli/triggers_state.js +3 -29
  102. package/dist/setup/cli/triggers_state.js.map +1 -1
  103. package/dist/workgraph/events.d.ts.map +1 -1
  104. package/dist/workgraph/events.js +10 -0
  105. package/dist/workgraph/events.js.map +1 -1
  106. package/dist/workgraph/store.d.ts.map +1 -1
  107. package/dist/workgraph/store.js +5 -0
  108. package/dist/workgraph/store.js.map +1 -1
  109. package/dist/workgraph/types.d.ts +2 -1
  110. package/dist/workgraph/types.d.ts.map +1 -1
  111. package/docs/ARCHITECTURE.md +268 -0
  112. package/docs/pack-runtime.md +26 -10
  113. package/package.json +5 -3
  114. package/packs/builtin/coding-flow/procedure.md +43 -0
  115. package/packs/builtin/coding-flow/skills/entry-and-handoffs/skill.yaml +49 -6
  116. package/packs/builtin/coding-flow/skills/pause-stop-guard/skill.yaml +11 -1
  117. package/packs/builtin/default-discipline/manifest.yaml +15 -6
  118. package/packs/builtin/pack-architect/skills/skill-yaml-author-walkthrough/skill.yaml +8 -0
  119. package/dist/anti-drift/evaluator.d.ts +0 -88
  120. package/dist/anti-drift/evaluator.d.ts.map +0 -1
  121. package/dist/anti-drift/evaluator.js +0 -417
  122. package/dist/anti-drift/evaluator.js.map +0 -1
  123. package/dist/anti-drift/evaluator.test.js +0 -78
  124. package/dist/anti-drift/rules.d.ts +0 -80
  125. package/dist/anti-drift/rules.d.ts.map +0 -1
  126. package/dist/anti-drift/rules.js +0 -368
  127. package/dist/anti-drift/rules.js.map +0 -1
  128. package/dist/anti-drift/rules.test.js +0 -213
  129. package/dist/anti-drift/state.d.ts +0 -107
  130. package/dist/anti-drift/state.d.ts.map +0 -1
  131. package/dist/anti-drift/state.js +0 -177
  132. package/dist/anti-drift/state.js.map +0 -1
  133. package/dist/anti-drift/state.test.js +0 -120
  134. package/dist/chat/adapters/discord.d.ts +0 -41
  135. package/dist/chat/adapters/discord.d.ts.map +0 -1
  136. package/dist/chat/adapters/discord.js +0 -176
  137. package/dist/chat/adapters/discord.js.map +0 -1
  138. package/dist/chat/adapters/discord.test.js +0 -25
  139. package/dist/chat/adapters/slack.d.ts +0 -43
  140. package/dist/chat/adapters/slack.d.ts.map +0 -1
  141. package/dist/chat/adapters/slack.js +0 -172
  142. package/dist/chat/adapters/slack.js.map +0 -1
  143. package/dist/chat/adapters/slack.test.js +0 -30
  144. package/dist/chat/adapters/telegram.d.ts +0 -148
  145. package/dist/chat/adapters/telegram.d.ts.map +0 -1
  146. package/dist/chat/adapters/telegram.js +0 -498
  147. package/dist/chat/adapters/telegram.js.map +0 -1
  148. package/dist/chat/adapters/telegram.test.js +0 -94
  149. package/dist/chat/config.d.ts +0 -98
  150. package/dist/chat/config.d.ts.map +0 -1
  151. package/dist/chat/config.js +0 -185
  152. package/dist/chat/config.js.map +0 -1
  153. package/dist/chat/daemon/active-project.d.ts +0 -17
  154. package/dist/chat/daemon/active-project.d.ts.map +0 -1
  155. package/dist/chat/daemon/active-project.js +0 -23
  156. package/dist/chat/daemon/active-project.js.map +0 -1
  157. package/dist/chat/daemon/autospawn.d.ts +0 -40
  158. package/dist/chat/daemon/autospawn.d.ts.map +0 -1
  159. package/dist/chat/daemon/autospawn.js +0 -129
  160. package/dist/chat/daemon/autospawn.js.map +0 -1
  161. package/dist/chat/daemon/autospawn.test.js +0 -112
  162. package/dist/chat/daemon/cli.d.ts +0 -18
  163. package/dist/chat/daemon/cli.d.ts.map +0 -1
  164. package/dist/chat/daemon/cli.js +0 -71
  165. package/dist/chat/daemon/cli.js.map +0 -1
  166. package/dist/chat/daemon/collisions.js +0 -384
  167. package/dist/chat/daemon/health-check.d.ts +0 -69
  168. package/dist/chat/daemon/health-check.d.ts.map +0 -1
  169. package/dist/chat/daemon/health-check.js +0 -112
  170. package/dist/chat/daemon/health-check.js.map +0 -1
  171. package/dist/chat/daemon/inbox-read.d.ts +0 -35
  172. package/dist/chat/daemon/inbox-read.d.ts.map +0 -1
  173. package/dist/chat/daemon/inbox-read.js +0 -75
  174. package/dist/chat/daemon/inbox-read.js.map +0 -1
  175. package/dist/chat/daemon/inbox-read.test.js +0 -97
  176. package/dist/chat/daemon/inbox.d.ts +0 -63
  177. package/dist/chat/daemon/inbox.d.ts.map +0 -1
  178. package/dist/chat/daemon/inbox.js +0 -56
  179. package/dist/chat/daemon/inbox.js.map +0 -1
  180. package/dist/chat/daemon/inbox.test.js +0 -110
  181. package/dist/chat/daemon/lifecycle.d.ts +0 -71
  182. package/dist/chat/daemon/lifecycle.d.ts.map +0 -1
  183. package/dist/chat/daemon/lifecycle.js +0 -221
  184. package/dist/chat/daemon/lifecycle.js.map +0 -1
  185. package/dist/chat/daemon/lifecycle.test.js +0 -163
  186. package/dist/chat/daemon/protocol.d.ts +0 -107
  187. package/dist/chat/daemon/protocol.d.ts.map +0 -1
  188. package/dist/chat/daemon/protocol.js +0 -54
  189. package/dist/chat/daemon/protocol.js.map +0 -1
  190. package/dist/chat/daemon/routing.d.ts +0 -140
  191. package/dist/chat/daemon/routing.d.ts.map +0 -1
  192. package/dist/chat/daemon/routing.js +0 -198
  193. package/dist/chat/daemon/routing.js.map +0 -1
  194. package/dist/chat/daemon/routing.test.js +0 -259
  195. package/dist/chat/daemon/rpc-client.d.ts +0 -45
  196. package/dist/chat/daemon/rpc-client.d.ts.map +0 -1
  197. package/dist/chat/daemon/rpc-client.js +0 -133
  198. package/dist/chat/daemon/rpc-client.js.map +0 -1
  199. package/dist/chat/daemon/rpc-server.d.ts +0 -39
  200. package/dist/chat/daemon/rpc-server.d.ts.map +0 -1
  201. package/dist/chat/daemon/rpc-server.js +0 -385
  202. package/dist/chat/daemon/rpc-server.js.map +0 -1
  203. package/dist/chat/daemon/rpc.test.js +0 -177
  204. package/dist/chat/daemon/subscribers.js +0 -257
  205. package/dist/chat/daemon/worker.d.ts +0 -27
  206. package/dist/chat/daemon/worker.d.ts.map +0 -1
  207. package/dist/chat/daemon/worker.js +0 -313
  208. package/dist/chat/daemon/worker.js.map +0 -1
  209. package/dist/chat/daemon/workspace-topic.js +0 -324
  210. package/dist/chat/env-token.d.ts +0 -60
  211. package/dist/chat/env-token.d.ts.map +0 -1
  212. package/dist/chat/env-token.js +0 -137
  213. package/dist/chat/env-token.js.map +0 -1
  214. package/dist/chat/env-token.test.js +0 -160
  215. package/dist/chat/factory.d.ts +0 -30
  216. package/dist/chat/factory.d.ts.map +0 -1
  217. package/dist/chat/factory.js +0 -50
  218. package/dist/chat/factory.js.map +0 -1
  219. package/dist/chat/factory.test.js +0 -55
  220. package/dist/chat/gateway.d.ts +0 -176
  221. package/dist/chat/gateway.d.ts.map +0 -1
  222. package/dist/chat/gateway.js +0 -146
  223. package/dist/chat/gateway.js.map +0 -1
  224. package/dist/chat/gateway.test.js +0 -192
  225. package/dist/claude-md.d.ts +0 -39
  226. package/dist/claude-md.d.ts.map +0 -1
  227. package/dist/claude-md.js +0 -113
  228. package/dist/claude-md.js.map +0 -1
  229. package/dist/claude-md.test.js +0 -91
  230. package/dist/codex/activate.d.ts +0 -66
  231. package/dist/codex/activate.d.ts.map +0 -1
  232. package/dist/codex/activate.js +0 -329
  233. package/dist/codex/activate.js.map +0 -1
  234. package/dist/codex/activate.test.js +0 -229
  235. package/dist/codex/bundled-default/bundled-default.test.js +0 -161
  236. package/dist/codex/cli-publish.test.js +0 -133
  237. package/dist/codex/cli.d.ts +0 -35
  238. package/dist/codex/cli.d.ts.map +0 -1
  239. package/dist/codex/cli.js +0 -554
  240. package/dist/codex/cli.js.map +0 -1
  241. package/dist/codex/cli.test.js +0 -277
  242. package/dist/codex/import-skill-md.d.ts +0 -53
  243. package/dist/codex/import-skill-md.d.ts.map +0 -1
  244. package/dist/codex/import-skill-md.js +0 -236
  245. package/dist/codex/import-skill-md.js.map +0 -1
  246. package/dist/codex/import-skill-md.test.js +0 -225
  247. package/dist/codex/loader.d.ts +0 -27
  248. package/dist/codex/loader.d.ts.map +0 -1
  249. package/dist/codex/loader.js +0 -86
  250. package/dist/codex/loader.js.map +0 -1
  251. package/dist/codex/loader.test.js +0 -75
  252. package/dist/codex/parse.d.ts +0 -28
  253. package/dist/codex/parse.d.ts.map +0 -1
  254. package/dist/codex/parse.js +0 -309
  255. package/dist/codex/parse.js.map +0 -1
  256. package/dist/codex/parse.test.js +0 -241
  257. package/dist/codex/store.d.ts +0 -87
  258. package/dist/codex/store.d.ts.map +0 -1
  259. package/dist/codex/store.js +0 -205
  260. package/dist/codex/store.js.map +0 -1
  261. package/dist/codex/store.test.js +0 -242
  262. package/dist/codex/types.d.ts +0 -398
  263. package/dist/codex/types.d.ts.map +0 -1
  264. package/dist/codex/types.js +0 -21
  265. package/dist/codex/types.js.map +0 -1
  266. package/dist/config.d.ts +0 -53
  267. package/dist/config.d.ts.map +0 -1
  268. package/dist/config.js +0 -202
  269. package/dist/config.js.map +0 -1
  270. package/dist/config.test.js +0 -117
  271. package/dist/engine/cli.d.ts +0 -14
  272. package/dist/engine/cli.d.ts.map +0 -1
  273. package/dist/engine/cli.js +0 -171
  274. package/dist/engine/cli.js.map +0 -1
  275. package/dist/engine/client.d.ts +0 -219
  276. package/dist/engine/client.d.ts.map +0 -1
  277. package/dist/engine/client.js +0 -312
  278. package/dist/engine/client.js.map +0 -1
  279. package/dist/engine/config.d.ts +0 -62
  280. package/dist/engine/config.d.ts.map +0 -1
  281. package/dist/engine/config.js +0 -223
  282. package/dist/engine/config.js.map +0 -1
  283. package/dist/engine/index.d.ts +0 -17
  284. package/dist/engine/index.d.ts.map +0 -1
  285. package/dist/engine/index.js +0 -16
  286. package/dist/engine/index.js.map +0 -1
  287. package/dist/engine/resolver.d.ts +0 -62
  288. package/dist/engine/resolver.d.ts.map +0 -1
  289. package/dist/engine/resolver.js +0 -103
  290. package/dist/engine/resolver.js.map +0 -1
  291. package/dist/engine/singleton.d.ts +0 -95
  292. package/dist/engine/singleton.d.ts.map +0 -1
  293. package/dist/engine/singleton.js +0 -325
  294. package/dist/engine/singleton.js.map +0 -1
  295. package/dist/engine/types.d.ts +0 -402
  296. package/dist/engine/types.d.ts.map +0 -1
  297. package/dist/engine/types.js +0 -22
  298. package/dist/engine/types.js.map +0 -1
  299. package/dist/engine-binary-resolver.js +0 -110
  300. package/dist/engine-binary-resolver.test.js +0 -61
  301. package/dist/engine-cli.js +0 -60
  302. package/dist/engine-client.js +0 -301
  303. package/dist/engine-client.test.js +0 -118
  304. package/dist/functions/chain_state.d.ts +0 -51
  305. package/dist/functions/chain_state.d.ts.map +0 -1
  306. package/dist/functions/chain_state.js +0 -59
  307. package/dist/functions/chain_state.js.map +0 -1
  308. package/dist/hooks/drift-catalog.d.ts +0 -68
  309. package/dist/hooks/drift-catalog.d.ts.map +0 -1
  310. package/dist/hooks/drift-catalog.js +0 -184
  311. package/dist/hooks/drift-catalog.js.map +0 -1
  312. package/dist/hooks/drift-catalog.test.js +0 -154
  313. package/dist/hooks/drift-patterns.d.ts +0 -110
  314. package/dist/hooks/drift-patterns.d.ts.map +0 -1
  315. package/dist/hooks/drift-patterns.js +0 -289
  316. package/dist/hooks/drift-patterns.js.map +0 -1
  317. package/dist/hooks/drift-patterns.test.js +0 -325
  318. package/dist/hooks/engine-vocab-gate.d.ts +0 -108
  319. package/dist/hooks/engine-vocab-gate.d.ts.map +0 -1
  320. package/dist/hooks/engine-vocab-gate.js +0 -225
  321. package/dist/hooks/engine-vocab-gate.js.map +0 -1
  322. package/dist/hooks/engine-vocab-gate.test.js +0 -170
  323. package/dist/hooks/heartbeat.d.ts +0 -107
  324. package/dist/hooks/heartbeat.d.ts.map +0 -1
  325. package/dist/hooks/heartbeat.js +0 -316
  326. package/dist/hooks/heartbeat.js.map +0 -1
  327. package/dist/hooks/heartbeat.test.js +0 -393
  328. package/dist/hooks/honesty-ledger-session-scope.test.js +0 -100
  329. package/dist/hooks/honesty-ledger.d.ts +0 -123
  330. package/dist/hooks/honesty-ledger.d.ts.map +0 -1
  331. package/dist/hooks/honesty-ledger.js +0 -226
  332. package/dist/hooks/honesty-ledger.js.map +0 -1
  333. package/dist/hooks/honesty-ledger.test.js +0 -466
  334. package/dist/hooks/inline-report-check.d.ts +0 -63
  335. package/dist/hooks/inline-report-check.d.ts.map +0 -1
  336. package/dist/hooks/inline-report-check.js +0 -88
  337. package/dist/hooks/inline-report-check.js.map +0 -1
  338. package/dist/hooks/inline-report-check.test.js +0 -96
  339. package/dist/hooks/pre-tool-use.d.ts +0 -62
  340. package/dist/hooks/pre-tool-use.d.ts.map +0 -1
  341. package/dist/hooks/pre-tool-use.js +0 -342
  342. package/dist/hooks/pre-tool-use.js.map +0 -1
  343. package/dist/hooks/pre-tool-use.test.js +0 -134
  344. package/dist/hooks/session-end.d.ts +0 -15
  345. package/dist/hooks/session-end.d.ts.map +0 -1
  346. package/dist/hooks/session-end.js +0 -60
  347. package/dist/hooks/session-end.js.map +0 -1
  348. package/dist/hooks/session-end.test.js +0 -52
  349. package/dist/hooks/stop.d.ts +0 -35
  350. package/dist/hooks/stop.d.ts.map +0 -1
  351. package/dist/hooks/stop.js +0 -136
  352. package/dist/hooks/stop.js.map +0 -1
  353. package/dist/hooks/transcript-active-task.test.js +0 -342
  354. package/dist/hooks/transcript.d.ts +0 -26
  355. package/dist/hooks/transcript.d.ts.map +0 -1
  356. package/dist/hooks/transcript.js +0 -266
  357. package/dist/hooks/transcript.js.map +0 -1
  358. package/dist/hooks/transcript.test.js +0 -103
  359. package/dist/hooks/user-prompt-submit.d.ts +0 -74
  360. package/dist/hooks/user-prompt-submit.d.ts.map +0 -1
  361. package/dist/hooks/user-prompt-submit.js +0 -256
  362. package/dist/hooks/user-prompt-submit.js.map +0 -1
  363. package/dist/hooks/user-prompt-submit.test.js +0 -118
  364. package/dist/hooks/versioning-gate.d.ts +0 -101
  365. package/dist/hooks/versioning-gate.d.ts.map +0 -1
  366. package/dist/hooks/versioning-gate.js +0 -245
  367. package/dist/hooks/versioning-gate.js.map +0 -1
  368. package/dist/hooks/versioning-gate.test.js +0 -368
  369. package/dist/hooks/workflow-gate.d.ts +0 -64
  370. package/dist/hooks/workflow-gate.d.ts.map +0 -1
  371. package/dist/hooks/workflow-gate.js +0 -152
  372. package/dist/hooks/workflow-gate.js.map +0 -1
  373. package/dist/hooks/workflow-gate.test.js +0 -197
  374. package/dist/hooks-cli.d.ts +0 -25
  375. package/dist/hooks-cli.d.ts.map +0 -1
  376. package/dist/hooks-cli.js +0 -286
  377. package/dist/hooks-cli.js.map +0 -1
  378. package/dist/hooks-cli.test.js +0 -148
  379. package/dist/origin.d.ts +0 -16
  380. package/dist/origin.d.ts.map +0 -1
  381. package/dist/origin.js +0 -92
  382. package/dist/origin.js.map +0 -1
  383. package/dist/packs/seed_lessons_ingest.d.ts +0 -30
  384. package/dist/packs/seed_lessons_ingest.d.ts.map +0 -1
  385. package/dist/packs/seed_lessons_ingest.js +0 -107
  386. package/dist/packs/seed_lessons_ingest.js.map +0 -1
  387. package/dist/project-cli.d.ts +0 -7
  388. package/dist/project-cli.d.ts.map +0 -1
  389. package/dist/project-cli.js +0 -145
  390. package/dist/project-cli.js.map +0 -1
  391. package/dist/project.d.ts +0 -127
  392. package/dist/project.d.ts.map +0 -1
  393. package/dist/project.js +0 -281
  394. package/dist/project.js.map +0 -1
  395. package/dist/project.test.js +0 -287
  396. package/dist/rag/backends/loop_engine.d.ts +0 -61
  397. package/dist/rag/backends/loop_engine.d.ts.map +0 -1
  398. package/dist/rag/backends/loop_engine.js +0 -160
  399. package/dist/rag/backends/loop_engine.js.map +0 -1
  400. package/dist/recall.d.ts +0 -82
  401. package/dist/recall.d.ts.map +0 -1
  402. package/dist/recall.js +0 -81
  403. package/dist/recall.js.map +0 -1
  404. package/dist/runtime/agent_bridge/autospawn.d.ts +0 -131
  405. package/dist/runtime/agent_bridge/autospawn.d.ts.map +0 -1
  406. package/dist/runtime/agent_bridge/autospawn.js +0 -251
  407. package/dist/runtime/agent_bridge/autospawn.js.map +0 -1
  408. package/dist/runtime/chain_state.d.ts +0 -124
  409. package/dist/runtime/chain_state.d.ts.map +0 -1
  410. package/dist/runtime/chain_state.js +0 -189
  411. package/dist/runtime/chain_state.js.map +0 -1
  412. package/dist/runtime/hooks/permission_decision.d.ts +0 -34
  413. package/dist/runtime/hooks/permission_decision.d.ts.map +0 -1
  414. package/dist/runtime/hooks/permission_decision.js +0 -39
  415. package/dist/runtime/hooks/permission_decision.js.map +0 -1
  416. package/dist/runtime/workflow_fsm.d.ts +0 -21
  417. package/dist/runtime/workflow_fsm.d.ts.map +0 -1
  418. package/dist/runtime/workflow_fsm.js +0 -25
  419. package/dist/runtime/workflow_fsm.js.map +0 -1
  420. package/dist/runtime/workflow_map.d.ts +0 -26
  421. package/dist/runtime/workflow_map.d.ts.map +0 -1
  422. package/dist/runtime/workflow_map.js +0 -38
  423. package/dist/runtime/workflow_map.js.map +0 -1
  424. package/dist/scope.d.ts +0 -48
  425. package/dist/scope.d.ts.map +0 -1
  426. package/dist/scope.js +0 -111
  427. package/dist/scope.js.map +0 -1
  428. package/dist/setup/cli/topic_create_step.d.ts +0 -84
  429. package/dist/setup/cli/topic_create_step.d.ts.map +0 -1
  430. package/dist/setup/cli/topic_create_step.js +0 -213
  431. package/dist/setup/cli/topic_create_step.js.map +0 -1
  432. package/dist/system-export.d.ts +0 -65
  433. package/dist/system-export.d.ts.map +0 -1
  434. package/dist/system-export.js +0 -194
  435. package/dist/system-export.js.map +0 -1
  436. package/dist/utterance/classifier.d.ts +0 -53
  437. package/dist/utterance/classifier.d.ts.map +0 -1
  438. package/dist/utterance/classifier.js +0 -184
  439. package/dist/utterance/classifier.js.map +0 -1
  440. package/dist/utterance/classifier.test.js +0 -147
@@ -1,313 +0,0 @@
1
- /**
2
- * Chat-daemon worker entrypoint (v0.7.1 Phase A).
3
- *
4
- * Spawned as a detached child by `lifecycle.startDaemon()`. Owns the
5
- * single long-poll connection per chat platform. The MCP server side
6
- * stays out of the polling business entirely — outbound RPC (Phase B)
7
- * and inbox tailing (Phase C) replace the in-process gateway.
8
- *
9
- * Lifecycle inside the worker:
10
- * 1. Write our PID to ~/.opensquid/chat-daemon.pid
11
- * 2. Build the chat gateway from ~/.opensquid/config.json
12
- * 3. Start every configured adapter (their long-poll loops run as
13
- * side effects of start())
14
- * 4. Install SIGTERM / SIGINT handlers that stop the gateway and
15
- * remove the pidfile before exit
16
- * 5. Park on process.stdin (which is /dev/null in detached mode)
17
- * so the event loop stays alive
18
- *
19
- * Crash behavior: any unhandled exception from gateway.start() prints
20
- * to the (parent-redirected) log file and exits non-zero. The pidfile
21
- * is cleaned up in the SIGTERM handler — if we crash before installing
22
- * it, the pidfile may linger, and the next `status` call will report
23
- * `stale_pid` (lifecycle.startDaemon cleans up stale pidfiles before
24
- * spawning).
25
- */
26
- import { promises as fs } from "node:fs";
27
- import { buildChatGateway } from "../factory.js";
28
- import { appendToInbox } from "./inbox.js";
29
- import { daemonPaths } from "./lifecycle.js";
30
- import { buildRoutingIndex, loadAllProjectChatRouting } from "./routing.js";
31
- import { RpcServer } from "./rpc-server.js";
32
- let gateway = null;
33
- let rpcServer = null;
34
- let routingIndex = new Map();
35
- let routingPollTimer = null;
36
- let pidFile = null;
37
- let shuttingDown = false;
38
- export async function runDaemonWorker(dataRoot) {
39
- const paths = daemonPaths(dataRoot);
40
- pidFile = paths.pidFile;
41
- // Write pidfile FIRST so a status check after spawn sees the worker
42
- // promptly. Truncate-write is the right semantic — any previous
43
- // pidfile is stale by definition (we already verified no live daemon
44
- // existed in lifecycle.startDaemon).
45
- await fs.writeFile(pidFile, `${process.pid}\n`, "utf8");
46
- log(`[chat-daemon] worker booted pid=${process.pid} cwd=${process.cwd()}`);
47
- // Build + start the gateway. If config is empty, no adapters
48
- // activate and the daemon parks idle — useful for testing the
49
- // lifecycle without configuring a real bot token.
50
- try {
51
- // 0.7.5 (#148): log which source each platform's token came from
52
- // (env / env-file / config-json) so operators can debug "which
53
- // bot is this daemon actually using" without exposing the secret.
54
- try {
55
- const { loadChatConfigWithSources } = await import("../config.js");
56
- const { sources } = await loadChatConfigWithSources(dataRoot);
57
- const lines = [];
58
- if (sources.telegram)
59
- lines.push(`telegram=${sources.telegram}`);
60
- if (sources.discord)
61
- lines.push(`discord=${sources.discord}`);
62
- if (sources.slack_bot)
63
- lines.push(`slack_bot=${sources.slack_bot}`);
64
- if (sources.slack_app)
65
- lines.push(`slack_app=${sources.slack_app}`);
66
- if (lines.length) {
67
- log(`[chat-daemon] token sources: ${lines.join(" ")}${sources.env_file_path ? ` (env-file: ${sources.env_file_path})` : ""}`);
68
- }
69
- }
70
- catch (logErr) {
71
- log(`[chat-daemon] could not log token sources (non-fatal): ${logErr instanceof Error ? logErr.message : logErr}`);
72
- }
73
- const built = await buildChatGateway({ dataRoot });
74
- gateway = built.gateway;
75
- log(`[chat-daemon] activating platforms: ${built.activated.join(",") || "(none)"}`);
76
- if (built.issues.length) {
77
- for (const i of built.issues) {
78
- log(`[chat-daemon] config issue ${i.platform}.${i.field}: ${i.problem}`);
79
- }
80
- }
81
- // Phase C: load per-project chat-routing.json files and build the
82
- // chat_id → project_uuid index BEFORE attaching the inbound
83
- // handler. The handler uses this index to route messages to
84
- // per-project inboxes.
85
- routingIndex = await rebuildRoutingIndex(dataRoot);
86
- log(`[chat-daemon] routing index built: ${routingIndex.size} inbound channels mapped`);
87
- gateway.onMessage(async (msg) => {
88
- // v0.5.94 (WAB.2 Part A / TG.1 (a)): DM-first routing.
89
- //
90
- // Key precedence:
91
- // 1. DM key (`telegram:dm:<user_id>`) when the message is a DM —
92
- // defined as `msg.channel === "telegram:" + msg.senderId`.
93
- // The Telegram adapter formats DM chats as `telegram:<chat_id>`
94
- // where chat_id === from.id, so the equality check is the
95
- // canonical Telegram private-chat indicator. This also rejects
96
- // group-message-from-self spoofs (group chat.id is negative
97
- // for supergroups; senderId is positive; they cannot collide).
98
- // 2. Topic-specific key (`<channel>:<threadId>`) when the message
99
- // is in a forum topic.
100
- // 3. Channel-only key (`<channel>`).
101
- //
102
- // Strict topic whitelist preserved per TG.1 (d) — a message in a
103
- // topic NOT listed by any project's `inbound_topic_ids` will not
104
- // fall back to the chat-only key (because `collectInboundChannels`
105
- // does not emit the chat-only key when `inbound_topic_ids` is set
106
- // on that project). Such messages orphan, which is the documented
107
- // security-correct default.
108
- const isDm = msg.platform === 'telegram' && msg.channel === `telegram:${msg.senderId}`;
109
- const dmKey = isDm ? `telegram:dm:${msg.senderId}` : null;
110
- const topicKey = msg.threadId ? `${msg.channel}:${msg.threadId}` : null;
111
- const projectUuid = (dmKey ? routingIndex.get(dmKey) : undefined) ??
112
- (topicKey ? routingIndex.get(topicKey) : undefined) ??
113
- routingIndex.get(msg.channel) ??
114
- null;
115
- try {
116
- const r = await appendToInbox(msg, projectUuid, dataRoot);
117
- log(`[chat-daemon] inbox ← ${msg.channel} (${msg.text.slice(0, 60).replace(/\n/g, " ")}…) → ${r.destination}${r.project_uuid ? "/" + r.project_uuid : ""}`);
118
- }
119
- catch (err) {
120
- log(`[chat-daemon] inbox append failed for ${msg.channel}: ${err instanceof Error ? err.message : err}`);
121
- }
122
- // TPS.6 patch 2 (v0.5.126) — broadcast to long-lived UDS
123
- // subscribers. The JSONL file write above is the durable record;
124
- // this push is the low-latency delivery. We broadcast on a
125
- // single key — the most-specific one available: topic-suffixed
126
- // `<channel>:<threadId>` if present, else bare `<channel>`.
127
- // This mirrors the routing-index key semantics from
128
- // `collectInboundChannels`: a project that registers only
129
- // `chat_id:thread_id` doesn't want to receive messages from
130
- // other topics in the same supergroup. Wildcard subscribers
131
- // (chat_ids=[]) see every message regardless of key shape.
132
- // Fire-and-forget — subscriber write failures are handled by
133
- // the registry's own socket lifecycle hooks.
134
- if (rpcServer !== null) {
135
- const broadcastKey = msg.threadId
136
- ? `${msg.channel}:${msg.threadId}`
137
- : msg.channel;
138
- const notif = {
139
- jsonrpc: "2.0",
140
- method: "inbound_message",
141
- params: {
142
- delivery_id: `del-${Date.now().toString()}-${Math.random().toString(36).slice(2, 10)}`,
143
- message_id: msg.id,
144
- platform: msg.platform,
145
- channel: msg.channel,
146
- ...(msg.threadId !== undefined ? { thread_id: msg.threadId } : {}),
147
- sender: msg.sender,
148
- sender_id: msg.senderId,
149
- text: msg.text,
150
- received_at: msg.receivedAt.toISOString(),
151
- mentions_bot: msg.mentionsBot,
152
- },
153
- };
154
- rpcServer.subscribers.broadcast(broadcastKey, notif);
155
- }
156
- });
157
- await gateway.start();
158
- log(`[chat-daemon] gateway start complete`);
159
- // v0.5.89 (TG.2) — startup reachability check against each
160
- // unique inbound chat_id. Catches kicked-from-supergroup (403),
161
- // stale chat_id (400), and network errors immediately at startup.
162
- // Best-effort: failures don't block daemon startup, just log
163
- // warnings so operators can see what's wrong without inspecting
164
- // hours of empty log noise. The check is Telegram-only for now;
165
- // Discord/Slack equivalents land in a follow-up if needed.
166
- try {
167
- const { verifyTelegramChats, formatReachabilityLine } = await import("./health-check.js");
168
- const { loadChatConfig } = await import("../config.js");
169
- const chatConfig = await loadChatConfig(dataRoot);
170
- const tgToken = chatConfig.telegram?.bot_token;
171
- // Collect unique chat_ids referenced across all projects' Telegram
172
- // routing (skip dm: and topic-suffixed keys — getChat takes only
173
- // the chat_id, no topic).
174
- const chatIds = new Set();
175
- const configs = await loadAllProjectChatRouting(dataRoot);
176
- for (const cfg of configs.values()) {
177
- for (const id of cfg.telegram?.inbound_chat_ids ?? [])
178
- chatIds.add(id);
179
- }
180
- if (chatIds.size > 0 && tgToken) {
181
- const results = await verifyTelegramChats(tgToken, [...chatIds]);
182
- for (const r of results)
183
- log(formatReachabilityLine(r));
184
- }
185
- }
186
- catch (err) {
187
- log(`[chat-daemon] chat-reachability check skipped (non-fatal): ${err instanceof Error ? err.message : err}`);
188
- }
189
- // RPC server listens for outbound send() calls from per-project
190
- // MCP servers. Starting it AFTER gateway.start() means clients
191
- // that connect successfully are guaranteed a fully-warmed gateway.
192
- rpcServer = new RpcServer({ gateway, dataRoot, version: "v0.7.1-phase-c" });
193
- await rpcServer.listen();
194
- // Poll the routing files every 30s so operators can edit a
195
- // chat-routing.json and have the daemon pick it up without a full
196
- // restart. Polling is the most portable option (fs.watch behavior
197
- // varies across macOS/Linux/Windows + recursive support).
198
- routingPollTimer = setInterval(() => {
199
- void (async () => {
200
- try {
201
- const next = await rebuildRoutingIndex(dataRoot);
202
- if (!sameIndex(routingIndex, next)) {
203
- routingIndex = next;
204
- log(`[chat-daemon] routing reload: ${routingIndex.size} inbound channels mapped`);
205
- }
206
- }
207
- catch (err) {
208
- log(`[chat-daemon] routing reload failed (non-fatal): ${err instanceof Error ? err.message : err}`);
209
- }
210
- })();
211
- }, 30_000);
212
- log(`[chat-daemon] rpc server listening; entering park loop`);
213
- }
214
- catch (err) {
215
- log(`[chat-daemon] FATAL: gateway start failed: ${err instanceof Error ? err.stack : err}`);
216
- await cleanup();
217
- process.exit(1);
218
- }
219
- // Signal handlers. SIGTERM = graceful, SIGINT = also graceful (for
220
- // manual `kill` during dev). Each calls cleanup() exactly once.
221
- process.on("SIGTERM", () => void shutdown("SIGTERM"));
222
- process.on("SIGINT", () => void shutdown("SIGINT"));
223
- // Park forever. process.stdin.resume() does NOT work here because
224
- // the parent spawned us with `stdio: ['ignore', ...]` — there's no
225
- // FD 0 to poll. An unresolved Promise alone won't hold the event
226
- // loop either; Node exits when nothing's scheduled. The reliable
227
- // pattern is a long-interval no-op timer (~12 days per tick); the
228
- // tick is a microsecond of CPU and easily survives clock jitter.
229
- // Signal handlers are independently registered above and still fire.
230
- setInterval(() => {
231
- /* keep-alive heartbeat */
232
- }, 1 << 30);
233
- // TypeScript demands a return path even though we never reach here.
234
- return await new Promise(() => {
235
- /* never resolves; held alive by the heartbeat interval */
236
- });
237
- }
238
- async function shutdown(signal) {
239
- if (shuttingDown)
240
- return;
241
- shuttingDown = true;
242
- log(`[chat-daemon] ${signal} received, shutting down...`);
243
- if (routingPollTimer)
244
- clearInterval(routingPollTimer);
245
- // TPS.6 patch 2 (v0.5.126) — tell subscribers we're going away
246
- // BEFORE closing the RPC server. The shutdown notification gives
247
- // MCP bridges a clean signal to back off their reconnect loop
248
- // instead of treating the disconnect as transient and immediately
249
- // retrying.
250
- try {
251
- if (rpcServer)
252
- rpcServer.subscribers.shutdown(signal);
253
- }
254
- catch (err) {
255
- log(`[chat-daemon] subscriber shutdown notify error (non-fatal): ${err instanceof Error ? err.message : err}`);
256
- }
257
- try {
258
- if (rpcServer)
259
- await rpcServer.close();
260
- }
261
- catch (err) {
262
- log(`[chat-daemon] rpc close error (non-fatal): ${err instanceof Error ? err.message : err}`);
263
- }
264
- try {
265
- if (gateway)
266
- await gateway.shutdown();
267
- }
268
- catch (err) {
269
- log(`[chat-daemon] gateway.shutdown error (non-fatal): ${err instanceof Error ? err.message : err}`);
270
- }
271
- await cleanup();
272
- log(`[chat-daemon] clean exit`);
273
- process.exit(0);
274
- }
275
- async function cleanup() {
276
- if (pidFile) {
277
- try {
278
- await fs.unlink(pidFile);
279
- }
280
- catch {
281
- /* race-tolerant */
282
- }
283
- }
284
- }
285
- function log(line) {
286
- // stdio is already redirected to the log file by the parent's spawn
287
- // options; plain console.log lands in the right place.
288
- process.stdout.write(`${new Date().toISOString()} ${line}\n`);
289
- }
290
- async function rebuildRoutingIndex(dataRoot) {
291
- const cfgs = await loadAllProjectChatRouting(dataRoot);
292
- const { recordCollision } = await import("./collisions.js");
293
- return buildRoutingIndex(cfgs, (info) => {
294
- log(`[chat-daemon] routing collision: ${info.channel_key} existing=${info.existing_uuid} newcomer=${info.newcomer_uuid} (latter wins)`);
295
- // Fire-and-forget: persist + notify happen async; the routing
296
- // rebuild must not wait. recordCollision swallows its own errors
297
- // (stderr-logged) so this `void` is intentional.
298
- void recordCollision({
299
- info,
300
- dataRoot,
301
- ...(gateway !== null ? { gateway } : {}),
302
- });
303
- });
304
- }
305
- function sameIndex(a, b) {
306
- if (a.size !== b.size)
307
- return false;
308
- for (const [k, v] of a) {
309
- if (b.get(k) !== v)
310
- return false;
311
- }
312
- return true;
313
- }
@@ -1 +0,0 @@
1
- {"version":3,"file":"worker.js","sourceRoot":"","sources":["../../../src.legacy/chat/daemon/worker.ts"],"names":[],"mappings":"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"}
@@ -1,324 +0,0 @@
1
- /**
2
- * workspace-topic.ts — workspace → forum-topic binding primitive
3
- * (TPS.3 / v0.5.120+).
4
- *
5
- * Solves: given a workspace (cwd-resolved uuid + path) and a target
6
- * supergroup chat_id, ensure the workspace has a bound forum topic +
7
- * persist the binding to its `chat-routing.json`. Idempotent.
8
- *
9
- * Used by:
10
- * - TPS.4 — `opensquid setup chat` wizard step (mode: "wizard")
11
- * - TPS.6 — daemon auto-boot on MCP subscribe (mode: "auto-boot")
12
- *
13
- * Concurrency: protected by `proper-lockfile` on the per-project
14
- * `chat-routing.json` so two concurrent invocations for the same
15
- * workspace can't race-create two topics. The lock window covers
16
- * (load existing config) → (call createTopic if missing) → (write
17
- * updated config). Lock retries are bounded; if a stale lock from a
18
- * crashed prior run blocks acquisition, an `LOCKED` error is thrown
19
- * upward — callers (wizard, auto-boot) surface this to the user
20
- * rather than papering over it.
21
- *
22
- * Error propagation: this module does NOT swallow errors. RPC failures
23
- * (bot not admin, network), parse errors, and lock failures all
24
- * propagate. Callers decide how to surface them (TPS.4 prints to the
25
- * wizard, TPS.6 logs + falls back to general topic).
26
- *
27
- * Rebuild path: same as adapters/telegram.ts — see that file's header
28
- * for the ad-hoc tsc invocation. `pnpm build` does NOT recompile this
29
- * file; the chat-daemon worker loads dist/chat/daemon/workspace-topic.js
30
- * at runtime.
31
- */
32
- import { promises as fs } from "node:fs";
33
- import { createRequire } from "node:module";
34
- import * as path from "node:path";
35
- import * as lockfile from "proper-lockfile";
36
- // Need synchronous require() to construct the rpc-client lazily without
37
- // making resolveOrCreateTopic's signature async-on-import. ESM Node 20+
38
- // exposes createRequire for exactly this case.
39
- const requireCJS = createRequire(import.meta.url);
40
- import { loadAllProjectChatRouting, loadProjectChatRouting, projectChatRoutingPath, } from "./routing.js";
41
- // ---------------------------------------------------------------------
42
- // Main entrypoint
43
- // ---------------------------------------------------------------------
44
- export async function resolveOrCreateTopic(args) {
45
- const routingPath = projectChatRoutingPath(args.workspaceUuid, args.dataRoot);
46
- // Lockfile lives next to the routing file; proper-lockfile handles
47
- // both lock acquisition and the necessary parent-dir creation logic
48
- // as long as the target exists. Ensure the dir exists first.
49
- await fs.mkdir(path.dirname(routingPath), { recursive: true });
50
- // proper-lockfile requires the target file to exist; touch it
51
- // (empty config) if it doesn't, so the lock can be acquired
52
- // regardless of whether the workspace has ever had routing set up.
53
- await ensureRoutingFileExists(routingPath);
54
- // Retry tuning rationale (TPS.3 pre-research): typical createTopic
55
- // round-trip is 200-600ms (UDS + HTTPS to api.telegram.org). 8
56
- // retries with 1.5× backoff at 50ms-800ms gives ~2.4s headroom —
57
- // plenty for one contender to finish while another waits. Stale is
58
- // proper-lockfile's default (10s); not setting it explicitly.
59
- const release = await lockfile.lock(routingPath, {
60
- retries: { retries: 8, factor: 1.5, minTimeout: 50, maxTimeout: 800 },
61
- });
62
- try {
63
- const existing = await loadProjectChatRouting(args.workspaceUuid, args.dataRoot);
64
- assertAutoBoundInvariant(existing, routingPath);
65
- const bound = existing?.telegram?.auto_bound;
66
- if (bound && Number.isFinite(bound.topic_id) && bound.topic_id > 0) {
67
- // Idempotent — already bound, return existing.
68
- // Sanity check: if the auto_bound.workspace_uuid disagrees with
69
- // the outer uuid (the directory name), log on stderr but trust
70
- // the outer uuid as authoritative.
71
- if (bound.workspace_uuid !== args.workspaceUuid) {
72
- process.stderr.write(`[workspace-topic] auto_bound.workspace_uuid (${bound.workspace_uuid}) ≠ outer uuid (${args.workspaceUuid}) for ${routingPath}; using existing binding\n`);
73
- }
74
- return { topicId: bound.topic_id, topicName: bound.topic_name, created: false };
75
- }
76
- const name = deriveTopicName(args.workspacePath, args.workspaceUuid);
77
- const client = args.rpcClient ?? defaultRpcClient(args.dataRoot);
78
- const created = await client.createTopic({
79
- platform: "telegram",
80
- chat_id: args.chatId,
81
- name,
82
- });
83
- const nextAutoBound = {
84
- workspace_path: args.workspacePath,
85
- workspace_uuid: args.workspaceUuid,
86
- topic_id: created.message_thread_id,
87
- topic_name: created.name,
88
- created_at: new Date().toISOString(),
89
- created_by: args.mode,
90
- };
91
- const merged = {
92
- ...(existing ?? {}),
93
- telegram: {
94
- ...(existing?.telegram ?? {}),
95
- // Persist both auto_bound metadata + the actual routing field
96
- // (inbound_topic_ids) so the routing index picks up the new
97
- // binding on its next ~30s hot-reload without separate writes.
98
- inbound_topic_ids: mergeTopicIds(existing?.telegram?.inbound_topic_ids, created.message_thread_id),
99
- inbound_chat_ids: mergeChatIds(existing?.telegram?.inbound_chat_ids, args.chatId),
100
- auto_bound: nextAutoBound,
101
- },
102
- };
103
- try {
104
- await persistRoutingAtomic(routingPath, merged);
105
- }
106
- catch (persistErr) {
107
- // TPS.3 pre-research, choice #6 partial-failure compensation:
108
- // createTopic SUCCEEDED but persist FAILED — Telegram has a real
109
- // topic we cannot reference. Log it to a recovery file so the
110
- // user can clean up (delete the orphan topic manually) instead
111
- // of accumulating ghost topics on every retry. Don't try to
112
- // rollback (delete the topic) here — that requires a second
113
- // RPC call that could also fail, compounding the problem.
114
- // The user-facing surface lives in TPS.5 collision channel.
115
- await recordOrphanTopic(args.dataRoot, {
116
- chat_id: args.chatId,
117
- topic_id: created.message_thread_id,
118
- topic_name: created.name,
119
- workspace_uuid: args.workspaceUuid,
120
- workspace_path: args.workspacePath,
121
- mode: args.mode,
122
- persist_error: persistErr instanceof Error ? persistErr.message : String(persistErr),
123
- occurred_at: new Date().toISOString(),
124
- });
125
- throw persistErr;
126
- }
127
- return {
128
- topicId: created.message_thread_id,
129
- topicName: created.name,
130
- created: true,
131
- };
132
- }
133
- finally {
134
- await release();
135
- }
136
- }
137
- // ---------------------------------------------------------------------
138
- // Helpers (exported for unit tests)
139
- // ---------------------------------------------------------------------
140
- /**
141
- * Derive a deterministic, human-readable topic name from the workspace
142
- * path + uuid. The basename of the path is the most user-recognisable
143
- * part; the uuid prefix disambiguates two workspaces with the same
144
- * basename. Examples:
145
- *
146
- * deriveTopicName("/Users/slee/projects/loop", "da96385b-...") =
147
- * "loop · da96385b"
148
- * deriveTopicName("/", "abc12345-...") = "root · abc12345"
149
- */
150
- export function deriveTopicName(workspacePath, workspaceUuid) {
151
- const basenameRaw = path.basename(workspacePath) || "root";
152
- // Telegram limit per [aiogram docs](https://docs.aiogram.dev/en/latest/api/methods/create_forum_topic.html)
153
- // is 1-128 chars. Cap basename at 48 to leave headroom for the
154
- // " · 12345678" suffix (11 chars) — total max output ~59 chars.
155
- // 48 is conservative: Telegram client truncates topic-list display
156
- // at ~30-35 chars anyway. Pre-research verdict #4.
157
- const basename = basenameRaw.length > 48 ? `${basenameRaw.slice(0, 45)}...` : basenameRaw;
158
- const uuidShort = workspaceUuid.slice(0, 8);
159
- return `${basename} · ${uuidShort}`;
160
- }
161
- /**
162
- * Merge a single new topic_id into an optional existing array. Avoids
163
- * duplicates while preserving order (existing first, new last).
164
- */
165
- export function mergeTopicIds(existing, newId) {
166
- if (!existing || existing.length === 0)
167
- return [newId];
168
- if (existing.includes(newId))
169
- return existing;
170
- return [...existing, newId];
171
- }
172
- /**
173
- * Same as mergeTopicIds for chat_ids (strings).
174
- */
175
- export function mergeChatIds(existing, newId) {
176
- if (!existing || existing.length === 0)
177
- return [newId];
178
- if (existing.includes(newId))
179
- return existing;
180
- return [...existing, newId];
181
- }
182
- /**
183
- * Clear an existing auto_bound block (TPS.7 stale-topic lifecycle).
184
- * Leaves `inbound_topic_ids` alone — caller decides whether to also
185
- * scrub those (typically yes, since the stale topic_id no longer
186
- * exists). Returns true if a binding was cleared, false if none.
187
- */
188
- export async function clearBinding(args) {
189
- const routingPath = projectChatRoutingPath(args.workspaceUuid, args.dataRoot);
190
- await fs.mkdir(path.dirname(routingPath), { recursive: true });
191
- await ensureRoutingFileExists(routingPath);
192
- const release = await lockfile.lock(routingPath, {
193
- retries: { retries: 8, factor: 1.5, minTimeout: 50, maxTimeout: 800 },
194
- stale: 10_000,
195
- });
196
- try {
197
- const existing = await loadProjectChatRouting(args.workspaceUuid, args.dataRoot);
198
- if (!existing?.telegram?.auto_bound)
199
- return false;
200
- const staleTopicId = existing.telegram.auto_bound.topic_id;
201
- const next = {
202
- ...existing,
203
- telegram: {
204
- ...existing.telegram,
205
- inbound_topic_ids: (existing.telegram.inbound_topic_ids ?? []).filter((t) => t !== staleTopicId),
206
- auto_bound: undefined,
207
- },
208
- };
209
- // Drop the auto_bound key entirely (don't leave `auto_bound: undefined`
210
- // in the JSON output).
211
- if (next.telegram)
212
- delete next.telegram.auto_bound;
213
- if (next.telegram?.inbound_topic_ids?.length === 0)
214
- delete next.telegram.inbound_topic_ids;
215
- await persistRoutingAtomic(routingPath, next);
216
- return true;
217
- }
218
- finally {
219
- await release();
220
- }
221
- }
222
- /**
223
- * Find the workspace whose `auto_bound` block claims the given
224
- * (chat_id, topic_id) pair. Used by TPS.7 stale-topic recovery to
225
- * locate which workspace owned a now-stale binding so we can
226
- * `clearBinding` for the right uuid.
227
- *
228
- * Returns the workspace uuid on match, or null when:
229
- * - no project has an auto_bound block at all
230
- * - no auto_bound block matches BOTH chat_id and topic_id
231
- * - the matching project's routing config was already cleared by a
232
- * concurrent recovery (race-safe)
233
- *
234
- * Match rule: `auto_bound.topic_id === topicId` AND
235
- * `inbound_chat_ids.includes(chatId)`.
236
- * The chat_id check guards against the (rare) case of identical topic_id
237
- * numbers across two different supergroups; without it we could clear
238
- * the wrong workspace's binding.
239
- *
240
- * Lock-free read: this just scans on-disk routing configs. The actual
241
- * mutation (`clearBinding`) is lockfile-protected on the per-project
242
- * routing file, so two concurrent recoveries racing to clear the same
243
- * binding serialize cleanly — first wins, second sees no binding to
244
- * clear and returns false.
245
- */
246
- export async function findOwnerOfBinding(args) {
247
- const all = await loadAllProjectChatRouting(args.dataRoot);
248
- for (const [uuid, cfg] of all) {
249
- const bound = cfg.telegram?.auto_bound;
250
- if (!bound)
251
- continue;
252
- if (bound.topic_id !== args.topicId)
253
- continue;
254
- const inboundChats = cfg.telegram?.inbound_chat_ids ?? [];
255
- if (!inboundChats.includes(args.chatId))
256
- continue;
257
- return uuid;
258
- }
259
- return null;
260
- }
261
- // ---------------------------------------------------------------------
262
- // Internal
263
- // ---------------------------------------------------------------------
264
- async function ensureRoutingFileExists(routingPath) {
265
- try {
266
- await fs.access(routingPath);
267
- }
268
- catch {
269
- await fs.writeFile(routingPath, "{}\n", { flag: "wx" }).catch((err) => {
270
- // EEXIST is fine — another process touched it in the race window.
271
- if (err.code !== "EEXIST")
272
- throw err;
273
- });
274
- }
275
- }
276
- async function persistRoutingAtomic(routingPath, cfg) {
277
- // Write to a sibling tmp + rename for atomicity (rename(2) is atomic
278
- // on the same filesystem). Avoids partial-write reads from the daemon's
279
- // 30s reload loop.
280
- const tmp = `${routingPath}.${process.pid}.tmp`;
281
- await fs.writeFile(tmp, JSON.stringify(cfg, null, 2) + "\n", "utf8");
282
- await fs.rename(tmp, routingPath);
283
- }
284
- function defaultRpcClient(dataRoot) {
285
- // No cache: DaemonClient construction is cheap (just stores config)
286
- // and caching it across calls broke tests that switch OPENSQUID_HOME
287
- // per test. Construct fresh; pay the ~no-op cost. Pre-research
288
- // verdict #7.
289
- const { DaemonClient } = requireCJS("./rpc-client.js");
290
- return new DaemonClient(dataRoot ? { dataRoot } : {});
291
- }
292
- /**
293
- * Invariant check: if `auto_bound.topic_id` is set, it MUST appear in
294
- * `inbound_topic_ids`. Pre-research verdict #9: log a warning on
295
- * mismatch but do NOT auto-repair (preserves user-edited intent).
296
- */
297
- function assertAutoBoundInvariant(cfg, routingPath) {
298
- const bound = cfg?.telegram?.auto_bound;
299
- if (!bound)
300
- return;
301
- const inboundTopics = cfg?.telegram?.inbound_topic_ids ?? [];
302
- if (!inboundTopics.includes(bound.topic_id)) {
303
- process.stderr.write(`[workspace-topic] invariant warning: auto_bound.topic_id=${bound.topic_id} not in inbound_topic_ids=${JSON.stringify(inboundTopics)} for ${routingPath}; not auto-repairing\n`);
304
- }
305
- }
306
- async function recordOrphanTopic(dataRoot, record) {
307
- // Pre-research verdict #6: log orphans to a recovery file so the
308
- // user can clean up (delete the topic manually via Telegram client
309
- // or via a future TPS.7 cleanup tool) instead of accumulating
310
- // ghost topics on every retry. Doesn't try to delete the topic
311
- // (that's a separate RPC call that could ALSO fail, compounding).
312
- const root = dataRoot ?? process.env.OPENSQUID_HOME;
313
- if (!root)
314
- return; // best-effort: nowhere to write
315
- const recoveryPath = path.join(root, "orphan-topics.jsonl");
316
- try {
317
- await fs.mkdir(path.dirname(recoveryPath), { recursive: true });
318
- await fs.appendFile(recoveryPath, JSON.stringify(record) + "\n", "utf8");
319
- }
320
- catch {
321
- // If even the recovery write fails, give up silently. The original
322
- // persist error will propagate; that's the load-bearing surface.
323
- }
324
- }