@songsid/agend 2.3.0-beta.9 → 2.3.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 (743) hide show
  1. package/README.md +16 -8
  2. package/README.zh-TW.md +16 -8
  3. package/dist/adapter-world.js.map +1 -1
  4. package/dist/agend-home.d.ts +5 -0
  5. package/dist/agend-home.js +7 -0
  6. package/dist/agend-home.js.map +1 -0
  7. package/dist/agent-cli-instructions.md +7 -1
  8. package/dist/agent-cli.js +41 -0
  9. package/dist/agent-cli.js.map +1 -1
  10. package/dist/agent-endpoint.d.ts +9 -0
  11. package/dist/agent-endpoint.js +23 -0
  12. package/dist/agent-endpoint.js.map +1 -1
  13. package/dist/agent-file-read.js +1 -1
  14. package/dist/agent-file-read.js.map +1 -1
  15. package/dist/agent-whats-new.d.ts +16 -0
  16. package/dist/agent-whats-new.js +53 -12
  17. package/dist/agent-whats-new.js.map +1 -1
  18. package/dist/apply-job.js.map +1 -1
  19. package/dist/atomic-write.js.map +1 -1
  20. package/dist/auth-api.d.ts +12 -0
  21. package/dist/auth-api.js +77 -22
  22. package/dist/auth-api.js.map +1 -1
  23. package/dist/authority/authority-state.d.ts +4 -0
  24. package/dist/authority/authority-state.js +17 -8
  25. package/dist/authority/authority-state.js.map +1 -1
  26. package/dist/authority/session-authorizations.js.map +1 -1
  27. package/dist/authority/web-authority.d.ts +16 -8
  28. package/dist/authority/web-authority.js +19 -5
  29. package/dist/authority/web-authority.js.map +1 -1
  30. package/dist/backend/antigravity.d.ts +2 -2
  31. package/dist/backend/antigravity.js +2 -1
  32. package/dist/backend/antigravity.js.map +1 -1
  33. package/dist/backend/claude-code.js +13 -4
  34. package/dist/backend/claude-code.js.map +1 -1
  35. package/dist/backend/codex-metadata.d.ts +8 -0
  36. package/dist/backend/codex-metadata.js +74 -17
  37. package/dist/backend/codex-metadata.js.map +1 -1
  38. package/dist/backend/codex-package.d.ts +30 -0
  39. package/dist/backend/codex-package.js +133 -0
  40. package/dist/backend/codex-package.js.map +1 -0
  41. package/dist/backend/codex-session-lookup.js.map +1 -1
  42. package/dist/backend/codex.d.ts +2 -0
  43. package/dist/backend/codex.js +25 -5
  44. package/dist/backend/codex.js.map +1 -1
  45. package/dist/backend/credential-profile.d.ts +2 -0
  46. package/dist/backend/credential-profile.js +2 -0
  47. package/dist/backend/credential-profile.js.map +1 -1
  48. package/dist/backend/effort-metadata.js.map +1 -1
  49. package/dist/backend/grok.js +58 -3
  50. package/dist/backend/grok.js.map +1 -1
  51. package/dist/backend/kiro-engine-ledger.js.map +1 -1
  52. package/dist/backend/kiro-kas-store.js.map +1 -1
  53. package/dist/backend/kiro-v2-store.js.map +1 -1
  54. package/dist/backend/kiro-v3-identity.js.map +1 -1
  55. package/dist/backend/kiro.d.ts +9 -1
  56. package/dist/backend/kiro.js +102 -1
  57. package/dist/backend/kiro.js.map +1 -1
  58. package/dist/backend/launch-effort.js.map +1 -1
  59. package/dist/backend/mock.js.map +1 -1
  60. package/dist/backend/muse.d.ts +20 -2
  61. package/dist/backend/muse.js +154 -16
  62. package/dist/backend/muse.js.map +1 -1
  63. package/dist/backend/opencode-permission.js +1 -1
  64. package/dist/backend/opencode-permission.js.map +1 -1
  65. package/dist/backend/opencode.js +2 -1
  66. package/dist/backend/opencode.js.map +1 -1
  67. package/dist/backend/session-signals.js.map +1 -1
  68. package/dist/backend-availability.js.map +1 -1
  69. package/dist/backend-command.d.ts +2 -0
  70. package/dist/backend-command.js +3 -1
  71. package/dist/backend-command.js.map +1 -1
  72. package/dist/backend-outage.js.map +1 -1
  73. package/dist/binary-probe.js +1 -1
  74. package/dist/binary-probe.js.map +1 -1
  75. package/dist/bug-report-cli.d.ts +30 -0
  76. package/dist/bug-report-cli.js +139 -0
  77. package/dist/bug-report-cli.js.map +1 -0
  78. package/dist/bug-report.d.ts +47 -0
  79. package/dist/bug-report.js +137 -0
  80. package/dist/bug-report.js.map +1 -0
  81. package/dist/bundled-skills.d.ts +18 -0
  82. package/dist/bundled-skills.js +38 -0
  83. package/dist/bundled-skills.js.map +1 -0
  84. package/dist/cache-service.js +1 -1
  85. package/dist/cache-service.js.map +1 -1
  86. package/dist/channel/adapters/discord-client-retirement.d.ts +5 -0
  87. package/dist/channel/adapters/discord-client-retirement.js +57 -0
  88. package/dist/channel/adapters/discord-client-retirement.js.map +1 -0
  89. package/dist/channel/adapters/discord.d.ts +4 -0
  90. package/dist/channel/adapters/discord.js +57 -12
  91. package/dist/channel/adapters/discord.js.map +1 -1
  92. package/dist/channel/adapters/telegram.d.ts +7 -1
  93. package/dist/channel/adapters/telegram.js +57 -49
  94. package/dist/channel/adapters/telegram.js.map +1 -1
  95. package/dist/channel/mcp-server.js +21 -4
  96. package/dist/channel/mcp-server.js.map +1 -1
  97. package/dist/channel/mcp-tools.d.ts +3 -1
  98. package/dist/channel/mcp-tools.js +41 -4
  99. package/dist/channel/mcp-tools.js.map +1 -1
  100. package/dist/channel/message-queue.js.map +1 -1
  101. package/dist/channel/sticker-download.js.map +1 -1
  102. package/dist/channel/tool-router.js.map +1 -1
  103. package/dist/child-process.d.ts +21 -0
  104. package/dist/child-process.js +54 -0
  105. package/dist/child-process.js.map +1 -0
  106. package/dist/classic-channel-manager.js +3 -2
  107. package/dist/classic-channel-manager.js.map +1 -1
  108. package/dist/cli-entry.js.map +1 -1
  109. package/dist/cli-flags.js.map +1 -1
  110. package/dist/cli.d.ts +1 -0
  111. package/dist/cli.js +448 -313
  112. package/dist/cli.js.map +1 -1
  113. package/dist/command-table.d.ts +1 -1
  114. package/dist/command-table.js +1 -1
  115. package/dist/command-table.js.map +1 -1
  116. package/dist/compact-watch.js.map +1 -1
  117. package/dist/completion-install.js.map +1 -1
  118. package/dist/completion.js.map +1 -1
  119. package/dist/config-validator.d.ts +6 -0
  120. package/dist/config-validator.js +81 -8
  121. package/dist/config-validator.js.map +1 -1
  122. package/dist/config.d.ts +15 -3
  123. package/dist/config.js +41 -10
  124. package/dist/config.js.map +1 -1
  125. package/dist/connection-secrets.js.map +1 -1
  126. package/dist/context-guardian.js.map +1 -1
  127. package/dist/coredump-filter.js.map +1 -1
  128. package/dist/cpu-profile.js.map +1 -1
  129. package/dist/cross-instance-envelope.js.map +1 -1
  130. package/dist/cross-instance-notice.d.ts +7 -0
  131. package/dist/cross-instance-notice.js +10 -0
  132. package/dist/cross-instance-notice.js.map +1 -1
  133. package/dist/daemon.d.ts +52 -1
  134. package/dist/daemon.js +385 -27
  135. package/dist/daemon.js.map +1 -1
  136. package/dist/daily-summary.js.map +1 -1
  137. package/dist/darwin-memory.js +1 -1
  138. package/dist/darwin-memory.js.map +1 -1
  139. package/dist/dashboard-live.d.ts +8 -0
  140. package/dist/dashboard-live.js +35 -0
  141. package/dist/dashboard-live.js.map +1 -0
  142. package/dist/dashboard-permissions.d.ts +69 -0
  143. package/dist/dashboard-permissions.js +138 -0
  144. package/dist/dashboard-permissions.js.map +1 -0
  145. package/dist/decision-list.js.map +1 -1
  146. package/dist/delivery-consumed-watch.js.map +1 -1
  147. package/dist/delivery-outbox.d.ts +11 -0
  148. package/dist/delivery-outbox.js +52 -2
  149. package/dist/delivery-outbox.js.map +1 -1
  150. package/dist/delivery-outbox.test.js.map +1 -1
  151. package/dist/delivery-reconciliation.test.js.map +1 -1
  152. package/dist/diagnostic-redaction.d.ts +10 -0
  153. package/dist/diagnostic-redaction.js +167 -0
  154. package/dist/diagnostic-redaction.js.map +1 -0
  155. package/dist/doctor.d.ts +8 -0
  156. package/dist/doctor.js +52 -0
  157. package/dist/doctor.js.map +1 -1
  158. package/dist/e2e-isolation.js.map +1 -1
  159. package/dist/event-log.js.map +1 -1
  160. package/dist/export-import.js +7 -5
  161. package/dist/export-import.js.map +1 -1
  162. package/dist/fleet-context.d.ts +1 -1
  163. package/dist/fleet-control-audit.js.map +1 -1
  164. package/dist/fleet-label.js.map +1 -1
  165. package/dist/fleet-level-config.d.ts +1 -1
  166. package/dist/fleet-level-config.js +2 -0
  167. package/dist/fleet-level-config.js.map +1 -1
  168. package/dist/fleet-lock.d.ts +6 -0
  169. package/dist/fleet-lock.js +22 -2
  170. package/dist/fleet-lock.js.map +1 -1
  171. package/dist/fleet-manager.d.ts +283 -15
  172. package/dist/fleet-manager.js +1825 -331
  173. package/dist/fleet-manager.js.map +1 -1
  174. package/dist/fleet-process-handlers.d.ts +18 -0
  175. package/dist/fleet-process-handlers.js +70 -0
  176. package/dist/fleet-process-handlers.js.map +1 -0
  177. package/dist/fleet-startup.d.ts +14 -0
  178. package/dist/fleet-startup.js +30 -0
  179. package/dist/fleet-startup.js.map +1 -0
  180. package/dist/fleet-traffic.js.map +1 -1
  181. package/dist/forged-envelope-scan.d.ts +0 -20
  182. package/dist/forged-envelope-scan.js +3 -2
  183. package/dist/forged-envelope-scan.js.map +1 -1
  184. package/dist/full-restart.d.ts +5 -5
  185. package/dist/full-restart.js +5 -5
  186. package/dist/full-restart.js.map +1 -1
  187. package/dist/general-knowledge/skills/bug-report/SKILL.md +31 -0
  188. package/dist/general-knowledge/skills/credential-profiles/SKILL.md +52 -11
  189. package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +5 -1
  190. package/dist/general-knowledge/skills/fleet-config/SKILL.md +3 -1
  191. package/dist/general-knowledge/skills/fleet-restart/SKILL.md +8 -9
  192. package/dist/general-knowledge/skills/instance-lifecycle/SKILL.md +11 -0
  193. package/dist/general-knowledge/skills/multi-channel/SKILL.md +1 -1
  194. package/dist/general-knowledge/skills/persona-emoji/SKILL.md +1 -1
  195. package/dist/general-knowledge/skills/scheduling/SKILL.md +4 -0
  196. package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +321 -0
  197. package/dist/general-knowledge/skills/web-appearance/SKILL.md +24 -4
  198. package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +554 -0
  199. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +156 -32
  200. package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +1 -1
  201. package/dist/general-knowledge/steering/core-rules.md +1 -1
  202. package/dist/host-memory.js.map +1 -1
  203. package/dist/idle-gate-log.d.ts +19 -0
  204. package/dist/idle-gate-log.js +15 -0
  205. package/dist/idle-gate-log.js.map +1 -0
  206. package/dist/instance-config-impact.js +25 -4
  207. package/dist/instance-config-impact.js.map +1 -1
  208. package/dist/instance-lifecycle.d.ts +11 -1
  209. package/dist/instance-lifecycle.js +97 -25
  210. package/dist/instance-lifecycle.js.map +1 -1
  211. package/dist/instructions.d.ts +12 -6
  212. package/dist/instructions.js +19 -10
  213. package/dist/instructions.js.map +1 -1
  214. package/dist/kiro-compact-pane.js.map +1 -1
  215. package/dist/kiro-db-reader.js.map +1 -1
  216. package/dist/kiro-engine-status.js.map +1 -1
  217. package/dist/kiro-log-gate.js.map +1 -1
  218. package/dist/kiro-transcript-lane.js.map +1 -1
  219. package/dist/locale.js +450 -20
  220. package/dist/locale.js.map +1 -1
  221. package/dist/log-service.js +1 -0
  222. package/dist/log-service.js.map +1 -1
  223. package/dist/log-steps.d.ts +1 -0
  224. package/dist/log-steps.js +15 -0
  225. package/dist/log-steps.js.map +1 -1
  226. package/dist/logger.js +3 -2
  227. package/dist/logger.js.map +1 -1
  228. package/dist/login-controller.d.ts +45 -0
  229. package/dist/login-controller.js +101 -5
  230. package/dist/login-controller.js.map +1 -1
  231. package/dist/login-flows.js +1 -1
  232. package/dist/login-manager.js.map +1 -1
  233. package/dist/login-shell.js +3 -1
  234. package/dist/login-shell.js.map +1 -1
  235. package/dist/ls-attach-resolve.d.ts +96 -0
  236. package/dist/ls-attach-resolve.js +180 -0
  237. package/dist/ls-attach-resolve.js.map +1 -0
  238. package/dist/ls-rows.d.ts +12 -0
  239. package/dist/ls-rows.js +83 -4
  240. package/dist/ls-rows.js.map +1 -1
  241. package/dist/memory-pressure.js.map +1 -1
  242. package/dist/muse-usage-relay.js.map +1 -1
  243. package/dist/needs-you-hub.d.ts +98 -1
  244. package/dist/needs-you-hub.js +313 -44
  245. package/dist/needs-you-hub.js.map +1 -1
  246. package/dist/needs-you-live.d.ts +5 -2
  247. package/dist/needs-you-live.js +6 -3
  248. package/dist/needs-you-live.js.map +1 -1
  249. package/dist/needs-you.d.ts +68 -3
  250. package/dist/needs-you.js +39 -4
  251. package/dist/needs-you.js.map +1 -1
  252. package/dist/network-family.js.map +1 -1
  253. package/dist/operator-control-paths.js +3 -4
  254. package/dist/operator-control-paths.js.map +1 -1
  255. package/dist/outbound-handlers.d.ts +5 -2
  256. package/dist/outbound-handlers.js +81 -24
  257. package/dist/outbound-handlers.js.map +1 -1
  258. package/dist/outbound-schemas.d.ts +106 -60
  259. package/dist/outbound-schemas.js +33 -10
  260. package/dist/outbound-schemas.js.map +1 -1
  261. package/dist/outbox-open-error.js.map +1 -1
  262. package/dist/pane-context-cache.js.map +1 -1
  263. package/dist/paths.js.map +1 -1
  264. package/dist/private-dir.js.map +1 -1
  265. package/dist/probe-worker-pool.js.map +1 -1
  266. package/dist/profile-control.js.map +1 -1
  267. package/dist/prompt-file-ref.js.map +1 -1
  268. package/dist/provider-probe.js.map +1 -1
  269. package/dist/provider-secret-registry.d.ts +5 -3
  270. package/dist/provider-secret-registry.js +8 -2
  271. package/dist/provider-secret-registry.js.map +1 -1
  272. package/dist/public-link-auto-open.d.ts +32 -0
  273. package/dist/public-link-auto-open.js +86 -0
  274. package/dist/public-link-auto-open.js.map +1 -0
  275. package/dist/public-link-owner.d.ts +10 -0
  276. package/dist/public-link-owner.js +36 -0
  277. package/dist/public-link-owner.js.map +1 -0
  278. package/dist/public-link-progress.d.ts +12 -1
  279. package/dist/public-link-progress.js +5 -10
  280. package/dist/public-link-progress.js.map +1 -1
  281. package/dist/public-preview.js.map +1 -1
  282. package/dist/public-tunnel-config.d.ts +11 -0
  283. package/dist/public-tunnel-config.js +68 -0
  284. package/dist/public-tunnel-config.js.map +1 -0
  285. package/dist/public-web-gateway.d.ts +2 -0
  286. package/dist/public-web-gateway.js +19 -6
  287. package/dist/public-web-gateway.js.map +1 -1
  288. package/dist/public-web-link.d.ts +48 -3
  289. package/dist/public-web-link.js +177 -51
  290. package/dist/public-web-link.js.map +1 -1
  291. package/dist/quickstart-api.d.ts +9 -0
  292. package/dist/quickstart-api.js +82 -0
  293. package/dist/quickstart-api.js.map +1 -1
  294. package/dist/quickstart.d.ts +5 -0
  295. package/dist/quickstart.js +40 -25
  296. package/dist/quickstart.js.map +1 -1
  297. package/dist/reply-buttons-controller.js.map +1 -1
  298. package/dist/reply-dedup.js.map +1 -1
  299. package/dist/resource-report.js +1 -1
  300. package/dist/resource-report.js.map +1 -1
  301. package/dist/restart-guard.js.map +1 -1
  302. package/dist/restart-progress.d.ts +80 -1
  303. package/dist/restart-progress.js +306 -21
  304. package/dist/restart-progress.js.map +1 -1
  305. package/dist/rollout-index.js.map +1 -1
  306. package/dist/runtime-cpu-profile.js.map +1 -1
  307. package/dist/scheduler/db.d.ts +40 -13
  308. package/dist/scheduler/db.js +123 -89
  309. package/dist/scheduler/db.js.map +1 -1
  310. package/dist/scheduler/scheduler.js.map +1 -1
  311. package/dist/scheduler/task-board-text.d.ts +26 -0
  312. package/dist/scheduler/task-board-text.js +140 -0
  313. package/dist/scheduler/task-board-text.js.map +1 -0
  314. package/dist/scheduler/task-board.d.ts +730 -0
  315. package/dist/scheduler/task-board.js +2179 -0
  316. package/dist/scheduler/task-board.js.map +1 -0
  317. package/dist/scheduler/types.d.ts +6 -1
  318. package/dist/sd-notify.js +2 -1
  319. package/dist/sd-notify.js.map +1 -1
  320. package/dist/search/search-docs.d.ts +58 -0
  321. package/dist/search/search-docs.js +63 -0
  322. package/dist/search/search-docs.js.map +1 -0
  323. package/dist/search/search-index.d.ts +149 -0
  324. package/dist/search/search-index.js +453 -0
  325. package/dist/search/search-index.js.map +1 -0
  326. package/dist/search/search-store.d.ts +122 -0
  327. package/dist/search/search-store.js +236 -0
  328. package/dist/search/search-store.js.map +1 -0
  329. package/dist/search/search-worker.d.ts +1 -0
  330. package/dist/search/search-worker.js +163 -0
  331. package/dist/search/search-worker.js.map +1 -0
  332. package/dist/secret-file.js.map +1 -1
  333. package/dist/secret-store.d.ts +2 -0
  334. package/dist/secret-store.js +21 -0
  335. package/dist/secret-store.js.map +1 -1
  336. package/dist/service-installer.d.ts +98 -1
  337. package/dist/service-installer.js +246 -11
  338. package/dist/service-installer.js.map +1 -1
  339. package/dist/service-owner.d.ts +44 -0
  340. package/dist/service-owner.js +48 -0
  341. package/dist/service-owner.js.map +1 -0
  342. package/dist/service-plan.d.ts +39 -2
  343. package/dist/service-plan.js +188 -6
  344. package/dist/service-plan.js.map +1 -1
  345. package/dist/session-oom-stop.d.ts +45 -0
  346. package/dist/session-oom-stop.js +259 -0
  347. package/dist/session-oom-stop.js.map +1 -0
  348. package/dist/session-oom.d.ts +34 -0
  349. package/dist/session-oom.js +251 -0
  350. package/dist/session-oom.js.map +1 -0
  351. package/dist/settings-api.d.ts +14 -1
  352. package/dist/settings-api.js +74 -13
  353. package/dist/settings-api.js.map +1 -1
  354. package/dist/settings-baseline.js.map +1 -1
  355. package/dist/settings-change.d.ts +5 -0
  356. package/dist/settings-change.js +167 -6
  357. package/dist/settings-change.js.map +1 -1
  358. package/dist/settings-confirmation.d.ts +9 -1
  359. package/dist/settings-confirmation.js +18 -8
  360. package/dist/settings-confirmation.js.map +1 -1
  361. package/dist/settings-control.js +2 -1
  362. package/dist/settings-control.js.map +1 -1
  363. package/dist/settings-effect.js +29 -4
  364. package/dist/settings-effect.js.map +1 -1
  365. package/dist/settings-http-confirmation.d.ts +13 -0
  366. package/dist/settings-http-confirmation.js +15 -3
  367. package/dist/settings-http-confirmation.js.map +1 -1
  368. package/dist/settings-instance-patch.js +13 -1
  369. package/dist/settings-instance-patch.js.map +1 -1
  370. package/dist/settings-request-capability.d.ts +4 -1
  371. package/dist/settings-request-capability.js +11 -3
  372. package/dist/settings-request-capability.js.map +1 -1
  373. package/dist/settings-transaction.js.map +1 -1
  374. package/dist/setup-auth.d.ts +1 -0
  375. package/dist/setup-auth.js +2 -1
  376. package/dist/setup-auth.js.map +1 -1
  377. package/dist/setup-form.d.ts +1 -1
  378. package/dist/setup-form.js +23 -7
  379. package/dist/setup-form.js.map +1 -1
  380. package/dist/setup-host.d.ts +6 -1
  381. package/dist/setup-host.js +15 -4
  382. package/dist/setup-host.js.map +1 -1
  383. package/dist/setup-wizard.js +6 -3
  384. package/dist/setup-wizard.js.map +1 -1
  385. package/dist/spawn-gate.d.ts +7 -0
  386. package/dist/spawn-gate.js +45 -12
  387. package/dist/spawn-gate.js.map +1 -1
  388. package/dist/start-attempt.d.ts +11 -0
  389. package/dist/start-attempt.js +29 -0
  390. package/dist/start-attempt.js.map +1 -0
  391. package/dist/start-failure-chat.d.ts +37 -0
  392. package/dist/start-failure-chat.js +142 -0
  393. package/dist/start-failure-chat.js.map +1 -0
  394. package/dist/start-failure-notice.d.ts +4 -0
  395. package/dist/start-failure-notice.js +20 -0
  396. package/dist/start-failure-notice.js.map +1 -0
  397. package/dist/start-failure.d.ts +171 -0
  398. package/dist/start-failure.js +348 -0
  399. package/dist/start-failure.js.map +1 -0
  400. package/dist/startup-memory-deadline.d.ts +23 -0
  401. package/dist/startup-memory-deadline.js +55 -0
  402. package/dist/startup-memory-deadline.js.map +1 -0
  403. package/dist/statusline-watcher.js.map +1 -1
  404. package/dist/storm-window.js.map +1 -1
  405. package/dist/systemd-peer-bus.js.map +1 -1
  406. package/dist/target-queue-worker.js.map +1 -1
  407. package/dist/task-board-summary.d.ts +101 -0
  408. package/dist/task-board-summary.js +177 -0
  409. package/dist/task-board-summary.js.map +1 -0
  410. package/dist/task-board-tool.d.ts +25 -0
  411. package/dist/task-board-tool.js +155 -0
  412. package/dist/task-board-tool.js.map +1 -0
  413. package/dist/task-board-waker.d.ts +51 -0
  414. package/dist/task-board-waker.js +141 -0
  415. package/dist/task-board-waker.js.map +1 -0
  416. package/dist/telegram-api-root.d.ts +1 -0
  417. package/dist/telegram-api-root.js +40 -0
  418. package/dist/telegram-api-root.js.map +1 -0
  419. package/dist/tips.js.map +1 -1
  420. package/dist/tmux-control.js +1 -1
  421. package/dist/tmux-control.js.map +1 -1
  422. package/dist/tmux-exec-helper.d.ts +104 -0
  423. package/dist/tmux-exec-helper.js +412 -0
  424. package/dist/tmux-exec-helper.js.map +1 -0
  425. package/dist/tmux-manager.d.ts +1 -0
  426. package/dist/tmux-manager.js +29 -13
  427. package/dist/tmux-manager.js.map +1 -1
  428. package/dist/tmux-read.js +2 -2
  429. package/dist/tmux-read.js.map +1 -1
  430. package/dist/tool-permissions.js.map +1 -1
  431. package/dist/tool-progress.js.map +1 -1
  432. package/dist/topic-archiver.js.map +1 -1
  433. package/dist/topic-commands.d.ts +13 -0
  434. package/dist/topic-commands.js +56 -33
  435. package/dist/topic-commands.js.map +1 -1
  436. package/dist/topic-link.js.map +1 -1
  437. package/dist/transcript-delta-reader.js.map +1 -1
  438. package/dist/transcript-jsonl.js.map +1 -1
  439. package/dist/transcript-monitor.js +5 -0
  440. package/dist/transcript-monitor.js.map +1 -1
  441. package/dist/transcript-sources.d.ts +2 -0
  442. package/dist/transcript-sources.js +4 -0
  443. package/dist/transcript-sources.js.map +1 -1
  444. package/dist/tunnel/cloudflared-install.js +3 -1
  445. package/dist/tunnel/cloudflared-install.js.map +1 -1
  446. package/dist/tunnel/cloudflared.d.ts +7 -16
  447. package/dist/tunnel/cloudflared.js +100 -13
  448. package/dist/tunnel/cloudflared.js.map +1 -1
  449. package/dist/tunnel/lease.js +3 -2
  450. package/dist/tunnel/lease.js.map +1 -1
  451. package/dist/tunnel/manager.js.map +1 -1
  452. package/dist/tunnel/named-diagnostics.d.ts +13 -0
  453. package/dist/tunnel/named-diagnostics.js +52 -0
  454. package/dist/tunnel/named-diagnostics.js.map +1 -0
  455. package/dist/tunnel/public-fetch.js.map +1 -1
  456. package/dist/tunnel/purpose-lane.js.map +1 -1
  457. package/dist/tunnel/types.d.ts +4 -1
  458. package/dist/tunnel/types.js.map +1 -1
  459. package/dist/types.d.ts +56 -2
  460. package/dist/ui/app-effects.js +23 -5
  461. package/dist/ui/app.html +2 -0
  462. package/dist/ui/backend-signin.js +297 -0
  463. package/dist/ui/chat-effects.js +10 -3
  464. package/dist/ui/chat-msg-actions.js +176 -0
  465. package/dist/ui/chat-render.js +62 -18
  466. package/dist/ui/chat-store.js +218 -48
  467. package/dist/ui/chat-strings.js +40 -8
  468. package/dist/ui/chat-thread.js +119 -10
  469. package/dist/ui/effect-trial-offer.js +2 -31
  470. package/dist/ui/effects-waker.js +2 -0
  471. package/dist/ui/fleet-canvas-layout.js +13 -4
  472. package/dist/ui/fleet-canvas.js +69 -28
  473. package/dist/ui/fleet-office-editor.js +149 -0
  474. package/dist/ui/fleet-office-model.js +358 -0
  475. package/dist/ui/fleet-office-pack.js +199 -0
  476. package/dist/ui/fleet-office-skin.js +474 -0
  477. package/dist/ui/fleet-office-store.js +49 -0
  478. package/dist/ui/fleet-office-zones.js +177 -0
  479. package/dist/ui/fleet-office.js +434 -0
  480. package/dist/ui/fleet-org.js +18 -5
  481. package/dist/ui/fleet-route-scene.js +272 -0
  482. package/dist/ui/fleet-route-worker.js +21 -0
  483. package/dist/ui/fleet-route.js +88 -0
  484. package/dist/ui/fleet-task-card.js +436 -0
  485. package/dist/ui/fleet-task-common.js +482 -0
  486. package/dist/ui/fleet-task-work.js +107 -0
  487. package/dist/ui/fleet-tasks.js +569 -0
  488. package/dist/ui/fleet-traffic.js +81 -18
  489. package/dist/ui/fleet-usage.js +36 -2
  490. package/dist/ui/guest-refused.html +41 -0
  491. package/dist/ui/icons/icon-192.png +0 -0
  492. package/dist/ui/icons/icon-512.png +0 -0
  493. package/dist/ui/image-lightbox.js +45 -1
  494. package/dist/ui/image-zoom.js +242 -0
  495. package/dist/ui/panel-chat.js +214 -57
  496. package/dist/ui/panel-details.js +2 -0
  497. package/dist/ui/panel-fleet.js +32 -71
  498. package/dist/ui/panel-log.js +3 -3
  499. package/dist/ui/panel-needs.js +23 -6
  500. package/dist/ui/panel-settings.js +166 -33
  501. package/dist/ui/preview-panel.js +2 -1
  502. package/dist/ui/search-dialog.js +350 -0
  503. package/dist/ui/settings-appearance.js +170 -10
  504. package/dist/ui/settings-apply.js +3 -3
  505. package/dist/ui/settings-confirm.js +7 -2
  506. package/dist/ui/settings-dialogs.js +46 -21
  507. package/dist/ui/settings-discord.js +6 -2
  508. package/dist/ui/settings-guests.js +2 -1
  509. package/dist/ui/settings-learn.js +1 -0
  510. package/dist/ui/settings-model.js +51 -18
  511. package/dist/ui/settings-mods.js +8 -3
  512. package/dist/ui/settings-owner-effects.js +6 -4
  513. package/dist/ui/settings-strings.js +186 -6
  514. package/dist/ui/settings-token.js +3 -1
  515. package/dist/ui/settings-web-authority.js +13 -5
  516. package/dist/ui/settings-wizard.js +13 -7
  517. package/dist/ui/shared/agend-auth.js +2 -0
  518. package/dist/ui/shared/app-i18n.js +12 -5
  519. package/dist/ui/shared/app-needs.js +2 -2
  520. package/dist/ui/shared/app-prefs.js +15 -0
  521. package/dist/ui/shared/app-route.js +18 -0
  522. package/dist/ui/shared/app-shell.js +189 -28
  523. package/dist/ui/shared/app-store.js +3 -2
  524. package/dist/ui/shared/app-stream.js +5 -1
  525. package/dist/ui/shared/app-themes.js +116 -1
  526. package/dist/ui/shared/app.css +668 -48
  527. package/dist/ui/shared/app.js +9 -1
  528. package/dist/ui/shared/card-strings.js +4 -0
  529. package/dist/ui/shared/effect-app-runtime.js +7 -5
  530. package/dist/ui/shared/effect-chat-runtime.js +2 -7
  531. package/dist/ui/shared/effect-cursor.js +3 -2
  532. package/dist/ui/shared/effect-display-metadata.js +39 -0
  533. package/dist/ui/shared/effect-owner-source.js +8 -1
  534. package/dist/ui/shared/effect-registry.js +14 -4
  535. package/dist/ui/shared/effect-runtime-loader.js +3 -1
  536. package/dist/ui/shared/effect-stock-trust.js +5 -5
  537. package/dist/ui/shared/feature-flags.js +127 -0
  538. package/dist/ui/shared/guest-refused.js +35 -0
  539. package/dist/ui/shared/header-tools.js +2 -2
  540. package/dist/ui/shared/identity-card.js +26 -8
  541. package/dist/ui/shared/identity-color.js +17 -10
  542. package/dist/ui/shared/instance-nav.js +183 -28
  543. package/dist/ui/shared/owner-css.js +30 -1
  544. package/dist/ui/shared/panel-view.js +14 -29
  545. package/dist/ui/shared/text-undo.js +210 -0
  546. package/dist/ui/shared/tokens.css +4 -0
  547. package/dist/ui/shared/ui-dialog.js +5 -4
  548. package/dist/ui/shared/ui-icons.js +4 -0
  549. package/dist/ui/shared/ui-menu.js +20 -7
  550. package/dist/ui/shared/ui-toast.js +16 -3
  551. package/dist/ui/shared/usage-dialog.js +216 -16
  552. package/dist/ui/shared/view-strings.js +70 -4
  553. package/dist/ui/shared/web-authorize.js +50 -1
  554. package/dist/ui/signin.html +2 -0
  555. package/dist/ui/web-terminal/terminal.html +10 -10
  556. package/dist/ui/web-terminal/terminal.js +58 -12
  557. package/dist/update-check.js.map +1 -1
  558. package/dist/update-dispatch.js +1 -1
  559. package/dist/update-dispatch.js.map +1 -1
  560. package/dist/update-install.js.map +1 -1
  561. package/dist/update-launch.d.ts +10 -1
  562. package/dist/update-launch.js +39 -14
  563. package/dist/update-launch.js.map +1 -1
  564. package/dist/update-marker.js.map +1 -1
  565. package/dist/update-preflight.js +1 -1
  566. package/dist/update-preflight.js.map +1 -1
  567. package/dist/update-progress.d.ts +3 -1
  568. package/dist/update-progress.js +3 -1
  569. package/dist/update-progress.js.map +1 -1
  570. package/dist/usage/format-rich.js +6 -3
  571. package/dist/usage/format-rich.js.map +1 -1
  572. package/dist/usage/i18n-keys.d.ts +1 -1
  573. package/dist/usage/i18n-keys.js +2 -1
  574. package/dist/usage/i18n-keys.js.map +1 -1
  575. package/dist/usage/i18n.d.ts +13 -1
  576. package/dist/usage/i18n.js +29 -0
  577. package/dist/usage/i18n.js.map +1 -1
  578. package/dist/usage/providers.d.ts +89 -2
  579. package/dist/usage/providers.js +247 -41
  580. package/dist/usage/providers.js.map +1 -1
  581. package/dist/usage/statusline-usage.js.map +1 -1
  582. package/dist/usage/usage-api.d.ts +5 -1
  583. package/dist/usage/usage-api.js +185 -58
  584. package/dist/usage/usage-api.js.map +1 -1
  585. package/dist/usage-report.js.map +1 -1
  586. package/dist/usage-rollup.js +1 -1
  587. package/dist/usage-rollup.js.map +1 -1
  588. package/dist/user-systemd.d.ts +24 -0
  589. package/dist/user-systemd.js +75 -0
  590. package/dist/user-systemd.js.map +1 -0
  591. package/dist/view-api.d.ts +7 -0
  592. package/dist/view-api.js +16 -4
  593. package/dist/view-api.js.map +1 -1
  594. package/dist/wake-coordinator.js.map +1 -1
  595. package/dist/web-ambient.js.map +1 -1
  596. package/dist/web-api.d.ts +58 -11
  597. package/dist/web-api.js +1104 -71
  598. package/dist/web-api.js.map +1 -1
  599. package/dist/web-app-icon-worker.d.ts +1 -0
  600. package/dist/web-app-icon-worker.js +16 -0
  601. package/dist/web-app-icon-worker.js.map +1 -0
  602. package/dist/web-app-icon.d.ts +109 -0
  603. package/dist/web-app-icon.js +257 -0
  604. package/dist/web-app-icon.js.map +1 -0
  605. package/dist/web-appearance-policy.js.map +1 -1
  606. package/dist/web-appearance-tools.d.ts +1 -0
  607. package/dist/web-appearance-tools.js +7 -3
  608. package/dist/web-appearance-tools.js.map +1 -1
  609. package/dist/web-appearance-view.js.map +1 -1
  610. package/dist/web-appearance.d.ts +4 -10
  611. package/dist/web-appearance.js +7 -4
  612. package/dist/web-appearance.js.map +1 -1
  613. package/dist/web-auth.d.ts +8 -4
  614. package/dist/web-auth.js +33 -21
  615. package/dist/web-auth.js.map +1 -1
  616. package/dist/web-backend-login.d.ts +130 -0
  617. package/dist/web-backend-login.js +225 -0
  618. package/dist/web-backend-login.js.map +1 -0
  619. package/dist/web-card.js.map +1 -1
  620. package/dist/web-channel-echo.js.map +1 -1
  621. package/dist/web-chat-history.d.ts +118 -8
  622. package/dist/web-chat-history.js +220 -25
  623. package/dist/web-chat-history.js.map +1 -1
  624. package/dist/web-commands.d.ts +5 -1
  625. package/dist/web-commands.js +1 -1
  626. package/dist/web-commands.js.map +1 -1
  627. package/dist/web-cookie-names.d.ts +5 -0
  628. package/dist/web-cookie-names.js +12 -0
  629. package/dist/web-cookie-names.js.map +1 -0
  630. package/dist/web-effect-bundle.d.ts +2 -2
  631. package/dist/web-effect-library.js.map +1 -1
  632. package/dist/web-effect-playback.js.map +1 -1
  633. package/dist/web-effect-profiles.d.ts +16 -16
  634. package/dist/web-effect-profiles.js +3 -2
  635. package/dist/web-effect-profiles.js.map +1 -1
  636. package/dist/web-effect-service.js.map +1 -1
  637. package/dist/web-effect-tools.js.map +1 -1
  638. package/dist/web-effect-trials.js.map +1 -1
  639. package/dist/web-effect-upload.js.map +1 -1
  640. package/dist/web-effect.d.ts +6 -6
  641. package/dist/web-effect.js.map +1 -1
  642. package/dist/web-experimental-http.d.ts +2 -0
  643. package/dist/web-experimental-http.js +12 -0
  644. package/dist/web-experimental-http.js.map +1 -0
  645. package/dist/web-experimental.d.ts +11 -0
  646. package/dist/web-experimental.js +49 -0
  647. package/dist/web-experimental.js.map +1 -0
  648. package/dist/web-guest-access.d.ts +4 -2
  649. package/dist/web-guest-access.js +22 -10
  650. package/dist/web-guest-access.js.map +1 -1
  651. package/dist/web-guest-rooms.js.map +1 -1
  652. package/dist/web-host-guard.d.ts +11 -0
  653. package/dist/web-host-guard.js +11 -0
  654. package/dist/web-host-guard.js.map +1 -1
  655. package/dist/web-icons.d.ts +33 -1
  656. package/dist/web-icons.js +83 -11
  657. package/dist/web-icons.js.map +1 -1
  658. package/dist/web-identity-color.js.map +1 -1
  659. package/dist/web-image-meta.d.ts +13 -0
  660. package/dist/web-image-meta.js +62 -0
  661. package/dist/web-image-meta.js.map +1 -1
  662. package/dist/web-images.js.map +1 -1
  663. package/dist/web-libavoid.d.ts +14 -0
  664. package/dist/web-libavoid.js +46 -0
  665. package/dist/web-libavoid.js.map +1 -0
  666. package/dist/web-login.js.map +1 -1
  667. package/dist/web-mentions.js.map +1 -1
  668. package/dist/web-mod-agent.js.map +1 -1
  669. package/dist/web-mod-store.d.ts +2 -0
  670. package/dist/web-mod-store.js +10 -0
  671. package/dist/web-mod-store.js.map +1 -1
  672. package/dist/web-office-store.d.ts +104 -0
  673. package/dist/web-office-store.js +236 -0
  674. package/dist/web-office-store.js.map +1 -0
  675. package/dist/web-owner-css-store.d.ts +44 -1
  676. package/dist/web-owner-css-store.js +70 -9
  677. package/dist/web-owner-css-store.js.map +1 -1
  678. package/dist/web-owner-css.js.map +1 -1
  679. package/dist/web-png.d.ts +33 -0
  680. package/dist/web-png.js +329 -0
  681. package/dist/web-png.js.map +1 -0
  682. package/dist/web-prefs.d.ts +6 -0
  683. package/dist/web-prefs.js +29 -3
  684. package/dist/web-prefs.js.map +1 -1
  685. package/dist/web-preview.d.ts +2 -0
  686. package/dist/web-preview.js +2 -0
  687. package/dist/web-preview.js.map +1 -1
  688. package/dist/web-profile.js.map +1 -1
  689. package/dist/web-reactions.d.ts +35 -0
  690. package/dist/web-reactions.js +47 -0
  691. package/dist/web-reactions.js.map +1 -0
  692. package/dist/web-request-context.d.ts +5 -0
  693. package/dist/web-request-context.js.map +1 -1
  694. package/dist/web-returning-device.d.ts +4 -2
  695. package/dist/web-returning-device.js +13 -6
  696. package/dist/web-returning-device.js.map +1 -1
  697. package/dist/web-schedules.js.map +1 -1
  698. package/dist/web-send-dedupe.d.ts +36 -0
  699. package/dist/web-send-dedupe.js +69 -0
  700. package/dist/web-send-dedupe.js.map +1 -0
  701. package/dist/web-session-authorization.js.map +1 -1
  702. package/dist/web-session.js +1 -1
  703. package/dist/web-session.js.map +1 -1
  704. package/dist/web-settings-patch.d.ts +3 -1
  705. package/dist/web-settings-patch.js +11 -5
  706. package/dist/web-settings-patch.js.map +1 -1
  707. package/dist/web-settings-tools.d.ts +6 -0
  708. package/dist/web-settings-tools.js +52 -17
  709. package/dist/web-settings-tools.js.map +1 -1
  710. package/dist/web-terminal-http.js +15 -2
  711. package/dist/web-terminal-http.js.map +1 -1
  712. package/dist/web-terminal.js +1 -1
  713. package/dist/web-terminal.js.map +1 -1
  714. package/dist/web-theme-store.d.ts +2 -2
  715. package/dist/web-theme-store.js +6 -3
  716. package/dist/web-theme-store.js.map +1 -1
  717. package/dist/web-theme.d.ts +29 -0
  718. package/dist/web-theme.js +89 -1
  719. package/dist/web-theme.js.map +1 -1
  720. package/dist/web-upload.d.ts +18 -1
  721. package/dist/web-upload.js +45 -7
  722. package/dist/web-upload.js.map +1 -1
  723. package/dist/web-widget.js.map +1 -1
  724. package/dist/webhook-emitter.js.map +1 -1
  725. package/dist/windows-shortcuts.d.ts +78 -0
  726. package/dist/windows-shortcuts.js +203 -0
  727. package/dist/windows-shortcuts.js.map +1 -0
  728. package/dist/workflow-templates/default.md +3 -0
  729. package/dist/working-state-journal.d.ts +68 -0
  730. package/dist/working-state-journal.js +93 -0
  731. package/dist/working-state-journal.js.map +1 -0
  732. package/dist/ws-server.js.map +1 -1
  733. package/dist/yaml-shared-nodes.d.ts +25 -0
  734. package/dist/yaml-shared-nodes.js +185 -0
  735. package/dist/yaml-shared-nodes.js.map +1 -0
  736. package/launcher/agend-install.cjs +828 -0
  737. package/launcher/install-admission.cjs +4 -39
  738. package/launcher/postinstall.cjs +14 -12
  739. package/launcher/private-home.cjs +43 -0
  740. package/package.json +16 -13
  741. package/scripts/preinstall-guard.cjs +2 -5
  742. package/templates/windows-shortcuts.ps1 +181 -0
  743. package/launcher/old-updater-note.cjs +0 -48
@@ -9,7 +9,7 @@ import { html, useEffect, useState } from "./app-html.js";
9
9
  import { createStore, useStore } from "./app-store.js";
10
10
  import { onPrefsChanged, prefInForce, savePref } from "./app-prefs.js";
11
11
  import { register, t } from "./app-i18n.js";
12
- import { themesShown, themesStore } from "./app-themes.js";
12
+ import { pageNow, themesShown, themesStore } from "./app-themes.js";
13
13
  import { Dialog } from "./ui-dialog.js";
14
14
  import { toast } from "./ui-toast.js";
15
15
 
@@ -63,16 +63,20 @@ export function contrast(a, b) {
63
63
  const [l1, l2] = [luminance(a), luminance(b)].sort((x, y) => y - x);
64
64
  return (l1 + 0.05) / (l2 + 0.05);
65
65
  }
66
- /** The shade of `hex` name text uses: itself if readable, else the nearest step toward black (light) or white (dark). */
67
- export function readableShade(hex, mode) {
68
- if (!HEX.test(hex)) return TEXT[mode];
66
+ /**
67
+ * The shade of `hex` name text uses: itself if readable, else the nearest step toward black (light) or white (dark).
68
+ * `page` (#2196): an agent's colours on the page now — names must read on its background and sidebar instead.
69
+ */
70
+ export function readableShade(hex, mode, page = null) {
71
+ const backgrounds = page ? [page.bg, page.sidebar] : BG[mode], text = page ? page.text : TEXT[mode];
72
+ if (!HEX.test(hex)) return text;
69
73
  const toward = mode === "light" ? 0 : 255;
70
74
  const ch = [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16));
71
75
  for (let step = 0; step <= 20; step++) {
72
76
  const shade = `#${ch.map((c) => Math.round(c + (toward - c) * (step / 20)).toString(16).padStart(2, "0")).join("")}`;
73
- if (BG[mode].every((bg) => contrast(shade, bg) >= 4.5)) return shade;
77
+ if (backgrounds.every((bg) => contrast(shade, bg) >= 4.5)) return shade;
74
78
  }
75
- return TEXT[mode];
79
+ return text;
76
80
  }
77
81
 
78
82
  // ── Resolution ──
@@ -135,7 +139,7 @@ export function identityOf(it, opts = {}) {
135
139
  const light = colorValue(c.color, "light"), dark = colorValue(c.color, "dark");
136
140
  return {
137
141
  color: c.color, source: c.source, show: p.show, showSource: p.source, off: opts.off ?? colorsOff(),
138
- light, dark, nameLight: readableShade(light, "light"), nameDark: readableShade(dark, "dark"),
142
+ light, dark, nameLight: readableShade(light, "light", pageNow("light")), nameDark: readableShade(dark, "dark", pageNow("dark")),
139
143
  };
140
144
  }
141
145
  /** The row's classes for its identity colour ("" when colours are off or shown nowhere). */
@@ -192,6 +196,8 @@ function ColorDialog({ name, it, onClose }) {
192
196
  }, [name]);
193
197
  // The preview: the instance as it would be drawn with the choice here.
194
198
  const preview = identityOf({ ...(it || {}), color: { ...((it && it.color) || {}), person: color || undefined }, color_show: { ...((it && it.color_show) || {}), person: places || undefined } }, { off: false });
199
+ // #2342: a swatch (or "no pick") is a choice, not text: it starts the hex field's undo history afresh (undoing the
200
+ // text back to a typed colour would leave the box and the chosen colour apart — Save would send the other one).
195
201
  const pick = (c) => { setColor(c); setHex(c && HEX.test(c) ? c : ""); setError(""); };
196
202
  const togglePlace = (p) => {
197
203
  const base = places || preview.show;
@@ -207,7 +213,8 @@ function ColorDialog({ name, it, onClose }) {
207
213
  onClose();
208
214
  } catch (err) { setError(t("color.failed", (err && err.message) || "network")); setBusy(false); }
209
215
  }
210
- const onHex = (v) => { setHex(v); const c = /^#[0-9a-fA-F]{6}$/.test(v) ? v.toLowerCase() : null; if (c) { setColor(c); setError(""); } };
216
+ // The text as typed (#2342: trimming it in the box would rewrite it, losing its undo); spaces around it are ignored.
217
+ const onHex = (v) => { setHex(v); const w = v.trim(), c = /^#[0-9a-fA-F]{6}$/.test(w) ? w.toLowerCase() : null; if (c) { setColor(c); setError(""); } };
211
218
  const off = colorsOff();
212
219
  return html`<${Dialog} title=${t("color.title", name)} onClose=${onClose} busy=${busy}
213
220
  actions=${html`<button type="button" class="btn" disabled=${busy || !loaded} onClick=${() => save({ color: null, color_show: null })}>${t("color.reset")}</button>
@@ -222,8 +229,8 @@ function ColorDialog({ name, it, onClose }) {
222
229
  ref=${(el) => { if (el && el.style) { el.style.setProperty("--sw-light", p.light); el.style.setProperty("--sw-dark", p.dark); } }}
223
230
  onClick=${() => pick(`preset:${p.id}`)}></button>`)}
224
231
  <label class="idc-custom">${t("color.custom")}
225
- <input type="color" value=${HEX.test(hex) ? hex : "#808080"} onInput=${(e) => onHex(e.currentTarget.value)} aria-label=${t("color.custom")} />
226
- <input type="text" class="idc-hex" inputmode="text" spellcheck=${false} maxlength="7" placeholder="#rrggbb" value=${hex} onInput=${(e) => onHex(e.currentTarget.value.trim())} aria-label=${t("color.hex")} /></label>
232
+ <input type="color" value=${HEX.test(hex.trim()) ? hex.trim() : "#808080"} onInput=${(e) => onHex(e.currentTarget.value)} aria-label=${t("color.custom")} />
233
+ <input type="text" class="idc-hex" inputmode="text" spellcheck=${false} maxlength="7" placeholder="#rrggbb" value=${hex} onInput=${(e) => onHex(e.currentTarget.value)} aria-label=${t("color.hex")} /></label>
227
234
  <button type="button" class="btn btn-sm" disabled=${!color} onClick=${() => pick(null)}>${t("color.noPick")}</button>
228
235
  </fieldset>
229
236
  <fieldset class="idc-places"><legend>${t("color.places")}</legend>
@@ -9,6 +9,7 @@
9
9
  // Public (/assets/): an anonymous View reader gets the same list, fed by View's roster read instead of status frames.
10
10
  import { html, useLayoutEffect, useRef, useState } from "./app-html.js";
11
11
  import { t } from "./app-i18n.js";
12
+ import { recordEdit } from "./text-undo.js";
12
13
  import { appStore, createStore, guestPage, useStore } from "./app-store.js";
13
14
  import { chatPath, detailsPath, logPath, viewPath } from "./app-route.js";
14
15
  import { Icon } from "./ui-icons.js";
@@ -30,14 +31,38 @@ const stored = (k) => { try { return localStorage.getItem(k); } catch { return n
30
31
  const nameOf = (it) => (it && (it.name != null ? it.name : it.instance_name)) || "";
31
32
 
32
33
  // ── Groups, labels, the filter ──
34
+ /** A team's group key: no tag can be it (a group name is never a control character), so a tag named like a team stays apart. */
35
+ export const TEAM_GROUP = "\u0001team:";
36
+ /**
37
+ * #2172: an agent in a team (fleet.yaml `teams.<name>.members`; `teams` on a status or roster row, in fleet.yaml's
38
+ * order) sits under its first team, whatever its tags say; one in no team under its first tag; a ClassicBot room under
39
+ * "Classic"; the rest under "Other".
40
+ */
33
41
  export function groupOf(it) {
34
42
  const tags = Array.isArray(it.tags) ? it.tags : [];
35
43
  if (tags.includes("classic")) return "Classic";
44
+ const teams = Array.isArray(it.teams) ? it.teams.filter((x) => typeof x === "string" && x) : [];
45
+ if (teams.length) return TEAM_GROUP + teams[0];
36
46
  if (tags.length) return tags[0];
37
47
  return "Other";
38
48
  }
39
- /** A group's heading: the fallback group ("Other") is a key, shown in the page's language; a tag is shown as it is. */
40
- export function groupLabel(g) { return g === "Other" ? tn("groupOther") : g; }
49
+ /** "team" for a team's group, "tag" for a tag's; null for Classic, Other and Favorites. */
50
+ export function groupKind(g) {
51
+ if (typeof g !== "string") return null;
52
+ if (g.startsWith(TEAM_GROUP)) return "team";
53
+ return g === "Other" || g === "Classic" || g.charCodeAt(0) < 32 ? null : "tag";
54
+ }
55
+ /** A group's heading: a team's name; the fallback group ("Other") in the page's language; a tag as it is. */
56
+ export function groupLabel(g) { return g === "Other" ? tn("groupOther") : groupKind(g) === "team" ? g.slice(TEAM_GROUP.length) : g; }
57
+ /** #2172: what a group is, said on its heading — a team's icon, or "#" before a tag — so the grouping can be read. */
58
+ export function GroupMark({ group }) {
59
+ const kind = groupKind(group);
60
+ if (!kind) return null;
61
+ const what = tn(kind === "team" ? "groupTeam" : "groupTag");
62
+ return kind === "team"
63
+ ? html`<span class="v-group-kind team" title=${what}><${Icon} name="team" size=${13} /><span class="sr-only">${what}: </span></span>`
64
+ : html`<span class="v-group-kind tag" title=${what} aria-hidden="true">#</span>`;
65
+ }
41
66
  /**
42
67
  * #2050: the model as a person reads it. The server's resolver writes English for a model it has not resolved yet
43
68
  * ("default (detected when it starts)") and for a CLI's own default ("x (default)"); those are shown in the page's language.
@@ -57,11 +82,12 @@ export function backendLabel(backend) { return BACKEND_LABELS[backend] || backen
57
82
  export const KNOWN_BACKENDS = new Set(Object.keys(BACKEND_LABELS));
58
83
  export const STATUS_KEYS = { running: "statusRunning", paused: "statusPaused", stopped: "statusStopped", crashed: "statusCrashed" };
59
84
 
60
- /** The status chips, in order (design Q5): what the row's dot says, in five words. */
61
- export const STATUS_FACETS = ["working", "needs", "idle", "stopped", "crashed"];
85
+ /** The status chips, in order: what the row's dot says, including agents queued for this boot. */
86
+ export const STATUS_FACETS = ["working", "needs", "starting", "idle", "stopped", "crashed"];
62
87
  /** Which status chip a row falls under: needs you first, then crashed, not running, working (or stuck), idle. */
63
88
  export function statusFacet(it, exec, awaiting) {
64
89
  if (awaiting != null) return "needs";
90
+ if (it?.startup_phase === "queued" || it?.startup_phase === "starting") return "starting";
65
91
  if (!it || it.status === "crashed") return "crashed";
66
92
  if (it.status !== "running") return "stopped";
67
93
  return exec === "working" || exec === "stuck" ? "working" : "idle";
@@ -102,7 +128,8 @@ export function filterSidebar(groupNames, instByGroup, rosterByName, query, labe
102
128
  }
103
129
  export function instanceTooltip(it) {
104
130
  const context = it.context_pct != null && Number.isFinite(it.context_pct) ? `${Math.round(Math.min(100, Math.max(0, it.context_pct)))}%` : tn("tooltipUnavailable");
105
- const status = STATUS_KEYS[it.status] ? tn(STATUS_KEYS[it.status]) : it.status || tn("statusUnknown");
131
+ const status = it.startup_phase === "queued" ? t("app.statusQueued") : it.startup_phase === "starting" ? t("app.statusStarting")
132
+ : STATUS_KEYS[it.status] ? tn(STATUS_KEYS[it.status]) : it.status || tn("statusUnknown");
106
133
  const line = (key, value) => tn(key, { value });
107
134
  const lines = [];
108
135
  const name = nameOf(it);
@@ -124,6 +151,7 @@ const SIDEBAR_ORDER_KEY = "agend_view_sidebar_order"; // View's key since #999
124
151
  const COLLAPSED_KEY = "agend_instance_collapsed";
125
152
  const FILTER_KEY = "agend_instance_filter";
126
153
  const FAVORITES_KEY = "agend_favorites"; // #1664
154
+ const SORT_KEY = "agend_nav_sort"; // #2179
127
155
  /**
128
156
  * A synced sidebar preference, as the JSON its reader parses: the value in force in memory (app-prefs.js — this
129
157
  * page's own change, else the host's; a full or blocked storage never brings an old one back), else this browser's copy.
@@ -154,6 +182,13 @@ function readFilter() {
154
182
  return { q: f && typeof f.q === "string" ? f.q : "", status: strings(f && f.status).filter((s) => STATUS_FACETS.includes(s)), cli: strings(f && f.cli) };
155
183
  } catch { return { q: "", status: [], cli: [] }; }
156
184
  }
185
+ /** #2179: how the list sorts — { mode: "custom" (the person's order; the default) | "recent", favs: "fixed" | "recent" }. */
186
+ function readSort() {
187
+ try {
188
+ const s = JSON.parse(inForce("nav.sort", SORT_KEY) || "{}");
189
+ return { mode: s && s.mode === "recent" ? "recent" : "custom", favs: s && s.favs === "recent" ? "recent" : "fixed" };
190
+ } catch { return { mode: "custom", favs: "fixed" }; }
191
+ }
157
192
  function readFavorites() {
158
193
  try { const a = JSON.parse(inForce("nav.favorites", FAVORITES_KEY) || "[]"); return Array.isArray(a) ? [...new Set(a.filter((x) => typeof x === "string"))] : []; } catch { return []; }
159
194
  }
@@ -167,7 +202,9 @@ export function computeOrder(roster, order) {
167
202
  if (ia !== ib) return ia - ib;
168
203
  if (a === "Other") return 1;
169
204
  if (b === "Other") return -1;
170
- return a.localeCompare(b);
205
+ const ta = groupKind(a) === "team", tb = groupKind(b) === "team";
206
+ if (ta !== tb) return ta ? -1 : 1; // #2172: teams first, then tags
207
+ return groupLabel(a).localeCompare(groupLabel(b));
171
208
  });
172
209
  const instByGroup = new Map();
173
210
  for (const [g, items] of groups) {
@@ -190,12 +227,18 @@ function moveIn(arr, moving, target, before) {
190
227
  }
191
228
 
192
229
  /** The list's state for the page's life: this browser's order, folded groups and filter (each also kept in storage). */
193
- export const navStore = createStore({ order: readOrder(), collapsed: readCollapsed(), filter: readFilter(), favorites: readFavorites() });
230
+ export const navStore = createStore({ order: readOrder(), collapsed: readCollapsed(), filter: readFilter(), favorites: readFavorites(), sort: readSort() });
194
231
  /** Read them again from this browser's storage — what a page load does (the store above is made that way). */
195
232
  export function loadNavPrefs() {
196
- navStore.set({ order: readOrder(), collapsed: readCollapsed(), filter: readFilter(), favorites: readFavorites() });
233
+ navStore.set({ order: readOrder(), collapsed: readCollapsed(), filter: readFilter(), favorites: readFavorites(), sort: readSort() });
197
234
  namesStore.set({ primary: readNamesPrimary() });
198
235
  }
236
+ /** #2179: change how the list sorts ({ mode?, favs? }): the same on every page and device. */
237
+ export function setNavSort(patch) {
238
+ const sort = { ...navStore.get().sort, ...patch };
239
+ navStore.set({ sort });
240
+ savePref("nav.sort", { mode: sort.mode, favs: sort.favs });
241
+ }
199
242
  /** Change the filter ({ q?, status?, cli? }): the same on every page, and kept by this browser. */
200
243
  export function setNavFilter(patch) {
201
244
  const filter = { ...navStore.get().filter, ...patch };
@@ -204,7 +247,7 @@ export function setNavFilter(patch) {
204
247
  }
205
248
  function setCollapsed(collapsed) { navStore.set({ collapsed }); savePref("nav.collapsed", [...collapsed]); }
206
249
  // Another device rearranged, folded or filtered: this page's list follows (its cache is already updated).
207
- onPrefsChanged((keys) => { if (keys.has("nav.order") || keys.has("nav.collapsed") || keys.has("nav.filter") || keys.has("nav.favorites")) loadNavPrefs(); });
250
+ onPrefsChanged((keys) => { if (keys.has("nav.order") || keys.has("nav.collapsed") || keys.has("nav.filter") || keys.has("nav.favorites") || keys.has("nav.sort")) loadNavPrefs(); });
208
251
 
209
252
  // ── #1664: favorites — starred instances, pinned at the top in the order they were starred (or moved) ──
210
253
  export const MAX_FAVORITES = 50;
@@ -320,12 +363,15 @@ export function avatarOf(it) {
320
363
  /** The status dot on the avatar: needs you, crashed, paused, stopped, looks stuck, working, idle — colour AND a label. */
321
364
  export function dotState(it, exec, awaiting) {
322
365
  if (awaiting != null) return "needs";
366
+ if (it?.startup_phase === "queued") return "queued";
367
+ if (it?.startup_phase === "starting") return "starting";
323
368
  if (!it || it.status === "crashed") return "crashed";
324
369
  if (it.status === "paused") return "paused";
325
370
  if (it.status !== "running") return "stopped";
326
371
  return exec === "stuck" ? "stuck" : exec === "working" ? "working" : "idle";
327
372
  }
328
373
  const DOT = {
374
+ queued: ["off", () => t("app.statusQueued")], starting: ["busy", () => t("app.statusStarting")],
329
375
  needs: ["warn", () => t("app.needsYou")], crashed: ["bad", () => t("app.statusCrashed")], paused: ["off hollow", () => t("app.statusPaused")],
330
376
  stopped: ["off", () => t("app.statusStopped")], stuck: ["warn", () => t("app.statusStuck")], working: ["busy", () => t("app.statusWorking")],
331
377
  idle: ["ok", () => t("app.statusIdle")],
@@ -352,9 +398,74 @@ export function InstanceAvatar({ it, state, cli = false }) {
352
398
 
353
399
  // ── The list ──
354
400
 
355
- const statusText = (facet) => (facet === "needs" ? t("app.needsYou") : facet === "working" ? t("app.statusWorking") : facet === "idle" ? t("app.statusIdle")
401
+ const statusText = (facet) => (facet === "needs" ? t("app.needsYou") : facet === "starting" ? t("app.statusStarting") : facet === "working" ? t("app.statusWorking") : facet === "idle" ? t("app.statusIdle")
356
402
  : facet === "crashed" ? t("app.statusCrashed") : t("app.statusStopped"));
357
403
 
404
+ // ── #2179: when each instance last replied to a person (the status frame's last_reply) ──
405
+ /** A reply this recent (minutes) is highlighted; the highlight fades to nothing over this time. */
406
+ export const REPLY_FRESH_MIN = 10;
407
+ /** Minutes since the instance last replied to a person, or null (none kept, or no time on this row). */
408
+ export function replyAge(it, now = Date.now()) {
409
+ const at = it && typeof it.last_reply === "string" ? Date.parse(it.last_reply) : NaN;
410
+ return Number.isFinite(at) ? Math.max(0, (now - at) / 60000) : null;
411
+ }
412
+ /** "just now", "2 min ago", "3 h ago", "yesterday", "4 d ago" — in the page's language. */
413
+ export function replyText(mins) {
414
+ if (mins < 1) return tn("replyJustNow");
415
+ if (mins < 60) return tn("replyMinutes", { n: Math.floor(mins) });
416
+ if (mins < 1440) return tn("replyHours", { n: Math.floor(mins / 60) });
417
+ if (mins < 2880) return tn("replyYesterday");
418
+ return tn("replyDays", { n: Math.floor(mins / 1440) });
419
+ }
420
+ /** How strong the highlight is: 1 just after a reply, down to 0 at REPLY_FRESH_MIN minutes. */
421
+ export function replyFade(mins) { return mins == null || mins >= REPLY_FRESH_MIN ? 0 : 1 - mins / REPLY_FRESH_MIN; }
422
+ /** The highlight's strength on the row, through the CSSOM (style-src has no 'unsafe-inline'). */
423
+ function paintReply(el, fade) {
424
+ if (!el) return;
425
+ if (fade > 0) el.style.setProperty("--reply-a", fade.toFixed(3)); else el.style.removeProperty("--reply-a");
426
+ }
427
+ /** Most recent reply first; rows with none keep their order after them (a stable sort). */
428
+ function byRecentReply(age) {
429
+ return (a, b) => {
430
+ const x = age(a), y = age(b);
431
+ if (x == null || y == null) return x == null ? (y == null ? 0 : 1) : -1;
432
+ return x - y;
433
+ };
434
+ }
435
+ /** The group that holds every non-favorite row in "recent" mode: no tag or team can be it, and it is never saved. */
436
+ const RECENT_GROUP = "\u0000recent";
437
+
438
+ /** #2179: the sort button and its menu — how the list sorts, said on the button. */
439
+ function SortMenu({ sort }) {
440
+ const [open, setOpen] = useState(false);
441
+ const box = useRef(null);
442
+ useLayoutEffect(() => {
443
+ if (!open) return undefined;
444
+ const doc = box.current && box.current.ownerDocument;
445
+ if (!doc) return undefined;
446
+ const away = (e) => { if (box.current && !box.current.contains(e.target)) setOpen(false); };
447
+ const esc = (e) => { if (e.key === "Escape") { e.preventDefault(); setOpen(false); } };
448
+ doc.addEventListener("pointerdown", away, true);
449
+ doc.addEventListener("keydown", esc, true);
450
+ return () => { doc.removeEventListener("pointerdown", away, true); doc.removeEventListener("keydown", esc, true); };
451
+ }, [open]);
452
+ const recent = sort.mode === "recent";
453
+ const radio = (group, value, label, hint) => html`<label class="nav-sort-opt"><input type="radio" name=${`nav-sort-${group}`} value=${value}
454
+ checked=${sort[group] === value} onChange=${() => setNavSort({ [group]: value })} /><span>${label}${hint ? html`<span class="nav-sort-hint">${hint}</span>` : null}</span></label>`;
455
+ return html`<div class="nav-sort" ref=${box}>
456
+ <button type="button" class=${`chip-btn nav-sort-btn${recent ? " on" : ""}`} aria-haspopup="dialog" aria-expanded=${open ? "true" : "false"}
457
+ title=${tn("sortTitle")} onClick=${() => setOpen(!open)}><${Icon} name="sort" size=${12} /><span>${tn(recent ? "sortRecent" : "sortCustom")}</span></button>
458
+ ${open ? html`<div class="nav-sort-menu" role="dialog" aria-label=${tn("sortTitle")}>
459
+ <fieldset><legend>${tn("sortTitle")}</legend>
460
+ ${radio("mode", "custom", tn("sortCustom"), tn("sortCustomHint"))}
461
+ ${radio("mode", "recent", tn("sortRecent"), tn("sortRecentHint"))}</fieldset>
462
+ <fieldset><legend>${tn("sortFavs")}</legend>
463
+ ${radio("favs", "fixed", tn("sortFavsFixed"), "")}
464
+ ${radio("favs", "recent", tn("sortFavsRecent"), "")}</fieldset>
465
+ </div>` : null}
466
+ </div>`;
467
+ }
468
+
358
469
  /**
359
470
  * The sidebar's instance list. `route`: the page's (which instance is open, and which view a row opens). `items`: the
360
471
  * rows (status frames' instances, or View's roster for an anonymous reader); `loaded` false shows a skeleton.
@@ -368,6 +479,9 @@ export function InstanceNav({ route, items, loaded = true, exec = {}, awaiting =
368
479
  const filterRef = useRef(null);
369
480
  const list = useRef(null);
370
481
  const drag = useRef(null);
482
+ // #2179: the reply times and their highlight move on by themselves — the list is drawn again every 30 s.
483
+ const [, setTick] = useState(0);
484
+ useLayoutEffect(() => { const id = setInterval(() => setTick((n) => n + 1), 30000); return () => clearInterval(id); }, []);
371
485
  // #1700: which facet groups are open — this device's (screen layout, not a synced preference), read when the list mounts.
372
486
  const [facetOpen, setFacetOpen] = useState(readFacetsOpen);
373
487
  const toggleFacet = (key) => setFacetOpen((cur) => { const next = { ...cur, [key]: !cur[key] }; writeFacetsOpen(next); return next; });
@@ -394,9 +508,26 @@ export function InstanceNav({ route, items, loaded = true, exec = {}, awaiting =
394
508
  // #1920: a guest's list is to pick from — no reordering and no filters (one this device kept would hide rows with no
395
509
  // way to clear it).
396
510
  const guest = guestPage();
397
- const shownGroups = favs.length ? [FAV_GROUP, ...groupNames] : groupNames;
398
- const shownInsts = new Map([...instByGroup].map(([g, names]) => [g, names.filter((n) => !favSet.has(n))]));
399
- if (favs.length) shownInsts.set(FAV_GROUP, favs);
511
+ // #2179: when each row last replied (a guest's list has no times). "recent" mode (never a guest's): the favorites keep
512
+ // their order or are sorted too, and every other row is one list, the last reply first. "custom" (the default) is the
513
+ // person's own order — a reply never moves a row.
514
+ const now = Date.now();
515
+ // The anonymous View reader (viewOnly) has no reply times (/api/profiles carries none) and no way to change the sort:
516
+ // its list is always Custom, whatever this browser kept from a signed-in page (#2181 review).
517
+ const ageOf = (n) => (guest || viewOnly ? null : replyAge(byName.get(n), now));
518
+ const recent = !guest && !viewOnly && nav.sort.mode === "recent";
519
+ const favsSorted = recent && nav.sort.favs === "recent";
520
+ let shownGroups, shownInsts;
521
+ if (recent) {
522
+ const sortRecent = byRecentReply(ageOf);
523
+ const rest = groupNames.flatMap((g) => instByGroup.get(g) || []).filter((n) => !favSet.has(n)).sort(sortRecent);
524
+ shownGroups = [...(favs.length ? [FAV_GROUP] : []), ...(rest.length ? [RECENT_GROUP] : [])];
525
+ shownInsts = new Map([[FAV_GROUP, favsSorted ? [...favs].sort(sortRecent) : favs], [RECENT_GROUP, rest]]);
526
+ } else {
527
+ shownGroups = favs.length ? [FAV_GROUP, ...groupNames] : groupNames;
528
+ shownInsts = new Map([...instByGroup].map(([g, names]) => [g, names.filter((n) => !favSet.has(n))]));
529
+ if (favs.length) shownInsts.set(FAV_GROUP, favs);
530
+ }
400
531
  const f = guest ? { q: "", status: [], cli: [] } : nav.filter;
401
532
  const shown = filterSidebar(shownGroups, shownInsts, byName, f.q, backendLabel, { status: f.status, cli: f.cli, statusOf: facetOf });
402
533
  // #1936: while a filter is on, the instance you have open stays on top, pinned and marked "Current", whether or not
@@ -411,7 +542,7 @@ export function InstanceNav({ route, items, loaded = true, exec = {}, awaiting =
411
542
  const flip = (key, value) => { const cur = new Set(navStore.get().filter[key]); if (cur.has(value)) cur.delete(value); else cur.add(value); setNavFilter({ [key]: [...cur] }); };
412
543
  // Drag to reorder: only the full list (a filtered subset would be ambiguous), groups among groups, instances within
413
544
  // their group.
414
- const dragProps = (item) => shown.active || guest ? {} : {
545
+ const dragProps = (item) => shown.active || guest || recent ? {} : { // #2179: not while sorted by reply
415
546
  draggable: true,
416
547
  onDragStart: (e) => { drag.current = item; e.currentTarget.classList.add("dragging"); try { e.dataTransfer.effectAllowed = "move"; e.dataTransfer.setData("text/plain", item.name); } catch { /* Firefox needs data */ } },
417
548
  onDragEnd: (e) => { e.currentTarget.classList.remove("dragging"); drag.current = null; },
@@ -445,7 +576,7 @@ export function InstanceNav({ route, items, loaded = true, exec = {}, awaiting =
445
576
  const sel = f[key];
446
577
  const summary = !open && sel.length ? (sel.length <= 2 ? sel.map(text).join(", ") : tn("facetSelected", { n: sel.length })) : "";
447
578
  const hint = tn(open ? "facetHide" : "facetShow", { group: label });
448
- return html`<div class=${`nav-facet${open ? " open" : ""}`}>
579
+ return html`<div class=${`nav-facet${open ? " open" : ""}${summary ? " has-sum" : ""}`}>
449
580
  <button type="button" class="nav-facet-head" aria-expanded=${open ? "true" : "false"} aria-controls=${`nav-facet-${key}`}
450
581
  title=${summary ? `${hint} (${label} · ${summary})` : hint} onClick=${() => toggleFacet(key)}>
451
582
  <${Icon} name="chevron" size=${12} cls=${open ? "caret" : "caret folded"} /><span class="nav-facet-label">${label}</span>
@@ -466,26 +597,37 @@ export function InstanceNav({ route, items, loaded = true, exec = {}, awaiting =
466
597
  // #1958: its identity colour — the ring, a left bar and/or the name, as resolved; painted through the CSSOM.
467
598
  const id = identityOf(it);
468
599
  const full = !starred && favs.length >= MAX_FAVORITES;
469
- return html`<li key=${name} class="v-li"><a class=${`inst v-inst${on ? " active" : ""} ${identityClasses(id)}`} ref=${(el) => paintIdentity(el, id)} href=${rowPath(name)} title=${instanceTooltip(it)}
600
+ // #2179 (B5, the user's pick): the % (and the ★ beside the row) on the name's line; the time of the last reply at the
601
+ // end of the second line, highlighted for REPLY_FRESH_MIN minutes, fading by itself (a reply under a minute pulses once).
602
+ const mins = ageOf(name), fade = replyFade(mins);
603
+ const two = !!secondary || mins != null;
604
+ return html`<li key=${name} class=${`v-li${two ? " two-line" : ""}`}><a class=${`inst v-inst${on ? " active" : ""}${fade > 0 ? " replied" : ""}${mins != null && mins < 1 ? " just" : ""} ${identityClasses(id)}`}
605
+ ref=${(el) => { paintIdentity(el, id); paintReply(el, fade); }} href=${rowPath(name)} title=${instanceTooltip(it)}
470
606
  aria-current=${on ? "page" : undefined} onClick=${onPick} ...${fav ? {} : dragProps({ type: "instance", name, group: g })}>
471
607
  <${InstanceAvatar} it=${it} state=${dotState(it, own(exec, name) ? exec[name] : null, waiting)} cli=${true} />
472
- <span class="inst-text"><span class="inst-name" title=${label}>${label}</span>
473
- ${secondary ? html`<span class="inst-alt" title=${secondary}>${secondary}</span>` : null}
608
+ <span class="inst-text"><span class="inst-line"><span class="inst-name" title=${label}>${label}</span>
609
+ <span class="v-meta">${it.context_pct != null ? `${Math.round(it.context_pct)}%` : ""}</span></span>
610
+ ${two ? html`<span class="inst-line inst-line2"><span class="inst-alt" title=${secondary || undefined}>${secondary || ""}</span>
611
+ ${mins != null ? html`<span class=${`inst-when${fade > 0 ? " fresh" : ""}`} title=${tn("replyTitle", { time: new Date(Date.parse(it.last_reply)).toLocaleString() })}>${replyText(mins)}</span>` : null}</span>` : null}
474
612
  ${facet === "needs" ? html`<span class="inst-sub"><span class="badge-await" title=${waiting || t("app.approxNote")}>${t("app.needsYou")}</span></span>` : null}
475
613
  <span class="sr-only">${tn("tooltipBackend", { value: backendLabel(it.backend) })}</span></span>
476
- <span class="v-meta">${it.context_pct != null ? `${Math.round(it.context_pct)}%` : ""}</span>
477
614
  </a>${fav && editing && canStar ? html`<button type="button" class="icon-btn inst-move" aria-label=${tn("favUp", { name: label })}
478
- title=${shown.active ? tn("favFiltered") : tn("favUp", { name: label })} disabled=${shown.active || i === 0} onClick=${() => { if (!shown.active) moveFavorite(name, -1, known); }}><${Icon} name="up" size=${14} /></button><button type="button" class="icon-btn inst-move"
479
- aria-label=${tn("favDown", { name: label })} title=${shown.active ? tn("favFiltered") : tn("favDown", { name: label })} disabled=${shown.active || i === count - 1}
480
- onClick=${() => { if (!shown.active) moveFavorite(name, 1, known); }}><${Icon} name="down" size=${14} /></button>` : null}${canStar
615
+ title=${shown.active ? tn("favFiltered") : favsSorted ? tn("favSorted") : tn("favUp", { name: label })} disabled=${shown.active || favsSorted || i === 0} onClick=${() => { if (!shown.active && !favsSorted) moveFavorite(name, -1, known); }}><${Icon} name="up" size=${14} /></button><button type="button" class="icon-btn inst-move"
616
+ aria-label=${tn("favDown", { name: label })} title=${shown.active ? tn("favFiltered") : favsSorted ? tn("favSorted") : tn("favDown", { name: label })} disabled=${shown.active || favsSorted || i === count - 1}
617
+ onClick=${() => { if (!shown.active && !favsSorted) moveFavorite(name, 1, known); }}><${Icon} name="down" size=${14} /></button>` : null}${canStar
481
618
  ? html`<button type="button" class=${`icon-btn inst-star${starred ? " on" : ""}`} aria-pressed=${starred ? "true" : "false"} disabled=${full}
482
619
  aria-label=${tn(starred ? "favRemove" : "favAdd", { name: label })} title=${full ? tn("favFull", { max: MAX_FAVORITES }) : tn(starred ? "favRemove" : "favAdd", { name: label })}
483
620
  onClick=${() => toggleFavorite(name, known)}><${Icon} name="star" size=${14} /></button>` : null}</li>`;
484
621
  };
622
+ // #2401: an open list of two or more lines leaves room under the count for "shown" and Clear (stacked); otherwise compact.
623
+ const facetSet = f.status.length > 0 || f.cli.length > 0;
624
+ const stacked = (!viewOnly && facetOpen.status) || (facetOpen.cli && backends.length > 1);
485
625
  // The list scrolls; the filter under it stays in view (#999), so it sits outside the scrolling section.
486
626
  return html`<div class=${`view-roster inst-nav${editing && canStar ? " fav-editing" : ""}`}>
487
627
  <div data-trust="navigation" class="side-section" id="instanceList" ref=${list}>
488
628
  <div class="inst-head"><h2 class="side-label">${tn("instancesLabel")}</h2>
629
+ ${canStar && items.length && !guest ? html`<${SortMenu} sort=${nav.sort} />`
630
+ : recent ? html`<span class="chip-btn nav-sort-btn on nav-sort-static"><${Icon} name="sort" size=${12} /><span>${tn("sortRecent")}</span></span>` : null}
489
631
  ${canStar && items.length ? html`<button type="button" class="chip-btn fav-edit" aria-pressed=${editing ? "true" : "false"} onClick=${() => setEditing(!editing)}>
490
632
  ${editing ? tn("favDone") : html`<${Icon} name="star" size=${12} />${tn("favEdit")}`}</button>` : null}</div>
491
633
  ${!loaded ? html`<${Skeleton} lines=${4} />` : !items.length ? html`<p class="side-empty">${t("app.noInstances")}</p>` : null}
@@ -493,6 +635,10 @@ export function InstanceNav({ route, items, loaded = true, exec = {}, awaiting =
493
635
  <div class="v-group-head pinned-head"><span class="grow">${tn("pinnedCurrent")}</span></div>
494
636
  <ul class="inst-list">${row(pinned, PINNED_GROUP, 0, 1)}</ul></div>` : null}
495
637
  ${listed.map(({ group: g, names }) => {
638
+ // #2179: in "recent" mode every other row is one list — open, not dragged, not saved as a group.
639
+ if (g === RECENT_GROUP) return html`<div key=${g} class="v-group v-recent" role="group" aria-label=${tn("recentOthers")}>
640
+ <div class="v-group-head recent-head"><${Icon} name="clock" size=${14} /><span class="grow">${tn("recentOthers")}</span><span class="count">${names.length}</span></div>
641
+ <ul class="inst-list">${names.map((name, i) => row(name, g, i, names.length))}</ul></div>`;
496
642
  // #1664: Favorites — always open, not dragged (↑ ↓ in Edit), named in the page's language.
497
643
  if (g === FAV_GROUP) return html`<div key=${g} class="v-group v-fav" role="group" aria-label=${tn("favorites")}>
498
644
  <div class="v-group-head fav-head"><${Icon} name="star" size=${14} cls="fav-mark" /><span class="grow">${tn("favorites")}</span><span class="count">${names.length}</span></div>
@@ -501,7 +647,7 @@ export function InstanceNav({ route, items, loaded = true, exec = {}, awaiting =
501
647
  return html`<div key=${g} class="v-group">
502
648
  <button type="button" class="v-group-head" aria-expanded=${folded ? "false" : "true"} onClick=${() => toggle(g)}
503
649
  title=${tn(folded ? "expandGroup" : "collapseGroup", { group: groupLabel(g) })} ...${dragProps({ type: "group", name: g })}>
504
- <${Icon} name="chevron" size=${14} cls=${folded ? "caret folded" : "caret"} /><span class="grow">${groupLabel(g)}</span><span class="count">${names.length}</span></button>
650
+ <${Icon} name="chevron" size=${14} cls=${folded ? "caret folded" : "caret"} /><${GroupMark} group=${g} /><span class="grow">${groupLabel(g)}</span><span class="count">${names.length}</span></button>
505
651
  ${folded ? null : html`<ul class="inst-list">${names.map((name, i) => row(name, g, i, names.length))}</ul>`}
506
652
  </div>`;
507
653
  })}
@@ -511,17 +657,26 @@ export function InstanceNav({ route, items, loaded = true, exec = {}, awaiting =
511
657
  <label class="v-filter-box"><${Icon} name="search" size=${14} /><span class="sr-only">${tn("filterPh")}</span>
512
658
  <input ref=${filterRef} id="filterInput" type="text" autocomplete="off" spellcheck=${false} placeholder=${tn("filterPh")} value=${f.q}
513
659
  onInput=${(e) => setNavFilter({ q: e.target.value })}
514
- onKeyDown=${(e) => { if (e.key === "Escape" && f.q) { e.preventDefault(); e.stopPropagation(); setNavFilter({ q: "" }); } }} />
660
+ onKeyDown=${(e) => { if (e.key === "Escape" && f.q) { e.preventDefault(); e.stopPropagation(); recordEdit(e.target); setNavFilter({ q: "" }); } }} />
515
661
  ${f.q ? html`<button type="button" class="icon-btn v-filter-clear" aria-label=${tn("filterClear")} title=${tn("filterClear")}
516
- onClick=${() => { setNavFilter({ q: "" }); filterRef.current && filterRef.current.focus(); }}><${Icon} name="close" size=${14} /></button>` : null}
662
+ onClick=${() => { recordEdit(filterRef.current); setNavFilter({ q: "" }); filterRef.current && filterRef.current.focus(); }}><${Icon} name="close" size=${14} /></button>` : null}
517
663
  </label>
518
- <div class="nav-facets">
664
+ <div class=${`nav-facets${stacked ? "" : " compact"}`}>
519
665
  <div class="nav-facet-groups">
520
666
  ${viewOnly ? null : chips("status", STATUS_FACETS, tn("filterStatus"), statusText)}
521
667
  ${backends.length > 1 || f.cli.length ? chips("cli", backends, tn("filterCli"), backendLabel) : null}
522
- ${f.status.length || f.cli.length ? html`<button type="button" class="chip-btn nav-facet-reset" onClick=${() => setNavFilter({ status: [], cli: [] })}>${tn("filterFacetsReset")}</button>` : null}
523
668
  </div>
524
- <p class="note nav-shown" aria-live="polite">${shown.shown} / ${shown.total}${shown.active ? ` ${tn("filterShown")}` : ""}</p>
669
+ ${/* #2401: the count, its "shown" and Clear stack at the row's right end — beside an open list there is room under
670
+ the count, so setting a facet adds no row (a word beside the count, or Clear beside it, wrapped CLI under the
671
+ list). With no such list (.compact) they share one cell of one line: a facet set, Clear takes the count's place
672
+ (the count kept for a screen reader and in Clear's title). Either way the column keeps Clear's width (idle, there
673
+ unseen at no height: .is-idle, a class — never a style attribute, #1300), and stacked the word's too
674
+ (data-reserve, drawn unseen at no height), so the headers' box beside it never narrows. */ ""}
675
+ <div class="nav-facet-end" data-reserve=${tn("filterShown")}>
676
+ <p class=${`note nav-shown${facetSet ? " is-set" : ""}`} aria-live="polite">${shown.shown} / ${shown.total}${shown.active ? html`<span class="nav-shown-word"> ${tn("filterShown")}</span>` : ""}</p>
677
+ <button type="button" class=${`chip-btn nav-facet-reset${facetSet ? "" : " is-idle"}`} title=${facetSet ? `${shown.shown} / ${shown.total} ${tn("filterShown")}` : undefined}
678
+ onClick=${() => setNavFilter({ status: [], cli: [] })}>${tn("filterFacetsReset")}</button>
679
+ </div>
525
680
  </div>
526
681
  </div>`}
527
682
  </div>`;
@@ -80,6 +80,7 @@ function sync() {
80
80
  let valid = false;
81
81
  try { valid = link.disabled !== true && !!link.sheet && link.sheet.ownerNode === link && link.sheet.disabled !== true && link.sheet.href === new URL(href, location.href).href; } catch { /* unknown stylesheet */ }
82
82
  linkState = valid ? "on" : "unknown"; life(linkState, valid ? "loaded" : "load_unverified");
83
+ if (valid) shownHere(sha); // #2198: an agent's trial is on this screen now
83
84
  });
84
85
  link.addEventListener("error", () => { if (!current()) return; linkState = "unknown"; life("unknown", "load_failed"); });
85
86
  life("loading", "loading");
@@ -189,12 +190,40 @@ async function reread() {
189
190
  if (body && mine === gen) reconcile(body); // nothing newer happened since this was asked
190
191
  } catch { /* the next change tries again */ }
191
192
  }
193
+ /**
194
+ * #2198: an agent's trial (General saved it) waits on the host until a person's page shows it. This page shows it once
195
+ * its stylesheet has loaded on screen, and says so: the host starts the keep window then (the dead-man switch, from when
196
+ * a person can see the Keep button). If the host cannot be told, this page reverts it itself at KEEP_SECONDS, as it does
197
+ * for a version it saved.
198
+ */
199
+ let waitingShown = null; // { id, sha } of an agent's trial this page is showing and has not reported yet
200
+ function shownHere(sha) {
201
+ const w = waitingShown;
202
+ if (!w || w.sha !== sha) return;
203
+ waitingShown = null;
204
+ const trial = ownerCssTrial.get();
205
+ if (!trial || trial.id !== w.id) return;
206
+ post("/ui/owner-css/shown", { trial: w.id }).then((r) => {
207
+ if (ownerCssTrial.get() !== trial) return;
208
+ const ms = r.ok && r.body && Number.isFinite(r.body.remaining_ms) ? r.body.remaining_ms : null;
209
+ if (ms === null) { startOwnerCssTrial(w.id, w.sha, trial.previous, KEEP_SECONDS, true); return; }
210
+ startOwnerCssTrial(w.id, w.sha, trial.previous, ms / 1000 + 1, false);
211
+ }, () => { if (ownerCssTrial.get() === trial) startOwnerCssTrial(w.id, w.sha, trial.previous, KEEP_SECONDS, true); });
212
+ }
192
213
  /** Match the host: the version in force, and whether one is on trial (another device's included) — or none any more. */
193
214
  function reconcile(state) {
194
215
  const pending = state.pending && state.pending.sha === state.active && typeof state.pending.id === "string" ? state.pending : null;
195
216
  const trial = ownerCssTrial.get();
196
- if (!pending) { if (trial) dropTrial(); showActive(state.active, state.effectState); return; }
217
+ if (!pending) { waitingShown = null; if (trial) dropTrial(); showActive(state.active, state.effectState); return; }
197
218
  if (trial && trial.id === pending.id) return; // already asking about this one
219
+ if (pending.waiting === true) {
220
+ // Shown here: its stylesheet goes in, and once it has loaded this page reports it (shownHere). Until then the bar
221
+ // waits on the host's word, as for another device's trial.
222
+ waitingShown = { id: pending.id, sha: pending.sha };
223
+ startOwnerCssTrial(pending.id, pending.sha, pending.previous, HOST_TRIAL_SECONDS, false);
224
+ if (linkState === "on" && ownedLink && ownedLink.getAttribute("href") === `/ui/owner-css.css?v=${encodeURIComponent(pending.sha)}`) shownHere(pending.sha);
225
+ return;
226
+ }
198
227
  // The host's own remaining time and a second (never this device's clock): it has settled the trial by then.
199
228
  const remaining = typeof pending.remaining_ms === "number" && Number.isFinite(pending.remaining_ms) ? pending.remaining_ms : 0;
200
229
  startOwnerCssTrial(pending.id, pending.sha, pending.previous, remaining / 1000 + 1, false);
@@ -1,4 +1,4 @@
1
- // #1408 step 2: the View panel (/view, /view/<name>) — every agent's live terminal, its profile card, the AI usage
1
+ // #1408 step 2: the View panel (/view, /view/<name>) — every agent's live terminal, its identity card, the AI usage
2
2
  // panel, in the app shell. Public (/assets/): under `web.view_access: open` an anonymous reader opens it in the
3
3
  // View-only shell, so nothing here needs a session to show, and the one write (Edit profile) asks for one.
4
4
  //
@@ -6,7 +6,7 @@
6
6
  // Everything recurring is a passive read (#1374) taken through the navigation's lease: the pane every 800 ms and the
7
7
  // roster every 5 s while the panel is mounted and the tab is visible, usage every 60 s while its dialog is open. They
8
8
  // stop when the panel goes.
9
- import { html, useEffect, useLayoutEffect, useMemo, useRef, useState } from "./app-html.js";
9
+ import { html, useEffect, useRef, useState } from "./app-html.js";
10
10
  import { t } from "./app-i18n.js";
11
11
  import { appStore, createStore, useStore } from "./app-store.js";
12
12
  import { useLease } from "./app-ctx.js";
@@ -16,7 +16,7 @@ import { HeaderTools, VIEW_SCALE, textSizeStore } from "./header-tools.js";
16
16
  import { readStream } from "./read-stream.js";
17
17
  // The usage dialog's metric row lives with the dialog now (usage-dialog.js); re-exported for what imported it here.
18
18
  export { UsageMetric } from "./usage-dialog.js";
19
- import { backendLabel, modelText, STATUS_KEYS } from "./instance-nav.js";
19
+ import { STATUS_KEYS } from "./instance-nav.js";
20
20
  import { viewStore } from "./view-roster-store.js";
21
21
 
22
22
  /** #1580: the app's stream says the fleet is not answering (a restart): the View's polls skip their turn until it is. */
@@ -189,7 +189,7 @@ export function ViewPanel({ route, navKey }) {
189
189
  else if (!name) body = html`<div data-trust="panel-content" class="panel-body"><${Skeleton} lines=${6} /></div>`;
190
190
  else if (!it) body = html`<div data-trust="panel-content" class="panel-body center"><${Empty} icon="alert" title=${tn("notFound", { name })} hint=${tn("notFoundHint")} /></div>`;
191
191
  else body = html`<${Terminal} key=${name} name=${name} lease=${lease} density=${density} />
192
- <${Card} it=${it} viewOnly=${viewOnly} onEdit=${() => setDialog("edit")} />`;
192
+ <${ViewIdentity} it=${it} viewOnly=${viewOnly} onEdit=${() => setDialog("edit")} />`;
193
193
 
194
194
  return html`<div data-effect-panel="view" class="panel p-view">
195
195
  <${PanelHeader} title=${it ? (it.display_name || it.instance_name) : tn("title")}
@@ -287,33 +287,18 @@ function glyphRatio(pre) {
287
287
  return cellRatio || 0.6;
288
288
  }
289
289
 
290
- /** The profile card under the terminal: one line by default, the whole profile when opened. */
291
- function Card({ it, viewOnly, onEdit }) {
292
- const [open, setOpen] = useState(stored("agend_view_card_expanded") === "1");
293
- const avatar = useRef(null);
294
- // The avatar is fetched once per instance (cache-busted then), not on every 5-second roster refresh.
295
- useLayoutEffect(() => {
296
- if (!avatar.current || !it.has_avatar) return;
297
- avatar.current.src = `/api/avatar/${encodeURIComponent(it.instance_name)}?t=${Date.now()}`;
298
- }, [it.instance_name, it.has_avatar]);
299
- const initial = (it.display_name || it.instance_name || "?").trim().charAt(0) || "?";
300
- const ctx = it.context_pct != null ? ` · ctx ${Math.round(it.context_pct)}%` : "";
301
- const toggle = () => { const next = !open; setOpen(next); store("agend_view_card_expanded", next ? "1" : "0"); };
302
- // #1629 r4 §1: the identity card, above the profile row while it is expanded.
290
+ /** The identity card under the terminal, with Edit profile (or Sign in to edit) below it. Always on: the old one-line
291
+ * strip is gone, and IdentityCard itself is null when the card is empty, so the button lives here rather than inside it. */
292
+ function ViewIdentity({ it, viewOnly, onEdit }) {
303
293
  const card = useCard(it.instance_name, it.card_v);
304
- return html`${open && card ? html`<div data-trust="panel-content" class="v-id"><${IdentityCard} name=${it.instance_name} card=${card} /></div>` : null}<section data-trust="panel-content" class=${`v-card${open ? " open" : ""}`} aria-label=${it.display_name || it.instance_name}>
305
- <div class="v-avatar">${it.has_avatar ? html`<img ref=${avatar} alt="" />` : html`<span class="v-initial" aria-hidden="true">${initial}</span>`}</div>
306
- <div class="v-card-body">
307
- <div class="v-card-line"><strong>${it.display_name || it.instance_name}</strong>${it.role ? html`<span class="v-role">${it.role}</span>` : null}</div>
308
- <div class="v-card-meta">${it.instance_name} · ${backendLabel(it.backend)}${it.model ? ` · ${modelText(it)}` : ""}${ctx}</div>
309
- ${open && it.description ? html`<p class="v-card-desc">${it.description}</p>` : null}
294
+ return html`<div data-trust="panel-content" class="v-id">
295
+ ${card ? html`<${IdentityCard} name=${it.instance_name} card=${card} />` : null}
296
+ <div class="v-id-actions">
297
+ ${viewOnly
298
+ ? html`<a class="btn btn-sm" href=${signInHref()}><${Icon} name="edit" size=${14} />${tn("signInToEdit")}</a>`
299
+ : html`<button type="button" class="btn btn-sm" onClick=${onEdit}><${Icon} name="edit" size=${14} />${tn("editProfile")}</button>`}
310
300
  </div>
311
- <button type="button" class="icon-btn" onClick=${toggle} aria-expanded=${open ? "true" : "false"} aria-label=${tn(open ? "collapseProfile" : "expandProfile")} title=${tn(open ? "collapseProfile" : "expandProfile")}>
312
- <${Icon} name="chevron" size=${16} cls=${open ? "caret" : "caret flipped"} /></button>
313
- ${viewOnly
314
- ? html`<a class="btn btn-sm" href=${signInHref()}><${Icon} name="edit" size=${14} />${tn("signInToEdit")}</a>`
315
- : html`<button type="button" class="btn btn-sm" onClick=${onEdit}><${Icon} name="edit" size=${14} />${tn("editProfile")}</button>`}
316
- </section>`;
301
+ </div>`;
317
302
  }
318
303
 
319
304
  /** Edit profile: display name, role, description, avatar. A write: it needs a session (CSRF via agend-auth.js). */