@songsid/agend 2.2.0-beta.9 → 2.3.0-beta.1

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 (422) hide show
  1. package/README.md +40 -24
  2. package/README.zh-TW.md +40 -24
  3. package/dist/agent-cli.js +248 -2
  4. package/dist/agent-cli.js.map +1 -1
  5. package/dist/agent-endpoint.d.ts +2 -0
  6. package/dist/agent-endpoint.js +28 -0
  7. package/dist/agent-endpoint.js.map +1 -1
  8. package/dist/agent-file-read.d.ts +30 -0
  9. package/dist/agent-file-read.js +129 -0
  10. package/dist/agent-file-read.js.map +1 -0
  11. package/dist/agent-whats-new.js +20 -1
  12. package/dist/agent-whats-new.js.map +1 -1
  13. package/dist/atomic-write.js +9 -1
  14. package/dist/atomic-write.js.map +1 -1
  15. package/dist/auth-api.d.ts +3 -1
  16. package/dist/auth-api.js +32 -1
  17. package/dist/auth-api.js.map +1 -1
  18. package/dist/backend/antigravity.d.ts +33 -0
  19. package/dist/backend/antigravity.js +129 -21
  20. package/dist/backend/antigravity.js.map +1 -1
  21. package/dist/backend/claude-code.d.ts +3 -0
  22. package/dist/backend/claude-code.js +9 -0
  23. package/dist/backend/claude-code.js.map +1 -1
  24. package/dist/backend/codex-metadata.d.ts +33 -0
  25. package/dist/backend/codex-metadata.js +122 -18
  26. package/dist/backend/codex-metadata.js.map +1 -1
  27. package/dist/backend/codex.d.ts +3 -0
  28. package/dist/backend/codex.js +17 -1
  29. package/dist/backend/codex.js.map +1 -1
  30. package/dist/backend/effort-metadata.d.ts +37 -1
  31. package/dist/backend/effort-metadata.js +86 -15
  32. package/dist/backend/effort-metadata.js.map +1 -1
  33. package/dist/backend/grok.d.ts +3 -0
  34. package/dist/backend/grok.js +9 -0
  35. package/dist/backend/grok.js.map +1 -1
  36. package/dist/backend/kiro.d.ts +3 -1
  37. package/dist/backend/kiro.js +7 -3
  38. package/dist/backend/kiro.js.map +1 -1
  39. package/dist/backend/launch-effort.d.ts +61 -0
  40. package/dist/backend/launch-effort.js +170 -0
  41. package/dist/backend/launch-effort.js.map +1 -0
  42. package/dist/backend/muse.js +10 -2
  43. package/dist/backend/muse.js.map +1 -1
  44. package/dist/backend/types.d.ts +24 -0
  45. package/dist/backend/types.js +12 -1
  46. package/dist/backend/types.js.map +1 -1
  47. package/dist/backend-command.d.ts +91 -0
  48. package/dist/backend-command.js +122 -0
  49. package/dist/backend-command.js.map +1 -0
  50. package/dist/channel/adapters/discord.d.ts +14 -0
  51. package/dist/channel/adapters/discord.js +66 -4
  52. package/dist/channel/adapters/discord.js.map +1 -1
  53. package/dist/channel/adapters/telegram.d.ts +26 -1
  54. package/dist/channel/adapters/telegram.js +98 -7
  55. package/dist/channel/adapters/telegram.js.map +1 -1
  56. package/dist/channel/mcp-tools.js +36 -0
  57. package/dist/channel/mcp-tools.js.map +1 -1
  58. package/dist/channel/tool-router.d.ts +2 -0
  59. package/dist/channel/tool-router.js +1 -1
  60. package/dist/channel/tool-router.js.map +1 -1
  61. package/dist/channel/types.d.ts +31 -0
  62. package/dist/cli.js +124 -24
  63. package/dist/cli.js.map +1 -1
  64. package/dist/command-table.d.ts +2 -2
  65. package/dist/command-table.js +5 -2
  66. package/dist/command-table.js.map +1 -1
  67. package/dist/compact-watch.d.ts +86 -0
  68. package/dist/compact-watch.js +288 -0
  69. package/dist/compact-watch.js.map +1 -0
  70. package/dist/config-validator.js +55 -0
  71. package/dist/config-validator.js.map +1 -1
  72. package/dist/config.d.ts +1 -0
  73. package/dist/config.js +2 -1
  74. package/dist/config.js.map +1 -1
  75. package/dist/daemon.d.ts +9 -0
  76. package/dist/daemon.js +394 -29
  77. package/dist/daemon.js.map +1 -1
  78. package/dist/delivery-outbox.d.ts +8 -1
  79. package/dist/delivery-outbox.js +6 -2
  80. package/dist/delivery-outbox.js.map +1 -1
  81. package/dist/doctor.js +7 -0
  82. package/dist/doctor.js.map +1 -1
  83. package/dist/fleet-context.d.ts +12 -0
  84. package/dist/fleet-manager.d.ts +247 -9
  85. package/dist/fleet-manager.js +1227 -142
  86. package/dist/fleet-manager.js.map +1 -1
  87. package/dist/forged-envelope-scan.d.ts +4 -3
  88. package/dist/forged-envelope-scan.js +4 -3
  89. package/dist/forged-envelope-scan.js.map +1 -1
  90. package/dist/general-knowledge/skills/model-discovery/SKILL.md +4 -1
  91. package/dist/general-knowledge/skills/web-appearance/SKILL.md +258 -0
  92. package/dist/install-lock.d.ts +18 -1
  93. package/dist/install-lock.js +30 -3
  94. package/dist/install-lock.js.map +1 -1
  95. package/dist/instance-avatar.d.ts +5 -4
  96. package/dist/instance-avatar.js +15 -8
  97. package/dist/instance-avatar.js.map +1 -1
  98. package/dist/instance-config-impact.js +3 -0
  99. package/dist/instance-config-impact.js.map +1 -1
  100. package/dist/instance-lifecycle.js +9 -4
  101. package/dist/instance-lifecycle.js.map +1 -1
  102. package/dist/instructions.js +2 -2
  103. package/dist/instructions.js.map +1 -1
  104. package/dist/kiro-compact-pane.d.ts +38 -0
  105. package/dist/kiro-compact-pane.js +57 -0
  106. package/dist/kiro-compact-pane.js.map +1 -0
  107. package/dist/launchd-handoff.d.ts +43 -0
  108. package/dist/launchd-handoff.js +56 -0
  109. package/dist/launchd-handoff.js.map +1 -0
  110. package/dist/locale.js +202 -30
  111. package/dist/locale.js.map +1 -1
  112. package/dist/log-service.js +10 -2
  113. package/dist/log-service.js.map +1 -1
  114. package/dist/login-shell.d.ts +21 -0
  115. package/dist/login-shell.js +98 -0
  116. package/dist/login-shell.js.map +1 -0
  117. package/dist/needs-you-hub.d.ts +4 -0
  118. package/dist/needs-you-hub.js +31 -6
  119. package/dist/needs-you-hub.js.map +1 -1
  120. package/dist/needs-you.d.ts +1 -13
  121. package/dist/needs-you.js +3 -37
  122. package/dist/needs-you.js.map +1 -1
  123. package/dist/outbound-handlers.d.ts +32 -0
  124. package/dist/outbound-handlers.js +150 -45
  125. package/dist/outbound-handlers.js.map +1 -1
  126. package/dist/outbound-schemas.d.ts +76 -0
  127. package/dist/outbound-schemas.js +65 -4
  128. package/dist/outbound-schemas.js.map +1 -1
  129. package/dist/provider-probe.d.ts +29 -0
  130. package/dist/provider-probe.js +198 -2
  131. package/dist/provider-probe.js.map +1 -1
  132. package/dist/public-web-gateway.d.ts +2 -0
  133. package/dist/public-web-gateway.js +25 -1
  134. package/dist/public-web-gateway.js.map +1 -1
  135. package/dist/quickstart-api.d.ts +1 -1
  136. package/dist/quickstart-api.js +8 -6
  137. package/dist/quickstart-api.js.map +1 -1
  138. package/dist/quickstart.d.ts +12 -0
  139. package/dist/quickstart.js +36 -6
  140. package/dist/quickstart.js.map +1 -1
  141. package/dist/reply-buttons-controller.d.ts +86 -5
  142. package/dist/reply-buttons-controller.js +271 -7
  143. package/dist/reply-buttons-controller.js.map +1 -1
  144. package/dist/reply-buttons.d.ts +48 -3
  145. package/dist/reply-buttons.js +105 -10
  146. package/dist/reply-buttons.js.map +1 -1
  147. package/dist/reply-polls.d.ts +85 -0
  148. package/dist/reply-polls.js +154 -0
  149. package/dist/reply-polls.js.map +1 -0
  150. package/dist/repo-worktree-guard.d.ts +36 -0
  151. package/dist/repo-worktree-guard.js +143 -0
  152. package/dist/repo-worktree-guard.js.map +1 -0
  153. package/dist/restart-guard.d.ts +6 -3
  154. package/dist/restart-guard.js +6 -3
  155. package/dist/restart-guard.js.map +1 -1
  156. package/dist/restart-progress.d.ts +0 -2
  157. package/dist/restart-progress.js +0 -2
  158. package/dist/restart-progress.js.map +1 -1
  159. package/dist/scheduler/scheduler.d.ts +58 -1
  160. package/dist/scheduler/scheduler.js +176 -10
  161. package/dist/scheduler/scheduler.js.map +1 -1
  162. package/dist/sd-notify.d.ts +22 -1
  163. package/dist/sd-notify.js +63 -10
  164. package/dist/sd-notify.js.map +1 -1
  165. package/dist/service-activation.d.ts +15 -7
  166. package/dist/service-activation.js +42 -26
  167. package/dist/service-activation.js.map +1 -1
  168. package/dist/service-installer.d.ts +24 -0
  169. package/dist/service-installer.js +49 -1
  170. package/dist/service-installer.js.map +1 -1
  171. package/dist/service-plan.d.ts +11 -0
  172. package/dist/service-plan.js +23 -0
  173. package/dist/service-plan.js.map +1 -1
  174. package/dist/settings-api.js +74 -1
  175. package/dist/settings-api.js.map +1 -1
  176. package/dist/settings-change.js +8 -0
  177. package/dist/settings-change.js.map +1 -1
  178. package/dist/settings-effect.js +16 -2
  179. package/dist/settings-effect.js.map +1 -1
  180. package/dist/settings-http-confirmation.js +5 -0
  181. package/dist/settings-http-confirmation.js.map +1 -1
  182. package/dist/systemd-peer-bus.d.ts +95 -0
  183. package/dist/systemd-peer-bus.js +430 -0
  184. package/dist/systemd-peer-bus.js.map +1 -0
  185. package/dist/systemd-runtime.d.ts +27 -1
  186. package/dist/systemd-runtime.js +69 -7
  187. package/dist/systemd-runtime.js.map +1 -1
  188. package/dist/tool-permissions.js +53 -3
  189. package/dist/tool-permissions.js.map +1 -1
  190. package/dist/topic-commands.d.ts +72 -2
  191. package/dist/topic-commands.js +156 -38
  192. package/dist/topic-commands.js.map +1 -1
  193. package/dist/topic-link.d.ts +128 -0
  194. package/dist/topic-link.js +269 -0
  195. package/dist/topic-link.js.map +1 -0
  196. package/dist/transcript-jsonl.d.ts +7 -2
  197. package/dist/transcript-jsonl.js +13 -7
  198. package/dist/transcript-jsonl.js.map +1 -1
  199. package/dist/transcript-sources.js +6 -1
  200. package/dist/transcript-sources.js.map +1 -1
  201. package/dist/types.d.ts +21 -0
  202. package/dist/ui/app-effects.js +187 -0
  203. package/dist/ui/chat-commands.js +1 -0
  204. package/dist/ui/chat-effect-posters.js +42 -0
  205. package/dist/ui/chat-effects.js +72 -0
  206. package/dist/ui/chat-live-replies.js +58 -0
  207. package/dist/ui/chat-render.js +59 -14
  208. package/dist/ui/chat-store.js +78 -8
  209. package/dist/ui/chat-strings.js +43 -16
  210. package/dist/ui/chat-thread.js +175 -31
  211. package/dist/ui/chat-tour.js +18 -15
  212. package/dist/ui/effect-trial-controller.js +119 -0
  213. package/dist/ui/effect-trial-offer.js +31 -0
  214. package/dist/ui/effect-trial-panel.js +37 -0
  215. package/dist/ui/image-lightbox.js +1 -1
  216. package/dist/ui/panel-chat.js +101 -27
  217. package/dist/ui/panel-details.js +18 -5
  218. package/dist/ui/panel-fleet.js +18 -5
  219. package/dist/ui/panel-log.js +10 -10
  220. package/dist/ui/panel-needs.js +13 -10
  221. package/dist/ui/panel-settings.js +66 -12
  222. package/dist/ui/preview-panel.js +25 -9
  223. package/dist/ui/settings-appearance.js +325 -0
  224. package/dist/ui/settings-dialogs.js +11 -6
  225. package/dist/ui/settings-effects.js +201 -0
  226. package/dist/ui/settings-model.js +3 -1
  227. package/dist/ui/settings-mods.js +257 -0
  228. package/dist/ui/settings-owner-effects.js +80 -0
  229. package/dist/ui/settings-strings.js +269 -10
  230. package/dist/ui/settings-token.js +7 -0
  231. package/dist/ui/settings-wizard.js +5 -3
  232. package/dist/ui/shared/agend-auth.js +27 -3
  233. package/dist/ui/shared/agent-widget.js +248 -0
  234. package/dist/ui/shared/app-i18n.js +3 -1
  235. package/dist/ui/shared/app-prefs.js +30 -10
  236. package/dist/ui/shared/app-route.js +23 -1
  237. package/dist/ui/shared/app-session.js +1 -1
  238. package/dist/ui/shared/app-shell.js +29 -16
  239. package/dist/ui/shared/app-store.js +2 -0
  240. package/dist/ui/shared/app-stream.js +15 -7
  241. package/dist/ui/shared/app-themes.js +188 -0
  242. package/dist/ui/shared/app.css +266 -11
  243. package/dist/ui/shared/app.js +6 -0
  244. package/dist/ui/shared/card-strings.js +28 -0
  245. package/dist/ui/shared/effect-app-runtime.js +248 -0
  246. package/dist/ui/shared/effect-baseline.js +64 -0
  247. package/dist/ui/shared/effect-chat-runtime.js +154 -0
  248. package/dist/ui/shared/effect-cursor.js +125 -0
  249. package/dist/ui/shared/effect-frame.js +127 -0
  250. package/dist/ui/shared/effect-geometry.js +122 -0
  251. package/dist/ui/shared/effect-host-style.js +10 -0
  252. package/dist/ui/shared/effect-owner-regions.js +74 -0
  253. package/dist/ui/shared/effect-owner-source.js +115 -0
  254. package/dist/ui/shared/effect-policy.js +137 -0
  255. package/dist/ui/shared/effect-poster-reader.js +98 -0
  256. package/dist/ui/shared/effect-posters.js +117 -0
  257. package/dist/ui/shared/effect-profiler.js +162 -0
  258. package/dist/ui/shared/effect-profiles.js +65 -0
  259. package/dist/ui/shared/effect-registry.js +496 -0
  260. package/dist/ui/shared/effect-runtime-loader.js +234 -0
  261. package/dist/ui/shared/effect-safety.js +153 -0
  262. package/dist/ui/shared/effect-stock-snapshot.js +34 -0
  263. package/dist/ui/shared/effect-stock-trust.js +165 -0
  264. package/dist/ui/shared/effect-trial-source.js +103 -0
  265. package/dist/ui/shared/effect-trial-stage.js +147 -0
  266. package/dist/ui/shared/header-tools.js +35 -15
  267. package/dist/ui/shared/identity-card.js +202 -0
  268. package/dist/ui/shared/identity-color.js +261 -0
  269. package/dist/ui/shared/instance-nav.js +22 -14
  270. package/dist/ui/shared/instance-switch.js +5 -2
  271. package/dist/ui/shared/owner-css.js +221 -0
  272. package/dist/ui/shared/owner-effect-prefs.js +117 -0
  273. package/dist/ui/shared/panel-view.js +96 -12
  274. package/dist/ui/shared/signin.js +17 -9
  275. package/dist/ui/shared/ui-dialog.js +1 -1
  276. package/dist/ui/shared/ui-icons.js +3 -0
  277. package/dist/ui/shared/ui-menu.js +1 -1
  278. package/dist/ui/shared/ui-toast.js +1 -1
  279. package/dist/ui/shared/view-strings.js +40 -2
  280. package/dist/ui/signin.html +9 -3
  281. package/dist/update-check.d.ts +31 -0
  282. package/dist/update-check.js +47 -0
  283. package/dist/update-check.js.map +1 -1
  284. package/dist/update-launch.d.ts +48 -1
  285. package/dist/update-launch.js +159 -5
  286. package/dist/update-launch.js.map +1 -1
  287. package/dist/usage/providers.d.ts +28 -8
  288. package/dist/usage/providers.js +96 -41
  289. package/dist/usage/providers.js.map +1 -1
  290. package/dist/view-api.d.ts +29 -0
  291. package/dist/view-api.js +214 -17
  292. package/dist/view-api.js.map +1 -1
  293. package/dist/web-api.d.ts +86 -2
  294. package/dist/web-api.js +1160 -92
  295. package/dist/web-api.js.map +1 -1
  296. package/dist/web-appearance-limits.d.ts +82 -0
  297. package/dist/web-appearance-limits.js +83 -0
  298. package/dist/web-appearance-limits.js.map +1 -0
  299. package/dist/web-appearance-policy.d.ts +66 -0
  300. package/dist/web-appearance-policy.js +94 -0
  301. package/dist/web-appearance-policy.js.map +1 -0
  302. package/dist/web-appearance-tools.d.ts +50 -0
  303. package/dist/web-appearance-tools.js +105 -0
  304. package/dist/web-appearance-tools.js.map +1 -0
  305. package/dist/web-appearance-view.d.ts +99 -0
  306. package/dist/web-appearance-view.js +163 -0
  307. package/dist/web-appearance-view.js.map +1 -0
  308. package/dist/web-appearance.d.ts +79 -0
  309. package/dist/web-appearance.js +298 -0
  310. package/dist/web-appearance.js.map +1 -0
  311. package/dist/web-asset-parts.d.ts +98 -0
  312. package/dist/web-asset-parts.js +133 -0
  313. package/dist/web-asset-parts.js.map +1 -0
  314. package/dist/web-auth.js +17 -1
  315. package/dist/web-auth.js.map +1 -1
  316. package/dist/web-card.d.ts +96 -0
  317. package/dist/web-card.js +232 -0
  318. package/dist/web-card.js.map +1 -0
  319. package/dist/web-chat-history.d.ts +39 -1
  320. package/dist/web-chat-history.js +69 -9
  321. package/dist/web-chat-history.js.map +1 -1
  322. package/dist/web-commands.d.ts +39 -3
  323. package/dist/web-commands.js +22 -5
  324. package/dist/web-commands.js.map +1 -1
  325. package/dist/web-effect-bundle.d.ts +28 -0
  326. package/dist/web-effect-bundle.js +126 -0
  327. package/dist/web-effect-bundle.js.map +1 -0
  328. package/dist/web-effect-canonical.d.ts +8 -0
  329. package/dist/web-effect-canonical.js +27 -0
  330. package/dist/web-effect-canonical.js.map +1 -0
  331. package/dist/web-effect-library.d.ts +114 -0
  332. package/dist/web-effect-library.js +530 -0
  333. package/dist/web-effect-library.js.map +1 -0
  334. package/dist/web-effect-poster.d.ts +8 -0
  335. package/dist/web-effect-poster.js +52 -0
  336. package/dist/web-effect-poster.js.map +1 -0
  337. package/dist/web-effect-profiles.d.ts +118 -0
  338. package/dist/web-effect-profiles.js +180 -0
  339. package/dist/web-effect-profiles.js.map +1 -0
  340. package/dist/web-effect-service.d.ts +86 -0
  341. package/dist/web-effect-service.js +312 -0
  342. package/dist/web-effect-service.js.map +1 -0
  343. package/dist/web-effect-tools.d.ts +47 -0
  344. package/dist/web-effect-tools.js +126 -0
  345. package/dist/web-effect-tools.js.map +1 -0
  346. package/dist/web-effect-trials.d.ts +86 -0
  347. package/dist/web-effect-trials.js +204 -0
  348. package/dist/web-effect-trials.js.map +1 -0
  349. package/dist/web-effect-upload.d.ts +29 -0
  350. package/dist/web-effect-upload.js +190 -0
  351. package/dist/web-effect-upload.js.map +1 -0
  352. package/dist/web-effect-worker.d.ts +8 -0
  353. package/dist/web-effect-worker.js +170 -0
  354. package/dist/web-effect-worker.js.map +1 -0
  355. package/dist/web-effect.d.ts +89 -0
  356. package/dist/web-effect.js +132 -0
  357. package/dist/web-effect.js.map +1 -0
  358. package/dist/web-identity-color.d.ts +87 -0
  359. package/dist/web-identity-color.js +209 -0
  360. package/dist/web-identity-color.js.map +1 -0
  361. package/dist/web-identity-palette.d.ts +113 -0
  362. package/dist/web-identity-palette.js +84 -0
  363. package/dist/web-identity-palette.js.map +1 -0
  364. package/dist/web-image-meta.d.ts +25 -0
  365. package/dist/web-image-meta.js +200 -0
  366. package/dist/web-image-meta.js.map +1 -0
  367. package/dist/web-login.d.ts +14 -0
  368. package/dist/web-login.js +4 -2
  369. package/dist/web-login.js.map +1 -1
  370. package/dist/web-mod-agent.d.ts +50 -0
  371. package/dist/web-mod-agent.js +203 -0
  372. package/dist/web-mod-agent.js.map +1 -0
  373. package/dist/web-mod-bundle.d.ts +144 -0
  374. package/dist/web-mod-bundle.js +464 -0
  375. package/dist/web-mod-bundle.js.map +1 -0
  376. package/dist/web-mod-export.d.ts +59 -0
  377. package/dist/web-mod-export.js +79 -0
  378. package/dist/web-mod-export.js.map +1 -0
  379. package/dist/web-mod-store.d.ts +283 -0
  380. package/dist/web-mod-store.js +624 -0
  381. package/dist/web-mod-store.js.map +1 -0
  382. package/dist/web-org.d.ts +3 -3
  383. package/dist/web-org.js +11 -17
  384. package/dist/web-org.js.map +1 -1
  385. package/dist/web-owner-css-store.d.ts +180 -0
  386. package/dist/web-owner-css-store.js +384 -0
  387. package/dist/web-owner-css-store.js.map +1 -0
  388. package/dist/web-owner-css.d.ts +58 -0
  389. package/dist/web-owner-css.js +552 -0
  390. package/dist/web-owner-css.js.map +1 -0
  391. package/dist/web-prefs.d.ts +66 -0
  392. package/dist/web-prefs.js +243 -35
  393. package/dist/web-prefs.js.map +1 -1
  394. package/dist/web-preview.d.ts +9 -1
  395. package/dist/web-preview.js +24 -7
  396. package/dist/web-preview.js.map +1 -1
  397. package/dist/web-profile.d.ts +32 -0
  398. package/dist/web-profile.js +132 -0
  399. package/dist/web-profile.js.map +1 -0
  400. package/dist/web-session.d.ts +14 -0
  401. package/dist/web-session.js +10 -0
  402. package/dist/web-session.js.map +1 -1
  403. package/dist/web-settings-patch.d.ts +18 -0
  404. package/dist/web-settings-patch.js +51 -0
  405. package/dist/web-settings-patch.js.map +1 -1
  406. package/dist/web-shell-routes.d.ts +1 -1
  407. package/dist/web-shell-routes.js +1 -1
  408. package/dist/web-shell-routes.js.map +1 -1
  409. package/dist/web-theme-store.d.ts +218 -0
  410. package/dist/web-theme-store.js +516 -0
  411. package/dist/web-theme-store.js.map +1 -0
  412. package/dist/web-theme.d.ts +121 -0
  413. package/dist/web-theme.js +407 -0
  414. package/dist/web-theme.js.map +1 -0
  415. package/dist/web-upload.d.ts +119 -4
  416. package/dist/web-upload.js +252 -39
  417. package/dist/web-upload.js.map +1 -1
  418. package/dist/web-widget.d.ts +72 -0
  419. package/dist/web-widget.js +245 -0
  420. package/dist/web-widget.js.map +1 -0
  421. package/launcher/install-admission.cjs +61 -6
  422. package/package.json +3 -3
package/dist/daemon.js CHANGED
@@ -1,6 +1,9 @@
1
1
  import { AGENT_WHATS_NEW, agendVersion, planWhatsNew, readWhatsNewRecord, recordWhatsNew, whatsNewNotice } from "./agent-whats-new.js";
2
- import { join, dirname, basename, resolve } from "node:path";
2
+ import { join, dirname, resolve } from "node:path";
3
+ import { checkoutTarget, readCheckouts, releaseCandidate, reposRoot, verifyWorktree, writeCheckouts } from "./repo-worktree-guard.js";
3
4
  import { mkdirSync, writeFileSync, readFileSync, readdirSync, existsSync, unlinkSync, rmSync, appendFileSync, statSync, renameSync } from "node:fs";
5
+ import { COMPACT_WATCH_TIMEOUT_MS, launchIdentity } from "./compact-watch.js";
6
+ import { KIRO_COMPACT_PANE_LINES, KiroCompactPaneFollower } from "./kiro-compact-pane.js";
4
7
  import { fileURLToPath } from "node:url";
5
8
  import { ensureInstanceDir } from "./private-dir.js";
6
9
  import { createHash, randomBytes, randomUUID } from "node:crypto";
@@ -17,6 +20,9 @@ import { TranscriptTurnLedger } from "./transcript-turns.js";
17
20
  import { credentialProfileStoreHome, resolveCredentialProfile } from "./backend/credential-profile.js";
18
21
  import { resolveToolSet } from "./tool-permissions.js";
19
22
  import { getAgendHome } from "./paths.js";
23
+ import { readAgentEffect, sendEffectInParts } from "./web-effect-upload.js";
24
+ import { readAgentFile } from "./agent-file-read.js";
25
+ import { ASSET_UPLOAD_MAX_BYTES, MOD_FILE, MOD_MAX_PARTS, MOD_UPLOAD_MAX_BYTES, receiveFiles, receivePreview, sendAssetInParts, writeModFile, writePreview } from "./web-asset-parts.js";
20
26
  import { LOGIN_FLOWS } from "./login-flows.js";
21
27
  import { ProgressAccumulator, summarizeProgress } from "./tool-progress.js";
22
28
  import { assembleSystemPrompt, resolveWorkflowText } from "./prompt-file-ref.js";
@@ -53,6 +59,26 @@ import { unrecognisedPromptShape } from "./unrecognised-prompt.js";
53
59
  const __filename = fileURLToPath(import.meta.url);
54
60
  const __dirname = dirname(__filename);
55
61
  // Tool routing sets — module-level to avoid re-creation on every handleToolCall
62
+ /** #1629: the caller's own web theme tools, each its own typed IPC message (one tool per type, for the permission check). */
63
+ const WEB_THEME_IPC = Object.freeze({
64
+ get_web_theme: "fleet_get_web_theme", set_web_theme: "fleet_set_web_theme",
65
+ set_web_theme_asset: "fleet_set_web_theme_asset", reset_web_theme: "fleet_reset_web_theme",
66
+ // r4 §1: how it appears — read-only; a preview's files are written here, on the agent's side.
67
+ get_web_appearance: "fleet_get_web_appearance", preview_web_appearance: "fleet_preview_web_appearance",
68
+ // r4-4: its own declarative widgets.
69
+ set_web_widget: "fleet_set_web_widget", remove_web_widget: "fleet_remove_web_widget",
70
+ // r4-5b: mods — an export's file is written here, a proposal's file read here (on the agent's side).
71
+ export_web_mod: "fleet_export_web_mod", propose_web_mod: "fleet_propose_web_mod",
72
+ upload_web_effect: "fleet_upload_web_effect",
73
+ list_web_effects: "fleet_list_web_effects",
74
+ preview_web_effect: "fleet_preview_web_effect",
75
+ profile_web_effect: "fleet_profile_web_effect",
76
+ apply_web_effect: "fleet_apply_web_effect",
77
+ remove_web_effect: "fleet_remove_web_effect",
78
+ delete_web_effect: "fleet_delete_web_effect",
79
+ });
80
+ /** The largest picture any slot may take (a card slot's ceiling; the fleet checks the limit in force): read no more. */
81
+ const WEB_THEME_ASSET_MAX_BYTES = ASSET_UPLOAD_MAX_BYTES;
56
82
  const CROSS_INSTANCE_TOOLS = new Set(["send_to_instance", "broadcast", "list_instances", "start_instance", "restart_instance", "create_instance", "delete_instance", "replace_instance", "request_information", "delegate_task", "report_result", "delivery_status", "describe_instance"]);
57
83
  const SCHEDULE_TOOLS = new Set(["create_schedule", "list_schedules", "update_schedule", "delete_schedule"]);
58
84
  const DECISION_TOOLS = new Set(["post_decision", "list_decisions", "update_decision"]);
@@ -102,7 +128,7 @@ export function buildInstructionReloadNotice(binaryName, instanceName, instanceD
102
128
  : binaryName === "kiro-cli"
103
129
  ? `.kiro/steering/agend-${instanceName}.md`
104
130
  : binaryName === "agy"
105
- ? ".agents/agents.md"
131
+ ? ".agents/AGENTS.md"
106
132
  : join(instanceDir, "fleet-instructions.md");
107
133
  return `[system] Your AgEnD instructions have been updated. Reload only ${source}; do not scan other instruction directories. Do not reply to this message.`;
108
134
  }
@@ -138,6 +164,8 @@ export const DEFAULT_STATE_IDLE_DEBOUNCE_MS = 2_000;
138
164
  export const DEFAULT_STATE_SAFETY_SWEEP_MS = 60_000;
139
165
  /** Coalesce one cosmetic-redraw burst before comparing the visible pane. */
140
166
  export const PERIODIC_REDRAW_PROBE_MS = 25;
167
+ /** Board 907c7899: how often a kiro /compact's pane is read (TUI V2's "too short" shows for about ten seconds). */
168
+ const KIRO_COMPACT_PANE_POLL_MS = 1_000;
141
169
  /** Do not turn a streaming Codex turn into one capture-pane per burst. */
142
170
  export const STRUCTURED_IDLE_PROBE_MS = 500;
143
171
  /**
@@ -645,6 +673,10 @@ export function channelStatus(meta) {
645
673
  // they are the dashboard's ticks (the fleet routes them by the message id, never to a platform).
646
674
  if (!messageId || (!chatId && meta.source !== "web"))
647
675
  return undefined;
676
+ // #1823: a notice from AgEnD itself (a poll's results) names its chat so the answer goes there, but its message id
677
+ // is AgEnD's own, not a platform message: there is nothing to react to.
678
+ if (meta.source === "agend-system")
679
+ return undefined;
648
680
  if (!chatId)
649
681
  return { chatId: "", messageId };
650
682
  const threadId = meta.thread_id || undefined;
@@ -1065,7 +1097,7 @@ export class BlockingProcessDetector {
1065
1097
  /** Response types the fleet sends back for tool calls the daemon forwarded. */
1066
1098
  const FLEET_RESPONSE_TYPES = new Set([
1067
1099
  "fleet_schedule_response", "fleet_outbound_response", "fleet_decision_response", "fleet_task_response",
1068
- "fleet_display_name_response", "fleet_description_response", "fleet_persona_emoji_response",
1100
+ "fleet_display_name_response", "fleet_description_response", "fleet_persona_emoji_response", "fleet_web_theme_response",
1069
1101
  ]);
1070
1102
  export class Daemon extends EventEmitter {
1071
1103
  name;
@@ -3767,6 +3799,42 @@ export class Daemon extends EventEmitter {
3767
3799
  : Math.max(this.deliveryEpoch, fleetEpoch);
3768
3800
  this.logger.info({ deliveryEpoch: this.deliveryEpoch }, "Pending delivery queue cleared by user cancel");
3769
3801
  }
3802
+ /** Board 907c7899: the layout a web /compact's end can be read from on this pane (kiro classic or TUI V2), or null. */
3803
+ compactPaneUi() {
3804
+ return this.backend?.compactPaneUi?.() ?? null;
3805
+ }
3806
+ /**
3807
+ * Board 907c7899: follow a /compact written to this pane until the pane says how it ended, then say so once
3808
+ * (`compact_pane_result`). Bound to the launch it was written under: another launch, or no pane, ends the look
3809
+ * silently (the fleet's watch sees the launch change and calls it lost); the fleet's deadline is the same.
3810
+ */
3811
+ async followCompactPane(id, owner, follower) {
3812
+ const tmux = this.tmux;
3813
+ if (!tmux || owner === null)
3814
+ return;
3815
+ // Monotonic, and asked after every await: a result past the deadline is not said (the fleet's watch has timed out).
3816
+ const deadline = performance.now() + COMPACT_WATCH_TIMEOUT_MS;
3817
+ const standing = () => this.tmux === tmux && launchIdentity(this.interactionOwner()) === owner && performance.now() < deadline;
3818
+ while (standing()) {
3819
+ await new Promise(resolve => { setTimeout(resolve, KIRO_COMPACT_PANE_POLL_MS).unref?.(); });
3820
+ if (!standing())
3821
+ return;
3822
+ let pane;
3823
+ try {
3824
+ pane = await tmux.capturePaneWithHistory(KIRO_COMPACT_PANE_LINES);
3825
+ }
3826
+ catch {
3827
+ continue;
3828
+ }
3829
+ if (!standing())
3830
+ return;
3831
+ const outcome = follower.look(pane);
3832
+ if (outcome) {
3833
+ this.ipcServer?.broadcast({ type: "compact_pane_result", id, owner, outcome });
3834
+ return;
3835
+ }
3836
+ }
3837
+ }
3770
3838
  captureDeliveryEpoch(value) {
3771
3839
  const epoch = typeof value === "number" && Number.isSafeInteger(value) && value >= 0
3772
3840
  ? value
@@ -3778,21 +3846,55 @@ export class Daemon extends EventEmitter {
3778
3846
  isDeliveryEpochCurrent(epoch) {
3779
3847
  return epoch === this.deliveryEpoch;
3780
3848
  }
3781
- queueRawPaste(rawText, deliveryEpoch = this.deliveryEpoch, confirmClear = false, durableAttempt, ownSwitch) {
3849
+ queueRawPaste(rawText, deliveryEpoch = this.deliveryEpoch, confirmClear = false, durableAttempt, ownSwitch, notifyWritten) {
3782
3850
  const reportSwitch = (result) => {
3783
3851
  if (!ownSwitch)
3784
3852
  return;
3785
3853
  this.ipcServer?.broadcast({ type: "runtime_switch_result", id: ownSwitch.id, ...result });
3786
3854
  };
3855
+ // Board 003648ac: the fleet follows a web /compact from just before its paste is written: the transcript's end then
3856
+ // (taken here, under this launch, before the write) and the launch it was written under ride with the notice — once.
3857
+ // The checkpoint and the launch are taken together, right before the paste (each attempt takes them again); the
3858
+ // notice carries those — never the launch at notice time, which a respawn in between would relabel.
3859
+ let told = false;
3860
+ let checkpoint = null;
3861
+ let owner = null;
3862
+ // Board 907c7899: kiro says how a /compact ended only on its own pane; read from the pane just before this write.
3863
+ let paneFollower = null;
3864
+ const tellWritten = (written) => {
3865
+ if (!notifyWritten || told)
3866
+ return;
3867
+ told = true;
3868
+ this.ipcServer?.broadcast({ type: "raw_paste_written", id: notifyWritten, outcome: written ? "written" : "not_written",
3869
+ ...(written ? { owner, checkpoint, ...(paneFollower ? { pane: true } : {}) } : {}) });
3870
+ if (written && paneFollower)
3871
+ void this.followCompactPane(notifyWritten, owner, paneFollower);
3872
+ };
3873
+ const takeCheckpoint = () => {
3874
+ owner = launchIdentity(this.interactionOwner());
3875
+ const path = this.logTranscriptPath();
3876
+ checkpoint = null;
3877
+ if (!path)
3878
+ return;
3879
+ try {
3880
+ const st = statSync(path);
3881
+ checkpoint = { path, ino: st.ino, size: st.size };
3882
+ }
3883
+ catch {
3884
+ checkpoint = null;
3885
+ }
3886
+ };
3787
3887
  if (!this.tmux || !this.isDeliveryEpochCurrent(deliveryEpoch)) {
3788
3888
  if (durableAttempt)
3789
3889
  this.retryDurableDeliveryBeforeBegin(durableAttempt, "raw_paste target unavailable before pane queue");
3790
3890
  reportSwitch({ outcome: "not_sent" });
3891
+ tellWritten(false);
3791
3892
  return;
3792
3893
  }
3793
3894
  this.pasteLock = this.pasteLock.then(async () => {
3794
3895
  if (!this.isDeliveryEpochCurrent(deliveryEpoch)) {
3795
3896
  reportSwitch({ outcome: "not_sent" });
3897
+ tellWritten(false);
3796
3898
  this.logger.info("Pending raw delivery dropped by user cancel");
3797
3899
  if (durableAttempt)
3798
3900
  this.retryDurableDeliveryBeforeBegin(durableAttempt, "raw_paste epoch changed while queued");
@@ -3805,8 +3907,16 @@ export class Daemon extends EventEmitter {
3805
3907
  if (durableAttempt)
3806
3908
  this.retryDurableDeliveryBeforeBegin(durableAttempt, "raw_paste epoch changed while waking");
3807
3909
  reportSwitch({ outcome: "not_sent" }); // #1690: cancelled before anything was typed — said at once, nothing saved
3910
+ tellWritten(false);
3808
3911
  return;
3809
3912
  }
3913
+ // Board 907c7899: the pane just before this attempt's paste — after its readiness wait, last capture and fence,
3914
+ // under the paste lock (no other input lands in between). Each attempt looks again.
3915
+ const lookBefore = async () => {
3916
+ const ui = this.backend?.compactPaneUi?.() ?? null;
3917
+ const before = ui ? await this.tmux?.capturePaneWithHistory(KIRO_COMPACT_PANE_LINES).catch(() => null) : null;
3918
+ paneFollower = ui && typeof before === "string" ? new KiroCompactPaneFollower(ui, before) : null;
3919
+ };
3810
3920
  const delivered = await this.deliverMessage(rawText, undefined, {
3811
3921
  deliveryEpoch,
3812
3922
  verdict,
@@ -3814,8 +3924,11 @@ export class Daemon extends EventEmitter {
3814
3924
  preserveExactBytes: true,
3815
3925
  // #1690: our own /model holds every other delivery from its write on — before the lock is released, so a steer
3816
3926
  // or /btw waiting on it cannot type into the gap before claude paints "Switch model?" (default cursor: Yes).
3817
- ...(ownSwitch ? { onPaneWritten: () => { this.ownSwitch = ownSwitch; } } : {}),
3927
+ ...(ownSwitch || notifyWritten ? { onPaneWritten: () => { if (ownSwitch)
3928
+ this.ownSwitch = ownSwitch; tellWritten(true); } } : {}),
3929
+ ...(notifyWritten ? { beforePaneWrite: takeCheckpoint, ...(this.backend?.compactPaneUi?.() ? { lookBeforePaneWrite: lookBefore } : {}) } : {}),
3818
3930
  });
3931
+ tellWritten(false); // never written (once told, this says nothing)
3819
3932
  if (delivered)
3820
3933
  this.finishDurableSubmission(durableAttempt ?? null, verdict);
3821
3934
  else if (durableAttempt && verdict.paneWriteStarted) {
@@ -3852,6 +3965,7 @@ export class Daemon extends EventEmitter {
3852
3965
  this.ownSwitch = null;
3853
3966
  reportSwitch({ outcome: "not_confirmed" });
3854
3967
  }
3968
+ tellWritten(false);
3855
3969
  return;
3856
3970
  }
3857
3971
  if (confirmClear)
@@ -3893,7 +4007,8 @@ export class Daemon extends EventEmitter {
3893
4007
  const sw = msg.runtime_switch;
3894
4008
  const ownSwitch = sw && typeof sw.id === "string" && sw.kind === "model" && typeof sw.requested === "string"
3895
4009
  ? { id: sw.id, kind: "model", requested: sw.requested } : undefined;
3896
- this.queueRawPaste(msg.content, this.captureDeliveryEpoch(msg.delivery_epoch), msg.confirm_clear === true, durableAttempt ? { ...durableAttempt, submissionMode: "raw_paste" } : undefined, ownSwitch);
4010
+ const notifyWritten = typeof msg.notify_written === "string" && /^[0-9a-f]{16}$/.test(msg.notify_written) ? msg.notify_written : undefined;
4011
+ this.queueRawPaste(msg.content, this.captureDeliveryEpoch(msg.delivery_epoch), msg.confirm_clear === true, durableAttempt ? { ...durableAttempt, submissionMode: "raw_paste", silentSchedule: msg.silent_schedule === true } : undefined, ownSwitch, notifyWritten);
3897
4012
  }
3898
4013
  /**
3899
4014
  * #1690 (the user's choice A): after AgEnD's own `/model <name>`, claude may ask "Switch model?" (a warm cache for
@@ -5332,8 +5447,9 @@ export class Daemon extends EventEmitter {
5332
5447
  * every delivery parked behind it. It recovers when the backend sees its own live, EMPTY composer with no busy, queued
5333
5448
  * or dialog marker (isStableUnknownLayoutIdlePane) AND the transcript shows no open turn — or, with no transcript to
5334
5449
  * ask, that same composer held unchanged for UNKNOWN_LAYOUT_STABLE_MS (#978's rule). An open turn never recovers.
5450
+ * #1865: `hold` makes the closed-turn case wait out that window too (a `working` reading, recovered before the stuck deadline).
5335
5451
  */
5336
- recoverStuck(pane, snapshot, changeAt, turn) {
5452
+ recoverStuck(pane, snapshot, changeAt, turn, hold = false) {
5337
5453
  const machine = this.instanceStateMachine;
5338
5454
  const idleComposer = this.backend?.isStableUnknownLayoutIdlePane?.(pane) === true;
5339
5455
  // The resume load (#1031: `Resuming session…` over the same composer) is input that is not open yet (#1641 R1).
@@ -5349,7 +5465,7 @@ export class Daemon extends EventEmitter {
5349
5465
  this.stuckRecoveryKey = key;
5350
5466
  this.stuckRecoverySince = mono;
5351
5467
  }
5352
- if (turn === "unknown" && mono - this.stuckRecoverySince < UNKNOWN_LAYOUT_STABLE_MS)
5468
+ if ((turn === "unknown" || hold) && mono - this.stuckRecoverySince < UNKNOWN_LAYOUT_STABLE_MS)
5353
5469
  return snapshot;
5354
5470
  // A footer this build cannot read stays unreadable: the same still pane reads stuck again at every sweep and is
5355
5471
  // recovered again. Only the edge into idle is news.
@@ -5918,6 +6034,13 @@ export class Daemon extends EventEmitter {
5918
6034
  }
5919
6035
  if (snapshot.state === "stuck")
5920
6036
  snapshot = this.recoverStuck(pane, snapshot, observedChangeAt, transcriptTurn);
6037
+ // #1865: a resumed codex can paint its footer without the Context item (sol after the beta.13 restart), so its
6038
+ // idle composer reads `working` for the whole stuck window. Its own rollout says no turn is open: the sweep needs
6039
+ // no deadline for that — the same composer held still for UNKNOWN_LAYOUT_STABLE_MS is enough.
6040
+ else if (snapshot.state === "working" && reason === "safety_sweep" && transcriptTurn === "closed"
6041
+ && this.backend?.binaryName === "codex") {
6042
+ snapshot = this.recoverStuck(pane, snapshot, observedChangeAt, transcriptTurn, true);
6043
+ }
5921
6044
  this.applyInstanceStateSnapshot(snapshot, pane);
5922
6045
  // Backends without a transcript feed can still say what they are doing, if
5923
6046
  // their TUI names it. Free-riding on a capture that already happened: no
@@ -6957,7 +7080,7 @@ export class Daemon extends EventEmitter {
6957
7080
  return stale();
6958
7081
  if (residue !== "clear")
6959
7082
  return this.failDelivery(verdict, status, "input-residue", residue);
6960
- const written = await this.writeMessageToPane(formatted, windowId, handingOffToNativeQueue, status, opts?.submissionId, verdict, opts?.durableAttempt, opts?.steer === true, writeCurrent, opts?.humanVerificationId);
7083
+ const written = await this.writeMessageToPane(formatted, windowId, handingOffToNativeQueue, status, opts?.submissionId, verdict, opts?.durableAttempt, opts?.steer === true, writeCurrent, opts?.humanVerificationId, opts?.beforePaneWrite, opts?.lookBeforePaneWrite);
6961
7084
  // #1690: still under the lock — whatever waits on it next must already see what this write started.
6962
7085
  if (verdict.paneWriteStarted)
6963
7086
  opts?.onPaneWritten?.();
@@ -8473,7 +8596,11 @@ export class Daemon extends EventEmitter {
8473
8596
  verdict = { reached: false }, durableAttempt, steer = false,
8474
8597
  // #829: the caller's fence, asked once more after the last await before the
8475
8598
  // first side effect. Stale means unwritten: false, with no failure verdict.
8476
- stillCurrent, humanVerificationId) {
8599
+ stillCurrent, humanVerificationId,
8600
+ // Board 003648ac: called synchronously right before each paste — after the last await and fence of that attempt.
8601
+ beforePaste,
8602
+ // Board 907c7899: awaited each attempt after its last capture, begin and fence; the fence is asked again after it.
8603
+ lookBeforePaste) {
8477
8604
  const signature = this.submissionSignature(formatted, humanVerificationId ?? submissionId, humanVerificationId !== undefined);
8478
8605
  const rawPaste = durableAttempt?.submissionMode === "raw_paste";
8479
8606
  const humanAttempt = verdict.humanReceipt;
@@ -8576,13 +8703,41 @@ export class Daemon extends EventEmitter {
8576
8703
  }
8577
8704
  }
8578
8705
  }
8579
- if (rawPaste)
8706
+ if (lookBeforePaste) {
8707
+ await lookBeforePaste();
8708
+ // The look awaited: this attempt is still this write's — the same fence, tmux and launch — or nothing is written.
8709
+ if (!steerCurrent()) {
8710
+ verdict.fenced = !verdict.durableBeginCommitted;
8711
+ return false;
8712
+ }
8713
+ }
8714
+ if (rawPaste) {
8580
8715
  verdict.paneWriteStarted = true;
8716
+ // #1843: this write starts silent work even if its submission proof later stays uncertain.
8717
+ // Retire the preceding human guard before the first write, not after proof: its idle callback must never
8718
+ // turn a silent heartbeat into a channel reply or a reply-required recovery prompt. This may also retire a
8719
+ // genuine missed human reply still awaiting confirmation/recovery; log that tradeoff instead of notifying.
8720
+ if (durableAttempt?.silentSchedule) {
8721
+ this.clearReplyGuardConfirm();
8722
+ const previous = this.turnReplyGuard.snapshot();
8723
+ if (previous) {
8724
+ this.turnReplyGuard.complete(previous.generation);
8725
+ this.logger.info({
8726
+ correlationId: previous.target.correlationId ?? null,
8727
+ generation: previous.generation,
8728
+ phase: previous.phase,
8729
+ reason: "silent_schedule_delivery",
8730
+ deliveryId: durableAttempt.deliveryId,
8731
+ }, "Reply guard retired before silent schedule write");
8732
+ }
8733
+ }
8734
+ }
8581
8735
  // For raw commands the paste itself may alter the live composer. Treat a
8582
8736
  // failed/throwing write as uncertain rather than retrying bytes into the
8583
8737
  // same pane; the write-ahead Enter marker later separates safe pre-Enter
8584
8738
  // recovery from a possibly executed command.
8585
8739
  const pasteGeneration = this.spawnGeneration;
8740
+ beforePaste?.();
8586
8741
  const pasted = await this.tmux.pasteBuffer(formatted);
8587
8742
  if (pasted)
8588
8743
  this.rememberPaste(formatted, pasteGeneration);
@@ -9814,6 +9969,169 @@ export class Daemon extends EventEmitter {
9814
9969
  });
9815
9970
  return;
9816
9971
  }
9972
+ // #1629: the caller's own web theme. set_web_theme_asset's file is read HERE, on the agent's side (in its working
9973
+ // directory, no symlink, size-capped): the fleet receives bytes, never a workspace path.
9974
+ if (Object.hasOwn(WEB_THEME_IPC, tool)) {
9975
+ // Answered exactly once, whatever happens on the way (#1887 review: a throw here once left the call unanswered).
9976
+ let answered = false;
9977
+ const reply = (result, error) => { if (answered)
9978
+ return; answered = true; respond(result, error); };
9979
+ // One fleet request; resolves with its answer, or an error string (never rejects).
9980
+ const ask = (payload) => new Promise((resolve) => {
9981
+ const fleetReqId = `theme_${++this.fleetRequestSeq}_${requestId}`;
9982
+ const timeout = setTimeout(() => {
9983
+ this.pendingIpcRequests.delete(fleetReqId);
9984
+ resolve({ result: null, error: `${tool} timed out` });
9985
+ }, daemonBudgetMs(tool));
9986
+ this.pendingIpcRequests.set(fleetReqId, (respMsg) => {
9987
+ clearTimeout(timeout);
9988
+ resolve({ result: respMsg.result, error: respMsg.error });
9989
+ });
9990
+ this.ipcServer?.broadcast({ type: WEB_THEME_IPC[tool], payload, meta: { instance_name: this.name }, fleetRequestId: fleetReqId });
9991
+ });
9992
+ void (async () => {
9993
+ if (tool === "preview_web_appearance") {
9994
+ // The fleet builds the SVGs (whole, or in parts); they are written here, in the working directory.
9995
+ const first = await ask({ ...(args.modes !== undefined ? { modes: args.modes } : {}) });
9996
+ if (first.error) {
9997
+ reply(null, first.error);
9998
+ return;
9999
+ }
10000
+ let failed;
10001
+ const got = await receivePreview((first.result && typeof first.result === "object" ? first.result : {}), async (payload) => {
10002
+ const r = await ask(payload);
10003
+ if (r.error) {
10004
+ failed = r.error;
10005
+ return { error: r.error };
10006
+ }
10007
+ return (r.result && typeof r.result === "object" ? r.result : {});
10008
+ });
10009
+ if (failed) {
10010
+ reply(null, failed);
10011
+ return;
10012
+ }
10013
+ if ("error" in got) {
10014
+ reply(got, undefined);
10015
+ return;
10016
+ }
10017
+ const written = await writePreview(got.files, { workspace: this.config.working_directory, stateDir: getAgendHome() });
10018
+ if ("error" in written) {
10019
+ reply(null, `preview_web_appearance: ${written.error}`);
10020
+ return;
10021
+ }
10022
+ reply(written, undefined);
10023
+ return;
10024
+ }
10025
+ if (tool === "export_web_mod") {
10026
+ // The fleet builds the file (whole, or in parts); it is written here, in the working directory.
10027
+ const first = await ask(args);
10028
+ if (first.error) {
10029
+ reply(null, first.error);
10030
+ return;
10031
+ }
10032
+ let failed;
10033
+ const firstResult = (first.result && typeof first.result === "object" ? first.result : {});
10034
+ const got = await receiveFiles(firstResult, async (payload) => {
10035
+ const r = await ask(payload);
10036
+ if (r.error) {
10037
+ failed = r.error;
10038
+ return { error: r.error };
10039
+ }
10040
+ return (r.result && typeof r.result === "object" ? r.result : {});
10041
+ }, { name: MOD_FILE, idKey: "transfer", what: "export", maxParts: MOD_MAX_PARTS });
10042
+ if (failed) {
10043
+ reply(null, failed);
10044
+ return;
10045
+ }
10046
+ if ("error" in got) {
10047
+ reply(got, undefined);
10048
+ return;
10049
+ }
10050
+ const written = await writeModFile(got.files, { workspace: this.config.working_directory, stateDir: getAgendHome() });
10051
+ if ("error" in written) {
10052
+ reply(null, `export_web_mod: ${written.error}`);
10053
+ return;
10054
+ }
10055
+ reply({ ...written, ...(firstResult.effects_omitted ? { effects_omitted: firstResult.effects_omitted } : {}) }, undefined);
10056
+ return;
10057
+ }
10058
+ if (tool === "propose_web_mod") {
10059
+ const read = await readAgentFile(args.file, { workspace: this.config.working_directory, stateDir: getAgendHome(), maxBytes: MOD_UPLOAD_MAX_BYTES });
10060
+ const named = typeof args.file === "string" ? args.file.slice(0, 200) : "the file argument";
10061
+ if (!read.ok) {
10062
+ reply(null, `propose_web_mod: ${named} — ${read.reason}`);
10063
+ return;
10064
+ }
10065
+ let failed;
10066
+ const answer = await sendAssetInParts("mod", read.bytes, async (payload) => {
10067
+ const r = await ask(payload);
10068
+ if (r.error) {
10069
+ failed = r.error;
10070
+ return { error: r.error };
10071
+ }
10072
+ return (r.result && typeof r.result === "object" ? r.result : {});
10073
+ });
10074
+ reply(failed ? null : answer, failed);
10075
+ return;
10076
+ }
10077
+ if (tool === "upload_web_effect") {
10078
+ const owner = { fence: this.launchFenceEpoch, spawn: this.spawnGeneration, boot: this.bootId, ipc: this.ipcServer };
10079
+ const current = () => !!owner.ipc && owner.ipc === this.ipcServer && owner.fence === this.launchFenceEpoch && owner.spawn === this.spawnGeneration && owner.boot === this.bootId && !this.deliveryWritesStopping && !this.startupAborted;
10080
+ if (!current()) {
10081
+ reply(null, "upload_web_effect: launch no longer current");
10082
+ return;
10083
+ }
10084
+ const read = await readAgentEffect(args.dir, { workspace: this.config.working_directory, stateDir: getAgendHome() });
10085
+ if (!current()) {
10086
+ reply(null, "upload_web_effect: launch no longer current");
10087
+ return;
10088
+ }
10089
+ if (!read.ok) {
10090
+ reply(null, `upload_web_effect: ${read.reason}`);
10091
+ return;
10092
+ }
10093
+ let failed;
10094
+ const answer = await sendEffectInParts(read.files, async (payload) => {
10095
+ if (!current()) {
10096
+ failed = "upload_web_effect: launch no longer current";
10097
+ return { error: failed };
10098
+ }
10099
+ const r = await ask(payload);
10100
+ if (r.error) {
10101
+ failed = r.error;
10102
+ return { error: r.error };
10103
+ }
10104
+ return (r.result && typeof r.result === "object" ? r.result : {});
10105
+ });
10106
+ reply(failed ? null : answer, failed);
10107
+ return;
10108
+ }
10109
+ if (tool !== "set_web_theme_asset") {
10110
+ const r = await ask(args);
10111
+ reply(r.result, r.error);
10112
+ return;
10113
+ }
10114
+ const read = await readAgentFile(args.file, { workspace: this.config.working_directory, stateDir: getAgendHome(), maxBytes: WEB_THEME_ASSET_MAX_BYTES });
10115
+ // The path as the agent gave it — only a string is shown (an object from raw JSON args is never coerced).
10116
+ const named = typeof args.file === "string" ? args.file.slice(0, 200) : "the file argument";
10117
+ if (!read.ok) {
10118
+ reply(null, `set_web_theme_asset: ${named} — ${read.reason}`);
10119
+ return;
10120
+ }
10121
+ // A picture larger than one IPC message goes up in parts (r4 §1: a card portrait may be megabytes).
10122
+ let failed;
10123
+ const answer = await sendAssetInParts(args.slot, read.bytes, async (payload) => {
10124
+ const r = await ask(payload);
10125
+ if (r.error) {
10126
+ failed = r.error;
10127
+ return { error: r.error };
10128
+ }
10129
+ return (r.result && typeof r.result === "object" ? r.result : {});
10130
+ });
10131
+ reply(failed ? null : answer, failed);
10132
+ })().catch((err) => reply(null, `${tool} failed: ${err instanceof Error ? err.message : "error"}`));
10133
+ return;
10134
+ }
9817
10135
  if (tool === TASK_TOOL) {
9818
10136
  const fleetReqId = `task_${++this.fleetRequestSeq}_${requestId}`;
9819
10137
  const timeout = setTimeout(() => {
@@ -9994,6 +10312,11 @@ export class Daemon extends EventEmitter {
9994
10312
  respond(null, `reply: ${buttons.error}`);
9995
10313
  return;
9996
10314
  }
10315
+ // #1823: a poll's results need the fleet's store and outbox; as text it would never close.
10316
+ if (args.poll !== undefined && args.poll !== null) {
10317
+ respond(null, "reply: a poll needs AgEnD's fleet; send the question without poll");
10318
+ return;
10319
+ }
9997
10320
  if (buttons) {
9998
10321
  routed = { ...args, text: `${String(args.text)}\n\n${replyButtonsFallbackText(buttons.buttons)}` };
9999
10322
  delete routed.buttons;
@@ -11208,6 +11531,11 @@ export class Daemon extends EventEmitter {
11208
11531
  this.logger.warn({ warning: launchWarning }, "Backend launch fell back");
11209
11532
  this.emit("backend_launch_warning", { name: this.name, message: launchWarning });
11210
11533
  }
11534
+ // #1811: what the launch left out and why (an effort this CLI or model does not take) — the Log tab, not the chat.
11535
+ for (const note of this.backend.consumeQuietLaunchNotes?.() ?? []) {
11536
+ this.logger.info({ note }, "Launch note");
11537
+ this.emit("backend_launch_warning", { name: this.name, message: note, quiet: true });
11538
+ }
11211
11539
  // Ensure tmux session exists (may have been destroyed if all windows died)
11212
11540
  await TmuxManager.ensureSession(this.tmuxSessionName);
11213
11541
  if (this.stormWindow?.observeServerAlive(await TmuxManager.getServerPid(this.tmuxSessionName))) {
@@ -11914,16 +12242,29 @@ export class Daemon extends EventEmitter {
11914
12242
  respond(null, `Not a git repository: ${source}`);
11915
12243
  return;
11916
12244
  }
11917
- const repoDir = join(this.instanceDir, "repos");
11918
- mkdirSync(repoDir, { recursive: true });
11919
- const safeName = `${basename(source)}-${branch.replace(/\//g, "-")}`;
11920
- const worktreePath = join(repoDir, safeName);
12245
+ mkdirSync(join(this.instanceDir, "repos"), { recursive: true });
12246
+ // Only a fresh direct child of a real repos/ directory (never through a symlinked repos/, never an existing path).
12247
+ const target = checkoutTarget(this.instanceDir, source, branch);
12248
+ if (!target.ok) {
12249
+ respond(null, `checkout_repo: ${target.reason}`);
12250
+ return;
12251
+ }
12252
+ const worktreePath = target.path;
11921
12253
  try {
11922
- // Resolve branch/ref to verify it exists. Use `--` so git never treats
11923
- // branch as an option flag (defense in depth on top of the regex above).
11924
- await execFileAsync("git", ["rev-parse", "--verify", "--", branch], { cwd: source });
11925
- await execFileAsync("git", ["worktree", "add", "--detach", worktreePath, branch], { cwd: source });
12254
+ // Resolve branch/ref to verify it exists. `--end-of-options` keeps git from ever reading branch as an option
12255
+ // flag (defense in depth on top of the regex above). Not `--`: rev-parse then reads branch as a path, and
12256
+ // `--verify` fails for every ref ("Needed a single revision"), which broke checkout_repo entirely.
12257
+ await execFileAsync("git", ["rev-parse", "--verify", "--end-of-options", `${branch}^{commit}`], { cwd: source });
12258
+ await execFileAsync("git", ["worktree", "add", "--detach", worktreePath, "--end-of-options", branch], { cwd: source });
11926
12259
  const { stdout: commitHash } = await execFileAsync("git", ["rev-parse", "--short", "HEAD"], { cwd: worktreePath });
12260
+ // Recorded so release_repo can tell this instance's checkout of this repository from anything else.
12261
+ try {
12262
+ const { stdout: common } = await execFileAsync("git", ["rev-parse", "--path-format=absolute", "--git-common-dir"], { cwd: worktreePath });
12263
+ const root = reposRoot(this.instanceDir);
12264
+ if (root.ok)
12265
+ writeCheckouts(root.path, { ...readCheckouts(root.path), [worktreePath]: common.trim() });
12266
+ }
12267
+ catch { /* unrecorded: release still requires a listed linked worktree */ }
11927
12268
  respond({ path: worktreePath, branch, source, commit: commitHash.trim() });
11928
12269
  }
11929
12270
  catch (err) {
@@ -11931,26 +12272,50 @@ export class Daemon extends EventEmitter {
11931
12272
  }
11932
12273
  }
11933
12274
  async handleReleaseRepo(args, respond) {
11934
- const repoPath = args.path;
11935
- const reposDir = join(this.instanceDir, "repos");
11936
- // Safety: only allow releasing paths under our repos/ directory
11937
- if (!repoPath.startsWith(reposDir)) {
11938
- respond(null, `Cannot release path outside instance repos directory`);
12275
+ // Containment first (real paths, strictly inside this instance's repos/), then proof it is a git worktree.
12276
+ // Nothing that fails either check is ever removed.
12277
+ const candidate = releaseCandidate(this.instanceDir, args.path);
12278
+ if (!candidate.ok) {
12279
+ respond(null, `Cannot release: ${candidate.reason}`);
11939
12280
  return;
11940
12281
  }
12282
+ const root = reposRoot(this.instanceDir);
12283
+ if (!root.ok) {
12284
+ respond(null, `Cannot release: ${root.reason}`);
12285
+ return;
12286
+ }
12287
+ const { execFile: execFileCb } = await import("node:child_process");
12288
+ const { promisify } = await import("node:util");
12289
+ const execFileAsync = promisify(execFileCb);
12290
+ const git = async (gitArgs, cwd) => (await execFileAsync("git", gitArgs, { cwd })).stdout;
12291
+ const worktree = await verifyWorktree(root.path, candidate.path, git);
12292
+ if (!worktree.ok) {
12293
+ respond(null, `Cannot release: ${worktree.reason}`);
12294
+ return;
12295
+ }
12296
+ const repoPath = candidate.path;
11941
12297
  try {
11942
- const { execFile: execFileCb } = await import("node:child_process");
11943
- const { promisify } = await import("node:util");
11944
- const execFileAsync = promisify(execFileCb);
11945
- await execFileAsync("git", ["worktree", "remove", "--force", repoPath]);
12298
+ await git(["--git-dir", worktree.commonDir, "worktree", "remove", "--force", repoPath], worktree.commonDir);
11946
12299
  }
11947
12300
  catch {
11948
- // Fallback: rm directly if git worktree remove fails
12301
+ // Fallback for a verified worktree only: remove it, then let its repository forget it.
11949
12302
  try {
11950
12303
  rmSync(repoPath, { recursive: true, force: true });
11951
12304
  }
11952
12305
  catch { /* best effort */ }
12306
+ try {
12307
+ await git(["--git-dir", worktree.commonDir, "worktree", "prune"], worktree.commonDir);
12308
+ }
12309
+ catch { /* best effort */ }
12310
+ }
12311
+ try {
12312
+ const records = readCheckouts(root.path);
12313
+ if (repoPath in records) {
12314
+ delete records[repoPath];
12315
+ writeCheckouts(root.path, records);
12316
+ }
11953
12317
  }
12318
+ catch { /* the worktree is gone; a stale record only refuses a later, different repo at this path */ }
11954
12319
  respond({ released: true, path: repoPath });
11955
12320
  }
11956
12321
  buildSnapshotPrompt() {