mstro-app 0.5.1 → 0.5.6

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 (283) hide show
  1. package/PRIVACY.md +9 -9
  2. package/README.md +71 -28
  3. package/bin/commands/config.js +1 -1
  4. package/bin/mstro.js +55 -4
  5. package/dist/server/cli/eta-estimator.d.ts +55 -0
  6. package/dist/server/cli/eta-estimator.d.ts.map +1 -0
  7. package/dist/server/cli/eta-estimator.js +222 -0
  8. package/dist/server/cli/eta-estimator.js.map +1 -0
  9. package/dist/server/cli/headless/claude-invoker-process.d.ts.map +1 -1
  10. package/dist/server/cli/headless/claude-invoker-process.js +9 -1
  11. package/dist/server/cli/headless/claude-invoker-process.js.map +1 -1
  12. package/dist/server/cli/headless/mcp-config.d.ts +22 -5
  13. package/dist/server/cli/headless/mcp-config.d.ts.map +1 -1
  14. package/dist/server/cli/headless/mcp-config.js +7 -5
  15. package/dist/server/cli/headless/mcp-config.js.map +1 -1
  16. package/dist/server/cli/headless/runner.d.ts.map +1 -1
  17. package/dist/server/cli/headless/runner.js +19 -0
  18. package/dist/server/cli/headless/runner.js.map +1 -1
  19. package/dist/server/cli/headless/stall-assessor.d.ts +50 -0
  20. package/dist/server/cli/headless/stall-assessor.d.ts.map +1 -1
  21. package/dist/server/cli/headless/stall-assessor.js +64 -9
  22. package/dist/server/cli/headless/stall-assessor.js.map +1 -1
  23. package/dist/server/cli/headless/tool-watchdog.d.ts +21 -0
  24. package/dist/server/cli/headless/tool-watchdog.d.ts.map +1 -1
  25. package/dist/server/cli/headless/tool-watchdog.js +19 -12
  26. package/dist/server/cli/headless/tool-watchdog.js.map +1 -1
  27. package/dist/server/cli/headless/types.d.ts +16 -1
  28. package/dist/server/cli/headless/types.d.ts.map +1 -1
  29. package/dist/server/cli/improvisation-history-store.d.ts.map +1 -1
  30. package/dist/server/cli/improvisation-history-store.js +5 -1
  31. package/dist/server/cli/improvisation-history-store.js.map +1 -1
  32. package/dist/server/cli/improvisation-output-queue.d.ts +5 -1
  33. package/dist/server/cli/improvisation-output-queue.d.ts.map +1 -1
  34. package/dist/server/cli/improvisation-output-queue.js +30 -7
  35. package/dist/server/cli/improvisation-output-queue.js.map +1 -1
  36. package/dist/server/cli/improvisation-session-manager.d.ts +35 -0
  37. package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -1
  38. package/dist/server/cli/improvisation-session-manager.js +58 -1
  39. package/dist/server/cli/improvisation-session-manager.js.map +1 -1
  40. package/dist/server/cli/improvisation-types.d.ts +9 -0
  41. package/dist/server/cli/improvisation-types.d.ts.map +1 -1
  42. package/dist/server/cli/improvisation-types.js.map +1 -1
  43. package/dist/server/cli/retry/retry-runner-factory.d.ts.map +1 -1
  44. package/dist/server/cli/retry/retry-runner-factory.js +1 -0
  45. package/dist/server/cli/retry/retry-runner-factory.js.map +1 -1
  46. package/dist/server/engines/EngineEvent.d.ts +126 -0
  47. package/dist/server/engines/EngineEvent.d.ts.map +1 -0
  48. package/dist/server/engines/EngineEvent.js +11 -0
  49. package/dist/server/engines/EngineEvent.js.map +1 -0
  50. package/dist/server/engines/claude/ClaudeCodeEngine.d.ts +47 -0
  51. package/dist/server/engines/claude/ClaudeCodeEngine.d.ts.map +1 -0
  52. package/dist/server/engines/claude/ClaudeCodeEngine.js +338 -0
  53. package/dist/server/engines/claude/ClaudeCodeEngine.js.map +1 -0
  54. package/dist/server/engines/factory.d.ts +21 -0
  55. package/dist/server/engines/factory.d.ts.map +1 -0
  56. package/dist/server/engines/factory.js +152 -0
  57. package/dist/server/engines/factory.js.map +1 -0
  58. package/dist/server/engines/opencode/OpenCodeEngine.d.ts +148 -0
  59. package/dist/server/engines/opencode/OpenCodeEngine.d.ts.map +1 -0
  60. package/dist/server/engines/opencode/OpenCodeEngine.js +630 -0
  61. package/dist/server/engines/opencode/OpenCodeEngine.js.map +1 -0
  62. package/dist/server/engines/opencode/OpenCodeServerManager.d.ts +172 -0
  63. package/dist/server/engines/opencode/OpenCodeServerManager.d.ts.map +1 -0
  64. package/dist/server/engines/opencode/OpenCodeServerManager.js +390 -0
  65. package/dist/server/engines/opencode/OpenCodeServerManager.js.map +1 -0
  66. package/dist/server/engines/opencode/model-catalog.d.ts +94 -0
  67. package/dist/server/engines/opencode/model-catalog.d.ts.map +1 -0
  68. package/dist/server/engines/opencode/model-catalog.js +141 -0
  69. package/dist/server/engines/opencode/model-catalog.js.map +1 -0
  70. package/dist/server/engines/types.d.ts +146 -0
  71. package/dist/server/engines/types.d.ts.map +1 -0
  72. package/dist/server/engines/types.js +4 -0
  73. package/dist/server/engines/types.js.map +1 -0
  74. package/dist/server/index.js +9 -2
  75. package/dist/server/index.js.map +1 -1
  76. package/dist/server/mcp/bouncer-haiku.d.ts +17 -4
  77. package/dist/server/mcp/bouncer-haiku.d.ts.map +1 -1
  78. package/dist/server/mcp/bouncer-haiku.js +8 -124
  79. package/dist/server/mcp/bouncer-haiku.js.map +1 -1
  80. package/dist/server/mcp/bouncer-integration.d.ts +45 -0
  81. package/dist/server/mcp/bouncer-integration.d.ts.map +1 -1
  82. package/dist/server/mcp/bouncer-integration.js +69 -5
  83. package/dist/server/mcp/bouncer-integration.js.map +1 -1
  84. package/dist/server/mcp/classifier/BouncerClassifier.d.ts +34 -0
  85. package/dist/server/mcp/classifier/BouncerClassifier.d.ts.map +1 -0
  86. package/dist/server/mcp/classifier/BouncerClassifier.js +4 -0
  87. package/dist/server/mcp/classifier/BouncerClassifier.js.map +1 -0
  88. package/dist/server/mcp/classifier/ClaudeBouncerClassifier.d.ts +17 -0
  89. package/dist/server/mcp/classifier/ClaudeBouncerClassifier.d.ts.map +1 -0
  90. package/dist/server/mcp/classifier/ClaudeBouncerClassifier.js +142 -0
  91. package/dist/server/mcp/classifier/ClaudeBouncerClassifier.js.map +1 -0
  92. package/dist/server/mcp/classifier/OpenCodeBouncerClassifier.d.ts +68 -0
  93. package/dist/server/mcp/classifier/OpenCodeBouncerClassifier.d.ts.map +1 -0
  94. package/dist/server/mcp/classifier/OpenCodeBouncerClassifier.js +182 -0
  95. package/dist/server/mcp/classifier/OpenCodeBouncerClassifier.js.map +1 -0
  96. package/dist/server/mcp/classifier/factory.d.ts +70 -0
  97. package/dist/server/mcp/classifier/factory.d.ts.map +1 -0
  98. package/dist/server/mcp/classifier/factory.js +155 -0
  99. package/dist/server/mcp/classifier/factory.js.map +1 -0
  100. package/dist/server/mcp/server.js +52 -0
  101. package/dist/server/mcp/server.js.map +1 -1
  102. package/dist/server/routes/index.d.ts +1 -0
  103. package/dist/server/routes/index.d.ts.map +1 -1
  104. package/dist/server/routes/index.js +1 -0
  105. package/dist/server/routes/index.js.map +1 -1
  106. package/dist/server/routes/internal.d.ts +16 -0
  107. package/dist/server/routes/internal.d.ts.map +1 -0
  108. package/dist/server/routes/internal.js +94 -0
  109. package/dist/server/routes/internal.js.map +1 -0
  110. package/dist/server/services/plan/agent-resolver.d.ts +26 -0
  111. package/dist/server/services/plan/agent-resolver.d.ts.map +1 -0
  112. package/dist/server/services/plan/agent-resolver.js +102 -0
  113. package/dist/server/services/plan/agent-resolver.js.map +1 -0
  114. package/dist/server/services/plan/composer.d.ts.map +1 -1
  115. package/dist/server/services/plan/composer.js +59 -11
  116. package/dist/server/services/plan/composer.js.map +1 -1
  117. package/dist/server/services/plan/executor.d.ts.map +1 -1
  118. package/dist/server/services/plan/executor.js +3 -1
  119. package/dist/server/services/plan/executor.js.map +1 -1
  120. package/dist/server/services/plan/issue-prompt-builder.d.ts.map +1 -1
  121. package/dist/server/services/plan/issue-prompt-builder.js +33 -1
  122. package/dist/server/services/plan/issue-prompt-builder.js.map +1 -1
  123. package/dist/server/services/plan/parser-core.d.ts.map +1 -1
  124. package/dist/server/services/plan/parser-core.js +1 -0
  125. package/dist/server/services/plan/parser-core.js.map +1 -1
  126. package/dist/server/services/plan/types.d.ts +1 -0
  127. package/dist/server/services/plan/types.d.ts.map +1 -1
  128. package/dist/server/services/runtime-info.d.ts +3 -0
  129. package/dist/server/services/runtime-info.d.ts.map +1 -0
  130. package/dist/server/services/runtime-info.js +21 -0
  131. package/dist/server/services/runtime-info.js.map +1 -0
  132. package/dist/server/services/settings.d.ts +76 -2
  133. package/dist/server/services/settings.d.ts.map +1 -1
  134. package/dist/server/services/settings.js +127 -4
  135. package/dist/server/services/settings.js.map +1 -1
  136. package/dist/server/services/websocket/ask-user-question-bridge.d.ts +32 -0
  137. package/dist/server/services/websocket/ask-user-question-bridge.d.ts.map +1 -0
  138. package/dist/server/services/websocket/ask-user-question-bridge.js +115 -0
  139. package/dist/server/services/websocket/ask-user-question-bridge.js.map +1 -0
  140. package/dist/server/services/websocket/git-branch-handlers.d.ts.map +1 -1
  141. package/dist/server/services/websocket/git-branch-handlers.js +19 -6
  142. package/dist/server/services/websocket/git-branch-handlers.js.map +1 -1
  143. package/dist/server/services/websocket/handler.d.ts +25 -1
  144. package/dist/server/services/websocket/handler.d.ts.map +1 -1
  145. package/dist/server/services/websocket/handler.js +84 -2
  146. package/dist/server/services/websocket/handler.js.map +1 -1
  147. package/dist/server/services/websocket/quality-complexity.d.ts.map +1 -1
  148. package/dist/server/services/websocket/quality-complexity.js +78 -26
  149. package/dist/server/services/websocket/quality-complexity.js.map +1 -1
  150. package/dist/server/services/websocket/quality-eta.d.ts +47 -0
  151. package/dist/server/services/websocket/quality-eta.d.ts.map +1 -0
  152. package/dist/server/services/websocket/quality-eta.js +110 -0
  153. package/dist/server/services/websocket/quality-eta.js.map +1 -0
  154. package/dist/server/services/websocket/quality-grading.d.ts +27 -4
  155. package/dist/server/services/websocket/quality-grading.d.ts.map +1 -1
  156. package/dist/server/services/websocket/quality-grading.js +369 -201
  157. package/dist/server/services/websocket/quality-grading.js.map +1 -1
  158. package/dist/server/services/websocket/quality-handlers.d.ts.map +1 -1
  159. package/dist/server/services/websocket/quality-handlers.js +145 -7
  160. package/dist/server/services/websocket/quality-handlers.js.map +1 -1
  161. package/dist/server/services/websocket/quality-operations.d.ts +34 -0
  162. package/dist/server/services/websocket/quality-operations.d.ts.map +1 -0
  163. package/dist/server/services/websocket/quality-operations.js +47 -0
  164. package/dist/server/services/websocket/quality-operations.js.map +1 -0
  165. package/dist/server/services/websocket/quality-persistence.d.ts +9 -0
  166. package/dist/server/services/websocket/quality-persistence.d.ts.map +1 -1
  167. package/dist/server/services/websocket/quality-persistence.js +10 -0
  168. package/dist/server/services/websocket/quality-persistence.js.map +1 -1
  169. package/dist/server/services/websocket/quality-review-agent.d.ts +1 -1
  170. package/dist/server/services/websocket/quality-review-agent.d.ts.map +1 -1
  171. package/dist/server/services/websocket/quality-review-agent.js +105 -56
  172. package/dist/server/services/websocket/quality-review-agent.js.map +1 -1
  173. package/dist/server/services/websocket/quality-service.d.ts +9 -1
  174. package/dist/server/services/websocket/quality-service.d.ts.map +1 -1
  175. package/dist/server/services/websocket/quality-service.js +334 -14
  176. package/dist/server/services/websocket/quality-service.js.map +1 -1
  177. package/dist/server/services/websocket/quality-tools.d.ts +21 -0
  178. package/dist/server/services/websocket/quality-tools.d.ts.map +1 -1
  179. package/dist/server/services/websocket/quality-tools.js +49 -0
  180. package/dist/server/services/websocket/quality-tools.js.map +1 -1
  181. package/dist/server/services/websocket/quality-types.d.ts +35 -2
  182. package/dist/server/services/websocket/quality-types.d.ts.map +1 -1
  183. package/dist/server/services/websocket/quality-types.js +1 -1
  184. package/dist/server/services/websocket/quality-types.js.map +1 -1
  185. package/dist/server/services/websocket/session-handlers.d.ts +3 -1
  186. package/dist/server/services/websocket/session-handlers.d.ts.map +1 -1
  187. package/dist/server/services/websocket/session-handlers.js +60 -9
  188. package/dist/server/services/websocket/session-handlers.js.map +1 -1
  189. package/dist/server/services/websocket/session-history.js +3 -0
  190. package/dist/server/services/websocket/session-history.js.map +1 -1
  191. package/dist/server/services/websocket/session-initialization.d.ts.map +1 -1
  192. package/dist/server/services/websocket/session-initialization.js +158 -42
  193. package/dist/server/services/websocket/session-initialization.js.map +1 -1
  194. package/dist/server/services/websocket/session-registry.d.ts +25 -0
  195. package/dist/server/services/websocket/session-registry.d.ts.map +1 -1
  196. package/dist/server/services/websocket/session-registry.js +19 -0
  197. package/dist/server/services/websocket/session-registry.js.map +1 -1
  198. package/dist/server/services/websocket/settings-handlers.d.ts +1 -1
  199. package/dist/server/services/websocket/settings-handlers.d.ts.map +1 -1
  200. package/dist/server/services/websocket/settings-handlers.js +35 -4
  201. package/dist/server/services/websocket/settings-handlers.js.map +1 -1
  202. package/dist/server/services/websocket/tab-broadcast.d.ts +7 -2
  203. package/dist/server/services/websocket/tab-broadcast.d.ts.map +1 -1
  204. package/dist/server/services/websocket/tab-broadcast.js +10 -2
  205. package/dist/server/services/websocket/tab-broadcast.js.map +1 -1
  206. package/dist/server/services/websocket/tab-event-buffer.d.ts +97 -8
  207. package/dist/server/services/websocket/tab-event-buffer.d.ts.map +1 -1
  208. package/dist/server/services/websocket/tab-event-buffer.js +138 -12
  209. package/dist/server/services/websocket/tab-event-buffer.js.map +1 -1
  210. package/dist/server/services/websocket/tab-event-replay.d.ts +29 -13
  211. package/dist/server/services/websocket/tab-event-replay.d.ts.map +1 -1
  212. package/dist/server/services/websocket/tab-event-replay.js +55 -2
  213. package/dist/server/services/websocket/tab-event-replay.js.map +1 -1
  214. package/dist/server/services/websocket/tab-handlers.d.ts +9 -1
  215. package/dist/server/services/websocket/tab-handlers.d.ts.map +1 -1
  216. package/dist/server/services/websocket/tab-handlers.js +47 -2
  217. package/dist/server/services/websocket/tab-handlers.js.map +1 -1
  218. package/dist/server/services/websocket/types.d.ts +67 -7
  219. package/dist/server/services/websocket/types.d.ts.map +1 -1
  220. package/dist/server/services/websocket/types.js +12 -6
  221. package/dist/server/services/websocket/types.js.map +1 -1
  222. package/package.json +5 -3
  223. package/server/cli/eta-estimator.ts +249 -0
  224. package/server/cli/headless/claude-invoker-process.ts +9 -1
  225. package/server/cli/headless/mcp-config.ts +30 -5
  226. package/server/cli/headless/runner.ts +21 -0
  227. package/server/cli/headless/stall-assessor.ts +93 -0
  228. package/server/cli/headless/tool-watchdog.ts +21 -0
  229. package/server/cli/headless/types.ts +16 -1
  230. package/server/cli/improvisation-history-store.ts +4 -1
  231. package/server/cli/improvisation-output-queue.ts +29 -7
  232. package/server/cli/improvisation-session-manager.ts +63 -1
  233. package/server/cli/improvisation-types.ts +9 -0
  234. package/server/cli/retry/retry-runner-factory.ts +1 -0
  235. package/server/engines/EngineEvent.ts +156 -0
  236. package/server/engines/claude/ClaudeCodeEngine.ts +404 -0
  237. package/server/engines/factory.ts +176 -0
  238. package/server/engines/opencode/OpenCodeEngine.ts +786 -0
  239. package/server/engines/opencode/OpenCodeServerManager.ts +577 -0
  240. package/server/engines/opencode/model-catalog.ts +217 -0
  241. package/server/engines/types.ts +173 -0
  242. package/server/index.ts +9 -1
  243. package/server/mcp/bouncer-haiku.ts +21 -145
  244. package/server/mcp/bouncer-integration.ts +107 -5
  245. package/server/mcp/classifier/BouncerClassifier.ts +40 -0
  246. package/server/mcp/classifier/ClaudeBouncerClassifier.ts +189 -0
  247. package/server/mcp/classifier/OpenCodeBouncerClassifier.ts +305 -0
  248. package/server/mcp/classifier/factory.ts +195 -0
  249. package/server/mcp/server.ts +57 -0
  250. package/server/routes/index.ts +1 -0
  251. package/server/routes/internal.ts +112 -0
  252. package/server/services/plan/agent-resolver.ts +115 -0
  253. package/server/services/plan/agents/code-review.md +38 -8
  254. package/server/services/plan/composer.ts +63 -11
  255. package/server/services/plan/executor.ts +3 -1
  256. package/server/services/plan/issue-prompt-builder.ts +39 -1
  257. package/server/services/plan/parser-core.ts +1 -0
  258. package/server/services/plan/types.ts +4 -0
  259. package/server/services/runtime-info.ts +24 -0
  260. package/server/services/settings.ts +161 -4
  261. package/server/services/websocket/ask-user-question-bridge.ts +148 -0
  262. package/server/services/websocket/git-branch-handlers.ts +20 -6
  263. package/server/services/websocket/handler.ts +89 -2
  264. package/server/services/websocket/quality-complexity.ts +80 -26
  265. package/server/services/websocket/quality-eta.ts +155 -0
  266. package/server/services/websocket/quality-grading.ts +445 -222
  267. package/server/services/websocket/quality-handlers.ts +153 -7
  268. package/server/services/websocket/quality-operations.ts +72 -0
  269. package/server/services/websocket/quality-persistence.ts +17 -0
  270. package/server/services/websocket/quality-review-agent.ts +154 -64
  271. package/server/services/websocket/quality-service.ts +361 -13
  272. package/server/services/websocket/quality-tools.ts +51 -0
  273. package/server/services/websocket/quality-types.ts +41 -2
  274. package/server/services/websocket/session-handlers.ts +67 -10
  275. package/server/services/websocket/session-history.ts +3 -0
  276. package/server/services/websocket/session-initialization.ts +189 -46
  277. package/server/services/websocket/session-registry.ts +37 -0
  278. package/server/services/websocket/settings-handlers.ts +41 -4
  279. package/server/services/websocket/tab-broadcast.ts +10 -2
  280. package/server/services/websocket/tab-event-buffer.ts +143 -11
  281. package/server/services/websocket/tab-event-replay.ts +70 -3
  282. package/server/services/websocket/tab-handlers.ts +53 -5
  283. package/server/services/websocket/types.ts +85 -7
@@ -39,20 +39,57 @@ export interface BufferedEvent {
39
39
  data: unknown;
40
40
  /** `Date.now()` at record time. Used for age-based eviction. */
41
41
  timestamp: number;
42
+ /**
43
+ * Approximate serialized byte size of `data`. Computed once at record
44
+ * time so eviction can enforce a memory cap without re-stringifying on
45
+ * every check. Type and seq overhead is small; we only bill `data` here.
46
+ */
47
+ byteSize: number;
42
48
  }
43
49
  /**
44
50
  * Bounded replay log for a single tab.
45
51
  *
46
- * Size/age limits are parameterised for testability but defaulted to values
47
- * that comfortably cover real-world reconnect windows.
52
+ * Size/age/byte limits are parameterised for testability but defaulted to
53
+ * values that comfortably cover real-world reconnect windows for long-running
54
+ * coding-agent tasks (multi-tool, multi-minute).
55
+ *
56
+ * ## Replay-gap detection
57
+ *
58
+ * The buffer tracks `evictedThroughSeq` — the highest seq that has ever been
59
+ * evicted (0 if nothing has been evicted). A web client whose `lastSeenSeq`
60
+ * is below this value has missed events the buffer can no longer supply, and
61
+ * an incremental replay would produce a silent gap. Callers should consult
62
+ * `hasGapSince` before relying on `getSince` for incremental replay; on a
63
+ * gap they should fall back to a full snapshot path (e.g. `outputHistory`).
64
+ *
65
+ * ## Eviction is FIFO with three caps
66
+ *
67
+ * Events are evicted from the front when ANY of these limits is exceeded:
68
+ * - count: `maxEvents` (default 10k)
69
+ * - age: `maxAgeMs` (default 60 min)
70
+ * - bytes: `maxTotalBytes` (default 32 MB)
71
+ *
72
+ * The byte cap is the safety belt against pathological events (e.g. a 50 MB
73
+ * grep result streamed as one event). Without it, count- and age-based caps
74
+ * still allow a single tab to hoard arbitrary memory.
48
75
  */
49
76
  export declare class TabEventBuffer {
50
77
  private readonly maxEvents;
51
78
  private readonly maxAgeMs;
52
79
  private readonly now;
80
+ private readonly maxTotalBytes;
53
81
  private readonly events;
54
82
  private nextSeq;
55
- constructor(maxEvents?: number, maxAgeMs?: number, now?: () => number);
83
+ /**
84
+ * Highest seq that has been evicted from the buffer. 0 means nothing has
85
+ * been evicted yet (buffer is operating within its window). Monotonically
86
+ * non-decreasing — eviction always happens from the front of the FIFO, in
87
+ * seq order, so the most recently evicted seq is always the highest.
88
+ */
89
+ private evictedThroughSeq;
90
+ /** Approximate sum of `byteSize` over still-resident events. */
91
+ private totalBytes;
92
+ constructor(maxEvents?: number, maxAgeMs?: number, now?: () => number, maxTotalBytes?: number);
56
93
  /**
57
94
  * Append an event and return its assigned sequence number.
58
95
  *
@@ -65,18 +102,51 @@ export declare class TabEventBuffer {
65
102
  * Return all still-buffered events with `seq > afterSeq`, in original
66
103
  * order. Returns an empty array if nothing newer is buffered (either the
67
104
  * web is caught up or the window has rolled past).
105
+ *
106
+ * NOTE: This does not detect or signal replay gaps. Pair with
107
+ * `hasGapSince(afterSeq)` to know whether a returned array is a complete
108
+ * incremental replay or a partial one (events between `afterSeq` and the
109
+ * oldest surviving seq have been evicted and are no longer available).
68
110
  */
69
111
  getSince(afterSeq: number): BufferedEvent[];
112
+ /**
113
+ * True when an incremental replay starting from `afterSeq` would silently
114
+ * skip events that the buffer has already evicted. Used by the replay
115
+ * orchestrator to decide whether to fall back to a full snapshot rather
116
+ * than emit a partial event stream the web can't reconstruct.
117
+ *
118
+ * `afterSeq < evictedThroughSeq` means the next event the caller expects
119
+ * (`afterSeq + 1`) is at or below the eviction frontier — that event has
120
+ * already been dropped from memory.
121
+ */
122
+ hasGapSince(afterSeq: number): boolean;
123
+ /**
124
+ * Highest seq that has been evicted from this buffer; 0 if nothing has been
125
+ * evicted yet. Exposed for telemetry and gap-recovery decisions.
126
+ */
127
+ getEvictedThroughSeq(): number;
70
128
  /** Current highest assigned seq (monotonic; not reset by eviction). */
71
129
  currentSeq(): number;
72
130
  /** Events currently held in memory. For tests. */
73
131
  size(): number;
132
+ /** Approximate bytes held by `data` payloads currently in memory. For tests/telemetry. */
133
+ byteSize(): number;
74
134
  /**
75
135
  * Drop events older than `maxAgeMs` from the front, then enforce
76
- * `maxEvents` by trimming the front further if needed. Eviction keeps the
77
- * newest events — they're the ones the web is most likely to still need.
136
+ * `maxEvents` and `maxTotalBytes` by trimming the front further if needed.
137
+ * Eviction keeps the newest events — they're the ones the web is most
138
+ * likely to still need.
139
+ *
140
+ * Each evicted seq advances `evictedThroughSeq` so callers can detect
141
+ * replay gaps. The FIFO ensures we always evict in seq order, so the last
142
+ * evicted seq is always the highest seen so far.
143
+ *
144
+ * The byte cap is enforced LAST so that count- and age-based eviction get
145
+ * a chance first; a chatty-but-small session evicts on age before it ever
146
+ * touches the byte cap, which keeps the usual case predictable.
78
147
  */
79
148
  private evict;
149
+ private popOldest;
80
150
  }
81
151
  /**
82
152
  * Registry of per-tab buffers. Kept as a thin collection so `HandlerContext`
@@ -96,8 +166,27 @@ export declare class TabEventBufferRegistry {
96
166
  /** Drop all bookkeeping. Used for tests; no production caller expected. */
97
167
  clear(): void;
98
168
  }
99
- /** 1000 events per tab covers typical reconnect windows comfortably. */
100
- export declare const DEFAULT_MAX_EVENTS = 1000;
101
- /** 15 minutes of history is more than enough for the longest plausible web reconnect. */
169
+ /**
170
+ * 10,000 events per tab.
171
+ *
172
+ * Sized for long-running coding-agent tasks (multi-tool, multi-minute) plus
173
+ * laptop sleep/wake reconnect windows. Worst-case observed: a 14-minute
174
+ * session with ~120 tool calls produces ~1.5–3k tab-scoped events; 10× that
175
+ * gives headroom for parallel agents and chatty improvisation. Memory
176
+ * footprint at ~500B/event = ~5MB per tab; the local-only single-tenant
177
+ * deployment makes this a non-issue.
178
+ */
179
+ export declare const DEFAULT_MAX_EVENTS = 10000;
180
+ /**
181
+ * 60 minutes of history. Covers laptop sleep/wake, long meetings between
182
+ * sessions, and the largest plausible reconnect window that a tab might
183
+ * legitimately want to recover incrementally instead of starting fresh.
184
+ */
102
185
  export declare const DEFAULT_MAX_AGE_MS: number;
186
+ /**
187
+ * 32 MB safety belt against pathological events (large grep results, full
188
+ * file reads streamed inline). Eviction by bytes guarantees a single tab
189
+ * can't hoard arbitrary memory regardless of count/age limits.
190
+ */
191
+ export declare const DEFAULT_MAX_TOTAL_BYTES: number;
103
192
  //# sourceMappingURL=tab-event-buffer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tab-event-buffer.d.ts","sourceRoot":"","sources":["../../../../server/services/websocket/tab-event-buffer.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,MAAM,WAAW,aAAa;IAC5B,4CAA4C;IAC5C,GAAG,EAAE,MAAM,CAAA;IACX,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAA;IACZ,2CAA2C;IAC3C,IAAI,EAAE,OAAO,CAAA;IACb,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAA;CAClB;AAED;;;;;GAKG;AACH,qBAAa,cAAc;IAKvB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,GAAG;IANtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;IAC7C,OAAO,CAAC,OAAO,CAAI;gBAGA,SAAS,GAAE,MAA2B,EACtC,QAAQ,GAAE,MAA2B,EACrC,GAAG,GAAE,MAAM,MAAiB;IAG/C;;;;;;OAMG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM;IAO3C;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,EAAE;IAS3C,uEAAuE;IACvE,UAAU,IAAI,MAAM;IAIpB,kDAAkD;IAClD,IAAI,IAAI,MAAM;IAId;;;;OAIG;IACH,OAAO,CAAC,KAAK;CASd;AAED;;;;GAIG;AACH,qBAAa,sBAAsB;IAI/B,OAAO,CAAC,QAAQ,CAAC,aAAa;IAHhC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoC;gBAGzC,aAAa,GAAE,MAAM,cAA2C;IAGnF,8DAA8D;IAC9D,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc;IAS1C,sDAAsD;IACtD,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS;IAI9C,wDAAwD;IACxD,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAI3B,2EAA2E;IAC3E,KAAK,IAAI,IAAI;CAGd;AAED,wEAAwE;AACxE,eAAO,MAAM,kBAAkB,OAAO,CAAA;AACtC,yFAAyF;AACzF,eAAO,MAAM,kBAAkB,QAAiB,CAAA"}
1
+ {"version":3,"file":"tab-event-buffer.d.ts","sourceRoot":"","sources":["../../../../server/services/websocket/tab-event-buffer.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,MAAM,WAAW,aAAa;IAC5B,4CAA4C;IAC5C,GAAG,EAAE,MAAM,CAAA;IACX,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAA;IACZ,2CAA2C;IAC3C,IAAI,EAAE,OAAO,CAAA;IACb,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,qBAAa,cAAc;IAcvB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAhBhC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;IAC7C,OAAO,CAAC,OAAO,CAAI;IACnB;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB,CAAI;IAC7B,gEAAgE;IAChE,OAAO,CAAC,UAAU,CAAI;gBAGH,SAAS,GAAE,MAA2B,EACtC,QAAQ,GAAE,MAA2B,EACrC,GAAG,GAAE,MAAM,MAAiB,EAC5B,aAAa,GAAE,MAAgC;IAGlE;;;;;;OAMG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM;IAS3C;;;;;;;;;OASG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,EAAE;IAS3C;;;;;;;;;OASG;IACH,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAKtC;;;OAGG;IACH,oBAAoB,IAAI,MAAM;IAI9B,uEAAuE;IACvE,UAAU,IAAI,MAAM;IAIpB,kDAAkD;IAClD,IAAI,IAAI,MAAM;IAId,0FAA0F;IAC1F,QAAQ,IAAI,MAAM;IAIlB;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,KAAK;IAab,OAAO,CAAC,SAAS;CAOlB;AAuBD;;;;GAIG;AACH,qBAAa,sBAAsB;IAI/B,OAAO,CAAC,QAAQ,CAAC,aAAa;IAHhC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoC;gBAGzC,aAAa,GAAE,MAAM,cAA2C;IAGnF,8DAA8D;IAC9D,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc;IAS1C,sDAAsD;IACtD,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS;IAI9C,wDAAwD;IACxD,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAI3B,2EAA2E;IAC3E,KAAK,IAAI,IAAI;CAGd;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,QAAS,CAAA;AACxC;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,QAAiB,CAAA;AAChD;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,QAAmB,CAAA"}
@@ -2,19 +2,51 @@
2
2
  /**
3
3
  * Bounded replay log for a single tab.
4
4
  *
5
- * Size/age limits are parameterised for testability but defaulted to values
6
- * that comfortably cover real-world reconnect windows.
5
+ * Size/age/byte limits are parameterised for testability but defaulted to
6
+ * values that comfortably cover real-world reconnect windows for long-running
7
+ * coding-agent tasks (multi-tool, multi-minute).
8
+ *
9
+ * ## Replay-gap detection
10
+ *
11
+ * The buffer tracks `evictedThroughSeq` — the highest seq that has ever been
12
+ * evicted (0 if nothing has been evicted). A web client whose `lastSeenSeq`
13
+ * is below this value has missed events the buffer can no longer supply, and
14
+ * an incremental replay would produce a silent gap. Callers should consult
15
+ * `hasGapSince` before relying on `getSince` for incremental replay; on a
16
+ * gap they should fall back to a full snapshot path (e.g. `outputHistory`).
17
+ *
18
+ * ## Eviction is FIFO with three caps
19
+ *
20
+ * Events are evicted from the front when ANY of these limits is exceeded:
21
+ * - count: `maxEvents` (default 10k)
22
+ * - age: `maxAgeMs` (default 60 min)
23
+ * - bytes: `maxTotalBytes` (default 32 MB)
24
+ *
25
+ * The byte cap is the safety belt against pathological events (e.g. a 50 MB
26
+ * grep result streamed as one event). Without it, count- and age-based caps
27
+ * still allow a single tab to hoard arbitrary memory.
7
28
  */
8
29
  export class TabEventBuffer {
9
30
  maxEvents;
10
31
  maxAgeMs;
11
32
  now;
33
+ maxTotalBytes;
12
34
  events = [];
13
35
  nextSeq = 1;
14
- constructor(maxEvents = DEFAULT_MAX_EVENTS, maxAgeMs = DEFAULT_MAX_AGE_MS, now = Date.now) {
36
+ /**
37
+ * Highest seq that has been evicted from the buffer. 0 means nothing has
38
+ * been evicted yet (buffer is operating within its window). Monotonically
39
+ * non-decreasing — eviction always happens from the front of the FIFO, in
40
+ * seq order, so the most recently evicted seq is always the highest.
41
+ */
42
+ evictedThroughSeq = 0;
43
+ /** Approximate sum of `byteSize` over still-resident events. */
44
+ totalBytes = 0;
45
+ constructor(maxEvents = DEFAULT_MAX_EVENTS, maxAgeMs = DEFAULT_MAX_AGE_MS, now = Date.now, maxTotalBytes = DEFAULT_MAX_TOTAL_BYTES) {
15
46
  this.maxEvents = maxEvents;
16
47
  this.maxAgeMs = maxAgeMs;
17
48
  this.now = now;
49
+ this.maxTotalBytes = maxTotalBytes;
18
50
  }
19
51
  /**
20
52
  * Append an event and return its assigned sequence number.
@@ -25,7 +57,9 @@ export class TabEventBuffer {
25
57
  */
26
58
  record(type, data) {
27
59
  const seq = this.nextSeq++;
28
- this.events.push({ seq, type, data, timestamp: this.now() });
60
+ const byteSize = estimateByteSize(data);
61
+ this.events.push({ seq, type, data, timestamp: this.now(), byteSize });
62
+ this.totalBytes += byteSize;
29
63
  this.evict();
30
64
  return seq;
31
65
  }
@@ -33,6 +67,11 @@ export class TabEventBuffer {
33
67
  * Return all still-buffered events with `seq > afterSeq`, in original
34
68
  * order. Returns an empty array if nothing newer is buffered (either the
35
69
  * web is caught up or the window has rolled past).
70
+ *
71
+ * NOTE: This does not detect or signal replay gaps. Pair with
72
+ * `hasGapSince(afterSeq)` to know whether a returned array is a complete
73
+ * incremental replay or a partial one (events between `afterSeq` and the
74
+ * oldest surviving seq have been evicted and are no longer available).
36
75
  */
37
76
  getSince(afterSeq) {
38
77
  this.evict();
@@ -43,6 +82,27 @@ export class TabEventBuffer {
43
82
  }
44
83
  return out;
45
84
  }
85
+ /**
86
+ * True when an incremental replay starting from `afterSeq` would silently
87
+ * skip events that the buffer has already evicted. Used by the replay
88
+ * orchestrator to decide whether to fall back to a full snapshot rather
89
+ * than emit a partial event stream the web can't reconstruct.
90
+ *
91
+ * `afterSeq < evictedThroughSeq` means the next event the caller expects
92
+ * (`afterSeq + 1`) is at or below the eviction frontier — that event has
93
+ * already been dropped from memory.
94
+ */
95
+ hasGapSince(afterSeq) {
96
+ this.evict();
97
+ return afterSeq < this.evictedThroughSeq;
98
+ }
99
+ /**
100
+ * Highest seq that has been evicted from this buffer; 0 if nothing has been
101
+ * evicted yet. Exposed for telemetry and gap-recovery decisions.
102
+ */
103
+ getEvictedThroughSeq() {
104
+ return this.evictedThroughSeq;
105
+ }
46
106
  /** Current highest assigned seq (monotonic; not reset by eviction). */
47
107
  currentSeq() {
48
108
  return this.nextSeq - 1;
@@ -51,20 +111,67 @@ export class TabEventBuffer {
51
111
  size() {
52
112
  return this.events.length;
53
113
  }
114
+ /** Approximate bytes held by `data` payloads currently in memory. For tests/telemetry. */
115
+ byteSize() {
116
+ return this.totalBytes;
117
+ }
54
118
  /**
55
119
  * Drop events older than `maxAgeMs` from the front, then enforce
56
- * `maxEvents` by trimming the front further if needed. Eviction keeps the
57
- * newest events — they're the ones the web is most likely to still need.
120
+ * `maxEvents` and `maxTotalBytes` by trimming the front further if needed.
121
+ * Eviction keeps the newest events — they're the ones the web is most
122
+ * likely to still need.
123
+ *
124
+ * Each evicted seq advances `evictedThroughSeq` so callers can detect
125
+ * replay gaps. The FIFO ensures we always evict in seq order, so the last
126
+ * evicted seq is always the highest seen so far.
127
+ *
128
+ * The byte cap is enforced LAST so that count- and age-based eviction get
129
+ * a chance first; a chatty-but-small session evicts on age before it ever
130
+ * touches the byte cap, which keeps the usual case predictable.
58
131
  */
59
132
  evict() {
60
133
  const cutoff = this.now() - this.maxAgeMs;
61
134
  while (this.events.length > 0 && this.events[0].timestamp < cutoff) {
62
- this.events.shift();
135
+ this.popOldest();
63
136
  }
64
137
  while (this.events.length > this.maxEvents) {
65
- this.events.shift();
138
+ this.popOldest();
139
+ }
140
+ while (this.events.length > 0 && this.totalBytes > this.maxTotalBytes) {
141
+ this.popOldest();
66
142
  }
67
143
  }
144
+ popOldest() {
145
+ const evicted = this.events.shift();
146
+ if (!evicted)
147
+ return;
148
+ this.evictedThroughSeq = evicted.seq;
149
+ this.totalBytes -= evicted.byteSize;
150
+ if (this.totalBytes < 0)
151
+ this.totalBytes = 0;
152
+ }
153
+ }
154
+ /**
155
+ * Estimate `data`'s serialized byte size for the eviction byte cap. Uses
156
+ * `JSON.stringify` because that's what hits the wire; falls back to a small
157
+ * default on circular structures so we don't crash the broadcast path.
158
+ *
159
+ * `Buffer.byteLength` would give us UTF-8 bytes vs UTF-16 code units, but on
160
+ * Node `JSON.stringify(...).length` is close enough (within a small constant
161
+ * factor for ASCII-heavy payloads) and avoids an extra allocation.
162
+ */
163
+ function estimateByteSize(data) {
164
+ if (data === undefined || data === null)
165
+ return 0;
166
+ try {
167
+ return JSON.stringify(data).length;
168
+ }
169
+ catch {
170
+ // Circular reference, BigInt, etc. — bill a small fixed cost so the
171
+ // byte cap still has SOME signal. We won't be able to wire-serialize
172
+ // this either, but that's a separate problem.
173
+ return 256;
174
+ }
68
175
  }
69
176
  /**
70
177
  * Registry of per-tab buffers. Kept as a thin collection so `HandlerContext`
@@ -99,8 +206,27 @@ export class TabEventBufferRegistry {
99
206
  this.buffers.clear();
100
207
  }
101
208
  }
102
- /** 1000 events per tab covers typical reconnect windows comfortably. */
103
- export const DEFAULT_MAX_EVENTS = 1000;
104
- /** 15 minutes of history is more than enough for the longest plausible web reconnect. */
105
- export const DEFAULT_MAX_AGE_MS = 15 * 60 * 1000;
209
+ /**
210
+ * 10,000 events per tab.
211
+ *
212
+ * Sized for long-running coding-agent tasks (multi-tool, multi-minute) plus
213
+ * laptop sleep/wake reconnect windows. Worst-case observed: a 14-minute
214
+ * session with ~120 tool calls produces ~1.5–3k tab-scoped events; 10× that
215
+ * gives headroom for parallel agents and chatty improvisation. Memory
216
+ * footprint at ~500B/event = ~5MB per tab; the local-only single-tenant
217
+ * deployment makes this a non-issue.
218
+ */
219
+ export const DEFAULT_MAX_EVENTS = 10_000;
220
+ /**
221
+ * 60 minutes of history. Covers laptop sleep/wake, long meetings between
222
+ * sessions, and the largest plausible reconnect window that a tab might
223
+ * legitimately want to recover incrementally instead of starting fresh.
224
+ */
225
+ export const DEFAULT_MAX_AGE_MS = 60 * 60 * 1000;
226
+ /**
227
+ * 32 MB safety belt against pathological events (large grep results, full
228
+ * file reads streamed inline). Eviction by bytes guarantees a single tab
229
+ * can't hoard arbitrary memory regardless of count/age limits.
230
+ */
231
+ export const DEFAULT_MAX_TOTAL_BYTES = 32 * 1024 * 1024;
106
232
  //# sourceMappingURL=tab-event-buffer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tab-event-buffer.js","sourceRoot":"","sources":["../../../../server/services/websocket/tab-event-buffer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tab-event-buffer.js","sourceRoot":"","sources":["../../../../server/services/websocket/tab-event-buffer.ts"],"names":[],"mappings":"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"}
@@ -1,20 +1,36 @@
1
- /**
2
- * Replay tab-scoped events missed during a transport gap.
3
- *
4
- * `session-initialization.ts` calls this right before sending `tabInitialized`
5
- * so the web sees any events with `seq > lastSeenSeq` in-order before the
6
- * usual initialization payload. Ordering matters: e.g. a `movementComplete`
7
- * before `outputHistory` would render duplicate content.
8
- *
9
- * Delivery is targeted (`ctx.send`) rather than broadcast because only the
10
- * rejoining web needs the replay; other connected webs already saw these
11
- * events live.
12
- */
13
1
  import type { HandlerContext } from './handler-context.js';
14
2
  import type { WSContext } from './types.js';
3
+ /** Result of a replay attempt — used by callers (and tests) for telemetry. */
4
+ export interface ReplayResult {
5
+ /** Number of events sent to the web during this replay. */
6
+ sentCount: number;
7
+ /**
8
+ * True when the buffer had already evicted events that fell between the
9
+ * web's `lastSeenSeq` and the oldest surviving seq. The replay is partial;
10
+ * the web's incremental state is now provably stale and the caller should
11
+ * fall back to a full snapshot path (e.g. `outputHistory`).
12
+ */
13
+ hadGap: boolean;
14
+ /**
15
+ * If `hadGap`, the highest seq that was evicted (so the gap range is
16
+ * `(lastSeenSeq + 1) .. evictedThroughSeq`). Undefined when no gap.
17
+ */
18
+ evictedThroughSeq?: number;
19
+ /**
20
+ * If `hadGap`, the seq the web requested replay from. Echoed into
21
+ * telemetry so log entries are self-contained.
22
+ */
23
+ lastSeenSeq?: number;
24
+ }
15
25
  /**
16
26
  * Replay tab events with `seq > lastSeenSeq` to `ws`. Silently no-ops when
17
27
  * the buffer is empty or `lastSeenSeq` is unset (full init, not a resume).
28
+ *
29
+ * Returns a `ReplayResult` so the caller can detect a partial replay (the
30
+ * buffer evicted events the web is asking about) and decide whether to send
31
+ * a recovery snapshot. This is the load-bearing telemetry surface for the
32
+ * "long-running task output disappears mid-stream" failure mode — a `hadGap`
33
+ * here is the smoking gun.
18
34
  */
19
- export declare function replayTabEventsSince(ctx: HandlerContext, ws: WSContext, tabId: string, lastSeenSeq: number | undefined): void;
35
+ export declare function replayTabEventsSince(ctx: HandlerContext, ws: WSContext, tabId: string, lastSeenSeq: number | undefined): ReplayResult;
20
36
  //# sourceMappingURL=tab-event-replay.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tab-event-replay.d.ts","sourceRoot":"","sources":["../../../../server/services/websocket/tab-event-replay.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAC1D,OAAO,KAAK,EAAqB,SAAS,EAAE,MAAM,YAAY,CAAA;AAE9D;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,cAAc,EACnB,EAAE,EAAE,SAAS,EACb,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,GAAG,SAAS,GAC9B,IAAI,CAaN"}
1
+ {"version":3,"file":"tab-event-replay.d.ts","sourceRoot":"","sources":["../../../../server/services/websocket/tab-event-replay.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAC1D,OAAO,KAAK,EAAqB,SAAS,EAAE,MAAM,YAAY,CAAA;AAE9D,8EAA8E;AAC9E,MAAM,WAAW,YAAY;IAC3B,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAA;IACjB;;;;;OAKG;IACH,MAAM,EAAE,OAAO,CAAA;IACf;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,cAAc,EACnB,EAAE,EAAE,SAAS,EACb,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,GAAG,SAAS,GAC9B,YAAY,CAkDd"}
@@ -1,14 +1,66 @@
1
1
  // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
+ /**
3
+ * Replay tab-scoped events missed during a transport gap.
4
+ *
5
+ * `session-initialization.ts` calls this right before sending `tabInitialized`
6
+ * so the web sees any events with `seq > lastSeenSeq` in-order before the
7
+ * usual initialization payload. Ordering matters: e.g. a `movementComplete`
8
+ * before `outputHistory` would render duplicate content.
9
+ *
10
+ * Delivery is targeted (`ctx.send`) rather than broadcast because only the
11
+ * rejoining web needs the replay; other connected webs already saw these
12
+ * events live.
13
+ */
14
+ import { captureException } from '../sentry.js';
2
15
  /**
3
16
  * Replay tab events with `seq > lastSeenSeq` to `ws`. Silently no-ops when
4
17
  * the buffer is empty or `lastSeenSeq` is unset (full init, not a resume).
18
+ *
19
+ * Returns a `ReplayResult` so the caller can detect a partial replay (the
20
+ * buffer evicted events the web is asking about) and decide whether to send
21
+ * a recovery snapshot. This is the load-bearing telemetry surface for the
22
+ * "long-running task output disappears mid-stream" failure mode — a `hadGap`
23
+ * here is the smoking gun.
5
24
  */
6
25
  export function replayTabEventsSince(ctx, ws, tabId, lastSeenSeq) {
7
26
  if (lastSeenSeq === undefined)
8
- return;
27
+ return { sentCount: 0, hadGap: false };
9
28
  const buffer = ctx.tabEventBuffers.get(tabId);
10
29
  if (!buffer)
11
- return;
30
+ return { sentCount: 0, hadGap: false };
31
+ const hadGap = buffer.hasGapSince(lastSeenSeq);
32
+ const evictedThroughSeq = hadGap ? buffer.getEvictedThroughSeq() : undefined;
33
+ if (hadGap) {
34
+ // Replay is structurally incomplete. Surface a single, structured warning
35
+ // so we can grep/Sentry-search for the failure mode without spamming logs
36
+ // on every event.
37
+ const message = `[tab-replay] gap detected for tab=${tabId}: web requested replay from seq=${lastSeenSeq}, ` +
38
+ `but buffer has evicted through seq=${evictedThroughSeq}. ` +
39
+ `Events (${lastSeenSeq + 1}..${evictedThroughSeq}) are unavailable; the web's ` +
40
+ `incremental state is stale and a full snapshot will be sent instead.`;
41
+ console.warn(message);
42
+ try {
43
+ captureException(new Error('TabEventBuffer replay gap'), {
44
+ context: 'tab-event-replay',
45
+ tabId,
46
+ lastSeenSeq,
47
+ evictedThroughSeq,
48
+ bufferCurrentSeq: buffer.currentSeq(),
49
+ gapSize: (evictedThroughSeq ?? 0) - lastSeenSeq,
50
+ });
51
+ }
52
+ catch {
53
+ // Sentry transport errors must not break the replay path.
54
+ }
55
+ // CRITICAL: do NOT emit partial events. If we did, the web would advance
56
+ // its `tabSeqs` past the (lastSeenSeq+1 .. evictedThroughSeq) range and
57
+ // the subsequent snapshot would land in a tab that thinks it's caught up
58
+ // — silently rendering only the post-gap tail. Returning early without
59
+ // events forces the caller (`session-initialization.ts`) into the
60
+ // snapshot-fallback branch, which sends a fresh `outputHistory` payload
61
+ // with `replayGap: true` so the web can replace its tab state cleanly.
62
+ return { sentCount: 0, hadGap: true, evictedThroughSeq, lastSeenSeq };
63
+ }
12
64
  const events = buffer.getSince(lastSeenSeq);
13
65
  for (const event of events) {
14
66
  // Types are checked at record time via `broadcastTabEvent`; the buffer
@@ -16,5 +68,6 @@ export function replayTabEventsSince(ctx, ws, tabId, lastSeenSeq) {
16
68
  // `WebSocketResponse['type']`. Narrow here without an extra runtime check.
17
69
  ctx.send(ws, { type: event.type, tabId, data: event.data, seq: event.seq });
18
70
  }
71
+ return { sentCount: events.length, hadGap: false, lastSeenSeq };
19
72
  }
20
73
  //# sourceMappingURL=tab-event-replay.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tab-event-replay.js","sourceRoot":"","sources":["../../../../server/services/websocket/tab-event-replay.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAkB9D;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAClC,GAAmB,EACnB,EAAa,EACb,KAAa,EACb,WAA+B;IAE/B,IAAI,WAAW,KAAK,SAAS;QAAE,OAAM;IAErC,MAAM,MAAM,GAAG,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;IAC7C,IAAI,CAAC,MAAM;QAAE,OAAM;IAEnB,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAA;IAC3C,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,uEAAuE;QACvE,4DAA4D;QAC5D,2EAA2E;QAC3E,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,IAAiC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,CAAA;IAC1G,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"tab-event-replay.js","sourceRoot":"","sources":["../../../../server/services/websocket/tab-event-replay.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAE9D;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AA2B/C;;;;;;;;;GASG;AACH,MAAM,UAAU,oBAAoB,CAClC,GAAmB,EACnB,EAAa,EACb,KAAa,EACb,WAA+B;IAE/B,IAAI,WAAW,KAAK,SAAS;QAAE,OAAO,EAAE,SAAS,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAA;IAErE,MAAM,MAAM,GAAG,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;IAC7C,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,SAAS,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAA;IAEnD,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,WAAW,CAAC,CAAA;IAC9C,MAAM,iBAAiB,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,oBAAoB,EAAE,CAAC,CAAC,CAAC,SAAS,CAAA;IAE5E,IAAI,MAAM,EAAE,CAAC;QACX,0EAA0E;QAC1E,0EAA0E;QAC1E,kBAAkB;QAClB,MAAM,OAAO,GACX,qCAAqC,KAAK,mCAAmC,WAAW,IAAI;YAC5F,sCAAsC,iBAAiB,IAAI;YAC3D,WAAW,WAAW,GAAG,CAAC,KAAK,iBAAiB,+BAA+B;YAC/E,sEAAsE,CAAA;QACxE,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QACrB,IAAI,CAAC;YACH,gBAAgB,CAAC,IAAI,KAAK,CAAC,2BAA2B,CAAC,EAAE;gBACvD,OAAO,EAAE,kBAAkB;gBAC3B,KAAK;gBACL,WAAW;gBACX,iBAAiB;gBACjB,gBAAgB,EAAE,MAAM,CAAC,UAAU,EAAE;gBACrC,OAAO,EAAE,CAAC,iBAAiB,IAAI,CAAC,CAAC,GAAG,WAAW;aAChD,CAAC,CAAA;QACJ,CAAC;QAAC,MAAM,CAAC;YACP,0DAA0D;QAC5D,CAAC;QACD,yEAAyE;QACzE,wEAAwE;QACxE,yEAAyE;QACzE,uEAAuE;QACvE,kEAAkE;QAClE,wEAAwE;QACxE,uEAAuE;QACvE,OAAO,EAAE,SAAS,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,EAAE,WAAW,EAAE,CAAA;IACvE,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAA;IAC3C,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,uEAAuE;QACvE,4DAA4D;QAC5D,2EAA2E;QAC3E,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,IAAiC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,CAAA;IAC1G,CAAC;IAED,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,CAAA;AACjE,CAAC"}
@@ -1,9 +1,17 @@
1
1
  import type { HandlerContext } from './handler-context.js';
2
- import type { WebSocketMessage, WSContext } from './types.js';
2
+ import { type WebSocketMessage, type WSContext } from './types.js';
3
3
  export declare function handleGetActiveTabs(ctx: HandlerContext, ws: WSContext, workingDir: string): void;
4
4
  export declare function handleSyncTabMeta(ctx: HandlerContext, _ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string): void;
5
5
  export declare function handleRemoveTab(ctx: HandlerContext, _ws: WSContext, tabId: string, workingDir: string): void;
6
6
  export declare function handleMarkTabViewed(ctx: HandlerContext, _ws: WSContext, tabId: string, workingDir: string): void;
7
+ /**
8
+ * Persist a per-tab engine override. `msg.data.override` is either a full
9
+ * `{ engine, model, effortLevel }` payload or `null` to clear the override.
10
+ * Persisted via the session registry so the override survives WebSocket
11
+ * disconnects — the core guarantee of IS-019. Broadcasts the change to all
12
+ * connected clients so multi-device sessions stay in sync.
13
+ */
14
+ export declare function handleSetTabEngine(ctx: HandlerContext, _ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string): void;
7
15
  export declare function handleCreateTab(ctx: HandlerContext, ws: WSContext, workingDir: string, tabName?: string, optimisticTabId?: string): Promise<void>;
8
16
  export declare function handleReorderTabs(ctx: HandlerContext, _ws: WSContext, workingDir: string, tabOrder?: string[]): void;
9
17
  //# sourceMappingURL=tab-handlers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tab-handlers.d.ts","sourceRoot":"","sources":["../../../../server/services/websocket/tab-handlers.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAuC9D,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,cAAc,EAAE,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAqBhG;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,cAAc,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CASrI;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,cAAc,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAY5G;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,cAAc,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAQhH;AAED,wBAAsB,eAAe,CAAC,GAAG,EAAE,cAAc,EAAE,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAiDvJ;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,cAAc,EAAE,GAAG,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAcpH"}
1
+ {"version":3,"file":"tab-handlers.d.ts","sourceRoot":"","sources":["../../../../server/services/websocket/tab-handlers.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAG3D,OAAO,EAAoC,KAAK,gBAAgB,EAAE,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AA2CrG,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,cAAc,EAAE,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAqBhG;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,cAAc,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CASrI;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,cAAc,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAY5G;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,cAAc,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAQhH;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,cAAc,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CA0BtI;AAED,wBAAsB,eAAe,CAAC,GAAG,EAAE,cAAc,EAAE,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyDvJ;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,cAAc,EAAE,GAAG,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAcpH"}