botmux 2.9.1 → 2.9.2

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 (330) hide show
  1. package/README.en.md +140 -76
  2. package/README.md +134 -75
  3. package/dist/adapters/backend/pty-backend.d.ts +6 -0
  4. package/dist/adapters/backend/pty-backend.d.ts.map +1 -1
  5. package/dist/adapters/backend/pty-backend.js +10 -0
  6. package/dist/adapters/backend/pty-backend.js.map +1 -1
  7. package/dist/adapters/backend/session-backend-selector.d.ts +11 -0
  8. package/dist/adapters/backend/session-backend-selector.d.ts.map +1 -0
  9. package/dist/adapters/backend/session-backend-selector.js +26 -0
  10. package/dist/adapters/backend/session-backend-selector.js.map +1 -0
  11. package/dist/adapters/backend/tmux-backend.d.ts +80 -3
  12. package/dist/adapters/backend/tmux-backend.d.ts.map +1 -1
  13. package/dist/adapters/backend/tmux-backend.js +301 -49
  14. package/dist/adapters/backend/tmux-backend.js.map +1 -1
  15. package/dist/adapters/backend/tmux-pipe-backend.d.ts +100 -0
  16. package/dist/adapters/backend/tmux-pipe-backend.d.ts.map +1 -0
  17. package/dist/adapters/backend/tmux-pipe-backend.js +473 -0
  18. package/dist/adapters/backend/tmux-pipe-backend.js.map +1 -0
  19. package/dist/adapters/cli/aiden.d.ts.map +1 -1
  20. package/dist/adapters/cli/aiden.js +5 -0
  21. package/dist/adapters/cli/aiden.js.map +1 -1
  22. package/dist/adapters/cli/claude-code.d.ts +40 -1
  23. package/dist/adapters/cli/claude-code.d.ts.map +1 -1
  24. package/dist/adapters/cli/claude-code.js +470 -49
  25. package/dist/adapters/cli/claude-code.js.map +1 -1
  26. package/dist/adapters/cli/coco.d.ts.map +1 -1
  27. package/dist/adapters/cli/coco.js +191 -9
  28. package/dist/adapters/cli/coco.js.map +1 -1
  29. package/dist/adapters/cli/codex.d.ts.map +1 -1
  30. package/dist/adapters/cli/codex.js +94 -17
  31. package/dist/adapters/cli/codex.js.map +1 -1
  32. package/dist/adapters/cli/shared-hints.d.ts +2 -2
  33. package/dist/adapters/cli/shared-hints.d.ts.map +1 -1
  34. package/dist/adapters/cli/shared-hints.js +7 -5
  35. package/dist/adapters/cli/shared-hints.js.map +1 -1
  36. package/dist/adapters/cli/types.d.ts +38 -1
  37. package/dist/adapters/cli/types.d.ts.map +1 -1
  38. package/dist/autostart.d.ts +14 -0
  39. package/dist/autostart.d.ts.map +1 -0
  40. package/dist/autostart.js +357 -0
  41. package/dist/autostart.js.map +1 -0
  42. package/dist/bot-registry.d.ts +29 -3
  43. package/dist/bot-registry.d.ts.map +1 -1
  44. package/dist/bot-registry.js +91 -12
  45. package/dist/bot-registry.js.map +1 -1
  46. package/dist/cli/arg-utils.d.ts +11 -0
  47. package/dist/cli/arg-utils.d.ts.map +1 -0
  48. package/dist/cli/arg-utils.js +25 -0
  49. package/dist/cli/arg-utils.js.map +1 -0
  50. package/dist/cli/create-group-resolver.d.ts +32 -0
  51. package/dist/cli/create-group-resolver.d.ts.map +1 -0
  52. package/dist/cli/create-group-resolver.js +70 -0
  53. package/dist/cli/create-group-resolver.js.map +1 -0
  54. package/dist/cli/quoted-render.d.ts +30 -0
  55. package/dist/cli/quoted-render.d.ts.map +1 -0
  56. package/dist/cli/quoted-render.js +29 -0
  57. package/dist/cli/quoted-render.js.map +1 -0
  58. package/dist/cli.js +916 -272
  59. package/dist/cli.js.map +1 -1
  60. package/dist/config.d.ts +6 -0
  61. package/dist/config.d.ts.map +1 -1
  62. package/dist/config.js +18 -8
  63. package/dist/config.js.map +1 -1
  64. package/dist/core/command-handler.d.ts +43 -0
  65. package/dist/core/command-handler.d.ts.map +1 -1
  66. package/dist/core/command-handler.js +167 -64
  67. package/dist/core/command-handler.js.map +1 -1
  68. package/dist/core/dashboard-events.d.ts +57 -0
  69. package/dist/core/dashboard-events.d.ts.map +1 -0
  70. package/dist/core/dashboard-events.js +23 -0
  71. package/dist/core/dashboard-events.js.map +1 -0
  72. package/dist/core/dashboard-ipc-server.d.ts +43 -0
  73. package/dist/core/dashboard-ipc-server.d.ts.map +1 -0
  74. package/dist/core/dashboard-ipc-server.js +481 -0
  75. package/dist/core/dashboard-ipc-server.js.map +1 -0
  76. package/dist/core/dashboard-locate.d.ts +20 -0
  77. package/dist/core/dashboard-locate.d.ts.map +1 -0
  78. package/dist/core/dashboard-locate.js +26 -0
  79. package/dist/core/dashboard-locate.js.map +1 -0
  80. package/dist/core/dashboard-rows.d.ts +31 -0
  81. package/dist/core/dashboard-rows.d.ts.map +1 -0
  82. package/dist/core/dashboard-rows.js +65 -0
  83. package/dist/core/dashboard-rows.js.map +1 -0
  84. package/dist/core/inherit-peer.d.ts +14 -0
  85. package/dist/core/inherit-peer.d.ts.map +1 -0
  86. package/dist/core/inherit-peer.js +32 -0
  87. package/dist/core/inherit-peer.js.map +1 -0
  88. package/dist/core/scheduler.d.ts +24 -0
  89. package/dist/core/scheduler.d.ts.map +1 -1
  90. package/dist/core/scheduler.js +93 -2
  91. package/dist/core/scheduler.js.map +1 -1
  92. package/dist/core/session-activity.d.ts +3 -0
  93. package/dist/core/session-activity.d.ts.map +1 -0
  94. package/dist/core/session-activity.js +20 -0
  95. package/dist/core/session-activity.js.map +1 -0
  96. package/dist/core/session-discovery.d.ts +39 -0
  97. package/dist/core/session-discovery.d.ts.map +1 -1
  98. package/dist/core/session-discovery.js +114 -21
  99. package/dist/core/session-discovery.js.map +1 -1
  100. package/dist/core/session-manager.d.ts +72 -0
  101. package/dist/core/session-manager.d.ts.map +1 -1
  102. package/dist/core/session-manager.js +396 -106
  103. package/dist/core/session-manager.js.map +1 -1
  104. package/dist/core/types.d.ts +27 -2
  105. package/dist/core/types.d.ts.map +1 -1
  106. package/dist/core/types.js +14 -3
  107. package/dist/core/types.js.map +1 -1
  108. package/dist/core/worker-pool.d.ts +72 -3
  109. package/dist/core/worker-pool.d.ts.map +1 -1
  110. package/dist/core/worker-pool.js +459 -38
  111. package/dist/core/worker-pool.js.map +1 -1
  112. package/dist/daemon.d.ts.map +1 -1
  113. package/dist/daemon.js +601 -309
  114. package/dist/daemon.js.map +1 -1
  115. package/dist/dashboard/aggregator.d.ts +41 -0
  116. package/dist/dashboard/aggregator.d.ts.map +1 -0
  117. package/dist/dashboard/aggregator.js +125 -0
  118. package/dist/dashboard/aggregator.js.map +1 -0
  119. package/dist/dashboard/auth.d.ts +23 -0
  120. package/dist/dashboard/auth.d.ts.map +1 -0
  121. package/dist/dashboard/auth.js +66 -0
  122. package/dist/dashboard/auth.js.map +1 -0
  123. package/dist/dashboard/operator-selector.d.ts +20 -0
  124. package/dist/dashboard/operator-selector.d.ts.map +1 -0
  125. package/dist/dashboard/operator-selector.js +39 -0
  126. package/dist/dashboard/operator-selector.js.map +1 -0
  127. package/dist/dashboard/registry.d.ts +35 -0
  128. package/dist/dashboard/registry.d.ts.map +1 -0
  129. package/dist/dashboard/registry.js +74 -0
  130. package/dist/dashboard/registry.js.map +1 -0
  131. package/dist/dashboard/web/app.d.ts +2 -0
  132. package/dist/dashboard/web/app.d.ts.map +1 -0
  133. package/dist/dashboard/web/app.js +45 -0
  134. package/dist/dashboard/web/app.js.map +1 -0
  135. package/dist/dashboard/web/bot-defaults.d.ts +2 -0
  136. package/dist/dashboard/web/bot-defaults.d.ts.map +1 -0
  137. package/dist/dashboard/web/bot-defaults.js +201 -0
  138. package/dist/dashboard/web/bot-defaults.js.map +1 -0
  139. package/dist/dashboard/web/groups.d.ts +16 -0
  140. package/dist/dashboard/web/groups.d.ts.map +1 -0
  141. package/dist/dashboard/web/groups.js +584 -0
  142. package/dist/dashboard/web/groups.js.map +1 -0
  143. package/dist/dashboard/web/schedules.d.ts +2 -0
  144. package/dist/dashboard/web/schedules.d.ts.map +1 -0
  145. package/dist/dashboard/web/schedules.js +105 -0
  146. package/dist/dashboard/web/schedules.js.map +1 -0
  147. package/dist/dashboard/web/sessions.d.ts +2 -0
  148. package/dist/dashboard/web/sessions.d.ts.map +1 -0
  149. package/dist/dashboard/web/sessions.js +374 -0
  150. package/dist/dashboard/web/sessions.js.map +1 -0
  151. package/dist/dashboard/web/store.d.ts +23 -0
  152. package/dist/dashboard/web/store.d.ts.map +1 -0
  153. package/dist/dashboard/web/store.js +82 -0
  154. package/dist/dashboard/web/store.js.map +1 -0
  155. package/dist/dashboard-web/app.js +263 -0
  156. package/dist/dashboard-web/index.html +23 -0
  157. package/dist/dashboard-web/style.css +93 -0
  158. package/dist/dashboard.d.ts +2 -0
  159. package/dist/dashboard.d.ts.map +1 -0
  160. package/dist/dashboard.js +639 -0
  161. package/dist/dashboard.js.map +1 -0
  162. package/dist/im/lark/card-builder.d.ts +18 -1
  163. package/dist/im/lark/card-builder.d.ts.map +1 -1
  164. package/dist/im/lark/card-builder.js +70 -9
  165. package/dist/im/lark/card-builder.js.map +1 -1
  166. package/dist/im/lark/card-handler.d.ts.map +1 -1
  167. package/dist/im/lark/card-handler.js +123 -109
  168. package/dist/im/lark/card-handler.js.map +1 -1
  169. package/dist/im/lark/client.d.ts +35 -0
  170. package/dist/im/lark/client.d.ts.map +1 -1
  171. package/dist/im/lark/client.js +114 -11
  172. package/dist/im/lark/client.js.map +1 -1
  173. package/dist/im/lark/event-dispatcher.d.ts +88 -6
  174. package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
  175. package/dist/im/lark/event-dispatcher.js +398 -62
  176. package/dist/im/lark/event-dispatcher.js.map +1 -1
  177. package/dist/im/lark/forwarded-renderer.d.ts +23 -0
  178. package/dist/im/lark/forwarded-renderer.d.ts.map +1 -0
  179. package/dist/im/lark/forwarded-renderer.js +105 -0
  180. package/dist/im/lark/forwarded-renderer.js.map +1 -0
  181. package/dist/im/lark/md-card.d.ts +73 -0
  182. package/dist/im/lark/md-card.d.ts.map +1 -0
  183. package/dist/im/lark/md-card.js +332 -0
  184. package/dist/im/lark/md-card.js.map +1 -0
  185. package/dist/im/lark/merge-forward.d.ts +32 -0
  186. package/dist/im/lark/merge-forward.d.ts.map +1 -0
  187. package/dist/im/lark/merge-forward.js +110 -0
  188. package/dist/im/lark/merge-forward.js.map +1 -0
  189. package/dist/im/lark/message-parser.d.ts +9 -3
  190. package/dist/im/lark/message-parser.d.ts.map +1 -1
  191. package/dist/im/lark/message-parser.js +48 -13
  192. package/dist/im/lark/message-parser.js.map +1 -1
  193. package/dist/im/lark/quote-hint.d.ts +18 -0
  194. package/dist/im/lark/quote-hint.d.ts.map +1 -0
  195. package/dist/im/lark/quote-hint.js +23 -0
  196. package/dist/im/lark/quote-hint.js.map +1 -0
  197. package/dist/services/bridge-fallback-gate.d.ts +42 -0
  198. package/dist/services/bridge-fallback-gate.d.ts.map +1 -0
  199. package/dist/services/bridge-fallback-gate.js +12 -0
  200. package/dist/services/bridge-fallback-gate.js.map +1 -0
  201. package/dist/services/bridge-rotation-policy.d.ts +139 -0
  202. package/dist/services/bridge-rotation-policy.d.ts.map +1 -0
  203. package/dist/services/bridge-rotation-policy.js +125 -0
  204. package/dist/services/bridge-rotation-policy.js.map +1 -0
  205. package/dist/services/bridge-turn-queue.d.ts +154 -0
  206. package/dist/services/bridge-turn-queue.d.ts.map +1 -0
  207. package/dist/services/bridge-turn-queue.js +316 -0
  208. package/dist/services/bridge-turn-queue.js.map +1 -0
  209. package/dist/services/chat-first-seen-store.d.ts +27 -0
  210. package/dist/services/chat-first-seen-store.d.ts.map +1 -0
  211. package/dist/services/chat-first-seen-store.js +114 -0
  212. package/dist/services/chat-first-seen-store.js.map +1 -0
  213. package/dist/services/claude-transcript.d.ts +268 -0
  214. package/dist/services/claude-transcript.d.ts.map +1 -0
  215. package/dist/services/claude-transcript.js +798 -0
  216. package/dist/services/claude-transcript.js.map +1 -0
  217. package/dist/services/coco-transcript.d.ts +35 -0
  218. package/dist/services/coco-transcript.d.ts.map +1 -0
  219. package/dist/services/coco-transcript.js +192 -0
  220. package/dist/services/coco-transcript.js.map +1 -0
  221. package/dist/services/codex-bridge-queue.d.ts +56 -0
  222. package/dist/services/codex-bridge-queue.d.ts.map +1 -0
  223. package/dist/services/codex-bridge-queue.js +150 -0
  224. package/dist/services/codex-bridge-queue.js.map +1 -0
  225. package/dist/services/codex-transcript.d.ts +84 -0
  226. package/dist/services/codex-transcript.d.ts.map +1 -0
  227. package/dist/services/codex-transcript.js +298 -0
  228. package/dist/services/codex-transcript.js.map +1 -0
  229. package/dist/services/group-creator.d.ts +23 -0
  230. package/dist/services/group-creator.d.ts.map +1 -0
  231. package/dist/services/group-creator.js +75 -0
  232. package/dist/services/group-creator.js.map +1 -0
  233. package/dist/services/groups-store.d.ts +98 -0
  234. package/dist/services/groups-store.d.ts.map +1 -0
  235. package/dist/services/groups-store.js +213 -0
  236. package/dist/services/groups-store.js.map +1 -0
  237. package/dist/services/oncall-store.d.ts +80 -8
  238. package/dist/services/oncall-store.d.ts.map +1 -1
  239. package/dist/services/oncall-store.js +265 -55
  240. package/dist/services/oncall-store.js.map +1 -1
  241. package/dist/services/project-scanner.d.ts +1 -2
  242. package/dist/services/project-scanner.d.ts.map +1 -1
  243. package/dist/services/project-scanner.js +118 -68
  244. package/dist/services/project-scanner.js.map +1 -1
  245. package/dist/services/schedule-store.d.ts +5 -0
  246. package/dist/services/schedule-store.d.ts.map +1 -1
  247. package/dist/services/schedule-store.js +77 -1
  248. package/dist/services/schedule-store.js.map +1 -1
  249. package/dist/services/session-store.d.ts +22 -0
  250. package/dist/services/session-store.d.ts.map +1 -1
  251. package/dist/services/session-store.js +62 -4
  252. package/dist/services/session-store.js.map +1 -1
  253. package/dist/setup/bots-store.d.ts +3 -0
  254. package/dist/setup/bots-store.d.ts.map +1 -0
  255. package/dist/setup/bots-store.js +24 -0
  256. package/dist/setup/bots-store.js.map +1 -0
  257. package/dist/setup/detect-platform.d.ts +14 -0
  258. package/dist/setup/detect-platform.d.ts.map +1 -0
  259. package/dist/setup/detect-platform.js +139 -0
  260. package/dist/setup/detect-platform.js.map +1 -0
  261. package/dist/setup/ensure-fonts.d.ts +13 -0
  262. package/dist/setup/ensure-fonts.d.ts.map +1 -0
  263. package/dist/setup/ensure-fonts.js +225 -0
  264. package/dist/setup/ensure-fonts.js.map +1 -0
  265. package/dist/setup/ensure-tmux.d.ts +60 -0
  266. package/dist/setup/ensure-tmux.d.ts.map +1 -0
  267. package/dist/setup/ensure-tmux.js +236 -0
  268. package/dist/setup/ensure-tmux.js.map +1 -0
  269. package/dist/setup/index.d.ts +9 -0
  270. package/dist/setup/index.d.ts.map +1 -0
  271. package/dist/setup/index.js +46 -0
  272. package/dist/setup/index.js.map +1 -0
  273. package/dist/setup/lark-scopes.json +301 -0
  274. package/dist/setup/register-app.d.ts +52 -0
  275. package/dist/setup/register-app.d.ts.map +1 -0
  276. package/dist/setup/register-app.js +91 -0
  277. package/dist/setup/register-app.js.map +1 -0
  278. package/dist/setup/verify-permissions.d.ts +115 -0
  279. package/dist/setup/verify-permissions.d.ts.map +1 -0
  280. package/dist/setup/verify-permissions.js +207 -0
  281. package/dist/setup/verify-permissions.js.map +1 -0
  282. package/dist/skills/definitions.d.ts +4 -0
  283. package/dist/skills/definitions.d.ts.map +1 -1
  284. package/dist/skills/definitions.js +133 -19
  285. package/dist/skills/definitions.js.map +1 -1
  286. package/dist/skills/installer.d.ts +3 -1
  287. package/dist/skills/installer.d.ts.map +1 -1
  288. package/dist/skills/installer.js +18 -3
  289. package/dist/skills/installer.js.map +1 -1
  290. package/dist/types.d.ts +44 -0
  291. package/dist/types.d.ts.map +1 -1
  292. package/dist/utils/bot-routing.d.ts +6 -0
  293. package/dist/utils/bot-routing.d.ts.map +1 -0
  294. package/dist/utils/bot-routing.js +50 -0
  295. package/dist/utils/bot-routing.js.map +1 -0
  296. package/dist/utils/file-lock.d.ts +2 -0
  297. package/dist/utils/file-lock.d.ts.map +1 -0
  298. package/dist/utils/file-lock.js +114 -0
  299. package/dist/utils/file-lock.js.map +1 -0
  300. package/dist/utils/font-installer.js +1 -1
  301. package/dist/utils/font-installer.js.map +1 -1
  302. package/dist/utils/idle-detector.d.ts +6 -0
  303. package/dist/utils/idle-detector.d.ts.map +1 -1
  304. package/dist/utils/idle-detector.js +25 -4
  305. package/dist/utils/idle-detector.js.map +1 -1
  306. package/dist/utils/logger.d.ts +10 -0
  307. package/dist/utils/logger.d.ts.map +1 -1
  308. package/dist/utils/logger.js +60 -8
  309. package/dist/utils/logger.js.map +1 -1
  310. package/dist/utils/render-dimensions.d.ts +48 -0
  311. package/dist/utils/render-dimensions.d.ts.map +1 -0
  312. package/dist/utils/render-dimensions.js +55 -0
  313. package/dist/utils/render-dimensions.js.map +1 -0
  314. package/dist/utils/screen-analyzer.d.ts.map +1 -1
  315. package/dist/utils/screen-analyzer.js +24 -0
  316. package/dist/utils/screen-analyzer.js.map +1 -1
  317. package/dist/utils/screenshot-renderer.d.ts.map +1 -1
  318. package/dist/utils/screenshot-renderer.js +67 -23
  319. package/dist/utils/screenshot-renderer.js.map +1 -1
  320. package/dist/utils/terminal-renderer.d.ts +16 -0
  321. package/dist/utils/terminal-renderer.d.ts.map +1 -1
  322. package/dist/utils/terminal-renderer.js +40 -23
  323. package/dist/utils/terminal-renderer.js.map +1 -1
  324. package/dist/utils/transient-snapshot.d.ts +28 -0
  325. package/dist/utils/transient-snapshot.d.ts.map +1 -0
  326. package/dist/utils/transient-snapshot.js +96 -0
  327. package/dist/utils/transient-snapshot.js.map +1 -0
  328. package/dist/worker.js +2220 -83
  329. package/dist/worker.js.map +1 -1
  330. package/package.json +12 -5
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Pure rotation-policy gate for the Claude bridge watcher.
3
+ *
4
+ * Lives outside `worker.ts` so tests can import without dragging worker-level
5
+ * fs / IPC side-effects.
6
+ */
7
+ export type PidFollowResult = 'unavailable' | 'same' | 'switched';
8
+ /**
9
+ * Decide whether `bridgeIngest` should fall through to the directory-mtime
10
+ * `maybeFollowQuietRotation` heuristic this tick.
11
+ *
12
+ * Inputs:
13
+ * - `pidFollow`: result of the authoritative pid-state probe
14
+ * (`maybeFollowSessionRotationViaPid`). `'switched'` means it already
15
+ * moved us; `'same'` means the pid file's `sessionId` matches our
16
+ * current path; `'unavailable'` means the pid file was unreadable
17
+ * (non-Linux, no `~/.claude/sessions/<pid>.json`, validation failure).
18
+ * - `switched`: whether ANY earlier rotation step (pid resolver OR
19
+ * fingerprint fallback) already moved the watcher this tick.
20
+ *
21
+ * Returns true only when there was no earlier switch AND the pid resolver
22
+ * gave no opinion.
23
+ *
24
+ * Trade-off: pid resolver `'same'` is NOT proof that no rotation happened
25
+ * — Claude Code 2.1.123 writes `sessionId` ONCE at process start and the
26
+ * in-pane `/clear` path does not refresh it. We still skip the mtime
27
+ * heuristic on `'same'` because the alternative is sibling-pane hijack:
28
+ * any other Claude pane in the same cwd gets a busier jsonl and the
29
+ * heuristic picks it. The cost is that a pure-local `/clear` with no
30
+ * pending Lark turn won't auto-follow until the user sends a Lark
31
+ * message (which arms fingerprint fallback). The Lark-message path is
32
+ * the dominant /clear recovery flow in practice; sibling-pane
33
+ * corruption would silently break every multi-pane adopt setup.
34
+ *
35
+ * `--resume` is a fresh spawn and rewrites the pid file's sessionId, so
36
+ * it surfaces here as `'switched'`, not `'same'` — it's not affected by
37
+ * this gate.
38
+ */
39
+ export declare function shouldRunQuietRotation(pidFollow: PidFollowResult, switched: boolean): boolean;
40
+ export interface PidResolverPullbackInput {
41
+ /** sessionId reported by the pid file this tick. */
42
+ resolvedCliSessionId: string;
43
+ /** The pid file's full jsonl path (derived from sessionId + cwd). */
44
+ resolvedPath: string;
45
+ /** Current bridge jsonl path. */
46
+ currentBridgeJsonlPath: string | undefined;
47
+ /** Sid recorded as "stale" by the fingerprint fallback the last time it
48
+ * accepted a candidate the pid file disagreed about. Undefined when no
49
+ * fingerprint accept has overridden the pid file. */
50
+ stalePidStateSessionId: string | undefined;
51
+ }
52
+ export interface PidResolverPullbackDecision {
53
+ /** True ⇒ pid resolver should report 'same' rather than rotate the
54
+ * watcher back to `resolvedPath`. */
55
+ suppress: boolean;
56
+ /** True ⇒ caller should clear `stalePidStateSessionId`: a fresh sid
57
+ * (different from the stale one) appeared in the pid file, meaning a
58
+ * real rotation has happened (`--resume` / fresh spawn) and the prior
59
+ * fingerprint accept is no longer relevant. */
60
+ clearStale: boolean;
61
+ }
62
+ /**
63
+ * Decide whether the pid resolver should pull the watcher back to a path
64
+ * that disagrees with the current bridgeJsonlPath, given the worker's
65
+ * staleness bookkeeping.
66
+ *
67
+ * Rules:
68
+ * - No `stalePidStateSessionId` recorded ⇒ honour pid resolver as before.
69
+ * - Recorded sid matches pid file's current sid ⇒ suppress: this is the
70
+ * spawn-time sid that fingerprint fallback already overrode for an
71
+ * in-pane /clear that pid file can't see.
72
+ * - Recorded sid differs from pid file's current sid ⇒ a NEW rotation has
73
+ * happened (`--resume` / fresh spawn / Claude restart with new pid file
74
+ * contents). Clear the stale flag and let pid resolver switch normally.
75
+ */
76
+ export declare function evaluatePidResolverPullback(input: PidResolverPullbackInput): PidResolverPullbackDecision;
77
+ /** UUID-shaped Claude jsonl filename (sessionId.jsonl). Duplicated from
78
+ * `src/adapters/cli/claude-code.ts:SESSION_UUID_RE` to keep this module
79
+ * free of adapter imports. Keep the two patterns in sync; both gate
80
+ * trust-set membership and fingerprint-fallback candidate eligibility. */
81
+ export declare const SESSION_ID_FILENAME_RE: RegExp;
82
+ /** Extract the sessionId portion of a `<sid>.jsonl` path (basename minus
83
+ * the `.jsonl` extension). Returns the empty string when the path lacks
84
+ * the expected suffix; callers should treat that as "untrusted". */
85
+ export declare function sessionIdFromJsonlPath(path: string): string;
86
+ export interface FingerprintSwitchInput {
87
+ /** Substring fingerprint to feed Phase 1's scanner. Required. */
88
+ contentFingerprint: string;
89
+ /** Full normalised content of the Lark turn. Phase 2 (unknown-sid
90
+ * exact-content recovery) is skipped when this is missing or empty —
91
+ * short content can't anchor a recovery without ambiguity. */
92
+ contentNormalized?: string;
93
+ /** Trust set populated from initial attach, pid resolver hits, fd probes. */
94
+ knownSessionIds: ReadonlySet<string>;
95
+ /** Phase 1 scanner: substring fingerprint search. Caller wires it to
96
+ * `findJsonlContainingFingerprint`; this helper only sees the result. */
97
+ findSubstring: (acceptCandidate: (path: string) => boolean) => string | null;
98
+ /** Phase 2 scanner: exact-content search returning ALL matches in
99
+ * mtime-descending order. Caller wires it to
100
+ * `findJsonlsContainingExactContent`. */
101
+ findExact: (acceptCandidate: (path: string) => boolean) => string[];
102
+ }
103
+ export type FingerprintSwitchDecision = {
104
+ action: 'switch';
105
+ path: string;
106
+ reason: 'known-sid-substring' | 'unknown-sid-exact';
107
+ } | {
108
+ action: 'abstain';
109
+ reason: 'multiple-unknown-exact';
110
+ candidates: string[];
111
+ } | {
112
+ action: 'no-match';
113
+ };
114
+ /**
115
+ * Two-phase candidate selection for the bridge fingerprint fallback.
116
+ *
117
+ * Phase 1 (known-sid substring): the cheap path. Run the substring
118
+ * fingerprint scanner with an acceptCandidate predicate that requires
119
+ * `(UUID-shaped sid) AND (sid in knownSessionIds)`. Sibling panes are
120
+ * UUID-shaped but not in our trust set — their fingerprint hits are
121
+ * rejected. UUID gate also blocks accidental non-Claude jsonls.
122
+ *
123
+ * Phase 2 (unknown-sid exact-content recovery): only runs when
124
+ * `contentNormalized` is non-empty. Fires when Phase 1 found no match,
125
+ * which is the worst-case in-pane `/clear` scenario where the new sid
126
+ * never reaches our trust set (pid file lags, fd probe missed the open
127
+ * window). Run the exact-content scanner with predicate
128
+ * `(UUID-shaped sid) AND (sid NOT in knownSessionIds)`. Decision:
129
+ * - 0 matches → no-match
130
+ * - 1 match → switch (the post-/clear file we couldn't otherwise see)
131
+ * - ≥2 matches → abstain; multiple untrusted files normalise to the
132
+ * same Lark content, so we cannot pick one without further evidence.
133
+ * Caller is expected to log and surface a diagnostic.
134
+ *
135
+ * Pure: never touches fs / IPC / module state. Tests inject fakes for
136
+ * `findSubstring` and `findExact` to exercise every branch.
137
+ */
138
+ export declare function decideFingerprintSwitch(input: FingerprintSwitchInput): FingerprintSwitchDecision;
139
+ //# sourceMappingURL=bridge-rotation-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge-rotation-policy.d.ts","sourceRoot":"","sources":["../../src/services/bridge-rotation-policy.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,MAAM,eAAe,GAAG,aAAa,GAAG,MAAM,GAAG,UAAU,CAAC;AAElE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,eAAe,EAC1B,QAAQ,EAAE,OAAO,GAChB,OAAO,CAGT;AAID,MAAM,WAAW,wBAAwB;IACvC,oDAAoD;IACpD,oBAAoB,EAAE,MAAM,CAAC;IAC7B,qEAAqE;IACrE,YAAY,EAAE,MAAM,CAAC;IACrB,iCAAiC;IACjC,sBAAsB,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3C;;0DAEsD;IACtD,sBAAsB,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5C;AAED,MAAM,WAAW,2BAA2B;IAC1C;0CACsC;IACtC,QAAQ,EAAE,OAAO,CAAC;IAClB;;;oDAGgD;IAChD,UAAU,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,wBAAwB,GAC9B,2BAA2B,CAQ7B;AAID;;;2EAG2E;AAC3E,eAAO,MAAM,sBAAsB,QAAoE,CAAC;AAExG;;qEAEqE;AACrE,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG3D;AAED,MAAM,WAAW,sBAAsB;IACrC,iEAAiE;IACjE,kBAAkB,EAAE,MAAM,CAAC;IAC3B;;mEAE+D;IAC/D,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,6EAA6E;IAC7E,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACrC;8EAC0E;IAC1E,aAAa,EAAE,CAAC,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,KAAK,MAAM,GAAG,IAAI,CAAC;IAC7E;;8CAE0C;IAC1C,SAAS,EAAE,CAAC,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,KAAK,MAAM,EAAE,CAAC;CACrE;AAED,MAAM,MAAM,yBAAyB,GACjC;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,qBAAqB,GAAG,mBAAmB,CAAA;CAAE,GACvF;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,wBAAwB,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAA;CAAE,GAC7E;IAAE,MAAM,EAAE,UAAU,CAAA;CAAE,CAAC;AAE3B;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,sBAAsB,GAC5B,yBAAyB,CAoB3B"}
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Pure rotation-policy gate for the Claude bridge watcher.
3
+ *
4
+ * Lives outside `worker.ts` so tests can import without dragging worker-level
5
+ * fs / IPC side-effects.
6
+ */
7
+ /**
8
+ * Decide whether `bridgeIngest` should fall through to the directory-mtime
9
+ * `maybeFollowQuietRotation` heuristic this tick.
10
+ *
11
+ * Inputs:
12
+ * - `pidFollow`: result of the authoritative pid-state probe
13
+ * (`maybeFollowSessionRotationViaPid`). `'switched'` means it already
14
+ * moved us; `'same'` means the pid file's `sessionId` matches our
15
+ * current path; `'unavailable'` means the pid file was unreadable
16
+ * (non-Linux, no `~/.claude/sessions/<pid>.json`, validation failure).
17
+ * - `switched`: whether ANY earlier rotation step (pid resolver OR
18
+ * fingerprint fallback) already moved the watcher this tick.
19
+ *
20
+ * Returns true only when there was no earlier switch AND the pid resolver
21
+ * gave no opinion.
22
+ *
23
+ * Trade-off: pid resolver `'same'` is NOT proof that no rotation happened
24
+ * — Claude Code 2.1.123 writes `sessionId` ONCE at process start and the
25
+ * in-pane `/clear` path does not refresh it. We still skip the mtime
26
+ * heuristic on `'same'` because the alternative is sibling-pane hijack:
27
+ * any other Claude pane in the same cwd gets a busier jsonl and the
28
+ * heuristic picks it. The cost is that a pure-local `/clear` with no
29
+ * pending Lark turn won't auto-follow until the user sends a Lark
30
+ * message (which arms fingerprint fallback). The Lark-message path is
31
+ * the dominant /clear recovery flow in practice; sibling-pane
32
+ * corruption would silently break every multi-pane adopt setup.
33
+ *
34
+ * `--resume` is a fresh spawn and rewrites the pid file's sessionId, so
35
+ * it surfaces here as `'switched'`, not `'same'` — it's not affected by
36
+ * this gate.
37
+ */
38
+ export function shouldRunQuietRotation(pidFollow, switched) {
39
+ if (switched)
40
+ return false;
41
+ return pidFollow === 'unavailable';
42
+ }
43
+ /**
44
+ * Decide whether the pid resolver should pull the watcher back to a path
45
+ * that disagrees with the current bridgeJsonlPath, given the worker's
46
+ * staleness bookkeeping.
47
+ *
48
+ * Rules:
49
+ * - No `stalePidStateSessionId` recorded ⇒ honour pid resolver as before.
50
+ * - Recorded sid matches pid file's current sid ⇒ suppress: this is the
51
+ * spawn-time sid that fingerprint fallback already overrode for an
52
+ * in-pane /clear that pid file can't see.
53
+ * - Recorded sid differs from pid file's current sid ⇒ a NEW rotation has
54
+ * happened (`--resume` / fresh spawn / Claude restart with new pid file
55
+ * contents). Clear the stale flag and let pid resolver switch normally.
56
+ */
57
+ export function evaluatePidResolverPullback(input) {
58
+ if (input.stalePidStateSessionId === undefined) {
59
+ return { suppress: false, clearStale: false };
60
+ }
61
+ if (input.resolvedCliSessionId === input.stalePidStateSessionId) {
62
+ return { suppress: true, clearStale: false };
63
+ }
64
+ return { suppress: false, clearStale: true };
65
+ }
66
+ // ─── Two-phase fingerprint-fallback decision ───────────────────────────────
67
+ /** UUID-shaped Claude jsonl filename (sessionId.jsonl). Duplicated from
68
+ * `src/adapters/cli/claude-code.ts:SESSION_UUID_RE` to keep this module
69
+ * free of adapter imports. Keep the two patterns in sync; both gate
70
+ * trust-set membership and fingerprint-fallback candidate eligibility. */
71
+ export const SESSION_ID_FILENAME_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
72
+ /** Extract the sessionId portion of a `<sid>.jsonl` path (basename minus
73
+ * the `.jsonl` extension). Returns the empty string when the path lacks
74
+ * the expected suffix; callers should treat that as "untrusted". */
75
+ export function sessionIdFromJsonlPath(path) {
76
+ const base = path.split('/').pop() ?? '';
77
+ return base.endsWith('.jsonl') ? base.slice(0, -'.jsonl'.length) : '';
78
+ }
79
+ /**
80
+ * Two-phase candidate selection for the bridge fingerprint fallback.
81
+ *
82
+ * Phase 1 (known-sid substring): the cheap path. Run the substring
83
+ * fingerprint scanner with an acceptCandidate predicate that requires
84
+ * `(UUID-shaped sid) AND (sid in knownSessionIds)`. Sibling panes are
85
+ * UUID-shaped but not in our trust set — their fingerprint hits are
86
+ * rejected. UUID gate also blocks accidental non-Claude jsonls.
87
+ *
88
+ * Phase 2 (unknown-sid exact-content recovery): only runs when
89
+ * `contentNormalized` is non-empty. Fires when Phase 1 found no match,
90
+ * which is the worst-case in-pane `/clear` scenario where the new sid
91
+ * never reaches our trust set (pid file lags, fd probe missed the open
92
+ * window). Run the exact-content scanner with predicate
93
+ * `(UUID-shaped sid) AND (sid NOT in knownSessionIds)`. Decision:
94
+ * - 0 matches → no-match
95
+ * - 1 match → switch (the post-/clear file we couldn't otherwise see)
96
+ * - ≥2 matches → abstain; multiple untrusted files normalise to the
97
+ * same Lark content, so we cannot pick one without further evidence.
98
+ * Caller is expected to log and surface a diagnostic.
99
+ *
100
+ * Pure: never touches fs / IPC / module state. Tests inject fakes for
101
+ * `findSubstring` and `findExact` to exercise every branch.
102
+ */
103
+ export function decideFingerprintSwitch(input) {
104
+ const matchedKnown = input.findSubstring((path) => {
105
+ const sid = sessionIdFromJsonlPath(path);
106
+ return SESSION_ID_FILENAME_RE.test(sid) && input.knownSessionIds.has(sid);
107
+ });
108
+ if (matchedKnown) {
109
+ return { action: 'switch', path: matchedKnown, reason: 'known-sid-substring' };
110
+ }
111
+ if (!input.contentNormalized || input.contentNormalized.length === 0) {
112
+ return { action: 'no-match' };
113
+ }
114
+ const exact = input.findExact((path) => {
115
+ const sid = sessionIdFromJsonlPath(path);
116
+ return SESSION_ID_FILENAME_RE.test(sid) && !input.knownSessionIds.has(sid);
117
+ });
118
+ if (exact.length === 0)
119
+ return { action: 'no-match' };
120
+ if (exact.length > 1) {
121
+ return { action: 'abstain', reason: 'multiple-unknown-exact', candidates: exact };
122
+ }
123
+ return { action: 'switch', path: exact[0], reason: 'unknown-sid-exact' };
124
+ }
125
+ //# sourceMappingURL=bridge-rotation-policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge-rotation-policy.js","sourceRoot":"","sources":["../../src/services/bridge-rotation-policy.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,UAAU,sBAAsB,CACpC,SAA0B,EAC1B,QAAiB;IAEjB,IAAI,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC3B,OAAO,SAAS,KAAK,aAAa,CAAC;AACrC,CAAC;AA4BD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,2BAA2B,CACzC,KAA+B;IAE/B,IAAI,KAAK,CAAC,sBAAsB,KAAK,SAAS,EAAE,CAAC;QAC/C,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;IAChD,CAAC;IACD,IAAI,KAAK,CAAC,oBAAoB,KAAK,KAAK,CAAC,sBAAsB,EAAE,CAAC;QAChE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;IAC/C,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;AAC/C,CAAC;AAED,8EAA8E;AAE9E;;;2EAG2E;AAC3E,MAAM,CAAC,MAAM,sBAAsB,GAAG,iEAAiE,CAAC;AAExG;;qEAEqE;AACrE,MAAM,UAAU,sBAAsB,CAAC,IAAY;IACjD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC;IACzC,OAAO,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AACxE,CAAC;AAyBD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,uBAAuB,CACrC,KAA6B;IAE7B,MAAM,YAAY,GAAG,KAAK,CAAC,aAAa,CAAC,CAAC,IAAI,EAAE,EAAE;QAChD,MAAM,GAAG,GAAG,sBAAsB,CAAC,IAAI,CAAC,CAAC;QACzC,OAAO,sBAAsB,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC5E,CAAC,CAAC,CAAC;IACH,IAAI,YAAY,EAAE,CAAC;QACjB,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,qBAAqB,EAAE,CAAC;IACjF,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,iBAAiB,IAAI,KAAK,CAAC,iBAAiB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IAChC,CAAC;IACD,MAAM,KAAK,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE;QACrC,MAAM,GAAG,GAAG,sBAAsB,CAAC,IAAI,CAAC,CAAC;QACzC,OAAO,sBAAsB,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC7E,CAAC,CAAC,CAAC;IACH,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IACtD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,wBAAwB,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;IACpF,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC;AAC3E,CAAC"}
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Adopt-bridge turn attribution state machine.
3
+ *
4
+ * Pure (no fs / IPC / timers) so the worker can wrap it with watchers and
5
+ * tests can drive it deterministically. The worker feeds it transcript
6
+ * events (already drained from JSONL) and Lark-message markers; this class
7
+ * decides which assistant uuids belong to which Lark turn.
8
+ *
9
+ * Attribution rule:
10
+ * - mark() — pushes a new pending turn entry (state: not started)
11
+ * - ingest(events) — for each new user/assistant event:
12
+ * * user event → the earliest unstarted pending turn whose fingerprint
13
+ * matches becomes 'started' (its assistantUuids will collect from
14
+ * now on). A user event that does NOT match any pending fingerprint
15
+ * (or arrives with no pending Lark turn at all) is treated as
16
+ * **local terminal input**: a synthetic local turn is created on
17
+ * the spot, started immediately, and inserted ahead of any
18
+ * still-unstarted Lark turns so emit ordering reflects when the
19
+ * user event actually landed in the transcript. The local turn is
20
+ * emitted with `isLocal: true` so the worker can format it with a
21
+ * "user typed in the terminal" marker for the Lark thread.
22
+ * * assistant text event (non-sidechain) → appended to the
23
+ * currently-collecting turn (Lark or local), if any.
24
+ * - drainEmittable() — pops any leading turn that has been started AND has
25
+ * accumulated at least one visible assistant-text uuid. Started turns with no text
26
+ * yet (Claude is mid-tool-use) stay queued for the next idle.
27
+ *
28
+ * Baseline (`absorb()`) takes a batch of historical events and registers
29
+ * their uuids as already-seen so future ingest doesn't double-attribute.
30
+ */
31
+ import { normaliseForFingerprint, type TranscriptEvent } from './claude-transcript.js';
32
+ export { normaliseForFingerprint };
33
+ export interface BridgePendingTurn {
34
+ turnId: string;
35
+ started: boolean;
36
+ assistantUuids: string[];
37
+ /** Set when this turn was synthesised from a local-terminal user event
38
+ * (no matching Lark fingerprint). Causes the worker emit path to format
39
+ * the Lark message with both user text and assistant text under a
40
+ * "🖥️ 终端本地对话" header — otherwise the user would see an orphan
41
+ * reply with no prompt for context. Lark-driven turns keep this unset. */
42
+ isLocal?: boolean;
43
+ /** Transcript uuid of the user event that started this turn. Stored for
44
+ * local turns so emit can fetch the user-typed content from the source
45
+ * jsonl alongside the assistant uuids. Lark turns don't need it because
46
+ * the user content is already known on the daemon side. */
47
+ userUuid?: string;
48
+ /** A short substring of the Lark message that we expect to find inside
49
+ * the next matching `user` event's content. When set, only a user event
50
+ * whose stringified content contains this fingerprint is allowed to
51
+ * start the turn. Local-terminal input (whose content won't contain
52
+ * the Lark fingerprint) leaves the turn unstarted. */
53
+ contentFingerprint?: string;
54
+ /** Full normalised content of the Lark message. Used by the rotation
55
+ * fallback's recovery path to gate a switch into an UNKNOWN sessionId
56
+ * on exact equality with a user/queue event in that file — much
57
+ * stronger than the substring fingerprint check, which can't tell
58
+ * "test" from "run tests" across sibling panes. Stored in addition to
59
+ * `contentFingerprint` (not instead of) because in-pane known-sid
60
+ * candidates still benefit from the cheaper substring path. */
61
+ contentNormalized?: string;
62
+ /** JSONL file the turn's user event was first seen in. Stamped by ingest()
63
+ * when the turn transitions to started. Lets the emit step re-read text
64
+ * from the original transcript even after a sessionId rotation has
65
+ * pointed bridgeJsonlPath at a *different* file — without this stamp,
66
+ * uuid → text resolution would fail and the reply would be silently
67
+ * dropped. */
68
+ sourceJsonlPath?: string;
69
+ /** Wall-clock millis when mark() was called. Lets the fingerprint-based
70
+ * rotation fallback bound its scan to events written after we marked
71
+ * the turn — short fingerprints ("hello", "test") would otherwise risk
72
+ * matching pre-existing user lines in unrelated sibling jsonls. */
73
+ markTimeMs?: number;
74
+ }
75
+ /** Trim a Lark message into a stable fingerprint. Keeps a leading window
76
+ * of non-whitespace-collapsed content; long enough to disambiguate, short
77
+ * enough that minor formatting differences (newlines, attachment hints
78
+ * appended below) don't break the match. */
79
+ export declare function makeFingerprint(message: string, len?: number): string | undefined;
80
+ export declare class BridgeTurnQueue {
81
+ private seen;
82
+ private queue;
83
+ private collecting;
84
+ /** Register events as historical — their uuids are now considered seen
85
+ * but no attribution happens. Used at attach time to baseline. */
86
+ absorb(events: TranscriptEvent[]): void;
87
+ /** Push a new pending turn for the next Lark message. `contentFingerprint`
88
+ * (when set) restricts which user event can start this turn — only a
89
+ * user event whose content contains the fingerprint qualifies. Pass
90
+ * `undefined` to start on the next user event regardless (legacy).
91
+ *
92
+ * `markTimeMs` is captured here so the rotation fallback can bound its
93
+ * fingerprint scan to events written after this point — protects short
94
+ * fingerprints from matching old history in unrelated sibling jsonls. */
95
+ mark(turnId: string, contentFingerprint?: string, markTimeMs?: number, contentNormalized?: string): string;
96
+ /** Drop all pending turns. Used when the worker discovers it can't
97
+ * reliably attribute future events (e.g. baseline raced with a turn
98
+ * already in flight) and wants to clear the slate. */
99
+ clearPending(): BridgePendingTurn[];
100
+ /** Drop a specific pending turn by turnId iff it has not yet started
101
+ * collecting assistant text. Returns the dropped turn or null if not
102
+ * found / already started. Used by the worker when a writeInput's
103
+ * deferred recheck conclusively fails — the user has been notified
104
+ * the message was lost, so keeping a fingerprint-bearing mark around
105
+ * only fuels the per-tick rotation-fallback scan that already
106
+ * spammed 99% CPU once (no jsonl line will ever match). */
107
+ dropPendingTurn(turnId: string): BridgePendingTurn | null;
108
+ /** Sweep pending (unstarted) turns whose mark is older than `maxAgeMs`.
109
+ * Returns the dropped turns for logging. Belt-and-braces backstop for
110
+ * any future code path that leaves an unstarted mark stranded — without
111
+ * it, `maybeSwitchBridgeJsonl` would keep doing full-directory jsonl
112
+ * scans every poll tick until the worker restarts. Started turns are
113
+ * never expired here: once Claude actually wrote the user line, the
114
+ * turn is collecting assistant text and we want to wait however long
115
+ * the model takes. */
116
+ pruneExpired(maxAgeMs: number, now?: number): BridgePendingTurn[];
117
+ /** Process newly-appended events. Idempotent on uuid: events with seen
118
+ * uuids are skipped, so callers can safely replay.
119
+ *
120
+ * `sourceJsonlPath` (when provided) is stamped onto a turn at the moment
121
+ * it transitions from "pending" to "started" — so that emit-time text
122
+ * resolution reads the same transcript file the user/assistant uuids
123
+ * were originally observed in. Without this, a sessionId rotation
124
+ * between ingest and emit would silently drop the reply, since the
125
+ * global current jsonl path would no longer contain those uuids. */
126
+ ingest(events: TranscriptEvent[], sourceJsonlPath?: string): void;
127
+ /** Shared turn-start handler. Called for both `role:user` and
128
+ * `attachment(queued_command)` events once meaningfulness has been
129
+ * established by the caller. Encapsulates:
130
+ * 1. HOL-block drop of the previous collecting turn when it got no
131
+ * assistant text (Claude moved on).
132
+ * 2. Fingerprint-gated start of the earliest unstarted Lark turn,
133
+ * falling through to local-turn synthesis on mismatch.
134
+ * 3. markTimeMs override to the transcript event's own timestamp —
135
+ * critical for type-ahead, where the original markTimeMs (set when
136
+ * the worker wrote to PTY) can be many seconds earlier than the
137
+ * moment Claude actually dequeues and starts processing the turn.
138
+ * The bridge-fallback gate's [markTimeMs, nextBoundaryMs) window
139
+ * MUST anchor on the latter, otherwise a `botmux send` from the
140
+ * previous turn can leak into the next turn's window and the
141
+ * suppression decision flips to the wrong turn (real reply
142
+ * suppressed, fallback shown — exactly what the type-ahead-disable
143
+ * in commit b2d9791 was protecting against). */
144
+ private handleTurnStart;
145
+ /** Pop FIFO any leading turn that's started AND has assistant text.
146
+ * Returns the popped turns in order; the caller is responsible for
147
+ * rebuilding the text payload from the assistant uuids. */
148
+ drainEmittable(): BridgePendingTurn[];
149
+ /** Number of queued (not-yet-emitted) Lark turns. */
150
+ size(): number;
151
+ /** Test helper — peek the queue without mutating. */
152
+ peek(): readonly BridgePendingTurn[];
153
+ }
154
+ //# sourceMappingURL=bridge-turn-queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge-turn-queue.d.ts","sourceRoot":"","sources":["../../src/services/bridge-turn-queue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,EAAE,uBAAuB,EAA0E,KAAK,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAI/J,OAAO,EAAE,uBAAuB,EAAE,CAAC;AAEnC,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB;;;;+EAI2E;IAC3E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;gEAG4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;2DAIuD;IACvD,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;;oEAMgE;IAChE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;;mBAKe;IACf,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;wEAGoE;IACpE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAQD;;;6CAG6C;AAC7C,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,SAAK,GAAG,MAAM,GAAG,SAAS,CAK7E;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,IAAI,CAAqB;IACjC,OAAO,CAAC,KAAK,CAA2B;IACxC,OAAO,CAAC,UAAU,CAAkC;IAEpD;uEACmE;IACnE,MAAM,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,IAAI;IAMvC;;;;;;;8EAO0E;IAC1E,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,kBAAkB,CAAC,EAAE,MAAM,EAAE,UAAU,GAAE,MAAmB,EAAE,iBAAiB,CAAC,EAAE,MAAM,GAAG,MAAM;IAYtH;;2DAEuD;IACvD,YAAY,IAAI,iBAAiB,EAAE;IAMnC;;;;;;gEAM4D;IAC5D,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI;IAOzD;;;;;;;2BAOuB;IACvB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,GAAE,MAAmB,GAAG,iBAAiB,EAAE;IAY7E;;;;;;;;yEAQqE;IACrE,MAAM,CAAC,MAAM,EAAE,eAAe,EAAE,EAAE,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI;IAgEjE;;;;;;;;;;;;;;;;0DAgBsD;IACtD,OAAO,CAAC,eAAe;IA0DvB;;gEAE4D;IAC5D,cAAc,IAAI,iBAAiB,EAAE;IAYrC,qDAAqD;IACrD,IAAI,IAAI,MAAM;IAId,qDAAqD;IACrD,IAAI,IAAI,SAAS,iBAAiB,EAAE;CAGrC"}