@songsid/agend 2.3.0-beta.2 → 2.3.0-beta.20

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 (502) hide show
  1. package/README.md +7 -1
  2. package/README.zh-TW.md +7 -1
  3. package/dist/agent-cli-instructions.md +9 -2
  4. package/dist/agent-cli.js +63 -2
  5. package/dist/agent-cli.js.map +1 -1
  6. package/dist/agent-endpoint.js +4 -0
  7. package/dist/agent-endpoint.js.map +1 -1
  8. package/dist/agent-file-read.d.ts +4 -1
  9. package/dist/agent-file-read.js +189 -16
  10. package/dist/agent-file-read.js.map +1 -1
  11. package/dist/agent-whats-new.d.ts +11 -0
  12. package/dist/agent-whats-new.js +29 -5
  13. package/dist/agent-whats-new.js.map +1 -1
  14. package/dist/auth-api.d.ts +30 -1
  15. package/dist/auth-api.js +45 -13
  16. package/dist/auth-api.js.map +1 -1
  17. package/dist/authority/authority-state.d.ts +38 -0
  18. package/dist/authority/authority-state.js +114 -0
  19. package/dist/authority/authority-state.js.map +1 -0
  20. package/dist/authority/session-authorizations.d.ts +43 -0
  21. package/dist/authority/session-authorizations.js +102 -0
  22. package/dist/authority/session-authorizations.js.map +1 -0
  23. package/dist/authority/web-authority.d.ts +120 -0
  24. package/dist/authority/web-authority.js +157 -0
  25. package/dist/authority/web-authority.js.map +1 -0
  26. package/dist/backend/claude-code.d.ts +2 -2
  27. package/dist/backend/claude-code.js +18 -8
  28. package/dist/backend/claude-code.js.map +1 -1
  29. package/dist/backend/codex-metadata.d.ts +8 -0
  30. package/dist/backend/codex-metadata.js +74 -17
  31. package/dist/backend/codex-metadata.js.map +1 -1
  32. package/dist/backend/codex.js +15 -2
  33. package/dist/backend/codex.js.map +1 -1
  34. package/dist/backend/credential-profile.d.ts +2 -0
  35. package/dist/backend/credential-profile.js +2 -0
  36. package/dist/backend/credential-profile.js.map +1 -1
  37. package/dist/backend/types.d.ts +6 -0
  38. package/dist/backend/types.js +15 -0
  39. package/dist/backend/types.js.map +1 -1
  40. package/dist/backend-availability.d.ts +120 -0
  41. package/dist/backend-availability.js +396 -0
  42. package/dist/backend-availability.js.map +1 -0
  43. package/dist/backend-command.d.ts +23 -3
  44. package/dist/backend-command.js +25 -4
  45. package/dist/backend-command.js.map +1 -1
  46. package/dist/bug-report-cli.d.ts +30 -0
  47. package/dist/bug-report-cli.js +138 -0
  48. package/dist/bug-report-cli.js.map +1 -0
  49. package/dist/bug-report.d.ts +47 -0
  50. package/dist/bug-report.js +136 -0
  51. package/dist/bug-report.js.map +1 -0
  52. package/dist/bundled-skills.d.ts +14 -0
  53. package/dist/bundled-skills.js +29 -0
  54. package/dist/bundled-skills.js.map +1 -0
  55. package/dist/cache-ledger.d.ts +16 -3
  56. package/dist/cache-ledger.js +33 -13
  57. package/dist/cache-ledger.js.map +1 -1
  58. package/dist/cache-service.d.ts +55 -3
  59. package/dist/cache-service.js +152 -16
  60. package/dist/cache-service.js.map +1 -1
  61. package/dist/channel/adapters/discord-client-retirement.d.ts +5 -0
  62. package/dist/channel/adapters/discord-client-retirement.js +57 -0
  63. package/dist/channel/adapters/discord-client-retirement.js.map +1 -0
  64. package/dist/channel/adapters/discord.d.ts +9 -0
  65. package/dist/channel/adapters/discord.js +49 -4
  66. package/dist/channel/adapters/discord.js.map +1 -1
  67. package/dist/channel/mcp-tools.js +22 -11
  68. package/dist/channel/mcp-tools.js.map +1 -1
  69. package/dist/channel/types.d.ts +6 -0
  70. package/dist/classic-channel-manager.d.ts +10 -5
  71. package/dist/classic-channel-manager.js +12 -9
  72. package/dist/classic-channel-manager.js.map +1 -1
  73. package/dist/cli.js +224 -111
  74. package/dist/cli.js.map +1 -1
  75. package/dist/config-validator.d.ts +6 -0
  76. package/dist/config-validator.js +129 -3
  77. package/dist/config-validator.js.map +1 -1
  78. package/dist/config.d.ts +13 -1
  79. package/dist/config.js +14 -5
  80. package/dist/config.js.map +1 -1
  81. package/dist/cross-instance-notice.d.ts +7 -0
  82. package/dist/cross-instance-notice.js +10 -0
  83. package/dist/cross-instance-notice.js.map +1 -1
  84. package/dist/daemon.d.ts +24 -1
  85. package/dist/daemon.js +151 -19
  86. package/dist/daemon.js.map +1 -1
  87. package/dist/decision-list.d.ts +25 -0
  88. package/dist/decision-list.js +30 -0
  89. package/dist/decision-list.js.map +1 -0
  90. package/dist/delivery-outbox.d.ts +10 -0
  91. package/dist/delivery-outbox.js +17 -1
  92. package/dist/delivery-outbox.js.map +1 -1
  93. package/dist/diagnostic-redaction.d.ts +10 -0
  94. package/dist/diagnostic-redaction.js +167 -0
  95. package/dist/diagnostic-redaction.js.map +1 -0
  96. package/dist/fleet-context.d.ts +10 -0
  97. package/dist/fleet-manager.d.ts +505 -15
  98. package/dist/fleet-manager.js +1870 -204
  99. package/dist/fleet-manager.js.map +1 -1
  100. package/dist/fleet-process-handlers.d.ts +18 -0
  101. package/dist/fleet-process-handlers.js +69 -0
  102. package/dist/fleet-process-handlers.js.map +1 -0
  103. package/dist/fleet-traffic.d.ts +74 -0
  104. package/dist/fleet-traffic.js +196 -0
  105. package/dist/fleet-traffic.js.map +1 -0
  106. package/dist/general-knowledge/skills/bug-report/SKILL.md +31 -0
  107. package/dist/general-knowledge/skills/credential-profiles/SKILL.md +17 -6
  108. package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +6 -1
  109. package/dist/general-knowledge/skills/fleet-config/SKILL.md +2 -0
  110. package/dist/general-knowledge/skills/multi-channel/SKILL.md +3 -2
  111. package/dist/general-knowledge/skills/persona-emoji/SKILL.md +1 -1
  112. package/dist/general-knowledge/skills/scheduling/SKILL.md +4 -0
  113. package/dist/general-knowledge/skills/web-appearance/SKILL.md +190 -47
  114. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +545 -0
  115. package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +1 -1
  116. package/dist/human-delivery-receipts.d.ts +24 -1
  117. package/dist/human-delivery-receipts.js +64 -11
  118. package/dist/human-delivery-receipts.js.map +1 -1
  119. package/dist/instance-config-impact.js +7 -1
  120. package/dist/instance-config-impact.js.map +1 -1
  121. package/dist/instance-lifecycle.d.ts +10 -1
  122. package/dist/instance-lifecycle.js +79 -18
  123. package/dist/instance-lifecycle.js.map +1 -1
  124. package/dist/instance-names.d.ts +4 -0
  125. package/dist/instance-names.js +12 -0
  126. package/dist/instance-names.js.map +1 -0
  127. package/dist/instructions.d.ts +12 -1
  128. package/dist/instructions.js +13 -2
  129. package/dist/instructions.js.map +1 -1
  130. package/dist/locale.js +173 -12
  131. package/dist/locale.js.map +1 -1
  132. package/dist/log-steps.d.ts +1 -0
  133. package/dist/log-steps.js +15 -0
  134. package/dist/log-steps.js.map +1 -1
  135. package/dist/login-controller.d.ts +45 -0
  136. package/dist/login-controller.js +101 -5
  137. package/dist/login-controller.js.map +1 -1
  138. package/dist/ls-attach-resolve.d.ts +96 -0
  139. package/dist/ls-attach-resolve.js +180 -0
  140. package/dist/ls-attach-resolve.js.map +1 -0
  141. package/dist/ls-rows.d.ts +12 -0
  142. package/dist/ls-rows.js +82 -3
  143. package/dist/ls-rows.js.map +1 -1
  144. package/dist/memory-hogs.d.ts +27 -0
  145. package/dist/memory-hogs.js +124 -0
  146. package/dist/memory-hogs.js.map +1 -0
  147. package/dist/needs-you.d.ts +10 -0
  148. package/dist/needs-you.js +2 -1
  149. package/dist/needs-you.js.map +1 -1
  150. package/dist/outbound-handlers.d.ts +11 -0
  151. package/dist/outbound-handlers.js +139 -11
  152. package/dist/outbound-handlers.js.map +1 -1
  153. package/dist/outbound-schemas.d.ts +200 -4
  154. package/dist/outbound-schemas.js +124 -11
  155. package/dist/outbound-schemas.js.map +1 -1
  156. package/dist/public-web-gateway.js +17 -4
  157. package/dist/public-web-gateway.js.map +1 -1
  158. package/dist/public-web-link.d.ts +9 -0
  159. package/dist/public-web-link.js +8 -0
  160. package/dist/public-web-link.js.map +1 -1
  161. package/dist/quickstart-api.d.ts +19 -1
  162. package/dist/quickstart-api.js +94 -4
  163. package/dist/quickstart-api.js.map +1 -1
  164. package/dist/reply-buttons-controller.d.ts +4 -0
  165. package/dist/reply-buttons-controller.js +2 -2
  166. package/dist/reply-buttons-controller.js.map +1 -1
  167. package/dist/restart-progress.d.ts +80 -1
  168. package/dist/restart-progress.js +306 -21
  169. package/dist/restart-progress.js.map +1 -1
  170. package/dist/rollout-index.d.ts +5 -0
  171. package/dist/rollout-index.js +26 -5
  172. package/dist/rollout-index.js.map +1 -1
  173. package/dist/scheduler/db.d.ts +40 -13
  174. package/dist/scheduler/db.js +123 -89
  175. package/dist/scheduler/db.js.map +1 -1
  176. package/dist/scheduler/task-board-text.d.ts +26 -0
  177. package/dist/scheduler/task-board-text.js +135 -0
  178. package/dist/scheduler/task-board-text.js.map +1 -0
  179. package/dist/scheduler/task-board.d.ts +650 -0
  180. package/dist/scheduler/task-board.js +1938 -0
  181. package/dist/scheduler/task-board.js.map +1 -0
  182. package/dist/scheduler/types.d.ts +6 -1
  183. package/dist/search/search-docs.d.ts +58 -0
  184. package/dist/search/search-docs.js +63 -0
  185. package/dist/search/search-docs.js.map +1 -0
  186. package/dist/search/search-index.d.ts +141 -0
  187. package/dist/search/search-index.js +440 -0
  188. package/dist/search/search-index.js.map +1 -0
  189. package/dist/search/search-store.d.ts +122 -0
  190. package/dist/search/search-store.js +236 -0
  191. package/dist/search/search-store.js.map +1 -0
  192. package/dist/search/search-worker.d.ts +1 -0
  193. package/dist/search/search-worker.js +163 -0
  194. package/dist/search/search-worker.js.map +1 -0
  195. package/dist/service-installer.js +12 -0
  196. package/dist/service-installer.js.map +1 -1
  197. package/dist/settings-api.d.ts +23 -0
  198. package/dist/settings-api.js +164 -3
  199. package/dist/settings-api.js.map +1 -1
  200. package/dist/settings-change.js +104 -0
  201. package/dist/settings-change.js.map +1 -1
  202. package/dist/settings-confirmation.d.ts +3 -1
  203. package/dist/settings-confirmation.js +2 -0
  204. package/dist/settings-confirmation.js.map +1 -1
  205. package/dist/settings-effect.js +34 -3
  206. package/dist/settings-effect.js.map +1 -1
  207. package/dist/settings-http-confirmation.d.ts +11 -0
  208. package/dist/settings-http-confirmation.js +10 -0
  209. package/dist/settings-http-confirmation.js.map +1 -1
  210. package/dist/setup-form.d.ts +1 -1
  211. package/dist/setup-form.js +23 -7
  212. package/dist/setup-form.js.map +1 -1
  213. package/dist/spawn-gate.d.ts +1 -0
  214. package/dist/spawn-gate.js +13 -3
  215. package/dist/spawn-gate.js.map +1 -1
  216. package/dist/task-board-tool.d.ts +25 -0
  217. package/dist/task-board-tool.js +154 -0
  218. package/dist/task-board-tool.js.map +1 -0
  219. package/dist/task-board-waker.d.ts +44 -0
  220. package/dist/task-board-waker.js +128 -0
  221. package/dist/task-board-waker.js.map +1 -0
  222. package/dist/tmux-exec-helper.d.ts +94 -0
  223. package/dist/tmux-exec-helper.js +407 -0
  224. package/dist/tmux-exec-helper.js.map +1 -0
  225. package/dist/tmux-manager.d.ts +5 -0
  226. package/dist/tmux-manager.js +42 -11
  227. package/dist/tmux-manager.js.map +1 -1
  228. package/dist/tool-permissions.d.ts +0 -7
  229. package/dist/tool-permissions.js +44 -5
  230. package/dist/tool-permissions.js.map +1 -1
  231. package/dist/topic-commands.d.ts +17 -1
  232. package/dist/topic-commands.js +45 -19
  233. package/dist/topic-commands.js.map +1 -1
  234. package/dist/tunnel/cloudflared.js +37 -2
  235. package/dist/tunnel/cloudflared.js.map +1 -1
  236. package/dist/tunnel/types.d.ts +3 -1
  237. package/dist/tunnel/types.js.map +1 -1
  238. package/dist/types.d.ts +63 -1
  239. package/dist/ui/app-effects.js +90 -32
  240. package/dist/ui/backend-signin.js +297 -0
  241. package/dist/ui/chat-ambient.js +307 -0
  242. package/dist/ui/chat-effect-posters.js +8 -8
  243. package/dist/ui/chat-effects.js +110 -15
  244. package/dist/ui/chat-msg-actions.js +176 -0
  245. package/dist/ui/chat-render.js +21 -3
  246. package/dist/ui/chat-store.js +64 -12
  247. package/dist/ui/chat-strings.js +85 -8
  248. package/dist/ui/chat-thread.js +204 -21
  249. package/dist/ui/effect-trial-controller.js +13 -8
  250. package/dist/ui/effect-trial-panel.js +19 -16
  251. package/dist/ui/effects-waker.js +70 -0
  252. package/dist/ui/fleet-canvas-layout.js +117 -0
  253. package/dist/ui/fleet-canvas.js +555 -0
  254. package/dist/ui/fleet-org.js +27 -12
  255. package/dist/ui/fleet-route-scene.js +207 -0
  256. package/dist/ui/fleet-route-worker.js +21 -0
  257. package/dist/ui/fleet-route.js +88 -0
  258. package/dist/ui/fleet-task-card.js +436 -0
  259. package/dist/ui/fleet-task-common.js +448 -0
  260. package/dist/ui/fleet-tasks.js +535 -0
  261. package/dist/ui/fleet-traffic.js +359 -0
  262. package/dist/ui/fleet-usage.js +435 -0
  263. package/dist/ui/panel-chat.js +264 -66
  264. package/dist/ui/panel-details.js +2 -1
  265. package/dist/ui/panel-fleet.js +65 -83
  266. package/dist/ui/panel-needs.js +4 -2
  267. package/dist/ui/panel-settings.js +56 -11
  268. package/dist/ui/preview.js +3 -2
  269. package/dist/ui/search-dialog.js +349 -0
  270. package/dist/ui/settings-appearance.js +44 -19
  271. package/dist/ui/settings-apply.js +3 -3
  272. package/dist/ui/settings-catalog.js +92 -0
  273. package/dist/ui/settings-confirm.js +7 -2
  274. package/dist/ui/settings-dialogs.js +40 -12
  275. package/dist/ui/settings-effects.js +4 -3
  276. package/dist/ui/settings-guests.js +117 -0
  277. package/dist/ui/settings-learn.js +25 -0
  278. package/dist/ui/settings-model.js +15 -6
  279. package/dist/ui/settings-mods.js +23 -9
  280. package/dist/ui/settings-owner-effects.js +15 -13
  281. package/dist/ui/settings-strings.js +122 -42
  282. package/dist/ui/settings-web-authority.js +91 -0
  283. package/dist/ui/shared/agend-auth.js +1 -1
  284. package/dist/ui/shared/app-i18n.js +11 -2
  285. package/dist/ui/shared/app-prefs.js +17 -4
  286. package/dist/ui/shared/app-route.js +19 -1
  287. package/dist/ui/shared/app-session.js +2 -0
  288. package/dist/ui/shared/app-shell.js +174 -30
  289. package/dist/ui/shared/app-store.js +26 -1
  290. package/dist/ui/shared/app-stream.js +5 -1
  291. package/dist/ui/shared/app-themes.js +140 -7
  292. package/dist/ui/shared/app.css +704 -20
  293. package/dist/ui/shared/app.js +26 -15
  294. package/dist/ui/shared/card-strings.js +8 -0
  295. package/dist/ui/shared/effect-app-runtime.js +68 -19
  296. package/dist/ui/shared/effect-chat-runtime.js +16 -3
  297. package/dist/ui/shared/effect-frame.js +26 -7
  298. package/dist/ui/shared/effect-geometry.js +24 -9
  299. package/dist/ui/shared/effect-owner-source.js +8 -1
  300. package/dist/ui/shared/effect-policy.js +21 -7
  301. package/dist/ui/shared/effect-profiler.js +31 -9
  302. package/dist/ui/shared/effect-registry.js +97 -50
  303. package/dist/ui/shared/effect-stock-snapshot.js +9 -0
  304. package/dist/ui/shared/effect-stock-trust.js +62 -10
  305. package/dist/ui/shared/effect-trial-shadow.js +62 -0
  306. package/dist/ui/shared/effect-trial-source.js +1 -1
  307. package/dist/ui/shared/effect-trial-stage.js +54 -31
  308. package/dist/ui/shared/effect-trial.css +40 -0
  309. package/dist/ui/shared/fx-motion.js +67 -0
  310. package/dist/ui/shared/identity-card.js +100 -11
  311. package/dist/ui/shared/identity-color.js +14 -10
  312. package/dist/ui/shared/instance-nav.js +179 -23
  313. package/dist/ui/shared/owner-css.js +30 -1
  314. package/dist/ui/shared/panel-view.js +24 -4
  315. package/dist/ui/shared/tokens.css +9 -3
  316. package/dist/ui/shared/ui-icons.js +2 -0
  317. package/dist/ui/shared/ui-menu.js +20 -7
  318. package/dist/ui/shared/ui-toast.js +16 -3
  319. package/dist/ui/shared/usage-dialog.js +34 -3
  320. package/dist/ui/shared/view-strings.js +72 -0
  321. package/dist/ui/shared/web-authorize.js +198 -0
  322. package/dist/ui/web-terminal/terminal.css +4 -4
  323. package/dist/ui/web-terminal/terminal.html +1 -1
  324. package/dist/update-launch.d.ts +9 -0
  325. package/dist/update-launch.js +38 -12
  326. package/dist/update-launch.js.map +1 -1
  327. package/dist/usage/format-rich.js +6 -3
  328. package/dist/usage/format-rich.js.map +1 -1
  329. package/dist/usage/i18n-keys.d.ts +1 -1
  330. package/dist/usage/i18n-keys.js +2 -1
  331. package/dist/usage/i18n-keys.js.map +1 -1
  332. package/dist/usage/i18n.d.ts +13 -1
  333. package/dist/usage/i18n.js +29 -0
  334. package/dist/usage/i18n.js.map +1 -1
  335. package/dist/usage/providers.d.ts +81 -2
  336. package/dist/usage/providers.js +240 -38
  337. package/dist/usage/providers.js.map +1 -1
  338. package/dist/usage/usage-api.d.ts +5 -1
  339. package/dist/usage/usage-api.js +184 -58
  340. package/dist/usage/usage-api.js.map +1 -1
  341. package/dist/usage-report.d.ts +158 -0
  342. package/dist/usage-report.js +226 -0
  343. package/dist/usage-report.js.map +1 -0
  344. package/dist/usage-rollup.d.ts +51 -0
  345. package/dist/usage-rollup.js +168 -0
  346. package/dist/usage-rollup.js.map +1 -0
  347. package/dist/user-systemd.d.ts +24 -0
  348. package/dist/user-systemd.js +75 -0
  349. package/dist/user-systemd.js.map +1 -0
  350. package/dist/view-api.d.ts +39 -0
  351. package/dist/view-api.js +69 -6
  352. package/dist/view-api.js.map +1 -1
  353. package/dist/web-ambient.d.ts +72 -0
  354. package/dist/web-ambient.js +161 -0
  355. package/dist/web-ambient.js.map +1 -0
  356. package/dist/web-api.d.ts +98 -14
  357. package/dist/web-api.js +1027 -83
  358. package/dist/web-api.js.map +1 -1
  359. package/dist/web-appearance-policy.d.ts +4 -6
  360. package/dist/web-appearance-policy.js +5 -12
  361. package/dist/web-appearance-policy.js.map +1 -1
  362. package/dist/web-appearance-tools.d.ts +3 -0
  363. package/dist/web-appearance-tools.js +22 -3
  364. package/dist/web-appearance-tools.js.map +1 -1
  365. package/dist/web-appearance-view.d.ts +27 -2
  366. package/dist/web-appearance-view.js +129 -14
  367. package/dist/web-appearance-view.js.map +1 -1
  368. package/dist/web-appearance.d.ts +17 -3
  369. package/dist/web-appearance.js +85 -8
  370. package/dist/web-appearance.js.map +1 -1
  371. package/dist/web-asset-parts.d.ts +2 -2
  372. package/dist/web-asset-parts.js +3 -3
  373. package/dist/web-asset-parts.js.map +1 -1
  374. package/dist/web-auth.js +3 -1
  375. package/dist/web-auth.js.map +1 -1
  376. package/dist/web-backend-login.d.ts +130 -0
  377. package/dist/web-backend-login.js +225 -0
  378. package/dist/web-backend-login.js.map +1 -0
  379. package/dist/web-card.d.ts +14 -0
  380. package/dist/web-card.js +40 -6
  381. package/dist/web-card.js.map +1 -1
  382. package/dist/web-channel-echo.d.ts +7 -1
  383. package/dist/web-channel-echo.js +6 -2
  384. package/dist/web-channel-echo.js.map +1 -1
  385. package/dist/web-chat-history.d.ts +83 -0
  386. package/dist/web-chat-history.js +137 -5
  387. package/dist/web-chat-history.js.map +1 -1
  388. package/dist/web-commands.d.ts +6 -1
  389. package/dist/web-commands.js +1 -1
  390. package/dist/web-commands.js.map +1 -1
  391. package/dist/web-effect-bootstrap.d.ts +12 -0
  392. package/dist/web-effect-bootstrap.js +49 -0
  393. package/dist/web-effect-bootstrap.js.map +1 -0
  394. package/dist/web-effect-bundle.js +46 -1
  395. package/dist/web-effect-bundle.js.map +1 -1
  396. package/dist/web-effect-playback.d.ts +30 -0
  397. package/dist/web-effect-playback.js +71 -0
  398. package/dist/web-effect-playback.js.map +1 -0
  399. package/dist/web-effect-profiles.d.ts +4 -4
  400. package/dist/web-effect-service.d.ts +11 -4
  401. package/dist/web-effect-service.js +25 -4
  402. package/dist/web-effect-service.js.map +1 -1
  403. package/dist/web-effect-tools.d.ts +1 -0
  404. package/dist/web-effect-tools.js +4 -4
  405. package/dist/web-effect-tools.js.map +1 -1
  406. package/dist/web-effect-trials.d.ts +2 -2
  407. package/dist/web-effect-trials.js +3 -3
  408. package/dist/web-effect-trials.js.map +1 -1
  409. package/dist/web-effect-upload.js +1 -1
  410. package/dist/web-effect-upload.js.map +1 -1
  411. package/dist/web-effect-worker.d.ts +1 -1
  412. package/dist/web-effect-worker.js +2 -2
  413. package/dist/web-effect-worker.js.map +1 -1
  414. package/dist/web-effect.d.ts +1 -1
  415. package/dist/web-guest-access.d.ts +126 -0
  416. package/dist/web-guest-access.js +238 -0
  417. package/dist/web-guest-access.js.map +1 -0
  418. package/dist/web-guest-rooms.d.ts +82 -0
  419. package/dist/web-guest-rooms.js +135 -0
  420. package/dist/web-guest-rooms.js.map +1 -0
  421. package/dist/web-host-guard.d.ts +11 -0
  422. package/dist/web-host-guard.js +11 -0
  423. package/dist/web-host-guard.js.map +1 -1
  424. package/dist/web-identity-color.d.ts +1 -1
  425. package/dist/web-identity-color.js +2 -2
  426. package/dist/web-identity-color.js.map +1 -1
  427. package/dist/web-identity-palette.d.ts +1 -1
  428. package/dist/web-identity-palette.js +2 -2
  429. package/dist/web-identity-palette.js.map +1 -1
  430. package/dist/web-image-meta.d.ts +13 -0
  431. package/dist/web-image-meta.js +62 -0
  432. package/dist/web-image-meta.js.map +1 -1
  433. package/dist/web-libavoid.d.ts +14 -0
  434. package/dist/web-libavoid.js +46 -0
  435. package/dist/web-libavoid.js.map +1 -0
  436. package/dist/web-login.d.ts +47 -0
  437. package/dist/web-login.js +115 -0
  438. package/dist/web-login.js.map +1 -1
  439. package/dist/web-mod-agent.d.ts +1 -1
  440. package/dist/web-mod-agent.js +7 -5
  441. package/dist/web-mod-agent.js.map +1 -1
  442. package/dist/web-mod-bundle.d.ts +9 -1
  443. package/dist/web-mod-bundle.js +62 -3
  444. package/dist/web-mod-bundle.js.map +1 -1
  445. package/dist/web-mod-export.d.ts +9 -0
  446. package/dist/web-mod-export.js +21 -0
  447. package/dist/web-mod-export.js.map +1 -1
  448. package/dist/web-mod-store.d.ts +7 -1
  449. package/dist/web-mod-store.js +19 -6
  450. package/dist/web-mod-store.js.map +1 -1
  451. package/dist/web-owner-css-store.d.ts +44 -1
  452. package/dist/web-owner-css-store.js +70 -9
  453. package/dist/web-owner-css-store.js.map +1 -1
  454. package/dist/web-prefs.d.ts +14 -0
  455. package/dist/web-prefs.js +37 -4
  456. package/dist/web-prefs.js.map +1 -1
  457. package/dist/web-preview.d.ts +6 -2
  458. package/dist/web-preview.js +4 -2
  459. package/dist/web-preview.js.map +1 -1
  460. package/dist/web-returning-device.d.ts +41 -0
  461. package/dist/web-returning-device.js +57 -5
  462. package/dist/web-returning-device.js.map +1 -1
  463. package/dist/web-session-authorization.d.ts +132 -0
  464. package/dist/web-session-authorization.js +193 -0
  465. package/dist/web-session-authorization.js.map +1 -0
  466. package/dist/web-session.d.ts +70 -10
  467. package/dist/web-session.js +137 -22
  468. package/dist/web-session.js.map +1 -1
  469. package/dist/web-settings-patch.d.ts +49 -2
  470. package/dist/web-settings-patch.js +145 -1
  471. package/dist/web-settings-patch.js.map +1 -1
  472. package/dist/web-settings-tools.d.ts +94 -0
  473. package/dist/web-settings-tools.js +301 -0
  474. package/dist/web-settings-tools.js.map +1 -0
  475. package/dist/web-shell-routes.d.ts +1 -1
  476. package/dist/web-shell-routes.js +1 -1
  477. package/dist/web-shell-routes.js.map +1 -1
  478. package/dist/web-terminal-http.js +15 -2
  479. package/dist/web-terminal-http.js.map +1 -1
  480. package/dist/web-theme-store.d.ts +39 -0
  481. package/dist/web-theme-store.js +122 -11
  482. package/dist/web-theme-store.js.map +1 -1
  483. package/dist/web-theme.d.ts +31 -0
  484. package/dist/web-theme.js +99 -2
  485. package/dist/web-theme.js.map +1 -1
  486. package/dist/web-upload.d.ts +13 -0
  487. package/dist/web-upload.js +39 -3
  488. package/dist/web-upload.js.map +1 -1
  489. package/dist/windows-shortcuts.d.ts +78 -0
  490. package/dist/windows-shortcuts.js +203 -0
  491. package/dist/windows-shortcuts.js.map +1 -0
  492. package/dist/workflow-templates/default.md +4 -1
  493. package/dist/yaml-shared-nodes.d.ts +25 -0
  494. package/dist/yaml-shared-nodes.js +185 -0
  495. package/dist/yaml-shared-nodes.js.map +1 -0
  496. package/launcher/agend-install.cjs +553 -0
  497. package/launcher/install-admission.cjs +4 -39
  498. package/launcher/postinstall.cjs +14 -12
  499. package/package.json +4 -2
  500. package/scripts/preinstall-guard.cjs +2 -5
  501. package/templates/windows-shortcuts.ps1 +181 -0
  502. package/launcher/old-updater-note.cjs +0 -48
@@ -0,0 +1,535 @@
1
+ // The task board (Fleet › Tasks): projects → main tasks (M-) → sub tasks (S-), ported from taskhall (任務會所).
2
+ // Home lists the projects and what waits for the person; a project is a board of its main tasks by status, each able to
3
+ // open up its sub tasks; a card opens in a dialog (fleet-task-card.js). Every write goes through /ui/tasks/* as the
4
+ // signed-in person, under the same rules an instance's task tool meets (scheduler/task-board.ts). A change from anywhere
5
+ // (an agent's tool, another tab) arrives as a "tasks" stream event with the board's revision; the board reads again
6
+ // when its revision moved. Over the temporary public link the board is read-only (the server refuses its writes there).
7
+ // A card moves by drag, by Alt+arrow keys on its title, or by its dialog ("Move to…", up and down) — touch and keyboard
8
+ // need no drag.
9
+ import { html, useEffect, useMemo, useRef, useState } from "/assets/app-html.js";
10
+ import { t } from "/assets/app-i18n.js";
11
+ import { appStore, useStore } from "/assets/app-store.js";
12
+ import { Dialog } from "/assets/ui-dialog.js";
13
+ import { Empty, ErrorState, Skeleton } from "/assets/ui-states.js";
14
+ import { Icon } from "/assets/ui-icons.js";
15
+ import { Menu } from "/assets/ui-menu.js";
16
+ import { toast } from "/assets/ui-toast.js";
17
+ import { AskHost, COLUMNS, NO_PROJECT, ProgressBar, StatusPill, UNASSIGNED, answerBlocked, boardApi, currentWork, dropNeighbours, errorText, followsSubs, moveCard, onPublicLink, projectKey, projectStats, readStored, recentlyDone, relTime, workersOf, wrapUpText, writeStored } from "./fleet-task-common.js";
18
+ import { CardDialog } from "./fleet-task-card.js";
19
+
20
+ const PROJECT_KEY = "agend_tasks_project";
21
+ const FILTER_KEY = "agend_tasks_assignee";
22
+
23
+ // ── The tab ───────────────────────────────────────────────
24
+
25
+ /**
26
+ * Fleet › Tasks. `?card=M-3` in the address opens that card (a link from a notice). The project last looked at is
27
+ * remembered on this device.
28
+ */
29
+ export function TaskBoardTab({ lease }) {
30
+ const [state, setState] = useState(null);
31
+ // `ifMoved`: a refresh that only wants the board if its revision moved (a stream event, a reconnect).
32
+ const [version, setVersion] = useState({ n: 0, ifMoved: false });
33
+ const writable = !onPublicLink();
34
+ const [at, setAtRaw] = useState(() => {
35
+ const v = readStored(PROJECT_KEY, "");
36
+ return v === "" ? null : v === NO_PROJECT ? NO_PROJECT : Number(v);
37
+ });
38
+ const [openId, setOpenId] = useState(() => {
39
+ try { return new URLSearchParams(globalThis.location?.search || "").get("card"); } catch { return null; }
40
+ });
41
+ const [tick, setTick] = useState(0);
42
+ const [flashed, setFlashed] = useState(() => new Map());
43
+ const [dialog, setDialog] = useState(null);
44
+ // The Done column's "All": every done main task is read only when asked for (the server keeps recent ones otherwise).
45
+ const [allDone, setAllDone] = useState(false);
46
+ const seq = useRef(0);
47
+ const setAt = (v) => { setAtRaw(v); writeStored(PROJECT_KEY, v === null ? null : String(v)); };
48
+ const reload = (ifMoved = false) => setVersion((v) => ({ n: v.n + 1, ifMoved }));
49
+ const shown = useRef({ rev: null, allDone: false });
50
+
51
+ useEffect(() => {
52
+ const mine = ++seq.current;
53
+ (async () => {
54
+ try {
55
+ const q = new URLSearchParams();
56
+ if (allDone) q.set("done", "all");
57
+ // The same board at the same revision is not sent again.
58
+ if (version.ifMoved && shown.current.rev !== null && shown.current.allDone === allDone) q.set("rev", String(shown.current.rev));
59
+ const qs = q.toString();
60
+ const r = await lease.fetch(`/ui/tasks/board${qs ? `?${qs}` : ""}`, { method: "GET", headers: { "Content-Type": "application/json" } });
61
+ if (!r.ok) throw new Error(`HTTP ${r.status}`);
62
+ const d = await r.json();
63
+ if (!lease.current() || mine !== seq.current || d.unchanged) return;
64
+ shown.current = { rev: typeof d.rev === "number" ? d.rev : null, allDone };
65
+ setState({ data: d });
66
+ } catch { if (lease.current() && mine === seq.current) setState((s) => (s && s.data ? s : { error: true })); }
67
+ })();
68
+ }, [lease, version, allDone]);
69
+
70
+ // Live: a change from anywhere reads the board again (once per burst) and lights up the cards it touched.
71
+ useEffect(() => {
72
+ const sub = appStore.get().streamOn;
73
+ if (!sub) return undefined;
74
+ let timer = null;
75
+ const off = sub("tasks", (change) => {
76
+ // A revision this page has already read (its own write, read again since) brings nothing new.
77
+ if (change && typeof change.rev === "number" && shown.current.rev !== null && change.rev <= shown.current.rev) return;
78
+ const ids = Array.isArray(change && change.ids) ? change.ids : [];
79
+ if (ids.length) {
80
+ const now = Date.now();
81
+ setFlashed((m) => { const next = new Map(m); for (const id of ids) next.set(id, now); return next; });
82
+ }
83
+ if (timer) clearTimeout(timer);
84
+ timer = setTimeout(() => { timer = null; reload(true); setTick((n) => n + 1); }, 250);
85
+ });
86
+ const offConn = sub("connection", (c) => { if (c === "live") reload(true); });
87
+ return () => { off(); offConn(); if (timer) clearTimeout(timer); };
88
+ }, [lease]);
89
+
90
+ const openCard = (id) => {
91
+ setOpenId(id);
92
+ try { const u = new URL(globalThis.location.href); u.searchParams.set("card", id); globalThis.history.replaceState(globalThis.history.state, "", u); } catch { /* no address bar */ }
93
+ };
94
+ const closeCard = () => {
95
+ setOpenId(null);
96
+ try { const u = new URL(globalThis.location.href); u.searchParams.delete("card"); globalThis.history.replaceState(globalThis.history.state, "", u); } catch { /* no address bar */ }
97
+ };
98
+
99
+ if (!state) return html`<${Skeleton} lines=${5} />`;
100
+ if (state.error) return html`<${ErrorState} message=${t("tasks.loadFailed")} onRetry=${reload} />`;
101
+ const cards = Array.isArray(state.data.cards) ? state.data.cards : [];
102
+ const projects = Array.isArray(state.data.projects) ? state.data.projects : [];
103
+ // The server's numbers cover every card (finished ones are not all on the board); computed here only without them.
104
+ const stats = (key) => (state.data.stats && state.data.stats[String(key)]) || projectStats(key, cards);
105
+ const project = at === null ? null : at === NO_PROJECT ? NO_PROJECT : projects.find((p) => p.id === at) || null;
106
+ const body = project === null
107
+ ? html`<${Home} cards=${cards} projects=${projects} stats=${stats} writable=${writable} onOpenProject=${setAt} onOpen=${openCard} onDialog=${setDialog} reload=${reload} />`
108
+ : html`<${ProjectBoard} project=${project} cards=${cards} stats=${stats} flashed=${flashed} allDone=${allDone} writable=${writable} onAllDone=${setAllDone} onBack=${() => setAt(null)}
109
+ onOpen=${openCard} onCreate=${() => setDialog({ kind: "main", project: project === NO_PROJECT ? null : project.id })} reload=${reload} />`;
110
+ return html`<div class="tb">
111
+ ${writable ? null : html`<p class="tb-readonly" role="note"><${Icon} name="eye" size=${16} />${t("tasks.readOnly")}</p>`}
112
+ ${body}
113
+ ${openId ? html`<${CardDialog} key=${openId} id=${openId} tick=${tick} projects=${projects} writable=${writable} place=${writable ? placeOf(cards, openId, reload) : null}
114
+ onClose=${closeCard} onOpen=${openCard} reloadBoard=${reload} />` : null}
115
+ ${dialog && dialog.kind === "main" ? html`<${CreateMainDialog} projects=${projects} project=${dialog.project} onClose=${() => setDialog(null)}
116
+ onCreated=${(id) => { if (!lease.current()) return; setDialog(null); reload(); openCard(id); }} />` : null}
117
+ ${dialog && dialog.kind === "project" ? html`<${ProjectDialog} project=${dialog.project} onClose=${() => setDialog(null)}
118
+ onSaved=${(p) => { if (!lease.current()) return; setDialog(null); reload(); if (!dialog.project) setAt(p.id); }} />` : null}
119
+ <${AskHost} />
120
+ </div>`;
121
+ }
122
+
123
+ // ── Home: the projects, and what waits for the person ─────────
124
+
125
+ function Home({ cards, projects, stats, writable, onOpenProject, onOpen, onDialog, reload }) {
126
+ const [showArchived, setShowArchived] = useState(false);
127
+ const active = projects.filter((p) => !p.archived);
128
+ const archived = projects.filter((p) => p.archived);
129
+ const loose = stats(NO_PROJECT).mains > 0;
130
+ return html`<div class="tb-home">
131
+ <div class="list-head tb-head"><span>${t("tasks.projects")}</span>
132
+ <span class="tb-grow"></span>
133
+ <${SearchBox} onOpen=${onOpen} />
134
+ ${writable ? html`<button type="button" class="btn btn-primary" onClick=${() => onDialog({ kind: "project", project: null })}><${Icon} name="plus" size=${16} />${t("tasks.newProject")}</button>` : null}</div>
135
+ <${Waiting} cards=${cards} writable=${writable} onOpen=${onOpen} reload=${reload} />
136
+ ${active.length || loose ? html`<div class="tb-projects">
137
+ ${active.map((p) => html`<${ProjectTile} key=${p.id} project=${p} stats=${stats(p.id)} onOpen=${() => onOpenProject(p.id)} onDialog=${writable ? onDialog : null} reload=${reload} />`)}
138
+ ${loose ? html`<${ProjectTile} project=${null} stats=${stats(NO_PROJECT)} onOpen=${() => onOpenProject(NO_PROJECT)} />` : null}
139
+ </div>` : html`<${Empty} icon="tasks" title=${t("tasks.noProjects")} hint=${t("tasks.noProjectsHint")} />`}
140
+ ${archived.length ? html`<div class="tb-archived">
141
+ <button type="button" class="btn btn-ghost btn-sm" aria-expanded=${showArchived ? "true" : "false"} onClick=${() => setShowArchived(!showArchived)}>
142
+ <${Icon} name="chevron" size=${14} cls=${showArchived ? "tb-chev open" : "tb-chev"} />${t("tasks.archived")} (${archived.length})</button>
143
+ ${showArchived ? html`<div class="tb-projects">${archived.map((p) => html`<${ProjectTile} key=${p.id} project=${p} stats=${stats(p.id)} onOpen=${() => onOpenProject(p.id)} onDialog=${writable ? onDialog : null} reload=${reload} />`)}</div>` : null}
144
+ </div>` : null}
145
+ </div>`;
146
+ }
147
+
148
+ function ProjectTile({ project, stats, onOpen, onDialog, reload }) {
149
+ async function setArchived(archived) {
150
+ try { await boardApi.updateProject(project.id, { archived }); toast(t("tasks.projectSaved")); reload(); } catch (err) { toast(errorText(err), false); }
151
+ }
152
+ const name = project ? project.name : t("tasks.noProject");
153
+ const desc = project ? project.description : t("tasks.noProjectDesc");
154
+ return html`<div class=${`tb-project${project && project.archived ? " is-archived" : ""}`}>
155
+ <button type="button" class="tb-project-open" onClick=${onOpen}>
156
+ <span class="tb-project-name">${name}</span>
157
+ ${desc ? html`<span class="tb-project-desc">${desc}</span>` : null}
158
+ <span class="tb-project-pct" title=${t("tasks.percentTitle")}><span class="tb-pct">${stats.percent}%</span><${ProgressBar} progress=${stats.units} wide /></span>
159
+ <span class="tb-project-meta">
160
+ <span>${stats.mains === 1 ? t("tasks.mainOne") : t("tasks.mainCount", stats.mains)}</span>
161
+ ${stats.waiting ? html`<span class="tb-flag st-review">${t("tasks.waiting", stats.waiting)}</span>` : null}
162
+ ${stats.blocked ? html`<span class="tb-flag st-blocked">${t("tasks.blockedN", stats.blocked)}</span>` : null}
163
+ ${stats.lastActivity ? html`<span class="muted">${t("tasks.lastActivity", relTime(stats.lastActivity))}</span>` : null}
164
+ </span>
165
+ </button>
166
+ ${project && onDialog ? html`<${Menu} cls="tb-project-menu" items=${[
167
+ { key: "edit", label: t("tasks.rename"), icon: "edit", onSelect: () => onDialog({ kind: "project", project }) },
168
+ project.archived
169
+ ? { key: "restore", label: t("tasks.restore"), icon: "restart", onSelect: () => setArchived(false) }
170
+ : { key: "archive", label: t("tasks.archive"), icon: "download", onSelect: () => setArchived(true) },
171
+ ]} />` : null}
172
+ </div>`;
173
+ }
174
+
175
+ /**
176
+ * Cards waiting for the person: to confirm or send back, and blocked ones to answer. A card's buttons take one answer
177
+ * at a time: claimed before the first await, so a double click sends one move.
178
+ */
179
+ function Waiting({ cards, writable, onOpen, reload }) {
180
+ const { instances } = useStore(appStore);
181
+ const fleetNames = new Set(instances.map((i) => i.name));
182
+ const pending = useRef(new Set());
183
+ const [busy, setBusy] = useState(() => new Set());
184
+ const review = cards.filter((c) => c.status === "review");
185
+ const blocked = cards.filter((c) => c.status === "blocked");
186
+ if (!review.length && !blocked.length) return null;
187
+ const act = async (fn, done, card) => {
188
+ if (pending.current.has(card.id)) return;
189
+ pending.current.add(card.id);
190
+ setBusy(new Set(pending.current));
191
+ try { const r = await fn(); if (r) { toast(t(done, card.id)); reload(); } }
192
+ finally { pending.current.delete(card.id); setBusy(new Set(pending.current)); }
193
+ };
194
+ return html`<section class="tb-waiting" aria-label=${t("tasks.waitingTitle")}>
195
+ <h3 class="tb-section-title"><${Icon} name="inbox" size=${16} />${t("tasks.waitingTitle")}</h3>
196
+ <ul class="rows">${[...review, ...blocked].map((c) => html`<li key=${c.id} class="row-item tb-wait-row">
197
+ <div class="tb-wait-main">
198
+ <${StatusPill} status=${c.status} />
199
+ <button type="button" class="tb-wait-title" onClick=${() => onOpen(c.id)}><span class="tb-code">${c.id}</span> ${c.title}
200
+ ${c.status === "blocked" && c.blockedReason ? html`<span class="tb-wait-reason">${c.blockedReason}</span>` : null}
201
+ ${c.status === "blocked" && c.dispatcher && c.dispatcher.kind === "instance" && c.dispatcher.name !== c.assignee && fleetNames.has(c.dispatcher.name)
202
+ ? html`<span class="tb-wait-asked muted">${t("tasks.askedFirst", c.dispatcher.name)}</span>` : null}
203
+ ${c.status === "review" && wrapUpText(c) ? html`<span class="tb-wait-reason wrapup">${t("tasks.wrapLine", wrapUpText(c))}</span>` : null}</button>
204
+ </div>
205
+ <div class="tb-wait-actions">
206
+ ${c.assignee ? html`<span class="muted">${c.assignee}</span>` : null}
207
+ ${!writable ? null : c.status === "review" ? html`
208
+ <button type="button" class="btn btn-primary btn-sm" disabled=${busy.has(c.id)} onClick=${() => act(() => moveCard(c, "done"), "tasks.confirmed", c)}><${Icon} name="check" size=${14} />${t("tasks.confirm")}</button>
209
+ <button type="button" class="btn btn-sm" disabled=${busy.has(c.id)} onClick=${() => act(() => moveCard(c, "claimed"), "tasks.sentBack", c)}>${t("tasks.sendBack")}</button>`
210
+ : html`<button type="button" class="btn btn-primary btn-sm" disabled=${busy.has(c.id)} onClick=${() => act(() => answerBlocked(c), "tasks.answered", c)}>${t("tasks.answer")}</button>`}
211
+ </div>
212
+ </li>`)}</ul>
213
+ </section>`;
214
+ }
215
+
216
+ function SearchBox({ onOpen }) {
217
+ const [q, setQ] = useState("");
218
+ const [results, setResults] = useState(null);
219
+ const [active, setActive] = useState(0);
220
+ const seq = useRef(0);
221
+ useEffect(() => {
222
+ const text = q.trim();
223
+ if (!text) { setResults(null); return undefined; }
224
+ const mine = ++seq.current;
225
+ const timer = setTimeout(async () => {
226
+ try { const r = await boardApi.search(text); if (mine === seq.current) { setResults(r.results || []); setActive(0); } } catch { if (mine === seq.current) setResults([]); }
227
+ }, 200);
228
+ return () => clearTimeout(timer);
229
+ }, [q]);
230
+ const pick = (id) => { setQ(""); setResults(null); onOpen(id); };
231
+ function onKey(e) {
232
+ if (!results || !results.length) return;
233
+ if (e.key === "ArrowDown") { e.preventDefault(); setActive((i) => (i + 1) % results.length); }
234
+ else if (e.key === "ArrowUp") { e.preventDefault(); setActive((i) => (i - 1 + results.length) % results.length); }
235
+ else if (e.key === "Enter") { e.preventDefault(); pick(results[active].card.id); }
236
+ else if (e.key === "Escape") { e.stopPropagation(); setQ(""); }
237
+ }
238
+ return html`<div class="tb-search">
239
+ <${Icon} name="search" size=${16} cls="tb-search-icon" />
240
+ <input type="search" value=${q} placeholder=${t("tasks.search")} aria-label=${t("tasks.searchHint")} title=${t("tasks.searchHint")}
241
+ onInput=${(e) => setQ(e.target.value)} onKeyDown=${onKey} />
242
+ ${results ? html`<ul class="tb-search-results" role="listbox">
243
+ ${results.length ? results.map((r, i) => html`<li key=${r.card.id} role="option" aria-selected=${i === active ? "true" : "false"} class=${i === active ? "active" : ""}>
244
+ <button type="button" onClick=${() => pick(r.card.id)}>
245
+ <span class="tb-code">${r.card.id}</span><span class="tb-search-title">${r.card.title}</span><${StatusPill} status=${r.card.status} />
246
+ <span class="tb-search-where muted">${[r.project, r.parentTitle].filter(Boolean).join(" › ")}${r.match ? ` · ${t("tasks.inField", t(`tasks.field${r.match.field}`))}: ${r.match.text}` : ""}</span>
247
+ </button></li>`) : html`<li class="muted tb-search-none">${t("tasks.searchEmpty")}</li>`}
248
+ </ul>` : null}
249
+ </div>`;
250
+ }
251
+
252
+ // ── A project's board ─────────────────────────────────────
253
+
254
+ function ProjectBoard({ project, cards, stats: statsOf, flashed, allDone, writable, onAllDone, onBack, onOpen, onCreate, reload }) {
255
+ const { instances } = useStore(appStore);
256
+ const [assignee, setAssigneeRaw] = useState(() => readStored(FILTER_KEY, ""));
257
+ const [expanded, setExpanded] = useState({});
258
+ const [drag, setDrag] = useState(null);
259
+ const [spot, setSpot] = useState(null);
260
+ const setAssignee = (v) => { setAssigneeRaw(v); writeStored(FILTER_KEY, v || null); };
261
+ const key = project === NO_PROJECT ? NO_PROJECT : project.id;
262
+ const archived = project !== NO_PROJECT && project.archived;
263
+ const stats = statsOf(key);
264
+ const now = Date.now();
265
+
266
+ const subsOf = useMemo(() => {
267
+ const m = new Map();
268
+ for (const c of cards) if (c.kind === "sub" && c.parentId && c.status !== "cancelled") m.set(c.parentId, [...(m.get(c.parentId) || []), c]);
269
+ return m;
270
+ }, [cards]);
271
+ const matches = (c) => !assignee || (assignee === UNASSIGNED ? c.assignee === null : c.assignee === assignee);
272
+ const mains = cards.filter((c) => c.kind === "main" && projectKey(c) === key && c.status !== "cancelled"
273
+ && (c.status !== "done" || allDone || recentlyDone(c, now))
274
+ && (matches(c) || (subsOf.get(c.id) || []).some(matches)));
275
+ const names = [...new Set([...instances.map((i) => i.name), ...cards.map((c) => c.assignee).filter(Boolean)])].sort();
276
+
277
+ const dropping = useRef(false);
278
+ // A card moved from the keyboard is drawn again in its new place, and the focus it had goes with the old tile: give it
279
+ // back to the card's title once it is there — only while nothing else took the focus, and for a few seconds at most.
280
+ const refocus = useRef(null);
281
+ useEffect(() => {
282
+ const r = refocus.current;
283
+ if (!r) return;
284
+ if (Date.now() > r.until) { refocus.current = null; return; }
285
+ const el = document.querySelector(`[data-tile="${r.id}"] .tb-tile-title`);
286
+ const lost = !document.activeElement || document.activeElement === document.body;
287
+ if (el && lost) el.focus();
288
+ });
289
+ async function drop(card, to, after, before) {
290
+ if (dropping.current) return;
291
+ dropping.current = true;
292
+ try { await place(card, to, after, before); } finally { dropping.current = false; }
293
+ }
294
+ async function place(card, to, after, before) {
295
+ if (to !== card.status) {
296
+ // By hand, a main task that follows its sub tasks is confirmed or sent back, or blocked and unblocked.
297
+ const allowed = !followsSubs(card) || (card.status === "review" && (to === "done" || to === "claimed"))
298
+ || (to === "blocked" && card.status !== "done") || (card.status === "blocked" && to === "claimed");
299
+ if (!allowed) { toast(t("tasks.derivedMove"), false); return; }
300
+ const r = await moveCard(card, to);
301
+ if (!r) return;
302
+ }
303
+ if (after || before) { try { await boardApi.reorder(card.id, after, before); } catch (err) { toast(errorText(err), false); } }
304
+ reload();
305
+ }
306
+ /** Alt+arrows on a card's title: ↑/↓ within its column, ←/→ to the status before or after (as a drop there would). */
307
+ function keyMove(card, e) {
308
+ if (!writable || !e.altKey || !["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(e.key)) return;
309
+ e.preventDefault();
310
+ refocus.current = { id: card.id, until: Date.now() + 5000 };
311
+ const column = mains.filter((c) => c.status === card.status);
312
+ const i = column.findIndex((c) => c.id === card.id);
313
+ if (e.key === "ArrowUp" && i > 0) void drop(card, card.status, column[i - 2]?.id ?? null, column[i - 1].id);
314
+ else if (e.key === "ArrowDown" && i < column.length - 1) void drop(card, card.status, column[i + 1].id, column[i + 2]?.id ?? null);
315
+ else if (e.key === "ArrowLeft" || e.key === "ArrowRight") {
316
+ const to = COLUMNS[COLUMNS.indexOf(card.status) + (e.key === "ArrowLeft" ? -1 : 1)];
317
+ if (to) void drop(card, to, null, null);
318
+ }
319
+ }
320
+ const end = () => { setDrag(null); setSpot(null); };
321
+ function over(e, status) {
322
+ if (!drag) return;
323
+ e.preventDefault();
324
+ e.dataTransfer.dropEffect = "move";
325
+ const tiles = [...e.currentTarget.querySelectorAll("[data-tile]")].filter((el) => el.dataset.tile !== drag.id);
326
+ let index = tiles.findIndex((el) => { const r = el.getBoundingClientRect(); return e.clientY < r.top + r.height / 2; });
327
+ if (index < 0) index = tiles.length;
328
+ if (!spot || spot.status !== status || spot.index !== index) setSpot({ status, index });
329
+ }
330
+ function dropOn(e, status, column) {
331
+ e.preventDefault();
332
+ const d = drag, at = spot;
333
+ end();
334
+ if (!d || !at) return;
335
+ const { after, before } = dropNeighbours(column, d.id, at.index);
336
+ void drop(d, status, after, before);
337
+ }
338
+
339
+ return html`<div class="tb-board-page">
340
+ <div class="tb-bar-head">
341
+ <button type="button" class="btn btn-ghost btn-sm" onClick=${onBack}><${Icon} name="back" size=${16} />${t("tasks.back")}</button>
342
+ <div class="tb-title">
343
+ <h2>${project === NO_PROJECT ? t("tasks.noProject") : project.name}</h2>
344
+ ${archived ? html`<span class="tb-flag">${t("tasks.archived")}</span>` : null}
345
+ ${project !== NO_PROJECT && project.description ? html`<span class="tb-project-desc">${project.description}</span>` : null}
346
+ </div>
347
+ <div class="tb-stats" title=${t("tasks.percentTitle")}>
348
+ <span class="tb-pct">${stats.percent}%</span><${ProgressBar} progress=${stats.units} />
349
+ <span class="muted">${stats.mains === 1 ? t("tasks.mainOne") : t("tasks.mainCount", stats.mains)}</span>
350
+ ${stats.waiting ? html`<span class="tb-flag st-review">${t("tasks.waiting", stats.waiting)}</span>` : null}
351
+ ${stats.blocked ? html`<span class="tb-flag st-blocked">${t("tasks.blockedN", stats.blocked)}</span>` : null}
352
+ </div>
353
+ <span class="tb-grow"></span>
354
+ <select class="tb-select" value=${assignee} aria-label=${t("tasks.filterLabel")} onChange=${(e) => setAssignee(e.target.value)}>
355
+ <option value="">${t("tasks.everyone")}</option>
356
+ <option value=${UNASSIGNED}>${t("tasks.nobody")}</option>
357
+ ${names.map((n) => html`<option key=${n} value=${n}>${n}</option>`)}
358
+ </select>
359
+ ${archived || !writable ? null : html`<button type="button" class="btn btn-primary" onClick=${onCreate}><${Icon} name="plus" size=${16} />${t("tasks.newMain")}</button>`}
360
+ </div>
361
+ ${mains.length === 0 && !drag ? html`<${Empty} icon="tasks" title=${assignee ? t("tasks.emptyFiltered") : t("tasks.emptyBoard")} hint=${assignee || archived ? undefined : t("tasks.emptyBoardHint")} />`
362
+ : html`<div class="tb-scroll"><div class="tb-columns">
363
+ ${COLUMNS.map((status) => {
364
+ const list = mains.filter((c) => c.status === status);
365
+ const line = spot && spot.status === status ? spot.index : -1;
366
+ return html`<section key=${status} class=${`tb-col st-${status}${drag ? " can-drop" : ""}${line >= 0 ? " over" : ""}`} aria-label=${t(`tasks.${status}`)}
367
+ onDragOver=${(e) => over(e, status)} onDrop=${(e) => dropOn(e, status, list)}>
368
+ <h3 class="tb-col-head"><span class="tb-dot"></span>${t(`tasks.${status}`)}
369
+ ${status === "done" ? html`<button type="button" class="tb-link" title=${t("tasks.toggleDone")} onClick=${() => onAllDone(!allDone)}>${allDone ? t("tasks.all") : t("tasks.recent")}</button>` : null}
370
+ <span class="tb-count">${list.length}</span></h3>
371
+ ${cellItems(list, drag ? drag.id : null, line, (card, dragging) => {
372
+ const lit = Math.max(flashed.get(card.id) || 0, ...(subsOf.get(card.id) || []).map((s) => flashed.get(s.id) || 0));
373
+ return html`<${Tile} key=${card.id} card=${card} dragging=${dragging} flash=${now - lit < 4000} subs=${subsOf.get(card.id)} writable=${writable}
374
+ expanded=${!!expanded[card.id]} onToggleSubs=${() => setExpanded({ ...expanded, [card.id]: !expanded[card.id] })}
375
+ onOpen=${onOpen} onKeyMove=${(e) => keyMove(card, e)}
376
+ onDragStart=${(e) => { e.dataTransfer.setData("text/plain", card.id); e.dataTransfer.effectAllowed = "move"; setDrag(card); }} onDragEnd=${end} />`;
377
+ })}
378
+ </section>`;
379
+ })}
380
+ </div></div>`}
381
+ </div>`;
382
+ }
383
+
384
+ /** A column's cards, with a line where a dragged card would land. */
385
+ function cellItems(list, draggingId, lineAt, render) {
386
+ const items = [];
387
+ let k = 0;
388
+ for (const card of list) {
389
+ const dragging = draggingId === card.id;
390
+ if (!dragging) {
391
+ if (lineAt === k) items.push(html`<div key=${`line-${k}`} class="tb-drop-line"></div>`);
392
+ k++;
393
+ }
394
+ items.push(render(card, dragging));
395
+ }
396
+ if (lineAt === k) items.push(html`<div key="line-end" class="tb-drop-line"></div>`);
397
+ return items;
398
+ }
399
+
400
+ /** A card on the board: a main task shows its sub-task progress, and can open up the list. */
401
+ function Tile({ card, dragging, flash, subs = [], expanded, writable, onToggleSubs, onOpen, onKeyMove, onDragStart, onDragEnd }) {
402
+ const accepted = card.acceptance.filter((a) => a.done).length;
403
+ const progress = card.progress;
404
+ const hasSubs = !!progress && progress.total > 0;
405
+ // The board carries only recently finished sub tasks; older ones are counted, not listed.
406
+ const older = hasSubs ? progress.total - subs.length : 0;
407
+ const replied = card.lastComment && card.lastComment.authorKind !== "person";
408
+ const gap = card.status === "review" ? wrapUpText(card) : "";
409
+ // Who works on a main task now (a step may be its reviewer's), rather than who owns it.
410
+ const now = hasSubs ? currentWork(card, subs) : null;
411
+ const workers = now ? workersOf(now.cards) : [];
412
+ // The tile is a group, not a button: its title is the control (keyboard, screen readers); a click anywhere opens it.
413
+ return html`<div class=${`tb-tile st-${card.status}${dragging ? " dragging" : ""}${flash ? " flash" : ""}`} data-tile=${card.id}
414
+ draggable=${writable} onDragStart=${writable ? onDragStart : undefined} onDragEnd=${writable ? onDragEnd : undefined}
415
+ onClick=${() => onOpen(card.id)}>
416
+ <div class="tb-tile-top">
417
+ <span class="tb-code">${card.id}</span>
418
+ ${card.priority === "urgent" || card.priority === "high" ? html`<span class=${`tb-tag p-${card.priority}`}>${t(card.priority === "urgent" ? "tasks.urgentTag" : "tasks.highTag")}</span>` : null}
419
+ ${card.kind === "sub" && card.needsReview && card.status !== "done" ? html`<span class="tb-tag st-review" title=${t("tasks.needsReviewTitle")}>${t("tasks.needsReview")}</span>` : null}
420
+ ${gap ? html`<span class="tb-tag wrapup" title=${t("tasks.wrapLine", gap)}>${t("tasks.wrapTag")}</span>` : null}
421
+ <span class="tb-grow"></span>
422
+ ${card.size ? html`<span class="tb-size" title=${t("tasks.size")}>${t(`tasks.size${card.size}`)}</span>` : null}
423
+ </div>
424
+ <button type="button" class="tb-tile-title" title=${writable ? t("tasks.tileHint") : undefined}
425
+ onClick=${(e) => { e.stopPropagation(); onOpen(card.id); }} onKeyDown=${onKeyMove}>${card.title}</button>
426
+ ${card.status === "blocked" && card.blockedReason ? html`<div class="tb-tile-note st-blocked"><${Icon} name="alert" size=${13} /><span>${card.blockedReason}</span></div>` : null}
427
+ ${hasSubs ? html`<button type="button" class="tb-tile-progress" aria-expanded=${expanded ? "true" : "false"} title=${expanded ? t("tasks.hideSubs") : t("tasks.showSubs")}
428
+ onClick=${(e) => { e.stopPropagation(); onToggleSubs(); }}>
429
+ <${ProgressBar} progress=${progress} wide />
430
+ <span>${t("tasks.subProgress", progress.done, progress.total)}</span>
431
+ ${progress.blocked ? html`<span class="tb-flag st-blocked"><${Icon} name="alert" size=${12} />${progress.blocked}</span>` : null}
432
+ <${Icon} name="chevron" size=${14} cls=${expanded ? "tb-chev open" : "tb-chev"} />
433
+ </button>` : null}
434
+ ${hasSubs && expanded ? html`<ul class="tb-subs" onClick=${(e) => e.stopPropagation()}>
435
+ ${subs.map((s) => html`<li key=${s.id}><button type="button" onClick=${() => onOpen(s.id)} title=${`${s.id} ${s.title} (${t(`tasks.${s.status}`)})`}>
436
+ <span class=${`tb-dot st-${s.status}`} aria-label=${t(`tasks.${s.status}`)}></span><span class="tb-code">${s.id}</span><span class="tb-sub-title">${s.title}</span>
437
+ ${s.assignee ? html`<span class="tb-who">${s.assignee}</span>` : null}</button>
438
+ ${s.status === "blocked" && s.blockedReason ? html`<div class="tb-sub-blocked">${s.blockedReason}</div>` : null}</li>`)}
439
+ ${older > 0 ? html`<li class="muted tb-sub-more">${t("tasks.olderSubs", older)}</li>` : null}
440
+ </ul>` : null}
441
+ ${now ? html`<div class="tb-tile-now" title=${now.cards.map((c) => `${c.id} ${c.title}`).join("\n")}>
442
+ <span class=${`tb-now-label st-${now.tone}`}>${t(now.label)}</span><span class="tb-code">${now.cards[0].id}</span>
443
+ <span class="tb-now-title">${now.cards[0].title}</span>${now.cards.length > 1 ? html`<span class="muted">${t("tasks.moreCards", now.cards.length - 1)}</span>` : null}</div>` : null}
444
+ <div class="tb-tile-foot">
445
+ ${now ? html`<span class="tb-who" title=${t("tasks.ownerTip", card.assignee || t("tasks.nobody"))}>${workers.map((w) => w || t("tasks.nobody")).join(", ")}</span>`
446
+ : hasSubs && card.status === "review" ? html`<span class="tb-who tb-now-label st-review" title=${t("tasks.ownerTip", card.assignee || t("tasks.nobody"))}>${t("tasks.review")}</span>`
447
+ : html`<span class="tb-who">${card.assignee || "—"}</span>`}
448
+ <span class="tb-tile-stats">
449
+ ${card.acceptance.length ? html`<span class=${accepted === card.acceptance.length ? "all" : ""} title=${t(card.kind === "main" ? "tasks.definition" : "tasks.acceptance")}><${Icon} name="check" size=${13} />${accepted}/${card.acceptance.length}</span>` : null}
450
+ ${card.unmetDeps.length ? html`<span title=${t("tasks.waitsFor", card.unmetDeps.join(", "))}><${Icon} name="clock" size=${13} /></span>` : null}
451
+ ${card.commentCount ? html`<span class=${replied ? "reply" : ""} title=${replied ? t("tasks.agentReplied") : t("tasks.comments")}><${Icon} name="chat" size=${13} />${card.commentCount}</span>` : null}
452
+ </span>
453
+ </div>
454
+ </div>`;
455
+ }
456
+
457
+ // ── Dialogs: a new main task, a project ───────────────────
458
+
459
+ function CreateMainDialog({ projects, project, onClose, onCreated }) {
460
+ const { instances } = useStore(appStore);
461
+ const active = projects.filter((p) => !p.archived);
462
+ const [f, setF] = useState({ project: project === null || project === undefined ? "" : String(project), title: "", goal: "", scope: "", acceptance: "", context: "", size: "", priority: "normal", assignee: "", needsReview: true });
463
+ const [busy, setBusy] = useState(false);
464
+ const set = (k) => (e) => { const v = e.target.type === "checkbox" ? e.target.checked : e.target.value; setF((cur) => ({ ...cur, [k]: v })); };
465
+ async function submit() {
466
+ if (!f.title.trim()) { toast(t("tasks.titleRequired"), false); return; }
467
+ setBusy(true);
468
+ try {
469
+ const { card } = await boardApi.create({
470
+ kind: "main", project: f.project === "" ? null : Number(f.project), title: f.title, goal: f.goal, scope: f.scope,
471
+ acceptance: f.acceptance.split("\n").map((l) => l.trim()).filter(Boolean), context: f.context,
472
+ size: f.size || null, priority: f.priority, assignee: f.assignee.trim() || null, needsReview: f.needsReview,
473
+ });
474
+ toast(t("tasks.created", card.id));
475
+ onCreated(card.id);
476
+ } catch (err) { toast(errorText(err), false); setBusy(false); }
477
+ }
478
+ const field = (label, input) => html`<label class="field"><span>${label}</span>${input}</label>`;
479
+ return html`<${Dialog} title=${t("tasks.createMain")} onClose=${onClose} busy=${busy} wide
480
+ actions=${html`<button type="button" class="btn" onClick=${onClose} disabled=${busy}>${t("tasks.cancel")}</button>
481
+ <button type="button" class="btn btn-primary" onClick=${submit} disabled=${busy || !f.title.trim()}>${t("tasks.create")}</button>`}>
482
+ <form class="form" onSubmit=${(e) => { e.preventDefault(); submit(); }}><fieldset class="form" disabled=${busy}>
483
+ ${field(`${t("tasks.title")} *`, html`<input value=${f.title} maxlength="100" placeholder=${t("tasks.titleHint")} onInput=${set("title")} required />`)}
484
+ ${field(t("tasks.project"), html`<select value=${f.project} onChange=${set("project")}><option value="">${t("tasks.noProject")}</option>${active.map((p) => html`<option key=${p.id} value=${String(p.id)}>${p.name}</option>`)}</select>`)}
485
+ ${field(t("tasks.goal"), html`<textarea rows="2" maxlength="300" placeholder=${t("tasks.goalHint")} value=${f.goal} onInput=${set("goal")}></textarea>`)}
486
+ ${field(t("tasks.scope"), html`<textarea rows="2" maxlength="600" placeholder=${t("tasks.scopeHint")} value=${f.scope} onInput=${set("scope")}></textarea>`)}
487
+ ${field(t("tasks.definition"), html`<textarea rows="3" placeholder=${t("tasks.definitionHint")} value=${f.acceptance} onInput=${set("acceptance")}></textarea>`)}
488
+ ${field(t("tasks.context"), html`<textarea rows="2" maxlength="1000" placeholder=${t("tasks.contextHint")} value=${f.context} onInput=${set("context")}></textarea>`)}
489
+ <div class="tb-form-row">
490
+ ${field(t("tasks.assignee"), html`<select value=${f.assignee} onChange=${set("assignee")}><option value="">${t("tasks.nobody")}</option>${instances.map((i) => html`<option key=${i.name} value=${i.name}>${i.name}</option>`)}</select>`)}
491
+ ${field(t("tasks.size"), html`<select value=${f.size} onChange=${set("size")}><option value="">${t("tasks.sizeNone")}</option><option value="S">${t("tasks.sizeS")}</option><option value="M">${t("tasks.sizeM")}</option><option value="L">${t("tasks.sizeL")}</option></select>`)}
492
+ ${field(t("tasks.priority"), html`<select value=${f.priority} onChange=${set("priority")}>${["urgent", "high", "normal", "low"].map((p) => html`<option key=${p} value=${p}>${t(`tasks.${p}`)}</option>`)}</select>`)}
493
+ </div>
494
+ <label class="tb-check"><input type="checkbox" checked=${f.needsReview} onChange=${set("needsReview")} />${t("tasks.reviewBox")}</label>
495
+ </fieldset></form></${Dialog}>`;
496
+ }
497
+
498
+ function ProjectDialog({ project, onClose, onSaved }) {
499
+ const [f, setF] = useState({ name: project ? project.name : "", description: project ? project.description : "" });
500
+ const [busy, setBusy] = useState(false);
501
+ async function submit() {
502
+ if (!f.name.trim()) return;
503
+ setBusy(true);
504
+ try {
505
+ const r = project ? await boardApi.updateProject(project.id, f) : await boardApi.createProject(f.name, f.description);
506
+ toast(t(project ? "tasks.projectSaved" : "tasks.projectCreated"));
507
+ onSaved(r.project);
508
+ } catch (err) { toast(errorText(err), false); setBusy(false); }
509
+ }
510
+ return html`<${Dialog} title=${project ? t("tasks.rename") : t("tasks.newProject")} onClose=${onClose} busy=${busy}
511
+ actions=${html`<button type="button" class="btn" onClick=${onClose} disabled=${busy}>${t("tasks.cancel")}</button>
512
+ <button type="button" class="btn btn-primary" onClick=${submit} disabled=${busy || !f.name.trim()}>${project ? t("tasks.save") : t("tasks.create")}</button>`}>
513
+ <form class="form" onSubmit=${(e) => { e.preventDefault(); submit(); }}><fieldset class="form" disabled=${busy}>
514
+ <label class="field"><span>${t("tasks.projectName")} *</span><input value=${f.name} maxlength="60" onInput=${(e) => { const v = e.target.value; setF((c) => ({ ...c, name: v })); }} required /></label>
515
+ <label class="field"><span>${t("tasks.projectDesc")}</span><input value=${f.description} maxlength="300" onInput=${(e) => { const v = e.target.value; setF((c) => ({ ...c, description: v })); }} /></label>
516
+ </fieldset></form></${Dialog}>`;
517
+ }
518
+
519
+ /**
520
+ * Up and down for the open card in its column — a main task among its project's main tasks of the same status, a sub
521
+ * task among its main task's — for touch and keyboard, where a drag is not there.
522
+ */
523
+ function placeOf(cards, id, reload) {
524
+ const card = cards.find((c) => c.id === id);
525
+ if (!card || card.status === "cancelled") return null;
526
+ const column = cards.filter((c) => c.status !== "cancelled" && (card.kind === "sub"
527
+ ? c.kind === "sub" && c.parentId === card.parentId
528
+ : c.kind === "main" && projectKey(c) === projectKey(card) && c.status === card.status)).sort((a, b) => a.sort - b.sort);
529
+ const i = column.findIndex((c) => c.id === id);
530
+ const go = (after, before) => async () => { try { await boardApi.reorder(id, after, before); reload(); } catch (err) { toast(errorText(err), false); } };
531
+ return {
532
+ up: i > 0 ? go(column[i - 2]?.id ?? null, column[i - 1].id) : null,
533
+ down: i >= 0 && i < column.length - 1 ? go(column[i + 1].id, column[i + 2]?.id ?? null) : null,
534
+ };
535
+ }