@songsid/agend 2.1.12 → 2.2.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (436) hide show
  1. package/README.md +4 -2
  2. package/README.zh-TW.md +4 -2
  3. package/dist/agent-cli.js +6 -1
  4. package/dist/agent-cli.js.map +1 -1
  5. package/dist/agent-endpoint.js +68 -12
  6. package/dist/agent-endpoint.js.map +1 -1
  7. package/dist/agent-token-header.d.ts +13 -0
  8. package/dist/agent-token-header.js +16 -0
  9. package/dist/agent-token-header.js.map +1 -0
  10. package/dist/auth/one-time-code.d.ts +19 -0
  11. package/dist/auth/one-time-code.js +49 -0
  12. package/dist/auth/one-time-code.js.map +1 -0
  13. package/dist/auth-api.d.ts +52 -0
  14. package/dist/auth-api.js +346 -0
  15. package/dist/auth-api.js.map +1 -0
  16. package/dist/backend/antigravity.js +4 -3
  17. package/dist/backend/antigravity.js.map +1 -1
  18. package/dist/backend/claude-code.d.ts +4 -0
  19. package/dist/backend/claude-code.js +97 -0
  20. package/dist/backend/claude-code.js.map +1 -1
  21. package/dist/backend/codex.js +59 -30
  22. package/dist/backend/codex.js.map +1 -1
  23. package/dist/backend/grok.js +1 -1
  24. package/dist/backend/grok.js.map +1 -1
  25. package/dist/backend/kiro-agent.d.ts +66 -0
  26. package/dist/backend/kiro-agent.js +302 -0
  27. package/dist/backend/kiro-agent.js.map +1 -0
  28. package/dist/backend/kiro-identity.d.ts +80 -0
  29. package/dist/backend/kiro-identity.js +537 -0
  30. package/dist/backend/kiro-identity.js.map +1 -0
  31. package/dist/backend/kiro-v2-store-worker.d.ts +1 -0
  32. package/dist/backend/kiro-v2-store-worker.js +87 -0
  33. package/dist/backend/kiro-v2-store-worker.js.map +1 -0
  34. package/dist/backend/kiro-v2-store.d.ts +54 -0
  35. package/dist/backend/kiro-v2-store.js +131 -0
  36. package/dist/backend/kiro-v2-store.js.map +1 -0
  37. package/dist/backend/kiro-v3-identity.d.ts +5 -0
  38. package/dist/backend/kiro-v3-identity.js +22 -0
  39. package/dist/backend/kiro-v3-identity.js.map +1 -1
  40. package/dist/backend/kiro.d.ts +90 -7
  41. package/dist/backend/kiro.js +500 -124
  42. package/dist/backend/kiro.js.map +1 -1
  43. package/dist/backend/muse.js +1 -1
  44. package/dist/backend/muse.js.map +1 -1
  45. package/dist/backend/types.d.ts +51 -3
  46. package/dist/backend/types.js.map +1 -1
  47. package/dist/cache-analysis.d.ts +92 -0
  48. package/dist/cache-analysis.js +147 -0
  49. package/dist/cache-analysis.js.map +1 -0
  50. package/dist/cache-ledger.d.ts +108 -0
  51. package/dist/cache-ledger.js +230 -0
  52. package/dist/cache-ledger.js.map +1 -0
  53. package/dist/cache-prices.d.ts +32 -0
  54. package/dist/cache-prices.js +73 -0
  55. package/dist/cache-prices.js.map +1 -0
  56. package/dist/cache-scan.d.ts +39 -0
  57. package/dist/cache-scan.js +115 -0
  58. package/dist/cache-scan.js.map +1 -0
  59. package/dist/cache-service.d.ts +115 -0
  60. package/dist/cache-service.js +417 -0
  61. package/dist/cache-service.js.map +1 -0
  62. package/dist/channel/adapters/discord.d.ts +1 -0
  63. package/dist/channel/adapters/discord.js +29 -10
  64. package/dist/channel/adapters/discord.js.map +1 -1
  65. package/dist/channel/adapters/telegram.d.ts +1 -0
  66. package/dist/channel/adapters/telegram.js +13 -1
  67. package/dist/channel/adapters/telegram.js.map +1 -1
  68. package/dist/channel/types.d.ts +9 -1
  69. package/dist/classic-channel-manager.d.ts +18 -10
  70. package/dist/classic-channel-manager.js +45 -18
  71. package/dist/classic-channel-manager.js.map +1 -1
  72. package/dist/cli-entry.d.ts +19 -0
  73. package/dist/cli-entry.js +36 -0
  74. package/dist/cli-entry.js.map +1 -0
  75. package/dist/cli.js +471 -146
  76. package/dist/cli.js.map +1 -1
  77. package/dist/command-table.d.ts +5 -3
  78. package/dist/command-table.js +29 -27
  79. package/dist/command-table.js.map +1 -1
  80. package/dist/config-validator.js +46 -0
  81. package/dist/config-validator.js.map +1 -1
  82. package/dist/config.js +12 -0
  83. package/dist/config.js.map +1 -1
  84. package/dist/connection-secrets.d.ts +6 -0
  85. package/dist/connection-secrets.js +4 -0
  86. package/dist/connection-secrets.js.map +1 -1
  87. package/dist/daemon.d.ts +95 -4
  88. package/dist/daemon.js +748 -111
  89. package/dist/daemon.js.map +1 -1
  90. package/dist/darwin-memory.d.ts +6 -1
  91. package/dist/darwin-memory.js +17 -6
  92. package/dist/darwin-memory.js.map +1 -1
  93. package/dist/delivery-consumed-watch.d.ts +85 -0
  94. package/dist/delivery-consumed-watch.js +244 -0
  95. package/dist/delivery-consumed-watch.js.map +1 -0
  96. package/dist/delivery-outbox.d.ts +61 -0
  97. package/dist/delivery-outbox.js +120 -2
  98. package/dist/delivery-outbox.js.map +1 -1
  99. package/dist/delivery-reconciliation.d.ts +14 -5
  100. package/dist/delivery-reconciliation.js +27 -4
  101. package/dist/delivery-reconciliation.js.map +1 -1
  102. package/dist/delivery-reconciliation.test.js +35 -1
  103. package/dist/delivery-reconciliation.test.js.map +1 -1
  104. package/dist/event-log.d.ts +4 -1
  105. package/dist/event-log.js +6 -3
  106. package/dist/event-log.js.map +1 -1
  107. package/dist/event-loop-watch.d.ts +28 -0
  108. package/dist/event-loop-watch.js +75 -3
  109. package/dist/event-loop-watch.js.map +1 -1
  110. package/dist/fleet-context.d.ts +22 -2
  111. package/dist/fleet-control-audit.d.ts +5 -0
  112. package/dist/fleet-control-audit.js +7 -0
  113. package/dist/fleet-control-audit.js.map +1 -1
  114. package/dist/fleet-level-config.d.ts +2 -2
  115. package/dist/fleet-level-config.js +28 -1
  116. package/dist/fleet-level-config.js.map +1 -1
  117. package/dist/fleet-manager.d.ts +339 -12
  118. package/dist/fleet-manager.js +2623 -461
  119. package/dist/fleet-manager.js.map +1 -1
  120. package/dist/full-restart.js +2 -3
  121. package/dist/full-restart.js.map +1 -1
  122. package/dist/general-knowledge/skills/persona-emoji/SKILL.md +3 -1
  123. package/dist/host-memory.d.ts +4 -0
  124. package/dist/host-memory.js.map +1 -1
  125. package/dist/install-lock.d.ts +49 -0
  126. package/dist/install-lock.js +200 -0
  127. package/dist/install-lock.js.map +1 -0
  128. package/dist/instance-config-impact.js +8 -2
  129. package/dist/instance-config-impact.js.map +1 -1
  130. package/dist/instance-lifecycle.d.ts +10 -5
  131. package/dist/instance-lifecycle.js +162 -53
  132. package/dist/instance-lifecycle.js.map +1 -1
  133. package/dist/locale.js +164 -16
  134. package/dist/locale.js.map +1 -1
  135. package/dist/login-controller.d.ts +2 -0
  136. package/dist/login-controller.js +17 -5
  137. package/dist/login-controller.js.map +1 -1
  138. package/dist/memory-pressure.d.ts +7 -8
  139. package/dist/memory-pressure.js +76 -15
  140. package/dist/memory-pressure.js.map +1 -1
  141. package/dist/monotonic-budget.d.ts +2 -0
  142. package/dist/monotonic-budget.js +28 -0
  143. package/dist/monotonic-budget.js.map +1 -0
  144. package/dist/needs-you-hub.d.ts +114 -0
  145. package/dist/needs-you-hub.js +370 -0
  146. package/dist/needs-you-hub.js.map +1 -0
  147. package/dist/needs-you-live.d.ts +116 -0
  148. package/dist/needs-you-live.js +228 -0
  149. package/dist/needs-you-live.js.map +1 -0
  150. package/dist/needs-you.d.ts +132 -0
  151. package/dist/needs-you.js +164 -0
  152. package/dist/needs-you.js.map +1 -0
  153. package/dist/node-version-guard.d.ts +30 -0
  154. package/dist/node-version-guard.js +50 -0
  155. package/dist/node-version-guard.js.map +1 -0
  156. package/dist/operator-control-paths.d.ts +4 -0
  157. package/dist/operator-control-paths.js +24 -0
  158. package/dist/operator-control-paths.js.map +1 -0
  159. package/dist/outbound-handlers.d.ts +2 -0
  160. package/dist/outbound-handlers.js +9 -2
  161. package/dist/outbound-handlers.js.map +1 -1
  162. package/dist/outbound-schemas.d.ts +5 -5
  163. package/dist/outbound-schemas.js +1 -1
  164. package/dist/outbound-schemas.js.map +1 -1
  165. package/dist/package-preimage.d.ts +26 -0
  166. package/dist/package-preimage.js +96 -0
  167. package/dist/package-preimage.js.map +1 -0
  168. package/dist/profile-control.js +4 -20
  169. package/dist/profile-control.js.map +1 -1
  170. package/dist/public-web-gateway.d.ts +18 -0
  171. package/dist/public-web-gateway.js +141 -0
  172. package/dist/public-web-gateway.js.map +1 -0
  173. package/dist/public-web-link.d.ts +53 -0
  174. package/dist/public-web-link.js +175 -0
  175. package/dist/public-web-link.js.map +1 -0
  176. package/dist/quickstart-api.d.ts +8 -1
  177. package/dist/quickstart-api.js +86 -48
  178. package/dist/quickstart-api.js.map +1 -1
  179. package/dist/quickstart.d.ts +1 -0
  180. package/dist/quickstart.js +16 -6
  181. package/dist/quickstart.js.map +1 -1
  182. package/dist/restart-guard.d.ts +52 -0
  183. package/dist/restart-guard.js +109 -0
  184. package/dist/restart-guard.js.map +1 -0
  185. package/dist/scheduler/db.d.ts +7 -1
  186. package/dist/scheduler/db.js +44 -2
  187. package/dist/scheduler/db.js.map +1 -1
  188. package/dist/scheduler/index.d.ts +1 -1
  189. package/dist/scheduler/retry-label.d.ts +11 -0
  190. package/dist/scheduler/retry-label.js +24 -0
  191. package/dist/scheduler/retry-label.js.map +1 -0
  192. package/dist/scheduler/scheduler.d.ts +55 -2
  193. package/dist/scheduler/scheduler.js +220 -8
  194. package/dist/scheduler/scheduler.js.map +1 -1
  195. package/dist/scheduler/types.d.ts +22 -0
  196. package/dist/scheduler/types.js.map +1 -1
  197. package/dist/secret-store.d.ts +5 -0
  198. package/dist/secret-store.js +20 -0
  199. package/dist/secret-store.js.map +1 -1
  200. package/dist/service-activation.d.ts +152 -0
  201. package/dist/service-activation.js +407 -0
  202. package/dist/service-activation.js.map +1 -0
  203. package/dist/service-installer.d.ts +63 -1
  204. package/dist/service-installer.js +141 -16
  205. package/dist/service-installer.js.map +1 -1
  206. package/dist/service-plan.d.ts +65 -0
  207. package/dist/service-plan.js +108 -0
  208. package/dist/service-plan.js.map +1 -0
  209. package/dist/service-restart-selection.d.ts +6 -5
  210. package/dist/service-restart-selection.js +6 -5
  211. package/dist/service-restart-selection.js.map +1 -1
  212. package/dist/settings-api.d.ts +10 -2
  213. package/dist/settings-api.js +216 -72
  214. package/dist/settings-api.js.map +1 -1
  215. package/dist/settings-baseline.d.ts +20 -0
  216. package/dist/settings-baseline.js +104 -0
  217. package/dist/settings-baseline.js.map +1 -0
  218. package/dist/settings-change.d.ts +21 -0
  219. package/dist/settings-change.js +161 -0
  220. package/dist/settings-change.js.map +1 -0
  221. package/dist/settings-confirmation.d.ts +105 -0
  222. package/dist/settings-confirmation.js +232 -0
  223. package/dist/settings-confirmation.js.map +1 -0
  224. package/dist/settings-control.d.ts +41 -0
  225. package/dist/settings-control.js +294 -0
  226. package/dist/settings-control.js.map +1 -0
  227. package/dist/settings-effect.d.ts +24 -0
  228. package/dist/settings-effect.js +169 -0
  229. package/dist/settings-effect.js.map +1 -0
  230. package/dist/settings-http-confirmation.d.ts +29 -0
  231. package/dist/settings-http-confirmation.js +123 -0
  232. package/dist/settings-http-confirmation.js.map +1 -0
  233. package/dist/settings-instance-patch.d.ts +4 -0
  234. package/dist/settings-instance-patch.js +26 -0
  235. package/dist/settings-instance-patch.js.map +1 -0
  236. package/dist/settings-request-capability.d.ts +16 -0
  237. package/dist/settings-request-capability.js +32 -0
  238. package/dist/settings-request-capability.js.map +1 -0
  239. package/dist/settings-transaction.d.ts +65 -0
  240. package/dist/settings-transaction.js +223 -0
  241. package/dist/settings-transaction.js.map +1 -0
  242. package/dist/setup-auth.d.ts +10 -18
  243. package/dist/setup-auth.js +14 -38
  244. package/dist/setup-auth.js.map +1 -1
  245. package/dist/setup-form.d.ts +1 -1
  246. package/dist/setup-form.js +75 -18
  247. package/dist/setup-form.js.map +1 -1
  248. package/dist/setup-guide.d.ts +13 -0
  249. package/dist/setup-guide.js +25 -0
  250. package/dist/setup-guide.js.map +1 -0
  251. package/dist/setup-host.d.ts +16 -0
  252. package/dist/setup-host.js +199 -35
  253. package/dist/setup-host.js.map +1 -1
  254. package/dist/setup-wizard.js +5 -1
  255. package/dist/setup-wizard.js.map +1 -1
  256. package/dist/slash-authz.d.ts +17 -3
  257. package/dist/slash-authz.js +5 -0
  258. package/dist/slash-authz.js.map +1 -1
  259. package/dist/spawn-gate.js +3 -3
  260. package/dist/spawn-gate.js.map +1 -1
  261. package/dist/sqlite-open-errors.d.ts +12 -0
  262. package/dist/sqlite-open-errors.js +13 -0
  263. package/dist/sqlite-open-errors.js.map +1 -0
  264. package/dist/statusline-watcher.d.ts +5 -0
  265. package/dist/statusline-watcher.js +2 -0
  266. package/dist/statusline-watcher.js.map +1 -1
  267. package/dist/steer-capability.d.ts +6 -0
  268. package/dist/steer-capability.js +8 -0
  269. package/dist/steer-capability.js.map +1 -1
  270. package/dist/sync-work-attribution.d.ts +14 -0
  271. package/dist/sync-work-attribution.js +43 -8
  272. package/dist/sync-work-attribution.js.map +1 -1
  273. package/dist/tips.js +10 -5
  274. package/dist/tips.js.map +1 -1
  275. package/dist/tmux-control.d.ts +57 -5
  276. package/dist/tmux-control.js +379 -61
  277. package/dist/tmux-control.js.map +1 -1
  278. package/dist/tmux-manager.d.ts +20 -6
  279. package/dist/tmux-manager.js +90 -47
  280. package/dist/tmux-manager.js.map +1 -1
  281. package/dist/tmux-read.d.ts +59 -0
  282. package/dist/tmux-read.js +213 -0
  283. package/dist/tmux-read.js.map +1 -0
  284. package/dist/topic-commands.d.ts +21 -10
  285. package/dist/topic-commands.js +205 -63
  286. package/dist/topic-commands.js.map +1 -1
  287. package/dist/transcript-delta-reader.js +2 -1
  288. package/dist/transcript-delta-reader.js.map +1 -1
  289. package/dist/tunnel/cloudflared-install.d.ts +2 -0
  290. package/dist/tunnel/cloudflared-install.js +1 -1
  291. package/dist/tunnel/cloudflared-install.js.map +1 -1
  292. package/dist/tunnel/purpose-lane.d.ts +14 -0
  293. package/dist/tunnel/purpose-lane.js +43 -0
  294. package/dist/tunnel/purpose-lane.js.map +1 -0
  295. package/dist/turn-reply-guard.d.ts +2 -0
  296. package/dist/turn-reply-guard.js +10 -1
  297. package/dist/turn-reply-guard.js.map +1 -1
  298. package/dist/types.d.ts +33 -0
  299. package/dist/ui/app.html +18 -0
  300. package/dist/ui/chat-commands.js +43 -0
  301. package/dist/ui/chat-render.js +478 -0
  302. package/dist/ui/chat-store.js +318 -0
  303. package/dist/ui/chat-strings.js +89 -0
  304. package/dist/ui/chat-thread.js +246 -0
  305. package/dist/ui/chat-tour.js +116 -0
  306. package/dist/ui/fleet-cache.js +168 -0
  307. package/dist/ui/fleet-org.js +142 -0
  308. package/dist/ui/panel-chat.js +504 -0
  309. package/dist/ui/panel-fleet.js +427 -0
  310. package/dist/ui/panel-needs.js +202 -0
  311. package/dist/ui/panel-settings.js +754 -0
  312. package/dist/ui/preview-panel.js +202 -0
  313. package/dist/ui/preview.js +270 -0
  314. package/dist/ui/settings-apply.js +239 -0
  315. package/dist/ui/settings-confirm.js +186 -0
  316. package/dist/ui/settings-dialogs.js +546 -0
  317. package/dist/ui/settings-model.js +277 -0
  318. package/dist/ui/settings-strings.js +654 -0
  319. package/dist/ui/settings-wizard.js +140 -0
  320. package/dist/ui/shared/agend-auth.js +93 -0
  321. package/dist/ui/shared/app-ctx.js +80 -0
  322. package/dist/ui/shared/app-html.js +9 -0
  323. package/dist/ui/shared/app-i18n.js +95 -0
  324. package/dist/ui/shared/app-nav.js +73 -0
  325. package/dist/ui/shared/app-needs.js +114 -0
  326. package/dist/ui/shared/app-route.js +73 -0
  327. package/dist/ui/shared/app-session.js +66 -0
  328. package/dist/ui/shared/app-shell.js +346 -0
  329. package/dist/ui/shared/app-store.js +51 -0
  330. package/dist/ui/shared/app-stream.js +201 -0
  331. package/dist/ui/shared/app.css +820 -0
  332. package/dist/ui/shared/app.js +88 -0
  333. package/dist/ui/shared/fonts/OFL.txt +92 -0
  334. package/dist/ui/shared/fonts/README.md +21 -0
  335. package/dist/ui/shared/fonts/inter.woff2 +0 -0
  336. package/dist/ui/shared/htm.module.js +1 -0
  337. package/dist/ui/shared/panel-view.js +655 -0
  338. package/dist/ui/shared/preact-hooks.module.js +2 -0
  339. package/dist/ui/shared/preact.module.js +2 -0
  340. package/dist/ui/shared/signin.js +142 -0
  341. package/dist/ui/shared/theme.js +29 -0
  342. package/dist/ui/shared/tokens.css +153 -0
  343. package/dist/ui/shared/ui-confirm.js +43 -0
  344. package/dist/ui/shared/ui-dialog.js +45 -0
  345. package/dist/ui/shared/ui-icons.js +63 -0
  346. package/dist/ui/shared/ui-menu.js +39 -0
  347. package/dist/ui/shared/ui-states.js +20 -0
  348. package/dist/ui/shared/ui-toast.js +18 -0
  349. package/dist/ui/shared/vendor/htm.LICENSE +202 -0
  350. package/dist/ui/shared/vendor/preact.LICENSE +21 -0
  351. package/dist/ui/shared/vendor/vendor.json +39 -0
  352. package/dist/ui/shared/view-strings.js +309 -0
  353. package/dist/ui/signin.html +52 -0
  354. package/dist/update-check.d.ts +6 -2
  355. package/dist/update-check.js +12 -3
  356. package/dist/update-check.js.map +1 -1
  357. package/dist/update-dispatch.d.ts +28 -0
  358. package/dist/update-dispatch.js +73 -0
  359. package/dist/update-dispatch.js.map +1 -0
  360. package/dist/update-install.d.ts +129 -0
  361. package/dist/update-install.js +262 -0
  362. package/dist/update-install.js.map +1 -0
  363. package/dist/upgrade-notices.d.ts +17 -0
  364. package/dist/upgrade-notices.js +93 -0
  365. package/dist/upgrade-notices.js.map +1 -0
  366. package/dist/usage/statusline-usage.d.ts +2 -0
  367. package/dist/usage/statusline-usage.js +14 -10
  368. package/dist/usage/statusline-usage.js.map +1 -1
  369. package/dist/view-api.d.ts +6 -1
  370. package/dist/view-api.js +62 -51
  371. package/dist/view-api.js.map +1 -1
  372. package/dist/web-api.d.ts +85 -14
  373. package/dist/web-api.js +557 -77
  374. package/dist/web-api.js.map +1 -1
  375. package/dist/web-auth.d.ts +71 -27
  376. package/dist/web-auth.js +169 -69
  377. package/dist/web-auth.js.map +1 -1
  378. package/dist/web-body.d.ts +3 -0
  379. package/dist/web-body.js +55 -0
  380. package/dist/web-body.js.map +1 -0
  381. package/dist/web-channel-echo.d.ts +9 -0
  382. package/dist/web-channel-echo.js +55 -0
  383. package/dist/web-channel-echo.js.map +1 -0
  384. package/dist/web-chat-history.d.ts +125 -0
  385. package/dist/web-chat-history.js +184 -0
  386. package/dist/web-chat-history.js.map +1 -0
  387. package/dist/web-commands.d.ts +77 -0
  388. package/dist/web-commands.js +81 -0
  389. package/dist/web-commands.js.map +1 -0
  390. package/dist/web-continuation.d.ts +7 -0
  391. package/dist/web-continuation.js +22 -0
  392. package/dist/web-continuation.js.map +1 -0
  393. package/dist/web-host-guard.d.ts +34 -0
  394. package/dist/web-host-guard.js +57 -1
  395. package/dist/web-host-guard.js.map +1 -1
  396. package/dist/web-login.d.ts +72 -0
  397. package/dist/web-login.js +132 -0
  398. package/dist/web-login.js.map +1 -0
  399. package/dist/web-org.d.ts +55 -0
  400. package/dist/web-org.js +67 -0
  401. package/dist/web-org.js.map +1 -0
  402. package/dist/web-preview.d.ts +77 -0
  403. package/dist/web-preview.js +240 -0
  404. package/dist/web-preview.js.map +1 -0
  405. package/dist/web-request-context.d.ts +10 -0
  406. package/dist/web-request-context.js +13 -0
  407. package/dist/web-request-context.js.map +1 -0
  408. package/dist/web-session.d.ts +167 -0
  409. package/dist/web-session.js +389 -0
  410. package/dist/web-session.js.map +1 -0
  411. package/dist/web-shell-routes.d.ts +59 -0
  412. package/dist/web-shell-routes.js +96 -0
  413. package/dist/web-shell-routes.js.map +1 -0
  414. package/dist/web-upload.d.ts +183 -0
  415. package/dist/web-upload.js +468 -0
  416. package/dist/web-upload.js.map +1 -0
  417. package/dist/website-tips-renderer.d.ts +2 -2
  418. package/dist/website-tips-renderer.js +3 -3
  419. package/dist/website-tips-renderer.js.map +1 -1
  420. package/launcher/agend +110 -0
  421. package/launcher/agend-agent +110 -0
  422. package/launcher/agend-agent.cjs +4 -0
  423. package/launcher/agend.cjs +4 -0
  424. package/launcher/install-admission.cjs +105 -0
  425. package/launcher/launch.cjs +64 -0
  426. package/launcher/postinstall.cjs +114 -0
  427. package/launcher/run-in-process.cjs +13 -0
  428. package/launcher/runtime-platform.cjs +86 -0
  429. package/launcher/runtime-select.cjs +217 -0
  430. package/package.json +18 -7
  431. package/scripts/preinstall-guard.cjs +55 -0
  432. package/templates/launchd.plist.ejs +2 -2
  433. package/templates/systemd.service.ejs +1 -1
  434. package/dist/ui/dashboard.html +0 -774
  435. package/dist/ui/settings.html +0 -2165
  436. package/dist/ui/view.html +0 -1139
@@ -0,0 +1,246 @@
1
+ // #1408 §0/§4: the conversation thread — the keyed DOM renderer #1307 and #1306 reviewed, now owned by the chat panel
2
+ // through a ref. Preact never manages these children: a node here may hold a live preview iframe, and an iframe that
3
+ // is moved or re-parented reloads. So:
4
+ // - a new message is appended; a changed one (its ticks) is replaced; the rest are left alone — an opened code block
5
+ // stays open, a selection survives, where the reader scrolled stays theirs;
6
+ // - before ONE node is replaced, moved or removed: AgendPreview.stopIn(that node) — frames elsewhere keep running;
7
+ // - when the whole thread goes (the chat left, another instance opened): AgendPreview.stopAll("leave").
8
+ // Unrelated updates (a new message, another message's tick) stop nothing (#1306 §6.2, #1408 §4).
9
+ import "./chat-render.js";
10
+ import "./preview.js";
11
+
12
+ const R = () => globalThis.AgendChatRender;
13
+ const P = () => globalThis.AgendPreview;
14
+ const COPY = '<svg class="icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';
15
+
16
+ /** Escapes & " ' < >: safe for text and inside a quoted attribute. */
17
+ export function escAttr(s) { return String(s == null ? "" : s).replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;"); }
18
+ export const msgKey = (x) => `${x.boot}-${x.id}`;
19
+
20
+ /**
21
+ * Bind a renderer to `list` (the thread element) inside `scroller`. opts: { t, tf, isUser(x), onJump({show, unseen}),
22
+ * setPreviewOptIn(on), copyText(text) → Promise<boolean>, download(code), panel? }. panel (#1481): { shown() → the card
23
+ * key the side panel shows, or null; open(spec, { run }) } — without it a card has no "Open in panel".
24
+ */
25
+ export function createThread(list, scroller, opts) {
26
+ const nodes = new Map(); // key → { html, node }
27
+ let msgs = [];
28
+ let unseen = 0;
29
+ // The reader is at the newest message. While pinned, content that grows after it was drawn — an image loading, a
30
+ // preview card, the composer growing — keeps the view at the bottom (#1269); a reader who scrolled up is never moved.
31
+ // Only a move UP unpins: the browser's own scroll anchoring also fires scroll events when content grows (the view
32
+ // moves down or stays), and those must not count as the reader leaving the bottom. Reaching the bottom pins again.
33
+ let pinned = true;
34
+ let lastTop = 0;
35
+ const cardRefresh = new WeakMap(); // card node → re-read availability (weak: a card that left takes its source with it)
36
+ const tr = opts.t, trf = opts.tf;
37
+ const stopIn = (node) => (P() ? P().stopIn(node) : []);
38
+
39
+ const TICKS = () => ({ queued: tr("chat.tick_queued"), processing: tr("chat.tick_processing"), delivered: tr("chat.tick_delivered"), failed: tr("chat.tick_failed"), cancelled: tr("chat.tick_cancelled") });
40
+
41
+ function msgHtml(x) {
42
+ const u = opts.isUser(x);
43
+ const time = x.ts ? new Date(x.ts).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : "";
44
+ // renderMarkdown escapes the whole text before it adds any tag of its own (chat-render.js).
45
+ const ticks = u && x.delivery ? R().deliveryHtml(x.delivery, TICKS()) : "";
46
+ const tools = u ? "" : `<div class="msg-tools"><button type="button" class="chip-btn icon-only" data-act="copyMsg" data-arg="${escAttr(msgKey(x))}" title="${escAttr(tr("chat.copyMessage"))}" aria-label="${escAttr(tr("chat.copyMessage"))}">${COPY}</button></div>`;
47
+ return `<div class="msg ${u ? "user" : "agent"}"><div class="meta"><span class="sender">${escAttr(x.sender)}</span><span class="time">${time}</span>${ticks}</div><div class="body"><div class="md">${R().renderMarkdown(x.text, x.role === "agent" ? { htmlCards: true } : undefined)}</div>${R().attachmentsHtml(x.attachments)}</div>${tools}</div>`;
48
+ }
49
+ function msgNode(html, x, stoppedKeys) {
50
+ const tpl = list.ownerDocument.createElement("template");
51
+ tpl.innerHTML = html;
52
+ const node = tpl.content.firstElementChild;
53
+ decorateCode(node);
54
+ if (x) decorateHtmlCards(node, x, stoppedKeys || []);
55
+ return node;
56
+ }
57
+
58
+ // Code blocks: a header (language, Wrap, Copy); a long one is folded until opened. Built from nodes.
59
+ function decorateCode(root) {
60
+ const doc = list.ownerDocument;
61
+ for (const pre of root.querySelectorAll(".md pre")) {
62
+ const box = doc.createElement("div"); box.className = "codeblock";
63
+ const head = doc.createElement("div"); head.className = "cb-head";
64
+ const lang = doc.createElement("span"); lang.className = "cb-lang"; lang.textContent = pre.dataset.lang || "text";
65
+ const wrap = doc.createElement("button"); wrap.type = "button"; wrap.className = "chip-btn"; wrap.dataset.act = "toggleWrap"; wrap.textContent = tr("chat.wrap");
66
+ const copy = doc.createElement("button"); copy.type = "button"; copy.className = "chip-btn"; copy.dataset.act = "copyCode"; copy.textContent = tr("chat.copy");
67
+ head.append(lang, wrap, copy);
68
+ pre.replaceWith(box); box.append(head, pre);
69
+ const lines = R().lineCount(pre.textContent);
70
+ if (lines > R().CODE_FOLD_LINES) {
71
+ box.classList.add("folded");
72
+ const more = doc.createElement("button"); more.type = "button"; more.className = "cb-more"; more.dataset.act = "toggleFold";
73
+ more.dataset.lines = String(lines); more.textContent = trf("chat.showAll", lines);
74
+ box.append(more);
75
+ }
76
+ }
77
+ }
78
+
79
+ // HTML cards (#1306): each ```html fence of a server-marked agent message gets a card under its code block. Building
80
+ // a card makes no frame; Preview does (AgendPreview.start → mountPreview).
81
+ function decorateHtmlCards(node, x, stoppedKeys) {
82
+ const doc = list.ownerDocument;
83
+ const fences = R().htmlFences(x.text);
84
+ for (const ph of node.querySelectorAll(".html-card[data-card]")) {
85
+ const n = Number(ph.dataset.card), fence = fences[n];
86
+ if (!fence) continue;
87
+ const key = `${msgKey(x)}:f${n}`;
88
+ ph.textContent = "";
89
+ if (ph.classList.contains("truncated") || !fence.terminated) {
90
+ const note = doc.createElement("div"); note.className = "pv-note"; note.textContent = tr("chat.pvTruncated"); ph.append(note);
91
+ continue;
92
+ }
93
+ const el = (tag, cls, text) => { const e = doc.createElement(tag); if (cls) e.className = cls; if (text != null) e.textContent = text; return e; };
94
+ const head = el("div", "pv-head");
95
+ const label = el("span", "pv-label", tr("chat.pvHtml"));
96
+ const run = el("button", "btn btn-sm pv-run", tr("chat.pvPreview")); run.type = "button";
97
+ const stopB = el("button", "btn btn-sm pv-stop", tr("chat.pvStop")); stopB.type = "button"; stopB.hidden = true;
98
+ const dl = el("button", "btn btn-sm btn-ghost pv-dl", tr("chat.pvDownload")); dl.type = "button";
99
+ const panel = opts.panel || null;
100
+ const toPanel = panel ? el("button", "btn btn-sm btn-ghost pv-open", tr("chat.pvOpenPanel")) : null;
101
+ if (toPanel) toPanel.type = "button";
102
+ const menu = el("details", "pv-menu");
103
+ const sum = el("summary", null, "⋯"); sum.title = tr("chat.pvMenu"); sum.setAttribute("aria-label", tr("chat.pvMenu"));
104
+ const optB = el("button", "chip-btn pv-opt"); optB.type = "button";
105
+ const neverB = el("button", "chip-btn pv-never"); neverB.type = "button";
106
+ menu.append(sum, optB, neverB);
107
+ head.append(label, run, stopB, ...(toPanel ? [toPanel] : []), dl, menu);
108
+ const note = el("div", "pv-note");
109
+ const banner = el("div", "pv-banner", P().BANNER); banner.hidden = true;
110
+ const holder = el("div", "pv-holder");
111
+ ph.append(head, note, banner, holder);
112
+ let state = "idle";
113
+ const refresh = (reason) => {
114
+ const a = P().availability();
115
+ const going = state === "starting" || state === "running";
116
+ const inPanel = !!panel && panel.shown() === key; // #1481: the panel shows this block; the card points there
117
+ run.hidden = !a.ok || going || inPanel;
118
+ if (toPanel) toPanel.hidden = inPanel;
119
+ stopB.hidden = !going;
120
+ banner.hidden = !going;
121
+ note.textContent = reason != null ? reason : going ? (state === "starting" ? tr("chat.pvStarting") : "") : inPanel ? tr("chat.pvInPanel") : a.ok ? "" : a.reason;
122
+ optB.textContent = P().optedIn() ? tr("chat.pvDisallow") : tr("chat.pvAllow");
123
+ neverB.textContent = P().never() ? tr("chat.pvNeverUndo") : tr("chat.pvNever");
124
+ };
125
+ const ui = { state: (name, reason) => { state = name === "starting" || name === "running" ? name : "idle"; refresh(reason || null); } };
126
+ run.onclick = () => P().start(key, holder, fence.code, ui);
127
+ stopB.onclick = () => P().stop(key, "stopped", "");
128
+ // A running preview moves: it stops here and starts in the panel (the click that ran it). An idle one opens the
129
+ // panel with its own Preview to click.
130
+ if (toPanel) toPanel.onclick = () => {
131
+ const moving = P().running(key);
132
+ if (moving) P().stop(key, "stopped", "");
133
+ panel.open({ key, instance: x.instance, msgKey: msgKey(x), n, code: fence.code, sender: x.sender, ts: x.ts }, { run: moving });
134
+ };
135
+ dl.onclick = () => opts.download(fence.code);
136
+ optB.onclick = () => { menu.open = false; opts.setPreviewOptIn(!P().optedIn()); };
137
+ neverB.onclick = () => { menu.open = false; P().setNever(!P().never()); refreshCards(); };
138
+ cardRefresh.set(ph, refresh);
139
+ refresh(stoppedKeys.includes(key) ? tr("chat.pvChanged") : null);
140
+ }
141
+ }
142
+ /** Re-read this device's preview choice on every card in this thread. */
143
+ function refreshCards() {
144
+ for (const node of list.querySelectorAll(".html-card[data-card]")) { const f = cardRefresh.get(node); if (f) f(); }
145
+ }
146
+
147
+ const nearBottom = () => R().isNearBottom(scroller.scrollTop, scroller.clientHeight, scroller.scrollHeight);
148
+ function reportJump() { opts.onJump({ show: !nearBottom(), unseen }); }
149
+ function toBottom() { scroller.scrollTop = scroller.scrollHeight; pinned = true; lastTop = scroller.scrollTop; }
150
+ // Sizes change without a scroll event (layout after the render): follow them while pinned.
151
+ const resized = () => { if (pinned && !nearBottom()) toBottom(); reportJump(); };
152
+ const ro = typeof ResizeObserver === "function" ? new ResizeObserver(resized) : null;
153
+ if (ro) { ro.observe(list); ro.observe(scroller); }
154
+
155
+ /** Draw `next` (this instance's messages). `restore`: the scroll position to return to (null = the bottom). */
156
+ function render(next, renderOpts = {}) {
157
+ const stick = pinned || nearBottom();
158
+ msgs = next || [];
159
+ if (!msgs.length) {
160
+ for (const v of nodes.values()) stopIn(v.node);
161
+ nodes.clear();
162
+ list.textContent = "";
163
+ opts.onEmpty(true);
164
+ return;
165
+ }
166
+ opts.onEmpty(false);
167
+ const keep = new Set(msgs.map(msgKey));
168
+ for (const [k, v] of nodes) if (!keep.has(k)) { stopIn(v.node); v.node.remove(); nodes.delete(k); }
169
+ let prev = null, added = 0;
170
+ for (const x of msgs) {
171
+ const key = msgKey(x), html = msgHtml(x);
172
+ let have = nodes.get(key);
173
+ if (!have || have.html !== html) {
174
+ const stopped = have ? stopIn(have.node) : [];
175
+ const node = msgNode(html, x, stopped);
176
+ if (have) have.node.replaceWith(node); else added++;
177
+ have = { html, node }; nodes.set(key, have);
178
+ }
179
+ const want = prev ? prev.nextSibling : list.firstChild;
180
+ // A node already in the list is being moved: its preview stops first. A new node (still in its template) has none.
181
+ if (have.node !== want) { if (have.node.parentNode === list) stopIn(have.node); list.insertBefore(have.node, want); }
182
+ prev = have.node;
183
+ }
184
+ if ("restore" in renderOpts) {
185
+ unseen = 0;
186
+ if (renderOpts.restore == null) toBottom();
187
+ else { scroller.scrollTop = renderOpts.restore; pinned = nearBottom(); lastTop = scroller.scrollTop; }
188
+ } else if (stick) toBottom();
189
+ else unseen += added;
190
+ reportJump();
191
+ }
192
+
193
+ function onScroll() {
194
+ const bottom = nearBottom(), top = scroller.scrollTop;
195
+ if (bottom) pinned = true;
196
+ else if (top < lastTop) pinned = false;
197
+ lastTop = top;
198
+ if (bottom) unseen = 0;
199
+ reportJump();
200
+ return bottom ? null : scroller.scrollTop;
201
+ }
202
+ function jumpLatest() { toBottom(); unseen = 0; reportJump(); }
203
+ /** Show one message (the panel's "from …" link, #1481): scrolled into view and briefly marked. */
204
+ function reveal(key) {
205
+ const have = nodes.get(key); if (!have) return false;
206
+ if (typeof have.node.scrollIntoView === "function") have.node.scrollIntoView({ block: "center" });
207
+ have.node.classList.add("flash");
208
+ setTimeout(() => { have.node.classList.remove("flash"); }, 1600);
209
+ return true;
210
+ }
211
+
212
+ async function copyFrom(button, text) {
213
+ const ok = await opts.copyText(text);
214
+ if (button.classList.contains("icon-only")) {
215
+ button.classList.add(ok ? "done" : "fail");
216
+ button.title = ok ? tr("chat.copied") : tr("chat.copyFailed"); button.setAttribute("aria-label", button.title);
217
+ setTimeout(() => { button.classList.remove("done", "fail"); button.title = tr("chat.copyMessage"); button.setAttribute("aria-label", button.title); }, 1200);
218
+ return;
219
+ }
220
+ const was = button.dataset.label || button.textContent; button.dataset.label = was;
221
+ button.textContent = ok ? tr("chat.copied") : tr("chat.copyFailed");
222
+ setTimeout(() => { button.textContent = was; }, 1200);
223
+ }
224
+ /** The thread's own buttons (Copy, Wrap, fold), delegated from the thread element. */
225
+ function onClick(e) {
226
+ const el = e.target && e.target.closest ? e.target.closest("[data-act]") : null;
227
+ if (!el || !list.contains(el)) return;
228
+ const act = el.dataset.act;
229
+ if (act === "copyMsg") { const x = msgs.find(y => msgKey(y) === el.dataset.arg); if (x) copyFrom(el, x.text); }
230
+ else if (act === "copyCode") { const pre = el.closest(".codeblock")?.querySelector("pre"); if (pre) copyFrom(el, pre.textContent); }
231
+ else if (act === "toggleFold") {
232
+ const box = el.closest(".codeblock"); if (!box) return;
233
+ const folded = box.classList.toggle("folded");
234
+ el.textContent = folded ? trf("chat.showAll", el.dataset.lines) : tr("chat.showLess");
235
+ } else if (act === "toggleWrap") opts.toggleWrap();
236
+ }
237
+
238
+ /** The thread goes (the chat left, or another instance opened): every preview in it stops first. */
239
+ function dispose() {
240
+ if (ro) ro.disconnect();
241
+ if (P()) P().stopAll("leave");
242
+ nodes.clear();
243
+ }
244
+
245
+ return { render, onScroll, onClick, jumpLatest, reveal, refreshCards, dispose, nodes, get unseen() { return unseen; } };
246
+ }
@@ -0,0 +1,116 @@
1
+ // #1366: the first sign-in tour, in the app shell (#1408). Shown once on this device (agend_tour_done), dismissed by
2
+ // Got it, Skip or Esc, and replayed from Tour in the sidebar. A card that outlines one thing at a time: no overlay,
3
+ // nothing blocked, and no style attribute — the outline is a class (#1300). When this browser's storage cannot be read
4
+ // it is not shown at all: a tour that comes back on every load is worse than none.
5
+ //
6
+ // Esc order: the phone drawer covers the card, so it closes first (the shell); then the tour; only then may Esc reach
7
+ // the chat and stop a reply. The Esc that closed the tour is spent: its key repeats (a held key) must not go on to stop
8
+ // the agent (#1369 review).
9
+ import { t, onLang } from "/assets/app-i18n.js";
10
+ import { shellStore, closeDrawer } from "/assets/app-shell.js";
11
+ import "./chat-strings.js";
12
+
13
+ const TOUR_KEY = "agend_tour_done";
14
+ const byId = (id) => document.getElementById(id);
15
+ // On a phone the instance list is in the closed drawer: the tour outlines what opens it (☰), and for "needs you" the
16
+ // bottom tab's badge (#1408 step 5: retargeted to the app's shell).
17
+ const narrow = () => typeof matchMedia === "function" && matchMedia("(max-width: 899px)").matches;
18
+ const STEPS = [
19
+ { text: "chat.tour1", spot: () => (narrow() ? byId("sbOpen") : byId("instanceList")) },
20
+ { text: "chat.tour2", spot: () => byId("main") },
21
+ { text: "chat.tour3", spot: () => byId("attachBtn") },
22
+ { text: "chat.tour4", spot: () => { const stop = byId("stopBtn"); return stop && !stop.hidden ? stop : byId("sendBtn"); } },
23
+ { text: "chat.tour5", spot: () => (narrow() ? document.querySelector(".tabs .tab-badge") || byId("sbOpen")
24
+ : document.querySelector(".badge-await") || byId("instanceList")) },
25
+ ];
26
+ let at = -1, spot = null, returnTo = null, escHeld = false, installed = false;
27
+
28
+ export function tourDone() { try { return localStorage.getItem(TOUR_KEY) === "1"; } catch { return true; } }
29
+ export function tourStep() { return at; }
30
+
31
+ export function startTour() {
32
+ // Replayed from the drawer: the drawer closes first (focus goes back to ☰), so the card is seen.
33
+ closeDrawer();
34
+ if (at < 0) returnTo = document.activeElement;
35
+ show(0);
36
+ }
37
+
38
+ /** The current step's outline follows a redraw (a new badge, Send swapped for Stop reply) — same card, same focus. */
39
+ export function refreshTourSpot() {
40
+ if (at < 0) return;
41
+ const next = STEPS[at].spot() || null;
42
+ if (next === spot && (!next || next.classList.contains("tour-spot"))) return;
43
+ if (spot) spot.classList.remove("tour-spot");
44
+ spot = next;
45
+ if (spot) spot.classList.add("tour-spot");
46
+ }
47
+
48
+ function show(i) {
49
+ if (i < 0 || i >= STEPS.length) return;
50
+ at = i;
51
+ if (spot) spot.classList.remove("tour-spot");
52
+ spot = STEPS[i].spot() || null;
53
+ if (spot) spot.classList.add("tour-spot");
54
+ let card = byId("tour");
55
+ if (!card) {
56
+ card = document.createElement("section"); card.id = "tour"; card.className = "tour";
57
+ card.setAttribute("role", "dialog"); card.setAttribute("aria-labelledby", "tourTitle");
58
+ card.addEventListener("click", onCardClick);
59
+ document.body.appendChild(card);
60
+ }
61
+ card.textContent = "";
62
+ const title = document.createElement("h2"); title.id = "tourTitle"; title.textContent = t("chat.tourTitle");
63
+ const text = document.createElement("p"); text.className = "tour-text"; text.setAttribute("aria-live", "polite"); text.textContent = t(STEPS[i].text);
64
+ const foot = document.createElement("div"); foot.className = "tour-foot";
65
+ const count = document.createElement("span"); count.className = "tour-count"; count.textContent = t("chat.tourStep", i + 1, STEPS.length);
66
+ const button = (label, act, arg, cls) => {
67
+ const b = document.createElement("button"); b.type = "button"; b.className = `btn btn-sm ${cls}`; b.dataset.tour = act;
68
+ if (arg != null) b.dataset.arg = String(arg);
69
+ b.textContent = t(label); return b;
70
+ };
71
+ foot.append(count);
72
+ if (i < STEPS.length - 1) foot.append(button("chat.tourSkip", "end", null, "btn-ghost"));
73
+ if (i > 0) foot.append(button("chat.tourBack", "move", -1, "btn-ghost"));
74
+ const next = i < STEPS.length - 1 ? button("chat.tourNext", "move", 1, "btn-primary") : button("chat.tourDone", "end", null, "btn-primary");
75
+ foot.append(next);
76
+ card.append(title, text, foot);
77
+ next.focus();
78
+ }
79
+
80
+ function onCardClick(e) {
81
+ const b = e.target && e.target.closest ? e.target.closest("[data-tour]") : null;
82
+ if (!b) return;
83
+ if (b.dataset.tour === "move") show(at + Number(b.dataset.arg));
84
+ else endTour();
85
+ }
86
+
87
+ export function endTour() {
88
+ try { localStorage.setItem(TOUR_KEY, "1"); } catch { /* this page only */ }
89
+ at = -1;
90
+ if (spot) spot.classList.remove("tour-spot");
91
+ spot = null;
92
+ const card = byId("tour"); if (card) card.remove();
93
+ if (returnTo && document.contains(returnTo) && typeof returnTo.focus === "function") returnTo.focus();
94
+ returnTo = null;
95
+ }
96
+
97
+ /** Capture-phase Esc: it runs before the shell's own key handling, so a tour Esc never reaches the chat. */
98
+ export function onTourKey(e) {
99
+ if (e.key !== "Escape" || e.isComposing) return;
100
+ if (escHeld) {
101
+ if (e.repeat) { e.preventDefault(); e.stopPropagation(); return; }
102
+ escHeld = false;
103
+ }
104
+ if (shellStore.get().drawer) return; // the drawer is on top: the shell closes it first
105
+ if (at >= 0) { e.preventDefault(); e.stopPropagation(); endTour(); escHeld = true; }
106
+ }
107
+
108
+ /** Once per page: the key handler, the language follow-up, and the first showing on a device that has not seen it. */
109
+ export function installTour() {
110
+ if (installed) return;
111
+ installed = true;
112
+ document.addEventListener("keydown", onTourKey, true);
113
+ onLang(() => { if (at >= 0) show(at); });
114
+ // Shown first on the chat (it points at the chat's own controls), never over View or another panel.
115
+ if (!tourDone() && /^\/ui(\/chat\/|$)/.test(location.pathname)) setTimeout(startTour, 0);
116
+ }
@@ -0,0 +1,168 @@
1
+ // #1468: Fleet → Cache (/ui/fleet/cache) — how often each instance's prompt cache expires between requests, what
2
+ // that costs, and whether keeping it warm would pay off. Behind the session gate (/ui/js/), imported by the Fleet
3
+ // panel. Read-only (Discord-first): it recommends, it changes nothing.
4
+ //
5
+ // One GET /ui/cache?window=… per opening or per choice of window (a person's navigation); no timer re-reads it
6
+ // (#1374). While the server is still reading transcripts the page says so and offers Refresh.
7
+ import { html } from "/assets/app-html.js";
8
+ import { t, register } from "/assets/app-i18n.js";
9
+ import { Empty } from "/assets/ui-states.js";
10
+ import { Icon } from "/assets/ui-icons.js";
11
+
12
+ register("cache", {
13
+ en: {
14
+ title: "Cache", w24h: "24 h", w7d: "7 days", w30d: "30 days", window: "Window",
15
+ fleetExpiry: "Expiry cost", fleetSaving: "Keep-warm would save", fleetRecommended: "Recommended", ofN: "{0} of {1}",
16
+ pricesNote: "List prices checked {0}.", quotaNote: "Subscriptions: Anthropic and OpenAI do not publish how cache reads weigh against a plan's quota, so savings are exact at list price and an estimate in quota terms.",
17
+ estimateNote: "Estimate: Codex does not record cache writes, so a write is taken as the request's uncached input.",
18
+ notPricedNote: "Some models are not in the price table; those instances are compared in input-price units.",
19
+ scanning: "Still reading transcripts — {0} left. Refresh to see more.", scanningFirst: "Reading transcripts for the first time — Refresh to see more.", refresh: "Refresh",
20
+ keepWarm: "Keep warm", leaveOff: "Leave off", estimate: "estimate", notPriced: "not priced",
21
+ requests: "Requests", pastTtl: "Past the TTL ({0})", expired: "Expired (rewrote)", rewritten: "Tokens rewritten",
22
+ expiryCost: "Expiry cost", share: "{0} of its cost", pings: "Keep-warm pings", pingsAfter: "{0} after its last request",
23
+ pingCost: "Ping cost", net: "Net with keep-warm", gaps: "Gaps between requests", ttlLine: "TTL {0}",
24
+ quiet: "Too little activity in this window to tell.",
25
+ neverExpired: "No gap outlasted the {0} cache.", survived: "{0} gaps outlasted the {1} TTL, but the cache was still there.",
26
+ onlyIdle: "No request ever came back to an expired cache: each session went quiet for good. Keep-warm would only send {0} pings nobody reads.",
27
+ saves: "Often idle past the {0} TTL and then used again: keep-warm would save {1} ({2} of the expiry cost).",
28
+ costsMore: "Keep-warm would send about {0} pings per expired request — it would cost {1} more than it saves.",
29
+ costsMoreUnits: "Keep-warm would send about {0} pings per expired request — it would cost more than it saves.",
30
+ unavailable: "Not available", creditBilled: "credit-billed; no cache data", unsupported: "no cache data from this CLI", noData: "no requests read yet",
31
+ shared: "shares its working directory with {0}; their transcripts cannot be told apart",
32
+ empty: "No instances", emptyHint: "Instances show here once the fleet has them.",
33
+ b0: "≤5m", b1: "≤30m", b2: "≤1h", b3: "≤2h", b4: "≤6h", b5: "≤24h", b6: ">24h", tokensUnit: "input-token equivalents",
34
+ },
35
+ "zh-TW": {
36
+ title: "快取", w24h: "24 小時", w7d: "7 天", w30d: "30 天", window: "期間",
37
+ fleetExpiry: "過期成本", fleetSaving: "保溫可省", fleetRecommended: "建議保溫", ofN: "{1} 個中的 {0} 個",
38
+ pricesNote: "牌價查證日期:{0}。", quotaNote: "訂閱方案:Anthropic 與 OpenAI 未公布快取讀取如何計入方案額度,因此節省金額以牌價計算為準,換算成額度則是估算。",
39
+ estimateNote: "估算:Codex 不記錄快取寫入,寫入量以該次請求未命中快取的輸入估算。",
40
+ notPricedNote: "有些模型不在價目表中,這些 instance 以「輸入價格單位」比較。",
41
+ scanning: "仍在讀取 transcript——還剩 {0}。重新整理可看到更多。", scanningFirst: "第一次讀取 transcript 中——重新整理可看到更多。", refresh: "重新整理",
42
+ keepWarm: "建議保溫", leaveOff: "不需保溫", estimate: "估算", notPriced: "未定價",
43
+ requests: "請求數", pastTtl: "超過 TTL({0})", expired: "已過期(重寫)", rewritten: "重寫的 token",
44
+ expiryCost: "過期成本", share: "占其成本 {0}", pings: "保溫 ping 次數", pingsAfter: "其中 {0} 次在最後一次請求之後",
45
+ pingCost: "ping 成本", net: "保溫後淨額", gaps: "請求間隔", ttlLine: "TTL {0}",
46
+ quiet: "這段期間活動太少,無法判斷。",
47
+ neverExpired: "沒有任何間隔超過 {0} 的快取時效。", survived: "有 {0} 個間隔超過 {1} TTL,但快取仍在。",
48
+ onlyIdle: "沒有任何請求在快取過期後回來使用:每個 session 閒置後就不再使用。保溫只會送出 {0} 次沒人讀的 ping。",
49
+ saves: "常閒置超過 {0} TTL 後又再使用:保溫可省 {1}(過期成本的 {2})。",
50
+ costsMore: "每次過期約需送 {0} 次 ping——保溫的花費比省下的多 {1}。",
51
+ costsMoreUnits: "每次過期約需送 {0} 次 ping——保溫的花費比省下的多。",
52
+ unavailable: "不提供", creditBilled: "以 credit 計費,沒有快取資料", unsupported: "這個 CLI 沒有快取資料", noData: "尚未讀到任何請求",
53
+ shared: "與 {0} 共用工作目錄,無法分辨各自的 transcript",
54
+ empty: "沒有 instance", emptyHint: "fleet 有 instance 後會顯示在這裡。",
55
+ b0: "≤5分", b1: "≤30分", b2: "≤1時", b3: "≤2時", b4: "≤6時", b5: "≤24時", b6: ">24時", tokensUnit: "輸入 token 當量",
56
+ },
57
+ });
58
+ const tc = (k, ...v) => t(`cache.${k}`, ...v);
59
+
60
+ export const CACHE_WINDOWS = ["24h", "7d", "30d"];
61
+ const EDGES = [300, 1800, 3600, 7200, 21600, 86400];
62
+
63
+ export function money(usd) {
64
+ if (!(usd > 0)) return "$0";
65
+ if (usd < 0.01) return "<$0.01";
66
+ return usd >= 100 ? `$${Math.round(usd).toLocaleString("en-US")}` : `$${usd.toFixed(2)}`;
67
+ }
68
+ export function tokens(n) {
69
+ if (n >= 1e9) return `${(n / 1e9).toFixed(1)}B`;
70
+ if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
71
+ if (n >= 1e3) return `${Math.round(n / 1e3)}k`;
72
+ return String(Math.round(n));
73
+ }
74
+ export function bytes(n) { return n >= 1e9 ? `${(n / 1e9).toFixed(1)} GB` : n >= 1e6 ? `${Math.round(n / 1e6)} MB` : `${Math.max(1, Math.round(n / 1e3))} KB`; }
75
+ export function ttlText(sec) { return sec >= 3600 ? `${sec / 3600} h` : `${Math.round(sec / 60)} min`; }
76
+ const pct = (x) => `${x >= 0.1 ? Math.round(x * 100) : (x * 100).toFixed(1)}%`;
77
+
78
+ /** The one line under an instance's recommendation. */
79
+ export function reasonText(a) {
80
+ const r = a.recommendation, ttl = ttlText(a.ttlSec);
81
+ if (r.reason === "quiet") return tc("quiet");
82
+ if (r.reason === "never_expired") {
83
+ if (a.sim.pastTtl) return tc("survived", a.sim.pastTtl, ttl);
84
+ return a.sim.tailPings ? tc("onlyIdle", a.sim.tailPings.toLocaleString("en-US")) : tc("neverExpired", ttl);
85
+ }
86
+ if (r.reason === "saves") return tc("saves", ttl, a.priced ? money(a.sim.net) : `${tokens(a.sim.net * 1e6)} ${tc("tokensUnit")}`, a.sim.expiryCost > 0 ? pct(a.sim.net / a.sim.expiryCost) : "—");
87
+ return a.priced ? tc("costsMore", r.pingsPerExpiry ?? 0, money(-a.sim.net)) : tc("costsMoreUnits", r.pingsPerExpiry ?? 0);
88
+ }
89
+
90
+ /** Gap histogram: one bar per bucket (square-root scale: a thousand short gaps must not flatten the few long ones). */
91
+ function Histogram({ buckets, ttlSec }) {
92
+ const W = 280, H = 84, top = 14, base = 66, slot = W / buckets.length, bw = slot - 8;
93
+ const max = Math.max(1, ...buckets);
94
+ const ttlAt = EDGES.indexOf(ttlSec);
95
+ const label = `${tc("gaps")}: ${buckets.map((n, i) => `${tc(`b${i}`)} ${n}`).join(", ")}`;
96
+ return html`<svg class="ch-hist" viewBox=${`0 0 ${W} ${H}`} role="img" aria-label=${label} preserveAspectRatio="none">
97
+ ${buckets.map((n, i) => {
98
+ const h = n ? Math.max(2, Math.round((base - top) * Math.sqrt(n / max))) : 0;
99
+ const x = i * slot + 4;
100
+ return html`<g key=${i} class=${ttlAt >= 0 && i > ttlAt ? "ch-col past" : "ch-col"}>
101
+ <rect x=${x} y=${base - h} width=${bw} height=${h} rx="2" />
102
+ <text class="ch-n" x=${x + bw / 2} y=${base - h - 3} text-anchor="middle">${n || ""}</text>
103
+ <text class="ch-l" x=${x + bw / 2} y=${H - 4} text-anchor="middle">${tc(`b${i}`)}</text></g>`;
104
+ })}
105
+ ${ttlAt >= 0 ? html`<g class="ch-ttl"><line x1=${(ttlAt + 1) * slot} x2=${(ttlAt + 1) * slot} y1="4" y2=${base} />
106
+ <text x=${(ttlAt + 1) * slot + 3} y="11">${tc("ttlLine", ttlText(ttlSec))}</text></g>` : null}
107
+ </svg>`;
108
+ }
109
+
110
+ function Metric({ label, value, sub }) {
111
+ return html`<div class="ch-metric"><dt>${label}</dt><dd>${value}${sub ? html`<span class="ch-sub">${sub}</span>` : null}</dd></div>`;
112
+ }
113
+
114
+ function InstanceCard({ r }) {
115
+ const a = r.analysis, s = a.sim, $ = (v) => (a.priced ? money(v) : "—");
116
+ const on = a.recommendation.on;
117
+ return html`<article class=${`ch-card${on ? " on" : ""}`} aria-labelledby=${`ch-${r.name}`}>
118
+ <header class="ch-head">
119
+ <div class="ch-id"><h3 id=${`ch-${r.name}`} class="mono">${r.name}</h3>
120
+ <span class="ch-models">${r.backend}${a.models.length ? ` · ${a.models.join(", ")}` : ""}</span></div>
121
+ <span class=${`ch-rec${on ? " on" : ""}`}>${on ? tc("keepWarm") : tc("leaveOff")}</span>
122
+ </header>
123
+ <p class="ch-reason">${reasonText(a)}${a.estimate ? html` <span class="ch-tag">${tc("estimate")}</span>` : null}${a.priced ? null : html` <span class="ch-tag">${tc("notPriced")}</span>`}</p>
124
+ <dl class="ch-metrics">
125
+ <${Metric} label=${tc("requests")} value=${a.requests.toLocaleString("en-US")} />
126
+ <${Metric} label=${tc("pastTtl", ttlText(a.ttlSec))} value=${s.pastTtl} />
127
+ <${Metric} label=${tc("expired")} value=${s.expired} />
128
+ <${Metric} label=${tc("rewritten")} value=${tokens(s.rewriteTokens)} />
129
+ <${Metric} label=${tc("expiryCost")} value=${$(s.expiryCost)} sub=${a.share != null && a.priced && s.expiryCost > 0 ? tc("share", pct(a.share)) : null} />
130
+ <${Metric} label=${tc("pings")} value=${(s.pings + s.tailPings).toLocaleString("en-US")} sub=${s.tailPings ? tc("pingsAfter", s.tailPings) : null} />
131
+ <${Metric} label=${tc("pingCost")} value=${$(s.pingCost + s.tailCost)} />
132
+ <${Metric} label=${tc("net")} value=${a.priced ? `${s.net >= 0 ? "+" : "−"}${money(Math.abs(s.net))}` : "—"} />
133
+ </dl>
134
+ <${Histogram} buckets=${a.gapBuckets} ttlSec=${a.ttlSec} />
135
+ </article>`;
136
+ }
137
+
138
+ /** The tab's body. `report`: GET /ui/cache's answer; `win`/`setWin`: the window; `refresh`: read again. */
139
+ export function CacheReport({ report, win, setWin, refresh }) {
140
+ const ok = report.instances.filter((r) => r.status === "ok")
141
+ .sort((x, y) => (y.analysis.priced - x.analysis.priced) || (y.analysis.sim.expiryCost - x.analysis.sim.expiryCost));
142
+ const rest = report.instances.filter((r) => r.status !== "ok");
143
+ const f = report.fleet;
144
+ const anyEstimate = ok.some((r) => r.analysis.estimate);
145
+ return html`<div class="ch">
146
+ <div class="ch-bar">
147
+ <div class="seg-inline" role="group" aria-label=${tc("window")}>${CACHE_WINDOWS.map((w) => html`<button key=${w} type="button"
148
+ class=${`btn btn-sm${w === win ? " btn-primary" : ""}`} aria-pressed=${w === win ? "true" : "false"} onClick=${() => setWin(w)}>${tc(`w${w}`)}</button>`)}</div>
149
+ ${!report.scanning.caughtUp || report.scanning.pendingBytes > 0 ? html`<div class="ch-scan" role="status"><span>${report.scanning.pendingBytes > 0
150
+ ? tc("scanning", bytes(report.scanning.pendingBytes)) : tc("scanningFirst")}</span>
151
+ <button type="button" class="btn btn-sm" onClick=${refresh}><${Icon} name="restart" size=${14} />${tc("refresh")}</button></div>` : null}
152
+ </div>
153
+ ${report.instances.length ? html`<section class="ch-fleet card">
154
+ <${Metric} label=${tc("fleetExpiry")} value=${money(f.expiryCost)} />
155
+ <${Metric} label=${tc("fleetSaving")} value=${money(f.saving)} />
156
+ <${Metric} label=${tc("fleetRecommended")} value=${tc("ofN", f.recommended, f.analysed)} />
157
+ </section>` : html`<${Empty} icon="chart" title=${tc("empty")} hint=${tc("emptyHint")} />`}
158
+ <div class="ch-notes note">
159
+ <p>${tc("pricesNote", report.pricesChecked)} ${tc("quotaNote")}</p>
160
+ ${anyEstimate ? html`<p>${tc("estimateNote")}</p>` : null}
161
+ ${f.priced ? null : html`<p>${tc("notPricedNote")}</p>`}
162
+ </div>
163
+ <div class="ch-cards">${ok.map((r) => html`<${InstanceCard} key=${r.name} r=${r} />`)}</div>
164
+ ${rest.length ? html`<ul class="rows ch-rest">${rest.map((r) => html`<li key=${r.name} class="row-item">
165
+ <span class="grow strong mono">${r.name}</span><span class="muted">${r.backend} · ${r.status === "no_data" ? tc("noData")
166
+ : `${tc("unavailable")} (${r.status === "credit_billed" ? tc("creditBilled") : r.status === "shared" ? tc("shared", (r.with || []).join(", ")) : tc("unsupported")})`}</span></li>`)}</ul>` : null}
167
+ </div>`;
168
+ }