devchain-cli 0.17.1 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (356) hide show
  1. package/README.md +64 -96
  2. package/dist/drizzle/0067_remove_claude_1m_provider_state.sql +3 -0
  3. package/dist/drizzle/0068_claude_launch_settings_json.sql +1 -0
  4. package/dist/drizzle/meta/0067_snapshot.json +5743 -0
  5. package/dist/drizzle/meta/0068_snapshot.json +5750 -0
  6. package/dist/drizzle/meta/_journal.json +15 -1
  7. package/dist/node_modules/@devchain/codebase-overview/tsconfig.tsbuildinfo +1 -1
  8. package/dist/node_modules/@devchain/shared/index.d.ts +1 -1
  9. package/dist/node_modules/@devchain/shared/index.d.ts.map +1 -1
  10. package/dist/node_modules/@devchain/shared/index.js +1 -1
  11. package/dist/node_modules/@devchain/shared/index.js.map +1 -1
  12. package/dist/node_modules/@devchain/shared/schemas/claude-launch-settings.d.ts +12 -0
  13. package/dist/node_modules/@devchain/shared/schemas/claude-launch-settings.d.ts.map +1 -0
  14. package/dist/node_modules/@devchain/shared/schemas/claude-launch-settings.js +97 -0
  15. package/dist/node_modules/@devchain/shared/schemas/claude-launch-settings.js.map +1 -0
  16. package/dist/node_modules/@devchain/shared/schemas/export-schema.d.ts +18 -10
  17. package/dist/node_modules/@devchain/shared/schemas/export-schema.d.ts.map +1 -1
  18. package/dist/node_modules/@devchain/shared/schemas/export-schema.js +11 -2
  19. package/dist/node_modules/@devchain/shared/schemas/export-schema.js.map +1 -1
  20. package/dist/node_modules/@devchain/shared/schemas/index.d.ts +1 -0
  21. package/dist/node_modules/@devchain/shared/schemas/index.d.ts.map +1 -1
  22. package/dist/node_modules/@devchain/shared/schemas/index.js +1 -0
  23. package/dist/node_modules/@devchain/shared/schemas/index.js.map +1 -1
  24. package/dist/node_modules/@devchain/shared/session-migration-reasons.d.ts +20 -0
  25. package/dist/node_modules/@devchain/shared/session-migration-reasons.d.ts.map +1 -0
  26. package/dist/node_modules/@devchain/shared/session-migration-reasons.js +109 -0
  27. package/dist/node_modules/@devchain/shared/session-migration-reasons.js.map +1 -0
  28. package/dist/node_modules/@devchain/shared/tsconfig.tsbuildinfo +1 -1
  29. package/dist/server/app.main.module.js +2 -0
  30. package/dist/server/app.main.module.js.map +1 -1
  31. package/dist/server/app.normal.module.js +2 -0
  32. package/dist/server/app.normal.module.js.map +1 -1
  33. package/dist/server/common/prompt-references.d.ts +29 -0
  34. package/dist/server/common/prompt-references.js +84 -0
  35. package/dist/server/common/prompt-references.js.map +1 -0
  36. package/dist/server/common/prompt-transfer.d.ts +31 -0
  37. package/dist/server/common/prompt-transfer.js +38 -0
  38. package/dist/server/common/prompt-transfer.js.map +1 -0
  39. package/dist/server/common/prompt-type.d.ts +12 -0
  40. package/dist/server/common/prompt-type.js +46 -0
  41. package/dist/server/common/prompt-type.js.map +1 -0
  42. package/dist/server/common/tags.d.ts +7 -0
  43. package/dist/server/common/tags.js +21 -0
  44. package/dist/server/common/tags.js.map +1 -0
  45. package/dist/server/main.js +9 -1
  46. package/dist/server/main.js.map +1 -1
  47. package/dist/server/modules/cloud-tunnel/services/mobile-chat-rpc.service.d.ts +9 -0
  48. package/dist/server/modules/cloud-tunnel/services/mobile-chat-rpc.service.js +33 -0
  49. package/dist/server/modules/cloud-tunnel/services/mobile-chat-rpc.service.js.map +1 -1
  50. package/dist/server/modules/cloud-tunnel/services/tunnel-handler.service.js +15 -0
  51. package/dist/server/modules/cloud-tunnel/services/tunnel-handler.service.js.map +1 -1
  52. package/dist/server/modules/core/services/preflight.service.js +28 -4
  53. package/dist/server/modules/core/services/preflight.service.js.map +1 -1
  54. package/dist/server/modules/events/catalog/broadcast-registry.js +29 -12
  55. package/dist/server/modules/events/catalog/broadcast-registry.js.map +1 -1
  56. package/dist/server/modules/events/catalog/index.d.ts +30 -3
  57. package/dist/server/modules/events/catalog/index.js +2 -0
  58. package/dist/server/modules/events/catalog/index.js.map +1 -1
  59. package/dist/server/modules/events/catalog/session.runtime-context.updated.d.ts +12 -0
  60. package/dist/server/modules/events/catalog/session.runtime-context.updated.js +13 -0
  61. package/dist/server/modules/events/catalog/session.runtime-context.updated.js.map +1 -0
  62. package/dist/server/modules/events/catalog/session.transcript.updated.d.ts +23 -3
  63. package/dist/server/modules/events/catalog/session.transcript.updated.js +41 -19
  64. package/dist/server/modules/events/catalog/session.transcript.updated.js.map +1 -1
  65. package/dist/server/modules/hooks/dtos/hook-event.dto.d.ts +50 -0
  66. package/dist/server/modules/hooks/dtos/hook-event.dto.js +18 -1
  67. package/dist/server/modules/hooks/dtos/hook-event.dto.js.map +1 -1
  68. package/dist/server/modules/hooks/hooks.module.js +2 -1
  69. package/dist/server/modules/hooks/hooks.module.js.map +1 -1
  70. package/dist/server/modules/hooks/services/hooks.service.d.ts +4 -1
  71. package/dist/server/modules/hooks/services/hooks.service.js +15 -3
  72. package/dist/server/modules/hooks/services/hooks.service.js.map +1 -1
  73. package/dist/server/modules/mcp/dtos/mcp.dto.d.ts +24 -0
  74. package/dist/server/modules/mcp/dtos/mcp.dto.js.map +1 -1
  75. package/dist/server/modules/mcp/services/handlers/agent-context.d.ts +2 -2
  76. package/dist/server/modules/mcp/services/handlers/agent-tools.js +103 -16
  77. package/dist/server/modules/mcp/services/handlers/agent-tools.js.map +1 -1
  78. package/dist/server/modules/mcp/services/handlers/prompt-tools.js +11 -9
  79. package/dist/server/modules/mcp/services/handlers/prompt-tools.js.map +1 -1
  80. package/dist/server/modules/mcp/services/instructions-resolver.js +14 -10
  81. package/dist/server/modules/mcp/services/instructions-resolver.js.map +1 -1
  82. package/dist/server/modules/mcp/tool-descriptors/agent.metadata.js +1 -1
  83. package/dist/server/modules/mcp/tool-descriptors/agent.metadata.js.map +1 -1
  84. package/dist/server/modules/metrics/controllers/metrics.controller.d.ts +6 -0
  85. package/dist/server/modules/metrics/controllers/metrics.controller.js +38 -0
  86. package/dist/server/modules/metrics/controllers/metrics.controller.js.map +1 -0
  87. package/dist/server/modules/metrics/helpers/byte-accounting.helper.d.ts +17 -0
  88. package/dist/server/modules/metrics/helpers/byte-accounting.helper.js +88 -0
  89. package/dist/server/modules/metrics/helpers/byte-accounting.helper.js.map +1 -0
  90. package/dist/server/modules/metrics/metrics.module.d.ts +2 -0
  91. package/dist/server/modules/metrics/metrics.module.js +23 -0
  92. package/dist/server/modules/metrics/metrics.module.js.map +1 -0
  93. package/dist/server/modules/metrics/services/metrics.service.d.ts +20 -0
  94. package/dist/server/modules/metrics/services/metrics.service.js +241 -0
  95. package/dist/server/modules/metrics/services/metrics.service.js.map +1 -0
  96. package/dist/server/modules/metrics/types/metrics.types.d.ts +100 -0
  97. package/dist/server/modules/{providers/adapters/capabilities/context-window.capability.js → metrics/types/metrics.types.js} +1 -1
  98. package/dist/server/modules/metrics/types/metrics.types.js.map +1 -0
  99. package/dist/server/modules/profiles/dto.d.ts +2 -2
  100. package/dist/server/modules/profiles/dto.js +14 -2
  101. package/dist/server/modules/profiles/dto.js.map +1 -1
  102. package/dist/server/modules/projects/controllers/projects.controller.d.ts +5 -6
  103. package/dist/server/modules/projects/helpers/profile-mapping.helpers.d.ts +5 -3
  104. package/dist/server/modules/projects/helpers/profile-mapping.helpers.js +25 -13
  105. package/dist/server/modules/projects/helpers/profile-mapping.helpers.js.map +1 -1
  106. package/dist/server/modules/projects/helpers/project-export.d.ts +0 -2
  107. package/dist/server/modules/projects/helpers/project-export.js +7 -3
  108. package/dist/server/modules/projects/helpers/project-export.js.map +1 -1
  109. package/dist/server/modules/projects/helpers/project-import.d.ts +7 -6
  110. package/dist/server/modules/projects/helpers/project-import.js +66 -105
  111. package/dist/server/modules/projects/helpers/project-import.js.map +1 -1
  112. package/dist/server/modules/projects/helpers/project-runtime.helpers.d.ts +1 -0
  113. package/dist/server/modules/projects/helpers/project-runtime.helpers.js +2 -1
  114. package/dist/server/modules/projects/helpers/project-runtime.helpers.js.map +1 -1
  115. package/dist/server/modules/projects/helpers/template-loader.d.ts +3 -3
  116. package/dist/server/modules/projects/helpers/template-loader.js +32 -12
  117. package/dist/server/modules/projects/helpers/template-loader.js.map +1 -1
  118. package/dist/server/modules/projects/projects.module.js +0 -2
  119. package/dist/server/modules/projects/projects.module.js.map +1 -1
  120. package/dist/server/modules/projects/services/project-registry-import.service.d.ts +2 -0
  121. package/dist/server/modules/projects/services/project-registry-import.service.js +29 -1
  122. package/dist/server/modules/projects/services/project-registry-import.service.js.map +1 -1
  123. package/dist/server/modules/projects/services/project-template-upgrade.service.d.ts +5 -0
  124. package/dist/server/modules/projects/services/project-template-upgrade.service.js +22 -2
  125. package/dist/server/modules/projects/services/project-template-upgrade.service.js.map +1 -1
  126. package/dist/server/modules/projects/services/projects.service.d.ts +12 -20
  127. package/dist/server/modules/projects/services/projects.service.js +8 -9
  128. package/dist/server/modules/projects/services/projects.service.js.map +1 -1
  129. package/dist/server/modules/projects/template-codec/codec-registry.js.map +1 -1
  130. package/dist/server/modules/projects/template-codec/codecs/agents.codec.d.ts +1 -1
  131. package/dist/server/modules/projects/template-codec/codecs/agents.codec.js +4 -4
  132. package/dist/server/modules/projects/template-codec/codecs/agents.codec.js.map +1 -1
  133. package/dist/server/modules/projects/template-codec/codecs/profiles.codec.d.ts +1 -1
  134. package/dist/server/modules/projects/template-codec/codecs/profiles.codec.js +27 -8
  135. package/dist/server/modules/projects/template-codec/codecs/profiles.codec.js.map +1 -1
  136. package/dist/server/modules/projects/template-codec/codecs/project-settings.codec.js +8 -1
  137. package/dist/server/modules/projects/template-codec/codecs/project-settings.codec.js.map +1 -1
  138. package/dist/server/modules/projects/template-codec/codecs/prompts.codec.js +23 -4
  139. package/dist/server/modules/projects/template-codec/codecs/prompts.codec.js.map +1 -1
  140. package/dist/server/modules/projects/template-codec/codecs/provider-settings.codec.d.ts +0 -5
  141. package/dist/server/modules/projects/template-codec/codecs/provider-settings.codec.js +10 -46
  142. package/dist/server/modules/projects/template-codec/codecs/provider-settings.codec.js.map +1 -1
  143. package/dist/server/modules/projects/template-codec/codecs/watchers.codec.js +1 -1
  144. package/dist/server/modules/projects/template-codec/codecs/watchers.codec.js.map +1 -1
  145. package/dist/server/modules/projects/template-codec/import-context.d.ts +6 -1
  146. package/dist/server/modules/projects/template-codec/import-context.js.map +1 -1
  147. package/dist/server/modules/projects/template-codec/template-section-codec.d.ts +7 -5
  148. package/dist/server/modules/providers/adapters/capabilities/auto-compact.capability.d.ts +13 -0
  149. package/dist/server/modules/providers/adapters/capabilities/auto-compact.capability.js +3 -0
  150. package/dist/server/modules/providers/adapters/capabilities/auto-compact.capability.js.map +1 -0
  151. package/dist/server/modules/providers/adapters/capabilities/index.d.ts +2 -2
  152. package/dist/server/modules/providers/adapters/capabilities/index.js +2 -2
  153. package/dist/server/modules/providers/adapters/capabilities/index.js.map +1 -1
  154. package/dist/server/modules/providers/adapters/capabilities/type-guards.d.ts +3 -3
  155. package/dist/server/modules/providers/adapters/capabilities/type-guards.js +3 -3
  156. package/dist/server/modules/providers/adapters/capabilities/type-guards.js.map +1 -1
  157. package/dist/server/modules/providers/adapters/claude.adapter.d.ts +3 -7
  158. package/dist/server/modules/providers/adapters/claude.adapter.js +5 -45
  159. package/dist/server/modules/providers/adapters/claude.adapter.js.map +1 -1
  160. package/dist/server/modules/providers/controllers/providers.controller.d.ts +3 -7
  161. package/dist/server/modules/providers/controllers/providers.controller.js +2 -14
  162. package/dist/server/modules/providers/controllers/providers.controller.js.map +1 -1
  163. package/dist/server/modules/providers/providers.module.js +0 -2
  164. package/dist/server/modules/providers/providers.module.js.map +1 -1
  165. package/dist/server/modules/providers/services/provider-state-manager.service.d.ts +4 -10
  166. package/dist/server/modules/providers/services/provider-state-manager.service.js +28 -83
  167. package/dist/server/modules/providers/services/provider-state-manager.service.js.map +1 -1
  168. package/dist/server/modules/registry/controllers/templates.controller.d.ts +2 -4
  169. package/dist/server/modules/runtime-context-capture/claude-launch-settings-materializer.service.d.ts +32 -0
  170. package/dist/server/modules/runtime-context-capture/claude-launch-settings-materializer.service.js +364 -0
  171. package/dist/server/modules/runtime-context-capture/claude-launch-settings-materializer.service.js.map +1 -0
  172. package/dist/server/modules/runtime-context-capture/context-window-policy.d.ts +40 -0
  173. package/dist/server/modules/runtime-context-capture/context-window-policy.js +73 -0
  174. package/dist/server/modules/runtime-context-capture/context-window-policy.js.map +1 -0
  175. package/dist/server/modules/runtime-context-capture/index.d.ts +6 -0
  176. package/dist/server/modules/runtime-context-capture/index.js +23 -0
  177. package/dist/server/modules/runtime-context-capture/index.js.map +1 -0
  178. package/dist/server/modules/runtime-context-capture/runtime-context-capture-files.d.ts +4 -0
  179. package/dist/server/modules/runtime-context-capture/runtime-context-capture-files.js +40 -0
  180. package/dist/server/modules/runtime-context-capture/runtime-context-capture-files.js.map +1 -0
  181. package/dist/server/modules/runtime-context-capture/runtime-context-capture.module.d.ts +2 -0
  182. package/dist/server/modules/runtime-context-capture/runtime-context-capture.module.js +25 -0
  183. package/dist/server/modules/runtime-context-capture/runtime-context-capture.module.js.map +1 -0
  184. package/dist/server/modules/runtime-context-capture/runtime-context-capture.service.d.ts +25 -0
  185. package/dist/server/modules/runtime-context-capture/runtime-context-capture.service.js +155 -0
  186. package/dist/server/modules/runtime-context-capture/runtime-context-capture.service.js.map +1 -0
  187. package/dist/server/modules/runtime-context-capture/runtime-context-capture.types.d.ts +44 -0
  188. package/dist/server/modules/runtime-context-capture/runtime-context-capture.types.js +5 -0
  189. package/dist/server/modules/runtime-context-capture/runtime-context-capture.types.js.map +1 -0
  190. package/dist/server/modules/seeders/seeders/0011_seed_remove_claude_1m_provider_env.d.ts +3 -0
  191. package/dist/server/modules/seeders/seeders/0011_seed_remove_claude_1m_provider_env.js +31 -0
  192. package/dist/server/modules/seeders/seeders/0011_seed_remove_claude_1m_provider_env.js.map +1 -0
  193. package/dist/server/modules/seeders/seeders/0012_seed_claude_launch_settings.d.ts +3 -0
  194. package/dist/server/modules/seeders/seeders/0012_seed_claude_launch_settings.js +28 -0
  195. package/dist/server/modules/seeders/seeders/0012_seed_claude_launch_settings.js.map +1 -0
  196. package/dist/server/modules/seeders/seeders/0013_seed_prompt_type_tags.d.ts +3 -0
  197. package/dist/server/modules/seeders/seeders/0013_seed_prompt_type_tags.js +112 -0
  198. package/dist/server/modules/seeders/seeders/0013_seed_prompt_type_tags.js.map +1 -0
  199. package/dist/server/modules/seeders/services/data-seeder.service.js +6 -0
  200. package/dist/server/modules/seeders/services/data-seeder.service.js.map +1 -1
  201. package/dist/server/modules/session-reader/adapters/antigravity-session-reader.adapter.d.ts +6 -0
  202. package/dist/server/modules/session-reader/adapters/antigravity-session-reader.adapter.js +66 -0
  203. package/dist/server/modules/session-reader/adapters/antigravity-session-reader.adapter.js.map +1 -1
  204. package/dist/server/modules/session-reader/adapters/claude-session-reader.adapter.d.ts +5 -0
  205. package/dist/server/modules/session-reader/adapters/claude-session-reader.adapter.js +12 -0
  206. package/dist/server/modules/session-reader/adapters/claude-session-reader.adapter.js.map +1 -1
  207. package/dist/server/modules/session-reader/adapters/codex-session-reader.adapter.d.ts +5 -0
  208. package/dist/server/modules/session-reader/adapters/codex-session-reader.adapter.js +12 -0
  209. package/dist/server/modules/session-reader/adapters/codex-session-reader.adapter.js.map +1 -1
  210. package/dist/server/modules/session-reader/adapters/copilot-session-reader.adapter.d.ts +5 -0
  211. package/dist/server/modules/session-reader/adapters/copilot-session-reader.adapter.js +12 -0
  212. package/dist/server/modules/session-reader/adapters/copilot-session-reader.adapter.js.map +1 -1
  213. package/dist/server/modules/session-reader/adapters/opencode-session-reader.adapter.d.ts +5 -0
  214. package/dist/server/modules/session-reader/adapters/opencode-session-reader.adapter.js +15 -0
  215. package/dist/server/modules/session-reader/adapters/opencode-session-reader.adapter.js.map +1 -1
  216. package/dist/server/modules/session-reader/adapters/session-reader-adapter.interface.d.ts +10 -0
  217. package/dist/server/modules/session-reader/adapters/session-reader-adapter.interface.js +28 -1
  218. package/dist/server/modules/session-reader/adapters/session-reader-adapter.interface.js.map +1 -1
  219. package/dist/server/modules/session-reader/controllers/session-reader.controller.d.ts +10 -4
  220. package/dist/server/modules/session-reader/controllers/session-reader.controller.js +18 -19
  221. package/dist/server/modules/session-reader/controllers/session-reader.controller.js.map +1 -1
  222. package/dist/server/modules/session-reader/data/pricing.json +334 -6
  223. package/dist/server/modules/session-reader/parsers/claude-jsonl.parser.d.ts +1 -0
  224. package/dist/server/modules/session-reader/parsers/claude-jsonl.parser.js +7 -3
  225. package/dist/server/modules/session-reader/parsers/claude-jsonl.parser.js.map +1 -1
  226. package/dist/server/modules/session-reader/parsers/codex-jsonl.parser.d.ts +1 -0
  227. package/dist/server/modules/session-reader/parsers/codex-jsonl.parser.js +22 -11
  228. package/dist/server/modules/session-reader/parsers/codex-jsonl.parser.js.map +1 -1
  229. package/dist/server/modules/session-reader/parsers/copilot-jsonl.parser.d.ts +1 -0
  230. package/dist/server/modules/session-reader/parsers/copilot-jsonl.parser.js +22 -9
  231. package/dist/server/modules/session-reader/parsers/copilot-jsonl.parser.js.map +1 -1
  232. package/dist/server/modules/session-reader/readers/opencode-sqlite.reader.d.ts +5 -1
  233. package/dist/server/modules/session-reader/readers/opencode-sqlite.reader.js +102 -0
  234. package/dist/server/modules/session-reader/readers/opencode-sqlite.reader.js.map +1 -1
  235. package/dist/server/modules/session-reader/services/pricing.interface.d.ts +2 -0
  236. package/dist/server/modules/session-reader/services/pricing.interface.js +3 -0
  237. package/dist/server/modules/session-reader/services/pricing.interface.js.map +1 -1
  238. package/dist/server/modules/session-reader/services/pricing.service.d.ts +1 -0
  239. package/dist/server/modules/session-reader/services/pricing.service.js +4 -2
  240. package/dist/server/modules/session-reader/services/pricing.service.js.map +1 -1
  241. package/dist/server/modules/session-reader/services/session-cache.service.d.ts +72 -4
  242. package/dist/server/modules/session-reader/services/session-cache.service.js +435 -68
  243. package/dist/server/modules/session-reader/services/session-cache.service.js.map +1 -1
  244. package/dist/server/modules/session-reader/services/session-reader.service.d.ts +33 -9
  245. package/dist/server/modules/session-reader/services/session-reader.service.js +200 -79
  246. package/dist/server/modules/session-reader/services/session-reader.service.js.map +1 -1
  247. package/dist/server/modules/session-reader/services/transcript-cursor.d.ts +1 -1
  248. package/dist/server/modules/session-reader/services/transcript-cursor.js +2 -2
  249. package/dist/server/modules/session-reader/services/transcript-cursor.js.map +1 -1
  250. package/dist/server/modules/session-reader/services/transcript-watcher.service.d.ts +3 -0
  251. package/dist/server/modules/session-reader/services/transcript-watcher.service.js +66 -11
  252. package/dist/server/modules/session-reader/services/transcript-watcher.service.js.map +1 -1
  253. package/dist/server/modules/session-reader/session-reader.module.js +12 -2
  254. package/dist/server/modules/session-reader/session-reader.module.js.map +1 -1
  255. package/dist/server/modules/sessions/dtos/sessions.dto.d.ts +1 -0
  256. package/dist/server/modules/sessions/dtos/sessions.dto.js.map +1 -1
  257. package/dist/server/modules/sessions/services/agent-presence-aggregator.d.ts +1 -0
  258. package/dist/server/modules/sessions/services/agent-presence-aggregator.js +1 -0
  259. package/dist/server/modules/sessions/services/agent-presence-aggregator.js.map +1 -1
  260. package/dist/server/modules/sessions/services/provider-launch-config/provider-launch-config.service.d.ts +6 -2
  261. package/dist/server/modules/sessions/services/provider-launch-config/provider-launch-config.service.js +24 -8
  262. package/dist/server/modules/sessions/services/provider-launch-config/provider-launch-config.service.js.map +1 -1
  263. package/dist/server/modules/sessions/services/session-runtime/__test-utils__/pipeline-harness.d.ts +30 -5
  264. package/dist/server/modules/sessions/services/session-runtime/__test-utils__/pipeline-harness.js +51 -8
  265. package/dist/server/modules/sessions/services/session-runtime/__test-utils__/pipeline-harness.js.map +1 -1
  266. package/dist/server/modules/sessions/services/session-runtime/session-launch-pipeline.service.d.ts +5 -1
  267. package/dist/server/modules/sessions/services/session-runtime/session-launch-pipeline.service.js +39 -6
  268. package/dist/server/modules/sessions/services/session-runtime/session-launch-pipeline.service.js.map +1 -1
  269. package/dist/server/modules/sessions/services/session-runtime/session-restore-pipeline.service.d.ts +7 -1
  270. package/dist/server/modules/sessions/services/session-runtime/session-restore-pipeline.service.js +47 -5
  271. package/dist/server/modules/sessions/services/session-runtime/session-restore-pipeline.service.js.map +1 -1
  272. package/dist/server/modules/sessions/services/sessions.service.d.ts +6 -1
  273. package/dist/server/modules/sessions/services/sessions.service.js +17 -3
  274. package/dist/server/modules/sessions/services/sessions.service.js.map +1 -1
  275. package/dist/server/modules/sessions/sessions.module.js +2 -0
  276. package/dist/server/modules/sessions/sessions.module.js.map +1 -1
  277. package/dist/server/modules/sessions/utils/profile-options.d.ts +1 -2
  278. package/dist/server/modules/sessions/utils/profile-options.js +3 -50
  279. package/dist/server/modules/sessions/utils/profile-options.js.map +1 -1
  280. package/dist/server/modules/storage/db/schema.d.ts +10 -87
  281. package/dist/server/modules/storage/db/schema.js +3 -13
  282. package/dist/server/modules/storage/db/schema.js.map +1 -1
  283. package/dist/server/modules/storage/interfaces/snapshot-prompt-writer.interface.d.ts +5 -0
  284. package/dist/server/modules/storage/interfaces/snapshot-prompt-writer.interface.js +5 -0
  285. package/dist/server/modules/storage/interfaces/snapshot-prompt-writer.interface.js.map +1 -0
  286. package/dist/server/modules/storage/local/delegates/prompt.delegate.d.ts +2 -0
  287. package/dist/server/modules/storage/local/delegates/prompt.delegate.js +31 -20
  288. package/dist/server/modules/storage/local/delegates/prompt.delegate.js.map +1 -1
  289. package/dist/server/modules/storage/local/delegates/provider.delegate.js +61 -16
  290. package/dist/server/modules/storage/local/delegates/provider.delegate.js.map +1 -1
  291. package/dist/server/modules/storage/local/local-storage.service.d.ts +3 -1
  292. package/dist/server/modules/storage/local/local-storage.service.js +3 -0
  293. package/dist/server/modules/storage/local/local-storage.service.js.map +1 -1
  294. package/dist/server/modules/storage/models/domain.models.d.ts +2 -4
  295. package/dist/server/modules/storage/storage.module.js +8 -2
  296. package/dist/server/modules/storage/storage.module.js.map +1 -1
  297. package/dist/server/modules/terminal/dtos/ws-envelope.dto.d.ts +185 -1
  298. package/dist/server/modules/terminal/dtos/ws-envelope.dto.js +93 -2
  299. package/dist/server/modules/terminal/dtos/ws-envelope.dto.js.map +1 -1
  300. package/dist/server/modules/terminal/gateways/terminal.gateway.d.ts +47 -2
  301. package/dist/server/modules/terminal/gateways/terminal.gateway.js +615 -62
  302. package/dist/server/modules/terminal/gateways/terminal.gateway.js.map +1 -1
  303. package/dist/server/modules/terminal/services/process-executor/fake-process-executor.d.ts +1 -0
  304. package/dist/server/modules/terminal/services/process-executor/fake-process-executor.js +1 -0
  305. package/dist/server/modules/terminal/services/process-executor/fake-process-executor.js.map +1 -1
  306. package/dist/server/modules/terminal/services/pty.service.d.ts +8 -3
  307. package/dist/server/modules/terminal/services/pty.service.js +11 -2
  308. package/dist/server/modules/terminal/services/pty.service.js.map +1 -1
  309. package/dist/server/modules/terminal/services/terminal-io/capture.js +2 -0
  310. package/dist/server/modules/terminal/services/terminal-io/capture.js.map +1 -1
  311. package/dist/server/modules/terminal/services/terminal-io/delivery.d.ts +1 -0
  312. package/dist/server/modules/terminal/services/terminal-io/delivery.js.map +1 -1
  313. package/dist/server/modules/terminal/services/terminal-io/terminal-io.service.js +28 -1
  314. package/dist/server/modules/terminal/services/terminal-io/terminal-io.service.js.map +1 -1
  315. package/dist/server/modules/terminal/services/terminal-key-input/terminal-key-input.facade.d.ts +6 -0
  316. package/dist/server/modules/terminal/services/terminal-key-input/terminal-key-input.facade.js +26 -1
  317. package/dist/server/modules/terminal/services/terminal-key-input/terminal-key-input.facade.js.map +1 -1
  318. package/dist/server/modules/terminal/services/terminal-seed.service.d.ts +27 -3
  319. package/dist/server/modules/terminal/services/terminal-seed.service.js +75 -19
  320. package/dist/server/modules/terminal/services/terminal-seed.service.js.map +1 -1
  321. package/dist/server/modules/terminal/services/terminal-send-scheduler.service.d.ts +73 -0
  322. package/dist/server/modules/terminal/services/terminal-send-scheduler.service.js +387 -0
  323. package/dist/server/modules/terminal/services/terminal-send-scheduler.service.js.map +1 -0
  324. package/dist/server/modules/terminal/services/terminal-session/terminal-session.d.ts +4 -10
  325. package/dist/server/modules/terminal/services/terminal-session/terminal-session.js +20 -48
  326. package/dist/server/modules/terminal/services/terminal-session/terminal-session.js.map +1 -1
  327. package/dist/server/modules/terminal/services/terminal-socket-drain.adapter.d.ts +10 -0
  328. package/dist/server/modules/terminal/services/terminal-socket-drain.adapter.js +75 -0
  329. package/dist/server/modules/terminal/services/terminal-socket-drain.adapter.js.map +1 -0
  330. package/dist/server/modules/terminal/services/terminal-stream.service.d.ts +53 -6
  331. package/dist/server/modules/terminal/services/terminal-stream.service.js +190 -35
  332. package/dist/server/modules/terminal/services/terminal-stream.service.js.map +1 -1
  333. package/dist/server/modules/terminal/terminal.module.js +7 -0
  334. package/dist/server/modules/terminal/terminal.module.js.map +1 -1
  335. package/dist/server/templates/3-agents-dev.json +120 -105
  336. package/dist/server/templates/teams-dev.json +204 -180
  337. package/dist/server/tsconfig.tsbuildinfo +1 -1
  338. package/dist/server/ui/assets/{ReviewDetailPage-DdJqgIFY.js → ReviewDetailPage-CkB_Fx1C.js} +1 -1
  339. package/dist/server/ui/assets/{ReviewsPage-iWEVgAdp.js → ReviewsPage-Bx6-dM75.js} +1 -1
  340. package/dist/server/ui/assets/index-5SDRTmwU.js +1156 -0
  341. package/dist/server/ui/assets/index-PyQSKUPl.css +32 -0
  342. package/dist/server/ui/assets/{useReviewSubscription-DCXbfBtE.js → useReviewSubscription-BZxAz2ZG.js} +1 -1
  343. package/dist/server/ui/index.html +2 -2
  344. package/dist/templates/3-agents-dev.json +120 -105
  345. package/dist/templates/teams-dev.json +204 -180
  346. package/package.json +20 -5
  347. package/dist/server/modules/providers/adapters/capabilities/context-window.capability.d.ts +0 -20
  348. package/dist/server/modules/providers/adapters/capabilities/context-window.capability.js.map +0 -1
  349. package/dist/server/modules/providers/services/probe-proof.service.d.ts +0 -8
  350. package/dist/server/modules/providers/services/probe-proof.service.js +0 -49
  351. package/dist/server/modules/providers/services/probe-proof.service.js.map +0 -1
  352. package/dist/server/modules/providers/utils/probe-1m.d.ts +0 -8
  353. package/dist/server/modules/providers/utils/probe-1m.js +0 -76
  354. package/dist/server/modules/providers/utils/probe-1m.js.map +0 -1
  355. package/dist/server/ui/assets/index-CM2KuqcE.js +0 -1113
  356. package/dist/server/ui/assets/index-COvyD_66.css +0 -32
@@ -12,11 +12,12 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
12
  return function (target, key) { decorator(target, key, paramIndex); }
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.TerminalGateway = void 0;
15
+ exports.TerminalGateway = exports.PROMPT_PASTE_MAX_REQUESTS_PER_SESSION = exports.PROMPT_PASTE_RETRY_WINDOW_MS = void 0;
16
16
  const websockets_1 = require("@nestjs/websockets");
17
17
  const common_1 = require("@nestjs/common");
18
18
  const event_emitter_1 = require("@nestjs/event-emitter");
19
19
  const socket_io_1 = require("socket.io");
20
+ const node_crypto_1 = require("node:crypto");
20
21
  const logger_1 = require("../../../common/logging/logger");
21
22
  const terminal_stream_service_1 = require("../services/terminal-stream.service");
22
23
  const pty_service_1 = require("../services/pty.service");
@@ -29,16 +30,33 @@ const ws_envelope_dto_1 = require("../dtos/ws-envelope.dto");
29
30
  const sessions_service_1 = require("../../sessions/services/sessions.service");
30
31
  const normalize_line_endings_1 = require("../utils/normalize-line-endings");
31
32
  const realtime_broadcast_service_1 = require("../../realtime/services/realtime-broadcast.service");
33
+ const metrics_service_1 = require("../../metrics/services/metrics.service");
34
+ const terminal_send_scheduler_service_1 = require("../services/terminal-send-scheduler.service");
32
35
  const logger = (0, logger_1.createLogger)('TerminalGateway');
33
36
  const THEME_HEX_RE = /^#[0-9a-fA-F]{6}$/;
34
37
  const HEARTBEAT_INTERVAL = 30000;
35
38
  const HEARTBEAT_TIMEOUT = 45000;
36
39
  const VIEWPORT_RESTORE_COALESCE_MS = 500;
40
+ const STOPPED_REPLAY_RETENTION_MS = 60000;
37
41
  const INPUT_RATE_WINDOW_MS = 5000;
38
42
  const INPUT_RATE_MSG_THRESHOLD = 500;
39
43
  const INPUT_RATE_BYTES_THRESHOLD = 512000;
44
+ exports.PROMPT_PASTE_RETRY_WINDOW_MS = 60000;
45
+ exports.PROMPT_PASTE_MAX_REQUESTS_PER_SESSION = 64;
46
+ function classifySubscribeCursor(lastSequence, sequenceEpoch) {
47
+ const sequence = typeof lastSequence === 'number' && Number.isFinite(lastSequence) ? lastSequence : undefined;
48
+ const epoch = typeof sequenceEpoch === 'string' && sequenceEpoch.length > 0 ? sequenceEpoch : undefined;
49
+ if (sequence !== undefined && epoch !== undefined) {
50
+ return { kind: 'cursor', cursor: { sequenceEpoch: epoch, sequence } };
51
+ }
52
+ if (sequence !== undefined)
53
+ return { kind: 'legacy-gap', sequence };
54
+ if (epoch !== undefined)
55
+ return { kind: 'invalid' };
56
+ return { kind: 'first-attach' };
57
+ }
40
58
  let TerminalGateway = class TerminalGateway {
41
- constructor(streamService, settingsService, ptyService, seedService, terminalIO, registry, sessionsService, realtimeBroadcast) {
59
+ constructor(streamService, settingsService, ptyService, seedService, terminalIO, registry, sessionsService, realtimeBroadcast, sendScheduler, metricsService) {
42
60
  this.streamService = streamService;
43
61
  this.settingsService = settingsService;
44
62
  this.ptyService = ptyService;
@@ -47,24 +65,41 @@ let TerminalGateway = class TerminalGateway {
47
65
  this.registry = registry;
48
66
  this.sessionsService = sessionsService;
49
67
  this.realtimeBroadcast = realtimeBroadcast;
68
+ this.sendScheduler = sendScheduler;
69
+ this.metricsService = metricsService;
50
70
  this.clientSessions = new Map();
51
71
  this.frameListeners = new Map();
52
72
  this.inputRateTracker = new Map();
53
73
  this.themeCache = new Map();
54
74
  this.viewportRestoreAt = new Map();
75
+ this.recoveries = new Map();
76
+ this.targetedSeedAttempts = new Map();
77
+ this.promptPasteLedger = new Map();
78
+ this.promptPasteExpiryTimers = new Map();
79
+ this.streamService.setClearExpiryHandler((sessionId) => this.retireSessionRecoveries(sessionId));
55
80
  }
56
81
  afterInit() {
57
82
  this.realtimeBroadcast.setServer(this.server);
83
+ this.metricsService.registerStatsProvider('sockets', () => this.getSocketStats());
58
84
  logger.info('WebSocket gateway initialized');
59
85
  this.startHeartbeat();
60
86
  }
87
+ getSocketStats() {
88
+ return {
89
+ connectedClients: this.clientSessions.size,
90
+ ...this.sendScheduler.getStats(),
91
+ ...(this.seedService.getCaptureStats?.() ?? {}),
92
+ };
93
+ }
61
94
  handleConnection(client) {
62
95
  logger.info({ clientId: client.id, transport: client.conn?.transport?.name }, 'Client connected');
63
96
  this.clientSessions.set(client.id, {
97
+ socket: client,
64
98
  sessionId: '',
65
99
  lastHeartbeat: new Date(),
66
100
  subscriptions: new Set(),
67
101
  });
102
+ this.sendScheduler.registerSocket(client);
68
103
  this.sendHeartbeat(client);
69
104
  }
70
105
  handleDisconnect(client) {
@@ -87,41 +122,39 @@ let TerminalGateway = class TerminalGateway {
87
122
  this.registry.get(sessionId)?.unsubscribe(clientId);
88
123
  }
89
124
  }
125
+ this.pruneInputRateEntries((key) => key.startsWith(`${clientId}:`));
126
+ for (const [key, recovery] of this.recoveries) {
127
+ if (recovery.socketId === clientId)
128
+ this.recoveries.delete(key);
129
+ }
130
+ for (const key of this.targetedSeedAttempts.keys()) {
131
+ if (key.startsWith(`${clientId}:`))
132
+ this.targetedSeedAttempts.delete(key);
133
+ }
134
+ this.sendScheduler.removeSocket(clientId);
90
135
  this.clientSessions.delete(clientId);
91
136
  }
92
137
  async handleSubscribe(client, payload) {
93
- const { sessionId, lastSequence, rows, cols } = payload;
94
- logger.info({ clientId: client.id, sessionId, lastSequence, rows, cols }, 'Client subscribing');
138
+ const { sessionId, lastSequence, sequenceEpoch, rows, cols } = payload;
139
+ logger.info({ clientId: client.id, sessionId, lastSequence, sequenceEpoch, rows, cols }, 'Client subscribing');
95
140
  const clientSession = this.clientSessions.get(client.id);
96
141
  if (!clientSession)
97
142
  return;
143
+ this.cancelReplayCleanup(sessionId);
98
144
  clientSession.sessionId = sessionId;
99
145
  clientSession.subscriptions.add(`session/${sessionId}`);
100
146
  client.join(`session:${sessionId}`);
101
147
  this.streamService.initializeBuffer(sessionId);
148
+ const cursor = classifySubscribeCursor(lastSequence, sequenceEpoch);
102
149
  const session = this.registry.get(sessionId);
103
150
  if (!session) {
104
151
  logger.warn({ sessionId }, 'No registry entry — using fallback seed path');
105
- client.emit('message', (0, ws_envelope_dto_1.createEnvelope)(`terminal/${sessionId}`, 'subscribed', {
106
- sessionId,
107
- currentSequence: 0,
108
- }));
152
+ const decision = this.resolveSubscribeReplay(sessionId, cursor);
153
+ this.emitSubscribed(client, sessionId, decision.cursorSnapshot.currentSequence, decision.replayStatus, decision.cursorSnapshot.sequenceEpoch);
109
154
  clientSession.subscriptions.add(`terminal/${sessionId}`);
110
155
  client.join(`terminal:${sessionId}`);
111
- if (typeof lastSequence !== 'number') {
112
- const { maxBytes: seedMaxBytes } = this.seedService.resolveSeedingConfig();
113
- this.seedService
114
- .emitSeedToClient({
115
- client,
116
- sessionId,
117
- maxBytes: seedMaxBytes,
118
- cols,
119
- rows,
120
- })
121
- .catch((error) => {
122
- logger.error({ sessionId, clientId: client.id, error }, 'Fallback seed failed');
123
- });
124
- }
156
+ this.streamService.resumeRetention(sessionId);
157
+ await this.applySubscribeReplay(client, sessionId, cursor, decision, cols, rows, false);
125
158
  return;
126
159
  }
127
160
  const tmuxAlive = await this.terminalIO.sessionExists({ name: session.tmuxSessionName });
@@ -130,13 +163,13 @@ let TerminalGateway = class TerminalGateway {
130
163
  return;
131
164
  }
132
165
  await this.ensurePtyStreaming(sessionId, session.tmuxSessionName, { cols, rows });
133
- const isFirstAttach = typeof lastSequence !== 'number';
166
+ const isSeedAttach = cursor.kind === 'first-attach' || cursor.kind === 'invalid';
134
167
  const validDims = typeof rows === 'number' && rows > 0 && typeof cols === 'number' && cols > 0;
135
168
  let wonInitialAuthority = false;
136
169
  if (validDims && session.claimInitialAuthority(client.id)) {
137
170
  wonInitialAuthority = true;
138
171
  this.ptyService.resize(sessionId, cols, rows);
139
- if (isFirstAttach) {
172
+ if (isSeedAttach) {
140
173
  this.seedService.invalidateCache(sessionId);
141
174
  await new Promise((r) => setTimeout(r, 50));
142
175
  }
@@ -145,26 +178,309 @@ let TerminalGateway = class TerminalGateway {
145
178
  logger.info({ sessionId, clientId: client.id }, 'Client gone mid-subscribe — skipping');
146
179
  return;
147
180
  }
148
- client.emit('message', (0, ws_envelope_dto_1.createEnvelope)(`terminal/${sessionId}`, 'subscribed', {
149
- sessionId,
150
- currentSequence: this.streamService.getCurrentSequence(sessionId),
151
- }));
181
+ const decision = this.resolveSubscribeReplay(sessionId, cursor);
182
+ this.emitSubscribed(client, sessionId, decision.cursorSnapshot.currentSequence, decision.replayStatus, decision.cursorSnapshot.sequenceEpoch);
152
183
  clientSession.subscriptions.add(`terminal/${sessionId}`);
153
184
  client.join(`terminal:${sessionId}`);
154
185
  this.wireFrameListener(sessionId);
155
- session.setUsesAlternateScreen(this.sessionsService.usesAlternateScreenFor(sessionId));
156
186
  if (wonInitialAuthority) {
157
187
  session.notifyInitialAuthority(client.id);
158
188
  }
159
189
  session.subscribe(client.id);
160
- if (!isFirstAttach) {
161
- const bufferedFrames = this.streamService.getFramesSince(sessionId, lastSequence);
162
- for (const frame of bufferedFrames) {
163
- client.emit('message', frame);
190
+ this.streamService.resumeRetention(sessionId);
191
+ await this.applySubscribeReplay(client, sessionId, cursor, decision, cols, rows, true);
192
+ }
193
+ resolveSubscribeReplay(sessionId, cursor) {
194
+ switch (cursor.kind) {
195
+ case 'cursor': {
196
+ const replay = this.streamService.getReconnectReplay(sessionId, cursor.cursor);
197
+ return {
198
+ replayStatus: replay.status,
199
+ cursorSnapshot: {
200
+ sequenceEpoch: replay.sequenceEpoch,
201
+ currentSequence: replay.currentSequence,
202
+ },
203
+ replay,
204
+ };
205
+ }
206
+ case 'legacy-gap':
207
+ return { replayStatus: 'gap', cursorSnapshot: this.streamService.sampleCursor(sessionId) };
208
+ case 'first-attach':
209
+ case 'invalid':
210
+ return { replayStatus: 'seed', cursorSnapshot: this.streamService.sampleCursor(sessionId) };
211
+ }
212
+ }
213
+ async applySubscribeReplay(client, sessionId, cursor, decision, cols, rows, restoreViewportOnCovered) {
214
+ switch (cursor.kind) {
215
+ case 'first-attach':
216
+ case 'invalid':
217
+ if (cursor.kind === 'invalid') {
218
+ logger.warn({ sessionId, clientId: client.id }, 'Invalid subscribe cursor — reseeding');
219
+ }
220
+ await this.emitTargetedSeed(client, sessionId, cols, rows);
221
+ return;
222
+ case 'legacy-gap':
223
+ await this.emitReplayResync(client, sessionId, cursor.sequence, { status: 'gap', currentSequence: decision.cursorSnapshot.currentSequence }, cols, rows);
224
+ return;
225
+ case 'cursor': {
226
+ const replay = decision.replay;
227
+ if (!replay)
228
+ return;
229
+ if (replay.status === 'covered') {
230
+ for (const frame of replay.frames)
231
+ this.enqueueLiveOrRecover(client, sessionId, frame);
232
+ if (restoreViewportOnCovered)
233
+ this.maybeRestoreViewportModes(sessionId);
234
+ }
235
+ else {
236
+ await this.emitReplayResync(client, sessionId, cursor.cursor.sequence, replay, cols, rows);
237
+ }
238
+ return;
239
+ }
240
+ }
241
+ }
242
+ emitSubscribed(client, sessionId, currentSequence, replayStatus, sequenceEpoch) {
243
+ const payload = ws_envelope_dto_1.SubscribedPayloadSchema.parse({
244
+ sessionId,
245
+ currentSequence,
246
+ sequenceEpoch,
247
+ replayStatus,
248
+ historyRefreshable: !this.sessionsService.usesAlternateScreenFor(sessionId),
249
+ });
250
+ client.emit('message', (0, ws_envelope_dto_1.createEnvelope)(`terminal/${sessionId}`, 'subscribed', payload));
251
+ }
252
+ async emitReplayResync(client, sessionId, requestedSequence, replay, cols, rows) {
253
+ client.emit('message', (0, ws_envelope_dto_1.createEnvelope)(`terminal/${sessionId}`, 'resync_required', {
254
+ sessionId,
255
+ requestedSequence,
256
+ currentSequence: replay.currentSequence,
257
+ earliestAvailableSequence: replay.earliestAvailableSequence,
258
+ }));
259
+ await this.startRecovery(client, sessionId, cols, rows);
260
+ }
261
+ async emitTargetedSeed(client, sessionId, cols, rows) {
262
+ const { maxBytes } = this.seedService.resolveSeedingConfig();
263
+ const recoveryKey = this.recoveryKey(client.id, sessionId);
264
+ const seedAttempt = Symbol();
265
+ this.targetedSeedAttempts.set(recoveryKey, seedAttempt);
266
+ let failedAdmission;
267
+ try {
268
+ await this.seedService.emitSeedToClient({
269
+ deliver: (envelope) => {
270
+ const clientSession = this.clientSessions.get(client.id);
271
+ if (this.targetedSeedAttempts.get(recoveryKey) !== seedAttempt ||
272
+ !clientSession?.subscriptions.has(`terminal/${sessionId}`) ||
273
+ this.recoveries.has(recoveryKey)) {
274
+ return terminal_seed_service_1.TerminalSeedDelivery.Abort;
275
+ }
276
+ const admission = this.sendScheduler.enqueueRecovery(client, envelope);
277
+ if (admission === terminal_send_scheduler_service_1.TerminalSendAdmission.Accepted) {
278
+ return terminal_seed_service_1.TerminalSeedDelivery.Continue;
279
+ }
280
+ failedAdmission ??= admission;
281
+ return terminal_seed_service_1.TerminalSeedDelivery.Abort;
282
+ },
283
+ sessionId,
284
+ maxBytes,
285
+ cols,
286
+ rows,
287
+ allowEmpty: true,
288
+ getCurrentSequence: () => this.streamService.getCurrentSequence(sessionId),
289
+ });
290
+ if (failedAdmission &&
291
+ this.targetedSeedAttempts.get(recoveryKey) === seedAttempt &&
292
+ this.clientSessions.get(client.id)?.subscriptions.has(`terminal/${sessionId}`)) {
293
+ logger.warn({ sessionId, clientId: client.id, admission: failedAdmission }, 'Initial targeted seed admission failed; starting bounded recovery');
294
+ await this.startRecovery(client, sessionId, cols, rows);
295
+ }
296
+ }
297
+ catch (error) {
298
+ logger.error({ sessionId, clientId: client.id, error }, 'Targeted terminal seed failed');
299
+ }
300
+ finally {
301
+ if (this.targetedSeedAttempts.get(recoveryKey) === seedAttempt) {
302
+ this.targetedSeedAttempts.delete(recoveryKey);
303
+ }
304
+ }
305
+ }
306
+ async handleResyncRequest(client, payload) {
307
+ const parsed = ws_envelope_dto_1.TerminalResyncRequestPayloadSchema.safeParse(payload);
308
+ if (!parsed.success)
309
+ return;
310
+ const { sessionId } = parsed.data;
311
+ const clientSession = this.clientSessions.get(client.id);
312
+ if (!clientSession?.subscriptions.has(`terminal/${sessionId}`))
313
+ return;
314
+ await this.startRecovery(client, sessionId);
315
+ }
316
+ handleResyncAbort(client, payload) {
317
+ const parsed = ws_envelope_dto_1.TerminalResyncAbortPayloadSchema.safeParse(payload);
318
+ if (!parsed.success)
319
+ return false;
320
+ const { sessionId, sequenceEpoch, recoveryEpoch } = parsed.data;
321
+ const key = this.recoveryKey(client.id, sessionId);
322
+ const state = this.recoveries.get(key);
323
+ if (!state ||
324
+ !this.isCurrentRecovery(state) ||
325
+ state.phase !== 'seeding' ||
326
+ state.sequenceEpoch !== sequenceEpoch ||
327
+ state.recoveryEpoch !== recoveryEpoch) {
328
+ return false;
329
+ }
330
+ this.recoveries.delete(key);
331
+ this.sendScheduler.removeLane(client.id, sessionId);
332
+ return true;
333
+ }
334
+ handleResyncComplete(client, payload) {
335
+ const parsed = ws_envelope_dto_1.TerminalResyncCompletePayloadSchema.safeParse(payload);
336
+ if (!parsed.success)
337
+ return;
338
+ const completion = parsed.data;
339
+ const state = this.recoveries.get(this.recoveryKey(client.id, completion.sessionId));
340
+ if (!state ||
341
+ !this.isCurrentRecovery(state) ||
342
+ state.phase !== 'seeding' ||
343
+ state.sequenceEpoch !== completion.sequenceEpoch ||
344
+ state.recoveryEpoch !== completion.recoveryEpoch ||
345
+ state.capturedSequence !== completion.capturedSequence) {
346
+ return;
347
+ }
348
+ state.phase = 'tail-catch-up';
349
+ this.deliverCoveredTail(client, state, completion.capturedSequence);
350
+ }
351
+ async startRecovery(client, sessionId, cols, rows) {
352
+ const key = this.recoveryKey(client.id, sessionId);
353
+ this.targetedSeedAttempts.delete(key);
354
+ if (this.recoveries.has(key))
355
+ return;
356
+ await this.launchRecovery(client, sessionId, cols, rows);
357
+ }
358
+ enqueueLiveOrRecover(client, sessionId, envelope) {
359
+ const admission = this.sendScheduler.enqueueLive(client, envelope);
360
+ if (admission === terminal_send_scheduler_service_1.TerminalSendAdmission.NewlyDesynchronized) {
361
+ void this.startRecovery(client, sessionId);
362
+ }
363
+ }
364
+ async launchRecovery(client, sessionId, cols, rows, replacementAttempt = 0) {
365
+ const sequenceEpoch = this.streamService.sampleCursor(sessionId).sequenceEpoch;
366
+ const recoveryEpoch = this.streamService.nextRecoveryCounter(sessionId);
367
+ const state = {
368
+ socketId: client.id,
369
+ sessionId,
370
+ sequenceEpoch,
371
+ recoveryEpoch,
372
+ phase: 'seeding',
373
+ replacementAttempt,
374
+ };
375
+ this.recoveries.set(this.recoveryKey(client.id, sessionId), state);
376
+ if (!this.sendScheduler.beginRecovery(client, sessionId, recoveryEpoch)) {
377
+ this.disconnectUnrecoverableRecovery(client, state, 'recovery_begin_rejected');
378
+ return;
379
+ }
380
+ this.seedService.invalidateCache(sessionId);
381
+ const { maxBytes } = this.seedService.resolveSeedingConfig();
382
+ let admissionFailed = false;
383
+ try {
384
+ const watermark = await this.seedService.emitSeedToClient({
385
+ deliver: (envelope) => {
386
+ if (!this.isCurrentRecovery(state))
387
+ return terminal_seed_service_1.TerminalSeedDelivery.Abort;
388
+ const admission = this.sendScheduler.enqueueRecovery(client, envelope);
389
+ if (admission !== terminal_send_scheduler_service_1.TerminalSendAdmission.Accepted) {
390
+ admissionFailed = true;
391
+ return terminal_seed_service_1.TerminalSeedDelivery.Abort;
392
+ }
393
+ return terminal_seed_service_1.TerminalSeedDelivery.Continue;
394
+ },
395
+ sessionId,
396
+ maxBytes,
397
+ cols,
398
+ rows,
399
+ allowEmpty: true,
400
+ recovery: {
401
+ sequenceEpoch,
402
+ recoveryEpoch,
403
+ getCurrentSequence: () => this.streamService.getCurrentSequence(sessionId),
404
+ onCapturedSequence: (capturedSequence) => {
405
+ if (this.isCurrentRecovery(state))
406
+ state.capturedSequence = capturedSequence;
407
+ },
408
+ },
409
+ });
410
+ if (!this.isCurrentRecovery(state))
411
+ return;
412
+ if (admissionFailed) {
413
+ this.disconnectUnrecoverableRecovery(client, state, 'recovery_seed_admission_failed');
414
+ return;
415
+ }
416
+ if (!watermark) {
417
+ this.disconnectUnrecoverableRecovery(client, state, 'recovery_seed_capture_failed');
418
+ return;
164
419
  }
165
- this.maybeRestoreViewportModes(sessionId);
420
+ state.capturedSequence = watermark.capturedSequence;
421
+ }
422
+ catch (error) {
423
+ logger.error({ sessionId, clientId: client.id, recoveryEpoch, error }, 'Targeted recovery seed failed');
424
+ this.disconnectUnrecoverableRecovery(client, state, 'recovery_seed_failed');
166
425
  }
167
426
  }
427
+ deliverCoveredTail(client, state, afterSequence) {
428
+ if (!this.isCurrentRecovery(state))
429
+ return;
430
+ const replay = this.streamService.getFramesSince(state.sessionId, afterSequence);
431
+ if (replay.status === 'gap') {
432
+ this.scheduleReplacementSeed(client, state);
433
+ return;
434
+ }
435
+ if (replay.frames.length === 0) {
436
+ this.finishRecovery(state);
437
+ return;
438
+ }
439
+ for (let index = 0; index < replay.frames.length; index += 1) {
440
+ const isLast = index === replay.frames.length - 1;
441
+ const admission = this.sendScheduler.enqueueRecovery(client, replay.frames[index], isLast ? () => this.deliverCoveredTail(client, state, replay.currentSequence) : undefined);
442
+ if (admission !== terminal_send_scheduler_service_1.TerminalSendAdmission.Accepted) {
443
+ this.disconnectUnrecoverableRecovery(client, state, 'recovery_tail_admission_failed');
444
+ return;
445
+ }
446
+ }
447
+ }
448
+ scheduleReplacementSeed(client, state) {
449
+ if (!this.isCurrentRecovery(state) || state.phase === 'replacing')
450
+ return;
451
+ if (state.replacementAttempt >= 1) {
452
+ this.disconnectUnrecoverableRecovery(client, state, 'recovery_tail_gap_repeated');
453
+ return;
454
+ }
455
+ state.phase = 'replacing';
456
+ void this.launchRecovery(client, state.sessionId, undefined, undefined, 1);
457
+ }
458
+ disconnectUnrecoverableRecovery(client, state, reason) {
459
+ if (!this.isCurrentRecovery(state))
460
+ return;
461
+ logger.warn({
462
+ clientId: client.id,
463
+ sessionId: state.sessionId,
464
+ recoveryEpoch: state.recoveryEpoch,
465
+ reason,
466
+ }, 'Terminal recovery failed closed; disconnecting affected socket');
467
+ this.recoveries.delete(this.recoveryKey(state.socketId, state.sessionId));
468
+ client.conn.close();
469
+ this.releaseClientSession(client.id);
470
+ }
471
+ finishRecovery(state) {
472
+ if (!this.isCurrentRecovery(state))
473
+ return;
474
+ this.sendScheduler.markSynchronized(state.socketId, state.sessionId, state.recoveryEpoch);
475
+ this.recoveries.delete(this.recoveryKey(state.socketId, state.sessionId));
476
+ }
477
+ isCurrentRecovery(state) {
478
+ return (this.recoveries.get(this.recoveryKey(state.socketId, state.sessionId)) === state &&
479
+ this.streamService.getSequenceEpoch(state.sessionId) === state.sequenceEpoch);
480
+ }
481
+ recoveryKey(socketId, sessionId) {
482
+ return `${socketId}:${sessionId}`;
483
+ }
168
484
  handleEventsSubscribe(client) {
169
485
  const cs = this.clientSessions.get(client.id);
170
486
  if (!cs)
@@ -201,6 +517,10 @@ let TerminalGateway = class TerminalGateway {
201
517
  const session = this.registry.get(sessionId);
202
518
  if (session)
203
519
  session.unsubscribe(client.id);
520
+ const recoveryKey = this.recoveryKey(client.id, sessionId);
521
+ this.recoveries.delete(recoveryKey);
522
+ this.targetedSeedAttempts.delete(recoveryKey);
523
+ this.sendScheduler.removeLane(client.id, sessionId);
204
524
  }
205
525
  async handleTheme(client, payload) {
206
526
  const cs = this.clientSessions.get(client.id);
@@ -268,6 +588,9 @@ let TerminalGateway = class TerminalGateway {
268
588
  }
269
589
  }
270
590
  async handleInput(client, payload) {
591
+ if (payload.kind === 'prompt-paste') {
592
+ return this.handlePromptPasteInput(client, payload);
593
+ }
271
594
  const { sessionId, data, ttyMode = false } = payload;
272
595
  const session = this.registry.get(sessionId);
273
596
  if (!session) {
@@ -305,8 +628,144 @@ let TerminalGateway = class TerminalGateway {
305
628
  }
306
629
  }
307
630
  }
631
+ handlePromptPasteInput(client, rawPayload) {
632
+ const parsed = ws_envelope_dto_1.TerminalPromptPasteInputSchema.safeParse(rawPayload);
633
+ if (!parsed.success) {
634
+ return Promise.resolve({
635
+ ok: false,
636
+ code: 'INVALID_REQUEST',
637
+ requestId: typeof rawPayload.requestId === 'string'
638
+ ? rawPayload.requestId
639
+ : '',
640
+ });
641
+ }
642
+ const payload = parsed.data;
643
+ const session = this.registry.get(payload.sessionId);
644
+ if (!session) {
645
+ return Promise.resolve(this.promptPasteFailure(payload.requestId, 'UNKNOWN_SESSION'));
646
+ }
647
+ if (!session.hasSubscriber(client.id)) {
648
+ return Promise.resolve(this.promptPasteFailure(payload.requestId, 'NOT_SUBSCRIBER'));
649
+ }
650
+ if (session.getAuthority() !== client.id) {
651
+ return Promise.resolve(this.promptPasteFailure(payload.requestId, 'NOT_AUTHORITY'));
652
+ }
653
+ this.trackInputRate(client.id, payload.sessionId, payload.data.length);
654
+ const fingerprint = (0, node_crypto_1.createHash)('sha256').update(payload.data, 'utf8').digest('hex');
655
+ const requests = this.getPromptPasteRequests(payload.sessionId);
656
+ this.pruneExpiredPromptPastes(payload.sessionId, requests);
657
+ const existing = requests.get(payload.requestId);
658
+ if (existing) {
659
+ if (existing.fingerprint !== fingerprint) {
660
+ return Promise.resolve(this.promptPasteFailure(payload.requestId, 'REQUEST_CONFLICT'));
661
+ }
662
+ return existing.kind === 'pending' ? existing.promise : Promise.resolve(existing.outcome);
663
+ }
664
+ if (requests.size >= exports.PROMPT_PASTE_MAX_REQUESTS_PER_SESSION) {
665
+ return Promise.resolve(this.promptPasteFailure(payload.requestId, 'BUSY'));
666
+ }
667
+ let settle;
668
+ const promise = new Promise((resolve) => {
669
+ settle = resolve;
670
+ });
671
+ const pending = {
672
+ kind: 'pending',
673
+ sessionId: payload.sessionId,
674
+ requestId: payload.requestId,
675
+ fingerprint,
676
+ promise,
677
+ };
678
+ requests.set(payload.requestId, pending);
679
+ void this.executePromptPaste(session, client, payload).then((outcome) => {
680
+ const current = requests.get(payload.requestId);
681
+ if (current === pending) {
682
+ const expiresAt = Date.now() + exports.PROMPT_PASTE_RETRY_WINDOW_MS;
683
+ requests.set(payload.requestId, {
684
+ kind: 'tombstone',
685
+ sessionId: payload.sessionId,
686
+ requestId: payload.requestId,
687
+ fingerprint,
688
+ outcome,
689
+ expiresAt,
690
+ });
691
+ this.schedulePromptPasteExpiry(payload.sessionId, payload.requestId, expiresAt);
692
+ }
693
+ settle(outcome);
694
+ });
695
+ return promise;
696
+ }
697
+ async executePromptPaste(session, client, payload) {
698
+ try {
699
+ const target = { name: session.tmuxSessionName };
700
+ const tmuxAlive = await this.terminalIO.sessionExists(target);
701
+ if (!tmuxAlive) {
702
+ await this.handleDeadTmuxSession(payload.sessionId, client);
703
+ return this.promptPasteFailure(payload.requestId, 'TMUX_UNAVAILABLE');
704
+ }
705
+ session.signalInput();
706
+ await this.terminalIO.deliverImmediate(target, payload.data, {
707
+ bracketed: true,
708
+ submitKeys: [],
709
+ });
710
+ return { ok: true, code: 'OK', requestId: payload.requestId };
711
+ }
712
+ catch (error) {
713
+ logger.warn({ sessionId: payload.sessionId, requestId: payload.requestId, error: String(error) }, 'Prompt paste delivery failed');
714
+ return this.promptPasteFailure(payload.requestId, 'DELIVERY_ERROR');
715
+ }
716
+ }
717
+ promptPasteFailure(requestId, code) {
718
+ return { ok: false, code, requestId };
719
+ }
720
+ getPromptPasteRequests(sessionId) {
721
+ const existing = this.promptPasteLedger.get(sessionId);
722
+ if (existing)
723
+ return existing;
724
+ const requests = new Map();
725
+ this.promptPasteLedger.set(sessionId, requests);
726
+ return requests;
727
+ }
728
+ pruneExpiredPromptPastes(sessionId, requests) {
729
+ const now = Date.now();
730
+ for (const [requestId, entry] of requests) {
731
+ if (entry.kind === 'tombstone' && entry.expiresAt <= now) {
732
+ requests.delete(requestId);
733
+ const timerKey = this.promptPasteTimerKey(sessionId, requestId);
734
+ const timer = this.promptPasteExpiryTimers.get(timerKey);
735
+ if (timer)
736
+ clearTimeout(timer);
737
+ this.promptPasteExpiryTimers.delete(timerKey);
738
+ }
739
+ }
740
+ }
741
+ schedulePromptPasteExpiry(sessionId, requestId, expiresAt) {
742
+ const timerKey = this.promptPasteTimerKey(sessionId, requestId);
743
+ const existing = this.promptPasteExpiryTimers.get(timerKey);
744
+ if (existing)
745
+ clearTimeout(existing);
746
+ const timer = setTimeout(() => {
747
+ this.promptPasteExpiryTimers.delete(timerKey);
748
+ const requests = this.promptPasteLedger.get(sessionId);
749
+ const entry = requests?.get(requestId);
750
+ if (entry?.kind === 'tombstone' && entry.expiresAt <= Date.now()) {
751
+ requests?.delete(requestId);
752
+ if (requests?.size === 0)
753
+ this.promptPasteLedger.delete(sessionId);
754
+ }
755
+ }, Math.max(0, expiresAt - Date.now()));
756
+ timer.unref();
757
+ this.promptPasteExpiryTimers.set(timerKey, timer);
758
+ }
759
+ promptPasteTimerKey(sessionId, requestId) {
760
+ return `${sessionId}:${requestId}`;
761
+ }
308
762
  async handleRequestFullHistory(client, payload) {
309
763
  const { sessionId } = payload;
764
+ const correlationParse = ws_envelope_dto_1.FullHistoryRequestPayloadSchema.shape.correlationId.safeParse(payload.correlationId);
765
+ if (!correlationParse.success) {
766
+ throw new websockets_1.WsException('correlationId must be a string');
767
+ }
768
+ const correlationId = correlationParse.data;
310
769
  let maxLines = 10000;
311
770
  if (payload.maxLines !== undefined && payload.maxLines !== null) {
312
771
  const parsed = Math.floor(Number(payload.maxLines));
@@ -323,10 +782,11 @@ let TerminalGateway = class TerminalGateway {
323
782
  if (!session)
324
783
  return;
325
784
  const target = { name: session.tmuxSessionName };
785
+ const beforeEpoch = this.streamService.sampleCursor(sessionId).sequenceEpoch;
326
786
  const captureResult = await this.terminalIO.captureHistory(target, maxLines, true);
327
787
  const capturedSequence = this.streamService.getCurrentSequence(sessionId);
328
788
  let history = captureResult.ok ? captureResult.output : '';
329
- history = (0, normalize_line_endings_1.stripFinalLineEnding)(history);
789
+ history = (0, normalize_line_endings_1.normalizeLineEndings)((0, normalize_line_endings_1.stripFinalLineEnding)(history));
330
790
  const { maxBytes } = this.seedService.resolveSeedingConfig();
331
791
  let hasHistory = false;
332
792
  if (Buffer.byteLength(history, 'utf-8') > maxBytes) {
@@ -334,14 +794,20 @@ let TerminalGateway = class TerminalGateway {
334
794
  history = truncated;
335
795
  hasHistory = wasTruncated;
336
796
  }
337
- history = (0, normalize_line_endings_1.normalizeLineEndings)(history);
338
797
  const cursorPos = await this.terminalIO.getCursorPosition(target);
798
+ const afterEpoch = this.streamService.sampleCursor(sessionId).sequenceEpoch;
799
+ if (afterEpoch !== beforeEpoch) {
800
+ logger.warn({ sessionId, beforeEpoch, afterEpoch }, 'Sequence domain changed during full_history capture; dropping response');
801
+ return;
802
+ }
339
803
  client.emit('message', (0, ws_envelope_dto_1.createEnvelope)(`terminal/${sessionId}`, 'full_history', {
340
804
  history,
341
805
  cursorX: cursorPos?.x,
342
806
  cursorY: cursorPos?.y,
343
807
  hasHistory,
344
808
  capturedSequence,
809
+ sequenceEpoch: beforeEpoch,
810
+ ...(correlationId !== undefined && { correlationId }),
345
811
  }));
346
812
  }
347
813
  handleRestoreViewportModes(client, payload) {
@@ -359,15 +825,40 @@ let TerminalGateway = class TerminalGateway {
359
825
  cs.lastHeartbeat = new Date();
360
826
  }
361
827
  broadcastTerminalData(sessionId, data) {
362
- this.registry.get(sessionId)?.pushFrame(data);
363
- const envelope = this.streamService.addFrame(sessionId, data);
364
- this.server.to(`terminal:${sessionId}`).emit('message', envelope);
828
+ const session = this.registry.get(sessionId);
829
+ if (!this.hasWebSubscribers(sessionId)) {
830
+ session?.pushFrame(data);
831
+ this.streamService.markDiscontinuous(sessionId);
832
+ return;
833
+ }
834
+ this.streamService.resumeRetention(sessionId);
835
+ const envelopes = this.streamService.addFrame(sessionId, data);
836
+ for (const envelope of envelopes) {
837
+ const chunk = envelope.payload.data;
838
+ session?.pushFrame(chunk);
839
+ for (const clientSession of this.clientSessions.values()) {
840
+ if (clientSession.subscriptions.has(`terminal/${sessionId}`)) {
841
+ this.enqueueLiveOrRecover(clientSession.socket, sessionId, envelope);
842
+ }
843
+ }
844
+ }
845
+ }
846
+ hasWebSubscribers(sessionId) {
847
+ const session = this.registry.get(sessionId);
848
+ if (session)
849
+ return session.getSubscriberCount() > 0;
850
+ for (const clientSession of this.clientSessions.values()) {
851
+ if (clientSession.subscriptions.has(`terminal/${sessionId}`))
852
+ return true;
853
+ }
854
+ return false;
365
855
  }
366
856
  handleSessionCrashed(payload) {
367
857
  this.sessionsService.markSessionFailed(payload.sessionId, 'tmux session lost (health check)');
368
- this.ptyService.stopStreaming(payload.sessionId);
369
- this.unwireFrameListener(payload.sessionId);
370
- this.registry.dispose(payload.sessionId);
858
+ this.cleanupSessionLifecycle(payload.sessionId, {
859
+ replayRetentionMs: 0,
860
+ disposeTerminalState: true,
861
+ });
371
862
  const ep = {
372
863
  sessionId: payload.sessionId,
373
864
  status: 'crashed',
@@ -376,9 +867,6 @@ let TerminalGateway = class TerminalGateway {
376
867
  this.server
377
868
  .to(`session:${payload.sessionId}`)
378
869
  .emit('message', (0, ws_envelope_dto_1.createEnvelope)(`session/${payload.sessionId}`, 'state_change', ep));
379
- setTimeout(() => this.streamService.clearBuffer(payload.sessionId), 60000);
380
- this.themeCache.delete(payload.sessionId);
381
- this.viewportRestoreAt.delete(payload.sessionId);
382
870
  }
383
871
  handleSessionStarted(payload) {
384
872
  const ep = {
@@ -389,6 +877,7 @@ let TerminalGateway = class TerminalGateway {
389
877
  this.server.emit('message', (0, ws_envelope_dto_1.createEnvelope)('sessions', 'started', ep));
390
878
  }
391
879
  handleSessionRestored(payload) {
880
+ this.cancelReplayCleanup(payload.sessionId);
392
881
  if (!this.registry.get(payload.sessionId) && payload.tmuxSessionName) {
393
882
  try {
394
883
  this.registry.create(payload.sessionId, payload.tmuxSessionName, {
@@ -407,17 +896,16 @@ let TerminalGateway = class TerminalGateway {
407
896
  this.server.emit('message', (0, ws_envelope_dto_1.createEnvelope)('sessions', 'started', ep));
408
897
  }
409
898
  handleSessionStopped(payload) {
410
- this.unwireFrameListener(payload.sessionId);
899
+ this.cleanupSessionLifecycle(payload.sessionId, {
900
+ replayRetentionMs: STOPPED_REPLAY_RETENTION_MS,
901
+ disposeTerminalState: false,
902
+ });
411
903
  const ep = {
412
904
  sessionId: payload.sessionId,
413
905
  status: 'ended',
414
906
  message: 'Session terminated',
415
907
  };
416
908
  this.server.emit('message', (0, ws_envelope_dto_1.createEnvelope)('sessions', 'stopped', ep));
417
- setTimeout(() => this.streamService.clearBuffer(payload.sessionId), 60000);
418
- this.seedService.invalidateCache(payload.sessionId);
419
- this.themeCache.delete(payload.sessionId);
420
- this.viewportRestoreAt.delete(payload.sessionId);
421
909
  }
422
910
  startHeartbeat() {
423
911
  this.heartbeatInterval = setInterval(() => {
@@ -452,12 +940,7 @@ let TerminalGateway = class TerminalGateway {
452
940
  logger.info({ sessionId }, 'Rewiring stale frame listener for restored session');
453
941
  this.frameListeners.delete(sessionId);
454
942
  }
455
- const FORWARDED_FRAME_TYPES = new Set([
456
- 'focus_changed',
457
- 'seed_ansi',
458
- 'resize_jiggle',
459
- 'full_history',
460
- ]);
943
+ const FORWARDED_FRAME_TYPES = new Set(['focus_changed', 'resize_jiggle', 'full_history']);
461
944
  const listener = (frame) => {
462
945
  if (FORWARDED_FRAME_TYPES.has(frame.type)) {
463
946
  this.server
@@ -478,11 +961,10 @@ let TerminalGateway = class TerminalGateway {
478
961
  async handleDeadTmuxSession(sessionId, client) {
479
962
  logger.warn({ sessionId }, 'Dead tmux detected — marking session failed');
480
963
  this.sessionsService.markSessionFailed(sessionId, 'tmux session no longer exists');
481
- this.ptyService.stopStreaming(sessionId);
482
- this.unwireFrameListener(sessionId);
483
- this.registry.dispose(sessionId);
484
- this.themeCache.delete(sessionId);
485
- this.viewportRestoreAt.delete(sessionId);
964
+ this.cleanupSessionLifecycle(sessionId, {
965
+ replayRetentionMs: 0,
966
+ disposeTerminalState: true,
967
+ });
486
968
  const ep = {
487
969
  sessionId,
488
970
  status: 'crashed',
@@ -492,6 +974,37 @@ let TerminalGateway = class TerminalGateway {
492
974
  client.emit('message', envelope);
493
975
  this.server.to(`session:${sessionId}`).emit('message', envelope);
494
976
  }
977
+ cancelReplayCleanup(sessionId) {
978
+ this.streamService.cancelScheduledClear(sessionId);
979
+ }
980
+ cleanupSessionLifecycle(sessionId, policy) {
981
+ this.cancelReplayCleanup(sessionId);
982
+ this.unwireFrameListener(sessionId);
983
+ this.seedService.invalidateCache(sessionId);
984
+ this.themeCache.delete(sessionId);
985
+ this.viewportRestoreAt.delete(sessionId);
986
+ this.pruneInputRateEntries((key) => key.endsWith(`:${sessionId}`));
987
+ this.retireSessionRecoveries(sessionId);
988
+ if (policy.disposeTerminalState) {
989
+ this.ptyService.stopStreaming(sessionId);
990
+ this.registry.dispose(sessionId);
991
+ }
992
+ if (policy.replayRetentionMs === 0) {
993
+ this.streamService.clearBuffer(sessionId);
994
+ return;
995
+ }
996
+ this.streamService.scheduleClear(sessionId, policy.replayRetentionMs);
997
+ }
998
+ retireSessionRecoveries(sessionId) {
999
+ for (const [key, recovery] of this.recoveries) {
1000
+ if (recovery.sessionId === sessionId)
1001
+ this.recoveries.delete(key);
1002
+ }
1003
+ for (const clientId of this.clientSessions.keys()) {
1004
+ this.targetedSeedAttempts.delete(this.recoveryKey(clientId, sessionId));
1005
+ }
1006
+ this.sendScheduler.removeSession(sessionId);
1007
+ }
495
1008
  async ensurePtyStreaming(sessionId, tmuxSessionName, options) {
496
1009
  if (this.ptyService.isStreaming(sessionId))
497
1010
  return;
@@ -544,6 +1057,12 @@ let TerminalGateway = class TerminalGateway {
544
1057
  entry.messages++;
545
1058
  entry.bytes += dataBytes;
546
1059
  }
1060
+ pruneInputRateEntries(matches) {
1061
+ for (const key of this.inputRateTracker.keys()) {
1062
+ if (matches(key))
1063
+ this.inputRateTracker.delete(key);
1064
+ }
1065
+ }
547
1066
  onModuleDestroy() {
548
1067
  if (this.heartbeatInterval)
549
1068
  clearInterval(this.heartbeatInterval);
@@ -552,6 +1071,14 @@ let TerminalGateway = class TerminalGateway {
552
1071
  }
553
1072
  this.themeCache.clear();
554
1073
  this.viewportRestoreAt.clear();
1074
+ this.inputRateTracker.clear();
1075
+ this.recoveries.clear();
1076
+ for (const timer of this.promptPasteExpiryTimers.values())
1077
+ clearTimeout(timer);
1078
+ this.promptPasteExpiryTimers.clear();
1079
+ this.promptPasteLedger.clear();
1080
+ this.sendScheduler.dispose();
1081
+ this.clientSessions.clear();
555
1082
  }
556
1083
  };
557
1084
  exports.TerminalGateway = TerminalGateway;
@@ -567,6 +1094,30 @@ __decorate([
567
1094
  __metadata("design:paramtypes", [socket_io_1.Socket, Object]),
568
1095
  __metadata("design:returntype", Promise)
569
1096
  ], TerminalGateway.prototype, "handleSubscribe", null);
1097
+ __decorate([
1098
+ (0, websockets_1.SubscribeMessage)('terminal:resync_request'),
1099
+ __param(0, (0, websockets_1.ConnectedSocket)()),
1100
+ __param(1, (0, websockets_1.MessageBody)()),
1101
+ __metadata("design:type", Function),
1102
+ __metadata("design:paramtypes", [socket_io_1.Socket, Object]),
1103
+ __metadata("design:returntype", Promise)
1104
+ ], TerminalGateway.prototype, "handleResyncRequest", null);
1105
+ __decorate([
1106
+ (0, websockets_1.SubscribeMessage)('terminal:resync_abort'),
1107
+ __param(0, (0, websockets_1.ConnectedSocket)()),
1108
+ __param(1, (0, websockets_1.MessageBody)()),
1109
+ __metadata("design:type", Function),
1110
+ __metadata("design:paramtypes", [socket_io_1.Socket, Object]),
1111
+ __metadata("design:returntype", Boolean)
1112
+ ], TerminalGateway.prototype, "handleResyncAbort", null);
1113
+ __decorate([
1114
+ (0, websockets_1.SubscribeMessage)('terminal:resync_complete'),
1115
+ __param(0, (0, websockets_1.ConnectedSocket)()),
1116
+ __param(1, (0, websockets_1.MessageBody)()),
1117
+ __metadata("design:type", Function),
1118
+ __metadata("design:paramtypes", [socket_io_1.Socket, Object]),
1119
+ __metadata("design:returntype", void 0)
1120
+ ], TerminalGateway.prototype, "handleResyncComplete", null);
570
1121
  __decorate([
571
1122
  (0, websockets_1.SubscribeMessage)('events:subscribe'),
572
1123
  __param(0, (0, websockets_1.ConnectedSocket)()),
@@ -690,6 +1241,8 @@ exports.TerminalGateway = TerminalGateway = __decorate([
690
1241
  terminal_io_service_1.TerminalIOService,
691
1242
  terminal_session_registry_1.TerminalSessionRegistry,
692
1243
  sessions_service_1.SessionsService,
693
- realtime_broadcast_service_1.RealtimeBroadcastService])
1244
+ realtime_broadcast_service_1.RealtimeBroadcastService,
1245
+ terminal_send_scheduler_service_1.TerminalSendSchedulerService,
1246
+ metrics_service_1.MetricsService])
694
1247
  ], TerminalGateway);
695
1248
  //# sourceMappingURL=terminal.gateway.js.map