@songsid/agend 2.3.1 → 2.3.2-beta.10

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 (338) hide show
  1. package/THIRD_PARTY_NOTICES.md +40 -0
  2. package/dist/agent-cli.js +21 -2
  3. package/dist/agent-cli.js.map +1 -1
  4. package/dist/agent-endpoint.js +5 -0
  5. package/dist/agent-endpoint.js.map +1 -1
  6. package/dist/antigravity-usage-scan.d.ts +58 -0
  7. package/dist/antigravity-usage-scan.js +257 -0
  8. package/dist/antigravity-usage-scan.js.map +1 -0
  9. package/dist/antigravity-usage-worker.d.ts +1 -0
  10. package/dist/antigravity-usage-worker.js +10 -0
  11. package/dist/antigravity-usage-worker.js.map +1 -0
  12. package/dist/auth-api.js +4 -0
  13. package/dist/auth-api.js.map +1 -1
  14. package/dist/authority/authority-state.d.ts +4 -0
  15. package/dist/authority/authority-state.js +16 -10
  16. package/dist/authority/authority-state.js.map +1 -1
  17. package/dist/authority/session-authorizations.d.ts +7 -1
  18. package/dist/authority/session-authorizations.js +6 -4
  19. package/dist/authority/session-authorizations.js.map +1 -1
  20. package/dist/authority/web-authority.d.ts +19 -6
  21. package/dist/authority/web-authority.js +22 -8
  22. package/dist/authority/web-authority.js.map +1 -1
  23. package/dist/backend/kiro-identity.d.ts +64 -0
  24. package/dist/backend/kiro-identity.js +101 -0
  25. package/dist/backend/kiro-identity.js.map +1 -1
  26. package/dist/backend/kiro-retired-model.d.ts +62 -0
  27. package/dist/backend/kiro-retired-model.js +160 -0
  28. package/dist/backend/kiro-retired-model.js.map +1 -0
  29. package/dist/backend/kiro.d.ts +71 -2
  30. package/dist/backend/kiro.js +352 -11
  31. package/dist/backend/kiro.js.map +1 -1
  32. package/dist/backend/types.d.ts +59 -0
  33. package/dist/backend/types.js.map +1 -1
  34. package/dist/backend-signin-guide.d.ts +1 -0
  35. package/dist/backend-signin-guide.js +9 -0
  36. package/dist/backend-signin-guide.js.map +1 -0
  37. package/dist/backend-signin-pending.d.ts +10 -0
  38. package/dist/backend-signin-pending.js +44 -0
  39. package/dist/backend-signin-pending.js.map +1 -0
  40. package/dist/browser-backend-signins.d.ts +56 -0
  41. package/dist/browser-backend-signins.js +189 -0
  42. package/dist/browser-backend-signins.js.map +1 -0
  43. package/dist/cache-ledger.d.ts +38 -2
  44. package/dist/cache-ledger.js +133 -8
  45. package/dist/cache-ledger.js.map +1 -1
  46. package/dist/cache-prices.d.ts +2 -1
  47. package/dist/cache-prices.js +5 -2
  48. package/dist/cache-prices.js.map +1 -1
  49. package/dist/cache-service.d.ts +68 -0
  50. package/dist/cache-service.js +355 -13
  51. package/dist/cache-service.js.map +1 -1
  52. package/dist/channel/adapters/discord.d.ts +13 -0
  53. package/dist/channel/adapters/discord.js +103 -16
  54. package/dist/channel/adapters/discord.js.map +1 -1
  55. package/dist/channel/mcp-tools.js +34 -7
  56. package/dist/channel/mcp-tools.js.map +1 -1
  57. package/dist/channel/tool-router.js +2 -1
  58. package/dist/channel/tool-router.js.map +1 -1
  59. package/dist/channel/types.d.ts +11 -0
  60. package/dist/classic-channel-manager.d.ts +11 -0
  61. package/dist/classic-channel-manager.js +39 -1
  62. package/dist/classic-channel-manager.js.map +1 -1
  63. package/dist/cli.js +21 -2
  64. package/dist/cli.js.map +1 -1
  65. package/dist/config-validator.d.ts +7 -1
  66. package/dist/config-validator.js +92 -12
  67. package/dist/config-validator.js.map +1 -1
  68. package/dist/config.d.ts +19 -0
  69. package/dist/config.js +31 -1
  70. package/dist/config.js.map +1 -1
  71. package/dist/cpu-quota.d.ts +23 -0
  72. package/dist/cpu-quota.js +86 -0
  73. package/dist/cpu-quota.js.map +1 -0
  74. package/dist/daemon.d.ts +57 -3
  75. package/dist/daemon.js +330 -50
  76. package/dist/daemon.js.map +1 -1
  77. package/dist/dashboard-permissions.d.ts +20 -6
  78. package/dist/dashboard-permissions.js +58 -9
  79. package/dist/dashboard-permissions.js.map +1 -1
  80. package/dist/delivery-outbox.d.ts +12 -1
  81. package/dist/delivery-outbox.js +67 -15
  82. package/dist/delivery-outbox.js.map +1 -1
  83. package/dist/doctor.d.ts +3 -0
  84. package/dist/doctor.js +11 -1
  85. package/dist/doctor.js.map +1 -1
  86. package/dist/event-log.d.ts +5 -0
  87. package/dist/event-log.js +13 -0
  88. package/dist/event-log.js.map +1 -1
  89. package/dist/fleet-manager.d.ts +212 -0
  90. package/dist/fleet-manager.js +914 -111
  91. package/dist/fleet-manager.js.map +1 -1
  92. package/dist/fleet-process-handlers.d.ts +1 -0
  93. package/dist/fleet-process-handlers.js +9 -1
  94. package/dist/fleet-process-handlers.js.map +1 -1
  95. package/dist/forged-envelope-scan.d.ts +9 -0
  96. package/dist/forged-envelope-scan.js +30 -5
  97. package/dist/forged-envelope-scan.js.map +1 -1
  98. package/dist/general-knowledge/skills/credential-profiles/SKILL.md +5 -2
  99. package/dist/general-knowledge/skills/kiro-engine-migration/SKILL.md +14 -1
  100. package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +28 -5
  101. package/dist/general-knowledge/skills/web-appearance/SKILL.md +31 -8
  102. package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +138 -59
  103. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +145 -61
  104. package/dist/guest-seen-people.d.ts +38 -0
  105. package/dist/guest-seen-people.js +78 -0
  106. package/dist/guest-seen-people.js.map +1 -0
  107. package/dist/instance-config-impact.js +14 -6
  108. package/dist/instance-config-impact.js.map +1 -1
  109. package/dist/instance-lifecycle.d.ts +20 -0
  110. package/dist/instance-lifecycle.js +71 -13
  111. package/dist/instance-lifecycle.js.map +1 -1
  112. package/dist/kiro-db-reader.d.ts +5 -0
  113. package/dist/kiro-db-reader.js +18 -0
  114. package/dist/kiro-db-reader.js.map +1 -1
  115. package/dist/kiro-engine-status.d.ts +2 -0
  116. package/dist/kiro-engine-status.js +9 -0
  117. package/dist/kiro-engine-status.js.map +1 -1
  118. package/dist/kiro-migrate.d.ts +49 -0
  119. package/dist/kiro-migrate.js +105 -0
  120. package/dist/kiro-migrate.js.map +1 -0
  121. package/dist/kiro-transcript-lane.d.ts +2 -0
  122. package/dist/kiro-transcript-lane.js.map +1 -1
  123. package/dist/kiro-transcript-worker.js +1 -0
  124. package/dist/kiro-transcript-worker.js.map +1 -1
  125. package/dist/kiro-tui-forensic.d.ts +7 -0
  126. package/dist/kiro-tui-forensic.js +110 -0
  127. package/dist/kiro-tui-forensic.js.map +1 -0
  128. package/dist/kiro-usage-run.d.ts +7 -0
  129. package/dist/kiro-usage-run.js +39 -0
  130. package/dist/kiro-usage-run.js.map +1 -0
  131. package/dist/kiro-usage-scan.d.ts +49 -0
  132. package/dist/kiro-usage-scan.js +223 -0
  133. package/dist/kiro-usage-scan.js.map +1 -0
  134. package/dist/kiro-usage-worker.d.ts +1 -0
  135. package/dist/kiro-usage-worker.js +10 -0
  136. package/dist/kiro-usage-worker.js.map +1 -0
  137. package/dist/kiro-usage.d.ts +58 -0
  138. package/dist/kiro-usage.js +310 -0
  139. package/dist/kiro-usage.js.map +1 -0
  140. package/dist/locale.js +120 -12
  141. package/dist/locale.js.map +1 -1
  142. package/dist/log-service.js +1 -1
  143. package/dist/log-service.js.map +1 -1
  144. package/dist/login-url.d.ts +4 -0
  145. package/dist/login-url.js +22 -0
  146. package/dist/login-url.js.map +1 -0
  147. package/dist/mac-auto-restore.d.ts +117 -0
  148. package/dist/mac-auto-restore.js +294 -0
  149. package/dist/mac-auto-restore.js.map +1 -0
  150. package/dist/needs-you-hub.d.ts +2 -1
  151. package/dist/needs-you-hub.js +1 -1
  152. package/dist/needs-you-hub.js.map +1 -1
  153. package/dist/needs-you.d.ts +8 -2
  154. package/dist/needs-you.js +6 -0
  155. package/dist/needs-you.js.map +1 -1
  156. package/dist/notice-delivery.d.ts +16 -0
  157. package/dist/notice-delivery.js +35 -0
  158. package/dist/notice-delivery.js.map +1 -0
  159. package/dist/office-share-page.d.ts +16 -0
  160. package/dist/office-share-page.js +85 -0
  161. package/dist/office-share-page.js.map +1 -0
  162. package/dist/office-share-service.d.ts +91 -0
  163. package/dist/office-share-service.js +176 -0
  164. package/dist/office-share-service.js.map +1 -0
  165. package/dist/office-share.d.ts +258 -0
  166. package/dist/office-share.js +549 -0
  167. package/dist/office-share.js.map +1 -0
  168. package/dist/opencode-usage-scan.d.ts +28 -0
  169. package/dist/opencode-usage-scan.js +92 -0
  170. package/dist/opencode-usage-scan.js.map +1 -0
  171. package/dist/opencode-usage-worker.d.ts +1 -0
  172. package/dist/opencode-usage-worker.js +10 -0
  173. package/dist/opencode-usage-worker.js.map +1 -0
  174. package/dist/outbound-handlers.js +22 -2
  175. package/dist/outbound-handlers.js.map +1 -1
  176. package/dist/outbound-schemas.d.ts +28 -2
  177. package/dist/outbound-schemas.js +29 -2
  178. package/dist/outbound-schemas.js.map +1 -1
  179. package/dist/public-web-gateway.js +16 -2
  180. package/dist/public-web-gateway.js.map +1 -1
  181. package/dist/quickstart-api.js +13 -2
  182. package/dist/quickstart-api.js.map +1 -1
  183. package/dist/quickstart.js +2 -1
  184. package/dist/quickstart.js.map +1 -1
  185. package/dist/remote-card-answer.d.ts +83 -0
  186. package/dist/remote-card-answer.js +208 -0
  187. package/dist/remote-card-answer.js.map +1 -0
  188. package/dist/reply-triggers.d.ts +2 -0
  189. package/dist/reply-triggers.js +3 -0
  190. package/dist/reply-triggers.js.map +1 -0
  191. package/dist/settings-api.d.ts +28 -0
  192. package/dist/settings-api.js +148 -17
  193. package/dist/settings-api.js.map +1 -1
  194. package/dist/settings-change.js +54 -17
  195. package/dist/settings-change.js.map +1 -1
  196. package/dist/settings-effect.js +12 -3
  197. package/dist/settings-effect.js.map +1 -1
  198. package/dist/settings-instance-patch.js +4 -1
  199. package/dist/settings-instance-patch.js.map +1 -1
  200. package/dist/setup-form.d.ts +1 -1
  201. package/dist/setup-form.js +55 -6
  202. package/dist/setup-form.js.map +1 -1
  203. package/dist/setup-host.d.ts +9 -0
  204. package/dist/setup-host.js +127 -6
  205. package/dist/setup-host.js.map +1 -1
  206. package/dist/setup-signin-assets.d.ts +2 -0
  207. package/dist/setup-signin-assets.js +6 -0
  208. package/dist/setup-signin-assets.js.map +1 -0
  209. package/dist/setup-wizard.js +2 -0
  210. package/dist/setup-wizard.js.map +1 -1
  211. package/dist/start-attempt.d.ts +9 -5
  212. package/dist/start-attempt.js +27 -8
  213. package/dist/start-attempt.js.map +1 -1
  214. package/dist/start-failure-chat.d.ts +4 -0
  215. package/dist/start-failure-chat.js +23 -6
  216. package/dist/start-failure-chat.js.map +1 -1
  217. package/dist/start-failure-notice.d.ts +4 -0
  218. package/dist/start-failure-notice.js +8 -0
  219. package/dist/start-failure-notice.js.map +1 -1
  220. package/dist/start-failure.d.ts +25 -0
  221. package/dist/start-failure.js +75 -2
  222. package/dist/start-failure.js.map +1 -1
  223. package/dist/structured-signin-transport.d.ts +8 -0
  224. package/dist/structured-signin-transport.js +79 -0
  225. package/dist/structured-signin-transport.js.map +1 -0
  226. package/dist/structured-signin.d.ts +131 -0
  227. package/dist/structured-signin.js +388 -0
  228. package/dist/structured-signin.js.map +1 -0
  229. package/dist/tool-permissions.js +12 -0
  230. package/dist/tool-permissions.js.map +1 -1
  231. package/dist/topic-commands.js +6 -2
  232. package/dist/topic-commands.js.map +1 -1
  233. package/dist/transcript-sources.d.ts +26 -2
  234. package/dist/transcript-sources.js +103 -40
  235. package/dist/transcript-sources.js.map +1 -1
  236. package/dist/turn-reply-guard.d.ts +25 -0
  237. package/dist/turn-reply-guard.js +111 -12
  238. package/dist/turn-reply-guard.js.map +1 -1
  239. package/dist/types.d.ts +46 -9
  240. package/dist/ui/backend-signin.js +68 -12
  241. package/dist/ui/chat-ambient.js +1 -1
  242. package/dist/ui/chat-effects.js +4 -4
  243. package/dist/ui/chat-render.js +5 -2
  244. package/dist/ui/chat-strings.js +4 -4
  245. package/dist/ui/chat-thread.js +2 -2
  246. package/dist/ui/first-run.js +2 -2
  247. package/dist/ui/fleet-canvas-view.js +114 -0
  248. package/dist/ui/fleet-canvas.js +15 -77
  249. package/dist/ui/fleet-office-editor.js +94 -14
  250. package/dist/ui/fleet-office-layout.js +235 -0
  251. package/dist/ui/fleet-office-model.js +12 -6
  252. package/dist/ui/fleet-office-overview.js +167 -0
  253. package/dist/ui/fleet-office-pack.d.ts +44 -0
  254. package/dist/ui/fleet-office-pack.js +352 -37
  255. package/dist/ui/fleet-office-skin.js +220 -36
  256. package/dist/ui/fleet-office-store.js +112 -7
  257. package/dist/ui/fleet-office-view.js +155 -0
  258. package/dist/ui/fleet-office-zones.js +264 -42
  259. package/dist/ui/fleet-office.js +957 -160
  260. package/dist/ui/fleet-usage.js +26 -21
  261. package/dist/ui/office-share-dialog.js +145 -0
  262. package/dist/ui/office-share-page.js +164 -0
  263. package/dist/ui/panel-chat.js +9 -2
  264. package/dist/ui/panel-details.js +2 -2
  265. package/dist/ui/panel-fleet.js +11 -77
  266. package/dist/ui/panel-log.js +4 -4
  267. package/dist/ui/panel-needs.js +4 -4
  268. package/dist/ui/panel-settings.js +608 -167
  269. package/dist/ui/search-dialog.js +26 -7
  270. package/dist/ui/settings-appearance.js +82 -29
  271. package/dist/ui/settings-apply.js +42 -7
  272. package/dist/ui/settings-dialogs.js +76 -23
  273. package/dist/ui/settings-draft.js +51 -0
  274. package/dist/ui/settings-guests.js +255 -79
  275. package/dist/ui/settings-learn.js +2 -2
  276. package/dist/ui/settings-model.js +16 -6
  277. package/dist/ui/settings-registry.js +146 -0
  278. package/dist/ui/settings-strings.js +116 -44
  279. package/dist/ui/settings-web-authority.js +34 -23
  280. package/dist/ui/shared/app-i18n.js +18 -2
  281. package/dist/ui/shared/app-nav.js +10 -5
  282. package/dist/ui/shared/app-prefs.js +80 -2
  283. package/dist/ui/shared/app-route.js +39 -5
  284. package/dist/ui/shared/app-shell.js +73 -10
  285. package/dist/ui/shared/app-stream.js +3 -1
  286. package/dist/ui/shared/app.css +307 -17
  287. package/dist/ui/shared/app.js +3 -0
  288. package/dist/ui/shared/effect-stock-trust.js +1 -1
  289. package/dist/ui/shared/identity-color.js +4 -4
  290. package/dist/ui/shared/office/cozy.png +0 -0
  291. package/dist/ui/shared/side-panel.js +95 -0
  292. package/dist/ui/shared/signin.js +2 -2
  293. package/dist/ui/shared/ui-dialog.js +6 -1
  294. package/dist/ui/shared/ui-icons.js +2 -0
  295. package/dist/ui/shared/web-authorize.js +13 -12
  296. package/dist/ui/structured-signin.js +115 -0
  297. package/dist/usage-report.d.ts +20 -0
  298. package/dist/usage-report.js +54 -17
  299. package/dist/usage-report.js.map +1 -1
  300. package/dist/usage-rollup.d.ts +9 -1
  301. package/dist/usage-rollup.js +20 -4
  302. package/dist/usage-rollup.js.map +1 -1
  303. package/dist/web-api.d.ts +26 -2
  304. package/dist/web-api.js +361 -5
  305. package/dist/web-api.js.map +1 -1
  306. package/dist/web-auth.js +8 -2
  307. package/dist/web-auth.js.map +1 -1
  308. package/dist/web-backend-login.d.ts +1 -7
  309. package/dist/web-backend-login.js +2 -24
  310. package/dist/web-backend-login.js.map +1 -1
  311. package/dist/web-experimental.d.ts +1 -1
  312. package/dist/web-experimental.js +1 -1
  313. package/dist/web-experimental.js.map +1 -1
  314. package/dist/web-mod-agent.js +2 -2
  315. package/dist/web-mod-agent.js.map +1 -1
  316. package/dist/web-office-acts.d.ts +173 -0
  317. package/dist/web-office-acts.js +285 -0
  318. package/dist/web-office-acts.js.map +1 -0
  319. package/dist/web-office-skins.d.ts +109 -0
  320. package/dist/web-office-skins.js +299 -0
  321. package/dist/web-office-skins.js.map +1 -0
  322. package/dist/web-office-store.d.ts +8 -2
  323. package/dist/web-office-store.js +77 -14
  324. package/dist/web-office-store.js.map +1 -1
  325. package/dist/web-settings-patch.d.ts +8 -1
  326. package/dist/web-settings-patch.js +65 -5
  327. package/dist/web-settings-patch.js.map +1 -1
  328. package/dist/web-shell-routes.d.ts +4 -2
  329. package/dist/web-shell-routes.js +17 -7
  330. package/dist/web-shell-routes.js.map +1 -1
  331. package/dist/working-recovery.d.ts +104 -0
  332. package/dist/working-recovery.js +298 -0
  333. package/dist/working-recovery.js.map +1 -0
  334. package/dist/working-state-journal.d.ts +5 -1
  335. package/dist/working-state-journal.js +31 -3
  336. package/dist/working-state-journal.js.map +1 -1
  337. package/package.json +3 -2
  338. package/templates/windows-shortcuts.ps1 +155 -140
@@ -6,17 +6,23 @@
6
6
  *
7
7
  * - `appearance`: its own look on the dashboard — theme, pictures, widgets, ambient, effects, mods it proposes;
8
8
  * - `task_board`: the task board (`task`, and `agend task …`);
9
- * - `web_settings`: Settings → Web appearance for the person (`get_web_settings`, `set_web_settings`).
9
+ * - `web_settings`: Settings → Web appearance for the person (`get_web_settings`, `set_web_settings`);
10
+ * - `office` (#2578), stored separately as `office_access`: its own character in Fleet → Org chart → Office — walking, a speech bubble, a pose
11
+ * (`office_view`, `office_set`).
10
12
  *
11
13
  * Unset is allowed: a fleet that switches nothing off works as before, and the profile still decides what an agent
12
- * has. `false` takes the feature's tools away whatever the profile carries: refused at the fleet's checks
14
+ * has — except for a feature in OFF_UNLESS_ON (`office`), which is off until a person sets it true. `false` takes the feature's tools away whatever the profile carries: refused at the fleet's checks
13
15
  * (tool-permissions.ts's sinks, read at each call — no restart) and left out of the agent's tool menu from its next
14
16
  * start (AGEND_DASHBOARD_OFF, read by mcp-server). Like a profile, policy protection for the resolved identity, not
15
17
  * isolation between same-uid agents with a shell.
16
18
  */
17
- export declare const DASHBOARD_FEATURES: readonly ["appearance", "task_board", "web_settings"];
19
+ export declare const DASHBOARD_FEATURES: readonly ["appearance", "task_board", "web_settings", "office"];
18
20
  export type DashboardFeature = typeof DASHBOARD_FEATURES[number];
19
- /** As written in fleet.yaml: each feature true (allowed), false (switched off) or absent (allowed). */
21
+ /** The features a person switches on (#2578): absent (or null) is off for these, never allowed. */
22
+ export declare const OFF_UNLESS_ON: ReadonlySet<DashboardFeature>;
23
+ /** What a feature is when nothing (not the instance, not the defaults) says: allowed, or off for OFF_UNLESS_ON. */
24
+ export declare const dashboardFeatureDefault: (feature: string) => boolean;
25
+ /** As written in fleet.yaml: each feature true (allowed), false (switched off) or absent (allowed; off for OFF_UNLESS_ON). */
20
26
  export type DashboardAccess = Partial<Record<DashboardFeature, boolean>>;
21
27
  /**
22
28
  * What a NEW fleet.yaml says about the features (the user's decision, 2026-10-22): the task board is off until the
@@ -29,7 +35,7 @@ export declare const NEW_FLEET_DASHBOARD: Readonly<DashboardAccess>;
29
35
  export declare const DASHBOARD_FEATURE_TOOLS: Readonly<Record<DashboardFeature, readonly string[]>>;
30
36
  /** The feature a tool belongs to, or null for a tool no switch governs. */
31
37
  export declare function dashboardFeatureOf(tool: string): DashboardFeature | null;
32
- /** The features switched off (`false`) in an instance's `dashboard` as resolved; anything else allows. */
38
+ /** The features switched off in an instance's `dashboard` as resolved: `false`, and an OFF_UNLESS_ON one not `true`. */
33
39
  export declare function dashboardFeaturesOff(access: unknown): DashboardFeature[];
34
40
  /** The refusal for `tool` when its feature is switched off in this resolved `dashboard` mapping, else null. */
35
41
  export declare function dashboardRefusal(access: unknown, tool: string): string | null;
@@ -52,9 +58,17 @@ export declare function dashboardAccessProblem(value: unknown): string | null;
52
58
  * the default — the same as absent (#2391 review: a literal `task_board: null` must not hide the default's `false`).
53
59
  */
54
60
  export declare function dashboardSwitches(value: unknown): DashboardAccess;
61
+ /** Office lives beside dashboard so published rollback validators never see a new feature in that closed map. */
62
+ export declare function officeAccessValue(config: any): unknown;
63
+ export declare function dashboardAccessOf(config: any): DashboardAccess;
64
+ /** Transitional dashboard.office input is accepted, but never written by a managed save. Canonical values win. */
65
+ export declare function normalizeOfficeStorage(fleet: any): void;
66
+ /** Accept an older Settings client, but project/save its Office edit using the canonical field. */
67
+ export declare function officeInputPatch(value: Record<string, any>): Record<string, any>;
68
+ export declare function hasLegacyOffice(fleet: any): boolean;
55
69
  /**
56
70
  * The features switched off in a fleet config for `name`: each feature's own switch where the instance has one, else
57
- * the default's, else allowed. `name` with no own `instances` entry (a ClassicBot identity) gets the defaults'.
71
+ * the default's, else allowed (off for OFF_UNLESS_ON). `name` with no own `instances` entry (a ClassicBot identity) gets the defaults'.
58
72
  */
59
73
  export declare function dashboardFeaturesOffIn(fleet: unknown, name: string): DashboardFeature[];
60
74
  /**
@@ -6,15 +6,21 @@
6
6
  *
7
7
  * - `appearance`: its own look on the dashboard — theme, pictures, widgets, ambient, effects, mods it proposes;
8
8
  * - `task_board`: the task board (`task`, and `agend task …`);
9
- * - `web_settings`: Settings → Web appearance for the person (`get_web_settings`, `set_web_settings`).
9
+ * - `web_settings`: Settings → Web appearance for the person (`get_web_settings`, `set_web_settings`);
10
+ * - `office` (#2578), stored separately as `office_access`: its own character in Fleet → Org chart → Office — walking, a speech bubble, a pose
11
+ * (`office_view`, `office_set`).
10
12
  *
11
13
  * Unset is allowed: a fleet that switches nothing off works as before, and the profile still decides what an agent
12
- * has. `false` takes the feature's tools away whatever the profile carries: refused at the fleet's checks
14
+ * has — except for a feature in OFF_UNLESS_ON (`office`), which is off until a person sets it true. `false` takes the feature's tools away whatever the profile carries: refused at the fleet's checks
13
15
  * (tool-permissions.ts's sinks, read at each call — no restart) and left out of the agent's tool menu from its next
14
16
  * start (AGEND_DASHBOARD_OFF, read by mcp-server). Like a profile, policy protection for the resolved identity, not
15
17
  * isolation between same-uid agents with a shell.
16
18
  */
17
- export const DASHBOARD_FEATURES = ["appearance", "task_board", "web_settings"];
19
+ export const DASHBOARD_FEATURES = ["appearance", "task_board", "web_settings", "office"];
20
+ /** The features a person switches on (#2578): absent (or null) is off for these, never allowed. */
21
+ export const OFF_UNLESS_ON = new Set(["office"]);
22
+ /** What a feature is when nothing (not the instance, not the defaults) says: allowed, or off for OFF_UNLESS_ON. */
23
+ export const dashboardFeatureDefault = (feature) => !OFF_UNLESS_ON.has(feature);
18
24
  /**
19
25
  * What a NEW fleet.yaml says about the features (the user's decision, 2026-10-22): the task board is off until the
20
26
  * person switches it on (Settings → defaults, or per agent). Written, visibly, by every path that creates the file —
@@ -34,6 +40,7 @@ export const DASHBOARD_FEATURE_TOOLS = {
34
40
  ],
35
41
  task_board: ["task"],
36
42
  web_settings: ["get_web_settings", "set_web_settings"],
43
+ office: ["office_view", "office_set"],
37
44
  };
38
45
  const FEATURE_OF = new Map(DASHBOARD_FEATURES.flatMap((f) => DASHBOARD_FEATURE_TOOLS[f].map((tool) => [tool, f])));
39
46
  /** The feature a tool belongs to, or null for a tool no switch governs. */
@@ -41,17 +48,20 @@ export function dashboardFeatureOf(tool) {
41
48
  return FEATURE_OF.get(tool) ?? null;
42
49
  }
43
50
  const isFeature = (k) => DASHBOARD_FEATURES.includes(k);
44
- /** The features switched off (`false`) in an instance's `dashboard` as resolved; anything else allows. */
51
+ /** The features switched off in an instance's `dashboard` as resolved: `false`, and an OFF_UNLESS_ON one not `true`. */
45
52
  export function dashboardFeaturesOff(access) {
46
- if (!access || typeof access !== "object" || Array.isArray(access))
47
- return [];
48
- return DASHBOARD_FEATURES.filter((f) => access[f] === false);
53
+ const a = access && typeof access === "object" && !Array.isArray(access) ? access : {};
54
+ return DASHBOARD_FEATURES.filter((f) => (OFF_UNLESS_ON.has(f) ? a[f] !== true : a[f] === false));
49
55
  }
50
56
  /** The refusal for `tool` when its feature is in `off`, else null. */
51
57
  function refusalFor(off, tool) {
52
58
  const feature = dashboardFeatureOf(tool);
53
59
  if (!feature || !off.includes(feature))
54
60
  return null;
61
+ if (OFF_UNLESS_ON.has(feature)) {
62
+ return `${tool} is not available to this instance: its dashboard feature "${feature}" is off until the person switches it on (${feature === "office" ? "office_access" : `dashboard.${feature}`}: true). `
63
+ + "Do not retry it; tell the person if you need it.";
64
+ }
55
65
  return `${tool} is not available to this instance: the person switched its dashboard feature "${feature}" off (dashboard.${feature}: false). `
56
66
  + "Do not retry it; tell the person if you need it.";
57
67
  }
@@ -106,15 +116,54 @@ export function dashboardSwitches(value) {
106
116
  }
107
117
  return out;
108
118
  }
119
+ /** Office lives beside dashboard so published rollback validators never see a new feature in that closed map. */
120
+ export function officeAccessValue(config) {
121
+ return config && Object.hasOwn(config, "office_access") ? config.office_access : config?.dashboard?.office;
122
+ }
123
+ export function dashboardAccessOf(config) {
124
+ const access = dashboardSwitches(config?.dashboard), office = officeAccessValue(config);
125
+ delete access.office;
126
+ if (typeof office === "boolean")
127
+ access.office = office;
128
+ return access;
129
+ }
130
+ /** Transitional dashboard.office input is accepted, but never written by a managed save. Canonical values win. */
131
+ export function normalizeOfficeStorage(fleet) {
132
+ for (const config of [fleet?.defaults, ...Object.values(fleet?.instances ?? {})]) {
133
+ if (!config || typeof config !== "object" || Array.isArray(config))
134
+ continue;
135
+ const item = config;
136
+ if (!item.dashboard || typeof item.dashboard !== "object" || !Object.hasOwn(item.dashboard, "office"))
137
+ continue;
138
+ if (!Object.hasOwn(item, "office_access") && typeof item.dashboard.office === "boolean")
139
+ item.office_access = item.dashboard.office;
140
+ const { office: _office, ...dashboard } = item.dashboard;
141
+ item.dashboard = dashboard;
142
+ }
143
+ }
144
+ /** Accept an older Settings client, but project/save its Office edit using the canonical field. */
145
+ export function officeInputPatch(value) {
146
+ const result = { ...value };
147
+ if (value.dashboard && typeof value.dashboard === "object" && !Array.isArray(value.dashboard) && Object.hasOwn(value.dashboard, "office")) {
148
+ const { office, ...dashboard } = value.dashboard;
149
+ result.dashboard = dashboard;
150
+ if (!Object.hasOwn(value, "office_access"))
151
+ result.office_access = office;
152
+ }
153
+ return result;
154
+ }
155
+ export function hasLegacyOffice(fleet) {
156
+ return [fleet?.defaults, ...Object.values(fleet?.instances ?? {})].some((item) => item?.dashboard && Object.hasOwn(item.dashboard, "office"));
157
+ }
109
158
  /**
110
159
  * The features switched off in a fleet config for `name`: each feature's own switch where the instance has one, else
111
- * the default's, else allowed. `name` with no own `instances` entry (a ClassicBot identity) gets the defaults'.
160
+ * the default's, else allowed (off for OFF_UNLESS_ON). `name` with no own `instances` entry (a ClassicBot identity) gets the defaults'.
112
161
  */
113
162
  export function dashboardFeaturesOffIn(fleet, name) {
114
163
  const f = fleet && typeof fleet === "object" ? fleet : {};
115
164
  const instances = f.instances && typeof f.instances === "object" ? f.instances : {};
116
165
  const inst = Object.hasOwn(instances, name) ? instances[name] : undefined; // a name is a key of its own, never an inherited one
117
- return dashboardFeaturesOff({ ...dashboardSwitches(f.defaults?.dashboard), ...dashboardSwitches(inst?.dashboard) });
166
+ return dashboardFeaturesOff({ ...dashboardAccessOf(f.defaults), ...dashboardAccessOf(inst) });
118
167
  }
119
168
  /**
120
169
  * (a) An agent without the task board is not told to use it: the built-in workflow's task board section ("## Progress
@@ -1 +1 @@
1
- {"version":3,"file":"dashboard-permissions.js","sourceRoot":"","sources":["../src/dashboard-permissions.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dashboard-permissions.js","sourceRoot":"","sources":["../src/dashboard-permissions.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,5 @@
1
+ import { WorkingRecoveryStore, type RecoveryEpisode } from "./working-recovery.js";
2
+ import { type SeenPerson } from "./guest-seen-people.js";
1
3
  import { EventEmitter } from "node:events";
2
4
  import type { QueueResumePolicy } from "./delivery-queue-evidence.js";
3
5
  import { type WorkingStateSnapshot, type WorkingStateReference } from "./working-state-journal.js";
@@ -275,6 +277,9 @@ export interface ClaimedOutboxDelivery extends OutboxDelivery {
275
277
  export declare class DeliveryOutbox extends EventEmitter {
276
278
  private readonly db;
277
279
  private workingStateJournal?;
280
+ private recoveryStore?;
281
+ get workingRecovery(): WorkingRecoveryStore;
282
+ admitWorkingRecovery(episode: RecoveryEpisode, content: string, advisory?: boolean): boolean;
278
283
  readonly managerBootId: string;
279
284
  /** Observational extension: its failure never participates in delivery admission. */
280
285
  observeWorkingState(instance: string, snapshot: Omit<WorkingStateSnapshot, "managerBootId">): import("./working-state-journal.js").WorkingStateEntry | null;
@@ -284,6 +289,8 @@ export declare class DeliveryOutbox extends EventEmitter {
284
289
  references: WorkingStateReference[];
285
290
  total: number;
286
291
  };
292
+ hasOtherPendingWork(target: string, deliveryId: string): boolean;
293
+ withdrawWorkingRecovery(deliveryId: string, reason: string): void;
287
294
  private noteWorkingReferences;
288
295
  constructor(dbPath: string, managerBootId?: string);
289
296
  private initialize;
@@ -343,6 +350,7 @@ export declare class DeliveryOutbox extends EventEmitter {
343
350
  threadId?: string;
344
351
  adapterId?: string;
345
352
  }): boolean;
353
+ private insertInternalNotice;
346
354
  /** The owner's open obligations, oldest first (#926). */
347
355
  openReplyObligations(ownerInstance: string): ReplyObligation[];
348
356
  getReplyObligation(correlationId: string, requesterInstance: string, ownerInstance: string): ReplyObligation | undefined;
@@ -362,6 +370,9 @@ export declare class DeliveryOutbox extends EventEmitter {
362
370
  inputKind: string;
363
371
  managerBootId: string;
364
372
  }): HumanReceiptRow;
373
+ /** #2348: the people seen in chat, for Settings → Guests (admin only), and whether one was. */
374
+ seenPeople(limit?: number): SeenPerson[];
375
+ isSeenPerson(adapterId: string, userId: string): boolean;
365
376
  /** #1939 §10: the highest epoch a stored receipt carries (authority-state.ts must not be behind it). */
366
377
  maxWebAuthorityEpoch(): number;
367
378
  /** #2122: delivered messages per instance and UTC hour in [from, to). */
@@ -403,7 +414,7 @@ export declare class DeliveryOutbox extends EventEmitter {
403
414
  countForTarget(target: string, states: readonly OutboxState[]): number;
404
415
  nextRetryAt(): string | null;
405
416
  /** Claim the earliest eligible target row; one caller owns each target lane. */
406
- claimNext(managerBootId: string, targetBootIdFor: (target: string) => string | null, blockedTargets: ReadonlySet<string>): ClaimedOutboxDelivery | undefined;
417
+ claimNext(managerBootId: string, targetBootIdFor: (target: string) => string | null, blockedTargets: ReadonlySet<string>, eligible?: (delivery: OutboxDelivery) => boolean): ClaimedOutboxDelivery | undefined;
407
418
  private claimNextSync;
408
419
  /** Idempotent begin permit, committed immediately before the pane side effect. */
409
420
  begin(deliveryId: string, targetBootId: string, attemptNo: number, evidence?: DeliveryAttemptEvidence): "begun" | "duplicate" | "stale";
@@ -1,3 +1,5 @@
1
+ import { WorkingRecoveryStore, WORKING_RECOVERY_KIND } from "./working-recovery.js";
2
+ import { initializeSeenPeople, isSeenPerson, listSeenPeople, noteSeenPerson, pruneSeenPeople, SEEN_PEOPLE_KEEP_DAYS } from "./guest-seen-people.js";
1
3
  import { measureSyncWork } from "./sync-work-attribution.js";
2
4
  import Database from "better-sqlite3";
3
5
  import { createHash, randomUUID } from "node:crypto";
@@ -302,6 +304,14 @@ function mapObligation(row) {
302
304
  export class DeliveryOutbox extends EventEmitter {
303
305
  db;
304
306
  workingStateJournal;
307
+ recoveryStore;
308
+ get workingRecovery() { return this.recoveryStore ??= new WorkingRecoveryStore(this.db); }
309
+ admitWorkingRecovery(episode, content, advisory = false) {
310
+ const inserted = this.workingRecovery.admit(episode, () => this.insertInternalNotice(episode.instance, `working-recovery:${episode.key}`, content, new Date(), undefined, WORKING_RECOVERY_KIND), advisory);
311
+ if (inserted)
312
+ this.emit("admitted");
313
+ return inserted;
314
+ }
305
315
  managerBootId;
306
316
  /** Observational extension: its failure never participates in delivery admission. */
307
317
  observeWorkingState(instance, snapshot) {
@@ -334,6 +344,16 @@ export class DeliveryOutbox extends EventEmitter {
334
344
  return { total: total.n, references: rows.map(r => ({ kind: r.kind, correlationId: r.correlation_id,
335
345
  requester: r.requester_instance, owner: r.owner_instance, deliveryId: r.request_delivery_id })) };
336
346
  }
347
+ hasOtherPendingWork(target, deliveryId) {
348
+ return !!this.db.prepare("SELECT 1 FROM deliveries WHERE target_instance=? AND delivery_id<>? AND kind<>? AND state IN ('queued','retry_wait','delivering','submission_started','reconciliation_pending') LIMIT 1")
349
+ .get(target, deliveryId, WORKING_RECOVERY_KIND);
350
+ }
351
+ withdrawWorkingRecovery(deliveryId, reason) {
352
+ const changed = this.db.prepare("UPDATE deliveries SET state='cancelled',finished_at=?,updated_at=?,last_error=? WHERE delivery_id=? AND kind=? AND state IN ('queued','retry_wait','delivering')")
353
+ .run(new Date().toISOString(), new Date().toISOString(), reason, deliveryId, WORKING_RECOVERY_KIND).changes;
354
+ if (changed)
355
+ this.emit("state", { deliveryId, state: "cancelled" });
356
+ }
337
357
  noteWorkingReferences(instances) {
338
358
  // The transaction has committed. Telemetry must not change its result.
339
359
  try {
@@ -490,6 +510,7 @@ export class DeliveryOutbox extends EventEmitter {
490
510
  `);
491
511
  this.migrateReplyObligationExpiry();
492
512
  initializeHumanReceipts(this.db);
513
+ initializeSeenPeople(this.db); // #2348: Settings → Guests' people (after the receipts: its backfill reads them)
493
514
  // Additive migration: existing Phase 1 databases keep their row identities,
494
515
  // foreign keys, and WAL while gaining reconciliation evidence columns.
495
516
  this.ensureColumn("deliveries", "reconciliation_pending", "INTEGER NOT NULL DEFAULT 0");
@@ -769,10 +790,22 @@ export class DeliveryOutbox extends EventEmitter {
769
790
  * answer goes there too. Without it the notice belongs to no chat.
770
791
  */
771
792
  admitSystemNotice(target, sourceKey, content, now = new Date(), place) {
793
+ const existed = this.db.prepare("SELECT 1 FROM deliveries WHERE source_key=?").get(sourceKey);
794
+ this.insertInternalNotice(target, sourceKey, content, now, place, SYSTEM_NOTICE_KIND);
795
+ // INSERT OR IGNORE returns its durable id even for a duplicate.
796
+ const inserted = !existed;
797
+ if (inserted)
798
+ this.emit("admitted");
799
+ return inserted;
800
+ }
801
+ insertInternalNotice(target, sourceKey, content, now, place, kind) {
802
+ const existing = this.db.prepare("SELECT delivery_id FROM deliveries WHERE source_key=?").get(sourceKey);
803
+ if (existing)
804
+ return existing.delivery_id;
772
805
  const noticeId = randomUUID();
773
806
  const nowIso = now.toISOString();
774
807
  const payload = {
775
- type: "fleet_inbound",
808
+ type: kind === WORKING_RECOVERY_KIND ? WORKING_RECOVERY_KIND : "fleet_inbound",
776
809
  content,
777
810
  meta: {
778
811
  user: "AgEnD", user_id: "agend-system", message_id: `system-notice-${noticeId}`,
@@ -780,17 +813,15 @@ export class DeliveryOutbox extends EventEmitter {
780
813
  ...(place?.adapterId ? { adapter_id: place.adapterId } : {}),
781
814
  },
782
815
  };
783
- const inserted = this.db.prepare(`
816
+ this.db.prepare(`
784
817
  INSERT OR IGNORE INTO deliveries (
785
818
  delivery_id,operation_id,source_key,source_instance,source_daemon_boot_id,
786
819
  target_instance,target_session,target_daemon_boot_id,kind,correlation_id,payload_json,message_id,content_sha256,state,
787
820
  attempt_no,created_seq,manager_boot_id,created_at,updated_at,accepted_at
788
821
  ) VALUES (?,?,?,'agend-system',?,?,NULL,NULL,?,?,?,?,?,'queued',0,
789
822
  (SELECT COALESCE(MAX(created_seq),0)+1 FROM deliveries),NULL,?,?,?)
790
- `).run(noticeId, `notice:${sourceKey}`, sourceKey, this.managerBootId, target, SYSTEM_NOTICE_KIND, `notice:${sourceKey}`, JSON.stringify(payload), payload.meta.message_id, deliveryContentDigest(content), nowIso, nowIso, nowIso).changes === 1;
791
- if (inserted)
792
- this.emit("admitted");
793
- return inserted;
823
+ `).run(noticeId, `notice:${sourceKey}`, sourceKey, this.managerBootId, target, kind, `notice:${sourceKey}`, JSON.stringify(payload), payload.meta.message_id, deliveryContentDigest(content), nowIso, nowIso, nowIso);
824
+ return noticeId;
794
825
  }
795
826
  /** The owner's open obligations, oldest first (#926). */
796
827
  openReplyObligations(ownerInstance) {
@@ -965,8 +996,20 @@ export class DeliveryOutbox extends EventEmitter {
965
996
  return row ? mapRow(row) : undefined;
966
997
  }
967
998
  createHumanReceipt(input) {
968
- return createHumanReceipt(this.db, input);
999
+ const row = createHumanReceipt(this.db, input);
1000
+ // #2348: a platform sender (never the web, never a bot: nativeHumanOrigin) is someone an admin may grant guest access.
1001
+ const o = input.origin;
1002
+ if ((o.platform === "discord" || o.platform === "telegram") && o.adapter_id && o.user_id) {
1003
+ try {
1004
+ noteSeenPerson(this.db, { adapterId: o.adapter_id, userId: o.user_id, username: o.username, instance: input.targetInstance, at: row.created_at });
1005
+ }
1006
+ catch { /* the receipt is the evidence; the picker's list may miss one sender */ }
1007
+ }
1008
+ return row;
969
1009
  }
1010
+ /** #2348: the people seen in chat, for Settings → Guests (admin only), and whether one was. */
1011
+ seenPeople(limit) { return listSeenPeople(this.db, limit); }
1012
+ isSeenPerson(adapterId, userId) { return isSeenPerson(this.db, adapterId, userId); }
970
1013
  /** #1939 §10: the highest epoch a stored receipt carries (authority-state.ts must not be behind it). */
971
1014
  maxWebAuthorityEpoch() { return maxWebAuthorityEpoch(this.db); }
972
1015
  /** #2122: delivered messages per instance and UTC hour in [from, to). */
@@ -1063,10 +1106,10 @@ export class DeliveryOutbox extends EventEmitter {
1063
1106
  return row.next_attempt_at;
1064
1107
  }
1065
1108
  /** Claim the earliest eligible target row; one caller owns each target lane. */
1066
- claimNext(managerBootId, targetBootIdFor, blockedTargets) {
1067
- return measureSyncWork("outbox.claimNext", () => this.claimNextSync(managerBootId, targetBootIdFor, blockedTargets));
1109
+ claimNext(managerBootId, targetBootIdFor, blockedTargets, eligible) {
1110
+ return measureSyncWork("outbox.claimNext", () => this.claimNextSync(managerBootId, targetBootIdFor, blockedTargets, eligible));
1068
1111
  }
1069
- claimNextSync(managerBootId, targetBootIdFor, blockedTargets) {
1112
+ claimNextSync(managerBootId, targetBootIdFor, blockedTargets, eligible) {
1070
1113
  const now = new Date().toISOString();
1071
1114
  const rows = this.db.prepare(`
1072
1115
  SELECT * FROM deliveries WHERE state IN ('queued','retry_wait')
@@ -1084,7 +1127,7 @@ export class DeliveryOutbox extends EventEmitter {
1084
1127
  if (visitedTargets.has(row.target_instance))
1085
1128
  continue;
1086
1129
  visitedTargets.add(row.target_instance);
1087
- if (blockedTargets.has(row.target_instance))
1130
+ if (blockedTargets.has(row.target_instance) || eligible && !eligible(mapRow(row)))
1088
1131
  continue;
1089
1132
  // A recovered submission owns only its target lane while its evidence is
1090
1133
  // classified; independent targets can keep dispatching.
@@ -1327,7 +1370,9 @@ export class DeliveryOutbox extends EventEmitter {
1327
1370
  // Readiness and idle deferrals occur before begin and do not consume the
1328
1371
  // submission-attempt budget. The age limit remains the visible bound for
1329
1372
  // work that never reaches a pane-side-effect attempt.
1330
- const exhausted = ageMs >= maxAgeForKind(row.kind, DURABLE_DELIVERY_MAX_AGE_MS);
1373
+ // A local continuation held before any input remains deferred across a crash
1374
+ // loop or a persistent draft. Ordinary deliveries retain their existing TTL.
1375
+ const exhausted = row.kind !== WORKING_RECOVERY_KIND && ageMs >= maxAgeForKind(row.kind, DURABLE_DELIVERY_MAX_AGE_MS);
1331
1376
  const outcome = exhausted ? "failed" : "retry_wait";
1332
1377
  const update = this.db.prepare(`
1333
1378
  UPDATE deliveries SET state=?,updated_at=?,finished_at=?,last_error=?,next_attempt_at=?
@@ -1382,6 +1427,8 @@ export class DeliveryOutbox extends EventEmitter {
1382
1427
  let changed = 0;
1383
1428
  let uncertain = 0;
1384
1429
  for (const row of stale) {
1430
+ if (row.kind === WORKING_RECOVERY_KIND)
1431
+ continue;
1385
1432
  // A kind with a longer life is only stale once that has passed too.
1386
1433
  if (Date.parse(row.created_at) > nowMs - maxAgeForKind(row.kind, maxAgeMs))
1387
1434
  continue;
@@ -1426,8 +1473,8 @@ export class DeliveryOutbox extends EventEmitter {
1426
1473
  nextExpiryAt() {
1427
1474
  const oldest = (systemNotices) => this.db.prepare(`
1428
1475
  SELECT MIN(created_at) AS created_at FROM deliveries
1429
- WHERE (state IN ('queued','retry_wait') AND (kind=?) = ?) OR (? = 0 AND state='submission_started' AND reconciliation_pending=1)
1430
- `).get(SYSTEM_NOTICE_KIND, systemNotices ? 1 : 0, systemNotices ? 1 : 0).created_at;
1476
+ WHERE (state IN ('queued','retry_wait') AND kind<>? AND (kind=?) = ?) OR (? = 0 AND state='submission_started' AND reconciliation_pending=1)
1477
+ `).get(WORKING_RECOVERY_KIND, SYSTEM_NOTICE_KIND, systemNotices ? 1 : 0, systemNotices ? 1 : 0).created_at;
1431
1478
  const expiries = [
1432
1479
  [oldest(false), DURABLE_DELIVERY_MAX_AGE_MS],
1433
1480
  [oldest(true), maxAgeForKind(SYSTEM_NOTICE_KIND, DURABLE_DELIVERY_MAX_AGE_MS)],
@@ -1697,6 +1744,11 @@ export class DeliveryOutbox extends EventEmitter {
1697
1744
  }
1698
1745
  // #2122: delivered-message counts are kept as long as the usage rollup (400 days), whatever `days` is.
1699
1746
  pruneMessageHours(this.db, Date.now() - 400 * 24 * 60 * 60_000);
1747
+ // #2348: the people seen in chat, the same 400 days since last seen.
1748
+ try {
1749
+ pruneSeenPeople(this.db, new Date(Date.now() - SEEN_PEOPLE_KEEP_DAYS * 24 * 60 * 60_000).toISOString());
1750
+ }
1751
+ catch { /* next pass */ }
1700
1752
  // Human rows are evidence only: retention never schedules or retries them.
1701
1753
  for (;;) {
1702
1754
  if (!this.db.open)
@@ -1745,7 +1797,7 @@ export class DeliveryOutbox extends EventEmitter {
1745
1797
  /** Must be called inside the same SQLite transaction as the terminal transition. */
1746
1798
  insertFailureNotice(parent, outcome, reason, now) {
1747
1799
  if (parent.kind === "delivery_outcome_notice" || parent.kind === "post_restart_outcome_notice"
1748
- || parent.kind === REPLY_OBLIGATION_NOTICE_KIND || parent.kind === SYSTEM_NOTICE_KIND)
1800
+ || parent.kind === REPLY_OBLIGATION_NOTICE_KIND || parent.kind === SYSTEM_NOTICE_KIND || parent.kind === WORKING_RECOVERY_KIND)
1749
1801
  return;
1750
1802
  // A self-scheduled silent heartbeat has no other sender to inform. Keep
1751
1803
  // its terminal state/evidence and fleet log without creating a new turn.