@hansjm10/volt-coding-agent 0.0.0-bootstrap.0 → 0.1.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 (1364) hide show
  1. package/BINARY-CAPABILITIES.md +28 -0
  2. package/CHANGELOG.md +4845 -0
  3. package/LICENSE +1 -1
  4. package/README.md +716 -3
  5. package/THIRD-PARTY-NOTICES.md +55 -0
  6. package/dist/LICENSES/clipboard-image-MIT.txt +9 -0
  7. package/dist/LICENSES/highlight.js-10.7.3-BSD-3-Clause.txt +29 -0
  8. package/dist/LICENSES/highlight.js-11.9.0-BSD-3-Clause.txt +29 -0
  9. package/dist/LICENSES/marked-18.0.5-LICENSE.txt +44 -0
  10. package/dist/LICENSES/run-jxa-MIT.txt +9 -0
  11. package/dist/cli/args.d.ts +60 -0
  12. package/dist/cli/args.d.ts.map +1 -0
  13. package/dist/cli/args.js +414 -0
  14. package/dist/cli/args.js.map +1 -0
  15. package/dist/cli/config-selector.d.ts +14 -0
  16. package/dist/cli/config-selector.d.ts.map +1 -0
  17. package/dist/cli/config-selector.js +31 -0
  18. package/dist/cli/config-selector.js.map +1 -0
  19. package/dist/cli/file-processor.d.ts +15 -0
  20. package/dist/cli/file-processor.d.ts.map +1 -0
  21. package/dist/cli/file-processor.js +85 -0
  22. package/dist/cli/file-processor.js.map +1 -0
  23. package/dist/cli/initial-message.d.ts +18 -0
  24. package/dist/cli/initial-message.d.ts.map +1 -0
  25. package/dist/cli/initial-message.js +22 -0
  26. package/dist/cli/initial-message.js.map +1 -0
  27. package/dist/cli/list-models.d.ts +9 -0
  28. package/dist/cli/list-models.d.ts.map +1 -0
  29. package/dist/cli/list-models.js +98 -0
  30. package/dist/cli/list-models.js.map +1 -0
  31. package/dist/cli/project-trust.d.ts +10 -0
  32. package/dist/cli/project-trust.d.ts.map +1 -0
  33. package/dist/cli/project-trust.js +48 -0
  34. package/dist/cli/project-trust.js.map +1 -0
  35. package/dist/cli/session-picker.d.ts +9 -0
  36. package/dist/cli/session-picker.d.ts.map +1 -0
  37. package/dist/cli/session-picker.js +35 -0
  38. package/dist/cli/session-picker.js.map +1 -0
  39. package/dist/cli/startup-ui.d.ts +17 -0
  40. package/dist/cli/startup-ui.d.ts.map +1 -0
  41. package/dist/cli/startup-ui.js +133 -0
  42. package/dist/cli/startup-ui.js.map +1 -0
  43. package/dist/cli.d.ts +3 -0
  44. package/dist/cli.d.ts.map +1 -0
  45. package/dist/cli.js +18 -0
  46. package/dist/cli.js.map +1 -0
  47. package/dist/config.d.ts +85 -0
  48. package/dist/config.d.ts.map +1 -0
  49. package/dist/config.js +487 -0
  50. package/dist/config.js.map +1 -0
  51. package/dist/core/agent-session-runtime.d.ts +160 -0
  52. package/dist/core/agent-session-runtime.d.ts.map +1 -0
  53. package/dist/core/agent-session-runtime.js +397 -0
  54. package/dist/core/agent-session-runtime.js.map +1 -0
  55. package/dist/core/agent-session-services.d.ts +98 -0
  56. package/dist/core/agent-session-services.d.ts.map +1 -0
  57. package/dist/core/agent-session-services.js +125 -0
  58. package/dist/core/agent-session-services.js.map +1 -0
  59. package/dist/core/agent-session.d.ts +739 -0
  60. package/dist/core/agent-session.d.ts.map +1 -0
  61. package/dist/core/agent-session.js +3158 -0
  62. package/dist/core/agent-session.js.map +1 -0
  63. package/dist/core/auth-guidance.d.ts +5 -0
  64. package/dist/core/auth-guidance.d.ts.map +1 -0
  65. package/dist/core/auth-guidance.js +21 -0
  66. package/dist/core/auth-guidance.js.map +1 -0
  67. package/dist/core/auth-storage.d.ts +159 -0
  68. package/dist/core/auth-storage.d.ts.map +1 -0
  69. package/dist/core/auth-storage.js +509 -0
  70. package/dist/core/auth-storage.js.map +1 -0
  71. package/dist/core/bash-executor.d.ts +32 -0
  72. package/dist/core/bash-executor.d.ts.map +1 -0
  73. package/dist/core/bash-executor.js +111 -0
  74. package/dist/core/bash-executor.js.map +1 -0
  75. package/dist/core/compaction/branch-summarization.d.ts +92 -0
  76. package/dist/core/compaction/branch-summarization.d.ts.map +1 -0
  77. package/dist/core/compaction/branch-summarization.js +257 -0
  78. package/dist/core/compaction/branch-summarization.js.map +1 -0
  79. package/dist/core/compaction/compaction.d.ts +121 -0
  80. package/dist/core/compaction/compaction.d.ts.map +1 -0
  81. package/dist/core/compaction/compaction.js +658 -0
  82. package/dist/core/compaction/compaction.js.map +1 -0
  83. package/dist/core/compaction/index.d.ts +7 -0
  84. package/dist/core/compaction/index.d.ts.map +1 -0
  85. package/dist/core/compaction/index.js +7 -0
  86. package/dist/core/compaction/index.js.map +1 -0
  87. package/dist/core/compaction/utils.d.ts +56 -0
  88. package/dist/core/compaction/utils.d.ts.map +1 -0
  89. package/dist/core/compaction/utils.js +271 -0
  90. package/dist/core/compaction/utils.js.map +1 -0
  91. package/dist/core/defaults.d.ts +3 -0
  92. package/dist/core/defaults.d.ts.map +1 -0
  93. package/dist/core/defaults.js +2 -0
  94. package/dist/core/defaults.js.map +1 -0
  95. package/dist/core/diagnostics.d.ts +15 -0
  96. package/dist/core/diagnostics.d.ts.map +1 -0
  97. package/dist/core/diagnostics.js +2 -0
  98. package/dist/core/diagnostics.js.map +1 -0
  99. package/dist/core/event-bus.d.ts +9 -0
  100. package/dist/core/event-bus.d.ts.map +1 -0
  101. package/dist/core/event-bus.js +25 -0
  102. package/dist/core/event-bus.js.map +1 -0
  103. package/dist/core/exec.d.ts +29 -0
  104. package/dist/core/exec.d.ts.map +1 -0
  105. package/dist/core/exec.js +75 -0
  106. package/dist/core/exec.js.map +1 -0
  107. package/dist/core/experimental.d.ts +2 -0
  108. package/dist/core/experimental.d.ts.map +1 -0
  109. package/dist/core/experimental.js +4 -0
  110. package/dist/core/experimental.js.map +1 -0
  111. package/dist/core/export-html/ansi-to-html.d.ts +22 -0
  112. package/dist/core/export-html/ansi-to-html.d.ts.map +1 -0
  113. package/dist/core/export-html/ansi-to-html.js +249 -0
  114. package/dist/core/export-html/ansi-to-html.js.map +1 -0
  115. package/dist/core/export-html/index.d.ts +37 -0
  116. package/dist/core/export-html/index.d.ts.map +1 -0
  117. package/dist/core/export-html/index.js +234 -0
  118. package/dist/core/export-html/index.js.map +1 -0
  119. package/dist/core/export-html/template.css +1066 -0
  120. package/dist/core/export-html/template.html +55 -0
  121. package/dist/core/export-html/template.js +1864 -0
  122. package/dist/core/export-html/tool-renderer.d.ts +34 -0
  123. package/dist/core/export-html/tool-renderer.d.ts.map +1 -0
  124. package/dist/core/export-html/tool-renderer.js +108 -0
  125. package/dist/core/export-html/tool-renderer.js.map +1 -0
  126. package/dist/core/export-html/vendor/highlight.min.js +1213 -0
  127. package/dist/core/export-html/vendor/marked.min.js +78 -0
  128. package/dist/core/extensions/index.d.ts +12 -0
  129. package/dist/core/extensions/index.d.ts.map +1 -0
  130. package/dist/core/extensions/index.js +9 -0
  131. package/dist/core/extensions/index.js.map +1 -0
  132. package/dist/core/extensions/loader.d.ts +25 -0
  133. package/dist/core/extensions/loader.d.ts.map +1 -0
  134. package/dist/core/extensions/loader.js +516 -0
  135. package/dist/core/extensions/loader.js.map +1 -0
  136. package/dist/core/extensions/runner.d.ts +180 -0
  137. package/dist/core/extensions/runner.d.ts.map +1 -0
  138. package/dist/core/extensions/runner.js +938 -0
  139. package/dist/core/extensions/runner.js.map +1 -0
  140. package/dist/core/extensions/types.d.ts +1215 -0
  141. package/dist/core/extensions/types.d.ts.map +1 -0
  142. package/dist/core/extensions/types.js +48 -0
  143. package/dist/core/extensions/types.js.map +1 -0
  144. package/dist/core/extensions/wrapper.d.ts +20 -0
  145. package/dist/core/extensions/wrapper.d.ts.map +1 -0
  146. package/dist/core/extensions/wrapper.js +22 -0
  147. package/dist/core/extensions/wrapper.js.map +1 -0
  148. package/dist/core/footer-data-provider.d.ts +54 -0
  149. package/dist/core/footer-data-provider.d.ts.map +1 -0
  150. package/dist/core/footer-data-provider.js +338 -0
  151. package/dist/core/footer-data-provider.js.map +1 -0
  152. package/dist/core/host-actions.d.ts +107 -0
  153. package/dist/core/host-actions.d.ts.map +1 -0
  154. package/dist/core/host-actions.js +632 -0
  155. package/dist/core/host-actions.js.map +1 -0
  156. package/dist/core/host-interaction.d.ts +35 -0
  157. package/dist/core/host-interaction.d.ts.map +1 -0
  158. package/dist/core/host-interaction.js +2 -0
  159. package/dist/core/host-interaction.js.map +1 -0
  160. package/dist/core/http-dispatcher.d.ts +22 -0
  161. package/dist/core/http-dispatcher.d.ts.map +1 -0
  162. package/dist/core/http-dispatcher.js +64 -0
  163. package/dist/core/http-dispatcher.js.map +1 -0
  164. package/dist/core/index.d.ts +18 -0
  165. package/dist/core/index.d.ts.map +1 -0
  166. package/dist/core/index.js +17 -0
  167. package/dist/core/index.js.map +1 -0
  168. package/dist/core/keybindings.d.ts +373 -0
  169. package/dist/core/keybindings.d.ts.map +1 -0
  170. package/dist/core/keybindings.js +303 -0
  171. package/dist/core/keybindings.js.map +1 -0
  172. package/dist/core/lsp/client.d.ts +119 -0
  173. package/dist/core/lsp/client.d.ts.map +1 -0
  174. package/dist/core/lsp/client.js +711 -0
  175. package/dist/core/lsp/client.js.map +1 -0
  176. package/dist/core/lsp/config.d.ts +96 -0
  177. package/dist/core/lsp/config.d.ts.map +1 -0
  178. package/dist/core/lsp/config.js +206 -0
  179. package/dist/core/lsp/config.js.map +1 -0
  180. package/dist/core/lsp/index.d.ts +6 -0
  181. package/dist/core/lsp/index.d.ts.map +1 -0
  182. package/dist/core/lsp/index.js +6 -0
  183. package/dist/core/lsp/index.js.map +1 -0
  184. package/dist/core/lsp/manager.d.ts +122 -0
  185. package/dist/core/lsp/manager.d.ts.map +1 -0
  186. package/dist/core/lsp/manager.js +1136 -0
  187. package/dist/core/lsp/manager.js.map +1 -0
  188. package/dist/core/lsp/trace.d.ts +26 -0
  189. package/dist/core/lsp/trace.d.ts.map +1 -0
  190. package/dist/core/lsp/trace.js +98 -0
  191. package/dist/core/lsp/trace.js.map +1 -0
  192. package/dist/core/lsp/workspace-edit.d.ts +60 -0
  193. package/dist/core/lsp/workspace-edit.d.ts.map +1 -0
  194. package/dist/core/lsp/workspace-edit.js +71 -0
  195. package/dist/core/lsp/workspace-edit.js.map +1 -0
  196. package/dist/core/mcp/audit.d.ts +39 -0
  197. package/dist/core/mcp/audit.d.ts.map +1 -0
  198. package/dist/core/mcp/audit.js +35 -0
  199. package/dist/core/mcp/audit.js.map +1 -0
  200. package/dist/core/mcp/auth.d.ts +7 -0
  201. package/dist/core/mcp/auth.d.ts.map +1 -0
  202. package/dist/core/mcp/auth.js +56 -0
  203. package/dist/core/mcp/auth.js.map +1 -0
  204. package/dist/core/mcp/client-factory.d.ts +20 -0
  205. package/dist/core/mcp/client-factory.d.ts.map +1 -0
  206. package/dist/core/mcp/client-factory.js +200 -0
  207. package/dist/core/mcp/client-factory.js.map +1 -0
  208. package/dist/core/mcp/config-loader.d.ts +9 -0
  209. package/dist/core/mcp/config-loader.d.ts.map +1 -0
  210. package/dist/core/mcp/config-loader.js +104 -0
  211. package/dist/core/mcp/config-loader.js.map +1 -0
  212. package/dist/core/mcp/config-writer.d.ts +21 -0
  213. package/dist/core/mcp/config-writer.d.ts.map +1 -0
  214. package/dist/core/mcp/config-writer.js +124 -0
  215. package/dist/core/mcp/config-writer.js.map +1 -0
  216. package/dist/core/mcp/config.d.ts +32 -0
  217. package/dist/core/mcp/config.d.ts.map +1 -0
  218. package/dist/core/mcp/config.js +385 -0
  219. package/dist/core/mcp/config.js.map +1 -0
  220. package/dist/core/mcp/direct-tools.d.ts +11 -0
  221. package/dist/core/mcp/direct-tools.d.ts.map +1 -0
  222. package/dist/core/mcp/direct-tools.js +47 -0
  223. package/dist/core/mcp/direct-tools.js.map +1 -0
  224. package/dist/core/mcp/gateway-tool.d.ts +29 -0
  225. package/dist/core/mcp/gateway-tool.d.ts.map +1 -0
  226. package/dist/core/mcp/gateway-tool.js +108 -0
  227. package/dist/core/mcp/gateway-tool.js.map +1 -0
  228. package/dist/core/mcp/index.d.ts +19 -0
  229. package/dist/core/mcp/index.d.ts.map +1 -0
  230. package/dist/core/mcp/index.js +18 -0
  231. package/dist/core/mcp/index.js.map +1 -0
  232. package/dist/core/mcp/manager.d.ts +117 -0
  233. package/dist/core/mcp/manager.d.ts.map +1 -0
  234. package/dist/core/mcp/manager.js +819 -0
  235. package/dist/core/mcp/manager.js.map +1 -0
  236. package/dist/core/mcp/metadata-cache.d.ts +27 -0
  237. package/dist/core/mcp/metadata-cache.d.ts.map +1 -0
  238. package/dist/core/mcp/metadata-cache.js +152 -0
  239. package/dist/core/mcp/metadata-cache.js.map +1 -0
  240. package/dist/core/mcp/oauth-flow.d.ts +94 -0
  241. package/dist/core/mcp/oauth-flow.d.ts.map +1 -0
  242. package/dist/core/mcp/oauth-flow.js +473 -0
  243. package/dist/core/mcp/oauth-flow.js.map +1 -0
  244. package/dist/core/mcp/oauth-provider.d.ts +39 -0
  245. package/dist/core/mcp/oauth-provider.d.ts.map +1 -0
  246. package/dist/core/mcp/oauth-provider.js +149 -0
  247. package/dist/core/mcp/oauth-provider.js.map +1 -0
  248. package/dist/core/mcp/oauth-store.d.ts +27 -0
  249. package/dist/core/mcp/oauth-store.d.ts.map +1 -0
  250. package/dist/core/mcp/oauth-store.js +111 -0
  251. package/dist/core/mcp/oauth-store.js.map +1 -0
  252. package/dist/core/mcp/output-store.d.ts +55 -0
  253. package/dist/core/mcp/output-store.d.ts.map +1 -0
  254. package/dist/core/mcp/output-store.js +206 -0
  255. package/dist/core/mcp/output-store.js.map +1 -0
  256. package/dist/core/mcp/rpc.d.ts +13 -0
  257. package/dist/core/mcp/rpc.d.ts.map +1 -0
  258. package/dist/core/mcp/rpc.js +29 -0
  259. package/dist/core/mcp/rpc.js.map +1 -0
  260. package/dist/core/mcp/safety.d.ts +6 -0
  261. package/dist/core/mcp/safety.d.ts.map +1 -0
  262. package/dist/core/mcp/safety.js +65 -0
  263. package/dist/core/mcp/safety.js.map +1 -0
  264. package/dist/core/mcp/search.d.ts +8 -0
  265. package/dist/core/mcp/search.d.ts.map +1 -0
  266. package/dist/core/mcp/search.js +85 -0
  267. package/dist/core/mcp/search.js.map +1 -0
  268. package/dist/core/mcp/server-supervisor.d.ts +59 -0
  269. package/dist/core/mcp/server-supervisor.d.ts.map +1 -0
  270. package/dist/core/mcp/server-supervisor.js +331 -0
  271. package/dist/core/mcp/server-supervisor.js.map +1 -0
  272. package/dist/core/mcp/types.d.ts +321 -0
  273. package/dist/core/mcp/types.d.ts.map +1 -0
  274. package/dist/core/mcp/types.js +2 -0
  275. package/dist/core/mcp/types.js.map +1 -0
  276. package/dist/core/messages.d.ts +78 -0
  277. package/dist/core/messages.d.ts.map +1 -0
  278. package/dist/core/messages.js +138 -0
  279. package/dist/core/messages.js.map +1 -0
  280. package/dist/core/model-catalog-watcher.d.ts +36 -0
  281. package/dist/core/model-catalog-watcher.d.ts.map +1 -0
  282. package/dist/core/model-catalog-watcher.js +101 -0
  283. package/dist/core/model-catalog-watcher.js.map +1 -0
  284. package/dist/core/model-registry.d.ts +160 -0
  285. package/dist/core/model-registry.d.ts.map +1 -0
  286. package/dist/core/model-registry.js +754 -0
  287. package/dist/core/model-registry.js.map +1 -0
  288. package/dist/core/model-resolver.d.ts +111 -0
  289. package/dist/core/model-resolver.d.ts.map +1 -0
  290. package/dist/core/model-resolver.js +534 -0
  291. package/dist/core/model-resolver.js.map +1 -0
  292. package/dist/core/output-guard.d.ts +7 -0
  293. package/dist/core/output-guard.d.ts.map +1 -0
  294. package/dist/core/output-guard.js +89 -0
  295. package/dist/core/output-guard.js.map +1 -0
  296. package/dist/core/package-manager.d.ts +220 -0
  297. package/dist/core/package-manager.d.ts.map +1 -0
  298. package/dist/core/package-manager.js +2217 -0
  299. package/dist/core/package-manager.js.map +1 -0
  300. package/dist/core/project-trust.d.ts +15 -0
  301. package/dist/core/project-trust.d.ts.map +1 -0
  302. package/dist/core/project-trust.js +58 -0
  303. package/dist/core/project-trust.js.map +1 -0
  304. package/dist/core/prompt-templates.d.ts +53 -0
  305. package/dist/core/prompt-templates.d.ts.map +1 -0
  306. package/dist/core/prompt-templates.js +236 -0
  307. package/dist/core/prompt-templates.js.map +1 -0
  308. package/dist/core/provider-attribution.d.ts +4 -0
  309. package/dist/core/provider-attribution.d.ts.map +1 -0
  310. package/dist/core/provider-attribution.js +81 -0
  311. package/dist/core/provider-attribution.js.map +1 -0
  312. package/dist/core/provider-display-names.d.ts +2 -0
  313. package/dist/core/provider-display-names.d.ts.map +1 -0
  314. package/dist/core/provider-display-names.js +36 -0
  315. package/dist/core/provider-display-names.js.map +1 -0
  316. package/dist/core/remote/iroh/access-grant.d.ts +40 -0
  317. package/dist/core/remote/iroh/access-grant.d.ts.map +1 -0
  318. package/dist/core/remote/iroh/access-grant.js +199 -0
  319. package/dist/core/remote/iroh/access-grant.js.map +1 -0
  320. package/dist/core/remote/iroh/active-stream-registry.d.ts +39 -0
  321. package/dist/core/remote/iroh/active-stream-registry.d.ts.map +1 -0
  322. package/dist/core/remote/iroh/active-stream-registry.js +109 -0
  323. package/dist/core/remote/iroh/active-stream-registry.js.map +1 -0
  324. package/dist/core/remote/iroh/audit.d.ts +67 -0
  325. package/dist/core/remote/iroh/audit.d.ts.map +1 -0
  326. package/dist/core/remote/iroh/audit.js +274 -0
  327. package/dist/core/remote/iroh/audit.js.map +1 -0
  328. package/dist/core/remote/iroh/authorization.d.ts +46 -0
  329. package/dist/core/remote/iroh/authorization.d.ts.map +1 -0
  330. package/dist/core/remote/iroh/authorization.js +327 -0
  331. package/dist/core/remote/iroh/authorization.js.map +1 -0
  332. package/dist/core/remote/iroh/control.d.ts +64 -0
  333. package/dist/core/remote/iroh/control.d.ts.map +1 -0
  334. package/dist/core/remote/iroh/control.js +326 -0
  335. package/dist/core/remote/iroh/control.js.map +1 -0
  336. package/dist/core/remote/iroh/device-log-rpc.d.ts +22 -0
  337. package/dist/core/remote/iroh/device-log-rpc.d.ts.map +1 -0
  338. package/dist/core/remote/iroh/device-log-rpc.js +152 -0
  339. package/dist/core/remote/iroh/device-log-rpc.js.map +1 -0
  340. package/dist/core/remote/iroh/engine.d.ts +148 -0
  341. package/dist/core/remote/iroh/engine.d.ts.map +1 -0
  342. package/dist/core/remote/iroh/engine.js +499 -0
  343. package/dist/core/remote/iroh/engine.js.map +1 -0
  344. package/dist/core/remote/iroh/handshake-reader.d.ts +19 -0
  345. package/dist/core/remote/iroh/handshake-reader.d.ts.map +1 -0
  346. package/dist/core/remote/iroh/handshake-reader.js +91 -0
  347. package/dist/core/remote/iroh/handshake-reader.js.map +1 -0
  348. package/dist/core/remote/iroh/handshake.d.ts +119 -0
  349. package/dist/core/remote/iroh/handshake.d.ts.map +1 -0
  350. package/dist/core/remote/iroh/handshake.js +621 -0
  351. package/dist/core/remote/iroh/handshake.js.map +1 -0
  352. package/dist/core/remote/iroh/host-policy.d.ts +13 -0
  353. package/dist/core/remote/iroh/host-policy.d.ts.map +1 -0
  354. package/dist/core/remote/iroh/host-policy.js +15 -0
  355. package/dist/core/remote/iroh/host-policy.js.map +1 -0
  356. package/dist/core/remote/iroh/index.d.ts +25 -0
  357. package/dist/core/remote/iroh/index.d.ts.map +1 -0
  358. package/dist/core/remote/iroh/index.js +25 -0
  359. package/dist/core/remote/iroh/index.js.map +1 -0
  360. package/dist/core/remote/iroh/metadata.d.ts +28 -0
  361. package/dist/core/remote/iroh/metadata.d.ts.map +1 -0
  362. package/dist/core/remote/iroh/metadata.js +16 -0
  363. package/dist/core/remote/iroh/metadata.js.map +1 -0
  364. package/dist/core/remote/iroh/outbound-filter.d.ts +29 -0
  365. package/dist/core/remote/iroh/outbound-filter.d.ts.map +1 -0
  366. package/dist/core/remote/iroh/outbound-filter.js +692 -0
  367. package/dist/core/remote/iroh/outbound-filter.js.map +1 -0
  368. package/dist/core/remote/iroh/protocol.d.ts +60 -0
  369. package/dist/core/remote/iroh/protocol.d.ts.map +1 -0
  370. package/dist/core/remote/iroh/protocol.js +204 -0
  371. package/dist/core/remote/iroh/protocol.js.map +1 -0
  372. package/dist/core/remote/iroh/push.d.ts +179 -0
  373. package/dist/core/remote/iroh/push.d.ts.map +1 -0
  374. package/dist/core/remote/iroh/push.js +662 -0
  375. package/dist/core/remote/iroh/push.js.map +1 -0
  376. package/dist/core/remote/iroh/qr.d.ts +12 -0
  377. package/dist/core/remote/iroh/qr.d.ts.map +1 -0
  378. package/dist/core/remote/iroh/qr.js +27 -0
  379. package/dist/core/remote/iroh/qr.js.map +1 -0
  380. package/dist/core/remote/iroh/rpc-command-filter.d.ts +47 -0
  381. package/dist/core/remote/iroh/rpc-command-filter.d.ts.map +1 -0
  382. package/dist/core/remote/iroh/rpc-command-filter.js +188 -0
  383. package/dist/core/remote/iroh/rpc-command-filter.js.map +1 -0
  384. package/dist/core/remote/iroh/rpc-transport.d.ts +21 -0
  385. package/dist/core/remote/iroh/rpc-transport.d.ts.map +1 -0
  386. package/dist/core/remote/iroh/rpc-transport.js +106 -0
  387. package/dist/core/remote/iroh/rpc-transport.js.map +1 -0
  388. package/dist/core/remote/iroh/state-manager.d.ts +141 -0
  389. package/dist/core/remote/iroh/state-manager.d.ts.map +1 -0
  390. package/dist/core/remote/iroh/state-manager.js +744 -0
  391. package/dist/core/remote/iroh/state-manager.js.map +1 -0
  392. package/dist/core/remote/iroh/state.d.ts +141 -0
  393. package/dist/core/remote/iroh/state.d.ts.map +1 -0
  394. package/dist/core/remote/iroh/state.js +433 -0
  395. package/dist/core/remote/iroh/state.js.map +1 -0
  396. package/dist/core/remote/iroh/ticket.d.ts +46 -0
  397. package/dist/core/remote/iroh/ticket.d.ts.map +1 -0
  398. package/dist/core/remote/iroh/ticket.js +267 -0
  399. package/dist/core/remote/iroh/ticket.js.map +1 -0
  400. package/dist/core/remote/iroh/transcript-text.d.ts +8 -0
  401. package/dist/core/remote/iroh/transcript-text.d.ts.map +1 -0
  402. package/dist/core/remote/iroh/transcript-text.js +26 -0
  403. package/dist/core/remote/iroh/transcript-text.js.map +1 -0
  404. package/dist/core/remote/iroh/workspace-rpc.d.ts +31 -0
  405. package/dist/core/remote/iroh/workspace-rpc.d.ts.map +1 -0
  406. package/dist/core/remote/iroh/workspace-rpc.js +73 -0
  407. package/dist/core/remote/iroh/workspace-rpc.js.map +1 -0
  408. package/dist/core/remote/iroh/workspace.d.ts +16 -0
  409. package/dist/core/remote/iroh/workspace.d.ts.map +1 -0
  410. package/dist/core/remote/iroh/workspace.js +104 -0
  411. package/dist/core/remote/iroh/workspace.js.map +1 -0
  412. package/dist/core/remote/iroh/worktree-rpc.d.ts +83 -0
  413. package/dist/core/remote/iroh/worktree-rpc.d.ts.map +1 -0
  414. package/dist/core/remote/iroh/worktree-rpc.js +148 -0
  415. package/dist/core/remote/iroh/worktree-rpc.js.map +1 -0
  416. package/dist/core/resolve-config-value.d.ts +30 -0
  417. package/dist/core/resolve-config-value.d.ts.map +1 -0
  418. package/dist/core/resolve-config-value.js +245 -0
  419. package/dist/core/resolve-config-value.js.map +1 -0
  420. package/dist/core/resource-loader.d.ts +214 -0
  421. package/dist/core/resource-loader.d.ts.map +1 -0
  422. package/dist/core/resource-loader.js +824 -0
  423. package/dist/core/resource-loader.js.map +1 -0
  424. package/dist/core/review.d.ts +318 -0
  425. package/dist/core/review.d.ts.map +1 -0
  426. package/dist/core/review.js +1271 -0
  427. package/dist/core/review.js.map +1 -0
  428. package/dist/core/rpc/duplex-write-gate.d.ts +29 -0
  429. package/dist/core/rpc/duplex-write-gate.d.ts.map +1 -0
  430. package/dist/core/rpc/duplex-write-gate.js +124 -0
  431. package/dist/core/rpc/duplex-write-gate.js.map +1 -0
  432. package/dist/core/rpc/index.d.ts +6 -0
  433. package/dist/core/rpc/index.d.ts.map +1 -0
  434. package/dist/core/rpc/index.js +5 -0
  435. package/dist/core/rpc/index.js.map +1 -0
  436. package/dist/core/rpc/iroh-transport.d.ts +58 -0
  437. package/dist/core/rpc/iroh-transport.d.ts.map +1 -0
  438. package/dist/core/rpc/iroh-transport.js +278 -0
  439. package/dist/core/rpc/iroh-transport.js.map +1 -0
  440. package/dist/core/rpc/jsonl.d.ts +17 -0
  441. package/dist/core/rpc/jsonl.d.ts.map +1 -0
  442. package/dist/core/rpc/jsonl.js +49 -0
  443. package/dist/core/rpc/jsonl.js.map +1 -0
  444. package/dist/core/rpc/loopback-transport.d.ts +14 -0
  445. package/dist/core/rpc/loopback-transport.d.ts.map +1 -0
  446. package/dist/core/rpc/loopback-transport.js +120 -0
  447. package/dist/core/rpc/loopback-transport.js.map +1 -0
  448. package/dist/core/rpc/transcript.d.ts +37 -0
  449. package/dist/core/rpc/transcript.d.ts.map +1 -0
  450. package/dist/core/rpc/transcript.js +624 -0
  451. package/dist/core/rpc/transcript.js.map +1 -0
  452. package/dist/core/rpc/transport.d.ts +41 -0
  453. package/dist/core/rpc/transport.d.ts.map +1 -0
  454. package/dist/core/rpc/transport.js +155 -0
  455. package/dist/core/rpc/transport.js.map +1 -0
  456. package/dist/core/rpc/types.d.ts +1254 -0
  457. package/dist/core/rpc/types.d.ts.map +1 -0
  458. package/dist/core/rpc/types.js +8 -0
  459. package/dist/core/rpc/types.js.map +1 -0
  460. package/dist/core/rpc/ui-action-args.d.ts +3 -0
  461. package/dist/core/rpc/ui-action-args.d.ts.map +1 -0
  462. package/dist/core/rpc/ui-action-args.js +66 -0
  463. package/dist/core/rpc/ui-action-args.js.map +1 -0
  464. package/dist/core/rpc/ui-actions.d.ts +43 -0
  465. package/dist/core/rpc/ui-actions.d.ts.map +1 -0
  466. package/dist/core/rpc/ui-actions.js +362 -0
  467. package/dist/core/rpc/ui-actions.js.map +1 -0
  468. package/dist/core/sdk.d.ts +138 -0
  469. package/dist/core/sdk.d.ts.map +1 -0
  470. package/dist/core/sdk.js +324 -0
  471. package/dist/core/sdk.js.map +1 -0
  472. package/dist/core/session-cwd.d.ts +19 -0
  473. package/dist/core/session-cwd.d.ts.map +1 -0
  474. package/dist/core/session-cwd.js +38 -0
  475. package/dist/core/session-cwd.js.map +1 -0
  476. package/dist/core/session-manager.d.ts +346 -0
  477. package/dist/core/session-manager.d.ts.map +1 -0
  478. package/dist/core/session-manager.js +1280 -0
  479. package/dist/core/session-manager.js.map +1 -0
  480. package/dist/core/settings-manager.d.ts +359 -0
  481. package/dist/core/settings-manager.d.ts.map +1 -0
  482. package/dist/core/settings-manager.js +1277 -0
  483. package/dist/core/settings-manager.js.map +1 -0
  484. package/dist/core/skills.d.ts +60 -0
  485. package/dist/core/skills.d.ts.map +1 -0
  486. package/dist/core/skills.js +387 -0
  487. package/dist/core/skills.js.map +1 -0
  488. package/dist/core/slash-commands.d.ts +14 -0
  489. package/dist/core/slash-commands.d.ts.map +1 -0
  490. package/dist/core/slash-commands.js +51 -0
  491. package/dist/core/slash-commands.js.map +1 -0
  492. package/dist/core/source-info.d.ts +18 -0
  493. package/dist/core/source-info.d.ts.map +1 -0
  494. package/dist/core/source-info.js +19 -0
  495. package/dist/core/source-info.js.map +1 -0
  496. package/dist/core/subagents/delegation-scope.d.ts +47 -0
  497. package/dist/core/subagents/delegation-scope.d.ts.map +1 -0
  498. package/dist/core/subagents/delegation-scope.js +134 -0
  499. package/dist/core/subagents/delegation-scope.js.map +1 -0
  500. package/dist/core/subagents/index.d.ts +46 -0
  501. package/dist/core/subagents/index.d.ts.map +1 -0
  502. package/dist/core/subagents/index.js +369 -0
  503. package/dist/core/subagents/index.js.map +1 -0
  504. package/dist/core/subagents/manager.d.ts +183 -0
  505. package/dist/core/subagents/manager.d.ts.map +1 -0
  506. package/dist/core/subagents/manager.js +820 -0
  507. package/dist/core/subagents/manager.js.map +1 -0
  508. package/dist/core/system-prompt.d.ts +28 -0
  509. package/dist/core/system-prompt.d.ts.map +1 -0
  510. package/dist/core/system-prompt.js +180 -0
  511. package/dist/core/system-prompt.js.map +1 -0
  512. package/dist/core/telemetry.d.ts +3 -0
  513. package/dist/core/telemetry.d.ts.map +1 -0
  514. package/dist/core/telemetry.js +9 -0
  515. package/dist/core/telemetry.js.map +1 -0
  516. package/dist/core/theme/dark.json +86 -0
  517. package/dist/core/theme/discovery.d.ts +48 -0
  518. package/dist/core/theme/discovery.d.ts.map +1 -0
  519. package/dist/core/theme/discovery.js +239 -0
  520. package/dist/core/theme/discovery.js.map +1 -0
  521. package/dist/core/theme/index.d.ts +6 -0
  522. package/dist/core/theme/index.d.ts.map +1 -0
  523. package/dist/core/theme/index.js +5 -0
  524. package/dist/core/theme/index.js.map +1 -0
  525. package/dist/core/theme/light.json +85 -0
  526. package/dist/core/theme/runtime.d.ts +55 -0
  527. package/dist/core/theme/runtime.d.ts.map +1 -0
  528. package/dist/core/theme/runtime.js +555 -0
  529. package/dist/core/theme/runtime.js.map +1 -0
  530. package/dist/core/theme/theme-schema.json +335 -0
  531. package/dist/core/theme/theme-service.d.ts +37 -0
  532. package/dist/core/theme/theme-service.d.ts.map +1 -0
  533. package/dist/core/theme/theme-service.js +222 -0
  534. package/dist/core/theme/theme-service.js.map +1 -0
  535. package/dist/core/theme/theme.d.ts +100 -0
  536. package/dist/core/theme/theme.d.ts.map +1 -0
  537. package/dist/core/theme/theme.js +243 -0
  538. package/dist/core/theme/theme.js.map +1 -0
  539. package/dist/core/theme/tokens.d.ts +20 -0
  540. package/dist/core/theme/tokens.d.ts.map +1 -0
  541. package/dist/core/theme/tokens.js +178 -0
  542. package/dist/core/theme/tokens.js.map +1 -0
  543. package/dist/core/theme/types.d.ts +21 -0
  544. package/dist/core/theme/types.d.ts.map +1 -0
  545. package/dist/core/theme/types.js +2 -0
  546. package/dist/core/theme/types.js.map +1 -0
  547. package/dist/core/timings.d.ts +8 -0
  548. package/dist/core/timings.d.ts.map +1 -0
  549. package/dist/core/timings.js +31 -0
  550. package/dist/core/timings.js.map +1 -0
  551. package/dist/core/tools/bash.d.ts +68 -0
  552. package/dist/core/tools/bash.d.ts.map +1 -0
  553. package/dist/core/tools/bash.js +345 -0
  554. package/dist/core/tools/bash.js.map +1 -0
  555. package/dist/core/tools/diagnostics-provider.d.ts +12 -0
  556. package/dist/core/tools/diagnostics-provider.d.ts.map +1 -0
  557. package/dist/core/tools/diagnostics-provider.js +2 -0
  558. package/dist/core/tools/diagnostics-provider.js.map +1 -0
  559. package/dist/core/tools/edit-diff.d.ts +77 -0
  560. package/dist/core/tools/edit-diff.d.ts.map +1 -0
  561. package/dist/core/tools/edit-diff.js +506 -0
  562. package/dist/core/tools/edit-diff.js.map +1 -0
  563. package/dist/core/tools/edit.d.ts +56 -0
  564. package/dist/core/tools/edit.d.ts.map +1 -0
  565. package/dist/core/tools/edit.js +300 -0
  566. package/dist/core/tools/edit.js.map +1 -0
  567. package/dist/core/tools/file-mutation-queue.d.ts +6 -0
  568. package/dist/core/tools/file-mutation-queue.d.ts.map +1 -0
  569. package/dist/core/tools/file-mutation-queue.js +52 -0
  570. package/dist/core/tools/file-mutation-queue.js.map +1 -0
  571. package/dist/core/tools/find.d.ts +35 -0
  572. package/dist/core/tools/find.d.ts.map +1 -0
  573. package/dist/core/tools/find.js +337 -0
  574. package/dist/core/tools/find.js.map +1 -0
  575. package/dist/core/tools/grep.d.ts +37 -0
  576. package/dist/core/tools/grep.d.ts.map +1 -0
  577. package/dist/core/tools/grep.js +305 -0
  578. package/dist/core/tools/grep.js.map +1 -0
  579. package/dist/core/tools/index.d.ts +56 -0
  580. package/dist/core/tools/index.d.ts.map +1 -0
  581. package/dist/core/tools/index.js +174 -0
  582. package/dist/core/tools/index.js.map +1 -0
  583. package/dist/core/tools/ls.d.ts +37 -0
  584. package/dist/core/tools/ls.d.ts.map +1 -0
  585. package/dist/core/tools/ls.js +167 -0
  586. package/dist/core/tools/ls.js.map +1 -0
  587. package/dist/core/tools/lsp.d.ts +59 -0
  588. package/dist/core/tools/lsp.d.ts.map +1 -0
  589. package/dist/core/tools/lsp.js +157 -0
  590. package/dist/core/tools/lsp.js.map +1 -0
  591. package/dist/core/tools/output-accumulator.d.ts +52 -0
  592. package/dist/core/tools/output-accumulator.d.ts.map +1 -0
  593. package/dist/core/tools/output-accumulator.js +184 -0
  594. package/dist/core/tools/output-accumulator.js.map +1 -0
  595. package/dist/core/tools/path-utils.d.ts +10 -0
  596. package/dist/core/tools/path-utils.d.ts.map +1 -0
  597. package/dist/core/tools/path-utils.js +99 -0
  598. package/dist/core/tools/path-utils.js.map +1 -0
  599. package/dist/core/tools/read.d.ts +35 -0
  600. package/dist/core/tools/read.d.ts.map +1 -0
  601. package/dist/core/tools/read.js +292 -0
  602. package/dist/core/tools/read.js.map +1 -0
  603. package/dist/core/tools/render-utils.d.ts +26 -0
  604. package/dist/core/tools/render-utils.d.ts.map +1 -0
  605. package/dist/core/tools/render-utils.js +69 -0
  606. package/dist/core/tools/render-utils.js.map +1 -0
  607. package/dist/core/tools/subagent.d.ts +219 -0
  608. package/dist/core/tools/subagent.d.ts.map +1 -0
  609. package/dist/core/tools/subagent.js +1445 -0
  610. package/dist/core/tools/subagent.js.map +1 -0
  611. package/dist/core/tools/tool-definition-wrapper.d.ts +14 -0
  612. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -0
  613. package/dist/core/tools/tool-definition-wrapper.js +34 -0
  614. package/dist/core/tools/tool-definition-wrapper.js.map +1 -0
  615. package/dist/core/tools/truncate.d.ts +70 -0
  616. package/dist/core/tools/truncate.d.ts.map +1 -0
  617. package/dist/core/tools/truncate.js +215 -0
  618. package/dist/core/tools/truncate.js.map +1 -0
  619. package/dist/core/tools/web-search.d.ts +69 -0
  620. package/dist/core/tools/web-search.d.ts.map +1 -0
  621. package/dist/core/tools/web-search.js +634 -0
  622. package/dist/core/tools/web-search.js.map +1 -0
  623. package/dist/core/tools/write.d.ts +33 -0
  624. package/dist/core/tools/write.d.ts.map +1 -0
  625. package/dist/core/tools/write.js +210 -0
  626. package/dist/core/tools/write.js.map +1 -0
  627. package/dist/core/trust-manager.d.ts +36 -0
  628. package/dist/core/trust-manager.d.ts.map +1 -0
  629. package/dist/core/trust-manager.js +220 -0
  630. package/dist/core/trust-manager.js.map +1 -0
  631. package/dist/daemon/cli.d.ts +8 -0
  632. package/dist/daemon/cli.d.ts.map +1 -0
  633. package/dist/daemon/cli.js +468 -0
  634. package/dist/daemon/cli.js.map +1 -0
  635. package/dist/daemon/control-client.d.ts +60 -0
  636. package/dist/daemon/control-client.d.ts.map +1 -0
  637. package/dist/daemon/control-client.js +411 -0
  638. package/dist/daemon/control-client.js.map +1 -0
  639. package/dist/daemon/control-protocol.d.ts +507 -0
  640. package/dist/daemon/control-protocol.d.ts.map +1 -0
  641. package/dist/daemon/control-protocol.js +395 -0
  642. package/dist/daemon/control-protocol.js.map +1 -0
  643. package/dist/daemon/control-server.d.ts +77 -0
  644. package/dist/daemon/control-server.d.ts.map +1 -0
  645. package/dist/daemon/control-server.js +327 -0
  646. package/dist/daemon/control-server.js.map +1 -0
  647. package/dist/daemon/conversation-commands.d.ts +176 -0
  648. package/dist/daemon/conversation-commands.d.ts.map +1 -0
  649. package/dist/daemon/conversation-commands.js +1268 -0
  650. package/dist/daemon/conversation-commands.js.map +1 -0
  651. package/dist/daemon/daemon-lock.d.ts +27 -0
  652. package/dist/daemon/daemon-lock.d.ts.map +1 -0
  653. package/dist/daemon/daemon-lock.js +238 -0
  654. package/dist/daemon/daemon-lock.js.map +1 -0
  655. package/dist/daemon/handshake-responses.d.ts +47 -0
  656. package/dist/daemon/handshake-responses.d.ts.map +1 -0
  657. package/dist/daemon/handshake-responses.js +113 -0
  658. package/dist/daemon/handshake-responses.js.map +1 -0
  659. package/dist/daemon/integrated-runtimes.d.ts +137 -0
  660. package/dist/daemon/integrated-runtimes.d.ts.map +1 -0
  661. package/dist/daemon/integrated-runtimes.js +763 -0
  662. package/dist/daemon/integrated-runtimes.js.map +1 -0
  663. package/dist/daemon/iroh-native.d.ts +76 -0
  664. package/dist/daemon/iroh-native.d.ts.map +1 -0
  665. package/dist/daemon/iroh-native.js +18 -0
  666. package/dist/daemon/iroh-native.js.map +1 -0
  667. package/dist/daemon/iroh-resource-guard.d.ts +57 -0
  668. package/dist/daemon/iroh-resource-guard.d.ts.map +1 -0
  669. package/dist/daemon/iroh-resource-guard.js +134 -0
  670. package/dist/daemon/iroh-resource-guard.js.map +1 -0
  671. package/dist/daemon/iroh-service.d.ts +51 -0
  672. package/dist/daemon/iroh-service.d.ts.map +1 -0
  673. package/dist/daemon/iroh-service.js +2604 -0
  674. package/dist/daemon/iroh-service.js.map +1 -0
  675. package/dist/daemon/keep-awake.d.ts +62 -0
  676. package/dist/daemon/keep-awake.d.ts.map +1 -0
  677. package/dist/daemon/keep-awake.js +190 -0
  678. package/dist/daemon/keep-awake.js.map +1 -0
  679. package/dist/daemon/lease-broker.d.ts +139 -0
  680. package/dist/daemon/lease-broker.d.ts.map +1 -0
  681. package/dist/daemon/lease-broker.js +504 -0
  682. package/dist/daemon/lease-broker.js.map +1 -0
  683. package/dist/daemon/log.d.ts +16 -0
  684. package/dist/daemon/log.d.ts.map +1 -0
  685. package/dist/daemon/log.js +40 -0
  686. package/dist/daemon/log.js.map +1 -0
  687. package/dist/daemon/main.d.ts +70 -0
  688. package/dist/daemon/main.d.ts.map +1 -0
  689. package/dist/daemon/main.js +781 -0
  690. package/dist/daemon/main.js.map +1 -0
  691. package/dist/daemon/paths.d.ts +32 -0
  692. package/dist/daemon/paths.d.ts.map +1 -0
  693. package/dist/daemon/paths.js +208 -0
  694. package/dist/daemon/paths.js.map +1 -0
  695. package/dist/daemon/process-identity.d.ts +58 -0
  696. package/dist/daemon/process-identity.d.ts.map +1 -0
  697. package/dist/daemon/process-identity.js +185 -0
  698. package/dist/daemon/process-identity.js.map +1 -0
  699. package/dist/daemon/relay-stream.d.ts +89 -0
  700. package/dist/daemon/relay-stream.d.ts.map +1 -0
  701. package/dist/daemon/relay-stream.js +202 -0
  702. package/dist/daemon/relay-stream.js.map +1 -0
  703. package/dist/daemon/remote-cli.d.ts +7 -0
  704. package/dist/daemon/remote-cli.d.ts.map +1 -0
  705. package/dist/daemon/remote-cli.js +774 -0
  706. package/dist/daemon/remote-cli.js.map +1 -0
  707. package/dist/daemon/service-install.d.ts +43 -0
  708. package/dist/daemon/service-install.d.ts.map +1 -0
  709. package/dist/daemon/service-install.js +198 -0
  710. package/dist/daemon/service-install.js.map +1 -0
  711. package/dist/daemon/session-target.d.ts +62 -0
  712. package/dist/daemon/session-target.d.ts.map +1 -0
  713. package/dist/daemon/session-target.js +66 -0
  714. package/dist/daemon/session-target.js.map +1 -0
  715. package/dist/daemon/spawn.d.ts +53 -0
  716. package/dist/daemon/spawn.d.ts.map +1 -0
  717. package/dist/daemon/spawn.js +205 -0
  718. package/dist/daemon/spawn.js.map +1 -0
  719. package/dist/daemon/state.d.ts +145 -0
  720. package/dist/daemon/state.d.ts.map +1 -0
  721. package/dist/daemon/state.js +454 -0
  722. package/dist/daemon/state.js.map +1 -0
  723. package/dist/daemon/theme-push.d.ts +23 -0
  724. package/dist/daemon/theme-push.d.ts.map +1 -0
  725. package/dist/daemon/theme-push.js +30 -0
  726. package/dist/daemon/theme-push.js.map +1 -0
  727. package/dist/daemon/viewer-feed.d.ts +42 -0
  728. package/dist/daemon/viewer-feed.d.ts.map +1 -0
  729. package/dist/daemon/viewer-feed.js +138 -0
  730. package/dist/daemon/viewer-feed.js.map +1 -0
  731. package/dist/daemon/workspace-directory.d.ts +35 -0
  732. package/dist/daemon/workspace-directory.d.ts.map +1 -0
  733. package/dist/daemon/workspace-directory.js +105 -0
  734. package/dist/daemon/workspace-directory.js.map +1 -0
  735. package/dist/daemon/workspace-streams.d.ts +70 -0
  736. package/dist/daemon/workspace-streams.d.ts.map +1 -0
  737. package/dist/daemon/workspace-streams.js +289 -0
  738. package/dist/daemon/workspace-streams.js.map +1 -0
  739. package/dist/daemon/worktree-manager.d.ts +219 -0
  740. package/dist/daemon/worktree-manager.d.ts.map +1 -0
  741. package/dist/daemon/worktree-manager.js +1204 -0
  742. package/dist/daemon/worktree-manager.js.map +1 -0
  743. package/dist/index.d.ts +44 -0
  744. package/dist/index.d.ts.map +1 -0
  745. package/dist/index.js +57 -0
  746. package/dist/index.js.map +1 -0
  747. package/dist/main.d.ts +12 -0
  748. package/dist/main.d.ts.map +1 -0
  749. package/dist/main.js +773 -0
  750. package/dist/main.js.map +1 -0
  751. package/dist/mcp-cli.d.ts +6 -0
  752. package/dist/mcp-cli.d.ts.map +1 -0
  753. package/dist/mcp-cli.js +347 -0
  754. package/dist/mcp-cli.js.map +1 -0
  755. package/dist/migrations.d.ts +33 -0
  756. package/dist/migrations.d.ts.map +1 -0
  757. package/dist/migrations.js +281 -0
  758. package/dist/migrations.js.map +1 -0
  759. package/dist/modes/index.d.ts +13 -0
  760. package/dist/modes/index.d.ts.map +1 -0
  761. package/dist/modes/index.js +12 -0
  762. package/dist/modes/index.js.map +1 -0
  763. package/dist/modes/interactive/components/armin.d.ts +34 -0
  764. package/dist/modes/interactive/components/armin.d.ts.map +1 -0
  765. package/dist/modes/interactive/components/armin.js +333 -0
  766. package/dist/modes/interactive/components/armin.js.map +1 -0
  767. package/dist/modes/interactive/components/assistant-message.d.ts +22 -0
  768. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -0
  769. package/dist/modes/interactive/components/assistant-message.js +129 -0
  770. package/dist/modes/interactive/components/assistant-message.js.map +1 -0
  771. package/dist/modes/interactive/components/bash-execution.d.ts +39 -0
  772. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -0
  773. package/dist/modes/interactive/components/bash-execution.js +212 -0
  774. package/dist/modes/interactive/components/bash-execution.js.map +1 -0
  775. package/dist/modes/interactive/components/bordered-loader.d.ts +17 -0
  776. package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -0
  777. package/dist/modes/interactive/components/bordered-loader.js +57 -0
  778. package/dist/modes/interactive/components/bordered-loader.js.map +1 -0
  779. package/dist/modes/interactive/components/branch-summary-message.d.ts +16 -0
  780. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -0
  781. package/dist/modes/interactive/components/branch-summary-message.js +44 -0
  782. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -0
  783. package/dist/modes/interactive/components/compaction-summary-message.d.ts +16 -0
  784. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -0
  785. package/dist/modes/interactive/components/compaction-summary-message.js +45 -0
  786. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -0
  787. package/dist/modes/interactive/components/config-selector.d.ts +71 -0
  788. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -0
  789. package/dist/modes/interactive/components/config-selector.js +510 -0
  790. package/dist/modes/interactive/components/config-selector.js.map +1 -0
  791. package/dist/modes/interactive/components/countdown-timer.d.ts +14 -0
  792. package/dist/modes/interactive/components/countdown-timer.d.ts.map +1 -0
  793. package/dist/modes/interactive/components/countdown-timer.js +33 -0
  794. package/dist/modes/interactive/components/countdown-timer.js.map +1 -0
  795. package/dist/modes/interactive/components/custom-editor.d.ts +21 -0
  796. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -0
  797. package/dist/modes/interactive/components/custom-editor.js +70 -0
  798. package/dist/modes/interactive/components/custom-editor.js.map +1 -0
  799. package/dist/modes/interactive/components/custom-message.d.ts +20 -0
  800. package/dist/modes/interactive/components/custom-message.d.ts.map +1 -0
  801. package/dist/modes/interactive/components/custom-message.js +76 -0
  802. package/dist/modes/interactive/components/custom-message.js.map +1 -0
  803. package/dist/modes/interactive/components/daxnuts.d.ts +23 -0
  804. package/dist/modes/interactive/components/daxnuts.d.ts.map +1 -0
  805. package/dist/modes/interactive/components/daxnuts.js +140 -0
  806. package/dist/modes/interactive/components/daxnuts.js.map +1 -0
  807. package/dist/modes/interactive/components/diff.d.ts +12 -0
  808. package/dist/modes/interactive/components/diff.d.ts.map +1 -0
  809. package/dist/modes/interactive/components/diff.js +133 -0
  810. package/dist/modes/interactive/components/diff.js.map +1 -0
  811. package/dist/modes/interactive/components/dynamic-border.d.ts +15 -0
  812. package/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -0
  813. package/dist/modes/interactive/components/dynamic-border.js +21 -0
  814. package/dist/modes/interactive/components/dynamic-border.js.map +1 -0
  815. package/dist/modes/interactive/components/extension-editor.d.ts +20 -0
  816. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -0
  817. package/dist/modes/interactive/components/extension-editor.js +116 -0
  818. package/dist/modes/interactive/components/extension-editor.js.map +1 -0
  819. package/dist/modes/interactive/components/extension-input.d.ts +23 -0
  820. package/dist/modes/interactive/components/extension-input.d.ts.map +1 -0
  821. package/dist/modes/interactive/components/extension-input.js +61 -0
  822. package/dist/modes/interactive/components/extension-input.js.map +1 -0
  823. package/dist/modes/interactive/components/extension-selector.d.ts +27 -0
  824. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -0
  825. package/dist/modes/interactive/components/extension-selector.js +98 -0
  826. package/dist/modes/interactive/components/extension-selector.js.map +1 -0
  827. package/dist/modes/interactive/components/first-time-setup.d.ts +25 -0
  828. package/dist/modes/interactive/components/first-time-setup.d.ts.map +1 -0
  829. package/dist/modes/interactive/components/first-time-setup.js +103 -0
  830. package/dist/modes/interactive/components/first-time-setup.js.map +1 -0
  831. package/dist/modes/interactive/components/footer.d.ts +27 -0
  832. package/dist/modes/interactive/components/footer.d.ts.map +1 -0
  833. package/dist/modes/interactive/components/footer.js +180 -0
  834. package/dist/modes/interactive/components/footer.js.map +1 -0
  835. package/dist/modes/interactive/components/hotkeys.d.ts +24 -0
  836. package/dist/modes/interactive/components/hotkeys.d.ts.map +1 -0
  837. package/dist/modes/interactive/components/hotkeys.js +97 -0
  838. package/dist/modes/interactive/components/hotkeys.js.map +1 -0
  839. package/dist/modes/interactive/components/index.d.ts +35 -0
  840. package/dist/modes/interactive/components/index.d.ts.map +1 -0
  841. package/dist/modes/interactive/components/index.js +36 -0
  842. package/dist/modes/interactive/components/index.js.map +1 -0
  843. package/dist/modes/interactive/components/keybinding-hints.d.ts +19 -0
  844. package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -0
  845. package/dist/modes/interactive/components/keybinding-hints.js +48 -0
  846. package/dist/modes/interactive/components/keybinding-hints.js.map +1 -0
  847. package/dist/modes/interactive/components/login-dialog.d.ts +52 -0
  848. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -0
  849. package/dist/modes/interactive/components/login-dialog.js +179 -0
  850. package/dist/modes/interactive/components/login-dialog.js.map +1 -0
  851. package/dist/modes/interactive/components/logo.d.ts +25 -0
  852. package/dist/modes/interactive/components/logo.d.ts.map +1 -0
  853. package/dist/modes/interactive/components/logo.js +89 -0
  854. package/dist/modes/interactive/components/logo.js.map +1 -0
  855. package/dist/modes/interactive/components/model-selector.d.ts +47 -0
  856. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -0
  857. package/dist/modes/interactive/components/model-selector.js +278 -0
  858. package/dist/modes/interactive/components/model-selector.js.map +1 -0
  859. package/dist/modes/interactive/components/oauth-selector.d.ts +31 -0
  860. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -0
  861. package/dist/modes/interactive/components/oauth-selector.js +165 -0
  862. package/dist/modes/interactive/components/oauth-selector.js.map +1 -0
  863. package/dist/modes/interactive/components/remote-control-center.d.ts +110 -0
  864. package/dist/modes/interactive/components/remote-control-center.d.ts.map +1 -0
  865. package/dist/modes/interactive/components/remote-control-center.js +1217 -0
  866. package/dist/modes/interactive/components/remote-control-center.js.map +1 -0
  867. package/dist/modes/interactive/components/review-tools-selector.d.ts +24 -0
  868. package/dist/modes/interactive/components/review-tools-selector.d.ts.map +1 -0
  869. package/dist/modes/interactive/components/review-tools-selector.js +93 -0
  870. package/dist/modes/interactive/components/review-tools-selector.js.map +1 -0
  871. package/dist/modes/interactive/components/scoped-models-selector.d.ts +42 -0
  872. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -0
  873. package/dist/modes/interactive/components/scoped-models-selector.js +290 -0
  874. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -0
  875. package/dist/modes/interactive/components/session-selector-search.d.ts +23 -0
  876. package/dist/modes/interactive/components/session-selector-search.d.ts.map +1 -0
  877. package/dist/modes/interactive/components/session-selector-search.js +155 -0
  878. package/dist/modes/interactive/components/session-selector-search.js.map +1 -0
  879. package/dist/modes/interactive/components/session-selector.d.ts +95 -0
  880. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -0
  881. package/dist/modes/interactive/components/session-selector.js +856 -0
  882. package/dist/modes/interactive/components/session-selector.js.map +1 -0
  883. package/dist/modes/interactive/components/settings-selector.d.ts +77 -0
  884. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -0
  885. package/dist/modes/interactive/components/settings-selector.js +484 -0
  886. package/dist/modes/interactive/components/settings-selector.js.map +1 -0
  887. package/dist/modes/interactive/components/show-images-selector.d.ts +10 -0
  888. package/dist/modes/interactive/components/show-images-selector.d.ts.map +1 -0
  889. package/dist/modes/interactive/components/show-images-selector.js +39 -0
  890. package/dist/modes/interactive/components/show-images-selector.js.map +1 -0
  891. package/dist/modes/interactive/components/skill-invocation-message.d.ts +17 -0
  892. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -0
  893. package/dist/modes/interactive/components/skill-invocation-message.js +47 -0
  894. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -0
  895. package/dist/modes/interactive/components/streaming-render-coalescer.d.ts +29 -0
  896. package/dist/modes/interactive/components/streaming-render-coalescer.d.ts.map +1 -0
  897. package/dist/modes/interactive/components/streaming-render-coalescer.js +98 -0
  898. package/dist/modes/interactive/components/streaming-render-coalescer.js.map +1 -0
  899. package/dist/modes/interactive/components/subagent-inspector.d.ts +32 -0
  900. package/dist/modes/interactive/components/subagent-inspector.d.ts.map +1 -0
  901. package/dist/modes/interactive/components/subagent-inspector.js +439 -0
  902. package/dist/modes/interactive/components/subagent-inspector.js.map +1 -0
  903. package/dist/modes/interactive/components/theme-selector.d.ts +11 -0
  904. package/dist/modes/interactive/components/theme-selector.d.ts.map +1 -0
  905. package/dist/modes/interactive/components/theme-selector.js +50 -0
  906. package/dist/modes/interactive/components/theme-selector.js.map +1 -0
  907. package/dist/modes/interactive/components/thinking-selector.d.ts +11 -0
  908. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -0
  909. package/dist/modes/interactive/components/thinking-selector.js +52 -0
  910. package/dist/modes/interactive/components/thinking-selector.js.map +1 -0
  911. package/dist/modes/interactive/components/tool-execution.d.ts +83 -0
  912. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -0
  913. package/dist/modes/interactive/components/tool-execution.js +421 -0
  914. package/dist/modes/interactive/components/tool-execution.js.map +1 -0
  915. package/dist/modes/interactive/components/tree-selector.d.ts +89 -0
  916. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -0
  917. package/dist/modes/interactive/components/tree-selector.js +1208 -0
  918. package/dist/modes/interactive/components/tree-selector.js.map +1 -0
  919. package/dist/modes/interactive/components/trust-selector.d.ts +23 -0
  920. package/dist/modes/interactive/components/trust-selector.d.ts.map +1 -0
  921. package/dist/modes/interactive/components/trust-selector.js +91 -0
  922. package/dist/modes/interactive/components/trust-selector.js.map +1 -0
  923. package/dist/modes/interactive/components/user-message-selector.d.ts +30 -0
  924. package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -0
  925. package/dist/modes/interactive/components/user-message-selector.js +114 -0
  926. package/dist/modes/interactive/components/user-message-selector.js.map +1 -0
  927. package/dist/modes/interactive/components/user-message.d.ts +9 -0
  928. package/dist/modes/interactive/components/user-message.d.ts.map +1 -0
  929. package/dist/modes/interactive/components/user-message.js +42 -0
  930. package/dist/modes/interactive/components/user-message.js.map +1 -0
  931. package/dist/modes/interactive/components/visual-truncate.d.ts +24 -0
  932. package/dist/modes/interactive/components/visual-truncate.d.ts.map +1 -0
  933. package/dist/modes/interactive/components/visual-truncate.js +33 -0
  934. package/dist/modes/interactive/components/visual-truncate.js.map +1 -0
  935. package/dist/modes/interactive/components/volt-announcement.d.ts +5 -0
  936. package/dist/modes/interactive/components/volt-announcement.d.ts.map +1 -0
  937. package/dist/modes/interactive/components/volt-announcement.js +15 -0
  938. package/dist/modes/interactive/components/volt-announcement.js.map +1 -0
  939. package/dist/modes/interactive/daemon-attach.d.ts +154 -0
  940. package/dist/modes/interactive/daemon-attach.d.ts.map +1 -0
  941. package/dist/modes/interactive/daemon-attach.js +552 -0
  942. package/dist/modes/interactive/daemon-attach.js.map +1 -0
  943. package/dist/modes/interactive/drain-viewer.d.ts +31 -0
  944. package/dist/modes/interactive/drain-viewer.d.ts.map +1 -0
  945. package/dist/modes/interactive/drain-viewer.js +166 -0
  946. package/dist/modes/interactive/drain-viewer.js.map +1 -0
  947. package/dist/modes/interactive/interactive-mode.d.ts +502 -0
  948. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -0
  949. package/dist/modes/interactive/interactive-mode.js +7007 -0
  950. package/dist/modes/interactive/interactive-mode.js.map +1 -0
  951. package/dist/modes/interactive/prompt-image-attachments.d.ts +34 -0
  952. package/dist/modes/interactive/prompt-image-attachments.d.ts.map +1 -0
  953. package/dist/modes/interactive/prompt-image-attachments.js +120 -0
  954. package/dist/modes/interactive/prompt-image-attachments.js.map +1 -0
  955. package/dist/modes/interactive/relay-stream-adapter.d.ts +19 -0
  956. package/dist/modes/interactive/relay-stream-adapter.d.ts.map +1 -0
  957. package/dist/modes/interactive/relay-stream-adapter.js +107 -0
  958. package/dist/modes/interactive/relay-stream-adapter.js.map +1 -0
  959. package/dist/modes/print-mode.d.ts +28 -0
  960. package/dist/modes/print-mode.d.ts.map +1 -0
  961. package/dist/modes/print-mode.js +132 -0
  962. package/dist/modes/print-mode.js.map +1 -0
  963. package/dist/modes/rpc/in-process-rpc-client.d.ts +31 -0
  964. package/dist/modes/rpc/in-process-rpc-client.d.ts.map +1 -0
  965. package/dist/modes/rpc/in-process-rpc-client.js +63 -0
  966. package/dist/modes/rpc/in-process-rpc-client.js.map +1 -0
  967. package/dist/modes/rpc/iroh-remote-agent-runtime.d.ts +61 -0
  968. package/dist/modes/rpc/iroh-remote-agent-runtime.d.ts.map +1 -0
  969. package/dist/modes/rpc/iroh-remote-agent-runtime.js +155 -0
  970. package/dist/modes/rpc/iroh-remote-agent-runtime.js.map +1 -0
  971. package/dist/modes/rpc/iroh-remote-rpc-mode.d.ts +69 -0
  972. package/dist/modes/rpc/iroh-remote-rpc-mode.d.ts.map +1 -0
  973. package/dist/modes/rpc/iroh-remote-rpc-mode.js +1223 -0
  974. package/dist/modes/rpc/iroh-remote-rpc-mode.js.map +1 -0
  975. package/dist/modes/rpc/jsonl.d.ts +2 -0
  976. package/dist/modes/rpc/jsonl.d.ts.map +1 -0
  977. package/dist/modes/rpc/jsonl.js +2 -0
  978. package/dist/modes/rpc/jsonl.js.map +1 -0
  979. package/dist/modes/rpc/rpc-client-base.d.ts +209 -0
  980. package/dist/modes/rpc/rpc-client-base.d.ts.map +1 -0
  981. package/dist/modes/rpc/rpc-client-base.js +530 -0
  982. package/dist/modes/rpc/rpc-client-base.js.map +1 -0
  983. package/dist/modes/rpc/rpc-client.d.ts +50 -0
  984. package/dist/modes/rpc/rpc-client.d.ts.map +1 -0
  985. package/dist/modes/rpc/rpc-client.js +190 -0
  986. package/dist/modes/rpc/rpc-client.js.map +1 -0
  987. package/dist/modes/rpc/rpc-command-dispatcher.d.ts +43 -0
  988. package/dist/modes/rpc/rpc-command-dispatcher.d.ts.map +1 -0
  989. package/dist/modes/rpc/rpc-command-dispatcher.js +613 -0
  990. package/dist/modes/rpc/rpc-command-dispatcher.js.map +1 -0
  991. package/dist/modes/rpc/rpc-command-validation.d.ts +2 -0
  992. package/dist/modes/rpc/rpc-command-validation.d.ts.map +1 -0
  993. package/dist/modes/rpc/rpc-command-validation.js +207 -0
  994. package/dist/modes/rpc/rpc-command-validation.js.map +1 -0
  995. package/dist/modes/rpc/rpc-mode.d.ts +48 -0
  996. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -0
  997. package/dist/modes/rpc/rpc-mode.js +1089 -0
  998. package/dist/modes/rpc/rpc-mode.js.map +1 -0
  999. package/dist/modes/rpc/rpc-transport-client.d.ts +26 -0
  1000. package/dist/modes/rpc/rpc-transport-client.d.ts.map +1 -0
  1001. package/dist/modes/rpc/rpc-transport-client.js +76 -0
  1002. package/dist/modes/rpc/rpc-transport-client.js.map +1 -0
  1003. package/dist/modes/rpc/rpc-types.d.ts +2 -0
  1004. package/dist/modes/rpc/rpc-types.d.ts.map +1 -0
  1005. package/dist/modes/rpc/rpc-types.js +2 -0
  1006. package/dist/modes/rpc/rpc-types.js.map +1 -0
  1007. package/dist/package-manager-cli.d.ts +28 -0
  1008. package/dist/package-manager-cli.d.ts.map +1 -0
  1009. package/dist/package-manager-cli.js +667 -0
  1010. package/dist/package-manager-cli.js.map +1 -0
  1011. package/dist/remote/integrated-runtime-retention.d.ts +17 -0
  1012. package/dist/remote/integrated-runtime-retention.d.ts.map +1 -0
  1013. package/dist/remote/integrated-runtime-retention.js +62 -0
  1014. package/dist/remote/integrated-runtime-retention.js.map +1 -0
  1015. package/dist/remote/iroh-native-adapter.cjs +19 -0
  1016. package/dist/sea/cli.d.ts +4 -0
  1017. package/dist/sea/cli.d.ts.map +1 -0
  1018. package/dist/sea/cli.js +4 -0
  1019. package/dist/sea/cli.js.map +1 -0
  1020. package/dist/sea/register-bedrock.d.ts +2 -0
  1021. package/dist/sea/register-bedrock.d.ts.map +1 -0
  1022. package/dist/sea/register-bedrock.js +4 -0
  1023. package/dist/sea/register-bedrock.js.map +1 -0
  1024. package/dist/store/catalog.d.ts +57 -0
  1025. package/dist/store/catalog.d.ts.map +1 -0
  1026. package/dist/store/catalog.js +331 -0
  1027. package/dist/store/catalog.js.map +1 -0
  1028. package/dist/store/inspector.d.ts +36 -0
  1029. package/dist/store/inspector.d.ts.map +1 -0
  1030. package/dist/store/inspector.js +578 -0
  1031. package/dist/store/inspector.js.map +1 -0
  1032. package/dist/store/install-plan.d.ts +26 -0
  1033. package/dist/store/install-plan.d.ts.map +1 -0
  1034. package/dist/store/install-plan.js +80 -0
  1035. package/dist/store/install-plan.js.map +1 -0
  1036. package/dist/store/render.d.ts +11 -0
  1037. package/dist/store/render.d.ts.map +1 -0
  1038. package/dist/store/render.js +153 -0
  1039. package/dist/store/render.js.map +1 -0
  1040. package/dist/store/resolver.d.ts +22 -0
  1041. package/dist/store/resolver.d.ts.map +1 -0
  1042. package/dist/store/resolver.js +207 -0
  1043. package/dist/store/resolver.js.map +1 -0
  1044. package/dist/store/store-cli.d.ts +7 -0
  1045. package/dist/store/store-cli.d.ts.map +1 -0
  1046. package/dist/store/store-cli.js +636 -0
  1047. package/dist/store/store-cli.js.map +1 -0
  1048. package/dist/store/targets.d.ts +21 -0
  1049. package/dist/store/targets.d.ts.map +1 -0
  1050. package/dist/store/targets.js +55 -0
  1051. package/dist/store/targets.js.map +1 -0
  1052. package/dist/utils/ansi.d.ts +2 -0
  1053. package/dist/utils/ansi.d.ts.map +1 -0
  1054. package/dist/utils/ansi.js +52 -0
  1055. package/dist/utils/ansi.js.map +1 -0
  1056. package/dist/utils/changelog.d.ts +22 -0
  1057. package/dist/utils/changelog.d.ts.map +1 -0
  1058. package/dist/utils/changelog.js +165 -0
  1059. package/dist/utils/changelog.js.map +1 -0
  1060. package/dist/utils/child-process.d.ts +18 -0
  1061. package/dist/utils/child-process.d.ts.map +1 -0
  1062. package/dist/utils/child-process.js +106 -0
  1063. package/dist/utils/child-process.js.map +1 -0
  1064. package/dist/utils/clipboard-image.d.ts +11 -0
  1065. package/dist/utils/clipboard-image.d.ts.map +1 -0
  1066. package/dist/utils/clipboard-image.js +245 -0
  1067. package/dist/utils/clipboard-image.js.map +1 -0
  1068. package/dist/utils/clipboard.d.ts +2 -0
  1069. package/dist/utils/clipboard.d.ts.map +1 -0
  1070. package/dist/utils/clipboard.js +94 -0
  1071. package/dist/utils/clipboard.js.map +1 -0
  1072. package/dist/utils/deprecation.d.ts +4 -0
  1073. package/dist/utils/deprecation.d.ts.map +1 -0
  1074. package/dist/utils/deprecation.js +13 -0
  1075. package/dist/utils/deprecation.js.map +1 -0
  1076. package/dist/utils/durable-atomic-write.d.ts +46 -0
  1077. package/dist/utils/durable-atomic-write.d.ts.map +1 -0
  1078. package/dist/utils/durable-atomic-write.js +104 -0
  1079. package/dist/utils/durable-atomic-write.js.map +1 -0
  1080. package/dist/utils/frontmatter.d.ts +8 -0
  1081. package/dist/utils/frontmatter.d.ts.map +1 -0
  1082. package/dist/utils/frontmatter.js +26 -0
  1083. package/dist/utils/frontmatter.js.map +1 -0
  1084. package/dist/utils/fs-watch.d.ts +5 -0
  1085. package/dist/utils/fs-watch.d.ts.map +1 -0
  1086. package/dist/utils/fs-watch.js +25 -0
  1087. package/dist/utils/fs-watch.js.map +1 -0
  1088. package/dist/utils/git.d.ts +26 -0
  1089. package/dist/utils/git.d.ts.map +1 -0
  1090. package/dist/utils/git.js +195 -0
  1091. package/dist/utils/git.js.map +1 -0
  1092. package/dist/utils/html.d.ts +7 -0
  1093. package/dist/utils/html.d.ts.map +1 -0
  1094. package/dist/utils/html.js +40 -0
  1095. package/dist/utils/html.js.map +1 -0
  1096. package/dist/utils/ignore-files.d.ts +5 -0
  1097. package/dist/utils/ignore-files.d.ts.map +1 -0
  1098. package/dist/utils/ignore-files.js +58 -0
  1099. package/dist/utils/ignore-files.js.map +1 -0
  1100. package/dist/utils/image-codec.d.ts +22 -0
  1101. package/dist/utils/image-codec.d.ts.map +1 -0
  1102. package/dist/utils/image-codec.js +35 -0
  1103. package/dist/utils/image-codec.js.map +1 -0
  1104. package/dist/utils/image-convert.d.ts +9 -0
  1105. package/dist/utils/image-convert.d.ts.map +1 -0
  1106. package/dist/utils/image-convert.js +26 -0
  1107. package/dist/utils/image-convert.js.map +1 -0
  1108. package/dist/utils/image-resize-core.d.ts +31 -0
  1109. package/dist/utils/image-resize-core.d.ts.map +1 -0
  1110. package/dist/utils/image-resize-core.js +181 -0
  1111. package/dist/utils/image-resize-core.js.map +1 -0
  1112. package/dist/utils/image-resize-worker.d.ts +2 -0
  1113. package/dist/utils/image-resize-worker.d.ts.map +1 -0
  1114. package/dist/utils/image-resize-worker.js +31 -0
  1115. package/dist/utils/image-resize-worker.js.map +1 -0
  1116. package/dist/utils/image-resize.d.ts +15 -0
  1117. package/dist/utils/image-resize.d.ts.map +1 -0
  1118. package/dist/utils/image-resize.js +92 -0
  1119. package/dist/utils/image-resize.js.map +1 -0
  1120. package/dist/utils/json.d.ts +3 -0
  1121. package/dist/utils/json.d.ts.map +1 -0
  1122. package/dist/utils/json.js +7 -0
  1123. package/dist/utils/json.js.map +1 -0
  1124. package/dist/utils/mime.d.ts +3 -0
  1125. package/dist/utils/mime.d.ts.map +1 -0
  1126. package/dist/utils/mime.js +69 -0
  1127. package/dist/utils/mime.js.map +1 -0
  1128. package/dist/utils/npm-spec.d.ts +9 -0
  1129. package/dist/utils/npm-spec.d.ts.map +1 -0
  1130. package/dist/utils/npm-spec.js +15 -0
  1131. package/dist/utils/npm-spec.js.map +1 -0
  1132. package/dist/utils/open-browser.d.ts +9 -0
  1133. package/dist/utils/open-browser.d.ts.map +1 -0
  1134. package/dist/utils/open-browser.js +22 -0
  1135. package/dist/utils/open-browser.js.map +1 -0
  1136. package/dist/utils/paths.d.ts +31 -0
  1137. package/dist/utils/paths.d.ts.map +1 -0
  1138. package/dist/utils/paths.js +92 -0
  1139. package/dist/utils/paths.js.map +1 -0
  1140. package/dist/utils/private-files.d.ts +21 -0
  1141. package/dist/utils/private-files.d.ts.map +1 -0
  1142. package/dist/utils/private-files.js +103 -0
  1143. package/dist/utils/private-files.js.map +1 -0
  1144. package/dist/utils/process-env.d.ts +2 -0
  1145. package/dist/utils/process-env.d.ts.map +1 -0
  1146. package/dist/utils/process-env.js +31 -0
  1147. package/dist/utils/process-env.js.map +1 -0
  1148. package/dist/utils/shell.d.ts +30 -0
  1149. package/dist/utils/shell.d.ts.map +1 -0
  1150. package/dist/utils/shell.js +195 -0
  1151. package/dist/utils/shell.js.map +1 -0
  1152. package/dist/utils/sleep.d.ts +5 -0
  1153. package/dist/utils/sleep.d.ts.map +1 -0
  1154. package/dist/utils/sleep.js +17 -0
  1155. package/dist/utils/sleep.js.map +1 -0
  1156. package/dist/utils/syntax-highlight.d.ts +18 -0
  1157. package/dist/utils/syntax-highlight.d.ts.map +1 -0
  1158. package/dist/utils/syntax-highlight.js +139 -0
  1159. package/dist/utils/syntax-highlight.js.map +1 -0
  1160. package/dist/utils/tools-manager.d.ts +3 -0
  1161. package/dist/utils/tools-manager.d.ts.map +1 -0
  1162. package/dist/utils/tools-manager.js +328 -0
  1163. package/dist/utils/tools-manager.js.map +1 -0
  1164. package/dist/utils/version-check.d.ts +15 -0
  1165. package/dist/utils/version-check.d.ts.map +1 -0
  1166. package/dist/utils/version-check.js +61 -0
  1167. package/dist/utils/version-check.js.map +1 -0
  1168. package/dist/utils/volt-user-agent.d.ts +2 -0
  1169. package/dist/utils/volt-user-agent.d.ts.map +1 -0
  1170. package/dist/utils/volt-user-agent.js +5 -0
  1171. package/dist/utils/volt-user-agent.js.map +1 -0
  1172. package/dist/utils/windows-self-update.d.ts +3 -0
  1173. package/dist/utils/windows-self-update.d.ts.map +1 -0
  1174. package/dist/utils/windows-self-update.js +77 -0
  1175. package/dist/utils/windows-self-update.js.map +1 -0
  1176. package/docs/compaction.md +394 -0
  1177. package/docs/containerization.md +111 -0
  1178. package/docs/custom-provider.md +737 -0
  1179. package/docs/daemon.md +190 -0
  1180. package/docs/development.md +70 -0
  1181. package/docs/docs.json +176 -0
  1182. package/docs/extensions.md +2692 -0
  1183. package/docs/images/exy.png +0 -0
  1184. package/docs/images/interactive-mode.png +0 -0
  1185. package/docs/images/tree-view.png +0 -0
  1186. package/docs/index.md +82 -0
  1187. package/docs/iroh-remote-access-design.md +340 -0
  1188. package/docs/iroh-remote-protocol.md +431 -0
  1189. package/docs/json.md +87 -0
  1190. package/docs/keybindings.md +200 -0
  1191. package/docs/live-shared-session-daemon-design.md +1090 -0
  1192. package/docs/lsp.md +143 -0
  1193. package/docs/mcp.md +1044 -0
  1194. package/docs/models.md +495 -0
  1195. package/docs/packages.md +230 -0
  1196. package/docs/prompt-templates.md +108 -0
  1197. package/docs/providers.md +274 -0
  1198. package/docs/quickstart.md +165 -0
  1199. package/docs/rpc.md +2050 -0
  1200. package/docs/sdk.md +1267 -0
  1201. package/docs/security.md +139 -0
  1202. package/docs/session-format.md +412 -0
  1203. package/docs/sessions.md +145 -0
  1204. package/docs/settings.md +371 -0
  1205. package/docs/shell-aliases.md +13 -0
  1206. package/docs/skills.md +243 -0
  1207. package/docs/subagents-design.md +419 -0
  1208. package/docs/terminal-setup.md +148 -0
  1209. package/docs/termux.md +127 -0
  1210. package/docs/themes.md +295 -0
  1211. package/docs/tla/ClientAuth.cfg +16 -0
  1212. package/docs/tla/ClientAuth.tla +205 -0
  1213. package/docs/tla/ClientConn.cfg +16 -0
  1214. package/docs/tla/ClientConn.tla +265 -0
  1215. package/docs/tla/LeaseBroker.cfg +43 -0
  1216. package/docs/tla/LeaseBroker.tla +563 -0
  1217. package/docs/tla/PLAN.md +240 -0
  1218. package/docs/tla/PushOrdering.cfg +17 -0
  1219. package/docs/tla/PushOrdering.tla +183 -0
  1220. package/docs/tla/README.md +205 -0
  1221. package/docs/tla/RelayViewer.cfg +26 -0
  1222. package/docs/tla/RelayViewer.tla +299 -0
  1223. package/docs/tla/SessionTarget.cfg +18 -0
  1224. package/docs/tla/SessionTarget.tla +192 -0
  1225. package/docs/tla/check.sh +34 -0
  1226. package/docs/tmux.md +63 -0
  1227. package/docs/tui.md +949 -0
  1228. package/docs/usage.md +482 -0
  1229. package/docs/windows.md +17 -0
  1230. package/docs/worktrees-design.md +605 -0
  1231. package/examples/README.md +28 -0
  1232. package/examples/extensions/README.md +211 -0
  1233. package/examples/extensions/auto-commit-on-exit.ts +49 -0
  1234. package/examples/extensions/bash-spawn-hook.ts +30 -0
  1235. package/examples/extensions/bookmark.ts +50 -0
  1236. package/examples/extensions/border-status-editor.ts +150 -0
  1237. package/examples/extensions/built-in-tool-renderer.ts +249 -0
  1238. package/examples/extensions/claude-rules.ts +86 -0
  1239. package/examples/extensions/commands.ts +72 -0
  1240. package/examples/extensions/confirm-destructive.ts +59 -0
  1241. package/examples/extensions/custom-compaction.ts +127 -0
  1242. package/examples/extensions/custom-footer.ts +64 -0
  1243. package/examples/extensions/custom-header.ts +72 -0
  1244. package/examples/extensions/custom-provider-anthropic/index.ts +604 -0
  1245. package/examples/extensions/custom-provider-anthropic/package-lock.json +24 -0
  1246. package/examples/extensions/custom-provider-anthropic/package.json +19 -0
  1247. package/examples/extensions/custom-provider-gitlab-duo/index.ts +400 -0
  1248. package/examples/extensions/custom-provider-gitlab-duo/package.json +16 -0
  1249. package/examples/extensions/custom-provider-gitlab-duo/test.ts +82 -0
  1250. package/examples/extensions/dirty-repo-guard.ts +56 -0
  1251. package/examples/extensions/dynamic-resources/SKILL.md +8 -0
  1252. package/examples/extensions/dynamic-resources/dynamic.json +79 -0
  1253. package/examples/extensions/dynamic-resources/dynamic.md +5 -0
  1254. package/examples/extensions/dynamic-resources/index.ts +15 -0
  1255. package/examples/extensions/dynamic-tools.ts +74 -0
  1256. package/examples/extensions/event-bus.ts +43 -0
  1257. package/examples/extensions/file-trigger.ts +41 -0
  1258. package/examples/extensions/git-checkpoint.ts +53 -0
  1259. package/examples/extensions/git-merge-and-resolve.ts +115 -0
  1260. package/examples/extensions/github-issue-autocomplete.ts +185 -0
  1261. package/examples/extensions/gondolin/index.ts +531 -0
  1262. package/examples/extensions/gondolin/package-lock.json +185 -0
  1263. package/examples/extensions/gondolin/package.json +19 -0
  1264. package/examples/extensions/handoff.ts +191 -0
  1265. package/examples/extensions/hello.ts +26 -0
  1266. package/examples/extensions/hidden-thinking-label.ts +53 -0
  1267. package/examples/extensions/inline-bash.ts +94 -0
  1268. package/examples/extensions/input-transform-streaming.ts +39 -0
  1269. package/examples/extensions/input-transform.ts +43 -0
  1270. package/examples/extensions/interactive-shell.ts +196 -0
  1271. package/examples/extensions/mac-system-theme.ts +47 -0
  1272. package/examples/extensions/message-renderer.ts +59 -0
  1273. package/examples/extensions/minimal-mode.ts +426 -0
  1274. package/examples/extensions/modal-editor.ts +85 -0
  1275. package/examples/extensions/model-status.ts +31 -0
  1276. package/examples/extensions/notify.ts +55 -0
  1277. package/examples/extensions/overlay-qa-tests.ts +1450 -0
  1278. package/examples/extensions/overlay-test.ts +153 -0
  1279. package/examples/extensions/permission-gate.ts +34 -0
  1280. package/examples/extensions/pirate.ts +47 -0
  1281. package/examples/extensions/plan-mode/README.md +65 -0
  1282. package/examples/extensions/plan-mode/index.ts +340 -0
  1283. package/examples/extensions/plan-mode/utils.ts +168 -0
  1284. package/examples/extensions/preset.ts +436 -0
  1285. package/examples/extensions/project-trust.ts +64 -0
  1286. package/examples/extensions/prompt-customizer.ts +97 -0
  1287. package/examples/extensions/protected-paths.ts +30 -0
  1288. package/examples/extensions/provider-payload.ts +18 -0
  1289. package/examples/extensions/qna.ts +122 -0
  1290. package/examples/extensions/question.ts +277 -0
  1291. package/examples/extensions/questionnaire.ts +440 -0
  1292. package/examples/extensions/rainbow-editor.ts +88 -0
  1293. package/examples/extensions/reload-runtime.ts +37 -0
  1294. package/examples/extensions/rpc-demo.ts +118 -0
  1295. package/examples/extensions/sandbox/index.ts +321 -0
  1296. package/examples/extensions/sandbox/package-lock.json +92 -0
  1297. package/examples/extensions/sandbox/package.json +19 -0
  1298. package/examples/extensions/send-user-message.ts +97 -0
  1299. package/examples/extensions/session-name.ts +27 -0
  1300. package/examples/extensions/shutdown-command.ts +63 -0
  1301. package/examples/extensions/snake.ts +343 -0
  1302. package/examples/extensions/space-invaders.ts +560 -0
  1303. package/examples/extensions/ssh.ts +220 -0
  1304. package/examples/extensions/status-line.ts +32 -0
  1305. package/examples/extensions/structured-output.ts +65 -0
  1306. package/examples/extensions/subagent/README.md +175 -0
  1307. package/examples/extensions/subagent/agents/planner.md +37 -0
  1308. package/examples/extensions/subagent/agents/reviewer.md +35 -0
  1309. package/examples/extensions/subagent/agents/scout.md +50 -0
  1310. package/examples/extensions/subagent/agents/worker.md +24 -0
  1311. package/examples/extensions/subagent/agents.ts +126 -0
  1312. package/examples/extensions/subagent/index.ts +1009 -0
  1313. package/examples/extensions/subagent/prompts/implement-and-review.md +10 -0
  1314. package/examples/extensions/subagent/prompts/implement.md +10 -0
  1315. package/examples/extensions/subagent/prompts/scout-and-plan.md +9 -0
  1316. package/examples/extensions/summarize.ts +206 -0
  1317. package/examples/extensions/system-prompt-header.ts +17 -0
  1318. package/examples/extensions/tic-tac-toe.ts +1008 -0
  1319. package/examples/extensions/timed-confirm.ts +70 -0
  1320. package/examples/extensions/titlebar-spinner.ts +58 -0
  1321. package/examples/extensions/todo.ts +297 -0
  1322. package/examples/extensions/tool-override.ts +144 -0
  1323. package/examples/extensions/tools.ts +146 -0
  1324. package/examples/extensions/trigger-compact.ts +50 -0
  1325. package/examples/extensions/truncated-tool.ts +195 -0
  1326. package/examples/extensions/widget-placement.ts +9 -0
  1327. package/examples/extensions/with-deps/index.ts +32 -0
  1328. package/examples/extensions/with-deps/package-lock.json +31 -0
  1329. package/examples/extensions/with-deps/package.json +22 -0
  1330. package/examples/extensions/working-indicator.ts +123 -0
  1331. package/examples/extensions/working-message-test.ts +25 -0
  1332. package/examples/remote/firebase-push-relay/README.md +79 -0
  1333. package/examples/remote/firebase-push-relay/firebase.json +12 -0
  1334. package/examples/remote/firebase-push-relay/firestore.indexes.json +11 -0
  1335. package/examples/remote/firebase-push-relay/firestore.rules +9 -0
  1336. package/examples/remote/firebase-push-relay/functions/core.js +511 -0
  1337. package/examples/remote/firebase-push-relay/functions/core.test.js +273 -0
  1338. package/examples/remote/firebase-push-relay/functions/index.js +413 -0
  1339. package/examples/remote/firebase-push-relay/functions/package-lock.json +2974 -0
  1340. package/examples/remote/firebase-push-relay/functions/package.json +18 -0
  1341. package/examples/remote/iroh-sidecar/README.md +211 -0
  1342. package/examples/remote/iroh-sidecar/client.mjs +554 -0
  1343. package/examples/remote/iroh-sidecar/common.mjs +195 -0
  1344. package/examples/remote/iroh-sidecar/host.mjs +4 -0
  1345. package/examples/remote/iroh-sidecar/package-lock.json +213 -0
  1346. package/examples/remote/iroh-sidecar/package.json +13 -0
  1347. package/examples/remote/iroh-sidecar/source-aware-run.mjs +41 -0
  1348. package/examples/rpc-extension-ui.ts +632 -0
  1349. package/examples/sdk/01-minimal.ts +26 -0
  1350. package/examples/sdk/02-custom-model.ts +53 -0
  1351. package/examples/sdk/03-custom-prompt.ts +70 -0
  1352. package/examples/sdk/04-skills.ts +55 -0
  1353. package/examples/sdk/05-tools.ts +48 -0
  1354. package/examples/sdk/06-extensions.ts +94 -0
  1355. package/examples/sdk/07-context-files.ts +42 -0
  1356. package/examples/sdk/08-prompt-templates.ts +51 -0
  1357. package/examples/sdk/09-api-keys-and-oauth.ts +52 -0
  1358. package/examples/sdk/10-settings.ts +53 -0
  1359. package/examples/sdk/11-sessions.ts +52 -0
  1360. package/examples/sdk/12-full-control.ts +78 -0
  1361. package/examples/sdk/13-session-runtime.ts +67 -0
  1362. package/examples/sdk/README.md +147 -0
  1363. package/npm-shrinkwrap.json +3442 -0
  1364. package/package.json +125 -19
@@ -0,0 +1,1090 @@
1
+ # RFC: Live Shared Sessions: voltd Daemon, Conversation Leases, and TUI Co-Attach
2
+
3
+ - Status: Accepted (implementation-ready)
4
+ - Workspaces: `Volt/packages/coding-agent` (primary), `volt-app` (iOS deltas, section 10)
5
+ - Supersedes: the host-process model described in `docs/iroh-remote-access-design.md` (that doc gets a superseded banner; see M10)
6
+ - Breaking changes: allowed and taken freely (pre-alpha, zero users) EXCEPT the Pi extension API contract in `src/core/extensions/types.ts` (`ExtensionContext`, `ExtensionAPI`, `ExtensionUIContext`), which MUST remain source-compatible.
7
+
8
+ All file paths in this document are relative to `Volt/packages/coding-agent/` unless prefixed with `volt-app/` or otherwise absolute. Line numbers reference the tree at design time and are anchors, not contracts; re-locate by symbol name when they drift.
9
+
10
+ ---
11
+
12
+ ## 1. Summary, Goals, Non-Goals
13
+
14
+ ### 1.1 Summary
15
+
16
+ Today, remote iOS access requires a foreground `volt remote host` process: a 3731-line standalone ESM script (`src/remote/iroh-host.mjs`) spawned by `main.ts` (`handleRemoteCommand`, L1009-1101, spawn at L1075-1097, stdio inherit). If the user closes their terminal, the host dies, phones disconnect, and the user must remember to restart it. Worse, a conversation opened in the TUI and the same conversation opened from the phone are two *different* runtimes over the same session file, because the host keys its runtime map by `${clientNodeId}\0${workspaceName}\0${sessionId}` (`getIntegratedRuntimeRegistryKey`, iroh-host.mjs L2298) and knows nothing about the TUI.
17
+
18
+ This RFC replaces that model with:
19
+
20
+ 1. **voltd** — a persistent, detached, Node-only daemon packaged inside `@hansjm10/volt-coding-agent`. It owns the stable Iroh node identity, pairing/revocation state, push + Live Activity dispatch, the workspace registry, an audit log, headless integrated runtimes, and a **conversation lease broker**. It is auto-spawned by `volt` startup when `remote.background` is enabled and is managed via `volt daemon start|stop|status|restart|logs|install-service`.
21
+ 2. **Conversation leases** — exactly one owner process per `(workspaceName, sessionId)` holds the live `AgentSessionRuntime` + `ExtensionRunner` + tools. The TUI owns leases for sessions it has open (full Pi extension fidelity, `ctx.mode === "tui"`); the daemon owns leases for headless sessions (`ctx.mode === "rpc"`, documented degradation). The lease key **drops** `clientNodeId`.
22
+ 3. **Byte relay** — when the TUI owns a lease, the daemon authenticates the phone's Iroh conversation stream itself, then relays the framed JSONL bytes over the unix control socket to the TUI, which serves the stream with the existing `runIrohRemoteRpcMode(runtime, { stream: relayedStream, disposeRuntimeOnClose: false, ... })` against its in-process runtime. Phone prompts render live in the TUI because `InteractiveMode` renders user messages from `message_start` events regardless of origin (interactive-mode.ts L3008-3015).
23
+ 4. **Turn-boundary handoff** in both directions, with a draining state and a read-only viewer feed so a user opening the TUI mid-remote-turn watches the turn finish before taking ownership.
24
+ 5. **Abort redesign** — phone abort becomes "stop current turn" without stream closure or runtime disposal, deleting the host's `invalidateStreamAfterAbortResponse` behavior (iroh-host.mjs L1556-1564) and the iOS abort→expect-closure→reopen dance.
25
+ 6. **Theme engine lift** — the module-global theme Proxy singleton (`src/modes/interactive/theme/theme.ts`) becomes an instanced `ThemeService` in `src/core/theme/`, so daemon-owned runtimes can resolve themes without a terminal and the Pi facade (`ctx.ui.theme`, `getAllThemes`, `getTheme`, `setTheme`) is preserved exactly.
26
+
27
+ The net user experience: start a task in the TUI, walk away, continue the same live conversation on the phone, come back, reopen the TUI, and see everything — with no host process to remember, no re-pairing, and no forked histories.
28
+
29
+ ### 1.2 Goals
30
+
31
+ | # | Goal | Verified by |
32
+ |---|------|-------------|
33
+ | G1 | Phone can attach to the SAME live conversation a TUI has open; prompts from either side appear on both. | Integration test §12.3.3; manual script §12.5 |
34
+ | G2 | Remote host survives TUI close/reopen with zero user action (`remote.background` on). | Integration test §12.3.1 |
35
+ | G3 | Paired phones do NOT re-pair after migration (Iroh secret key preserved). | Migration unit test §12.2.4 |
36
+ | G4 | TUI open against a daemon-held session takes ownership at the next turn boundary, watching any in-flight turn read-only. | Integration test §12.3.2 |
37
+ | G5 | TUI quit hands the session back so the phone continues seamlessly (auto-reconnect, lazy daemon resume). | Integration test §12.3.2 |
38
+ | G6 | Pi extensions require zero code changes; handoff is an ordinary `session_shutdown`/`session_start` lifecycle pair. | §8; extension fixture test §12.3.4 |
39
+ | G7 | Phone abort stops the turn without closing the stream. | Unit + iOS VoltClient tests §12.4 |
40
+ | G8 | `iroh-host.mjs` deleted; host logic lives in typed TS under `src/daemon/`. | M3 acceptance criteria |
41
+
42
+ ### 1.3 Non-Goals (explicit, do not implement)
43
+
44
+ - **Mid-turn ownership migration.** Handoff happens only at turn boundaries (idle). A TUI killed mid-turn loses that turn (accepted limitation; TUI warns on exit while `session.isStreaming` and a phone is attached).
45
+ - **TUI tunneling / terminal remoting.** The phone never sees the TUI's rendered frames.
46
+ - **Remoting extension custom TUI components.** `ctx.ui.custom()` etc. remain terminal-only, per existing `docs/rpc.md` degradation.
47
+ - **Durable crash recovery of in-flight turns.** If the owner process dies mid-turn, the turn is gone; the session file reflects the last flush.
48
+ - **Multi-TUI co-attach.** A second TUI requesting a lease held by another TUI gets `lease_denied` reason `held_by_tui` and opens the session read-only (plain resume-from-file, no live view; it may retry acquire on user action).
49
+ - **Windows named pipes.** The daemon is unix-socket only (macOS/Linux). Windows keeps the no-daemon path (all lease calls no-op; behavior identical to today).
50
+ - **Forcible lease steal.** `lease_acquire.force` is reserved in the protocol; any request with `force: true` returns `lease_denied` reason `force_unsupported`.
51
+
52
+ ---
53
+
54
+ ## 2. Architecture Overview
55
+
56
+ ### 2.1 Processes and data planes
57
+
58
+ Three processes participate: **voltd** (persistent), **volt TUI** (per-terminal, transient), and the **iOS app** (via Iroh). Two planes:
59
+
60
+ - **Control plane**: JSONL over the unix socket `~/.volt/agent/daemon/voltd.sock`. Carries hello/leases/pairing/status/viewer feed/theme snapshots/push registration.
61
+ - **Conversation plane**: framed JSONL RPC (existing `docs/rpc.md` protocol) between phone and the runtime owner. When the daemon owns the lease, it terminates the stream locally. When the TUI owns it, the daemon relays raw bytes over a dedicated per-relay unix-socket connection.
62
+
63
+ ```
64
+ Iroh (QUIC, e2e encrypted)
65
+ +------------------+ pairing/auth/handshake +---------------------------------------+
66
+ | iOS app | <=========================> | voltd |
67
+ | (volt-app) | conversation streams | - Iroh node identity (stable key) |
68
+ +------------------+ workspace/device streams | - pairing, revocation, audit |
69
+ | - push relay + Live Activity state |
70
+ | - workspace registry |
71
+ | - LEASE BROKER (wsName,sessionId) |
72
+ | - headless runtimes (ctx.mode=rpc) |
73
+ | - relay dispatcher |
74
+ +-------------------+-------------------+
75
+ |
76
+ unix socket ~/.volt/agent/daemon/voltd.sock
77
+ (control JSONL + per-relay byte-pipe conns)
78
+ |
79
+ +--------------------------------------------------------------------+----------------+
80
+ | volt TUI (InteractiveMode) |
81
+ | - in-process AgentSessionRuntime + ExtensionRunner (ctx.mode = "tui") |
82
+ | - DaemonClient (control-client.ts): lease_acquire/release/rekey, relay accept, |
83
+ | viewer feed during drain, relay_rpc forwarding, theme_set |
84
+ | - per-relay: runIrohRemoteRpcMode(runtime, {stream: relayedStream, |
85
+ | disposeRuntimeOnClose:false, ...}) — one per attached phone |
86
+ +--------------------------------------------------------------------------------------+
87
+
88
+ Lease ownership decides which box terminates the phone's conversation stream:
89
+
90
+ daemon-owned: phone <==Iroh==> voltd[runtime, rpc mode] (headless, ctx.mode=rpc)
91
+ tui-owned: phone <==Iroh==> voltd[handshake+auth, byte relay] <--unix--> TUI[runtime, rpc mode]
92
+ ```
93
+
94
+ ### 2.2 Ownership invariants
95
+
96
+ 1. **One runtime per conversation.** For any `(workspaceName, sessionId)`, at most one process holds a live `AgentSessionRuntime`. All phone streams and the TUI view fan out from that single runtime via `AgentSession.subscribe` (agent-session.ts L744-754; `_emit` L522-526 iterates subscribers).
97
+ 2. **Extensions co-locate with the runtime.** Handoff = dispose old runtime (`session_shutdown`) + fresh load in the new owner (`session_start` from the session file). Never state migration. There is no `session_switched` event in the codebase and none is added.
98
+ 3. **The session file is the source of truth across handoffs.** Both owners flush via the existing session persistence (JSONL tree format, `docs/session-format.md`, dir `join(agentDir, "sessions", "--<cwd-mangled>--")`, `SessionManager` in `src/core/session-manager.ts`).
99
+ 4. **The daemon always terminates Iroh.** The TUI never runs an Iroh node. Auth, handshake parsing, target resolution, and lease lookup happen in the daemon before any bytes reach the TUI.
100
+ 5. **Daemon integration is strictly additive for the TUI.** If the daemon is unreachable, every lease/relay/push-forward call silently no-ops and the TUI behaves exactly as today. `remote.background` controls auto-spawn; supported TUIs may still join a daemon started by another process.
101
+
102
+ ### 2.3 Stream routing decision (daemon, per phone conversation stream)
103
+
104
+ ```
105
+ phone opens conversation stream
106
+ -> engine accepts, handshake read + client auth (existing handshake.ts / authorization.ts)
107
+ -> resolveIrohRemoteSessionTarget(handshake.target) [pure helper, §3.7]
108
+ last | new | session -> concrete sessionId (+ selection kind created/created_after_missing/resumed)
109
+ -> leaseBroker.lookup(workspaceName, sessionId)
110
+ tui-owned -> mint relay token, send relay_offer to owning TUI, pipe bytes (§5.6)
111
+ daemon-active -> attach stream to existing daemon runtime (multi-subscriber)
112
+ daemon-detached -> cancel retention timer, reattach (state -> daemon-active)
113
+ daemon-draining -> attach as viewer of the draining runtime? NO — draining only exists
114
+ while a TUI acquire is pending; phone streams attach to the daemon
115
+ runtime read/write EXCEPT prompt-class commands, which are rejected
116
+ with error code "lease_draining" (§4.5)
117
+ unowned -> lazily resume runtime from session file, lease -> daemon-active
118
+ -> duplicate check: same clientNodeId + same conversation + live stream
119
+ -> duplicate_conversation_connection (retryAfterMs 500), replace-stale-stream semantics
120
+ preserved from iroh-host.mjs L2912-2939 (§4.6)
121
+ ```
122
+
123
+ ---
124
+
125
+ ## 3. voltd Daemon Specification
126
+
127
+ ### 3.1 Process model
128
+
129
+ - **Runtime**: npm package or source checkout only. Use `isStandaloneBinary` from `src/config.ts` to reject the Node SEA distribution, which intentionally omits Iroh; `volt daemon *` in that distribution prints actionable npm/source guidance and exits 1. Native dependency `@number0/iroh` loads via the existing `src/remote/iroh-native-adapter.cjs` mechanism (keep this file; it is the native loader, not host logic).
130
+ - **Entry point**: `src/daemon/main.ts` exporting `runVoltDaemon(config: VoltdConfig): Promise<number>`; a thin bin shim is invoked as `volt daemon run --foreground` (internal) so packaging needs no new binary. The daemon process title is set to `voltd`.
131
+ - **Detached spawn** (`src/daemon/spawn.ts`): `spawn(process.execPath, [cliEntry, "daemon", "run", "--foreground"], { detached: true, stdio: ["ignore", logFd, logFd] })`, `child.unref()`. `logFd` is an append fd to `voltd.log`. The spawner waits up to 5s for the socket to accept a `status` probe before reporting success.
132
+ - **Auto-spawn**: in TUI startup (see §6.2), if setting `remote.background === true` and platform is not Windows and the process is not a standalone SEA, `ensureDaemonRunning()` (spawn.ts) probes the socket; if no healthy daemon, spawns one. Failures are logged at debug level and never block TUI startup.
133
+ - **Single instance**: guaranteed by socket bind. On `EADDRINUSE`: connect and send `status`; if a valid `status_result` arrives within 2s, another daemon is healthy → exit 3 (`already_running`). If connect fails or times out, unlink the socket and retry the bind exactly once; if that fails, exit 4.
134
+ - **Pidfile** `voltd.pid` is advisory (JSON: `{ pid, version, startedAtMs, socketPath }`), written after successful bind, removed on graceful shutdown. Liveness truth is always the socket probe, never the pidfile.
135
+
136
+ ### 3.2 File layout
137
+
138
+ All under `join(getAgentDir(), "daemon")` (`getAgentDir` from `src/config.ts`). Directory mode `0700`, socket mode `0600` (chmod after bind).
139
+
140
+ | Path | Purpose | Format |
141
+ |------|---------|--------|
142
+ | `daemon/voltd.sock` | control socket | unix stream, JSONL (§5) |
143
+ | `daemon/voltd.pid` | advisory pidfile | JSON `{pid, version, startedAtMs, socketPath}` |
144
+ | `daemon/voltd.log` | daemon log | text; rotate at 10 MiB, keep exactly 1 rotated file (`voltd.log.1`) |
145
+ | `daemon/state.json` | persistent state | `VoltdStateFileV1` (§3.3), atomic write (tmp + rename) |
146
+ | `daemon/audit.jsonl` | audit log | JSONL, reuse `src/core/remote/iroh/audit.ts` writer pointed at this path |
147
+
148
+ ### 3.3 State schema
149
+
150
+ `src/daemon/state.ts`:
151
+
152
+ ```ts
153
+ export interface VoltdStateFileV1 {
154
+ version: 1;
155
+ /** Hex/base32 Iroh secret key. MUST survive migration so phones stay paired. */
156
+ irohSecretKey: string;
157
+ /** Paired clients, same element shape as today's remote/iroh-host.json `clients`. */
158
+ clients: RemotePairedClient[]; // reuse type from src/core/remote/iroh/state.ts
159
+ revokedClients: RemoteRevokedClient[]; // reuse type from src/core/remote/iroh/state.ts
160
+ /** Registered workspaces: name -> absolute path (+ registration metadata). */
161
+ workspaces: RemoteWorkspaceRecord[]; // reuse/extend type from src/core/remote/iroh/state.ts
162
+ /** Push targets keyed by clientNodeId; shape from src/core/remote/iroh/push.ts. */
163
+ pushTargets: Record<string, RemotePushTargetRecord>;
164
+ /** Live Activity channels keyed by clientNodeId. */
165
+ liveActivityChannels: Record<string, RemoteLiveActivityChannelRecord>;
166
+ settings: {
167
+ /** Detached headless runtime retention. Default DEFAULT_INTEGRATED_DETACHED_RUNTIME_TTL_MS
168
+ * (30 min, src/remote/integrated-runtime-retention.ts L1). */
169
+ detachedRuntimeTtlMs: number;
170
+ /** Tool allowlist applied ONLY to daemon-owned headless runtimes (§11.2). */
171
+ allowTools: string[] | null;
172
+ };
173
+ }
174
+ ```
175
+
176
+ Where a named type does not yet exist in `src/core/remote/iroh/state.ts`, define it there (typed) and make `state-manager.ts` use it; the daemon must not carry untyped state.
177
+
178
+ **Persistence**: `src/daemon/state.ts` wraps the existing `state-manager.ts` logic with the new path and envelope. Writes are debounced (250ms) and always atomic. State is flushed synchronously (best-effort) during graceful shutdown.
179
+
180
+ ### 3.4 One-time migration from `remote/iroh-host.json`
181
+
182
+ `src/daemon/state.ts` exports `migrateLegacyRemoteState(agentDir: string): VoltdStateFileV1 | null`:
183
+
184
+ 1. Runs on daemon startup only when `daemon/state.json` does not exist and `join(agentDir, "remote", "iroh-host.json")` (the default path used by main.ts: `join(getAgentDir(), "remote", "iroh-host.json")`) does.
185
+ 2. Maps legacy fields → `VoltdStateFileV1`: secret key (**verbatim** — this is what preserves pairing), clients, revokedClients, workspaces, push targets, live-activity channels. Unknown legacy fields are dropped. Missing sections default to empty.
186
+ 3. Writes `daemon/state.json`, then renames the legacy file to `remote/iroh-host.json.migrated` (never deleted).
187
+ 4. Emits audit event `daemon_started` with `details.migratedFromLegacyState: true`.
188
+ 5. Idempotent: if `.migrated` exists and `state.json` exists, nothing happens.
189
+
190
+ **Acceptance**: after migration, a previously paired phone's `clientNodeId` authenticates against voltd without re-pairing; `volt remote status` lists the same clients.
191
+
192
+ ### 3.5 Daemon internals (module map for `src/daemon/`)
193
+
194
+ | File | Responsibility |
195
+ |------|----------------|
196
+ | `main.ts` | `runVoltDaemon`: wire Iroh engine (`src/core/remote/iroh/engine.ts` `IrohRemoteHostEngine`) + control server + lease broker + state + audit + shutdown; signal handlers (SIGTERM/SIGINT → graceful shutdown §3.9). |
197
+ | `cli.ts` | `volt daemon` subcommand implementations (start/stop/status/restart/logs/install-service/run); invoked from main.ts command router. |
198
+ | `spawn.ts` | detached spawn, socket health probe, `ensureDaemonRunning()`, stale-socket recovery, version-skew handling (§3.8). |
199
+ | `state.ts` | `VoltdStateFileV1`, load/save/migrate. |
200
+ | `control-protocol.ts` | ALL control-plane TS types (§5.3-5.5), frame encode/decode, 8 MiB line cap. Shared by daemon and TUI. Zero runtime deps beyond node:buffer. |
201
+ | `control-server.ts` | unix socket listener; hello/hello_ack; request dispatch; unsolicited event fan-out; per-connection lease bookkeeping (implicit release on disconnect); relay-socket admission (role:"relay" hellos, token check). |
202
+ | `control-client.ts` | `DaemonClient` used by TUI and CLI: connect/reconnect backoff (250ms→5s, jittered), request/response correlation, event subscription, relay socket dialing. |
203
+ | `lease-broker.ts` | pure-ish state machine (§4): Map keyed `${workspaceName}\0${sessionId}`, transitions, draining orchestration hooks, timers injected for tests. |
204
+ | `integrated-runtimes.ts` | daemon-owned runtime lifecycle: create via `createIrohRemoteAgentRuntimeWithSessionSelection` (src/modes/rpc/iroh-remote-agent-runtime.ts L85), attach/detach subscribers (port iroh-host.mjs L2624-2682), stop entry (port `stopIntegratedRuntimeEntry` L2684-2723), retention (port L2741-2774 using `src/remote/integrated-runtime-retention.ts`), workflow event replay, `onSessionChanged` rekey (port `handleIntegratedRuntimeSessionChanged` L2168-2179 → calls `leaseBroker.rekey`). |
205
+ | `relay-stream.ts` | relay lifecycle daemon-side: token mint/expiry (10s, single-use), preamble write, bidirectional raw byte pump Iroh stream ↔ relay unix connection, half-close + error propagation, `relay_opened`/`relay_closed` audit. |
206
+ | `session-target.ts` | `resolveIrohRemoteSessionTarget` (extracted pure helper, §3.7). |
207
+ | `conversation-commands.ts` | port of integrated-conversation host RPC command handling (`handleIntegratedConversationRpcCommand`), including `INTEGRATED_CONVERSATION_UNSUPPORTED_RPC_TYPES = {new_session, switch_session_by_id, get_messages}` rejection, and the NEW abort semantics (§7.4). |
208
+ | `handshake-responses.ts` | `createIntegratedConversationHandshakeResponse` ported from iroh-host.mjs into a typed module **shared by the daemon path and the TUI relay path** (the TUI writes the handshake success response itself, §5.6 step 7). |
209
+ | `workspace-streams.ts` | workspace discovery/management + device-log streams, porting the remaining serve() surface (iroh-host.mjs L3458-3639) onto `workspace-rpc.ts` / `device-log-rpc.ts`. |
210
+
211
+ Reused as-is from `src/core/remote/iroh/`: `engine.ts`, `handshake.ts`, `handshake-reader.ts`, `protocol.ts` (feature strings `multi_streams.v1`, `conversation_streams.v1`, plus new additive `conversation_leases.v1`), `rpc-command-filter.ts`, `outbound-filter.ts`, `active-stream-registry.ts`, `state-manager.ts`, `push.ts`, `ticket.ts`, `audit.ts`, `qr.ts`, `host-policy.ts`, `workspace-rpc.ts`, `device-log-rpc.ts`, `authorization.ts`, `metadata.ts`.
212
+
213
+ ### 3.6 CLI surface
214
+
215
+ `main.ts` command router changes:
216
+
217
+ | Command | Behavior |
218
+ |---------|----------|
219
+ | `volt daemon start` | `ensureDaemonRunning()`; prints socket path + pid; exit 0 if already running. |
220
+ | `volt daemon stop` | control `shutdown` request; waits up to 75s (60s drain cap + margin); falls back to SIGTERM via pidfile if socket dead; exit 0 on stop, 1 on timeout. |
221
+ | `volt daemon status` | control `status` request; prints version, protocolVersion, pid, uptime, lease table, connected phones, relay count, workspace count. `--json` for machine output. Exit 0 running / 1 not running. |
222
+ | `volt daemon restart` | stop + start; preserves state (state.json survives). |
223
+ | `volt daemon logs [-f] [-n N]` | tail `voltd.log` (default 200 lines); `-f` follows. |
224
+ | `volt daemon install-service` | OPTIONAL milestone M9: writes launchd plist (`~/Library/LaunchAgents/com.github.hansjm10.voltd.plist`) or systemd user unit (`~/.config/systemd/user/voltd.service`) that runs `volt daemon run --foreground`; prints load/enable instructions. Does not load/enable itself. |
225
+ | `volt daemon run --foreground` | internal: runs the daemon in-process (used by spawn + service units + tests). |
226
+ | `volt remote host` | **REMOVED.** Prints: `"volt remote host" has been replaced by the background daemon. Run "volt daemon start" (or enable remote.background). See docs/daemon.md.` Exit 1. Delete the spawn path (main.ts L1075-1097) and the `remoteHost*` plumbing. |
227
+ | `volt remote pair` | Now a control-socket client: sends `pair_request`, renders ticket + QR (reuse `qr.ts` rendering client-side from `pairing_progress` payloads), streams `pairing_progress` until `completed`/`failed`/Ctrl-C (which sends cancel by closing the request scope). Replaces `startPairControlServer` (iroh-host.mjs L3357). Requires a running daemon; auto-starts it when `remote.background` is on, else instructs `volt daemon start`. |
228
+ | `volt remote status` | Real implementation via control `status` (replaces the placeholder at main.ts L237-238). |
229
+ | `volt remote clients` / `volt remote revoke <id>` | via `clients_list` / `client_revoke`. |
230
+ | `volt remote workspace add/remove/list` | via `workspace_register` / `workspace_unregister` / `status`. |
231
+
232
+ All `volt remote *` and `volt daemon *` reject the standalone SEA with actionable npm/source guidance.
233
+
234
+ ### 3.7 Session target resolution (shared helper)
235
+
236
+ Extract from `createIrohRemoteAgentRuntimeWithSessionSelection` (src/modes/rpc/iroh-remote-agent-runtime.ts L85) a pure function in `src/daemon/session-target.ts`:
237
+
238
+ ```ts
239
+ export type IrohRemoteSessionTarget =
240
+ | { kind: "last" }
241
+ | { kind: "new" }
242
+ | { kind: "session"; sessionId: string };
243
+
244
+ export interface ResolvedSessionTarget {
245
+ sessionId: string; // concrete id (existing file id, or freshly created)
246
+ sessionFilePath: string;
247
+ selection: "created" | "created_after_missing" | "resumed";
248
+ workspaceName: string;
249
+ workspacePath: string;
250
+ }
251
+
252
+ export function resolveIrohRemoteSessionTarget(
253
+ target: IrohRemoteSessionTarget,
254
+ workspace: { name: string; path: string },
255
+ sessions: SessionManagerLike, // list/open/create — injectable for tests
256
+ ): Promise<ResolvedSessionTarget>;
257
+ ```
258
+
259
+ Rules (must exactly match today's behavior in iroh-remote-agent-runtime.ts, including `created_after_missing` when `kind:"session"` names a missing id, and host-synthesized `session_rekeyed` semantics for the client-side selection validation):
260
+
261
+ - `last`: newest session for the workspace path, else create → `created`.
262
+ - `new`: always create → `created`.
263
+ - `session`: open if exists → `resumed`; else create fresh → `created_after_missing`.
264
+
265
+ The daemon calls this **before** lease lookup (routing needs the concrete sessionId). `createIrohRemoteAgentRuntimeWithSessionSelection` is refactored to accept a pre-resolved `ResolvedSessionTarget` (new overload/param) so resolution is not run twice; its existing signature keeps working by resolving internally via the same helper.
266
+
267
+ ### 3.8 Version skew
268
+
269
+ - `status_result` carries `{ version, protocolVersion }` (package version from package.json; `PROTOCOL_VERSION = 1` from control-protocol.ts).
270
+ - Every CLI/TUI connect compares. On mismatch:
271
+ - If the daemon is **idle** (zero daemon-owned leases in `daemon-active`/`daemon-draining`, zero live phone conversation streams, zero relays): the client triggers an automatic in-place restart (`shutdown` + `ensureDaemonRunning()`) and logs one info line.
272
+ - Else: warn once per process (`voltd version X != client Y; restart when idle with "volt daemon restart"`), continue on protocol 1. Any future protocolVersion bump makes mismatched hellos hard-fail with `hello_ack.error = "protocol_mismatch"`.
273
+
274
+ ### 3.9 Graceful shutdown sequence
275
+
276
+ On `shutdown` request or SIGTERM/SIGINT:
277
+
278
+ 1. Stop accepting new phone streams and new control connections; reject with close reason `host_shutdown` / hello error `shutting_down`.
279
+ 2. For each daemon-owned runtime that `session.isStreaming`: `await session.waitForIdle()` with a **60s cap per runtime** (run concurrently). **Never call `session.abort()`** — a hard cap simply proceeds to disposal (the in-flight turn is lost only in the pathological case; acceptable per non-goals).
280
+ 3. Flush + dispose each daemon runtime (extension `session_shutdown` reason `"quit"` via the normal dispose path; `AgentSession.dispose()` invalidates extension ctx, agent-session.ts L781-800).
281
+ 4. Close all phone conversation/workspace streams with reason `host_shutdown`.
282
+ 5. Send `daemon_shutdown` event to all control connections. TUIs keep their in-process leases and runtimes untouched; they mark relay availability off (footer indicator clears) and begin reconnect backoff.
283
+ 6. Flush `state.json`, write audit `daemon_shutdown`, unlink socket, remove pidfile, exit 0.
284
+
285
+ ### 3.10 Audit log
286
+
287
+ Reuse `src/core/remote/iroh/audit.ts` (writer + event envelope) targeting `daemon/audit.jsonl`. Preserve ALL existing event types emitted by iroh-host.mjs (pairing, auth success/failure, session_created, stream open/close, revocation, push dispatch, etc. — enumerate from grep of `auditLog`/`type:` in iroh-host.mjs during port and carry every one). Add:
288
+
289
+ | New event | details |
290
+ |-----------|---------|
291
+ | `daemon_started` | `{ version, migratedFromLegacyState }` |
292
+ | `daemon_shutdown` | `{ reason: "cli" \| "signal", drainedRuntimes, cappedRuntimes }` |
293
+ | `lease_acquired` | `{ workspaceName, sessionId, owner: "tui" \| "daemon", handoff: "cold" \| "warm" \| "none", connectionId? }` |
294
+ | `lease_released` | `{ workspaceName, sessionId, owner, reason: "quit" \| "switch" \| "connection_lost" \| "rekey" \| "shutdown" }` |
295
+ | `lease_denied` | `{ workspaceName, sessionId, requester, reason }` |
296
+ | `relay_opened` | `{ relayId, clientNodeId, workspaceName, sessionId, connectionId, streamId }` |
297
+ | `relay_closed` | `{ relayId, reason, bytesUp, bytesDown, durationMs }` |
298
+
299
+ ---
300
+
301
+ ## 4. Lease Broker
302
+
303
+ `src/daemon/lease-broker.ts`. Key: `${workspaceName}\0${sessionId}` — **`clientNodeId` is dropped** from today's key (iroh-host.mjs L2298). The broker is the single authority on ownership; runtimes and relays are effects driven by its transitions.
304
+
305
+ ### 4.1 Types
306
+
307
+ ```ts
308
+ export type LeaseState =
309
+ | "unowned"
310
+ | "daemon-active" // daemon runtime live, >=1 phone stream attached
311
+ | "daemon-detached" // daemon runtime live, 0 streams, retention TTL ticking
312
+ | "daemon-draining" // TUI acquire pending; finishing current turn before release
313
+ | "tui-owned"; // TUI holds runtime; daemon relays phone streams
314
+
315
+ export interface LeaseRecord {
316
+ key: string; // `${workspaceName}\0${sessionId}`
317
+ workspaceName: string;
318
+ sessionId: string;
319
+ state: LeaseState;
320
+ /** control connectionId of owning TUI when tui-owned / target of daemon-draining */
321
+ tuiConnectionId?: string;
322
+ /** live daemon runtime entry when daemon-* */
323
+ runtimeEntryId?: string;
324
+ /** active relayIds when tui-owned */
325
+ relayIds: Set<string>;
326
+ /** retention timer handle when daemon-detached */
327
+ retentionTimer?: TimerHandle;
328
+ /** drain bookkeeping when daemon-draining */
329
+ drain?: { viewerFeedId: string; requestId: string; startedAtMs: number };
330
+ }
331
+
332
+ export type LeaseDenyReason =
333
+ | "held_by_tui" // another TUI connection owns it (multi-TUI non-goal)
334
+ | "force_unsupported" // force flag is reserved
335
+ | "draining_elsewhere";// a different TUI's drain is already pending
336
+
337
+ export interface LeaseBroker {
338
+ lookup(workspaceName: string, sessionId: string): LeaseRecord | undefined;
339
+ /** TUI acquire; may resolve granted immediately, or pending->granted after drain. */
340
+ acquireForTui(req: {
341
+ connectionId: string; workspaceName: string; sessionId: string; force?: boolean;
342
+ }): Promise<
343
+ | { kind: "granted"; handoff: "cold" | "warm" | "none" }
344
+ | { kind: "pending"; viewerFeedId: string; granted: Promise<{ handoff: "warm" }> }
345
+ | { kind: "denied"; reason: LeaseDenyReason }
346
+ >;
347
+ releaseFromTui(connectionId: string, workspaceName: string, sessionId: string): void;
348
+ rekey(workspaceName: string, oldSessionId: string, newSessionId: string): void;
349
+ /** connection died: implicit release of ALL leases held by connectionId */
350
+ releaseAllForConnection(connectionId: string): void;
351
+ /** daemon runtime lifecycle notifications */
352
+ onDaemonRuntimeAttached(workspaceName: string, sessionId: string, entryId: string): void;
353
+ onDaemonRuntimeStreamCountChanged(key: string, liveStreams: number): void;
354
+ onDaemonRuntimeDisposed(key: string): void;
355
+ }
356
+ ```
357
+
358
+ ### 4.2 State machine
359
+
360
+ | From \ Event | tui acquire | tui release / conn lost | phone stream attach | last phone stream detach | retention TTL fires | daemon runtime turn idle (drain) | rekey |
361
+ |---|---|---|---|---|---|---|---|
362
+ | **unowned** | → `tui-owned`, `lease_granted{handoff:"none"}` | n/a | daemon lazily resumes runtime from session file → `daemon-active` | n/a | n/a | n/a | n/a |
363
+ | **daemon-active** | if `session.isStreaming`: → `daemon-draining` + `lease_pending{viewerFeedId}`; else immediate: dispose runtime (shutdown reason `"quit"`), → `tui-owned`, `lease_granted{handoff:"warm"}`, phone streams closed reason `lease_transferred` then re-routed as relays on reconnect | n/a | attach additional subscriber (multi-device) | → `daemon-detached`, start retention timer | n/a | n/a | rekey key, keep state |
364
+ | **daemon-detached** | cancel timer; dispose runtime + immediate grant `handoff:"warm"`. A turn still streaming on a detached runtime is intentionally abandoned, not drained (see note below) | n/a | cancel timer → `daemon-active` | n/a | dispose runtime (shutdown reason `"quit"`) → `unowned` | n/a | rekey key, keep state |
365
+ | **daemon-draining** | same TUI conn: idempotent (same pending). Different TUI conn: `lease_denied{draining_elsewhere}` | pending TUI conn lost: cancel drain → `daemon-active` (or `daemon-detached` if 0 streams); viewer feed ends `viewer_end{reason:"cancelled"}` | attach subscriber; prompt-class commands rejected `lease_draining` (§4.5) | stream count may hit 0; stay `daemon-draining` (drain completes regardless) | timer not armed in this state | flush + dispose runtime (shutdown `"quit"`), close phone streams `lease_transferred`, → `tui-owned`, resolve `lease_granted{handoff:"warm"}`, `viewer_end{reason:"granted"}` | rekey key + notify pending TUI via granted payload sessionId |
366
+ | **tui-owned** | same conn: idempotent ok. Other conn: `lease_denied{held_by_tui}` | flush (TUI-side) + release → `unowned`; all relays closed reason `lease_transferred`; phones auto-reconnect and lazily resume (§4.4) | daemon mints relay to owning TUI (§5.6) | (relay closed; no state change) | n/a | n/a | TUI sends `lease_rekey`; broker rekeys; open relays for the old key are closed reason `session_rekeyed_reconnect` (phone revalidates via its existing `session_rekeyed` selection logic) |
367
+
368
+ `handoff` semantics in `lease_granted`:
369
+ - `"none"`: nothing was running in the daemon; TUI proceeds without any reload (its normal open path).
370
+ - `"warm"`: the daemon had a runtime and disposed it; the TUI MUST (re)load the session from file before serving (its normal resume path already does this on open; on an already-open TUI reacquiring after reconnect, it triggers `session.reload()` — agent-session.ts L2657-2681 — if the daemon reports it owned a runtime in the gap).
371
+ - `"cold"`: reserved for future use (grant where daemon never had state but the session file changed externally); implement as an alias of `"warm"` on the TUI side.
372
+
373
+ **Drain is `daemon-active`-only; detached turns are abandonable (decided).** The
374
+ `session.isStreaming` drain gate fires only for `daemon-active` — i.e. a phone is
375
+ attached and can watch the turn finish. A `daemon-detached` runtime whose turn is
376
+ still running (the last phone left mid-turn; the turn continued on the host per
377
+ §Lifecycle) is disposed immediately on TUI acquire, killing that in-flight turn
378
+ rather than draining it. This is intentional, not an oversight: the drain exists
379
+ to hand a *watched* turn off gracefully, so once no device is receiving the turn
380
+ (no attached phone, and the desktop is only now acquiring) there is nothing to
381
+ watch and the turn is abandonable — the same way closing a TUI mid-turn loses that
382
+ turn (§1.3 non-goal). The tradeoff is a mild asymmetry: whether a walked-away turn
383
+ survives opening the desktop depends on whether a phone was still attached. We
384
+ accept it; draining an unwatched detached runtime would add a viewer feed with no
385
+ viewer for marginal benefit. (Formalized as the `IdleAcquireOnlyWhenIdle`
386
+ predicate in `docs/tla/LeaseBroker.tla`, which intentionally does NOT hold.)
387
+
388
+ ### 4.3 Draining protocol detail (daemon side)
389
+
390
+ On `acquireForTui` finding `daemon-active` + `session.isStreaming`:
391
+
392
+ 1. Transition → `daemon-draining`; create `viewerFeedId`; reply `lease_pending{viewerFeedId}`.
393
+ 2. From this instant, subscribe to the runtime's `AgentSession` and forward every `AgentSessionEvent` as a `viewer_event` control message to the acquiring TUI connection. Buffer events emitted between drain start and the TUI's `viewer_subscribe` (cap 2000 events / 4 MiB; if exceeded, drop the buffer and send `viewer_event{kind:"truncated"}` first — the TUI shows a spinner instead of partial transcript, then relies on the post-grant file load for truth).
394
+ 3. Reject phone prompt-class commands with error `lease_draining` (§4.5) so the drain converges (no new turns can start).
395
+ 4. `await session.waitForIdle()` (no cap here — the turn ends when it ends; abort is available to the user from either surface and is now non-destructive §7.4).
396
+ 5. Flush; dispose daemon runtime (`session_shutdown` reason `"quit"`); close phone streams reason `lease_transferred`; `viewer_end{reason:"granted"}`; transition → `tui-owned`; resolve the pending acquire with `lease_granted{handoff:"warm"}`.
397
+
398
+ ### 4.4 Lazy daemon resume (tui-owned → unowned → daemon-active)
399
+
400
+ On TUI release, the daemon does NOT speculatively build a runtime. The lease goes `unowned`. Phones whose relays closed with `lease_transferred` auto-reconnect (iOS delta D9); the next conversation-stream arrival for that key finds `unowned` and resumes from the session file via `integrated-runtimes.ts` (which uses `createIrohRemoteAgentRuntimeWithSessionSelection` with the pre-resolved target from §3.7). This keeps release O(1) and avoids warm runtimes nobody uses.
401
+
402
+ ### 4.5 Prompt-class command rejection during draining
403
+
404
+ `conversation-commands.ts` classifies inbound RPC commands. During `daemon-draining`, commands that would start or extend a turn — `prompt` (any `streamingBehavior`), and any command documented in docs/rpc.md as turn-initiating — receive:
405
+
406
+ ```json
407
+ { "type": "response", "command": "prompt", "success": false,
408
+ "error": { "code": "lease_draining", "message": "Handing off to the desktop TUI; retry shortly.", "retryAfterMs": 1000 } }
409
+ ```
410
+
411
+ Read-only commands (`get_state`, `get_transcript`, `get_pending_host_actions`, `set_client_capabilities`, abort, etc.) pass through. The iOS app treats `lease_draining` like a transient failure with retry (iOS delta D10).
412
+
413
+ ### 4.6 `conversation_in_use` policy exception and duplicate derivation
414
+
415
+ - `conversation_in_use` is retired as a general ownership rejection: all paired devices belong to the same human, and distinct clients normally co-attach. A security exception applies when an existing daemon runtime permits tools outside the attaching client's persisted grant; that client receives `conversation_in_use` rather than being allowed to drive the broader runtime. TUI-owned conversations retain the explicit full-local-tool policy.
416
+ - `duplicate_conversation_connection` (SAME clientNodeId, same `(workspaceName, sessionId)`, existing live stream) keeps today's semantics ported from iroh-host.mjs L2912-2939: replace-stale-stream (if the existing stream is dead, silently replace), else reject with `retryAfterMs: 500`. Applies identically to daemon-owned attaches and relay minting (the daemon tracks live relays per clientNodeId+key). The iOS retry loop (max 5 attempts, backoff+jitter, retryAfterMs clamp 500-5000; `volt-app` `VoltHostSessionManager+AgentSelection.swift`) continues to work unchanged.
417
+
418
+ ### 4.7 Rekey
419
+
420
+ Session id changes originate from the runtime owner:
421
+
422
+ - Daemon-owned: `onSessionChanged` (the `runIrohRemoteRpcMode` option, see call site iroh-host.mjs L1587-1611) → port `handleIntegratedRuntimeSessionChanged` (L2168-2179) → `leaseBroker.rekey(ws, oldId, newId)` + host-synthesized `session_rekeyed` notice to attached phones (existing behavior preserved).
423
+ - TUI-owned: TUI sends `lease_rekey` (§6.4). The broker rekeys, closes open relays for the old key with reason `session_rekeyed_reconnect`; phones re-run target/selection validation on reconnect (existing `IrohProtocol.selectionIsValid` / `validatedRequestedSessionId` logic).
424
+
425
+ ### 4.8 Race conditions and ordering rules (normative)
426
+
427
+ The broker runs single-threaded on the daemon event loop; every transition is a synchronous state mutation followed by async effects. The following interleavings MUST be handled exactly as specified:
428
+
429
+ | Race | Rule |
430
+ |------|------|
431
+ | Phone stream arrives while `acquireForTui` is transitioning `daemon-active` → `tui-owned` (immediate grant path) | Transition is atomic on the loop: the routing decision (§2.3) reads the post-transition state. If the runtime disposal effect is still in flight, the relay offer is queued until `lease_granted` has been sent; the phone stream waits (handshake response deferred) up to 10s, then transient handshake error → phone retries. |
432
+ | TUI control connection drops between `lease_pending` and drain completion | `releaseAllForConnection` cancels the drain (§4.2 daemon-draining row): viewer buffer discarded, `viewer_end` unsendable (connection gone), state reverts to `daemon-active`/`daemon-detached`. The pending acquire promise rejects internally; nothing is sent. |
433
+ | Two phones (distinct clientNodeIds) race to attach to an `unowned` key | First arrival triggers lazy resume and moves state to `daemon-active`; second arrival observes `daemon-active` (or an in-flight resume — the broker records `resuming` as a sub-flag of `daemon-active` with a pending-runtime promise both attaches await). Resume failure rejects both attaches with a handshake error and reverts to `unowned`. |
434
+ | `rekey` arrives while a `relay_offer` for the old key is outstanding (token unredeemed) | The offer's token is invalidated (mark used); daemon closes the phone stream with a transient error; phone retries and re-resolves the target (its `session` target now takes the `session_rekeyed`/`created_after_missing` path per existing selection validation). |
435
+ | Retention timer fires concurrently with a phone attach | Timer callback checks state first: if not `daemon-detached`, it is a no-op. Attach cancels the timer before transitioning; a fired-but-not-yet-run callback is guarded by the state check. |
436
+ | TUI sends `lease_release` for a key it does not hold | `error{code:"not_held"}`; broker state untouched. Never treat as fatal. |
437
+ | Daemon shutdown begins during `daemon-draining` | Shutdown's per-runtime `waitForIdle` (60s cap) subsumes the drain; on idle, the drain completes normally FIRST (grant sent) if the requester connection is still up, then shutdown proceeds; the freshly `tui-owned` lease survives shutdown (TUIs keep in-process runtimes, §3.9 step 5). If the cap fires, drain is cancelled (`viewer_end{reason:"error"}`, response `error{drain_failed}`) and disposal proceeds. |
438
+ | Duplicate relay offer: same clientNodeId reconnects while its previous relay's token is unredeemed | Invalidate the old token, emit `relay_closed{reason:"error"}` for the never-opened relayId, mint a fresh offer. |
439
+
440
+ ### 4.9 Subagents
441
+
442
+ Subagent runtimes (docs/subagents-design.md; `src/core/tools/subagent*`) are **children of the parent lease** and never acquire their own leases. The broker has no knowledge of them; they live and die inside the owner process with the parent runtime. A handoff disposes them with the parent (`session_shutdown` cascade) — in-flight subagent work is lost with the parent turn per the drain rule (drain waits for the parent turn, which by definition includes awaited subagent work).
443
+
444
+ ### 4.10 Retention interplay
445
+
446
+ `daemon-detached` reuses `src/remote/integrated-runtime-retention.ts` (`DEFAULT_INTEGRATED_DETACHED_RUNTIME_TTL_MS = 30 * 60 * 1000`, override via `settings.detachedRuntimeTtlMs`). Timer arms on last stream detach, cancels on any attach or TUI acquire. TTL fire disposes the runtime (audit `lease_released{reason:"connection_lost"}`? — no: use dedicated `details.reason:"retention_expired"` on the existing runtime-stop audit event, and `lease_released` with reason `"quit"` is NOT emitted; instead emit `lease_released{owner:"daemon", reason:"retention_expired"}` — add this reason to the union in §3.10's table).
447
+
448
+ ---
449
+
450
+ ## 5. Unix Socket Control Protocol
451
+
452
+ `src/daemon/control-protocol.ts` — single module defining every wire type; imported by daemon, TUI, and CLI. No `any`.
453
+
454
+ ### 5.1 Framing
455
+
456
+ - Transport: `net` unix stream socket at `~/.volt/agent/daemon/voltd.sock`.
457
+ - Framing: JSONL — one JSON object per `\n`-terminated line, UTF-8. Hard cap **8 MiB per line**; a longer line closes the connection with a final `{"type":"fatal","error":"frame_too_large"}` best-effort write.
458
+ - Two connection roles established by the first line (`hello`): `control` (long-lived, multiplexed requests/events) and `relay` (single-purpose byte pipe after a one-line preamble, §5.6).
459
+
460
+ ### 5.2 Hello
461
+
462
+ ```ts
463
+ export const PROTOCOL_VERSION = 1;
464
+
465
+ export type HelloMessage =
466
+ | { type: "hello"; role: "control"; protocolVersion: number; pid: number;
467
+ version: string; client: "tui" | "cli" }
468
+ | { type: "hello"; role: "relay"; protocolVersion: number;
469
+ relayId: string; relayToken: string };
470
+
471
+ export interface HelloAck {
472
+ type: "hello_ack";
473
+ ok: boolean;
474
+ error?: "protocol_mismatch" | "shutting_down" | "bad_relay_token";
475
+ connectionId?: string; // daemon-assigned, present when ok (control role)
476
+ version?: string; // daemon package version
477
+ protocolVersion?: number;
478
+ }
479
+ ```
480
+
481
+ For `role:"relay"`, a successful `hello_ack` is followed immediately by exactly one `relay_preamble` line (§5.6), after which the connection is a raw byte pipe (no further JSONL parsing by the daemon).
482
+
483
+ ### 5.3 Requests and responses (control role)
484
+
485
+ Every request carries a client-generated `id`; every response echoes it. Unsolicited events have no `id`.
486
+
487
+ ```ts
488
+ export type ControlRequest =
489
+ | { type: "status"; id: string }
490
+ | { type: "shutdown"; id: string }
491
+ | { type: "lease_acquire"; id: string; workspaceName: string; sessionId: string;
492
+ /** reserved; true => lease_denied{force_unsupported} */ force?: boolean }
493
+ | { type: "lease_release"; id: string; workspaceName: string; sessionId: string }
494
+ | { type: "lease_rekey"; id: string; workspaceName: string;
495
+ oldSessionId: string; newSessionId: string }
496
+ | { type: "pair_request"; id: string } // events: pairing_progress
497
+ | { type: "clients_list"; id: string }
498
+ | { type: "client_revoke"; id: string; clientNodeId: string }
499
+ | { type: "workspace_register"; id: string; name: string; path: string }
500
+ | { type: "workspace_unregister"; id: string; name: string }
501
+ | { type: "theme_set"; id: string; theme: string } // name; daemon resolves + broadcasts
502
+ | { type: "viewer_subscribe"; id: string; viewerFeedId: string }
503
+ | { type: "viewer_unsubscribe"; id: string; viewerFeedId: string }
504
+ | { type: "relay_rpc"; id: string; clientNodeId: string; workspaceName: string;
505
+ sessionId: string;
506
+ /** verbatim state-touching RPC command (register_push_target,
507
+ * register/unregister_live_activity, unregister_workspace) forwarded from a
508
+ * TUI-owned conversation (§6.6); the daemon replies relay_rpc_result with the
509
+ * verbatim RPC response for the phone */
510
+ command: Record<string, unknown> & { type: string } };
511
+
512
+ export type ControlResponse =
513
+ | { type: "ok"; id: string }
514
+ | { type: "error"; id: string; code: string; message: string }
515
+ | { type: "lease_granted"; id: string; workspaceName: string; sessionId: string;
516
+ handoff: "cold" | "warm" | "none" }
517
+ | { type: "lease_pending"; id: string; viewerFeedId: string }
518
+ // followed later (same id) by lease_granted or error
519
+ | { type: "lease_denied"; id: string;
520
+ reason: "held_by_tui" | "force_unsupported" | "draining_elsewhere" }
521
+ | { type: "status_result"; id: string; version: string; protocolVersion: number;
522
+ pid: number; startedAtMs: number;
523
+ leases: Array<{ workspaceName: string; sessionId: string; state: LeaseState;
524
+ relayCount: number; streamCount: number }>;
525
+ phoneConnections: number; workspaces: Array<{ name: string; path: string }>;
526
+ clients: Array<{ clientNodeId: string; label?: string; pairedAtMs: number }> };
527
+ ```
528
+
529
+ Note: `lease_pending` is a *provisional* response; the terminal response for the same `id` arrives when the drain completes (`lease_granted`) or fails (`error` with code `drain_cancelled` if the requester disconnects — moot — or `drain_failed` on runtime error, in which case the lease reverts to `daemon-active`).
530
+
531
+ ### 5.4 Unsolicited events (daemon → control clients)
532
+
533
+ ```ts
534
+ export type ControlEvent =
535
+ | { type: "relay_offer"; relayId: string; relayToken: string; // single-use, 10s expiry
536
+ workspaceName: string; sessionId: string;
537
+ clientNodeId: string; connectionId: string; streamId: string }
538
+ | { type: "relay_closed"; relayId: string;
539
+ reason: "phone_disconnected" | "lease_transferred" | "session_rekeyed_reconnect"
540
+ | "host_shutdown" | "error" }
541
+ | { type: "viewer_event"; viewerFeedId: string; seq: number;
542
+ event: unknown /* AgentSessionEvent JSON, union agent-session.ts L138-163 */ }
543
+ | { type: "viewer_end"; viewerFeedId: string; reason: "granted" | "cancelled" | "error" }
544
+ | { type: "theme_snapshot"; themeName: string; tokens: Record<string, string> } // §9.5
545
+ | { type: "pairing_progress"; requestId: string;
546
+ phase: "ticket" | "qr" | "waiting" | "completed" | "failed";
547
+ ticket?: string; qrLines?: string[]; clientNodeId?: string; error?: string }
548
+ | { type: "daemon_shutdown" };
549
+ ```
550
+
551
+ `relay_offer` is sent only to the control connection that owns the target lease. `viewer_event` is sent only to the drain requester after `viewer_subscribe` (buffered from drain start, §4.3).
552
+
553
+ ### 5.5 DaemonClient (TUI/CLI side, `src/daemon/control-client.ts`)
554
+
555
+ ```ts
556
+ export interface DaemonClientOptions {
557
+ socketPath: string; client: "tui" | "cli"; version: string;
558
+ onEvent(event: ControlEvent): void;
559
+ onConnectionStateChange(state: "connected" | "reconnecting" | "gone"): void;
560
+ }
561
+ export interface DaemonClient {
562
+ request(req: DistributiveOmit<ControlRequest, "id">): Promise<ControlResponse>;
563
+ /** dial a fresh unix connection with role:"relay", returns duplex after preamble read */
564
+ openRelay(offer: RelayOfferInfo): Promise<{ preamble: RelayPreamble; stream: Duplex }>;
565
+ close(): Promise<void>;
566
+ }
567
+ ```
568
+
569
+ - Reconnect: exponential backoff 250ms → 5s (factor 2, ±20% jitter), forever while enabled.
570
+ - On reconnect, the TUI **re-acquires** all leases it believes it holds (§6.5). Daemon-side, the old connection's leases were already implicitly released on disconnect (crash rule); a brief window where the daemon serves phones directly is acceptable — the re-acquire then follows the normal (possibly draining) path with `handoff:"warm"`, and the TUI runs `session.reload()` to pick up anything the daemon runtime appended.
571
+ - All lease methods used by the TUI are wrapped by `src/modes/interactive/daemon-attach.ts` so that unreachable-daemon ⇒ resolved no-op (§6.1).
572
+
573
+ ### 5.6 Relay lifecycle (authoritative step list)
574
+
575
+ Preconditions: phone conversation stream authenticated; target resolved (§3.7); lease is `tui-owned` with live control connection `C`.
576
+
577
+ 1. Daemon performs the full handshake read + client authorization itself (existing `handshake.ts` / `handshake-reader.ts` / `authorization.ts`). It does NOT write the handshake success response.
578
+ 2. Duplicate check per §4.6 (per clientNodeId + key against live relays); may reject `duplicate_conversation_connection{retryAfterMs:500}` or replace a stale relay.
579
+ 3. Daemon mints `relayId` (uuid) + `relayToken` (32B random, base64url), stores `{token, key, expiresAt: now+10_000, used:false}`.
580
+ 4. Daemon sends `relay_offer{relayId, relayToken, workspaceName, sessionId, clientNodeId, connectionId, streamId}` on `C`.
581
+ 5. TUI dials a NEW unix connection, sends `hello{role:"relay", relayId, relayToken}`. Daemon validates token (unexpired, unused, matching relayId), marks used, replies `hello_ack{ok:true}`. Invalid/expired ⇒ `hello_ack{ok:false,error:"bad_relay_token"}` + close; daemon closes the phone stream with a transient handshake error so the phone retries.
582
+ 6. Daemon writes exactly one JSONL `relay_preamble` line:
583
+
584
+ ```ts
585
+ export interface RelayPreamble {
586
+ type: "relay_preamble";
587
+ relayId: string;
588
+ /** verbatim phone handshake JSON as received (parsed object, re-serialized) */
589
+ handshake: unknown;
590
+ /** authorization subset — everything the TUI needs to serve the stream */
591
+ authorization: { clientNodeId: string; workspaceName: string; workspacePath: string };
592
+ connectionId: string;
593
+ streamId: string;
594
+ resolvedTarget: ResolvedSessionTarget; // §3.7 (sessionId, selection, paths)
595
+ }
596
+ ```
597
+
598
+ 7. **TUI writes the handshake success response itself** onto the relay stream, using `createIntegratedConversationHandshakeResponse` from the shared module `src/daemon/handshake-responses.ts` (fed from the preamble's handshake + authorization + resolvedTarget + its live runtime entry). This keeps response construction identical between daemon-owned and relayed paths.
599
+ 8. Daemon enters raw pipe mode: bidirectional byte pump Iroh stream ⇄ relay connection, **no inspection, no reframing** (`relay-stream.ts`). Backpressure via standard stream `pipe` semantics. Half-close propagates; error on either side closes both. Audit `relay_opened`.
600
+ 9. TUI wraps the relay `Duplex` in the stream shape expected by `runIrohRemoteRpcMode` (the `stream` option; see the option bundle at the existing call site iroh-host.mjs L1587-1611) and invokes:
601
+
602
+ ```ts
603
+ runIrohRemoteRpcMode(this.runtime, {
604
+ stream: relayedStream,
605
+ disposeRuntimeOnClose: false,
606
+ workspaceName: preamble.authorization.workspaceName,
607
+ workspacePath: preamble.authorization.workspacePath,
608
+ initialInput: handshake.initialInput,
609
+ remoteCommandHandler: tuiConversationCommandHandler, // §6.7 incl. abort semantics
610
+ onSessionChanged: (s) => this.daemonAttach.rekey(...), // §6.4
611
+ registerPushTarget: (args) => this.daemonAttach.forwardPushRegister(...), // §6.6
612
+ notificationDelivery: pushForwardingDispatcher, // §6.6
613
+ decorateOutbound: undefined, // host-state decoration is daemon-side metadata; the daemon
614
+ // cannot decorate (no inspection), so the TUI applies the
615
+ // ported decorateRemoteHostState from a shared module if the
616
+ // existing protocol requires it on conversation streams —
617
+ // port decorateRemoteHostState into src/daemon/handshake-responses.ts
618
+ // (or a sibling shared module) and call it here.
619
+ onResponseWritten: undefined, // invalidateStreamAfterAbortResponse is DELETED (§7.4)
620
+ })
621
+ ```
622
+
623
+ `runIrohRemoteRpcMode` (src/modes/rpc/iroh-remote-rpc-mode.ts L99) already layers outbound filter → close-deferring `waitForIdle` → inbound command filter → host-command intercept; all of that now runs in the TUI for relayed streams. `INTEGRATED_CONVERSATION_UNSUPPORTED_RPC_TYPES` `{new_session, switch_session_by_id, get_messages}` rejection lives in the shared `conversation-commands.ts` handler used by both owners.
624
+ 10. Multiple concurrent relays (multiple paired devices) each repeat steps 3-9 independently; each gets its own `runIrohRemoteRpcMode` invocation against the same runtime; `AgentSession.subscribe` fans out events to each stream's subscriber.
625
+ 11. Teardown: phone disconnect → daemon closes relay conn → TUI's rpc-mode invocation ends (runtime NOT disposed) → daemon audits `relay_closed{reason:"phone_disconnected"}` and emits `relay_closed` event on `C` (footer count update). TUI-initiated lease release / rekey / daemon shutdown close relays with the reasons in §5.4.
626
+
627
+ ### 5.7 Relay stream adapter (TUI side, exact shape)
628
+
629
+ `runIrohRemoteRpcMode`'s `stream` option (see the existing call site option bundle, iroh-host.mjs L1587-1611, and the transport layering at src/modes/rpc/iroh-remote-rpc-mode.ts L99) expects the Iroh stream shape used by the host today (a `send` writable side consumed by `writeIrohRemoteHandshakeResponse`/response writer and a readable side consumed by the framed reader, plus close semantics). The TUI wraps the relay `net.Socket` in an adapter, `src/modes/interactive/relay-stream-adapter.ts`:
630
+
631
+ ```ts
632
+ export interface RelayedIrohStreamLike {
633
+ /** writable side: same interface surface the rpc-mode response writer and
634
+ * writeIrohRemoteHandshakeResponse consume (write(bytes), flush, closeWrite) */
635
+ send: IrohSendStreamLike;
636
+ /** readable side: async chunk iteration feeding the existing framed JSONL reader */
637
+ recv: IrohRecvStreamLike;
638
+ /** close both directions; maps to socket.destroy() */
639
+ close(reason?: string): void;
640
+ readonly closed: Promise<{ reason?: string }>;
641
+ }
642
+ export function adaptRelaySocketToIrohStream(socket: Duplex): RelayedIrohStreamLike;
643
+ ```
644
+
645
+ Implementation notes:
646
+ - Define `IrohSendStreamLike` / `IrohRecvStreamLike` in `src/core/remote/iroh/rpc-transport.ts` (which already abstracts transport) as the minimal structural interfaces `runIrohRemoteRpcMode` actually touches, then make both the real Iroh stream (daemon path) and the relay socket (TUI path) satisfy them. If `iroh-remote-rpc-mode.ts` currently reaches into Iroh-specific members beyond that minimal surface, narrow it to the interface as part of M1/M5 — this is the load-bearing refactor that makes byte relay possible without a parallel rpc-mode implementation.
647
+ - `closeIrohRemoteStream(stream, reason)` equivalents on the adapter map reason strings to a best-effort trailer: NO trailer bytes are written by the TUI (the daemon owns close-reason signaling to the phone via the Iroh layer). Instead, the adapter destroys the socket; the daemon's relay pump observes EOF/error and closes the Iroh stream with reason `error` unless the daemon itself initiated the close with a specific reason (`lease_transferred`, `session_rekeyed_reconnect`, `host_shutdown`). Consequence: TUI-initiated stream closes surface to the phone as generic closures — acceptable because the TUI only closes relays via lease release/rekey, which the DAEMON executes with proper reasons; assert in code review that InteractiveMode never destroys a relay socket outside those flows (except process exit, where reason `lease_transferred` is again daemon-driven by the implicit release).
648
+
649
+ ### 5.8 Wire transcript examples (informative)
650
+
651
+ TUI startup, lease acquire, phone attach via relay:
652
+
653
+ ```jsonl
654
+ -> {"type":"hello","role":"control","protocolVersion":1,"pid":4242,"version":"0.9.0","client":"tui"}
655
+ <- {"type":"hello_ack","ok":true,"connectionId":"c-01","version":"0.9.0","protocolVersion":1}
656
+ -> {"type":"lease_acquire","id":"r1","workspaceName":"volt","sessionId":"s-abc"}
657
+ <- {"type":"lease_granted","id":"r1","workspaceName":"volt","sessionId":"s-abc","handoff":"none"}
658
+ <- {"type":"relay_offer","relayId":"rl-7","relayToken":"tK…","workspaceName":"volt","sessionId":"s-abc","clientNodeId":"n-phone","connectionId":"ic-3","streamId":"st-9"}
659
+ (TUI dials new unix conn)
660
+ -> {"type":"hello","role":"relay","protocolVersion":1,"relayId":"rl-7","relayToken":"tK…"}
661
+ <- {"type":"hello_ack","ok":true}
662
+ <- {"type":"relay_preamble","relayId":"rl-7","handshake":{…verbatim…},"authorization":{"clientNodeId":"n-phone","workspaceName":"volt","workspacePath":"/Users/x/volt"},"connectionId":"ic-3","streamId":"st-9","resolvedTarget":{"sessionId":"s-abc","selection":"resumed",…}}
663
+ (raw bytes both ways from here; first TUI write is the handshake success response)
664
+ ```
665
+
666
+ TUI acquire against a mid-turn daemon runtime (drain):
667
+
668
+ ```jsonl
669
+ -> {"type":"lease_acquire","id":"r2","workspaceName":"volt","sessionId":"s-abc"}
670
+ <- {"type":"lease_pending","id":"r2","viewerFeedId":"vf-1"}
671
+ -> {"type":"viewer_subscribe","id":"r3","viewerFeedId":"vf-1"}
672
+ <- {"type":"ok","id":"r3"}
673
+ <- {"type":"viewer_event","viewerFeedId":"vf-1","seq":0,"event":{"type":"message_delta",…}}
674
+ <- {"type":"viewer_event","viewerFeedId":"vf-1","seq":1,"event":{"type":"agent_end",…}}
675
+ <- {"type":"viewer_end","viewerFeedId":"vf-1","reason":"granted"}
676
+ <- {"type":"lease_granted","id":"r2","workspaceName":"volt","sessionId":"s-abc","handoff":"warm"}
677
+ ```
678
+
679
+ ### 5.9 Stale socket recovery & single instance
680
+
681
+ Already specified in §3.1: connect → `status` probe → 2s timeout → unlink → retry bind once.
682
+
683
+ ---
684
+
685
+ ## 6. TUI Integration
686
+
687
+ Surgical changes to `src/modes/interactive/interactive-mode.ts` plus a new façade `src/modes/interactive/daemon-attach.ts` wrapping `src/daemon/control-client.ts`. InteractiveMode is 7152 lines with 64 distinct `this.session.*` member uses — deep coupling that justifies co-locating the runtime with the TUI rather than remoting it.
688
+
689
+ ### 6.1 `daemon-attach.ts` façade
690
+
691
+ ```ts
692
+ export interface DaemonAttach {
693
+ /** All methods resolve successfully as no-ops when daemon is off/unreachable. */
694
+ acquire(ws: string, sessionId: string): Promise<AcquireOutcome>; // granted|pending(viewer)|denied|noop
695
+ release(ws: string, sessionId: string): Promise<void>;
696
+ rekey(ws: string, oldId: string, newId: string): Promise<void>;
697
+ forwardPushRegister(ws: string, sessionId: string,
698
+ kind: "push_target" | "live_activity", payload: unknown): Promise<void>;
699
+ onRelayOffer(handler: (offer, openRelay) => void): void;
700
+ relayCount(): number; // for footer indicator
701
+ onRelayCountChange(cb: (n: number) => void): void;
702
+ connectionState(): "connected" | "reconnecting" | "gone" | "disabled";
703
+ dispose(): Promise<void>;
704
+ }
705
+ ```
706
+
707
+ Constructed at TUI startup whenever the platform supports it. `remote.background === true` enables auto-spawn; otherwise the attach remains in reconnect backoff until another process starts the daemon. Unsupported platforms use a `disabledDaemonAttach` stub where every method is an immediate no-op. **No InteractiveMode code path may throw or block on daemon absence.**
708
+
709
+ ### 6.2 Lifecycle seams (exact insertion points)
710
+
711
+ | Seam | Location | Action |
712
+ |------|----------|--------|
713
+ | Startup | after `bindCurrentSessionExtensions()` completes for the initial session (call site ~L1741 within the startup flow; method defined L1631) | `acquire(ws, session.id)`; handle pending (drain viewer, §6.3) / denied (`held_by_tui` → read-only banner) |
714
+ | `/new`, resume, fork, tree-navigate | the same seams that fire extension `session_shutdown` → `session_start` (reasons `new`/`resume`/`fork`) | `release(ws, oldId)` **before** the old session's shutdown completes is not required — order is: begin switch → `release(old)` → normal switch → `acquire(new)` after the new `bindCurrentSessionExtensions` |
715
+ | Session id change in place (compaction/rekey without shutdown) | wherever `session.id` changes without the shutdown/start pair | `rekey(ws, oldId, newId)` |
716
+ | Quit | TUI shutdown path (after final flush, before process exit) | if `session.isStreaming` && `relayCount() > 0` → exit warning prompt ("A phone is attached and a turn is streaming; quitting will kill the turn. Quit anyway?"); then `release(ws, id)`; `dispose()` |
717
+
718
+ `ws` (workspaceName) resolution: the daemon's workspace registry maps registered names→paths; the TUI resolves its cwd against `status_result.workspaces` (longest-prefix match on real paths). When connected, the TUI sends `workspace_register` automatically if its cwd is inside no registered workspace — name = basename with numeric suffix on collision.
719
+
720
+ ### 6.3 Drain viewer ("Attaching — finishing remote turn…")
721
+
722
+ On `AcquireOutcome.pending{viewerFeedId}`:
723
+
724
+ 1. TUI sends `viewer_subscribe{viewerFeedId}` and enters a read-only attach overlay: editor disabled, status line "Attaching — finishing remote turn…", input keystrokes buffered into a local queue (plain text only; commands ignored).
725
+ 2. Each `viewer_event` is rendered through the same event-rendering path used for live session events where feasible; implement as a lightweight renderer that feeds `AgentSessionEvent` objects into the existing message/tool renderers WITHOUT a live session (new `src/modes/interactive/drain-viewer.ts`). If `viewer_event{kind:"truncated"}` arrives first, show spinner only.
726
+ 3. On terminal `lease_granted{handoff:"warm"}`: dismiss overlay, load the session from file via the normal resume path (which fires `session_start` reason per the seam), then flush the queued input into the editor (not auto-submit).
727
+ 4. On `viewer_end{reason:"error"}` / response `error{drain_failed}`: show notice, fall back to read-only open with a retry hint.
728
+
729
+ ### 6.4 Rekey and relays
730
+
731
+ When `onSessionChanged` fires inside a TUI-served relay (or the TUI itself rekeys), `daemon-attach.rekey` updates the broker; the daemon closes affected relays `session_rekeyed_reconnect`; phones reconnect and re-validate selection. The TUI does not tear down its runtime.
732
+
733
+ ### 6.5 Reconnect behavior
734
+
735
+ On DaemonClient reconnect, `daemon-attach` re-sends `lease_acquire` for the currently open session. If the response is `granted{handoff:"warm"}` (daemon had spun up a runtime during the gap), the TUI calls `session.reload()` (agent-session.ts L2657-2681) to absorb file changes; extension lifecycle follows reload semantics (reason `"reload"`). `pending` follows the drain viewer path with the current transcript kept on screen behind the overlay.
736
+
737
+ ### 6.6 Push / Live Activity / workspace forwarding
738
+
739
+ Relayed conversations must keep push working after TUI exit, so push state lives in the daemon. The relay's `remoteCommandHandler` intercepts the state-touching RPC commands (`RELAY_RPC_COMMAND_TYPES`: `register_push_target`, `register_live_activity`, `unregister_live_activity`, `unregister_workspace`) and forwards them verbatim as `relay_rpc` control requests carrying the phone's `clientNodeId` and the TUI's current session id. The daemon executes them against its real state (push dispatcher, live-activity delivery channels, workspace registry with full unregister cleanup) and returns the verbatim RPC response in `relay_rpc_result`, which the TUI relays to the phone; `unregister_workspace` results also carry refreshed workspace metadata for the TUI's authorization decoration. Actual APNs dispatch always happens daemon-side.
740
+
741
+ ### 6.7 Abort symmetry and command handling
742
+
743
+ The TUI's relay command handler (shared `conversation-commands.ts`) implements the new abort: on `abort` → `session.abort()` (agent-session.ts L1535-1539) + `waitForIdle`, reply success, **stream stays open**. Local TUI abort (Esc) is now behaviorally identical from the phone's perspective: the phone sees the turn end (`agent_end`/idle events) and its stream stays open. No `onResponseWritten` stream invalidation anywhere.
744
+
745
+ ### 6.8 Footer indicator
746
+
747
+ Add a "phone attached" segment via the existing footer-data-provider seam (the mechanism used by `setFooter`/footer data providers in interactive mode): visible when `relayCount() >= 1`, shows `📱 n` (or `[phone n]` in ASCII terminals). Updates on `relay_offer` accept / `relay_closed`.
748
+
749
+ ### 6.9 Extension dialogs bind to the owner frontend
750
+
751
+ For TUI-owned runtimes, extension UI requests route to the TUI's real `ExtensionUIContext` (created via `createExtensionUIContext`, theme mappings interactive-mode.ts L2242-2261). Phones do **not** receive `extension_ui_request` for TUI-owned runtimes — the outbound filter for relayed streams suppresses `extension_ui_request` frames. (Mechanism: `runIrohRemoteRpcMode`'s outbound filter layer gets an option `suppressExtensionUiRequests: boolean`, set `true` for relayed streams in the TUI and `false` for daemon-owned streams.) Rationale: dialogs are answered where the extension's mediated UI actually lives; split-brain answers are worse than phone silence. docs/rpc.md gets a note (M10).
752
+
753
+ ---
754
+
755
+ ## 7. Daemon-Owned Runtime Path (headless)
756
+
757
+ ### 7.1 Construction
758
+
759
+ `integrated-runtimes.ts` builds runtimes with `createIrohRemoteAgentRuntimeWithSessionSelection` (iroh-remote-agent-runtime.ts L85) using the pre-resolved target (§3.7). Selection kinds `created` / `created_after_missing` / `resumed` and host-synthesized `session_rekeyed` behavior are preserved bit-for-bit for the iOS selection validation.
760
+
761
+ ### 7.2 Serving streams
762
+
763
+ Each attached phone stream is served by `runIrohRemoteRpcMode(entry.runtime, options)` with the ported option bundle (decorateOutbound host-state decoration, `disposeRuntimeOnClose:false`, push dispatcher as `notificationDelivery` + `registerPushTarget`, `onSessionChanged` → rekey, `onWorkflowEvent` → workflow replay bookkeeping, `remoteCommandHandler` → shared `conversation-commands.ts`, `initialInput`, workspace name/path). Attach/detach subscriber logic ports from iroh-host.mjs L2624-2682; stop from `stopIntegratedRuntimeEntry` L2684-2723. **Workflow event replay** on (re)attach is preserved (port `replayIntegratedRuntimeWorkflowEvents`).
764
+
765
+ ### 7.3 Extensions in daemon mode
766
+
767
+ `ExtensionRunner` loads normally (loader reads `pkg.volt` or Pi-compatible `pkg.pi`, loader.ts L492-506; tool wrapping via wrapper.ts `wrapRegisteredTool` injecting `runner.createContext`). `ctx.mode === "rpc"` (`ExtensionMode`, types.ts:301). UI context is the rpc-mediated one: dialogs (`select/confirm/input/editor`) go over `extension_ui_request` to phones per docs/rpc.md L1513+; `custom()` returns undefined; `setFooter/setHeader/setEditorComponent` no-op; `setWidget` component factories ignored (runner.ts `noOpUIContext` L230-265 shape for the terminal-only surface). This is today's documented degradation — unchanged.
768
+
769
+ ### 7.4 Abort redesign (daemon side)
770
+
771
+ Delete `invalidateStreamAfterAbortResponse` (iroh-host.mjs L1556-1564: on successful abort it removed the active stream, stopped the runtime entry, and closed the Iroh stream). New behavior in shared `conversation-commands.ts`:
772
+
773
+ ```
774
+ abort command -> session.abort() -> await session.waitForIdle()
775
+ -> respond { success: true }
776
+ -> stream stays open; runtime stays live; lease state unchanged
777
+ ```
778
+
779
+ No `onResponseWritten` hook is passed anywhere. Audit keeps recording the abort as a command execution; no `runtime stop reason "abort"` events exist anymore.
780
+
781
+ ### 7.5 Prompt streaming behavior
782
+
783
+ Unchanged: `prompt` while streaming requires `streamingBehavior: "steer" | "followUp"` (agent-session.ts L1153-1166); `queue_update` events (in the event union L138-163) flow to all subscribers, so a TUI viewing a daemon drain and a second phone both see queued steering.
784
+
785
+ ---
786
+
787
+ ## 8. Extension Compatibility Contract
788
+
789
+ **Fixed constraint: zero source changes to `ExtensionContext` / `ExtensionAPI` / `ExtensionUIContext` (types.ts).** Theme facade shape (types.ts:261-270: `theme` getter, `getAllThemes()`, `getTheme(name)`, `setTheme(string|Theme)`) preserved exactly. Lifecycle reasons remain the existing unions: `session_start` reasons `startup|reload|new|resume|fork`; `session_shutdown` reasons `quit|reload|new|resume|fork`. **No new lifecycle event** (no `session_switched`).
790
+
791
+ What a Pi extension experiences, by lease state of its host process:
792
+
793
+ | Situation | ctx.mode | UI dialogs | custom()/setFooter/setHeader/setEditorComponent | theme APIs | lifecycle observed |
794
+ |---|---|---|---|---|---|
795
+ | TUI-owned, no phones | `"tui"` | real TUI dialogs | full fidelity | live ThemeService (TUI facade) | normal |
796
+ | TUI-owned, phones relayed | `"tui"` | real TUI dialogs (phones suppressed §6.9) | full fidelity | live ThemeService | normal; phone prompts are indistinguishable from local prompts |
797
+ | Daemon-owned (headless) | `"rpc"` | mediated over extension_ui_request to phones; on iOS only `confirm` is answerable, `select/input/editor` auto-cancelled (volt-app `VoltSession+HostActions.swift` 381-396) | undefined / no-ops per docs/rpc.md | ThemeService rpc facade (§9.4): `getAllThemes()` returns real list, `setTheme` persists + broadcasts, `ctx.ui.theme` returns last resolved snapshot | normal |
798
+ | Handoff daemon→TUI | old instance: `session_shutdown{reason:"quit"}` then disposed with runtime; new instance in TUI: fresh `load` + `session_start{reason:"resume"}` (or the TUI's actual open reason) | — | — | — | exactly the same as user quitting a headless session and resuming in a TUI |
799
+ | Handoff TUI→daemon | old: `session_shutdown{reason:"quit"}` at TUI exit; new (lazy, on next phone attach): fresh load + `session_start{reason:"resume"}` in the daemon | — | — | — | same as quit+resume |
800
+
801
+ Guarantees the implementation must uphold:
802
+
803
+ 1. Extensions never migrate in-memory state across processes. Anything an extension wants to survive handoff must already live in the session file or its own storage — identical to surviving `volt` quit/reopen today. Document this in docs/extensions.md (M10).
804
+ 2. `AgentSession.dispose()` invalidates the extension context (agent-session.ts L781-800; runner.ts `invalidate` L510-517) — the dispose ordering in drain (§4.3 step 5) and shutdown (§3.9 step 3) must go through the normal dispose path so this happens.
805
+ 3. `runner.setUIContext` (runner.ts L400-403) is only ever called with the owner-appropriate context; relays never swap the TUI's UI context.
806
+ 4. Loader behavior (pkg.volt / pkg.pi, loader.ts L492-506) and tool wrapping (wrapper.ts) are untouched.
807
+ 5. `commandContextActions` / `uiContext` passed at `bindCurrentSessionExtensions` (interactive-mode.ts L1631) are untouched in shape.
808
+
809
+ ---
810
+
811
+ ## 9. Theme Service
812
+
813
+ ### 9.1 Problem
814
+
815
+ `src/modes/interactive/theme/theme.ts` is a module-global Proxy singleton (globalThis-keyed, L732), with a single `onThemeChange` callback slot and an fs watcher (L812-892). The daemon cannot import interactive-mode internals, and a singleton with one subscriber cannot serve daemon + broadcast needs.
816
+
817
+ ### 9.2 New module: `src/core/theme/`
818
+
819
+ ```
820
+ src/core/theme/
821
+ theme-service.ts // ThemeService class
822
+ tokens.ts // token resolution (moved from modes/interactive/theme/theme.ts)
823
+ discovery.ts // theme file discovery per docs/themes.md locations
824
+ types.ts // Theme, ThemeName, ResolvedThemeTokens
825
+ index.ts
826
+ ```
827
+
828
+ ```ts
829
+ export interface ThemeService {
830
+ readonly current: Theme; // resolved active theme
831
+ getAllThemes(): Theme[];
832
+ getTheme(name: string): Theme | undefined;
833
+ setTheme(theme: string | Theme): Promise<void>;
834
+ resolveTokens(theme?: Theme): ResolvedThemeTokens; // flat token map for snapshots
835
+ subscribe(cb: (theme: Theme) => void): () => void; // subscriber LIST, not single slot
836
+ /** hot-reload fs watcher — enabled ONLY by the rendering (TUI) process */
837
+ enableHotReload(): void;
838
+ dispose(): void;
839
+ }
840
+ export function createThemeService(opts?: { agentDir?: string; initialTheme?: string }): ThemeService;
841
+ ```
842
+
843
+ Instanced: TUI creates one at `initTheme` (interactive-mode.ts L481 seam) with `enableHotReload()`; the daemon creates one without the watcher; CLI paths (config-selector, startup-ui, export-html) create ephemeral instances or receive one.
844
+
845
+ ### 9.3 Importer migration (complete list, from current imports of the singleton)
846
+
847
+ Migrate every importer of `modes/interactive/theme/theme.ts` to receive/construct a `ThemeService` (or a bound token-resolver where a full service is overkill):
848
+
849
+ - `src/core/agent-session.ts:35`
850
+ - `src/core/resource-loader.ts:5`
851
+ - `src/core/extensions/runner.ts:8`
852
+ - `src/core/extensions/types.ts:43` (type-only import — repoint to `src/core/theme/types.ts`; NO shape change)
853
+ - `src/core/export-html/*` (all files importing theme)
854
+ - `src/core/tools/*`: `bash`, `edit`, `find`, `grep`, `ls`, `lsp`, `read`, `render-utils`, `subagent`, `web-search`, `write`
855
+ - `src/main.ts:72`
856
+ - `src/index.ts:775`
857
+ - `src/cli/config-selector.ts`
858
+ - `src/cli/startup-ui.ts`
859
+
860
+ A compatibility Proxy re-export at the old path MAY remain during the milestone to keep the tree green mid-migration, but M6's acceptance criteria require zero remaining imports of the old path and deletion of the shim. Tools that only need colors at render time should take a `ResolvedThemeTokens`/theme accessor through their existing context plumbing rather than a service instance (implementer's choice per tool; the acceptance criterion is only "no module-global theme").
861
+
862
+ ### 9.4 Pi facade per mode (shape unchanged, types.ts:261-270)
863
+
864
+ | API | TUI (`mode:"tui"`) | Daemon (`mode:"rpc"`) |
865
+ |---|---|---|
866
+ | `ctx.ui.theme` (getter) | live `service.current` | last resolved snapshot (`service.current` of the daemon's instance) |
867
+ | `getAllThemes()` | full list | full list (**supersedes** docs/rpc.md's "returns []" — update doc, M10) |
868
+ | `getTheme(name)` | lookup | lookup |
869
+ | `setTheme(x)` | applies + re-render | persists to daemon settings, applies to daemon instance, broadcasts `theme_snapshot{themeName, tokens}` to all control connections (**supersedes** docs/rpc.md's "fails in rpc mode") |
870
+
871
+ TUIs receiving `theme_snapshot` for a theme they didn't set: apply only if the user hasn't set a per-TUI theme override this session; otherwise ignore (local explicit choice wins). Keep this rule simple and documented.
872
+
873
+ ### 9.5 Optional milestone (M11): iOS token push
874
+
875
+ Feature-flagged, additive: daemon pushes sanitized resolved tokens (hex colors only, denylist anything path-like) as a new notification frame `host_theme_tokens` on workspace streams, gated on the phone advertising a future feature string. `volt-app/Volt/Theme/` today is static caseless enums (`VoltPalette`, `VoltSpacing`, `VoltTypography`) with **no host data path and no theme RPC in `VoltRPC.swift`** — the iOS side would add a `HostThemeStore` mapping tokens onto VoltPalette accessors. Ship OFF by default; app MAY ignore the frame entirely. No acceptance dependency for M1-M10.
876
+
877
+ ---
878
+
879
+ ## 10. iOS App Deltas (volt-app; breaking OK, enumerate exactly)
880
+
881
+ The app keeps advertising BOTH `multi_streams.v1` and `conversation_streams.v1` (handshake requires both — `IrohProtocol.swift:375-377`). The host MAY additionally advertise `conversation_leases.v1` (additive; app MAY ignore, D11 makes it optional-but-useful).
882
+
883
+ | # | Delta | Affected Swift files | Detail |
884
+ |---|-------|----------------------|--------|
885
+ | D1 | **Abort contract change (breaking).** Abort success now means: turn stopped, stream STAYS open, runtime stays live. Remove the abort→expect-closure→reopen dance. | `Volt/.../VoltSession+Prompting.swift` | At `:140+`: keep sending the abort command and recording `SelectedAbortRequest` for UI state, but DELETE `markUserAbortClosureExpected`. `handleSelectedAbortResponse` (`:222`): on success, clear pending abort UI, do NOT arm closure expectation. DELETE `completePendingSelectedAbortAfterExpectedClosure` (`:261`) and its call sites. DELETE `scheduleSelectedAbortRecovery` (`:297`) and its reopen-via-`selectPinnedAgent` path. |
886
+ | D2 | **Remove user-abort closure ledger markers.** | `ConversationClosureLedger` (file containing it), `VoltSession+Connect.swift` (`:535`, `:549`), `WorkspaceEventStreams.swift` (`:126`) | Remove userAbort marker recording (10s TTL) and the two Connect.swift + one WorkspaceEventStreams.swift call sites of `completePendingSelectedAbortAfterExpectedClosure`. Ledger stays for other expected-closure kinds (incl. new D9). |
887
+ | D3 | **`conversation_in_use` vestigial at handshake.** Host never emits it at handshake for paired clients. | `FailureClassification.swift:143-146`, `PinnedAgentStatus` definition, any UI rendering `.inUse` | Keep the classification mapping for forward-compat (harmless), but mark `.inUse` handling as vestigial with a comment; delete any proactive UX built around expecting it (e.g., "in use by another device" retry prompts) if such flows are wired to handshake outcomes. No new behavior required. |
888
+ | D4 | **`duplicate_conversation_connection` unchanged.** | `VoltHostSessionManager+AgentSelection.swift` | Keep retry loop max 5 attempts, backoff+jitter, `retryAfterMs` clamp 500-5000. Verify tests still pass against daemon host. |
889
+ | D5 | **New transient error `lease_draining` on prompt.** | `FailureClassification.swift`, prompt error handling in `VoltSession+Prompting.swift` | Classify as retryable-transient; honor `retryAfterMs` (default 1000ms); surface as brief "Handing off to desktop…" toast, auto-retry a few times, then leave the composer content intact for manual retry. |
890
+ | D6 | **Selection validation unchanged.** | `IrohProtocol.swift` (`selectionIsValid`, `validatedRequestedSessionId`) | `.last/.new/.session` targets and `created/created_after_missing/resumed` + `session_rekeyed` handling stay as-is; daemon preserves semantics (§3.7, §7.1). Re-run existing tests. |
891
+ | D7 | **Backgrounding unchanged.** | `VoltHostSessionManager.swift:175-190`, `VoltSession+Lifecycle.swift` (`suspendForBackgroundRecovery`) | Detach-only backgrounding still correct; daemon-detached retention gives 30min reattach. No change. |
892
+ | D8 | **Foreground reconnect + catchup unchanged.** | `VoltSession+Connect.swift:408-412`, `VoltSession+PostCommitCatchup.swift` | `set_client_capabilities` / `get_pending_host_actions` / `get_state` / `get_transcript` sequence and post-commit catchup barrier (max 500 entries / 2 MiB) work against both daemon-owned and relayed streams (relay is byte-transparent). No change; add a test against a relayed fake. |
893
+ | D9 | **New close reason `lease_transferred` = expected closure → immediate reconnect.** | `ConversationClosureLedger` (or the closure-reason → reconnect policy map), `VoltSession+Connect.swift` reconnect scheduling | Map `lease_transferred` (and `session_rekeyed_reconnect`) to expected-closure: no error UI, reconnect immediately (no backoff first attempt). This is THE hook that makes TUI-open/TUI-quit invisible on the phone. |
894
+ | D10 | **Transcript merge already multi-origin ready — verify.** | `VoltSession+Transcript.swift` | remoteEntryID dedupe + optimistic reconciliation appending unmatched incoming user entries must accept user entries originating from the TUI. Expected zero code change; add a VoltClient test with an interleaved TUI-origin user entry. |
895
+ | D11 | **Optional: recognize `conversation_leases.v1`.** | `IrohProtocol.swift` feature parsing | If present, the app MAY show "shared with desktop" affordance and expect `lease_transferred` closures; ignoring the feature is fully safe. |
896
+ | D12 | **Extension UI: no `extension_ui_request` while TUI owns the lease.** | `VoltSession+HostActions.swift:381-396` | No code change needed (fewer requests arrive); update in-code comment: only `confirm` is answerable, `select/input/editor` auto-cancelled, and TUI-owned conversations produce none. |
897
+ | D13 | **Push ordering unchanged.** | `VoltSession+LiveActivity.swift` (`confirmedLiveActivityDeliveryChannel`) | `register_push_target` then `register_live_activity` ordering is preserved by the daemon even for relayed conversations (§6.6). No change; add ordering assertion to VoltClient tests. |
898
+
899
+ ---
900
+
901
+ ## 11. Security Considerations
902
+
903
+ 1. **Unix socket trust boundary.** `daemon/` dir `0700`, socket `0600`, owned by the user. Anyone who can connect is the same OS user, who already owns the agent dir, session files, and could read the Iroh secret key — the socket adds no privilege. No socket auth beyond filesystem perms. Relay tokens (single-use, 10s) exist to bind a relay connection to a specific offer, not as an auth boundary.
904
+ 2. **Iroh termination stays in the daemon.** Phone identity verification (paired clientNodeId), handshake parsing, and revocation checks all complete before any relay is offered. The TUI receives only post-auth streams plus the authorization subset. Revocation (`client_revoke`) closes live streams and relays for that client immediately.
905
+ 3. **Tool policy (explicit decision).** When the TUI owns the lease, phone prompts execute with the **TUI session's full local tool set** — the phone is the same paired user driving the same conversation; splitting tool policy mid-conversation creates confusing, falsely-reassuring states. This **supersedes `--allow-tools` for co-attached conversations**. Daemon-owned headless runtimes keep the allow-tools restriction (`settings.allowTools`). `docs/security.md` MUST document this asymmetry prominently (M10), including the corollary: pairing a phone grants it desktop-equivalent power over any TUI-open conversation.
906
+ 4. **`conversation_in_use` policy exception** removes the old pseudo-lock for compatible clients while rejecting a narrower client that would otherwise inherit a broader daemon runtime. Documented in security.md.
907
+ 5. **State file** contains the Iroh secret key — `state.json` written `0600` (enforce on write), as `remote/iroh-host.json` is today.
908
+ 6. **Audit** (§3.10) covers lease/relay/daemon lifecycle so post-hoc "what did the phone do while I was away" review is possible (`volt daemon logs` + `audit.jsonl`).
909
+ 7. **Frame cap** (8 MiB) bounds control-plane memory; relay pipes are unframed and bounded by stream backpressure.
910
+ 8. **No new network listeners.** The daemon listens only on the unix socket and the existing Iroh endpoint.
911
+
912
+ ---
913
+
914
+ ## 12. Testing Strategy
915
+
916
+ Volt entry point: `./test.sh` (repo root of Volt). iOS: VoltClient package tests in volt-app.
917
+
918
+ ### 12.1 Test infrastructure to build
919
+
920
+ - `src/daemon/testing/fake-clock.ts` — injectable timers for lease broker (retention, token expiry, drain).
921
+ - `src/daemon/testing/loopback-control.ts` — in-memory duplex pair implementing the control framing (no real socket) for protocol tests; plus a real-socket harness (tmpdir socket) for integration tests.
922
+ - `src/daemon/testing/fake-phone.ts` — drives the conversation-plane JSONL protocol (handshake, prompt, abort, get_transcript) over an arbitrary Duplex — reused against daemon-owned streams AND relayed streams. Build on existing loopback rpc utilities (`src/modes/rpc/in-process-rpc-client.ts`, `createLoopbackRpcTransportPair`).
923
+
924
+ ### 12.2 Unit tests
925
+
926
+ 1. **Lease broker** (`lease-broker.test.ts`): every cell of the §4.2 table; idempotent acquire; connection-loss implicit release; draining cancel on requester disconnect; rekey during each state; retention arm/cancel/fire; force → `force_unsupported`; `draining_elsewhere`.
927
+ 2. **Control protocol** (`control-protocol.test.ts`): encode/decode round-trip for every type in §5.3-5.5; 8 MiB cap enforcement; partial-line buffering; relay hello token validation incl. expiry and reuse rejection.
928
+ 3. **Relay framing** (`relay-stream.test.ts`): preamble exactness (verbatim handshake JSON, authorization subset fields); byte-transparency (random binary chunks survive both directions unmodified); half-close propagation; token single-use.
929
+ 4. **State migration** (`state.test.ts`): legacy iroh-host.json → VoltdStateFileV1; secret key byte-identical; idempotency; `.migrated` rename; missing/partial legacy sections.
930
+ 5. **Session target** (`session-target.test.ts`): last/new/session × existing/missing → correct selection kinds, matching current iroh-remote-agent-runtime behavior (golden tests extracted before refactor).
931
+ 6. **Theme service** (`theme-service.test.ts`): discovery, token resolution parity with old singleton (golden snapshot), multi-subscriber notify, setTheme persistence hook, watcher only when enabled.
932
+ 7. **Conversation commands** (`conversation-commands.test.ts`): abort → success + stream stays open (assert no close, runtime live); `INTEGRATED_CONVERSATION_UNSUPPORTED_RPC_TYPES` rejection; `lease_draining` rejection set.
933
+
934
+ ### 12.3 Integration tests (real daemon on tmpdir socket, fake Iroh transport where possible; mark network-Iroh tests optional/skipped in CI without the native module)
935
+
936
+ 1. **Daemon lifecycle**: spawn → probe → status → single-instance rejection → stale-socket recovery → graceful shutdown ordering (§3.9 observable via audit lines + control events); log rotation at threshold.
937
+ 2. **Handoff both directions**: (a) daemon-active + mid-turn fake turn → TUI acquire → `lease_pending` → viewer events observed → turn idle → `lease_granted{warm}` → daemon runtime disposed (extension fixture logs `session_shutdown{quit}`) → phone stream closed `lease_transferred`; (b) TUI release → `unowned` → fake-phone reconnect → lazy resume → transcript continuity (entry appended by TUI visible via `get_transcript`).
938
+ 3. **Dual-frontend live session over loopback**: TUI harness (headless InteractiveMode driver or a runtime-level stand-in exercising the relay serving path) owns lease; two fake phones relay-attach concurrently; phone A prompts; assert: TUI runtime received prompt, both phones receive streamed events, `message_start` user entry carries phone origin and is renderable, abort from phone B stops turn with both streams still open.
939
+ 4. **Extension fidelity fixture**: a test extension recording lifecycle events + ctx.mode + theme facade results, loaded in both owners across a handoff; assert the §8 table rows it can observe (mode value, dialogs routing, getAllThemes non-empty in rpc mode, shutdown/start reasons).
940
+ 5. **Push ordering**: relayed conversation registers push target then live activity via control forwarding; assert daemon dispatch order per client.
941
+ 6. **Reconnect/re-acquire**: kill daemon under a lease-holding TUI harness → restart → DaemonClient reconnects → re-acquire → `warm` + reload path invoked.
942
+
943
+ ### 12.4 iOS (VoltClient package tests, volt-app)
944
+
945
+ 1. Abort: success response with NO subsequent closure → UI clears pending abort, no reconnect scheduled (D1/D2).
946
+ 2. Closure reasons: `lease_transferred` and `session_rekeyed_reconnect` → expected-closure, immediate reconnect (D9).
947
+ 3. `lease_draining` prompt error → retry with retryAfterMs, composer preserved (D5).
948
+ 4. Transcript merge with interleaved TUI-origin user entry → dedup/append correctness (D10).
949
+ 5. Duplicate retry loop against fake daemon responses (D4). 6. Push ordering assertion (D13).
950
+
951
+ ### 12.5 Manual walk-away verification script
952
+
953
+ Add `docs/daemon.md` appendix + `scripts/manual-walkaway.md` checklist:
954
+
955
+ 1. Start the daemon manually or set `remote.background: true`; open TUI in a registered workspace; confirm `volt daemon status` shows a tui-owned lease.
956
+ 2. Pair phone (`volt remote pair`), open the same conversation on phone; footer shows 📱1.
957
+ 3. Phone prompt → appears live in TUI; TUI prompt → appears on phone.
958
+ 4. Quit TUI → phone continues within ~2s (lease_transferred reconnect); run another turn from phone.
959
+ 5. While phone turn is streaming, reopen TUI → "Attaching — finishing remote turn…" viewer → editor unlocks at turn end → full transcript incl. away-time turns.
960
+ 6. Phone abort mid-turn → turn stops, phone stream stays connected (no reconnect spinner).
961
+ 7. `volt daemon restart` with TUI open → footer relay indicator drops and returns; phone reconnects.
962
+
963
+ ---
964
+
965
+ ## 13. Implementation Plan (ordered milestones)
966
+
967
+ Each milestone leaves `./test.sh` green. Branch implements M1-M8 (+M10 docs) in one pass; M9/M11 optional.
968
+
969
+ ### M1 — Shared extraction & theme lift foundations
970
+ **Files**: new `src/core/theme/*` (§9.2); new `src/daemon/session-target.ts` (+ refactor `src/modes/rpc/iroh-remote-agent-runtime.ts` to consume it); new `src/daemon/handshake-responses.ts` (port `createIntegratedConversationHandshakeResponse` + `decorateRemoteHostState` from iroh-host.mjs into typed TS); new `src/daemon/conversation-commands.ts` (port command handling; abort NOT yet changed); golden tests for target resolution + theme tokens.
971
+ **Accept**: old theme singleton still present (shim re-export); iroh-host.mjs temporarily imports nothing new (parallel code allowed); unit tests §12.2.5/§12.2.6 pass; no behavior change.
972
+
973
+ ### M2 — Control protocol + daemon skeleton
974
+ **Files**: `src/daemon/control-protocol.ts`, `control-server.ts`, `control-client.ts`, `state.ts` (+migration), `spawn.ts`, `cli.ts`, `main.ts` (daemon), main.ts (CLI router: add `volt daemon *`); daemon file layout, pidfile, log rotation, single-instance, version skew, graceful shutdown skeleton (no runtimes yet).
975
+ **Accept**: `volt daemon start/stop/status/restart/logs` work end-to-end on macOS+Linux; migration test §12.2.4 passes; standalone SEA rejected; unit tests §12.2.2 pass; lifecycle integration §12.3.1 passes (minus runtime drain).
976
+
977
+ ### M3 — Host rewrite: daemon-owned runtimes (parity port)
978
+ **Files**: `src/daemon/integrated-runtimes.ts`, `workspace-streams.ts`; wire `IrohRemoteHostEngine` into daemon main; port pairing (`pair_request` control flow replacing `startPairControlServer`), revocation, push/live-activity (ordering invariant), audit (all existing events + `daemon_started`/`daemon_shutdown`), workflow replay, retention, `onSessionChanged` rekey plumbing (broker stub: everything `daemon-active`/`daemon-detached`), duplicate handling, UNSUPPORTED_RPC rejection. **Delete** `src/remote/iroh-host.mjs`; **delete** main.ts spawn path (L1075-1097) and `volt remote host` (replace with removal error); rewrite `volt remote pair/status/clients/revoke/workspace` as control clients.
979
+ **Accept**: a phone (or fake-phone harness) pairs, connects, prompts, backgrounds/reattaches within TTL against voltd with behavior parity to the old host; `git grep iroh-host.mjs` only hits docs/changelog; §12.3.1 fully passes.
980
+
981
+ ### M4 — Lease broker + abort redesign
982
+ **Files**: `src/daemon/lease-broker.ts` (+tests), integrate into stream routing (§2.3); drop clientNodeId from runtime key; retire handshake `conversation_in_use`; new abort semantics in `conversation-commands.ts` (delete `invalidateStreamAfterAbortResponse` port); `lease_draining` rejection; audit lease events.
983
+ **Accept**: §12.2.1 and §12.2.7 pass; two fake phones (distinct clientNodeIds) co-attach to one daemon runtime and both stream; abort keeps streams open; retention interplay test passes.
984
+
985
+ ### M5 — Relay path + TUI integration
986
+ **Files**: `src/daemon/relay-stream.ts`; relay admission in `control-server.ts`; `src/modes/interactive/daemon-attach.ts`; `src/modes/interactive/drain-viewer.ts`; surgical edits to `src/modes/interactive/interactive-mode.ts` (seams §6.2, footer indicator §6.8, exit warning, relay serving with `runIrohRemoteRpcMode` §5.6 step 9, `suppressExtensionUiRequests` option added to iroh-remote-rpc-mode outbound filter); push forwarding (`relay_rpc`) §6.6.
987
+ **Accept**: §12.2.3 passes; dual-frontend integration §12.3.3 passes; with the daemon stopped, InteractiveMode behavior remains unchanged apart from the dormant reconnect client; footer indicator toggles.
988
+
989
+ ### M6 — Theme migration completion
990
+ **Files**: migrate ALL importers listed in §9.3; delete `src/modes/interactive/theme/theme.ts` singleton + shim (move remaining pure logic into core/theme); daemon `theme_set` + `theme_snapshot` broadcast; rpc-mode facade (getAllThemes real, setTheme persists) in runner UI-context wiring.
991
+ **Accept**: `git grep "modes/interactive/theme/theme"` returns nothing; §12.2.6 + extension fixture theme rows (§12.3.4) pass; `ctx.ui.*` types unchanged (compile-time check: a fixture Pi extension from before the branch compiles unmodified).
992
+
993
+ ### M7 — Handoff (drain, viewer, lazy resume, reconnect)
994
+ **Files**: drain orchestration in `lease-broker.ts` + `integrated-runtimes.ts`; viewer feed in `control-server.ts`/`control-client.ts`; drain-viewer rendering + queued input in InteractiveMode; `lease_transferred` close reason emission; DaemonClient re-acquire + `session.reload()` on warm; auto workspace_register for TUI cwd.
995
+ **Accept**: §12.3.2 both directions; §12.3.6 reconnect; queued input lands in editor un-submitted; extension fixture observes `quit`→`resume` pairs across both handoff directions.
996
+
997
+ ### M8 — iOS deltas
998
+ **Files** (volt-app): per §10 table — `VoltSession+Prompting.swift`, `VoltSession+Connect.swift`, `WorkspaceEventStreams.swift`, `ConversationClosureLedger` file, `FailureClassification.swift`, `VoltSession+Transcript.swift` (test only), `IrohProtocol.swift` (D11 optional), comments in `VoltSession+HostActions.swift`.
999
+ **Accept**: VoltClient tests §12.4 pass; manual: abort leaves stream connected; TUI quit/open invisible except momentary reconnect.
1000
+
1001
+ ### M9 (optional) — `volt daemon install-service`
1002
+ **Files**: `src/daemon/cli.ts` (+ templates). **Accept**: generated launchd plist/systemd unit boots a working daemon after logout/login.
1003
+
1004
+ ### M10 — Documentation
1005
+ **Files**: README "Remote Access Preview" section (daemon model, no more `remote host`); `docs/usage.md` (`volt daemon` family, revised `volt remote`); `docs/security.md` (§11.3 tool-policy decision, in_use retirement); `docs/rpc.md` (extension UI: phones get no extension_ui_request for TUI-owned runtimes; theme rpc-mode statements superseded per §9.4); `docs/iroh-remote-access-design.md` (superseded banner pointing here); `docs/settings.md` (`remote.background`); new `docs/daemon.md` (file layout, CLI, lease model, troubleshooting, manual walkaway script); `docs/extensions.md` (handoff = quit+resume note).
1006
+ **Accept**: docs build (`docs.json` updated); no doc references `volt remote host` as a live command.
1007
+
1008
+ ### M11 (optional) — iOS theme token push (§9.5)
1009
+ **Accept**: flag off by default; app ignores frame safely when off/unrecognized.
1010
+
1011
+ ---
1012
+
1013
+ ## 14. Risks & Mitigations, Open Questions
1014
+
1015
+ ### 14.1 Risks
1016
+
1017
+ | Risk | Mitigation |
1018
+ |------|------------|
1019
+ | InteractiveMode edits regress the daemon-off path | §6.1 no-op façade rule; M5 acceptance requires byte-identical daemon-off behavior; keep all new code behind `daemonAttach` calls. |
1020
+ | Drain never converges (runaway turn) | Prompt-class rejection (§4.5) prevents new turns; abort is available and non-destructive; user can abort from phone or from the TUI drain overlay (add an "Abort remote turn" keybinding in drain-viewer sending abort via a temporary control channel — implement as `viewer` extension: `viewer_abort{viewerFeedId}` control request, daemon calls `session.abort()`; ADD this request to §5.3 catalog: `{ type:"viewer_abort"; id; viewerFeedId }` → `ok`). |
1021
+ | Relay byte-pipe hides protocol drift between daemon handshake and TUI serving | Handshake response construction shared (`handshake-responses.ts`); preamble carries verbatim handshake + resolved target; §12.2.3 transparency tests. |
1022
+ | Port of 3731-line host loses subtle behavior (replay, decoration, retention edge cases) | Parity milestone M3 before any behavior change (M4+); golden tests extracted from current behavior in M1; audit-event parity check (diff event-type sets old vs new). |
1023
+ | Daemon leaks runtimes (lease bookkeeping bugs) | Broker is sole authority with invariant asserts (runtime entry exists ⟺ state ∈ daemon-*); `volt daemon status` exposes the lease table; retention TTL is a backstop for detached runtimes. |
1024
+ | Version skew after `npm update` leaves an old daemon serving | §3.8 idle auto-restart + warning; `status` always reports both versions. |
1025
+ | Secret-key migration bug forces re-pairing | Byte-identical key assertion in migration tests; legacy file kept as `.migrated` for manual recovery. |
1026
+ | Two TUIs in the same workspace fight over one session | Explicit `held_by_tui` denial + read-only open; multi-TUI is a stated non-goal. |
1027
+ | Unix socket path length limits (104/108 bytes) with deep home dirs | Compute at startup; if too long, fall back to `$XDG_RUNTIME_DIR/voltd-<uid>.sock` (or `/tmp/voltd-<uid>/voltd.sock` mode 0700) and record the actual path in the pidfile, which clients read when the default probe fails. |
1028
+
1029
+ ### 14.2 Appendix A: iroh-host.mjs function → new home mapping (port checklist)
1030
+
1031
+ Every exported/major function in the dissolved script must land somewhere or be deliberately deleted. The implementer completes this table during M3 by grepping the script top-to-bottom; the known majors:
1032
+
1033
+ | iroh-host.mjs symbol (anchor) | Disposition |
1034
+ |---|---|
1035
+ | `serve()` (L3458-3639) | split: engine wiring → `daemon/main.ts`; per-stream routing → `control-server.ts` + `integrated-runtimes.ts` + `workspace-streams.ts` |
1036
+ | `runIntegratedVoltConnection` (L1497-1632) | `integrated-runtimes.ts` (daemon-owned serve) + §5.6 (relayed serve in TUI); option bundle preserved per §7.2 |
1037
+ | `invalidateStreamAfterAbortResponse` (L1556-1564) | **DELETED** (§7.4) |
1038
+ | `createIntegratedConversationHandshakeResponse` | `daemon/handshake-responses.ts` (shared) |
1039
+ | `decorateRemoteHostState` | `daemon/handshake-responses.ts` or sibling shared module (§5.6 step 9 note) |
1040
+ | `getIntegratedRuntimeRegistryKey` (L2298) | replaced by lease key `${workspaceName}\0${sessionId}` in `lease-broker.ts` |
1041
+ | `attachIntegratedRuntimeSubscriber` / detach (L2624-2682) | `integrated-runtimes.ts` |
1042
+ | `stopIntegratedRuntimeEntry` (L2684-2723) | `integrated-runtimes.ts` |
1043
+ | retention scheduling (L2741-2774) | `integrated-runtimes.ts` + existing `src/remote/integrated-runtime-retention.ts` |
1044
+ | `handleIntegratedRuntimeSessionChanged` (L2168-2179) | `integrated-runtimes.ts` → `leaseBroker.rekey` |
1045
+ | `handleIntegratedConversationRpcCommand` | `daemon/conversation-commands.ts` (shared daemon+TUI) |
1046
+ | `replayIntegratedRuntimeWorkflowEvents` / `handleIntegratedRuntimeWorkflowEvent` | `integrated-runtimes.ts` |
1047
+ | duplicate rejection (L2912-2939) | `lease-broker.ts` routing layer (§4.6), semantics preserved incl. retryAfterMs 500 + replace-stale |
1048
+ | `startPairControlServer` (L3357) | **DELETED**; replaced by `pair_request` control flow (§3.6) |
1049
+ | push dispatcher creation (`createPushNotificationDispatcher`) | daemon-side dispatcher in `integrated-runtimes.ts` reusing `core/remote/iroh/push.ts`; TUI relays forward via `relay_rpc` (§6.6) |
1050
+ | workspace/device-log stream serving | `workspace-streams.ts` on `workspace-rpc.ts` / `device-log-rpc.ts` |
1051
+ | audit emission sites | preserved 1:1; new events per §3.10 |
1052
+ | `cleanupUncommittedIntegratedRuntimeEntry` / `commitIntegratedRuntimeEntry` / `closeReplacedActiveStreams` / `registerActiveStream` | `integrated-runtimes.ts` on `active-stream-registry.ts` |
1053
+
1054
+ Acceptance for the checklist: an M3 review artifact (PR description table) lists every function ≥10 lines from the script with its disposition; "forgotten" is not a valid disposition.
1055
+
1056
+ ### 14.3 Appendix B: settings & config surface
1057
+
1058
+ `docs/settings.md` additions (M10):
1059
+
1060
+ | Setting | Type | Default | Effect |
1061
+ |---|---|---|---|
1062
+ | `remote.background` | boolean | `false` | Enables daemon auto-spawn at `volt` startup. On supported platforms the DaemonClient, lease integration, and auto workspace registration activate whenever any process starts the daemon. |
1063
+ | `remote.detachedRuntimeTtlMs` | number | 1800000 | Mirrors daemon `settings.detachedRuntimeTtlMs`; `volt daemon start` syncs CLI-side setting → daemon state on connect (daemon state is authoritative at runtime). |
1064
+ | `remote.allowTools` | string[] \| null | null | Tool allowlist for daemon-owned headless runtimes ONLY (§11.3). Ignored for TUI-owned co-attached conversations by design. |
1065
+
1066
+ `VoltdConfig` (daemon/main.ts):
1067
+
1068
+ ```ts
1069
+ export interface VoltdConfig {
1070
+ agentDir: string; // getAgentDir()
1071
+ socketPath?: string; // default computed; §14.1 fallback for long paths
1072
+ logPath?: string;
1073
+ foreground: boolean; // true under `volt daemon run --foreground`
1074
+ clock?: Clock; // injectable for tests
1075
+ }
1076
+ ```
1077
+
1078
+ Daemon log line format: `<iso8601> <level> <subsystem> <message> <json-details?>` — plain text, greppable, no JSON-only logging (audit.jsonl is the structured record).
1079
+
1080
+ ### 14.4 Open questions (minimized; defaults decided)
1081
+
1082
+ 1. **Should the TUI auto-register its cwd as a workspace?** Decided **yes** (§6.2) — otherwise G1 silently fails for unregistered dirs. Revisit only if name-collision UX proves confusing.
1083
+ 2. **Apply daemon `theme_snapshot` to a TUI that set its own theme?** Decided **no** (local explicit choice wins, §9.4).
1084
+ 3. **Drain viewer rendering fidelity** — full renderer reuse vs. minimal event log. Decided: best-effort reuse via `drain-viewer.ts`; truncation fallback to spinner (§6.3). Fidelity gaps are cosmetic and post-grant file load is authoritative.
1085
+ 4. **Retention TTL for daemon runtimes with zero phone history (created by TUI release)** — none exist: release goes straight to `unowned` with no runtime (§4.4). No question remains.
1086
+ 5. **launchd/systemd auto-install by default?** Deferred to M9 as opt-in `install-service`; auto-spawn-on-`volt`-start covers the primary UX.
1087
+
1088
+ ---
1089
+
1090
+ *End of RFC.*