@cuna_labs/cli 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 (624) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +9 -0
  3. package/README.md +169 -0
  4. package/THIRD_PARTY_NOTICES.md +29 -0
  5. package/dist/api/capability-evidence.d.ts +19 -0
  6. package/dist/api/capability-evidence.d.ts.map +1 -0
  7. package/dist/api/capability-evidence.js +38 -0
  8. package/dist/api/capability-evidence.js.map +1 -0
  9. package/dist/api/client.d.ts +167 -0
  10. package/dist/api/client.d.ts.map +1 -0
  11. package/dist/api/client.js +937 -0
  12. package/dist/api/client.js.map +1 -0
  13. package/dist/api/contracts.d.ts +345 -0
  14. package/dist/api/contracts.d.ts.map +1 -0
  15. package/dist/api/contracts.js +976 -0
  16. package/dist/api/contracts.js.map +1 -0
  17. package/dist/api/execution-workspaces.d.ts +17 -0
  18. package/dist/api/execution-workspaces.d.ts.map +1 -0
  19. package/dist/api/execution-workspaces.js +46 -0
  20. package/dist/api/execution-workspaces.js.map +1 -0
  21. package/dist/api/http.d.ts +76 -0
  22. package/dist/api/http.d.ts.map +1 -0
  23. package/dist/api/http.js +838 -0
  24. package/dist/api/http.js.map +1 -0
  25. package/dist/api/managed-executions.d.ts +37 -0
  26. package/dist/api/managed-executions.d.ts.map +1 -0
  27. package/dist/api/managed-executions.js +80 -0
  28. package/dist/api/managed-executions.js.map +1 -0
  29. package/dist/api/observer-v2-schema.d.ts +206 -0
  30. package/dist/api/observer-v2-schema.d.ts.map +1 -0
  31. package/dist/api/observer-v2-schema.js +264 -0
  32. package/dist/api/observer-v2-schema.js.map +1 -0
  33. package/dist/api/observer-v2.d.ts +33 -0
  34. package/dist/api/observer-v2.d.ts.map +1 -0
  35. package/dist/api/observer-v2.js +60 -0
  36. package/dist/api/observer-v2.js.map +1 -0
  37. package/dist/api/owner-observe-grants-v2-schema.d.ts +678 -0
  38. package/dist/api/owner-observe-grants-v2-schema.d.ts.map +1 -0
  39. package/dist/api/owner-observe-grants-v2-schema.js +887 -0
  40. package/dist/api/owner-observe-grants-v2-schema.js.map +1 -0
  41. package/dist/api/owner-observe-grants-v2.d.ts +268 -0
  42. package/dist/api/owner-observe-grants-v2.d.ts.map +1 -0
  43. package/dist/api/owner-observe-grants-v2.js +604 -0
  44. package/dist/api/owner-observe-grants-v2.js.map +1 -0
  45. package/dist/api/provider-v2-schema.d.ts +293 -0
  46. package/dist/api/provider-v2-schema.d.ts.map +1 -0
  47. package/dist/api/provider-v2-schema.js +381 -0
  48. package/dist/api/provider-v2-schema.js.map +1 -0
  49. package/dist/api/provider-v2.d.ts +55 -0
  50. package/dist/api/provider-v2.d.ts.map +1 -0
  51. package/dist/api/provider-v2.js +54 -0
  52. package/dist/api/provider-v2.js.map +1 -0
  53. package/dist/api/remote-workspace.d.ts +29 -0
  54. package/dist/api/remote-workspace.d.ts.map +1 -0
  55. package/dist/api/remote-workspace.js +62 -0
  56. package/dist/api/remote-workspace.js.map +1 -0
  57. package/dist/api/supervisor-live-update.d.ts +248 -0
  58. package/dist/api/supervisor-live-update.d.ts.map +1 -0
  59. package/dist/api/supervisor-live-update.js +434 -0
  60. package/dist/api/supervisor-live-update.js.map +1 -0
  61. package/dist/api/terminal-reason.d.ts +5 -0
  62. package/dist/api/terminal-reason.d.ts.map +1 -0
  63. package/dist/api/terminal-reason.js +12 -0
  64. package/dist/api/terminal-reason.js.map +1 -0
  65. package/dist/auth/browser-handoff.d.ts +59 -0
  66. package/dist/auth/browser-handoff.d.ts.map +1 -0
  67. package/dist/auth/browser-handoff.js +33 -0
  68. package/dist/auth/browser-handoff.js.map +1 -0
  69. package/dist/auth/browser.d.ts +11 -0
  70. package/dist/auth/browser.d.ts.map +1 -0
  71. package/dist/auth/browser.js +47 -0
  72. package/dist/auth/browser.js.map +1 -0
  73. package/dist/auth/human-client.d.ts +39 -0
  74. package/dist/auth/human-client.d.ts.map +1 -0
  75. package/dist/auth/human-client.js +66 -0
  76. package/dist/auth/human-client.js.map +1 -0
  77. package/dist/auth/human-contracts.d.ts +52 -0
  78. package/dist/auth/human-contracts.d.ts.map +1 -0
  79. package/dist/auth/human-contracts.js +229 -0
  80. package/dist/auth/human-contracts.js.map +1 -0
  81. package/dist/auth/human-session.d.ts +48 -0
  82. package/dist/auth/human-session.d.ts.map +1 -0
  83. package/dist/auth/human-session.js +902 -0
  84. package/dist/auth/human-session.js.map +1 -0
  85. package/dist/auth/onboarding-state.d.ts +31 -0
  86. package/dist/auth/onboarding-state.d.ts.map +1 -0
  87. package/dist/auth/onboarding-state.js +52 -0
  88. package/dist/auth/onboarding-state.js.map +1 -0
  89. package/dist/auth/pkce.d.ts +15 -0
  90. package/dist/auth/pkce.d.ts.map +1 -0
  91. package/dist/auth/pkce.js +19 -0
  92. package/dist/auth/pkce.js.map +1 -0
  93. package/dist/bin/cuna.d.ts +3 -0
  94. package/dist/bin/cuna.d.ts.map +1 -0
  95. package/dist/bin/cuna.js +4 -0
  96. package/dist/bin/cuna.js.map +1 -0
  97. package/dist/build-identity.d.ts +20 -0
  98. package/dist/build-identity.d.ts.map +1 -0
  99. package/dist/build-identity.js +77 -0
  100. package/dist/build-identity.js.map +1 -0
  101. package/dist/cli/command-help.d.ts +9 -0
  102. package/dist/cli/command-help.d.ts.map +1 -0
  103. package/dist/cli/command-help.js +324 -0
  104. package/dist/cli/command-help.js.map +1 -0
  105. package/dist/cli/help.d.ts +40 -0
  106. package/dist/cli/help.d.ts.map +1 -0
  107. package/dist/cli/help.js +256 -0
  108. package/dist/cli/help.js.map +1 -0
  109. package/dist/cli/output.d.ts +28 -0
  110. package/dist/cli/output.d.ts.map +1 -0
  111. package/dist/cli/output.js +95 -0
  112. package/dist/cli/output.js.map +1 -0
  113. package/dist/cli/parser.d.ts +65 -0
  114. package/dist/cli/parser.d.ts.map +1 -0
  115. package/dist/cli/parser.js +294 -0
  116. package/dist/cli/parser.js.map +1 -0
  117. package/dist/cli/process-entrypoint.d.ts +18 -0
  118. package/dist/cli/process-entrypoint.d.ts.map +1 -0
  119. package/dist/cli/process-entrypoint.js +26 -0
  120. package/dist/cli/process-entrypoint.js.map +1 -0
  121. package/dist/cli/run.d.ts +114 -0
  122. package/dist/cli/run.d.ts.map +1 -0
  123. package/dist/cli/run.js +1969 -0
  124. package/dist/cli/run.js.map +1 -0
  125. package/dist/commands/commands.d.ts +74 -0
  126. package/dist/commands/commands.d.ts.map +1 -0
  127. package/dist/commands/commands.js +2814 -0
  128. package/dist/commands/commands.js.map +1 -0
  129. package/dist/config/config.d.ts +86 -0
  130. package/dist/config/config.d.ts.map +1 -0
  131. package/dist/config/config.js +323 -0
  132. package/dist/config/config.js.map +1 -0
  133. package/dist/config/infra-contract-witness.d.ts +29 -0
  134. package/dist/config/infra-contract-witness.d.ts.map +1 -0
  135. package/dist/config/infra-contract-witness.js +30 -0
  136. package/dist/config/infra-contract-witness.js.map +1 -0
  137. package/dist/core/browser-url.d.ts +2 -0
  138. package/dist/core/browser-url.d.ts.map +1 -0
  139. package/dist/core/browser-url.js +20 -0
  140. package/dist/core/browser-url.js.map +1 -0
  141. package/dist/core/deployed-wire-compatibility.d.ts +15 -0
  142. package/dist/core/deployed-wire-compatibility.d.ts.map +1 -0
  143. package/dist/core/deployed-wire-compatibility.js +15 -0
  144. package/dist/core/deployed-wire-compatibility.js.map +1 -0
  145. package/dist/core/errors.d.ts +59 -0
  146. package/dist/core/errors.d.ts.map +1 -0
  147. package/dist/core/errors.js +82 -0
  148. package/dist/core/errors.js.map +1 -0
  149. package/dist/core/exit-codes.d.ts +68 -0
  150. package/dist/core/exit-codes.d.ts.map +1 -0
  151. package/dist/core/exit-codes.js +84 -0
  152. package/dist/core/exit-codes.js.map +1 -0
  153. package/dist/core/instant.d.ts +60 -0
  154. package/dist/core/instant.d.ts.map +1 -0
  155. package/dist/core/instant.js +90 -0
  156. package/dist/core/instant.js.map +1 -0
  157. package/dist/core/namespace.d.ts +171 -0
  158. package/dist/core/namespace.d.ts.map +1 -0
  159. package/dist/core/namespace.js +227 -0
  160. package/dist/core/namespace.js.map +1 -0
  161. package/dist/core/observation-budget.d.ts +185 -0
  162. package/dist/core/observation-budget.d.ts.map +1 -0
  163. package/dist/core/observation-budget.js +209 -0
  164. package/dist/core/observation-budget.js.map +1 -0
  165. package/dist/core/product-web.d.ts +81 -0
  166. package/dist/core/product-web.d.ts.map +1 -0
  167. package/dist/core/product-web.js +87 -0
  168. package/dist/core/product-web.js.map +1 -0
  169. package/dist/core/validation.d.ts +90 -0
  170. package/dist/core/validation.d.ts.map +1 -0
  171. package/dist/core/validation.js +199 -0
  172. package/dist/core/validation.js.map +1 -0
  173. package/dist/credentials/contracts.d.ts +82 -0
  174. package/dist/credentials/contracts.d.ts.map +1 -0
  175. package/dist/credentials/contracts.js +2 -0
  176. package/dist/credentials/contracts.js.map +1 -0
  177. package/dist/credentials/errors.d.ts +19 -0
  178. package/dist/credentials/errors.d.ts.map +1 -0
  179. package/dist/credentials/errors.js +16 -0
  180. package/dist/credentials/errors.js.map +1 -0
  181. package/dist/credentials/index.d.ts +6 -0
  182. package/dist/credentials/index.d.ts.map +1 -0
  183. package/dist/credentials/index.js +6 -0
  184. package/dist/credentials/index.js.map +1 -0
  185. package/dist/credentials/local-session.d.ts +101 -0
  186. package/dist/credentials/local-session.d.ts.map +1 -0
  187. package/dist/credentials/local-session.js +839 -0
  188. package/dist/credentials/local-session.js.map +1 -0
  189. package/dist/credentials/secret-material.d.ts +17 -0
  190. package/dist/credentials/secret-material.d.ts.map +1 -0
  191. package/dist/credentials/secret-material.js +49 -0
  192. package/dist/credentials/secret-material.js.map +1 -0
  193. package/dist/credentials/vault.d.ts +41 -0
  194. package/dist/credentials/vault.d.ts.map +1 -0
  195. package/dist/credentials/vault.js +719 -0
  196. package/dist/credentials/vault.js.map +1 -0
  197. package/dist/daemon/endpoint.d.ts +29 -0
  198. package/dist/daemon/endpoint.d.ts.map +1 -0
  199. package/dist/daemon/endpoint.js +66 -0
  200. package/dist/daemon/endpoint.js.map +1 -0
  201. package/dist/daemon/journal.d.ts +62 -0
  202. package/dist/daemon/journal.d.ts.map +1 -0
  203. package/dist/daemon/journal.js +299 -0
  204. package/dist/daemon/journal.js.map +1 -0
  205. package/dist/daemon/lease.d.ts +25 -0
  206. package/dist/daemon/lease.d.ts.map +1 -0
  207. package/dist/daemon/lease.js +94 -0
  208. package/dist/daemon/lease.js.map +1 -0
  209. package/dist/daemon/lifecycle.d.ts +14 -0
  210. package/dist/daemon/lifecycle.d.ts.map +1 -0
  211. package/dist/daemon/lifecycle.js +29 -0
  212. package/dist/daemon/lifecycle.js.map +1 -0
  213. package/dist/daemon/protocol.d.ts +65 -0
  214. package/dist/daemon/protocol.d.ts.map +1 -0
  215. package/dist/daemon/protocol.js +194 -0
  216. package/dist/daemon/protocol.js.map +1 -0
  217. package/dist/daemon/supervisor.d.ts +29 -0
  218. package/dist/daemon/supervisor.d.ts.map +1 -0
  219. package/dist/daemon/supervisor.js +87 -0
  220. package/dist/daemon/supervisor.js.map +1 -0
  221. package/dist/daemon/views.d.ts +31 -0
  222. package/dist/daemon/views.d.ts.map +1 -0
  223. package/dist/daemon/views.js +86 -0
  224. package/dist/daemon/views.js.map +1 -0
  225. package/dist/index.d.ts +38 -0
  226. package/dist/index.d.ts.map +1 -0
  227. package/dist/index.js +38 -0
  228. package/dist/index.js.map +1 -0
  229. package/dist/journey/api-effects.d.ts +59 -0
  230. package/dist/journey/api-effects.d.ts.map +1 -0
  231. package/dist/journey/api-effects.js +386 -0
  232. package/dist/journey/api-effects.js.map +1 -0
  233. package/dist/journey/derived-identity.d.ts +91 -0
  234. package/dist/journey/derived-identity.d.ts.map +1 -0
  235. package/dist/journey/derived-identity.js +123 -0
  236. package/dist/journey/derived-identity.js.map +1 -0
  237. package/dist/journey/index.d.ts +6 -0
  238. package/dist/journey/index.d.ts.map +1 -0
  239. package/dist/journey/index.js +6 -0
  240. package/dist/journey/index.js.map +1 -0
  241. package/dist/journey/intent.d.ts +36 -0
  242. package/dist/journey/intent.d.ts.map +1 -0
  243. package/dist/journey/intent.js +191 -0
  244. package/dist/journey/intent.js.map +1 -0
  245. package/dist/journey/orchestrator.d.ts +140 -0
  246. package/dist/journey/orchestrator.d.ts.map +1 -0
  247. package/dist/journey/orchestrator.js +415 -0
  248. package/dist/journey/orchestrator.js.map +1 -0
  249. package/dist/journey/provider-launch-intent.d.ts +12 -0
  250. package/dist/journey/provider-launch-intent.d.ts.map +1 -0
  251. package/dist/journey/provider-launch-intent.js +40 -0
  252. package/dist/journey/provider-launch-intent.js.map +1 -0
  253. package/dist/journey/remote-workspace.d.ts +34 -0
  254. package/dist/journey/remote-workspace.d.ts.map +1 -0
  255. package/dist/journey/remote-workspace.js +91 -0
  256. package/dist/journey/remote-workspace.js.map +1 -0
  257. package/dist/journey/root-entry.d.ts +56 -0
  258. package/dist/journey/root-entry.d.ts.map +1 -0
  259. package/dist/journey/root-entry.js +19 -0
  260. package/dist/journey/root-entry.js.map +1 -0
  261. package/dist/journey/selection.d.ts +165 -0
  262. package/dist/journey/selection.d.ts.map +1 -0
  263. package/dist/journey/selection.js +565 -0
  264. package/dist/journey/selection.js.map +1 -0
  265. package/dist/journey/session-failure.d.ts +4 -0
  266. package/dist/journey/session-failure.d.ts.map +1 -0
  267. package/dist/journey/session-failure.js +28 -0
  268. package/dist/journey/session-failure.js.map +1 -0
  269. package/dist/journey/workspace-effects.d.ts +30 -0
  270. package/dist/journey/workspace-effects.d.ts.map +1 -0
  271. package/dist/journey/workspace-effects.js +348 -0
  272. package/dist/journey/workspace-effects.js.map +1 -0
  273. package/dist/local-actions/broker.d.ts +32 -0
  274. package/dist/local-actions/broker.d.ts.map +1 -0
  275. package/dist/local-actions/broker.js +400 -0
  276. package/dist/local-actions/broker.js.map +1 -0
  277. package/dist/local-actions/browser-action.d.ts +78 -0
  278. package/dist/local-actions/browser-action.d.ts.map +1 -0
  279. package/dist/local-actions/browser-action.js +307 -0
  280. package/dist/local-actions/browser-action.js.map +1 -0
  281. package/dist/local-actions/callback-relay.d.ts +45 -0
  282. package/dist/local-actions/callback-relay.d.ts.map +1 -0
  283. package/dist/local-actions/callback-relay.js +265 -0
  284. package/dist/local-actions/callback-relay.js.map +1 -0
  285. package/dist/local-actions/clipboard.d.ts +3 -0
  286. package/dist/local-actions/clipboard.d.ts.map +1 -0
  287. package/dist/local-actions/clipboard.js +18 -0
  288. package/dist/local-actions/clipboard.js.map +1 -0
  289. package/dist/local-actions/contracts.d.ts +63 -0
  290. package/dist/local-actions/contracts.d.ts.map +1 -0
  291. package/dist/local-actions/contracts.js +18 -0
  292. package/dist/local-actions/contracts.js.map +1 -0
  293. package/dist/local-actions/desktop-presentation.d.ts +141 -0
  294. package/dist/local-actions/desktop-presentation.d.ts.map +1 -0
  295. package/dist/local-actions/desktop-presentation.js +253 -0
  296. package/dist/local-actions/desktop-presentation.js.map +1 -0
  297. package/dist/local-actions/device-selection.d.ts +43 -0
  298. package/dist/local-actions/device-selection.d.ts.map +1 -0
  299. package/dist/local-actions/device-selection.js +154 -0
  300. package/dist/local-actions/device-selection.js.map +1 -0
  301. package/dist/local-actions/executor.d.ts +49 -0
  302. package/dist/local-actions/executor.d.ts.map +1 -0
  303. package/dist/local-actions/executor.js +329 -0
  304. package/dist/local-actions/executor.js.map +1 -0
  305. package/dist/local-actions/file-transfers.d.ts +141 -0
  306. package/dist/local-actions/file-transfers.d.ts.map +1 -0
  307. package/dist/local-actions/file-transfers.js +446 -0
  308. package/dist/local-actions/file-transfers.js.map +1 -0
  309. package/dist/local-actions/foreground-notifications.d.ts +45 -0
  310. package/dist/local-actions/foreground-notifications.d.ts.map +1 -0
  311. package/dist/local-actions/foreground-notifications.js +163 -0
  312. package/dist/local-actions/foreground-notifications.js.map +1 -0
  313. package/dist/local-actions/git-signing.d.ts +39 -0
  314. package/dist/local-actions/git-signing.d.ts.map +1 -0
  315. package/dist/local-actions/git-signing.js +135 -0
  316. package/dist/local-actions/git-signing.js.map +1 -0
  317. package/dist/local-actions/index.d.ts +19 -0
  318. package/dist/local-actions/index.d.ts.map +1 -0
  319. package/dist/local-actions/index.js +19 -0
  320. package/dist/local-actions/index.js.map +1 -0
  321. package/dist/local-actions/local-service-actions.d.ts +87 -0
  322. package/dist/local-actions/local-service-actions.d.ts.map +1 -0
  323. package/dist/local-actions/local-service-actions.js +347 -0
  324. package/dist/local-actions/local-service-actions.js.map +1 -0
  325. package/dist/local-actions/loopback.d.ts +5 -0
  326. package/dist/local-actions/loopback.d.ts.map +1 -0
  327. package/dist/local-actions/loopback.js +15 -0
  328. package/dist/local-actions/loopback.js.map +1 -0
  329. package/dist/local-actions/policy-store.d.ts +7 -0
  330. package/dist/local-actions/policy-store.d.ts.map +1 -0
  331. package/dist/local-actions/policy-store.js +70 -0
  332. package/dist/local-actions/policy-store.js.map +1 -0
  333. package/dist/local-actions/policy.d.ts +21 -0
  334. package/dist/local-actions/policy.d.ts.map +1 -0
  335. package/dist/local-actions/policy.js +43 -0
  336. package/dist/local-actions/policy.js.map +1 -0
  337. package/dist/local-actions/private-port-forward.d.ts +36 -0
  338. package/dist/local-actions/private-port-forward.d.ts.map +1 -0
  339. package/dist/local-actions/private-port-forward.js +154 -0
  340. package/dist/local-actions/private-port-forward.js.map +1 -0
  341. package/dist/local-actions/provider-mcp-config.d.ts +101 -0
  342. package/dist/local-actions/provider-mcp-config.d.ts.map +1 -0
  343. package/dist/local-actions/provider-mcp-config.js +166 -0
  344. package/dist/local-actions/provider-mcp-config.js.map +1 -0
  345. package/dist/local-actions/providers.d.ts +12 -0
  346. package/dist/local-actions/providers.d.ts.map +1 -0
  347. package/dist/local-actions/providers.js +42 -0
  348. package/dist/local-actions/providers.js.map +1 -0
  349. package/dist/local-actions/schemas.d.ts +4 -0
  350. package/dist/local-actions/schemas.d.ts.map +1 -0
  351. package/dist/local-actions/schemas.js +314 -0
  352. package/dist/local-actions/schemas.js.map +1 -0
  353. package/dist/local-actions/sensitive-consent.d.ts +32 -0
  354. package/dist/local-actions/sensitive-consent.d.ts.map +1 -0
  355. package/dist/local-actions/sensitive-consent.js +47 -0
  356. package/dist/local-actions/sensitive-consent.js.map +1 -0
  357. package/dist/machines/command-launch.d.ts +17 -0
  358. package/dist/machines/command-launch.d.ts.map +1 -0
  359. package/dist/machines/command-launch.js +52 -0
  360. package/dist/machines/command-launch.js.map +1 -0
  361. package/dist/machines/execution-receipt.d.ts +18 -0
  362. package/dist/machines/execution-receipt.d.ts.map +1 -0
  363. package/dist/machines/execution-receipt.js +87 -0
  364. package/dist/machines/execution-receipt.js.map +1 -0
  365. package/dist/machines/executions-screen.d.ts +6 -0
  366. package/dist/machines/executions-screen.d.ts.map +1 -0
  367. package/dist/machines/executions-screen.js +410 -0
  368. package/dist/machines/executions-screen.js.map +1 -0
  369. package/dist/machines/explorer.d.ts +75 -0
  370. package/dist/machines/explorer.d.ts.map +1 -0
  371. package/dist/machines/explorer.js +1726 -0
  372. package/dist/machines/explorer.js.map +1 -0
  373. package/dist/machines/live-supervisor-update.d.ts +325 -0
  374. package/dist/machines/live-supervisor-update.d.ts.map +1 -0
  375. package/dist/machines/live-supervisor-update.js +513 -0
  376. package/dist/machines/live-supervisor-update.js.map +1 -0
  377. package/dist/machines/machine-first.d.ts +123 -0
  378. package/dist/machines/machine-first.d.ts.map +1 -0
  379. package/dist/machines/machine-first.js +119 -0
  380. package/dist/machines/machine-first.js.map +1 -0
  381. package/dist/machines/opencode-supervisor.d.ts +51 -0
  382. package/dist/machines/opencode-supervisor.d.ts.map +1 -0
  383. package/dist/machines/opencode-supervisor.js +124 -0
  384. package/dist/machines/opencode-supervisor.js.map +1 -0
  385. package/dist/machines/pagination.d.ts +4 -0
  386. package/dist/machines/pagination.d.ts.map +1 -0
  387. package/dist/machines/pagination.js +7 -0
  388. package/dist/machines/pagination.js.map +1 -0
  389. package/dist/machines/provider-availability.d.ts +42 -0
  390. package/dist/machines/provider-availability.d.ts.map +1 -0
  391. package/dist/machines/provider-availability.js +82 -0
  392. package/dist/machines/provider-availability.js.map +1 -0
  393. package/dist/machines/provider-screen.d.ts +12 -0
  394. package/dist/machines/provider-screen.d.ts.map +1 -0
  395. package/dist/machines/provider-screen.js +129 -0
  396. package/dist/machines/provider-screen.js.map +1 -0
  397. package/dist/machines/session-actionability.d.ts +86 -0
  398. package/dist/machines/session-actionability.d.ts.map +1 -0
  399. package/dist/machines/session-actionability.js +125 -0
  400. package/dist/machines/session-actionability.js.map +1 -0
  401. package/dist/machines/session-visibility.d.ts +139 -0
  402. package/dist/machines/session-visibility.d.ts.map +1 -0
  403. package/dist/machines/session-visibility.js +109 -0
  404. package/dist/machines/session-visibility.js.map +1 -0
  405. package/dist/platform/adapter.d.ts +62 -0
  406. package/dist/platform/adapter.d.ts.map +1 -0
  407. package/dist/platform/adapter.js +240 -0
  408. package/dist/platform/adapter.js.map +1 -0
  409. package/dist/platform/support.d.ts +12 -0
  410. package/dist/platform/support.d.ts.map +1 -0
  411. package/dist/platform/support.js +25 -0
  412. package/dist/platform/support.js.map +1 -0
  413. package/dist/pty/contracts.d.ts +45 -0
  414. package/dist/pty/contracts.d.ts.map +1 -0
  415. package/dist/pty/contracts.js +2 -0
  416. package/dist/pty/contracts.js.map +1 -0
  417. package/dist/pty/evidence-gate.d.ts +14 -0
  418. package/dist/pty/evidence-gate.d.ts.map +1 -0
  419. package/dist/pty/evidence-gate.js +35 -0
  420. package/dist/pty/evidence-gate.js.map +1 -0
  421. package/dist/pty/index.d.ts +5 -0
  422. package/dist/pty/index.d.ts.map +1 -0
  423. package/dist/pty/index.js +5 -0
  424. package/dist/pty/index.js.map +1 -0
  425. package/dist/pty/node-host-terminal.d.ts +14 -0
  426. package/dist/pty/node-host-terminal.d.ts.map +1 -0
  427. package/dist/pty/node-host-terminal.js +173 -0
  428. package/dist/pty/node-host-terminal.js.map +1 -0
  429. package/dist/pty/node-process.d.ts +26 -0
  430. package/dist/pty/node-process.d.ts.map +1 -0
  431. package/dist/pty/node-process.js +102 -0
  432. package/dist/pty/node-process.js.map +1 -0
  433. package/dist/runtime/api-terminal-control-plane.d.ts +7 -0
  434. package/dist/runtime/api-terminal-control-plane.d.ts.map +1 -0
  435. package/dist/runtime/api-terminal-control-plane.js +95 -0
  436. package/dist/runtime/api-terminal-control-plane.js.map +1 -0
  437. package/dist/runtime/backoff.d.ts +17 -0
  438. package/dist/runtime/backoff.d.ts.map +1 -0
  439. package/dist/runtime/backoff.js +61 -0
  440. package/dist/runtime/backoff.js.map +1 -0
  441. package/dist/runtime/boundary.d.ts +171 -0
  442. package/dist/runtime/boundary.d.ts.map +1 -0
  443. package/dist/runtime/boundary.js +1858 -0
  444. package/dist/runtime/boundary.js.map +1 -0
  445. package/dist/runtime/capability-gate.d.ts +25 -0
  446. package/dist/runtime/capability-gate.d.ts.map +1 -0
  447. package/dist/runtime/capability-gate.js +77 -0
  448. package/dist/runtime/capability-gate.js.map +1 -0
  449. package/dist/runtime/contracts.d.ts +25 -0
  450. package/dist/runtime/contracts.d.ts.map +1 -0
  451. package/dist/runtime/contracts.js +66 -0
  452. package/dist/runtime/contracts.js.map +1 -0
  453. package/dist/runtime/errors.d.ts +20 -0
  454. package/dist/runtime/errors.d.ts.map +1 -0
  455. package/dist/runtime/errors.js +22 -0
  456. package/dist/runtime/errors.js.map +1 -0
  457. package/dist/runtime/index.d.ts +9 -0
  458. package/dist/runtime/index.d.ts.map +1 -0
  459. package/dist/runtime/index.js +9 -0
  460. package/dist/runtime/index.js.map +1 -0
  461. package/dist/runtime/node-foreground-session.d.ts +50 -0
  462. package/dist/runtime/node-foreground-session.d.ts.map +1 -0
  463. package/dist/runtime/node-foreground-session.js +431 -0
  464. package/dist/runtime/node-foreground-session.js.map +1 -0
  465. package/dist/runtime/node-websocket-connector.d.ts +6 -0
  466. package/dist/runtime/node-websocket-connector.d.ts.map +1 -0
  467. package/dist/runtime/node-websocket-connector.js +344 -0
  468. package/dist/runtime/node-websocket-connector.js.map +1 -0
  469. package/dist/runtime/observer-screen.d.ts +6 -0
  470. package/dist/runtime/observer-screen.d.ts.map +1 -0
  471. package/dist/runtime/observer-screen.js +144 -0
  472. package/dist/runtime/observer-screen.js.map +1 -0
  473. package/dist/runtime/owner-grant-operations.d.ts +136 -0
  474. package/dist/runtime/owner-grant-operations.d.ts.map +1 -0
  475. package/dist/runtime/owner-grant-operations.js +216 -0
  476. package/dist/runtime/owner-grant-operations.js.map +1 -0
  477. package/dist/runtime/owner-grants-screen.d.ts +40 -0
  478. package/dist/runtime/owner-grants-screen.d.ts.map +1 -0
  479. package/dist/runtime/owner-grants-screen.js +987 -0
  480. package/dist/runtime/owner-grants-screen.js.map +1 -0
  481. package/dist/runtime/terminal-transport.d.ts +85 -0
  482. package/dist/runtime/terminal-transport.d.ts.map +1 -0
  483. package/dist/runtime/terminal-transport.js +152 -0
  484. package/dist/runtime/terminal-transport.js.map +1 -0
  485. package/dist/sync/continuous-sync-supervisor.d.ts +107 -0
  486. package/dist/sync/continuous-sync-supervisor.d.ts.map +1 -0
  487. package/dist/sync/continuous-sync-supervisor.js +1117 -0
  488. package/dist/sync/continuous-sync-supervisor.js.map +1 -0
  489. package/dist/sync/index.d.ts +9 -0
  490. package/dist/sync/index.d.ts.map +1 -0
  491. package/dist/sync/index.js +9 -0
  492. package/dist/sync/index.js.map +1 -0
  493. package/dist/sync/journal.d.ts +57 -0
  494. package/dist/sync/journal.d.ts.map +1 -0
  495. package/dist/sync/journal.js +685 -0
  496. package/dist/sync/journal.js.map +1 -0
  497. package/dist/sync/revisions.d.ts +118 -0
  498. package/dist/sync/revisions.d.ts.map +1 -0
  499. package/dist/sync/revisions.js +364 -0
  500. package/dist/sync/revisions.js.map +1 -0
  501. package/dist/sync/supervisor.d.ts +107 -0
  502. package/dist/sync/supervisor.d.ts.map +1 -0
  503. package/dist/sync/supervisor.js +246 -0
  504. package/dist/sync/supervisor.js.map +1 -0
  505. package/dist/sync/workspace-sync-client.d.ts +18 -0
  506. package/dist/sync/workspace-sync-client.d.ts.map +1 -0
  507. package/dist/sync/workspace-sync-client.js +150 -0
  508. package/dist/sync/workspace-sync-client.js.map +1 -0
  509. package/dist/sync/workspace-sync-coordinator.d.ts +76 -0
  510. package/dist/sync/workspace-sync-coordinator.d.ts.map +1 -0
  511. package/dist/sync/workspace-sync-coordinator.js +621 -0
  512. package/dist/sync/workspace-sync-coordinator.js.map +1 -0
  513. package/dist/sync/workspace-sync-product-service.d.ts +79 -0
  514. package/dist/sync/workspace-sync-product-service.d.ts.map +1 -0
  515. package/dist/sync/workspace-sync-product-service.js +398 -0
  516. package/dist/sync/workspace-sync-product-service.js.map +1 -0
  517. package/dist/sync/workspace-sync-protocol.d.ts +145 -0
  518. package/dist/sync/workspace-sync-protocol.d.ts.map +1 -0
  519. package/dist/sync/workspace-sync-protocol.js +347 -0
  520. package/dist/sync/workspace-sync-protocol.js.map +1 -0
  521. package/dist/terminal/appbar.d.ts +40 -0
  522. package/dist/terminal/appbar.d.ts.map +1 -0
  523. package/dist/terminal/appbar.js +55 -0
  524. package/dist/terminal/appbar.js.map +1 -0
  525. package/dist/terminal/cell-width.d.ts +3 -0
  526. package/dist/terminal/cell-width.d.ts.map +1 -0
  527. package/dist/terminal/cell-width.js +53 -0
  528. package/dist/terminal/cell-width.js.map +1 -0
  529. package/dist/terminal/codec.d.ts +107 -0
  530. package/dist/terminal/codec.d.ts.map +1 -0
  531. package/dist/terminal/codec.js +538 -0
  532. package/dist/terminal/codec.js.map +1 -0
  533. package/dist/terminal/foreground.d.ts +135 -0
  534. package/dist/terminal/foreground.d.ts.map +1 -0
  535. package/dist/terminal/foreground.js +2097 -0
  536. package/dist/terminal/foreground.js.map +1 -0
  537. package/dist/terminal/generated/terminal-wire-v1.d.ts +26 -0
  538. package/dist/terminal/generated/terminal-wire-v1.d.ts.map +1 -0
  539. package/dist/terminal/generated/terminal-wire-v1.js +27 -0
  540. package/dist/terminal/generated/terminal-wire-v1.js.map +1 -0
  541. package/dist/terminal/mode.d.ts +33 -0
  542. package/dist/terminal/mode.d.ts.map +1 -0
  543. package/dist/terminal/mode.js +99 -0
  544. package/dist/terminal/mode.js.map +1 -0
  545. package/dist/terminal/passthrough.d.ts +47 -0
  546. package/dist/terminal/passthrough.d.ts.map +1 -0
  547. package/dist/terminal/passthrough.js +666 -0
  548. package/dist/terminal/passthrough.js.map +1 -0
  549. package/dist/terminal/resume.d.ts +58 -0
  550. package/dist/terminal/resume.d.ts.map +1 -0
  551. package/dist/terminal/resume.js +185 -0
  552. package/dist/terminal/resume.js.map +1 -0
  553. package/dist/terminal/viewport.d.ts +93 -0
  554. package/dist/terminal/viewport.d.ts.map +1 -0
  555. package/dist/terminal/viewport.js +266 -0
  556. package/dist/terminal/viewport.js.map +1 -0
  557. package/dist/terminal/workbench.d.ts +32 -0
  558. package/dist/terminal/workbench.d.ts.map +1 -0
  559. package/dist/terminal/workbench.js +316 -0
  560. package/dist/terminal/workbench.js.map +1 -0
  561. package/dist/terminal/xterm-vte.d.ts +49 -0
  562. package/dist/terminal/xterm-vte.d.ts.map +1 -0
  563. package/dist/terminal/xterm-vte.js +602 -0
  564. package/dist/terminal/xterm-vte.js.map +1 -0
  565. package/dist/version.d.ts +3 -0
  566. package/dist/version.d.ts.map +1 -0
  567. package/dist/version.js +3 -0
  568. package/dist/version.js.map +1 -0
  569. package/dist/workspace/binding-path.d.ts +17 -0
  570. package/dist/workspace/binding-path.d.ts.map +1 -0
  571. package/dist/workspace/binding-path.js +91 -0
  572. package/dist/workspace/binding-path.js.map +1 -0
  573. package/dist/workspace/binding-store.d.ts +107 -0
  574. package/dist/workspace/binding-store.d.ts.map +1 -0
  575. package/dist/workspace/binding-store.js +783 -0
  576. package/dist/workspace/binding-store.js.map +1 -0
  577. package/dist/workspace/binding.d.ts +35 -0
  578. package/dist/workspace/binding.d.ts.map +1 -0
  579. package/dist/workspace/binding.js +70 -0
  580. package/dist/workspace/binding.js.map +1 -0
  581. package/dist/workspace/errors.d.ts +4 -0
  582. package/dist/workspace/errors.d.ts.map +1 -0
  583. package/dist/workspace/errors.js +19 -0
  584. package/dist/workspace/errors.js.map +1 -0
  585. package/dist/workspace/exclusion.d.ts +21 -0
  586. package/dist/workspace/exclusion.d.ts.map +1 -0
  587. package/dist/workspace/exclusion.js +243 -0
  588. package/dist/workspace/exclusion.js.map +1 -0
  589. package/dist/workspace/index.d.ts +8 -0
  590. package/dist/workspace/index.d.ts.map +1 -0
  591. package/dist/workspace/index.js +8 -0
  592. package/dist/workspace/index.js.map +1 -0
  593. package/dist/workspace/lifecycle.d.ts +56 -0
  594. package/dist/workspace/lifecycle.d.ts.map +1 -0
  595. package/dist/workspace/lifecycle.js +94 -0
  596. package/dist/workspace/lifecycle.js.map +1 -0
  597. package/dist/workspace/manifest.d.ts +47 -0
  598. package/dist/workspace/manifest.d.ts.map +1 -0
  599. package/dist/workspace/manifest.js +243 -0
  600. package/dist/workspace/manifest.js.map +1 -0
  601. package/dist/workspace/paths.d.ts +14 -0
  602. package/dist/workspace/paths.d.ts.map +1 -0
  603. package/dist/workspace/paths.js +67 -0
  604. package/dist/workspace/paths.js.map +1 -0
  605. package/dist/workspace/schema.d.ts +15 -0
  606. package/dist/workspace/schema.d.ts.map +1 -0
  607. package/dist/workspace/schema.js +33 -0
  608. package/dist/workspace/schema.js.map +1 -0
  609. package/dist/workspace/selection-screen.d.ts +4 -0
  610. package/dist/workspace/selection-screen.d.ts.map +1 -0
  611. package/dist/workspace/selection-screen.js +216 -0
  612. package/dist/workspace/selection-screen.js.map +1 -0
  613. package/dist/workspace/selection-service.d.ts +16 -0
  614. package/dist/workspace/selection-service.d.ts.map +1 -0
  615. package/dist/workspace/selection-service.js +65 -0
  616. package/dist/workspace/selection-service.js.map +1 -0
  617. package/node_modules/@xterm/headless/README.md +31 -0
  618. package/node_modules/@xterm/headless/lib-headless/xterm-headless.js +2 -0
  619. package/node_modules/@xterm/headless/lib-headless/xterm-headless.js.map +1 -0
  620. package/node_modules/@xterm/headless/lib-headless/xterm-headless.mjs +46 -0
  621. package/node_modules/@xterm/headless/lib-headless/xterm-headless.mjs.map +7 -0
  622. package/node_modules/@xterm/headless/package.json +29 -0
  623. package/node_modules/@xterm/headless/typings/xterm-headless.d.ts +1385 -0
  624. package/package.json +75 -0
@@ -0,0 +1,1726 @@
1
+ import { decideCapability } from "../api/client.js";
2
+ import { sanitizeHumanTerminalOutput } from "../cli/output.js";
3
+ import { CunaError } from "../core/errors.js";
4
+ import { OBSERVATION_BUDGET_CODES } from "../core/observation-budget.js";
5
+ import { CredentialBoundaryError } from "../credentials/errors.js";
6
+ import { createNodeForegroundTerminalHost } from "../pty/node-host-terminal.js";
7
+ import { reconnectDelay } from "../runtime/backoff.js";
8
+ import { terminalCellWidth, truncateTerminalLine } from "../terminal/cell-width.js";
9
+ import { listAllMachines } from "./pagination.js";
10
+ import { isOpenCodeRuntimeUnverifiedReason, isOpenCodeSupervisorProtocolUnavailableReason, isOpenCodeSupervisorUpgradeRequiredReason, } from "./opencode-supervisor.js";
11
+ import { machineProviderAvailability, providerDisplayName, providerVerdict } from "./provider-availability.js";
12
+ import { classifySessionActionability, displaySessionActionability } from "./session-actionability.js";
13
+ import { isAgentSessionIntendedActive } from "./session-visibility.js";
14
+ import { INITIAL_MACHINE_FIRST_STATE, reduceMachineFirstNavigation, resolveMachineContextActions, resolveProviderContextActions, } from "./machine-first.js";
15
+ const encoder = new TextEncoder();
16
+ const SPINNER = Object.freeze(["◐", "◓", "◑", "◒"]);
17
+ const PROGRESS = Object.freeze(["━╺━━━━", "━━╺━━━", "━━━╺━━", "━━━━╺━", "━━━━━╺", "━━━━╸━", "━━━╸━━", "━━╸━━━"]);
18
+ const LIVE_REFRESH_MS = 10_000;
19
+ const ESCAPE_SEQUENCE_TIMEOUT_MS = 150;
20
+ const CLOSE_FRAME_MS = 90;
21
+ const CLOSE_FRAMES = Object.freeze(["✦ Closing Cuna...", "✧ Closing Cuna...", "✓ Closed."]);
22
+ /**
23
+ * PRD-PM-008 E13-R3. A lifecycle request that outlives the per-request
24
+ * response budget is not an error inside the screen: the Machine usually
25
+ * did start. Read it back until the state converges or this budget lapses.
26
+ */
27
+ const LIFECYCLE_CONVERGENCE_BUDGET_MS = 180_000;
28
+ const LIFECYCLE_POLL_INTERVAL_MS = 2_500;
29
+ const MACHINE_NAME_MAX_LENGTH = 80;
30
+ /**
31
+ * PRD-PM-008 E13-R7. A retryable machine-list failure (a 5xx while the edge
32
+ * redeploys, a transport failure) keeps the screen alive and is retried with
33
+ * the runtime's backoff until this window lapses; only then is the notice
34
+ * final and `r` the only retry. 1 s, 2 s, 4 s, 8 s, 8 s fits inside 30 s.
35
+ */
36
+ const LIST_RETRY_WINDOW_MS = 30_000;
37
+ const LIST_RETRY_POLICY = Object.freeze({
38
+ maximumAttempts: 5,
39
+ maximumElapsedMs: LIST_RETRY_WINDOW_MS,
40
+ initialDelayMs: 1_000,
41
+ maximumDelayMs: 8_000,
42
+ jitterRatio: 0.2,
43
+ });
44
+ const ANSI = Object.freeze({
45
+ reset: "\u001b[0m",
46
+ bold: "\u001b[1m",
47
+ dim: "\u001b[2m",
48
+ ground: "\u001b[38;5;232m",
49
+ orange: "\u001b[38;5;208m",
50
+ flareBackground: "\u001b[48;5;202m",
51
+ emberBackground: "\u001b[48;5;52m",
52
+ cream: "\u001b[38;5;223m",
53
+ green: "\u001b[38;5;42m",
54
+ red: "\u001b[38;5;196m",
55
+ cyan: "\u001b[38;5;45m",
56
+ gray: "\u001b[38;5;245m",
57
+ });
58
+ export async function runNodeMachinesExplorer(input, dependencies = {}) {
59
+ if (input.signal?.aborted)
60
+ return;
61
+ const host = dependencies.host ?? createNodeForegroundTerminalHost();
62
+ input.onBeforeTerminalOwnership?.();
63
+ const lease = await host.acquire("rich");
64
+ if (input.signal?.aborted) {
65
+ await lease.restore();
66
+ return;
67
+ }
68
+ let rows = [];
69
+ let selectedKey;
70
+ let paintedSelectionKeys = [];
71
+ let loadingPhase = "machines";
72
+ // A rendered inventory is one completed observation. Keep evaluating that
73
+ // snapshot at the time it was received while a slower refresh is in flight;
74
+ // otherwise the same cached lease can appear to expire and then revive when
75
+ // the renewed response arrives.
76
+ let snapshotObservedAt = dependencies.now?.() ?? Date.now();
77
+ let animationFrame = 0;
78
+ let stopped = false;
79
+ let refreshInFlight = false;
80
+ /**
81
+ * E13-R7. The one-line notice for a failed machine list. It carries the
82
+ * typed reason and says whether Cuna is still retrying by itself.
83
+ */
84
+ let refreshError;
85
+ /** The current outage: when it began and how many list attempts failed in it. */
86
+ let listFailure;
87
+ let listRetryTimer;
88
+ const listRetryPolicy = dependencies.listRetry?.policy ?? LIST_RETRY_POLICY;
89
+ const listRetryWindowMs = dependencies.listRetry?.windowMs ?? LIST_RETRY_WINDOW_MS;
90
+ const listRetryRandom = dependencies.listRetry?.random ?? Math.random;
91
+ let interactionNotice;
92
+ /**
93
+ * Outcome of an in-place lifecycle action. Kept apart from
94
+ * `interactionNotice` because a refresh clears that one, and the typed
95
+ * budget notice must outlive the automatic refresh that follows it.
96
+ */
97
+ let lifecycleNotice;
98
+ let pendingSupervisorUpdateMachineId;
99
+ let pendingDelete;
100
+ const deleteFingerprint = (row) => JSON.stringify([row.machine.id, row.machine.name, row.machine.state,
101
+ row.sessions.map(session => [session.id, session.processEpoch]).sort((a, b) => String(a[0]).localeCompare(String(b[0])))]);
102
+ let newMachine = Object.freeze({ capability: "checking" });
103
+ let newMachineName = "";
104
+ let closingNotice;
105
+ const pollIntervalMs = dependencies.convergence?.pollIntervalMs ?? LIFECYCLE_POLL_INTERVAL_MS;
106
+ const convergenceBudgetMs = dependencies.convergence?.budgetMs ?? LIFECYCLE_CONVERGENCE_BUDGET_MS;
107
+ let failure;
108
+ const lifetimeAbort = new AbortController();
109
+ const requestSignal = input.signal === undefined
110
+ ? lifetimeAbort.signal
111
+ : AbortSignal.any([input.signal, lifetimeAbort.signal]);
112
+ const expanded = new Set();
113
+ let finish;
114
+ const done = new Promise((resolve) => { finish = resolve; });
115
+ let renderQueue = Promise.resolve();
116
+ let renderInFlight = false;
117
+ let renderRequested = false;
118
+ let firstRender = true;
119
+ let previousLineCount = 0;
120
+ let previousColumns;
121
+ let previousRows;
122
+ let inputSequence = "none";
123
+ let escapeTimer;
124
+ let initialized = false;
125
+ let selection;
126
+ let navigation = INITIAL_MACHINE_FIRST_STATE;
127
+ // A local exit fences every in-flight read with this controller before the
128
+ // close animation has rendered its first frame. Treat that fence itself as
129
+ // closing state: otherwise an aborted capability read can win the tiny
130
+ // interval before `closingNotice` is assigned and leak a misleading
131
+ // `cuna.network.cancelled` error after a normal q/Ctrl-C exit.
132
+ const isClosing = () => stopped || closingNotice !== undefined || lifetimeAbort.signal.aborted;
133
+ const stop = () => {
134
+ if (stopped)
135
+ return;
136
+ stopped = true;
137
+ lifetimeAbort.abort(new Error("Machines explorer detached."));
138
+ finish();
139
+ };
140
+ const render = () => {
141
+ if (stopped)
142
+ return;
143
+ if (pendingDelete !== undefined) {
144
+ const screen = navigation.screen;
145
+ const target = screen.kind === "machine" ? rows.find(row => row.machine.id === screen.machineId) : undefined;
146
+ if (target === undefined || selectedKey !== "machine-lifecycle:delete" || deleteFingerprint(target) !== pendingDelete.fingerprint)
147
+ pendingDelete = undefined;
148
+ }
149
+ renderRequested = true;
150
+ if (renderInFlight)
151
+ return;
152
+ renderInFlight = true;
153
+ renderQueue = renderQueue
154
+ .then(async () => {
155
+ while (renderRequested && !stopped) {
156
+ renderRequested = false;
157
+ const { columns, rows: terminalRows } = host.dimensions();
158
+ const frame = renderMachinesExplorer({
159
+ rows,
160
+ ...(selectedKey === undefined ? {} : { selectedKey }),
161
+ expanded,
162
+ loadingPhase,
163
+ animationFrame,
164
+ columns,
165
+ now: snapshotObservedAt,
166
+ navigation,
167
+ machinesListed: initialized,
168
+ refreshError,
169
+ interactionNotice: pendingDelete?.notice ?? interactionNotice,
170
+ lifecycleNotice,
171
+ closingNotice,
172
+ newMachine,
173
+ newMachineName,
174
+ });
175
+ const frameSelectionKeys = selectableKeys(rows, expanded, snapshotObservedAt, navigation, initialized);
176
+ const visible = selectVisibleLines(frame, terminalRows);
177
+ const painted = paintMachinesExplorer(visible, columns, input.color ?? false);
178
+ const sizeChanged = previousColumns !== undefined && (previousColumns !== columns || previousRows !== terminalRows);
179
+ const linesToClear = sizeChanged ? 0 : Math.max(0, previousLineCount - painted.length);
180
+ const outputLines = [...painted, ...Array.from({ length: linesToClear }, () => "")];
181
+ const initialClear = firstRender || sizeChanged ? "\u001b[2J" : "";
182
+ firstRender = false;
183
+ previousLineCount = painted.length;
184
+ previousColumns = columns;
185
+ previousRows = terminalRows;
186
+ await host.write(encoder.encode(`\u001b[?2026h\u001b[?25l${initialClear}\u001b[H${outputLines.map((line) => `${line}\u001b[K`).join("\r\n")}\u001b[?2026l`));
187
+ paintedSelectionKeys = frameSelectionKeys;
188
+ }
189
+ })
190
+ .catch((error) => {
191
+ failure ??= error;
192
+ stop();
193
+ })
194
+ .finally(() => {
195
+ renderInFlight = false;
196
+ // A state change can request a repaint after the render loop observes
197
+ // `renderRequested === false` but before this finalizer releases the
198
+ // in-flight flag. Do not strand that final frame (notably the
199
+ // zero-machine actions that replace the initial loading frame).
200
+ if (renderRequested && !stopped)
201
+ render();
202
+ });
203
+ };
204
+ const exitWithFeedback = async () => {
205
+ if (stopped || closingNotice !== undefined)
206
+ return;
207
+ loadingPhase = undefined;
208
+ // Closing is immediate from the product's point of view: cancel remote
209
+ // reads before spending the short grace period on visual feedback. A
210
+ // transport that ignores AbortSignal is still fenced by closingNotice at
211
+ // every post-await effect boundary below.
212
+ lifetimeAbort.abort(new Error("Machines explorer is closing."));
213
+ for (const [index, notice] of CLOSE_FRAMES.entries()) {
214
+ if (stopped)
215
+ return;
216
+ closingNotice = notice;
217
+ render();
218
+ await renderQueue;
219
+ if (index < CLOSE_FRAMES.length - 1) {
220
+ await new Promise((resolve) => setTimeout(resolve, CLOSE_FRAME_MS));
221
+ }
222
+ }
223
+ await new Promise((resolve) => setTimeout(resolve, CLOSE_FRAME_MS));
224
+ stop();
225
+ };
226
+ const updateMachineRow = (machineId, update) => {
227
+ rows = Object.freeze(rows.map((row) => row.machine.id === machineId
228
+ ? Object.freeze({ ...row, ...update })
229
+ : row));
230
+ };
231
+ const sleep = (ms) => new Promise((resolve) => {
232
+ const timer = setTimeout(resolve, Math.max(0, ms));
233
+ timer.unref();
234
+ });
235
+ const recoverCreation = async (machineId) => {
236
+ const row = rows.find(candidate => candidate.machine.id === machineId);
237
+ const operation = row?.machine.createOperation;
238
+ if (row === undefined || row.pendingLifecycle !== undefined || operation?.machineId !== machineId
239
+ || !machineContextActions(row, snapshotObservedAt).some(action => action.kind === "recover"))
240
+ return;
241
+ updateMachineRow(machineId, { pendingLifecycle: "recover" });
242
+ lifecycleNotice = undefined;
243
+ selectedKey = undefined;
244
+ render();
245
+ try {
246
+ const result = await input.client.reconcileMachineCreateRequest(operation.id, requestSignal);
247
+ if (result.id !== operation.id || result.machineId !== machineId)
248
+ throw new Error("Mismatched recovery response");
249
+ const observed = await input.client.getMachine(machineId, requestSignal);
250
+ if (observed.id !== machineId)
251
+ throw new Error("Mismatched Machine response");
252
+ if (isClosing())
253
+ return;
254
+ updateMachineRow(machineId, { machine: observed });
255
+ lifecycleNotice = observed.state === "running"
256
+ ? `${safeLine(observed.name)} is running.`
257
+ : `Creation recovery is ${result.state}. Refresh or Recover creation again to check the same Machine.`;
258
+ }
259
+ catch (error) {
260
+ if (isClosing())
261
+ return;
262
+ lifecycleNotice = error instanceof CunaError
263
+ ? `${error.code}: ${safeLine(error.message)}${error.hint === undefined ? "" : ` ${safeLine(error.hint)}`}`
264
+ : "Could not confirm creation recovery. Refresh to check the same Machine.";
265
+ }
266
+ finally {
267
+ updateMachineRow(machineId, { pendingLifecycle: undefined });
268
+ if (!isClosing())
269
+ reconcileSelection();
270
+ }
271
+ };
272
+ /**
273
+ * E13-R2/R3. Run Start, Stop or Delete in place: capability gate, one
274
+ * request, then a bounded read-back. A lapsed response budget is treated
275
+ * as "the answer did not arrive", never as "the action failed" — the
276
+ * owner's Machine was `running` a moment after the screen used to die.
277
+ */
278
+ const runLifecycle = async (machineId, action) => {
279
+ const row = rows.find((candidate) => candidate.machine.id === machineId);
280
+ if (row === undefined || row.pendingLifecycle !== undefined)
281
+ return;
282
+ const name = safeLine(row.machine.name);
283
+ const settle = () => updateMachineRow(machineId, { pendingLifecycle: undefined });
284
+ updateMachineRow(machineId, { pendingLifecycle: action });
285
+ lifecycleNotice = undefined;
286
+ selectedKey = undefined;
287
+ render();
288
+ const capabilityId = action === "delete" ? "machines.delete" : "machines.lifecycle";
289
+ const decision = await decideExplorerCapability(input.client, "machine", machineId, capabilityId, () => dependencies.now?.() ?? Date.now(), requestSignal);
290
+ if (isClosing())
291
+ return;
292
+ if (decision.status !== "supported") {
293
+ settle();
294
+ lifecycleNotice = `${capabilityId} is not available for ${name}: ${decision.reason ?? decision.status}. Nothing was requested.`;
295
+ reconcileSelection();
296
+ return;
297
+ }
298
+ try {
299
+ if (action === "delete")
300
+ await input.client.deleteMachine(machineId);
301
+ else
302
+ await input.client.transitionMachine(machineId, action, requestSignal);
303
+ }
304
+ catch (error) {
305
+ if (isClosing())
306
+ return;
307
+ if (!(error instanceof CunaError && error.code === OBSERVATION_BUDGET_CODES.response)) {
308
+ settle();
309
+ lifecycleNotice = typedLifecycleFailure(error, name, action);
310
+ reconcileSelection();
311
+ return;
312
+ }
313
+ // The request outlived the response budget. The Machine may well have
314
+ // moved; only the read-back below can say.
315
+ }
316
+ const expectedState = action === "start" ? "running" : action === "stop" ? "stopped" : "deleted";
317
+ const deadline = Date.now() + convergenceBudgetMs;
318
+ for (;;) {
319
+ if (isClosing())
320
+ return;
321
+ let converged = false;
322
+ try {
323
+ const observed = await input.client.getMachine(machineId, requestSignal);
324
+ if (isClosing())
325
+ return;
326
+ if (observed.id === machineId) {
327
+ updateMachineRow(machineId, { machine: observed });
328
+ converged = observed.state === expectedState;
329
+ }
330
+ }
331
+ catch (error) {
332
+ if (isClosing())
333
+ return;
334
+ if (action === "delete" && error instanceof CunaError && error.code === "cuna.remote.not_found") {
335
+ converged = true;
336
+ }
337
+ else {
338
+ settle();
339
+ lifecycleNotice = typedLifecycleFailure(error, name, action);
340
+ reconcileSelection();
341
+ return;
342
+ }
343
+ }
344
+ if (converged) {
345
+ settle();
346
+ if (action === "delete") {
347
+ rows = Object.freeze(rows.filter((candidate) => candidate.machine.id !== machineId));
348
+ expanded.delete(machineId);
349
+ if (navigation.screen.kind !== "machines" && navigation.screen.kind !== "new-machine"
350
+ && navigation.screen.kind !== "new-machine-name" && navigation.screen.machineId === machineId) {
351
+ navigation = INITIAL_MACHINE_FIRST_STATE;
352
+ }
353
+ lifecycleNotice = `Deleted ${name}.`;
354
+ }
355
+ else {
356
+ lifecycleNotice = `${name} is ${expectedState}.`;
357
+ }
358
+ reconcileSelection();
359
+ void refresh().catch((error) => { failure ??= error; stop(); });
360
+ return;
361
+ }
362
+ const remaining = deadline - Date.now();
363
+ if (remaining <= 0) {
364
+ settle();
365
+ lifecycleNotice = `${OBSERVATION_BUDGET_CODES.convergence}: ${name} had not reached ${expectedState} after ${Math.round(convergenceBudgetMs / 1000)} s. Run \`cuna machines list\` to see its current state.`;
366
+ reconcileSelection();
367
+ return;
368
+ }
369
+ await sleep(Math.min(pollIntervalMs, remaining));
370
+ }
371
+ };
372
+ /** E13-R1. `n` from any overview: read the account capability, then offer providers. */
373
+ const openNewMachine = () => {
374
+ interactionNotice = undefined;
375
+ lifecycleNotice = undefined;
376
+ pendingSupervisorUpdateMachineId = undefined;
377
+ pendingDelete = undefined;
378
+ navigation = reduceMachineFirstNavigation(navigation, { type: "open-new-machine" });
379
+ newMachine = Object.freeze({ capability: "checking" });
380
+ selectedKey = undefined;
381
+ render();
382
+ void decideExplorerCapability(input.client, "account", undefined, "machines.create", () => dependencies.now?.() ?? Date.now(), requestSignal)
383
+ .then((decision) => {
384
+ if (isClosing())
385
+ return;
386
+ newMachine = decision.status === "supported"
387
+ ? Object.freeze({ capability: "available" })
388
+ : Object.freeze({ capability: "unavailable", reason: decision.reason ?? decision.status });
389
+ if (navigation.screen.kind === "new-machine")
390
+ reconcileSelection();
391
+ });
392
+ };
393
+ const chooseNewMachineProvider = (provider) => {
394
+ // The capability answer, not the key press, admits the name step.
395
+ if (newMachine.capability !== "available")
396
+ return;
397
+ navigation = reduceMachineFirstNavigation(navigation, { type: "choose-new-machine-provider", provider });
398
+ newMachineName = defaultMachineName(provider, rows.map((row) => row.machine.name));
399
+ selectedKey = "new-machine-name";
400
+ render();
401
+ };
402
+ const clearListRetry = () => {
403
+ if (listRetryTimer === undefined)
404
+ return;
405
+ clearTimeout(listRetryTimer);
406
+ listRetryTimer = undefined;
407
+ };
408
+ /**
409
+ * E13-R7. The delay before the next automatic list attempt, or `undefined`
410
+ * once the outage has used up the policy's attempts or the bounded window.
411
+ * The elapsed time is measured from the first failure of this outage, so a
412
+ * slow server cannot stretch the window by answering late.
413
+ */
414
+ const nextListRetryDelay = (attempt, elapsedMs) => {
415
+ if (attempt > listRetryPolicy.maximumAttempts)
416
+ return undefined;
417
+ const delay = reconnectDelay(attempt, listRetryPolicy, listRetryRandom);
418
+ return elapsedMs + delay > listRetryWindowMs ? undefined : delay;
419
+ };
420
+ const scheduleListRetry = (delayMs) => {
421
+ clearListRetry();
422
+ listRetryTimer = setTimeout(() => {
423
+ listRetryTimer = undefined;
424
+ if (isClosing())
425
+ return;
426
+ void refresh().catch((error) => { failure ??= error; stop(); });
427
+ }, delayMs);
428
+ listRetryTimer.unref();
429
+ };
430
+ const refresh = async () => {
431
+ if (refreshInFlight || stopped)
432
+ return;
433
+ refreshInFlight = true;
434
+ loadingPhase = "machines";
435
+ render();
436
+ try {
437
+ const previousIds = new Set(rows.map((row) => row.machine.id));
438
+ const previousRows = new Map(rows.map((row) => [row.machine.id, row]));
439
+ const machines = await listAllMachines(input.client, requestSignal);
440
+ if (stopped || closingNotice !== undefined)
441
+ return;
442
+ refreshError = undefined;
443
+ listFailure = undefined;
444
+ clearListRetry();
445
+ interactionNotice = undefined;
446
+ const capabilityNow = dependencies.now?.() ?? Date.now();
447
+ const loaded = machines.map((machine) => {
448
+ const previous = previousRows.get(machine.id);
449
+ const retainCapability = previous?.sessionCreateCapabilityState === "verified" &&
450
+ (previous.sessionCreateCapabilityExpiresAt ?? 0) > capabilityNow;
451
+ return Object.freeze({
452
+ machine,
453
+ sessions: previous?.sessions ?? Object.freeze([]),
454
+ sessionsLoading: true,
455
+ canCreateSession: retainCapability && previous.canCreateSession === true,
456
+ sessionCreateCapabilityState: retainCapability ? "verified" : "checking",
457
+ sessionCreateCapabilityReason: retainCapability ? previous.sessionCreateCapabilityReason : undefined,
458
+ sessionCreateCapabilityExpiresAt: retainCapability ? previous.sessionCreateCapabilityExpiresAt : undefined,
459
+ opencodeSupervisorRepairReason: previous?.opencodeSupervisorRepairReason,
460
+ opencodeSupervisorProtocolUnavailable: previous?.opencodeSupervisorProtocolUnavailable ?? false,
461
+ opencodeRuntimeUnverified: previous?.opencodeRuntimeUnverified ?? false,
462
+ pendingLifecycle: previous?.pendingLifecycle,
463
+ });
464
+ });
465
+ loaded.sort((left, right) => left.machine.name.localeCompare(right.machine.name) || left.machine.id.localeCompare(right.machine.id));
466
+ rows = Object.freeze(loaded);
467
+ const currentIds = new Set(rows.map((row) => row.machine.id));
468
+ for (const id of expanded)
469
+ if (!currentIds.has(id))
470
+ expanded.delete(id);
471
+ for (const row of rows) {
472
+ if (!initialized || !previousIds.has(row.machine.id))
473
+ expanded.add(row.machine.id);
474
+ }
475
+ initialized = true;
476
+ const visibleKeys = selectableKeys(rows, expanded, snapshotObservedAt, navigation, initialized);
477
+ // Input remains live while the network refresh is pending. Reconcile the
478
+ // selection that is current when the response is applied, rather than a
479
+ // stale value captured before awaiting the API. Otherwise an arrow press
480
+ // during refresh appears to work and then jumps back to the machine row.
481
+ const currentSelection = selectedKey;
482
+ // Session refresh temporarily withholds creation choices. Preserve the
483
+ // user's existing choice here as well as during later partial responses;
484
+ // Enter still requires current authority before it can execute.
485
+ const pendingCreation = rows.length > 0 && currentSelection?.startsWith("create:") === true;
486
+ selectedKey = currentSelection !== undefined && (visibleKeys.includes(currentSelection) || pendingCreation)
487
+ ? currentSelection
488
+ : visibleKeys[0];
489
+ loadingPhase = rows.length === 0 ? undefined : "sessions";
490
+ render();
491
+ await Promise.all(rows.map(async ({ machine }) => {
492
+ // Session inventory is the useful, non-mutating first answer. Do not
493
+ // hide it behind a slower capability read: that used to make bare
494
+ // `cuna` look frozen and made an existing session appear absent for
495
+ // the entire capability timeout.
496
+ const sessionsTask = listAllAgentSessions(input.client, machine.id, requestSignal)
497
+ .then((observedSessions) => {
498
+ if (stopped || closingNotice !== undefined)
499
+ return;
500
+ const previous = rows.find((row) => row.machine.id === machine.id)?.sessions ?? Object.freeze([]);
501
+ const sessions = mergeAgentSessionObservations(previous, observedSessions)
502
+ .filter(isAgentSessionIntendedActive)
503
+ .sort((left, right) => left.agent.localeCompare(right.agent) || left.name.localeCompare(right.name) || left.id.localeCompare(right.id));
504
+ updateMachineRow(machine.id, {
505
+ sessions: Object.freeze(sessions),
506
+ sessionsLoading: false,
507
+ sessionsError: undefined,
508
+ });
509
+ reconcileSelection();
510
+ })
511
+ .catch(() => {
512
+ if (stopped || closingNotice !== undefined)
513
+ return;
514
+ const confirmed = rows.find((row) => row.machine.id === machine.id)?.sessions ?? Object.freeze([]);
515
+ updateMachineRow(machine.id, {
516
+ // A failed child read is not an authoritative empty membership
517
+ // observation. Retain the last confirmed list and make the
518
+ // partial failure explicit until a successful refresh replaces it.
519
+ sessions: confirmed,
520
+ sessionsLoading: false,
521
+ sessionsError: "sessions unavailable",
522
+ });
523
+ reconcileSelection();
524
+ });
525
+ const capabilityTask = observeSessionCreateCapability(input.client, machine.id, () => dependencies.now?.() ?? Date.now(), requestSignal).then((capability) => {
526
+ if (stopped || closingNotice !== undefined)
527
+ return;
528
+ const provider = machineProviderAvailability(machine);
529
+ const repairReason = provider.agent === "opencode" &&
530
+ isOpenCodeSupervisorUpgradeRequiredReason(capability.reason)
531
+ ? capability.reason
532
+ : undefined;
533
+ const supervisorProtocolUnavailable = provider.agent === "opencode" &&
534
+ isOpenCodeSupervisorProtocolUnavailableReason(capability.reason);
535
+ const runtimeUnverified = provider.agent === "opencode" &&
536
+ isOpenCodeRuntimeUnverifiedReason(capability.reason);
537
+ updateMachineRow(machine.id, {
538
+ canCreateSession: capability.canCreateSession,
539
+ sessionCreateCapabilityState: capability.state,
540
+ sessionCreateCapabilityReason: capability.reason,
541
+ sessionCreateCapabilityExpiresAt: capability.expiresAt,
542
+ opencodeSupervisorRepairReason: repairReason,
543
+ opencodeSupervisorProtocolUnavailable: supervisorProtocolUnavailable,
544
+ opencodeRuntimeUnverified: runtimeUnverified,
545
+ });
546
+ reconcileSelection();
547
+ });
548
+ await Promise.all([sessionsTask, capabilityTask]);
549
+ }));
550
+ }
551
+ catch (error) {
552
+ if (isClosing())
553
+ return;
554
+ // E13-R7. Only a non-retryable failure (auth, policy, usage) leaves the
555
+ // screen, and it leaves with the typed error. A retryable one is not
556
+ // evidence that the inventory disappeared, and before any inventory it
557
+ // is not an empty list either: keep what is known, say why the list is
558
+ // unavailable, and retry within the bounded window.
559
+ if (!isRetryableExplorerRefreshError(error))
560
+ throw error;
561
+ const failedAt = Date.now();
562
+ const attempt = (listFailure?.attempt ?? 0) + 1;
563
+ listFailure = Object.freeze({ since: listFailure?.since ?? failedAt, attempt });
564
+ const retryDelayMs = nextListRetryDelay(attempt, failedAt - listFailure.since);
565
+ refreshError = listFailureNotice(error, rows.length > 0, retryDelayMs);
566
+ if (retryDelayMs !== undefined)
567
+ scheduleListRetry(retryDelayMs);
568
+ render();
569
+ }
570
+ finally {
571
+ snapshotObservedAt = dependencies.now?.() ?? Date.now();
572
+ loadingPhase = undefined;
573
+ refreshInFlight = false;
574
+ render();
575
+ }
576
+ };
577
+ const moveSelection = (delta) => {
578
+ interactionNotice = undefined;
579
+ lifecycleNotice = undefined;
580
+ pendingSupervisorUpdateMachineId = undefined;
581
+ pendingDelete = undefined;
582
+ // Arrow input refers to the choices the user has actually seen. Refresh
583
+ // may already have changed the model while its repaint is still queued.
584
+ const keys = paintedSelectionKeys;
585
+ if (keys.length === 0)
586
+ return;
587
+ const currentIndex = selectedKey === undefined ? -1 : keys.indexOf(selectedKey);
588
+ const origin = currentIndex < 0 ? (delta > 0 ? -1 : keys.length) : currentIndex;
589
+ selectedKey = keys[Math.min(keys.length - 1, Math.max(0, origin + delta))];
590
+ navigation = reduceMachineFirstNavigation(navigation, { type: "move", delta, itemCount: keys.length });
591
+ render();
592
+ };
593
+ const reconcileSelection = () => {
594
+ const keys = selectableKeys(rows, expanded, snapshotObservedAt, navigation, initialized);
595
+ const pendingCreation = loadingPhase === "sessions" && selectedKey?.startsWith("create:") === true;
596
+ if (selectedKey === undefined || (!keys.includes(selectedKey) && !pendingCreation))
597
+ selectedKey = keys[0];
598
+ render();
599
+ };
600
+ const goBack = () => {
601
+ interactionNotice = undefined;
602
+ lifecycleNotice = undefined;
603
+ pendingSupervisorUpdateMachineId = undefined;
604
+ pendingDelete = undefined;
605
+ const previous = navigation;
606
+ const previousScreen = previous.screen;
607
+ navigation = reduceMachineFirstNavigation(navigation, { type: "back" });
608
+ if (navigation !== previous) {
609
+ const keys = selectableKeys(rows, expanded, snapshotObservedAt, navigation, initialized);
610
+ if (previousScreen.kind === "machine" && navigation.screen.kind === "machines") {
611
+ const parentKey = `machine:${previousScreen.machineId}`;
612
+ selectedKey = keys.includes(parentKey) ? parentKey : keys[0];
613
+ }
614
+ else if (previousScreen.kind === "provider" && navigation.screen.kind === "machine") {
615
+ const row = rows.find((candidate) => candidate.machine.id === previousScreen.machineId);
616
+ const providerAction = row === undefined
617
+ ? undefined
618
+ : machineContextActions(row, snapshotObservedAt).find((action) => action.kind === "provider" && action.provider === previousScreen.provider);
619
+ const parentKey = providerAction === undefined ? undefined : machineActionSelectionKey(providerAction);
620
+ selectedKey = parentKey !== undefined && keys.includes(parentKey) ? parentKey : keys[0];
621
+ }
622
+ else {
623
+ selectedKey = keys[0];
624
+ }
625
+ render();
626
+ }
627
+ };
628
+ const activateOverviewSession = (sessionId) => {
629
+ const row = rows.find((candidate) => candidate.sessions.some((session) => session.id === sessionId));
630
+ const session = row?.sessions.find((candidate) => candidate.id === sessionId);
631
+ if (row === undefined || session === undefined || !isActionableProvider(session.agent))
632
+ return false;
633
+ const actionability = classifySessionActionability({ session, machine: row.machine, now: snapshotObservedAt });
634
+ if (actionability.canAttach) {
635
+ selection = Object.freeze({ kind: "attach", agentSessionId: session.id, agent: session.agent });
636
+ stop();
637
+ return true;
638
+ }
639
+ if (actionability.recoveryAction === "refresh") {
640
+ void refresh().catch((error) => { failure ??= error; stop(); });
641
+ return false;
642
+ }
643
+ if (actionability.recoveryAction === "wait") {
644
+ interactionNotice = hasLegacySupervisorBlockedOpenCodeSession(row) &&
645
+ session.agent === "opencode" && isUnobservedLaunchedSession(session)
646
+ ? legacySupervisorBlockedNotice()
647
+ : waitingForSessionObservation(session);
648
+ render();
649
+ return false;
650
+ }
651
+ interactionNotice = `Session ended. Open ${safeLine(row.machine.name)} to start a new ${providerDisplayName(session.agent)} session.`;
652
+ render();
653
+ return false;
654
+ };
655
+ const goForward = () => {
656
+ pendingSupervisorUpdateMachineId = undefined;
657
+ pendingDelete = undefined;
658
+ if (selectedKey?.startsWith("new-machine:") === true && navigation.screen.kind === "new-machine") {
659
+ chooseNewMachineProvider(selectedKey.slice("new-machine:".length));
660
+ return;
661
+ }
662
+ if (selectedKey?.startsWith("machine:") === true && navigation.screen.kind === "machines") {
663
+ const machineId = selectedKey.slice("machine:".length);
664
+ navigation = reduceMachineFirstNavigation(navigation, { type: "open-machine", machineId });
665
+ selectedKey = selectableKeys(rows, expanded, snapshotObservedAt, navigation, initialized)[0];
666
+ render();
667
+ return;
668
+ }
669
+ if (selectedKey?.startsWith("session:") === true && navigation.screen.kind === "machines") {
670
+ const sessionId = selectedKey.slice("session:".length);
671
+ activateOverviewSession(sessionId);
672
+ return;
673
+ }
674
+ if ((selectedKey?.startsWith("machine-provider:") === true || selectedKey?.startsWith("machine-lifecycle:") === true)
675
+ && navigation.screen.kind === "machine") {
676
+ const screen = navigation.screen;
677
+ const row = rows.find((candidate) => candidate.machine.id === screen.machineId);
678
+ const action = row === undefined
679
+ ? undefined
680
+ : machineContextActions(row, snapshotObservedAt).find((candidate) => machineActionSelectionKey(candidate) === selectedKey);
681
+ if (action?.kind === "provider") {
682
+ navigation = reduceMachineFirstNavigation(navigation, {
683
+ type: "open-provider",
684
+ machineId: action.machineId,
685
+ provider: action.provider,
686
+ });
687
+ selectedKey = selectableKeys(rows, expanded, snapshotObservedAt, navigation, initialized)[0];
688
+ render();
689
+ }
690
+ }
691
+ };
692
+ const applyCursorFinal = (byte) => {
693
+ if (byte === 0x41)
694
+ moveSelection(-1);
695
+ else if (byte === 0x42)
696
+ moveSelection(1);
697
+ else if (byte === 0x43)
698
+ goForward();
699
+ else if (byte === 0x44)
700
+ goBack();
701
+ };
702
+ const applyKey = (byte) => {
703
+ if (closingNotice !== undefined)
704
+ return true;
705
+ if (byte === 0x65 && inputSequence === "none" && (navigation.screen.kind === "machine" || navigation.screen.kind === "provider")) {
706
+ selection = Object.freeze({ kind: "executions", machineId: navigation.screen.machineId });
707
+ stop();
708
+ return true;
709
+ }
710
+ if (byte === 0x77 && inputSequence === "none" && (navigation.screen.kind === "machine" || navigation.screen.kind === "provider")) {
711
+ selection = Object.freeze({ kind: "workspaces", machineId: navigation.screen.machineId });
712
+ stop();
713
+ return true;
714
+ }
715
+ // `q` is a letter inside the name field; Ctrl-C quits from anywhere.
716
+ if (byte === 0x03 || (byte === 0x71 && navigation.screen.kind !== "new-machine-name")) {
717
+ void exitWithFeedback().catch((error) => { failure ??= error; stop(); });
718
+ return true;
719
+ }
720
+ if (inputSequence === "escape") {
721
+ if (escapeTimer !== undefined)
722
+ clearTimeout(escapeTimer);
723
+ escapeTimer = undefined;
724
+ inputSequence = "none";
725
+ if (byte === 0x5b || byte === 0x4f) {
726
+ inputSequence = "cursor";
727
+ return false;
728
+ }
729
+ return applyKey(byte);
730
+ }
731
+ if (inputSequence === "cursor") {
732
+ if ((byte >= 0x30 && byte <= 0x3f) || (byte >= 0x20 && byte <= 0x2f))
733
+ return false;
734
+ inputSequence = "none";
735
+ applyCursorFinal(byte);
736
+ return false;
737
+ }
738
+ if (byte === 0x1b) {
739
+ inputSequence = "escape";
740
+ escapeTimer = setTimeout(() => {
741
+ if (inputSequence !== "escape")
742
+ return;
743
+ inputSequence = "none";
744
+ escapeTimer = undefined;
745
+ goBack();
746
+ }, ESCAPE_SEQUENCE_TIMEOUT_MS);
747
+ escapeTimer.unref();
748
+ return false;
749
+ }
750
+ if (navigation.screen.kind === "new-machine-name") {
751
+ // A text field: every printable byte is a character, including the
752
+ // letters that are hotkeys everywhere else. Only Ctrl-C, Esc and the
753
+ // cursor sequences above keep their meaning.
754
+ const provider = navigation.screen.provider;
755
+ if (byte === 0x0d || byte === 0x0a) {
756
+ const name = newMachineName.trim();
757
+ if (name.length === 0) {
758
+ interactionNotice = "Give the Machine a name, then press Enter.";
759
+ render();
760
+ return false;
761
+ }
762
+ selection = Object.freeze({ kind: "create", agent: provider, name });
763
+ stop();
764
+ return true;
765
+ }
766
+ if (byte === 0x08 || byte === 0x7f) {
767
+ newMachineName = newMachineName.slice(0, -1);
768
+ }
769
+ else if (byte >= 0x20 && byte <= 0x7e && newMachineName.length < MACHINE_NAME_MAX_LENGTH) {
770
+ newMachineName += String.fromCharCode(byte);
771
+ }
772
+ interactionNotice = undefined;
773
+ render();
774
+ return false;
775
+ }
776
+ if (byte === 0x6b)
777
+ moveSelection(-1);
778
+ else if (byte === 0x6a)
779
+ moveSelection(1);
780
+ else if (byte === 0x08 || byte === 0x7f || byte === 0x62)
781
+ goBack();
782
+ else if (byte === 0x70) {
783
+ const session = rows.flatMap(row => row.sessions).find(item => selectedKey === `session:${item.id}` || selectedKey?.endsWith(`:${item.id}`) === true);
784
+ if (session) {
785
+ selection = Object.freeze({ kind: "provider-check", agentSessionId: session.id });
786
+ stop();
787
+ return true;
788
+ }
789
+ }
790
+ else if (byte === 0x6e && navigation.screen.kind === "machines")
791
+ openNewMachine();
792
+ else if (byte === 0x0d || byte === 0x0a || byte === 0x20) {
793
+ if (navigation.screen.kind === "new-machine") {
794
+ if (selectedKey?.startsWith("new-machine:") === true) {
795
+ chooseNewMachineProvider(selectedKey.slice("new-machine:".length));
796
+ }
797
+ return false;
798
+ }
799
+ if (selectedKey?.startsWith("machine:") === true) {
800
+ const id = selectedKey.slice("machine:".length);
801
+ // A Machine is a container, not a synonym for whichever session
802
+ // happens to be unique right now. Attaching is an explicit action on
803
+ // a concrete AgentSession only; this keeps one-key navigation read-only
804
+ // and prevents a stale/unique child from being opened by surprise.
805
+ navigation = reduceMachineFirstNavigation(navigation, { type: "open-machine", machineId: id });
806
+ selectedKey = selectableKeys(rows, expanded, snapshotObservedAt, navigation, initialized)[0];
807
+ render();
808
+ }
809
+ else if (selectedKey?.startsWith("session:") === true && navigation.screen.kind === "machines") {
810
+ return activateOverviewSession(selectedKey.slice("session:".length));
811
+ }
812
+ else if ((selectedKey?.startsWith("machine-provider:") === true || selectedKey?.startsWith("machine-create:") === true
813
+ || selectedKey?.startsWith("machine-lifecycle:") === true || selectedKey?.startsWith("machine-supervisor:") === true)
814
+ && navigation.screen.kind === "machine") {
815
+ const screen = navigation.screen;
816
+ const row = rows.find((candidate) => candidate.machine.id === screen.machineId);
817
+ const action = row === undefined
818
+ ? undefined
819
+ : machineContextActions(row, snapshotObservedAt).find((candidate) => machineActionSelectionKey(candidate) === selectedKey);
820
+ if (action?.kind === "provider") {
821
+ navigation = reduceMachineFirstNavigation(navigation, {
822
+ type: "open-provider",
823
+ machineId: action.machineId,
824
+ provider: action.provider,
825
+ });
826
+ selectedKey = selectableKeys(rows, expanded, snapshotObservedAt, navigation, initialized)[0];
827
+ render();
828
+ }
829
+ else if (action?.kind === "recover") {
830
+ pendingDelete = undefined;
831
+ void recoverCreation(action.machineId).catch((error) => { failure ??= error; stop(); });
832
+ }
833
+ else if (action?.kind === "start" || action?.kind === "stop") {
834
+ pendingDelete = undefined;
835
+ void runLifecycle(action.machineId, action.kind).catch((error) => { failure ??= error; stop(); });
836
+ }
837
+ else if (action?.kind === "delete") {
838
+ // E13-R2: double confirmation, like the supervisor repair, naming
839
+ // the Machine and what disappears with it. No request before the
840
+ // second Enter; any move or back re-arms it.
841
+ if (row === undefined || refreshInFlight || row.sessionsLoading === true || row.sessionsError !== undefined || refreshError !== undefined)
842
+ return false;
843
+ const fingerprint = deleteFingerprint(row);
844
+ if (pendingDelete?.fingerprint !== fingerprint) {
845
+ const sessionCount = row.sessions.length;
846
+ pendingDelete = { fingerprint, notice: `Press Enter again to delete ${safeLine(row.machine.name)} and its ${sessionCount} AgentSession${sessionCount === 1 ? "" : "s"}. This cannot be undone.` };
847
+ render();
848
+ return false;
849
+ }
850
+ pendingDelete = undefined;
851
+ interactionNotice = undefined;
852
+ void runLifecycle(action.machineId, "delete").catch((error) => { failure ??= error; stop(); });
853
+ }
854
+ else if (action?.kind === "supervisor-blocked") {
855
+ pendingSupervisorUpdateMachineId = undefined;
856
+ interactionNotice = row !== undefined && hasLegacySupervisorBlockedOpenCodeSession(row)
857
+ ? legacySupervisorBlockedNotice()
858
+ : row?.machine.state === "stopped"
859
+ ? "OpenCode is ready for a terminal update. Select Update terminal supervisor, then press Enter again to confirm."
860
+ : `Protected: Cuna will not stop ${safeLine(row?.machine.name ?? "this Machine")} or terminate AgentSessions. Stop it yourself when you are ready; the update will then be available here.`;
861
+ render();
862
+ }
863
+ else if (action?.kind === "update-supervisor") {
864
+ if (pendingSupervisorUpdateMachineId !== action.machineId) {
865
+ pendingSupervisorUpdateMachineId = action.machineId;
866
+ interactionNotice = "Press Enter again to update the terminal supervisor. Cuna will not stop the Machine or terminate AgentSessions.";
867
+ render();
868
+ return false;
869
+ }
870
+ selection = Object.freeze({ kind: "supervisor-update", machineId: action.machineId });
871
+ stop();
872
+ return true;
873
+ }
874
+ else if (action?.kind === "new-session") {
875
+ selection = Object.freeze({
876
+ kind: "launch",
877
+ agent: action.provider,
878
+ newSession: true,
879
+ machineId: row.machine.id,
880
+ machineName: row.machine.name,
881
+ });
882
+ stop();
883
+ return true;
884
+ }
885
+ }
886
+ else if (selectedKey?.startsWith("provider-session:") === true
887
+ && navigation.screen.kind === "provider") {
888
+ const screen = navigation.screen;
889
+ const row = rows.find((candidate) => candidate.machine.id === screen.machineId);
890
+ const action = row === undefined ? undefined : resolveProviderContextActions({
891
+ machine: row.machine,
892
+ provider: screen.provider,
893
+ sessions: row.sessions,
894
+ now: snapshotObservedAt,
895
+ }).find((candidate) => providerActionSelectionKey(candidate) === selectedKey);
896
+ if (action?.kind === "session") {
897
+ const actionability = classifySessionActionability({ session: action.session, machine: row.machine, now: snapshotObservedAt });
898
+ if (actionability.canAttach) {
899
+ selection = Object.freeze({ kind: "attach", agentSessionId: action.session.id, agent: action.session.agent });
900
+ stop();
901
+ return true;
902
+ }
903
+ if (actionability.recoveryAction === "refresh") {
904
+ void refresh().catch((error) => { failure ??= error; stop(); });
905
+ }
906
+ else if (actionability.recoveryAction === "wait") {
907
+ interactionNotice = row !== undefined && hasLegacySupervisorBlockedOpenCodeSession(row) &&
908
+ action.session.agent === "opencode" && isUnobservedLaunchedSession(action.session)
909
+ ? legacySupervisorBlockedNotice()
910
+ : waitingForSessionObservation(action.session);
911
+ render();
912
+ }
913
+ else {
914
+ render();
915
+ }
916
+ }
917
+ }
918
+ else if (selectedKey?.startsWith("create:") === true) {
919
+ if (!selectableKeys(rows, expanded, snapshotObservedAt, navigation, initialized).includes(selectedKey)) {
920
+ render();
921
+ return false;
922
+ }
923
+ const agent = selectedKey.slice("create:".length);
924
+ selection = Object.freeze({ kind: "launch", agent });
925
+ stop();
926
+ return true;
927
+ }
928
+ else if (selectedKey?.startsWith("session:") === true) {
929
+ const id = selectedKey.slice("session:".length);
930
+ const session = rows.flatMap((row) => row.sessions).find((candidate) => candidate.id === id);
931
+ const row = rows.find((candidate) => candidate.sessions.some((item) => item.id === id));
932
+ const actionability = session === undefined || row === undefined ? undefined : classifySessionActionability({
933
+ session,
934
+ machine: row.machine,
935
+ now: snapshotObservedAt,
936
+ refreshStatus: row.sessionsLoading === true ? "pending" : "idle",
937
+ });
938
+ if (session !== undefined && isActionableProvider(session.agent) && actionability?.canAttach === true) {
939
+ selection = Object.freeze({ kind: "attach", agentSessionId: session.id, agent: session.agent });
940
+ stop();
941
+ return true;
942
+ }
943
+ if (actionability?.recoveryAction === "refresh") {
944
+ void refresh().catch((error) => { failure ??= error; stop(); });
945
+ }
946
+ else if (actionability?.recoveryAction === "wait") {
947
+ interactionNotice = row !== undefined && hasLegacySupervisorBlockedOpenCodeSession(row) &&
948
+ session?.agent === "opencode" && isUnobservedLaunchedSession(session)
949
+ ? legacySupervisorBlockedNotice()
950
+ : waitingForSessionObservation(session);
951
+ render();
952
+ }
953
+ else if (actionability?.recoveryAction === "authenticate") {
954
+ // Authentication owns the same provider PTY, but remains distinct
955
+ // from an already-attachable base state in the policy.
956
+ if (session !== undefined && isActionableProvider(session.agent)) {
957
+ selection = Object.freeze({ kind: "attach", agentSessionId: session.id, agent: session.agent });
958
+ stop();
959
+ return true;
960
+ }
961
+ }
962
+ else {
963
+ // A visible but non-attachable supported session is still a useful
964
+ // navigation target. Open its provider context instead of making
965
+ // Enter appear broken; no attach or mutation is attempted.
966
+ if (session !== undefined && isActionableProvider(session.agent))
967
+ goForward();
968
+ else
969
+ render();
970
+ }
971
+ }
972
+ }
973
+ else if (byte === 0x72) {
974
+ // E13-R7: a person's retry opens a fresh bounded window; it is not one
975
+ // more attempt charged against the automatic one.
976
+ listFailure = undefined;
977
+ clearListRetry();
978
+ void refresh().catch((error) => { failure ??= error; stop(); });
979
+ }
980
+ return false;
981
+ };
982
+ const removeInput = host.onInput((bytes) => {
983
+ for (const byte of bytes) {
984
+ if (applyKey(byte))
985
+ break;
986
+ }
987
+ });
988
+ const removeResize = host.onResize(render);
989
+ const onAbort = () => {
990
+ void exitWithFeedback().catch((error) => { failure ??= error; stop(); });
991
+ };
992
+ input.signal?.addEventListener("abort", onAbort, { once: true });
993
+ const animationTimer = setInterval(() => {
994
+ if (loadingPhase === undefined || stopped)
995
+ return;
996
+ animationFrame = (animationFrame + 1) % PROGRESS.length;
997
+ render();
998
+ }, 90);
999
+ animationTimer.unref();
1000
+ const liveRefreshTimer = setInterval(() => {
1001
+ void refresh().catch((error) => { failure ??= error; stop(); });
1002
+ }, LIVE_REFRESH_MS);
1003
+ liveRefreshTimer.unref();
1004
+ try {
1005
+ try {
1006
+ await refresh();
1007
+ }
1008
+ catch (error) {
1009
+ if (!stopped && closingNotice === undefined)
1010
+ throw error;
1011
+ }
1012
+ if (!stopped)
1013
+ await done;
1014
+ await renderQueue;
1015
+ if (failure !== undefined)
1016
+ throw failure;
1017
+ }
1018
+ finally {
1019
+ clearInterval(animationTimer);
1020
+ clearInterval(liveRefreshTimer);
1021
+ clearListRetry();
1022
+ if (escapeTimer !== undefined)
1023
+ clearTimeout(escapeTimer);
1024
+ stopped = true;
1025
+ removeInput();
1026
+ removeResize();
1027
+ input.signal?.removeEventListener("abort", onAbort);
1028
+ await lease.restore();
1029
+ }
1030
+ return selection;
1031
+ }
1032
+ async function observeSessionCreateCapability(client, machineId, now, signal) {
1033
+ if (typeof client.discoverCapabilities !== "function") {
1034
+ return Object.freeze({
1035
+ canCreateSession: false,
1036
+ state: "unverified",
1037
+ reason: "capability_discovery_unavailable",
1038
+ });
1039
+ }
1040
+ try {
1041
+ const snapshot = await client.discoverCapabilities("machine", machineId, signal);
1042
+ if (snapshot.subjectScope !== "machine" || snapshot.subjectId !== machineId) {
1043
+ return Object.freeze({ canCreateSession: false, state: "unverified", reason: "subject_scope_mismatch" });
1044
+ }
1045
+ const decision = decideCapability(snapshot, "agent_sessions.workspace.create", now(), ["native"]);
1046
+ const expiresAt = Date.parse(snapshot.expiresAt);
1047
+ return Object.freeze({
1048
+ canCreateSession: decision.status === "supported",
1049
+ state: decision.status === "unknown" ? "unverified" : "verified",
1050
+ ...(decision.status === "supported" ? {} : { reason: decision.reason }),
1051
+ ...(decision.status === "unknown" ? {} : { expiresAt }),
1052
+ });
1053
+ }
1054
+ catch {
1055
+ return Object.freeze({
1056
+ canCreateSession: false,
1057
+ state: "unverified",
1058
+ reason: "capability_discovery_unavailable",
1059
+ });
1060
+ }
1061
+ }
1062
+ async function listAllAgentSessions(client, machineId, signal) {
1063
+ const items = [];
1064
+ const cursors = new Set();
1065
+ let cursor;
1066
+ do {
1067
+ const page = await client.listAgentSessions(machineId, {
1068
+ limit: 100,
1069
+ ...(cursor === undefined ? {} : { cursor }),
1070
+ }, signal);
1071
+ items.push(...page.items);
1072
+ cursor = page.nextCursor;
1073
+ if (cursor !== undefined && cursors.has(cursor))
1074
+ throw new Error("AgentSession pagination repeated a cursor.");
1075
+ if (cursor !== undefined)
1076
+ cursors.add(cursor);
1077
+ } while (cursor !== undefined);
1078
+ return Object.freeze(items);
1079
+ }
1080
+ function mergeAgentSessionObservations(confirmed, candidates) {
1081
+ const prior = new Map(confirmed.map((session) => [session.id, session]));
1082
+ const merged = new Map();
1083
+ for (const candidate of candidates) {
1084
+ const previous = prior.get(candidate.id);
1085
+ // Successful membership is authoritative: an omitted ID is removed. For
1086
+ // IDs still present, only a strictly newer revision may replace the last
1087
+ // confirmed observation.
1088
+ merged.set(candidate.id, previous === undefined || candidate.rowVersion > previous.rowVersion ? candidate : previous);
1089
+ }
1090
+ return Object.freeze([...merged.values()]);
1091
+ }
1092
+ function renderMachinesExplorer(input) {
1093
+ if (input.closingNotice !== undefined) {
1094
+ return {
1095
+ lines: [machineHeader("Machines"), "", ` ${input.closingNotice}`, " Returning to your terminal."],
1096
+ };
1097
+ }
1098
+ if (input.navigation.screen.kind === "new-machine" || input.navigation.screen.kind === "new-machine-name") {
1099
+ return renderNewMachineScreen(input, input.navigation.screen);
1100
+ }
1101
+ if (input.navigation.screen.kind !== "machines")
1102
+ return renderContextScreen(input);
1103
+ const lines = [machineHeader("Machines"), " Your machines and the agents running inside them.", ""];
1104
+ let selectedLine;
1105
+ const offerGlobalCreation = shouldOfferGlobalCreation(input.rows, input.now, input.machinesListed);
1106
+ if (input.loadingPhase === "machines" && input.rows.length === 0) {
1107
+ lines.push(loaderLine("Discovering machines", input.animationFrame));
1108
+ }
1109
+ else if (!input.machinesListed) {
1110
+ // E13-R7: no list has succeeded yet. This is an error state, not an
1111
+ // empty inventory; the notice below carries the typed reason.
1112
+ lines.push("Machines could not be listed.");
1113
+ }
1114
+ else if (input.rows.length === 0) {
1115
+ lines.push("No machines yet. Choose a supported configuration:");
1116
+ for (const agent of providerCreationOrder()) {
1117
+ const key = `create:${agent}`;
1118
+ const selected = input.selectedKey === key;
1119
+ if (selected)
1120
+ selectedLine = lines.length;
1121
+ lines.push(`${selected ? "❯" : " "} Create ${providerDisplayName(agent)} machine`);
1122
+ }
1123
+ }
1124
+ for (const row of input.rows) {
1125
+ const open = input.expanded.has(row.machine.id);
1126
+ const machineKey = `machine:${row.machine.id}`;
1127
+ const machineSelected = input.selectedKey === machineKey;
1128
+ if (machineSelected)
1129
+ selectedLine = lines.length;
1130
+ const provider = machineProviderAvailability(row.machine);
1131
+ // One fact the reader acts on: how many agents run here. The former
1132
+ // three-provider tally (`OpenCode 0/0 live · Claude 0/0 live · Codex 0/0
1133
+ // live`) repeated on every machine, including stopped and errored ones,
1134
+ // was the loudest line on the screen and answered nothing.
1135
+ const sessions = row.sessionsLoading === true && row.sessions.length === 0
1136
+ ? "…"
1137
+ : row.sessionsError !== undefined
1138
+ ? "sessions ?"
1139
+ : row.sessions.length === 0
1140
+ ? "no sessions"
1141
+ : `${row.sessions.length} session${row.sessions.length === 1 ? "" : "s"}`;
1142
+ // `usability` is the declaration, not the verdict. Printing it raw said
1143
+ // `declared-installed` — a word the reader cannot act on, and worse, one
1144
+ // that reads as usable on exactly the machines where the next command
1145
+ // fails closed. `providerVerdict` is the reconciliation the other surfaces
1146
+ // already use, which is what makes this row agree with `machines list`.
1147
+ //
1148
+ // E13-R4: the verdict is about a runtime, so it exists only while the
1149
+ // Machine is `running`. `OpenCode ready` on a stopped or errored Machine
1150
+ // was the declaration again, wearing the verdict's clothes. E13-R3: a
1151
+ // Machine mid-transition shows the transition, not the stale state.
1152
+ const pending = row.pendingLifecycle;
1153
+ const state = pending !== undefined
1154
+ ? lifecycleLabel(pending)
1155
+ : row.machine.state === "running"
1156
+ ? `${safeLine(row.machine.state)} ${provider.displayName} ${providerVerdict(provider)}`
1157
+ : safeLine(row.machine.state);
1158
+ lines.push(`${machineSelected ? "❯" : " "} ${open ? "▾" : "▸"} ${safeLine(row.machine.name)} ${state} ${sessions}`);
1159
+ if (!open)
1160
+ continue;
1161
+ if (row.sessionsLoading === true && row.sessions.length === 0) {
1162
+ lines.push(` ${loaderLine("Loading sessions", input.animationFrame)}`);
1163
+ continue;
1164
+ }
1165
+ if (row.sessions.length === 0) {
1166
+ // E13-R5: decide the whole child list first, then assign glyphs, so
1167
+ // `├─` can only ever precede a sibling. The old per-line guesses
1168
+ // printed `├─ No AgentSessions` when the runtime line that would have
1169
+ // followed was itself suppressed on a non-running Machine.
1170
+ const children = [];
1171
+ if (row.sessionsError !== undefined)
1172
+ children.push(`${row.sessionsError}; showing last confirmed sessions`);
1173
+ else
1174
+ children.push("No sessions");
1175
+ if (row.opencodeSupervisorRepairReason !== undefined)
1176
+ children.push(openCodeRepairSummary(row));
1177
+ if (row.opencodeSupervisorProtocolUnavailable && canWaitForOpenCodeRuntime(row))
1178
+ children.push(openCodeSupervisorProtocolWaitSummary());
1179
+ // Only a running machine can be verified; on a stopped or errored one
1180
+ // the line is noise the reader cannot act on.
1181
+ if (row.opencodeRuntimeUnverified && canWaitForOpenCodeRuntime(row))
1182
+ children.push("OpenCode runtime not verified yet");
1183
+ for (const [childIndex, child] of children.entries()) {
1184
+ lines.push(` ${childIndex === children.length - 1 ? "└─" : "├─"} ${child}`);
1185
+ }
1186
+ continue;
1187
+ }
1188
+ if (row.sessionsError !== undefined)
1189
+ lines.push(` ├─ ${row.sessionsError}; showing last confirmed sessions`);
1190
+ for (const [sessionIndex, session] of row.sessions.entries()) {
1191
+ const branch = sessionIndex === row.sessions.length - 1 ? "└─" : "├─";
1192
+ const sessionKey = `session:${session.id}`;
1193
+ const sessionSelected = input.selectedKey === sessionKey;
1194
+ if (sessionSelected)
1195
+ selectedLine = lines.length;
1196
+ const actionability = classifySessionActionability({
1197
+ session,
1198
+ machine: row.machine,
1199
+ now: input.now,
1200
+ refreshStatus: row.sessionsLoading === true ? "pending" : "idle",
1201
+ });
1202
+ lines.push(`${sessionSelected ? "❯" : " "} ${branch} ${providerDisplayName(session.agent)} · ${safeLine(session.name)} ${displaySessionActionability(actionability)}`);
1203
+ lines.push(` ${session.id} · ${safeLine(session.cwd)}`);
1204
+ }
1205
+ if (row.opencodeSupervisorRepairReason !== undefined) {
1206
+ lines.push(` ${openCodeRepairSummary(row)}`);
1207
+ if (hasLegacySupervisorBlockedOpenCodeSession(row))
1208
+ lines.push(` ${legacySupervisorRecoveryRoute()}`);
1209
+ }
1210
+ if (row.opencodeSupervisorProtocolUnavailable && canWaitForOpenCodeRuntime(row)) {
1211
+ lines.push(` ${openCodeSupervisorProtocolWaitSummary()}`);
1212
+ }
1213
+ if (row.opencodeRuntimeUnverified && canWaitForOpenCodeRuntime(row)) {
1214
+ lines.push(" OpenCode runtime not verified yet");
1215
+ }
1216
+ }
1217
+ const capacityNotice = overviewSessionCreateCapacityNotice(input.rows);
1218
+ if (capacityNotice !== undefined)
1219
+ lines.push("", capacityNotice);
1220
+ if (input.rows.length > 0 && offerGlobalCreation) {
1221
+ lines.push("", "No available machine can open an AgentSession. Create a supported machine:");
1222
+ for (const agent of providerCreationOrder()) {
1223
+ const key = `create:${agent}`;
1224
+ const selected = input.selectedKey === key;
1225
+ if (selected)
1226
+ selectedLine = lines.length;
1227
+ lines.push(`${selected ? "❯" : " "} Create ${providerDisplayName(agent)} machine`);
1228
+ }
1229
+ }
1230
+ if (input.loadingPhase === "sessions" && input.rows.length > 0) {
1231
+ lines.push("", loaderLine("Refreshing live sessions", input.animationFrame));
1232
+ }
1233
+ if (input.refreshError !== undefined)
1234
+ lines.push("", input.refreshError);
1235
+ if (input.interactionNotice !== undefined)
1236
+ lines.push("", input.interactionNotice);
1237
+ if (input.lifecycleNotice !== undefined)
1238
+ lines.push("", ` ${input.lifecycleNotice}`);
1239
+ lines.push("", overviewFooter(input.rows, input.selectedKey, input.now));
1240
+ return Object.freeze({
1241
+ lines: Object.freeze(lines.map((line) => truncateTerminalLine(line, input.columns))),
1242
+ ...(selectedLine === undefined ? {} : { selectedLine }),
1243
+ });
1244
+ }
1245
+ function renderContextScreen(input) {
1246
+ const screen = input.navigation.screen;
1247
+ if (screen.kind !== "machine" && screen.kind !== "provider")
1248
+ return Object.freeze({ lines: Object.freeze([]) });
1249
+ const row = input.rows.find((candidate) => candidate.machine.id === screen.machineId);
1250
+ const lines = [machineHeader("Machine"), ""];
1251
+ if (row === undefined) {
1252
+ lines.push("Machine observation is no longer available.", "", " ←/Esc/Backspace back · q quit");
1253
+ return Object.freeze({ lines: Object.freeze(lines.map((line) => truncateTerminalLine(line, input.columns))) });
1254
+ }
1255
+ const pending = row.pendingLifecycle;
1256
+ lines[0] = machineHeader(screen.kind === "provider"
1257
+ ? `${safeLine(row.machine.name)} / ${providerDisplayName(screen.provider)} sessions`
1258
+ : safeLine(row.machine.name));
1259
+ lines[1] = ` ${pending === undefined ? safeLine(row.machine.state) : lifecycleLabel(pending)} · record updated ${safeLine(row.machine.updatedAt ?? "time unavailable")}`;
1260
+ lines.push("");
1261
+ const actions = pending !== undefined
1262
+ ? []
1263
+ : screen.kind === "machine"
1264
+ ? machineContextActions(row, input.now)
1265
+ : resolveProviderContextActions({
1266
+ machine: row.machine,
1267
+ provider: screen.provider,
1268
+ sessions: row.sessions,
1269
+ now: input.now,
1270
+ });
1271
+ let selectedLine;
1272
+ if (pending !== undefined) {
1273
+ lines.push(` ${lifecycleLabel(pending)} Cuna is reading the Machine back until it settles; this screen stays open.`);
1274
+ }
1275
+ else if (actions.length === 0) {
1276
+ lines.push(" No available actions for this observation.");
1277
+ }
1278
+ for (const action of actions) {
1279
+ const key = screen.kind === "machine"
1280
+ ? machineActionSelectionKey(action)
1281
+ : providerActionSelectionKey(action);
1282
+ const selected = input.selectedKey === key;
1283
+ if (selected)
1284
+ selectedLine = lines.length;
1285
+ const detail = action.kind === "session"
1286
+ ? displaySessionActionability(classifySessionActionability({ session: action.session, machine: row.machine, now: input.now }))
1287
+ : action.kind === "provider" ? "sessions" : "";
1288
+ const label = action.kind === "start" || action.kind === "stop" || action.kind === "delete" ? `${action.label} machine` : action.label;
1289
+ lines.push(`${selected ? "❯" : " "} ${label}${detail === "" ? "" : ` ${detail}`}`);
1290
+ }
1291
+ if (row.opencodeSupervisorRepairReason !== undefined) {
1292
+ lines.push("");
1293
+ lines.push(` ${openCodeRepairSummary(row)}`);
1294
+ if (row.machine.state === "stopped") {
1295
+ lines.push(" Select Update terminal supervisor, then press Enter again to confirm.");
1296
+ }
1297
+ else if (hasLegacySupervisorBlockedOpenCodeSession(row)) {
1298
+ lines.push(` ${legacySupervisorRecoveryRoute()}`);
1299
+ }
1300
+ else {
1301
+ lines.push(" Protected: stop this Machine yourself after ending only the sessions you no longer need.");
1302
+ }
1303
+ }
1304
+ if (row.opencodeSupervisorProtocolUnavailable && canWaitForOpenCodeRuntime(row)) {
1305
+ lines.push("");
1306
+ lines.push(` ${openCodeSupervisorProtocolWaitSummary()}`);
1307
+ lines.push(" Keep this Machine running; Cuna refreshes status automatically. Press r to check now.");
1308
+ }
1309
+ if (row.opencodeRuntimeUnverified && canWaitForOpenCodeRuntime(row)) {
1310
+ lines.push("");
1311
+ lines.push(" Checking OpenCode runtime. No new OpenCode session was requested.");
1312
+ lines.push(" Keep this Machine running; Cuna refreshes status automatically. Press r to check now.");
1313
+ }
1314
+ const capacityNotice = sessionCreateCapacityNotice(row);
1315
+ if (capacityNotice !== undefined) {
1316
+ lines.push("");
1317
+ lines.push(` ${capacityNotice}`);
1318
+ }
1319
+ if (input.loadingPhase !== undefined)
1320
+ lines.push("", loaderLine("Refreshing machine", input.animationFrame));
1321
+ if (input.refreshError !== undefined)
1322
+ lines.push("", input.refreshError);
1323
+ if (input.interactionNotice !== undefined)
1324
+ lines.push("", input.interactionNotice);
1325
+ if (input.lifecycleNotice !== undefined)
1326
+ lines.push("", ` ${input.lifecycleNotice}`);
1327
+ lines.push("", " p Check provider · w Workspaces · e Executions", " ↑↓ move · ←→ navigate · Enter select · Esc/Backspace back · q quit");
1328
+ return Object.freeze({
1329
+ lines: Object.freeze(lines.map((line) => truncateTerminalLine(line, input.columns))),
1330
+ ...(selectedLine === undefined ? {} : { selectedLine }),
1331
+ });
1332
+ }
1333
+ function selectVisibleLines(frame, terminalRows) {
1334
+ const height = Number.isSafeInteger(terminalRows) ? Math.max(1, terminalRows) : 1;
1335
+ if (frame.lines.length <= height)
1336
+ return frame.lines;
1337
+ const selectedLine = frame.selectedLine;
1338
+ if (selectedLine === undefined)
1339
+ return Object.freeze(frame.lines.slice(0, height));
1340
+ if (height === 1)
1341
+ return Object.freeze([frame.lines[selectedLine] ?? ""]);
1342
+ const fixedCount = Math.min(3, height - 1);
1343
+ const bodyHeight = height - fixedCount;
1344
+ const bodyStart = 3;
1345
+ const latestStart = Math.max(bodyStart, frame.lines.length - bodyHeight);
1346
+ const windowStart = Math.min(Math.max(bodyStart, selectedLine - bodyHeight + 1), latestStart);
1347
+ return Object.freeze([
1348
+ ...frame.lines.slice(0, fixedCount),
1349
+ ...frame.lines.slice(windowStart, windowStart + bodyHeight),
1350
+ ]);
1351
+ }
1352
+ function machineContextActions(row, now) {
1353
+ // A Machine mid-transition accepts no second action until it settles.
1354
+ if (row.pendingLifecycle !== undefined)
1355
+ return Object.freeze([]);
1356
+ const provider = machineProviderAvailability(row.machine);
1357
+ const hasSessions = provider.actionable && provider.agent !== undefined
1358
+ ? resolveProviderContextActions({
1359
+ machine: row.machine,
1360
+ provider: provider.agent,
1361
+ sessions: row.sessions,
1362
+ now,
1363
+ }).length > 0
1364
+ : false;
1365
+ return resolveMachineContextActions(row.machine, {
1366
+ hasSessions,
1367
+ canCreateSession: row.canCreateSession === true,
1368
+ opencodeSupervisorRepairRequired: row.opencodeSupervisorRepairReason !== undefined,
1369
+ });
1370
+ }
1371
+ function hasOpenableSession(row, now) {
1372
+ return row.sessions.some((session) => {
1373
+ if (!isActionableProvider(session.agent))
1374
+ return false;
1375
+ const actionability = classifySessionActionability({ session, machine: row.machine, now });
1376
+ return actionability.canAttach || actionability.recoveryAction === "authenticate";
1377
+ });
1378
+ }
1379
+ function waitingForSessionObservation(session) {
1380
+ return `${providerDisplayName(session.agent)} is starting. Waiting for its first process observation; refreshing automatically — r to refresh now.`;
1381
+ }
1382
+ function shouldOfferGlobalCreation(rows, now, machinesListed) {
1383
+ // E13-R7: an empty list is a create affordance only once a list succeeded.
1384
+ if (!machinesListed)
1385
+ return false;
1386
+ if (rows.length === 0)
1387
+ return true;
1388
+ return rows.every((row) => {
1389
+ // A requested transition has its own convergence path. It is not yet
1390
+ // evidence that this Machine is unusable and a replacement is needed.
1391
+ if (row.pendingLifecycle !== undefined)
1392
+ return false;
1393
+ // `temporarily_unavailable` is not evidence that another Machine is
1394
+ // needed. Do not turn a runtime verification wait into a misleading
1395
+ // create affordance (and a possible extra billable resource).
1396
+ if (row.opencodeRuntimeUnverified && canWaitForOpenCodeRuntime(row))
1397
+ return false;
1398
+ // A current supervisor-repair prerequisite names the existing Machine
1399
+ // that needs attention. Offering a global OpenCode create beside it turns
1400
+ // a safe repair into an accidental extra Machine.
1401
+ if (row.opencodeSupervisorRepairReason !== undefined)
1402
+ return false;
1403
+ // An unannounced supervisor is a transient observation wait. It is not
1404
+ // proof that another Machine is needed or that this one should be changed.
1405
+ if (row.opencodeSupervisorProtocolUnavailable && canWaitForOpenCodeRuntime(row))
1406
+ return false;
1407
+ if (row.sessionsLoading === true)
1408
+ return false;
1409
+ if (row.sessionsError !== undefined)
1410
+ return false;
1411
+ if (row.sessionCreateCapabilityState !== "verified")
1412
+ return false;
1413
+ return !machineContextActions(row, now).some((action) => action.kind === "start" || action.kind === "provider" || action.kind === "new-session") && !hasOpenableSession(row, now);
1414
+ });
1415
+ }
1416
+ function overviewFooter(rows, selectedKey, now) {
1417
+ // E13-R1: `n new machine` is on every overview footer, whatever is selected.
1418
+ const tail = "p Check provider · n new machine · r refresh · q quit";
1419
+ if (selectedKey?.startsWith("machine:") === true) {
1420
+ return ` ↑↓ move · Enter/→ manage machine · ${tail}`;
1421
+ }
1422
+ if (selectedKey?.startsWith("session:") === true) {
1423
+ const sessionId = selectedKey.slice("session:".length);
1424
+ const row = rows.find((candidate) => candidate.sessions.some((session) => session.id === sessionId));
1425
+ const session = row?.sessions.find((candidate) => candidate.id === sessionId);
1426
+ if (row !== undefined && session !== undefined && isActionableProvider(session.agent)) {
1427
+ const actionability = classifySessionActionability({ session, machine: row.machine, now });
1428
+ if (actionability.canAttach || actionability.recoveryAction === "authenticate") {
1429
+ return ` ↑↓ move · Enter/→ attach ${providerDisplayName(session.agent)} · ${tail}`;
1430
+ }
1431
+ if (actionability.recoveryAction === "refresh") {
1432
+ return ` ↑↓ move · Enter refresh session · ← back · ${tail}`;
1433
+ }
1434
+ if (actionability.recoveryAction === "wait") {
1435
+ return hasLegacySupervisorBlockedOpenCodeSession(row) && session.agent === "opencode" &&
1436
+ isUnobservedLaunchedSession(session)
1437
+ ? ` ↑↓ move · Legacy supervisor blocked · ← back · ${tail}`
1438
+ : ` ↑↓ move · Waiting for process observation · ← back · ${tail}`;
1439
+ }
1440
+ return ` ↑↓ move · Enter session details · ← back · ${tail}`;
1441
+ }
1442
+ }
1443
+ if (selectedKey?.startsWith("create:") === true) {
1444
+ return ` ↑↓ move · Enter create machine · ${tail}`;
1445
+ }
1446
+ return ` ↑↓ move · ←→ navigate · Enter open · ${tail}`;
1447
+ }
1448
+ function renderNewMachineScreen(input, screen) {
1449
+ const lines = [];
1450
+ let selectedLine;
1451
+ if (screen.kind === "new-machine") {
1452
+ lines.push(machineHeader("New machine"), " Choose the agent this Machine runs.", "");
1453
+ if (input.newMachine.capability === "checking") {
1454
+ lines.push(` ${loaderLine("Checking whether this account can create a Machine", input.animationFrame)}`);
1455
+ }
1456
+ else if (input.newMachine.capability === "unavailable") {
1457
+ // E13-R1 negative control: the reason is shown, never hidden, and
1458
+ // nothing below is selectable, so nothing can be created.
1459
+ lines.push(` New machine is not available for this account: ${safeLine(input.newMachine.reason)}`);
1460
+ lines.push(" Nothing was created.");
1461
+ }
1462
+ else {
1463
+ for (const agent of providerCreationOrder()) {
1464
+ const key = `new-machine:${agent}`;
1465
+ const selected = input.selectedKey === key;
1466
+ if (selected)
1467
+ selectedLine = lines.length;
1468
+ lines.push(`${selected ? "❯" : " "} ${providerDisplayName(agent)}`);
1469
+ }
1470
+ }
1471
+ if (input.interactionNotice !== undefined)
1472
+ lines.push("", input.interactionNotice);
1473
+ lines.push("", " ↑↓ move · Enter choose · Esc/Backspace back · q quit");
1474
+ }
1475
+ else {
1476
+ lines.push(machineHeader(`New machine / ${providerDisplayName(screen.provider)}`), " Name this Machine.", "");
1477
+ selectedLine = lines.length;
1478
+ lines.push(`❯ ${input.newMachineName}▏`);
1479
+ if (input.interactionNotice !== undefined)
1480
+ lines.push("", input.interactionNotice);
1481
+ lines.push("", " type to edit · Backspace delete", " Enter create · Esc back");
1482
+ }
1483
+ return Object.freeze({
1484
+ lines: Object.freeze(lines.map((line) => truncateTerminalLine(line, input.columns))),
1485
+ ...(selectedLine === undefined ? {} : { selectedLine }),
1486
+ });
1487
+ }
1488
+ function lifecycleLabel(action) {
1489
+ if (action === "recover")
1490
+ return "Recovering creation…";
1491
+ return action === "start" ? "Starting…" : action === "stop" ? "Stopping…" : "Deleting…";
1492
+ }
1493
+ /**
1494
+ * The default name the console would suggest: `cuna-<provider>-<n>`, where
1495
+ * `n` is one more than the Machines already carrying that prefix.
1496
+ */
1497
+ function defaultMachineName(provider, existingNames) {
1498
+ const slug = provider === "claude-code" ? "claude" : provider;
1499
+ const prefix = `cuna-${slug}-`;
1500
+ const count = existingNames.filter((name) => name.startsWith(prefix)).length;
1501
+ return `${prefix}${count + 1}`;
1502
+ }
1503
+ /**
1504
+ * One typed line for a lifecycle request the screen could not complete. The
1505
+ * code is the server's or the CLI's own; the message never invents a cause.
1506
+ */
1507
+ function typedLifecycleFailure(error, name, action) {
1508
+ const verb = action === "start" ? "start" : action === "stop" ? "stop" : "delete";
1509
+ if (error instanceof CunaError) {
1510
+ return `${error.code}: could not ${verb} ${name}. ${safeLine(error.message)}${error.hint === undefined ? "" : ` ${safeLine(error.hint)}`}`;
1511
+ }
1512
+ return `Could not ${verb} ${name}: unexpected error. Run \`cuna machines list\` to see its current state.`;
1513
+ }
1514
+ /**
1515
+ * Same question the batch commands ask (`requireCapability`), answered as a
1516
+ * value so the screen can render the refusal instead of throwing out of it.
1517
+ */
1518
+ async function decideExplorerCapability(client, scope, resourceId, capabilityId, now, signal) {
1519
+ if (typeof client.discoverCapabilities !== "function") {
1520
+ return Object.freeze({ status: "unknown", reason: "capability_discovery_unavailable" });
1521
+ }
1522
+ try {
1523
+ const snapshot = await client.discoverCapabilities(scope, resourceId, signal);
1524
+ if (snapshot.subjectScope !== scope || (scope !== "account" && snapshot.subjectId !== resourceId)) {
1525
+ return Object.freeze({ status: "unknown", reason: "subject_scope_mismatch" });
1526
+ }
1527
+ const decision = decideCapability(snapshot, capabilityId, now());
1528
+ return decision.status === "supported"
1529
+ ? Object.freeze({ status: "supported" })
1530
+ : Object.freeze({ status: decision.status, ...(decision.reason === undefined ? {} : { reason: decision.reason }) });
1531
+ }
1532
+ catch (error) {
1533
+ return Object.freeze({
1534
+ status: "unknown",
1535
+ reason: error instanceof CunaError ? error.code : "capability_discovery_unavailable",
1536
+ });
1537
+ }
1538
+ }
1539
+ function selectableKeys(rows, expanded, now, navigation, machinesListed) {
1540
+ if (navigation.screen.kind === "new-machine") {
1541
+ return Object.freeze(providerCreationOrder().map((agent) => `new-machine:${agent}`));
1542
+ }
1543
+ if (navigation.screen.kind === "new-machine-name")
1544
+ return Object.freeze(["new-machine-name"]);
1545
+ if (navigation.screen.kind === "machine") {
1546
+ const screen = navigation.screen;
1547
+ const row = rows.find((candidate) => candidate.machine.id === screen.machineId);
1548
+ return row === undefined
1549
+ ? Object.freeze([])
1550
+ : Object.freeze(machineContextActions(row, now).map(machineActionSelectionKey));
1551
+ }
1552
+ if (navigation.screen.kind === "provider") {
1553
+ const screen = navigation.screen;
1554
+ const row = rows.find((candidate) => candidate.machine.id === screen.machineId);
1555
+ return row === undefined
1556
+ ? Object.freeze([])
1557
+ : Object.freeze(resolveProviderContextActions({
1558
+ machine: row.machine,
1559
+ provider: screen.provider,
1560
+ sessions: row.sessions,
1561
+ now,
1562
+ }).map(providerActionSelectionKey));
1563
+ }
1564
+ const machineKeys = rows.flatMap((row) => [
1565
+ `machine:${row.machine.id}`,
1566
+ ...(expanded.has(row.machine.id)
1567
+ ? row.sessions
1568
+ .filter((session) => isActionableProvider(session.agent))
1569
+ .map((session) => `session:${session.id}`)
1570
+ : []),
1571
+ ]);
1572
+ const creationKeys = shouldOfferGlobalCreation(rows, now, machinesListed)
1573
+ ? providerCreationOrder().map((agent) => `create:${agent}`)
1574
+ : [];
1575
+ return Object.freeze([...machineKeys, ...creationKeys]);
1576
+ }
1577
+ function machineActionSelectionKey(action) {
1578
+ return action.kind === "provider"
1579
+ ? `machine-provider:${action.provider}`
1580
+ : action.kind === "new-session"
1581
+ ? `machine-create:${action.provider}`
1582
+ : action.kind === "supervisor-blocked"
1583
+ ? "machine-supervisor:blocked"
1584
+ : action.kind === "update-supervisor"
1585
+ ? "machine-supervisor:update"
1586
+ : `machine-lifecycle:${action.kind}`;
1587
+ }
1588
+ function providerActionSelectionKey(action) {
1589
+ return `provider-session:${action.session.id}`;
1590
+ }
1591
+ function isActionableProvider(provider) {
1592
+ return provider === "claude-code" || provider === "codex" || provider === "opencode";
1593
+ }
1594
+ function providerCreationOrder() {
1595
+ return Object.freeze(["opencode", "claude-code", "codex"]);
1596
+ }
1597
+ function sessionCreateCapacityNotice(row) {
1598
+ if (row.sessionCreateCapabilityState === "checking") {
1599
+ return "Checking whether this Machine can start a new session; existing sessions stay available.";
1600
+ }
1601
+ if (row.sessionCreateCapabilityState === "unverified") {
1602
+ return "New-session capacity cannot be verified; Cuna will not offer a create action.";
1603
+ }
1604
+ return undefined;
1605
+ }
1606
+ function overviewSessionCreateCapacityNotice(rows) {
1607
+ if (rows.some((row) => row.sessionCreateCapabilityState === "unverified")) {
1608
+ return "New-session capacity cannot be verified; Cuna will not offer a create action.";
1609
+ }
1610
+ if (rows.some((row) => row.sessionCreateCapabilityState === "checking")) {
1611
+ return "Checking whether these Machines can start a new session; existing sessions stay available.";
1612
+ }
1613
+ return undefined;
1614
+ }
1615
+ function isRetryableExplorerRefreshError(error) {
1616
+ return (error instanceof CredentialBoundaryError || error instanceof CunaError) && error.retryable;
1617
+ }
1618
+ /**
1619
+ * E13-R7. One line: what is unavailable, the typed reason the client already
1620
+ * carries, and whether Cuna is still retrying by itself. The server's own
1621
+ * sentence is not repeated here; `cuna machines list` prints it in full.
1622
+ */
1623
+ function listFailureNotice(error, hasRows, retryDelayMs) {
1624
+ const code = error instanceof CunaError || error instanceof CredentialBoundaryError ? error.code : "unknown_failure";
1625
+ const status = error instanceof CunaError ? error.details?.http_status : undefined;
1626
+ const reason = typeof status === "number" ? `${code} (http ${status})` : code;
1627
+ // Kept short: the whole line must survive a 120-column truncation.
1628
+ const subject = hasRows
1629
+ ? "refresh failed; showing last confirmed machines"
1630
+ : "machines could not be listed";
1631
+ const retry = retryDelayMs === undefined
1632
+ ? "Press r to retry."
1633
+ : `Retrying in ${Math.max(1, Math.round(retryDelayMs / 1000))} s; r retries now.`;
1634
+ return ` ${reason}: ${subject}. ${retry}`;
1635
+ }
1636
+ function loaderLine(label, frame) {
1637
+ return `${SPINNER[frame % SPINNER.length]} ${label} ${PROGRESS[frame % PROGRESS.length]}`;
1638
+ }
1639
+ function machineHeader(title) {
1640
+ return ` CUNA ◆── ${title}`;
1641
+ }
1642
+ function paintMachinesExplorer(lines, columns, color) {
1643
+ if (!color)
1644
+ return lines;
1645
+ return Object.freeze(lines.map((line, index) => {
1646
+ if (index === 0 && line.startsWith(" CUNA ")) {
1647
+ const brand = line.slice(0, Math.min(6, line.length));
1648
+ const context = line.slice(brand.length);
1649
+ const padding = " ".repeat(Math.max(0, columns - terminalCellWidth(line)));
1650
+ return `${ANSI.flareBackground}${ANSI.ground}${ANSI.bold}${brand}${ANSI.reset}${ANSI.emberBackground}${ANSI.cream}${ANSI.bold}${context}${padding}${ANSI.reset}`;
1651
+ }
1652
+ if (line.startsWith("❯"))
1653
+ return `${ANSI.orange}${ANSI.bold}${paintStatus(line)}${ANSI.reset}`;
1654
+ if (line.startsWith(" ▾") || line.startsWith(" ▸") || line.startsWith(" ▾") || line.startsWith(" ▸")) {
1655
+ return `${ANSI.bold}${ANSI.orange}${paintStatus(line)}${ANSI.reset}`;
1656
+ }
1657
+ if (line.includes("Blocked by legacy supervisor")) {
1658
+ return `${ANSI.red}${ANSI.bold}${paintStatus(line)}${ANSI.reset}`;
1659
+ }
1660
+ if (line.includes("Route: end stale") || line.includes("Protected:") || line.includes("needs a terminal update")) {
1661
+ return `${ANSI.orange}${ANSI.bold}${paintStatus(line)}${ANSI.reset}`;
1662
+ }
1663
+ if (line.includes("Claude") || line.includes("Codex") || line.includes("OpenCode"))
1664
+ return `${ANSI.cyan}${paintStatus(line)}${ANSI.reset}`;
1665
+ if (line.includes("Discovering") || line.includes("Loading") || line.includes("Refreshing")) {
1666
+ return `${ANSI.orange}${line}${ANSI.reset}`;
1667
+ }
1668
+ if (line.includes("Closing Cuna"))
1669
+ return `${ANSI.orange}${ANSI.bold}${line}${ANSI.reset}`;
1670
+ if (line.includes("✓ Closed"))
1671
+ return `${ANSI.green}${ANSI.bold}${line}${ANSI.reset}`;
1672
+ if (/^[ ]{7}[0-9a-f-]{36}/u.test(line))
1673
+ return `${ANSI.gray}${ANSI.dim}${line}${ANSI.reset}`;
1674
+ if (line.startsWith(" Your machines") || line.includes("↑↓ move"))
1675
+ return `${ANSI.gray}${line}${ANSI.reset}`;
1676
+ return paintStatus(line);
1677
+ }));
1678
+ }
1679
+ function paintStatus(line) {
1680
+ return line
1681
+ .replaceAll("running", `${ANSI.green}running${ANSI.reset}`)
1682
+ .replaceAll("attachable", `${ANSI.green}attachable${ANSI.reset}`)
1683
+ .replaceAll("checking", `${ANSI.orange}checking${ANSI.reset}`)
1684
+ .replaceAll("starting", `${ANSI.orange}starting${ANSI.reset}`)
1685
+ .replaceAll("login-required", `${ANSI.orange}login-required${ANSI.reset}`)
1686
+ .replaceAll("stale", `${ANSI.orange}stale${ANSI.reset}`)
1687
+ .replaceAll("failed", `${ANSI.red}failed${ANSI.reset}`)
1688
+ .replaceAll("unsupported", `${ANSI.gray}unsupported${ANSI.reset}`);
1689
+ }
1690
+ function openCodeRepairSummary(row) {
1691
+ // This capability is evaluated only for a new AgentSession. An existing
1692
+ // child can be unobserved for a different reason, so keep the two messages
1693
+ // distinct. A launched-but-unknown child plus a legacy capability is the
1694
+ // one proved deadlock: further refreshing cannot create its observation.
1695
+ return hasLegacySupervisorBlockedOpenCodeSession(row)
1696
+ ? "Blocked by legacy supervisor — waiting will not fix this"
1697
+ : "New OpenCode sessions are blocked until the terminal supervisor is updated";
1698
+ }
1699
+ function openCodeSupervisorProtocolWaitSummary() {
1700
+ return "Waiting for this Machine's OpenCode terminal supervisor; no new OpenCode session was requested";
1701
+ }
1702
+ function canWaitForOpenCodeRuntime(row) {
1703
+ // Preserve the capability observation, but apply its wait advice only to
1704
+ // a running Machine with no lifecycle request already changing that state.
1705
+ return row.machine.state === "running" && row.pendingLifecycle === undefined;
1706
+ }
1707
+ function isUnobservedLaunchedSession(session) {
1708
+ return session.agent === "opencode" && session.requestState === "launched" && session.processState === "unknown";
1709
+ }
1710
+ function hasLegacySupervisorBlockedOpenCodeSession(row) {
1711
+ return row.opencodeSupervisorRepairReason !== undefined &&
1712
+ row.sessions.some(isUnobservedLaunchedSession);
1713
+ }
1714
+ function legacySupervisorRecoveryRoute() {
1715
+ return "Route: end stale OpenCode session → stop Machine → Update terminal supervisor.";
1716
+ }
1717
+ function legacySupervisorBlockedNotice() {
1718
+ return `${openCodeRepairSummaryForLegacy()}\n${legacySupervisorRecoveryRoute()}`;
1719
+ }
1720
+ function openCodeRepairSummaryForLegacy() {
1721
+ return "Blocked by legacy supervisor — waiting will not fix this.";
1722
+ }
1723
+ function safeLine(value) {
1724
+ return sanitizeHumanTerminalOutput(value).replaceAll("\n", " ").replaceAll("\t", " ");
1725
+ }
1726
+ //# sourceMappingURL=explorer.js.map