clay-server 5.2.0-beta.3 → 5.2.0-beta.5

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 (339) hide show
  1. package/lib/account-auth-socket.js +1 -0
  2. package/lib/builtin-mates.js +19 -16
  3. package/lib/bundled-skills/clay-agent-browser/SKILL.md +6 -2
  4. package/lib/canonical-clay-record-authority.js +15 -0
  5. package/lib/canonical-clay-scheduling-authority.js +19 -0
  6. package/lib/claude-hook-installer.js +28 -0
  7. package/lib/clay-mate-chat-receipts.js +26 -0
  8. package/lib/clay-mate-chat.js +87 -0
  9. package/lib/clay-mate-knowledge-input.js +26 -0
  10. package/lib/clay-mate-knowledge.js +95 -0
  11. package/lib/clay-mate-lifecycle.js +95 -0
  12. package/lib/clay-product-control-input.js +33 -0
  13. package/lib/clay-product-control-tools.js +51 -0
  14. package/lib/clay-product-control.js +237 -0
  15. package/lib/clay-project-lifecycle.js +154 -0
  16. package/lib/clay-project-record-input.js +44 -0
  17. package/lib/clay-project-records.js +83 -0
  18. package/lib/clay-scheduling-input.js +21 -0
  19. package/lib/clay-scheduling-receipts.js +26 -0
  20. package/lib/clay-scheduling.js +119 -0
  21. package/lib/clay-screen-controls.js +96 -0
  22. package/lib/clay-workspace-controls.js +83 -0
  23. package/lib/clay-workspace-input.js +17 -0
  24. package/lib/clay-workspace-receipts.js +26 -0
  25. package/lib/daemon-project-access.js +11 -0
  26. package/lib/daemon-project-lifecycle-callbacks.js +15 -0
  27. package/lib/daemon-project-lifecycle.js +262 -0
  28. package/lib/daemon-projects.js +1 -1
  29. package/lib/daemon.js +73 -221
  30. package/lib/issues-service.js +29 -15
  31. package/lib/knowledge-db-import.js +5 -4
  32. package/lib/knowledge-db-service.js +9 -6
  33. package/lib/knowledge-document-index.js +2 -1
  34. package/lib/knowledge-file-store.js +7 -1
  35. package/lib/knowledge-product-operations.js +46 -0
  36. package/lib/mate-avatar-catalog.js +12 -0
  37. package/lib/mate-avatar-migration.js +41 -0
  38. package/lib/mate-creation-mcp-server.js +2 -0
  39. package/lib/mate-knowledge-canonical-access.js +10 -0
  40. package/lib/mate-knowledge-service.js +7 -4
  41. package/lib/mate-lifecycle-service.js +94 -0
  42. package/lib/mate-message-delivery.js +43 -0
  43. package/lib/mate-ready-creation.js +9 -1
  44. package/lib/mate-schedule-input.js +29 -0
  45. package/lib/mate-schedule-lifecycle.js +151 -0
  46. package/lib/mate-scheduling.js +103 -0
  47. package/lib/mates.js +18 -6
  48. package/lib/mcp-agent-tools.js +117 -0
  49. package/lib/mcp-connection-store.js +119 -15
  50. package/lib/mcp-import-credentials.js +31 -0
  51. package/lib/mcp-local-transport.js +13 -0
  52. package/lib/mcp-oauth.js +8 -6
  53. package/lib/mcp-query-bridge.js +30 -0
  54. package/lib/mcp-remote-connections.js +74 -20
  55. package/lib/mcp-source-adapters.js +91 -0
  56. package/lib/mcp-source-context.js +42 -0
  57. package/lib/mcp-source-discovery.js +169 -0
  58. package/lib/mcp-source-plugins.js +68 -0
  59. package/lib/mcp-source-restrictions.js +47 -0
  60. package/lib/mcp-tool-policy.js +34 -0
  61. package/lib/notes-checklist.js +50 -0
  62. package/lib/notes.js +49 -3
  63. package/lib/os-users.js +20 -5
  64. package/lib/project-admin-overview.js +51 -0
  65. package/lib/project-ask-user.js +8 -4
  66. package/lib/project-debate.js +1 -0
  67. package/lib/project-delegated-follow-up.js +2 -1
  68. package/lib/project-directory-claim.js +65 -0
  69. package/lib/project-file-operations.js +61 -0
  70. package/lib/project-filesystem.js +13 -87
  71. package/lib/project-full-access.js +23 -2
  72. package/lib/project-knowledge.js +32 -18
  73. package/lib/project-lifecycle-operations.js +98 -0
  74. package/lib/project-lifecycle-process-witness.js +32 -0
  75. package/lib/project-lifecycle-runtime.js +50 -0
  76. package/lib/project-linux-access.js +22 -0
  77. package/lib/project-logs-canonical-clay.js +27 -0
  78. package/lib/project-logs-clay-query.js +16 -0
  79. package/lib/project-logs-schema.js +4 -2
  80. package/lib/project-logs-service.js +8 -7
  81. package/lib/project-logs.js +1 -0
  82. package/lib/project-loop.js +1 -0
  83. package/lib/project-mate-chat-control.js +55 -0
  84. package/lib/project-mate-creation-proposal.js +18 -3
  85. package/lib/project-mate-interaction.js +8 -0
  86. package/lib/project-mcp.js +54 -16
  87. package/lib/project-models.js +14 -31
  88. package/lib/project-notifications.js +74 -16
  89. package/lib/project-ownership.js +77 -0
  90. package/lib/project-pair-result-delivery.js +14 -3
  91. package/lib/project-pending-message-queue.js +1 -0
  92. package/lib/project-product-control.js +87 -0
  93. package/lib/project-product-session-queue.js +46 -0
  94. package/lib/project-push-access.js +28 -0
  95. package/lib/project-scheduled-message-queue.js +30 -0
  96. package/lib/project-scheduled-messages.js +63 -64
  97. package/lib/project-scheduled-task-control.js +83 -0
  98. package/lib/project-scheduled-tasks.js +13 -2
  99. package/lib/project-scheduling-access.js +10 -0
  100. package/lib/project-session-actions.js +65 -0
  101. package/lib/project-session-delete.js +8 -1
  102. package/lib/project-session-document.js +8 -3
  103. package/lib/project-session-github.js +63 -19
  104. package/lib/project-session-history-actions.js +165 -0
  105. package/lib/project-session-linear.js +14 -6
  106. package/lib/project-session-notes.js +7 -3
  107. package/lib/project-session-runtime-actions.js +63 -0
  108. package/lib/project-session-vendor.js +54 -0
  109. package/lib/project-sessions.js +68 -241
  110. package/lib/project-shared-browser.js +56 -12
  111. package/lib/project-user-message.js +30 -1
  112. package/lib/project-workspace-operations.js +126 -0
  113. package/lib/project-workspace-query.js +9 -2
  114. package/lib/project.js +144 -37
  115. package/lib/public/app.js +5 -0
  116. package/lib/public/appearance-bootstrap.js +14 -0
  117. package/lib/public/css/clay-tool-activity.css +51 -0
  118. package/lib/public/css/clay-workspace.css +29 -0
  119. package/lib/public/css/home-debate-planning.css +2 -0
  120. package/lib/public/css/home-mate-creation.css +5 -0
  121. package/lib/public/css/mate-avatar-gallery.css +12 -0
  122. package/lib/public/css/mate-conversation.css +10 -5
  123. package/lib/public/css/overlays.css +4 -2
  124. package/lib/public/css/project-admin-overview.css +13 -0
  125. package/lib/public/css/session-folders.css +11 -0
  126. package/lib/public/css/session-github.css +46 -1
  127. package/lib/public/css/shared-browser.css +35 -1
  128. package/lib/public/css/sticky-notes.css +61 -1
  129. package/lib/public/index.html +11 -5
  130. package/lib/public/mate-avatars/B01.png +0 -0
  131. package/lib/public/mate-avatars/B02.png +0 -0
  132. package/lib/public/mate-avatars/B03.png +0 -0
  133. package/lib/public/mate-avatars/B04.png +0 -0
  134. package/lib/public/mate-avatars/B05.png +0 -0
  135. package/lib/public/mate-avatars/B06.png +0 -0
  136. package/lib/public/mate-avatars/B07.png +0 -0
  137. package/lib/public/mate-avatars/B08.png +0 -0
  138. package/lib/public/mate-avatars/B09.png +0 -0
  139. package/lib/public/mate-avatars/B10.png +0 -0
  140. package/lib/public/mate-avatars/B11.png +0 -0
  141. package/lib/public/mate-avatars/B12.png +0 -0
  142. package/lib/public/mate-avatars/B13.png +0 -0
  143. package/lib/public/mate-avatars/B14.png +0 -0
  144. package/lib/public/mate-avatars/B15.png +0 -0
  145. package/lib/public/mate-avatars/B16.png +0 -0
  146. package/lib/public/mate-avatars/B17.png +0 -0
  147. package/lib/public/mate-avatars/B18.png +0 -0
  148. package/lib/public/mate-avatars/B19.png +0 -0
  149. package/lib/public/mate-avatars/B20.png +0 -0
  150. package/lib/public/mate-avatars/B21.png +0 -0
  151. package/lib/public/mate-avatars/B22.png +0 -0
  152. package/lib/public/mate-avatars/B23.png +0 -0
  153. package/lib/public/mate-avatars/B24.png +0 -0
  154. package/lib/public/mate-avatars/B25.png +0 -0
  155. package/lib/public/mate-avatars/B26.png +0 -0
  156. package/lib/public/mate-avatars/B27.png +0 -0
  157. package/lib/public/mate-avatars/B28.png +0 -0
  158. package/lib/public/mate-avatars/B29.png +0 -0
  159. package/lib/public/mate-avatars/B30.png +0 -0
  160. package/lib/public/mate-avatars/B31.png +0 -0
  161. package/lib/public/mate-avatars/B32.png +0 -0
  162. package/lib/public/mate-avatars/B33.png +0 -0
  163. package/lib/public/mate-avatars/B34.png +0 -0
  164. package/lib/public/mate-avatars/B35.png +0 -0
  165. package/lib/public/mate-avatars/B36.png +0 -0
  166. package/lib/public/mate-avatars/B37.png +0 -0
  167. package/lib/public/mate-avatars/B38.png +0 -0
  168. package/lib/public/mate-avatars/B39.png +0 -0
  169. package/lib/public/mate-avatars/B40.png +0 -0
  170. package/lib/public/mate-avatars/B41.png +0 -0
  171. package/lib/public/mate-avatars/B42.png +0 -0
  172. package/lib/public/mate-avatars/B43.png +0 -0
  173. package/lib/public/mate-avatars/B44.png +0 -0
  174. package/lib/public/mate-avatars/B45.png +0 -0
  175. package/lib/public/mate-avatars/B46.png +0 -0
  176. package/lib/public/mate-avatars/B47.png +0 -0
  177. package/lib/public/mate-avatars/B48.png +0 -0
  178. package/lib/public/mate-avatars/C01.png +0 -0
  179. package/lib/public/mate-avatars/C02.png +0 -0
  180. package/lib/public/mate-avatars/C03.png +0 -0
  181. package/lib/public/mate-avatars/C04.png +0 -0
  182. package/lib/public/mate-avatars/C05.png +0 -0
  183. package/lib/public/mate-avatars/C06.png +0 -0
  184. package/lib/public/mate-avatars/C07.png +0 -0
  185. package/lib/public/mate-avatars/C08.png +0 -0
  186. package/lib/public/mate-avatars/C09.png +0 -0
  187. package/lib/public/mate-avatars/C10.png +0 -0
  188. package/lib/public/mate-avatars/C11.png +0 -0
  189. package/lib/public/mate-avatars/C12.png +0 -0
  190. package/lib/public/mate-avatars/C13.png +0 -0
  191. package/lib/public/mate-avatars/C14.png +0 -0
  192. package/lib/public/mate-avatars/C15.png +0 -0
  193. package/lib/public/mate-avatars/C16.png +0 -0
  194. package/lib/public/mate-avatars/C17.png +0 -0
  195. package/lib/public/mate-avatars/C18.png +0 -0
  196. package/lib/public/mate-avatars/C19.png +0 -0
  197. package/lib/public/mate-avatars/C20.png +0 -0
  198. package/lib/public/mate-avatars/C21.png +0 -0
  199. package/lib/public/mate-avatars/C22.png +0 -0
  200. package/lib/public/mate-avatars/C23.png +0 -0
  201. package/lib/public/mate-avatars/C24.png +0 -0
  202. package/lib/public/mate-avatars/C25.png +0 -0
  203. package/lib/public/mate-avatars/C26.png +0 -0
  204. package/lib/public/mate-avatars/C27.png +0 -0
  205. package/lib/public/mate-avatars/C28.png +0 -0
  206. package/lib/public/mate-avatars/C29.png +0 -0
  207. package/lib/public/mate-avatars/C30.png +0 -0
  208. package/lib/public/mate-avatars/C31.png +0 -0
  209. package/lib/public/mate-avatars/C32.png +0 -0
  210. package/lib/public/mate-avatars/C33.png +0 -0
  211. package/lib/public/mate-avatars/C34.png +0 -0
  212. package/lib/public/mate-avatars/C35.png +0 -0
  213. package/lib/public/mate-avatars/C36.png +0 -0
  214. package/lib/public/mate-avatars/C37.png +0 -0
  215. package/lib/public/mate-avatars/C38.png +0 -0
  216. package/lib/public/mate-avatars/C39.png +0 -0
  217. package/lib/public/mate-avatars/C40.png +0 -0
  218. package/lib/public/mate-avatars/C41.png +0 -0
  219. package/lib/public/mate-avatars/C42.png +0 -0
  220. package/lib/public/mate-avatars/C43.png +0 -0
  221. package/lib/public/mate-avatars/C44.png +0 -0
  222. package/lib/public/mate-avatars/C45.png +0 -0
  223. package/lib/public/mate-avatars/C46.png +0 -0
  224. package/lib/public/mate-avatars/C47.png +0 -0
  225. package/lib/public/mate-avatars/C48.png +0 -0
  226. package/lib/public/mate-avatars/catalog.json +482 -0
  227. package/lib/public/mate-avatars/lead-clay.png +0 -0
  228. package/lib/public/modules/admin-settings-projects.js +6 -2
  229. package/lib/public/modules/app-connection.js +8 -1
  230. package/lib/public/modules/app-message-router.js +4 -0
  231. package/lib/public/modules/app-messages.js +31 -5
  232. package/lib/public/modules/app-projects.js +5 -0
  233. package/lib/public/modules/app-rendering.js +1 -1
  234. package/lib/public/modules/avatar.js +15 -41
  235. package/lib/public/modules/chat-render-runtime.js +10 -1
  236. package/lib/public/modules/clay-browser-links.js +47 -0
  237. package/lib/public/modules/clay-log-links.js +1 -1
  238. package/lib/public/modules/clay-owner-navigation.js +27 -0
  239. package/lib/public/modules/clay-tool-activity.js +140 -0
  240. package/lib/public/modules/clay-tool-presentation.js +338 -0
  241. package/lib/public/modules/command-palette.js +2 -2
  242. package/lib/public/modules/debate.js +1 -0
  243. package/lib/public/modules/default-ai.js +11 -3
  244. package/lib/public/modules/history-replay-batch.js +5 -1
  245. package/lib/public/modules/home-debate-live.js +2 -0
  246. package/lib/public/modules/home-debate-planning.js +7 -7
  247. package/lib/public/modules/home-mate-avatar-editor.js +127 -208
  248. package/lib/public/modules/home-mate-chat.js +8 -6
  249. package/lib/public/modules/home-mate-creation.js +73 -1
  250. package/lib/public/modules/home-mate-settings.js +19 -12
  251. package/lib/public/modules/markdown.js +4 -0
  252. package/lib/public/modules/mate-avatar-catalog.js +97 -0
  253. package/lib/public/modules/mate-management.js +40 -7
  254. package/lib/public/modules/mcp-connection-view.js +31 -13
  255. package/lib/public/modules/mcp-discovery-view.js +31 -0
  256. package/lib/public/modules/mcp-ui.js +23 -4
  257. package/lib/public/modules/mention.js +3 -0
  258. package/lib/public/modules/pane-browser-bridge.js +20 -0
  259. package/lib/public/modules/personal-settings-dialog.js +7 -1
  260. package/lib/public/modules/profile.js +1 -238
  261. package/lib/public/modules/project-access-recovery.js +17 -0
  262. package/lib/public/modules/project-admin-overview.js +21 -0
  263. package/lib/public/modules/project-mate-navigation.js +99 -14
  264. package/lib/public/modules/search-clay-chat.js +16 -2
  265. package/lib/public/modules/session-folder-layout.js +1 -1
  266. package/lib/public/modules/session-folder-view-menu.js +9 -0
  267. package/lib/public/modules/session-github.js +85 -14
  268. package/lib/public/modules/session-work-primary.js +51 -0
  269. package/lib/public/modules/shared-browser-settings.js +112 -0
  270. package/lib/public/modules/shared-browser-tabs.js +13 -3
  271. package/lib/public/modules/shared-browser.js +27 -6
  272. package/lib/public/modules/sidebar-session-folders-ui.js +1 -1
  273. package/lib/public/modules/split-view.js +2 -0
  274. package/lib/public/modules/sticky-note-markdown.js +72 -6
  275. package/lib/public/modules/sticky-notes-actions.js +30 -0
  276. package/lib/public/modules/sticky-notes-browser.js +9 -2
  277. package/lib/public/modules/sticky-notes-card.js +23 -7
  278. package/lib/public/modules/sticky-notes-checklist.js +71 -0
  279. package/lib/public/modules/sticky-notes-editor.js +108 -44
  280. package/lib/public/modules/sticky-notes-shared.js +50 -2
  281. package/lib/public/modules/sticky-notes-state.js +66 -0
  282. package/lib/public/modules/sticky-notes.js +34 -51
  283. package/lib/public/modules/theme.js +8 -1
  284. package/lib/public/modules/thinking-lifecycle.js +33 -3
  285. package/lib/public/modules/thinking-view.js +23 -1
  286. package/lib/public/modules/tools.js +36 -7
  287. package/lib/public/modules/user-settings-appearance.js +10 -3
  288. package/lib/public/modules/user-settings-editors.js +1 -1
  289. package/lib/public/modules/user-settings-layout.js +11 -2
  290. package/lib/public/modules/user-settings-preferences.js +6 -2
  291. package/lib/public/modules/user-settings-profile.js +12 -2
  292. package/lib/public/modules/user-settings.js +3 -2
  293. package/lib/public/style.css +3 -0
  294. package/lib/public/sw.js +3 -1
  295. package/lib/scheduled-task-records.js +3 -1
  296. package/lib/scheduler.js +2 -0
  297. package/lib/sdk-bridge.js +228 -23
  298. package/lib/sdk-message-processor.js +2 -0
  299. package/lib/sdk-response-recovery.js +155 -0
  300. package/lib/server-admin.js +63 -16
  301. package/lib/server-appearance-settings.js +13 -1
  302. package/lib/server-dm.js +2 -1
  303. package/lib/server-global-ws.js +10 -20
  304. package/lib/server-home-chat-events.js +18 -4
  305. package/lib/server-home-chat.js +12 -16
  306. package/lib/server-home-mate-creation.js +1 -0
  307. package/lib/server-home-mcp-consent.js +14 -0
  308. package/lib/server-mates.js +25 -62
  309. package/lib/server-owner-ui.js +77 -0
  310. package/lib/server-path-access.js +38 -0
  311. package/lib/server-project-removal.js +22 -0
  312. package/lib/server-settings.js +4 -1
  313. package/lib/server.js +144 -41
  314. package/lib/session-config-transition.js +45 -0
  315. package/lib/session-folders.js +7 -2
  316. package/lib/session-github.js +9 -2
  317. package/lib/session-visibility.js +5 -2
  318. package/lib/session-work-primary.js +50 -0
  319. package/lib/sessions.js +13 -0
  320. package/lib/shared-browser-cookie-sync.js +32 -0
  321. package/lib/shared-browser-cookies.js +79 -0
  322. package/lib/shared-browser-runtime.js +25 -9
  323. package/lib/shared-browser-storage-sync.js +43 -0
  324. package/lib/shared-browser-storage.js +101 -0
  325. package/lib/shared-browser-tools.js +2 -1
  326. package/lib/shared-browser-worker.js +46 -10
  327. package/lib/terminal-manager.js +52 -0
  328. package/lib/users-permissions.js +2 -8
  329. package/lib/users.js +4 -0
  330. package/lib/workspace-query-access.js +4 -1
  331. package/lib/ws-schema.js +8 -2
  332. package/lib/yoke/acp-agent-profiles.js +2 -2
  333. package/lib/yoke/adapters/claude.js +47 -8
  334. package/lib/yoke/adapters/codex.js +52 -111
  335. package/lib/yoke/claude-worker-startup.js +28 -0
  336. package/lib/yoke/codex-idle-recovery.js +81 -1
  337. package/lib/yoke/codex-recovery-admission.js +164 -0
  338. package/lib/yoke/session-tool-transport.js +3 -0
  339. package/package.json +5 -1
@@ -1,228 +1,147 @@
1
- // Mate avatar choices hosted inside the current settings dialog.
2
-
3
- import { mateStyleAvatarUrl, mateAvatarUrl, MATE_AVATAR_STYLES } from './avatar.js';
1
+ // Explicit avatar drafts in the existing owner-scoped Mate settings surface.
2
+ import { mateAvatarUrl, mateCatalogAvatarUrl } from './avatar.js';
4
3
  import { showAvatarPositioner } from './profile.js';
5
4
  import { getWs } from './ws-ref.js';
6
5
  import { store } from './store.js';
6
+ import { MATE_AVATAR_COLLECTIONS } from './mate-avatar-catalog.js';
7
7
 
8
- var activeToken = 0;
9
- var activeMateId = null;
10
- var pending = null;
11
- var statusByMate = Object.create(null);
12
- var draftByMate = Object.create(null);
13
-
14
- function mateName(mate) {
15
- var profile = mate && mate.profile ? mate.profile : {};
16
- return profile.displayName || (mate && (mate.displayName || mate.name)) || 'Mate';
17
- }
18
-
19
- function mateProfile(mate) {
20
- return Object.assign({}, mate && mate.profile ? mate.profile : {});
8
+ var timer = null;
9
+ function state() { return store.get('mateAvatarDraft'); }
10
+ function put(value) { store.set({ mateAvatarDraft: value }); }
11
+ function profile(mate) { return Object.assign({}, mate.profile || {}); }
12
+ function currentMate(mate) {
13
+ return (store.get('cachedMatesList') || []).find(function (item) { return item.id === mate.id; }) || mate;
21
14
  }
22
-
23
- function sendProfile(mate, profile, rerender, message) {
24
- draftByMate[mate.id] = profile;
25
- var ws = getWs();
26
- if (!ws || ws.readyState !== 1) {
27
- statusByMate[mate.id] = { kind: 'error', text: 'Clay is offline. Your choice was not saved.' };
28
- rerender();
29
- return false;
15
+ function ensure(mate) {
16
+ var s = state();
17
+ if (!s || s.mateId !== mate.id) {
18
+ s = { mateId: mate.id, selected: (mate.profile || {}).avatarCatalogId || '', dirty: false, requestId: null, error: '', saved: false, uploading: false };
19
+ put(s);
20
+ } else if (!s.dirty && !s.requestId && !s.uploading && s.selected !== ((mate.profile || {}).avatarCatalogId || '')) {
21
+ s = Object.assign({}, s, { selected: (mate.profile || {}).avatarCatalogId || '' }); put(s);
30
22
  }
31
- pending = { mateId: mate.id, style: profile.avatarStyle || 'imprint', seed: profile.avatarSeed || '', custom: profile.avatarCustom || '' };
32
- statusByMate[mate.id] = { kind: 'pending', text: message || 'Saving avatar…' };
33
- ws.send(JSON.stringify({ type: 'mate_update', mateId: mate.id, updates: { profile: profile } }));
34
- rerender();
35
- return true;
36
- }
37
-
38
- function appendStatus(body, mate) {
39
- var state = statusByMate[mate.id];
40
- var status = document.createElement('div');
41
- status.className = 'home-mate-avatar-status' + (state ? ' is-' + state.kind : '');
42
- status.setAttribute('aria-live', 'polite');
43
- status.textContent = state ? state.text : '';
44
- body.appendChild(status);
23
+ return s;
45
24
  }
46
-
47
- function createChoice(label, src, selected) {
48
- var button = document.createElement('button');
49
- button.type = 'button';
50
- button.className = 'home-mate-avatar-choice';
51
- button.setAttribute('aria-label', label + ' avatar');
52
- button.setAttribute('aria-pressed', selected ? 'true' : 'false');
53
- button.title = label;
54
- var image = document.createElement('img');
55
- image.src = src;
56
- image.alt = '';
57
- image.draggable = false;
58
- var text = document.createElement('span');
59
- text.textContent = label;
60
- button.appendChild(image);
61
- button.appendChild(text);
62
- return button;
25
+ function button(label, id, action) {
26
+ var b = document.createElement('button');
27
+ b.type = 'button'; b.textContent = label; b.id = id;
28
+ b.addEventListener('click', action);
29
+ return b;
63
30
  }
64
-
65
- function selectStyle(mate, style, seed, rerender) {
66
- var profile = mateProfile(mate);
67
- profile.avatarStyle = style;
68
- profile.avatarSeed = seed;
69
- profile.avatarCustom = '';
70
- sendProfile(mate, profile, rerender);
31
+ function closePicker(render) {
32
+ clearHomeMateAvatarEditor();
33
+ store.set({ mateSettingsAvatarOpen: false }); render();
34
+ var opener = document.getElementById('mate-profile-avatar');
35
+ if (opener) opener.focus();
71
36
  }
72
-
73
- function uploadBlob(mate, blob, token, rerender) {
74
- pending = null;
75
- statusByMate[mate.id] = { kind: 'pending', text: 'Uploading image…' };
76
- rerender();
77
- blob.arrayBuffer().then(function (buffer) {
78
- return fetch('/api/mate-avatar/' + encodeURIComponent(mate.id), {
79
- method: 'POST',
80
- headers: { 'Content-Type': 'application/octet-stream' },
81
- body: new Uint8Array(buffer),
82
- });
83
- }).then(function (response) {
84
- return response.json().then(function (data) {
85
- if (!response.ok || !data.ok || !data.avatar) throw new Error(data.error || 'Upload failed');
86
- return data.avatar;
87
- });
88
- }).then(function (avatar) {
89
- if (token !== activeToken) return;
90
- var profile = mateProfile(mate);
91
- profile.avatarCustom = avatar;
92
- draftByMate[mate.id] = profile;
93
- statusByMate[mate.id] = { kind: 'saved', text: 'Image saved.' };
94
- var mates = store.get('cachedMatesList') || [];
95
- store.set({ cachedMatesList: mates.map(function (item) {
96
- return item && item.id === mate.id ? Object.assign({}, item, { profile: profile }) : item;
97
- }) });
98
- var ws = getWs();
99
- if (ws && ws.readyState === 1) {
100
- ws.send(JSON.stringify({ type: 'mate_update', mateId: mate.id, updates: { profile: profile } }));
101
- }
102
- rerender();
103
- }).catch(function (error) {
104
- if (token !== activeToken) return;
105
- statusByMate[mate.id] = { kind: 'error', text: error && error.message ? error.message : 'Could not upload this image.' };
106
- rerender();
107
- });
37
+ function save(mate, render) {
38
+ var s = state(); var ws = getWs();
39
+ if (!s || s.requestId || !s.dirty) return;
40
+ if (!ws || ws.readyState !== 1) { put(Object.assign({}, s, { error: 'Clay is offline. Your choice was not saved.' })); render(); return; }
41
+ var requestId = 'mate-avatar-' + Date.now() + '-' + Math.random().toString(36).slice(2);
42
+ var p = profile(currentMate(mate)); p.avatarCatalogId = s.selected; p.avatarCustom = '';
43
+ put(Object.assign({}, s, { requestId: requestId, error: '', saved: false })); render();
44
+ try { ws.send(JSON.stringify({ type: 'mate_update', mateId: mate.id, requestId: requestId, updates: { profile: p } })); }
45
+ catch (error) { failHomeMateAvatar(mate.id, 'Could not send your choice. Try again.', requestId); render(); return; }
46
+ clearTimeout(timer);
47
+ timer = setTimeout(function () { if (failHomeMateAvatar(mate.id, 'No response received. Your choice is retained; try again.', requestId)) render(); }, 15000);
108
48
  }
109
-
110
- function openUpload(mate, input, rerender) {
111
- var file = input.files && input.files[0];
112
- if (!file) return;
113
- var token = ++activeToken;
114
- var image = new Image();
115
- var objectUrl = URL.createObjectURL(file);
49
+ function upload(mate, input, render) {
50
+ var file = input.files && input.files[0]; if (!file) return;
51
+ var image = new Image(); var url = URL.createObjectURL(file); var originalState = state();
52
+ image.onerror = function () {
53
+ URL.revokeObjectURL(url);
54
+ if (state() === originalState) { put(Object.assign({}, originalState, { error: 'Could not read this image.' })); render(); }
55
+ };
116
56
  image.onload = function () {
117
- if (token !== activeToken) {
118
- URL.revokeObjectURL(objectUrl);
119
- return;
120
- }
121
- showAvatarPositioner(image, objectUrl, function (blob) {
122
- URL.revokeObjectURL(objectUrl);
123
- if (token === activeToken) uploadBlob(mate, blob, token, rerender);
57
+ if (state() !== originalState) { URL.revokeObjectURL(url); return; }
58
+ showAvatarPositioner(image, url, function (blob) {
59
+ URL.revokeObjectURL(url);
60
+ if (state() !== originalState) return;
61
+ var uploadingState = Object.assign({}, originalState, { uploading: true, error: '' }); put(uploadingState); render();
62
+ blob.arrayBuffer().then(function (buffer) {
63
+ return fetch('/api/mate-avatar/' + encodeURIComponent(mate.id), { method: 'POST', headers: { 'Content-Type': 'application/octet-stream' }, body: new Uint8Array(buffer) });
64
+ }).then(function (response) {
65
+ return response.json().then(function (data) { if (!response.ok || !data.ok || !data.avatar) throw new Error(data.error || 'Upload failed.'); return data.avatar; });
66
+ }).then(function (avatar) {
67
+ if (state() !== uploadingState) return;
68
+ var p = profile(currentMate(mate)); p.avatarCustom = avatar;
69
+ store.set({ cachedMatesList: (store.get('cachedMatesList') || []).map(function (item) { return item.id === mate.id ? Object.assign({}, item, { profile: p }) : item; }) });
70
+ put(Object.assign({}, uploadingState, { uploading: false, dirty: false, saved: true }));
71
+ // The HTTP upload is persisted; the update also refreshes other owner tabs.
72
+ var ws = getWs();
73
+ if (ws && ws.readyState === 1) {
74
+ try { ws.send(JSON.stringify({ type: 'mate_update', mateId: mate.id, updates: { profile: p } })); }
75
+ catch (error) { /* The acknowledged upload remains saved. */ }
76
+ }
77
+ render();
78
+ }).catch(function (error) {
79
+ if (state() !== uploadingState) return;
80
+ put(Object.assign({}, uploadingState, { uploading: false, error: error.message || 'Could not upload this image.' })); render();
81
+ });
124
82
  });
125
83
  };
126
- image.onerror = function () {
127
- URL.revokeObjectURL(objectUrl);
128
- if (token !== activeToken) return;
129
- statusByMate[mate.id] = { kind: 'error', text: 'Could not read this image.' };
130
- rerender();
131
- };
132
- image.src = objectUrl;
84
+ image.src = url;
133
85
  }
134
-
135
- export function renderHomeMateAvatarEditor(body, mate, rerender) {
86
+ export function renderHomeMateAvatarEditor(body, mate, render, onCancel) {
136
87
  if (!mate) return;
137
- if (activeMateId !== mate.id) {
138
- activeMateId = mate.id;
139
- activeToken++;
140
- }
141
- var profile = draftByMate[mate.id] || mateProfile(mate);
142
- var name = mateName(mate);
143
- var style = profile.avatarStyle || 'imprint';
144
- var seed = profile.avatarSeed || mate.id || name;
145
- var locked = profile.avatarLocked === true;
146
- var section = document.createElement('section');
147
- section.className = 'home-mate-avatar-editor';
148
- var heading = document.createElement('div');
149
- heading.className = 'home-mate-avatar-heading';
150
- var preview = document.createElement('img');
151
- preview.className = 'home-mate-avatar-preview';
152
- preview.src = mateAvatarUrl(Object.assign({}, mate, { profile: profile }), 72);
153
- preview.alt = name + ' avatar';
154
- var copy = document.createElement('div');
155
- var title = document.createElement('h3');
156
- title.textContent = 'Avatar';
157
- var note = document.createElement('p');
158
- note.textContent = locked ? 'This built-in avatar is managed by Clay.' : 'Choose a locally generated style or upload your own image.';
159
- copy.appendChild(title);
160
- copy.appendChild(note);
161
- heading.appendChild(preview);
162
- heading.appendChild(copy);
163
- section.appendChild(heading);
164
- if (!locked) {
165
- var choices = document.createElement('div');
166
- choices.className = 'home-mate-avatar-choices';
167
- choices.setAttribute('role', 'group');
168
- choices.setAttribute('aria-label', 'Avatar for ' + name);
169
- for (var i = 0; i < MATE_AVATAR_STYLES.length; i++) {
170
- (function (entry) {
171
- var choice = createChoice(entry.name, mateStyleAvatarUrl(entry.id, seed, 48), !profile.avatarCustom && style === entry.id);
172
- choice.addEventListener('click', function () { selectStyle(mate, entry.id, seed, rerender); });
173
- choices.appendChild(choice);
174
- })(MATE_AVATAR_STYLES[i]);
175
- }
176
- section.appendChild(choices);
177
- var actions = document.createElement('div');
178
- actions.className = 'home-mate-avatar-actions';
179
- var shuffle = document.createElement('button');
180
- shuffle.type = 'button';
181
- shuffle.textContent = 'Shuffle selected style';
182
- shuffle.disabled = style === 'imprint' || style === 'initial' || !!profile.avatarCustom;
183
- shuffle.addEventListener('click', function () {
184
- selectStyle(mate, style, Math.random().toString(36).substring(2, 10), rerender);
88
+ var s = ensure(mate); var p = profile(mate); var busy = !!s.requestId || s.uploading;
89
+ var section = document.createElement('section'); section.className = 'home-mate-avatar-editor';
90
+ var preview = document.createElement('div'); preview.className = 'home-mate-avatar-selection';
91
+ var image = document.createElement('img'); image.className = 'home-mate-avatar-preview';
92
+ image.src = s.dirty ? mateCatalogAvatarUrl(s.selected) : mateAvatarUrl(mate, 52); image.alt = 'Avatar preview';
93
+ var caption = document.createElement('p'); caption.textContent = s.dirty ? 'Preview' : 'Current avatar';
94
+ preview.appendChild(image); preview.appendChild(caption); section.appendChild(preview);
95
+ if (!p.avatarLocked) {
96
+ MATE_AVATAR_COLLECTIONS.forEach(function (collection) {
97
+ var family = collection.id;
98
+ var heading = document.createElement('h4'); heading.textContent = collection.name;
99
+ heading.id = 'mate-avatar-family-' + family; section.appendChild(heading);
100
+ var grid = document.createElement('div'); grid.className = 'home-mate-avatar-choices';
101
+ grid.setAttribute('role', 'group'); grid.setAttribute('aria-labelledby', heading.id);
102
+ collection.avatars.forEach(function (entry) {
103
+ var id = entry.id;
104
+ var choice = button(id, 'mate-avatar-' + id, function () {
105
+ put(Object.assign({}, state(), { selected: id, dirty: id !== (currentMate(mate).profile || {}).avatarCatalogId || !!(currentMate(mate).profile || {}).avatarCustom, error: '', saved: false }));
106
+ render(); var target = document.getElementById('mate-avatar-' + id); if (target) target.focus({ preventScroll: true });
107
+ });
108
+ choice.textContent = ''; choice.className = 'home-mate-avatar-choice'; choice.disabled = busy;
109
+ choice.setAttribute('aria-label', entry.name + ' avatar'); choice.title = entry.name; choice.setAttribute('aria-pressed', String(s.selected === id && (s.dirty || !p.avatarCustom)));
110
+ var img = document.createElement('img'); img.src = mateCatalogAvatarUrl(id); img.alt = ''; img.draggable = false;
111
+ choice.appendChild(img);
112
+ choice.addEventListener('keydown', function (event) {
113
+ var buttons = Array.from(grid.querySelectorAll('button')); var index = buttons.indexOf(choice);
114
+ var columns = getComputedStyle(grid).gridTemplateColumns.split(' ').length;
115
+ var offset = { ArrowRight: 1, ArrowLeft: -1, ArrowDown: columns, ArrowUp: -columns }[event.key];
116
+ if (offset !== undefined || event.key === 'Home' || event.key === 'End') {
117
+ event.preventDefault(); var next = event.key === 'Home' ? 0 : event.key === 'End' ? 47 : Math.max(0, Math.min(47, index + offset)); buttons[next].focus();
118
+ }
119
+ });
120
+ grid.appendChild(choice);
121
+ });
122
+ section.appendChild(grid);
185
123
  });
186
- var upload = document.createElement('button');
187
- upload.type = 'button';
188
- upload.textContent = profile.avatarCustom ? 'Replace image' : 'Upload image';
189
- var input = document.createElement('input');
190
- input.type = 'file';
191
- input.accept = 'image/png,image/jpeg,image/gif,image/webp';
192
- input.className = 'home-mate-avatar-file-input';
193
- input.hidden = true;
194
- input.tabIndex = -1;
195
- input.setAttribute('aria-hidden', 'true');
196
- input.addEventListener('change', function () { openUpload(mate, input, rerender); });
197
- upload.addEventListener('click', function () { input.click(); });
198
- actions.appendChild(shuffle);
199
- actions.appendChild(upload);
200
- actions.appendChild(input);
201
- section.appendChild(actions);
124
+ var actions = document.createElement('div'); actions.className = 'home-mate-avatar-actions';
125
+ var cancel = button('Cancel', 'mate-avatar-cancel', function () { if (onCancel) onCancel(); else closePicker(render); }); cancel.disabled = busy;
126
+ var saveButton = button('Save avatar', 'mate-avatar-save', function () { save(mate, render); }); saveButton.disabled = busy || !s.dirty;
127
+ saveButton.className = 'primary';
128
+ var input = document.createElement('input'); input.type = 'file'; input.accept = 'image/png,image/jpeg,image/gif,image/webp'; input.hidden = true; input.tabIndex = -1;
129
+ input.className = 'home-mate-avatar-file-input'; input.addEventListener('change', function () { upload(mate, input, render); });
130
+ var uploadButton = button('Upload image', 'mate-avatar-upload', function () { input.click(); }); uploadButton.disabled = busy;
131
+ actions.appendChild(cancel); actions.appendChild(saveButton); actions.appendChild(uploadButton); actions.appendChild(input); section.appendChild(actions);
202
132
  }
203
- appendStatus(section, mate);
204
- body.appendChild(section);
133
+ var status = document.createElement('p'); status.className = 'home-mate-avatar-status' + (s.error ? ' is-error' : '');
134
+ status.setAttribute('aria-live', 'polite'); status.setAttribute('role', s.error ? 'alert' : 'status'); status.textContent = s.error || (s.requestId ? 'Saving avatar…' : s.uploading ? 'Uploading image…' : s.saved ? 'Avatar saved.' : 'Choose a face, then save.');
135
+ (actions || section).appendChild(status); body.appendChild(section);
205
136
  }
206
-
207
- export function confirmHomeMateAvatar(mate) {
208
- if (!mate || !pending || pending.mateId !== mate.id) return false;
209
- var profile = mateProfile(mate);
210
- if ((profile.avatarStyle || 'imprint') !== pending.style || (profile.avatarSeed || '') !== pending.seed || (profile.avatarCustom || '') !== pending.custom) return false;
211
- pending = null;
212
- delete draftByMate[mate.id];
213
- statusByMate[mate.id] = { kind: 'saved', text: 'Avatar saved.' };
214
- return true;
137
+ export function hasHomeMateAvatarDraft() { var s = state(); return !!s && s.dirty; }
138
+ export function homeMateAvatarSaving() { var s = state(); return !!s && (!!s.requestId || s.uploading); }
139
+ export function confirmHomeMateAvatar(mate, requestId) {
140
+ var s = state(); if (!mate || !s || s.mateId !== mate.id || !s.requestId || s.requestId !== requestId || (mate.profile || {}).avatarCatalogId !== s.selected) return false;
141
+ clearTimeout(timer); put(Object.assign({}, s, { requestId: null, dirty: false, saved: true, error: '' })); return true;
215
142
  }
216
-
217
- export function failHomeMateAvatar(mateId, message) {
218
- if (!pending || !mateId || pending.mateId !== mateId) return false;
219
- statusByMate[pending.mateId] = { kind: 'error', text: message || 'Could not save this avatar.' };
220
- pending = null;
221
- return true;
222
- }
223
-
224
- export function clearHomeMateAvatarEditor() {
225
- activeToken++;
226
- activeMateId = null;
227
- pending = null;
143
+ export function failHomeMateAvatar(mateId, message, requestId) {
144
+ var s = state(); if (!s || !s.requestId || s.mateId !== mateId || s.requestId !== requestId) return false;
145
+ clearTimeout(timer); put(Object.assign({}, s, { requestId: null, error: message || 'Could not save this avatar.' })); return true;
228
146
  }
147
+ export function clearHomeMateAvatarEditor() { clearTimeout(timer); put(null); }
@@ -340,24 +340,26 @@ export function handleHomeDebateTranscript(msg) {
340
340
  if (!isCurrentSessionMessage(msg)) return;
341
341
  markHomeChatActivity();
342
342
  settleHomeDebateLaunch(msg);
343
- if (msg.type === "home_debate_proposal" || msg.type === "home_debate_question" || msg.type === "home_debate_event" || msg.type === "home_mate_creation_proposal" || msg.type === "home_mate_creation_question") messages = finalizeHomeAssistant(messages, streamingText, "", Date.now());
343
+ var questionMessage = ["home_debate_question", "home_mcp_import_question", "home_mate_question"].indexOf(msg.type) >= 0;
344
+ var questionResolved = ["home_debate_question_resolved", "home_mcp_import_question_resolved", "home_mate_question_resolved"].indexOf(msg.type) >= 0;
345
+ if (msg.type === "home_debate_proposal" || questionMessage || msg.type === "home_debate_event" || msg.type === "home_mate_creation_proposal" || msg.type === "home_mate_creation_question") messages = finalizeHomeAssistant(messages, streamingText, "", Date.now());
344
346
  if (msg.type === "home_debate_event") messages = applyHomeDebateEvent(messages, msg);
345
347
  if (msg.type === "home_debate_proposal") messages = applyHomeDebateProposal(messages, msg);
346
348
  if (msg.type === "home_debate_proposal_resolved") messages = resolveHomeDebateProposal(messages, msg);
347
- if (msg.type === "home_debate_question") messages = applyHomeDebateQuestion(messages, msg);
348
- if (msg.type === "home_debate_question_resolved") messages = resolveHomeDebateQuestion(messages, msg);
349
+ if (questionMessage) messages = applyHomeDebateQuestion(messages, msg);
350
+ if (questionResolved) messages = resolveHomeDebateQuestion(messages, msg);
349
351
  if (msg.type === "home_mate_creation_question") messages = applyHomeDebateQuestion(messages, msg);
350
352
  if (msg.type === "home_mate_creation_question_resolved") messages = resolveHomeDebateQuestion(messages, msg);
351
353
  if (msg.type === "home_mate_creation_proposal") messages = applyHomeMateProposal(messages, msg);
352
354
  if (msg.type === "home_mate_creation_proposal_resolved") messages = resolveHomeMateProposal(messages, msg);
353
355
  if (msg.type === "home_mate_error" && /^question_/.test(msg.code || "")) messages = failHomeDebateQuestion(messages, msg);
354
- if (msg.type === "home_debate_proposal" || msg.type === "home_debate_question" || msg.type === "home_debate_event" || msg.type === "home_mate_creation_proposal" || msg.type === "home_mate_creation_question") { clearHomeDebatePlanningPending(); streaming = false; streamingText = ""; }
355
- if ((msg.type === "home_debate_question_resolved" || msg.type === "home_mate_creation_question_resolved") && msg.status === "expired") clearHomeDebatePlanningPending();
356
+ if (msg.type === "home_debate_proposal" || questionMessage || msg.type === "home_debate_event" || msg.type === "home_mate_creation_proposal" || msg.type === "home_mate_creation_question") { clearHomeDebatePlanningPending(); streaming = false; streamingText = ""; }
357
+ if ((questionResolved || msg.type === "home_mate_creation_question_resolved") && msg.status === "expired") clearHomeDebatePlanningPending();
356
358
  renderHomeChat();
357
359
  if (msg.proposalId) restoreHomeDebateProposalFocus(msg.proposalId);
358
360
  if (msg.proposalId && msg.type === "home_mate_creation_proposal_resolved") restoreHomeMateProposalFocus(msg.proposalId);
359
361
  if (msg.toolId && msg.type === "home_mate_creation_question") restoreHomeDebateQuestionFocus(msg.toolId);
360
- if (msg.toolId && msg.type === "home_debate_question_resolved") restoreHomeDebateQuestionFocus(msg.toolId);
362
+ if (msg.toolId && questionResolved) restoreHomeDebateQuestionFocus(msg.toolId);
361
363
  if (msg.toolId && msg.type === "home_mate_error") restoreHomeDebateQuestionFocus(msg.toolId);
362
364
  }
363
365
  export function handleHomeMateDelta(msg) {
@@ -7,6 +7,67 @@ function textElement(tag, className, text) {
7
7
  return element;
8
8
  }
9
9
 
10
+ function scheduleDate(value) {
11
+ var parts = String(value || "").split("-").map(Number);
12
+ var date = new Date(Date.UTC(parts[0], parts[1] - 1, parts[2]));
13
+ return Number.isFinite(date.getTime()) ? date.toLocaleDateString("en-US", { month: "long", day: "numeric", year: "numeric", timeZone: "UTC" }) : value;
14
+ }
15
+
16
+ function exactScheduleFact(proposal) {
17
+ var intent = proposal.scheduleIntent;
18
+ var schedule = intent.scheduleRule;
19
+ var recurrence = schedule.recurrence;
20
+ var timing = "On " + scheduleDate(schedule.anchorDate);
21
+ if (recurrence) {
22
+ var days = (recurrence.weekdays || []).map(function (day) { return ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"][day]; });
23
+ if (recurrence.unit === "week" && recurrence.every === 1 && (recurrence.weekdays || []).join(",") === "1,2,3,4,5") timing = "Every weekday";
24
+ else if (recurrence.unit === "week" && recurrence.every === 1 && days.length) timing = "Every " + days.join(", ");
25
+ else timing = "Every " + (recurrence.every === 1 ? recurrence.unit : recurrence.every + " " + recurrence.unit + "s") + (days.length ? " on " + days.join(", ") : "");
26
+ }
27
+ timing += " at " + schedule.time + " (" + (proposal.scheduleTimezone || "server local") + ")";
28
+ if (recurrence) timing += ", starting " + scheduleDate(schedule.anchorDate);
29
+ if (schedule.recurrenceEnd) timing += schedule.recurrenceEnd.type === "after" ? "; for " + schedule.recurrenceEnd.count + " deliveries" : "; through " + scheduleDate(schedule.recurrenceEnd.date);
30
+ if (schedule.interval) {
31
+ var interval = schedule.interval;
32
+ timing += "; every " + interval.every + " " + interval.unit + (interval.every === 1 ? "" : "s");
33
+ timing += !interval.end ? " until end of day" : interval.end.type === "after" ? ", " + interval.end.count + " deliveries per day" : " until " + interval.end.time;
34
+ }
35
+ return timing + ": " + intent.message;
36
+ }
37
+
38
+ function friendlyTime(value) {
39
+ var parts = value.split(":");
40
+ var hour = Number(parts[0]);
41
+ if (parts[1] === "00" && (hour === 0 || hour === 12)) return hour === 0 ? "midnight" : "noon";
42
+ return (hour % 12 || 12) + (parts[1] === "00" ? "" : ":" + parts[1]) + (hour < 12 ? " AM" : " PM");
43
+ }
44
+
45
+ function scheduleSummary(proposal) {
46
+ var intent = proposal.scheduleIntent;
47
+ var schedule = intent.scheduleRule;
48
+ var recurrence = schedule.recurrence;
49
+ var timing = scheduleDate(schedule.anchorDate);
50
+ if (recurrence) {
51
+ var days = (recurrence.weekdays || []).map(function (day) { return ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"][day]; });
52
+ if (recurrence.unit === "week" && recurrence.every === 1 && (recurrence.weekdays || []).join(",") === "1,2,3,4,5") timing = "Every weekday";
53
+ else if (recurrence.unit === "week" && recurrence.every === 1 && days.length) timing = "Every " + days.join(", ");
54
+ else timing = "Every " + (recurrence.every === 1 ? recurrence.unit : recurrence.every + " " + recurrence.unit + "s") + (days.length ? " on " + days.join(", ") : "");
55
+ }
56
+ var hour = Number(schedule.time.split(":")[0]);
57
+ var time = friendlyTime(schedule.time);
58
+ var period = hour >= 5 && hour < 12 ? "morning" : hour >= 13 && hour < 17 ? "afternoon" : hour >= 17 && hour < 21 ? "evening" : "";
59
+ if (recurrence && period && recurrence.unit === "day" && recurrence.every === 1) timing = "Every " + period + " at " + time.replace(/ (AM|PM)$/, "");
60
+ else if (recurrence && period && timing === "Every weekday") timing += " " + period + " at " + time.replace(/ (AM|PM)$/, "");
61
+ else timing += " at " + time;
62
+ if (schedule.recurrenceEnd) timing += schedule.recurrenceEnd.type === "after" ? ", for " + schedule.recurrenceEnd.count + (schedule.recurrenceEnd.count === 1 ? " delivery" : " deliveries") : ", through " + scheduleDate(schedule.recurrenceEnd.date);
63
+ if (schedule.interval) {
64
+ var interval = schedule.interval;
65
+ timing += ", then every " + interval.every + " " + interval.unit + (interval.every === 1 ? "" : "s");
66
+ timing += !interval.end ? " until the end of the day" : interval.end.type === "after" ? ", " + interval.end.count + " deliveries a day" : " until " + friendlyTime(interval.end.time);
67
+ }
68
+ return timing + ": " + intent.message;
69
+ }
70
+
10
71
  export function createHomeMateProposalCard(message, respond, openMate) {
11
72
  var proposal = message.proposal || {};
12
73
  var card = document.createElement("section");
@@ -21,12 +82,23 @@ export function createHomeMateProposalCard(message, respond, openMate) {
21
82
  function fact(label, value) {
22
83
  if (!value) return;
23
84
  facts.appendChild(textElement("dt", "", label));
24
- facts.appendChild(textElement("dd", "", value));
85
+ var description = textElement("dd", "", typeof value === "string" ? value : "");
86
+ if (typeof value !== "string") description.appendChild(value);
87
+ facts.appendChild(description);
88
+ return description;
25
89
  }
26
90
  fact("Relationship", proposal.relationship);
27
91
  fact("Activities", Array.isArray(proposal.activities) ? proposal.activities.join(", ") : "");
28
92
  fact("Communication", Array.isArray(proposal.communicationStyle) ? proposal.communicationStyle.join(", ") : "");
29
93
  fact("Autonomy", proposal.autonomy);
94
+ if (proposal.scheduleIntent && proposal.scheduleIntent.message && proposal.scheduleIntent.scheduleRule) {
95
+ var scheduleItem = fact("Schedule", textElement("span", "home-mate-proposal-schedule-summary", scheduleSummary(proposal)));
96
+ var exactTiming = document.createElement("details");
97
+ exactTiming.className = "home-mate-proposal-schedule-details";
98
+ exactTiming.appendChild(textElement("summary", "", "Exact timing"));
99
+ exactTiming.appendChild(textElement("div", "", exactScheduleFact(proposal)));
100
+ scheduleItem.appendChild(exactTiming);
101
+ }
30
102
  card.appendChild(facts);
31
103
  if (proposal.identityMarkdown) {
32
104
  var details = document.createElement("details");
@@ -8,7 +8,7 @@ import { store } from './store.js';
8
8
  import { getWs } from './ws-ref.js';
9
9
  import { iconHtml, refreshIcons } from './icons.js';
10
10
  import { confirmMateRemoval } from './mate-management.js';
11
- import { renderHomeMateAvatarEditor, confirmHomeMateAvatar, failHomeMateAvatar, clearHomeMateAvatarEditor } from './home-mate-avatar-editor.js';
11
+ import { hasHomeMateAvatarDraft, homeMateAvatarSaving, renderHomeMateAvatarEditor, confirmHomeMateAvatar, failHomeMateAvatar, clearHomeMateAvatarEditor } from './home-mate-avatar-editor.js';
12
12
  import { resetHomeMateModelPicker, clearHomeMateModelPicker, hasHomeMateModelDraft, requestHomeMateModels, renderHomeMateModelPicker, applyHomeMateModelsState, applyHomeMateModelResult } from './home-mate-model-picker.js';
13
13
 
14
14
  var dialog = null;
@@ -73,16 +73,19 @@ function renderGeneral(body, mate) {
73
73
  description.textContent = "Manage " + getMateName(mate) + "’s identity and appearance.";
74
74
  body.appendChild(description);
75
75
  renderMateProfile(body, mate, renderDialogContent, function () {
76
+ if (homeMateAvatarSaving()) return;
77
+ if (store.get('mateSettingsAvatarOpen') && hasHomeMateAvatarDraft()) {
78
+ showConfirm('Discard this avatar choice?', function () { clearHomeMateAvatarEditor(); store.set({mateSettingsAvatarOpen:false}); renderDialogContent(); document.getElementById('mate-profile-avatar').focus(); }, 'Discard choice');
79
+ return;
80
+ }
81
+ clearHomeMateAvatarEditor();
76
82
  store.set({mateSettingsAvatarOpen: !store.get('mateSettingsAvatarOpen')});
77
83
  renderDialogContent();
78
84
  });
79
85
  if (store.get('mateSettingsAvatarOpen')) {
80
86
  var appearance = document.createElement("section");
81
87
  appearance.className = "mate-settings-appearance";
82
- renderHomeMateAvatarEditor(appearance, mate, renderDialogContent);
83
- var done = document.createElement('button'); done.type = 'button'; done.className = 'mate-settings-button'; done.textContent = 'Done';
84
- done.addEventListener('click',function () { store.set({mateSettingsAvatarOpen:false}); renderDialogContent(); document.getElementById('mate-profile-avatar').focus(); });
85
- appearance.appendChild(done);
88
+ renderHomeMateAvatarEditor(appearance, mate, renderDialogContent, store.get('mateSettingsAvatarDirect') ? function () { clearHomeMateAvatarEditor(); renderDialogContent(); closeHomeMateSettings(); } : null);
86
89
  body.appendChild(appearance);
87
90
  }
88
91
  renderMateColorPicker(body, mate, renderDialogContent);
@@ -118,7 +121,7 @@ function requestSection(section) {
118
121
  function savePending() {
119
122
  var prompt = store.get('mateInstructionsDraft');
120
123
  var model = store.get('mateModelDraft');
121
- return mateColorSaving() || mateProfileSaving() || !!((prompt && prompt.status === 'saving') || (model && model.saveId));
124
+ return homeMateAvatarSaving() || mateColorSaving() || mateProfileSaving() || !!((prompt && prompt.status === 'saving') || (model && model.saveId));
122
125
  }
123
126
 
124
127
  function renderDialogContent() {
@@ -199,7 +202,7 @@ function handleDialogKeydown(event) {
199
202
 
200
203
  export function closeHomeMateSettings(force) {
201
204
  if (force !== true && savePending()) return false;
202
- if (force !== true && (hasMateProfileDraft() || hasMateInstructionDraft() || hasHomeMateModelDraft())) {
205
+ if (force !== true && (hasHomeMateAvatarDraft() || hasMateProfileDraft() || hasMateInstructionDraft() || hasHomeMateModelDraft())) {
203
206
  var previous = document.activeElement;
204
207
  showConfirm("Discard unsaved Mate settings?", function () { closeHomeMateSettings(true); }, "Discard changes", false, function () { if (previous && previous.isConnected) previous.focus(); });
205
208
  document.getElementById("confirm-cancel").focus();
@@ -215,7 +218,7 @@ export function closeHomeMateSettings(force) {
215
218
  memoryState = null;
216
219
  knowledgeState = null;
217
220
  modelRequested = false;
218
- store.set({mateSettingsAvatarOpen:false});
221
+ store.set({mateSettingsAvatarOpen:false, mateSettingsAvatarDirect:false});
219
222
  resetMateProfile();
220
223
  resetMateColor();
221
224
  clearHomeMateModelPicker();
@@ -230,12 +233,13 @@ export function closeHomeMateSettings(force) {
230
233
  export function openHomeMateSettings(mateId, opener, options) {
231
234
  var mate = getMate(mateId);
232
235
  if (!mate || (dialog && savePending())) return false;
233
- if (dialog && (hasMateProfileDraft() || hasMateInstructionDraft() || hasHomeMateModelDraft())) {
236
+ if (dialog && (hasHomeMateAvatarDraft() || hasMateProfileDraft() || hasMateInstructionDraft() || hasHomeMateModelDraft())) {
234
237
  showConfirm("Discard unsaved Mate settings?", function () { closeHomeMateSettings(true); openHomeMateSettings(mateId, opener, options); }, "Discard changes", false);
235
238
  return false;
236
239
  }
237
240
  closeHomeMateSettings(true);
238
241
  resetMateInstructions(mateId);
242
+ store.set({ mateSettingsAvatarOpen: !!(options && options.avatar && !(mate.profile || {}).avatarLocked), mateSettingsAvatarDirect: !!(options && options.avatar) });
239
243
  dialogMateId = mateId;
240
244
  dialogOpener = opener || document.activeElement;
241
245
  dialogSection = options && ["model", "prompt"].indexOf(options.section) !== -1 ? options.section : "general";
@@ -308,7 +312,10 @@ export function openHomeMateSettings(mateId, opener, options) {
308
312
  requestSection(dialogSection);
309
313
  loadMateInstructions(renderDialogContent);
310
314
  refreshIcons();
311
- requestAnimationFrame(function () { close.focus({ preventScroll: true }); });
315
+ requestAnimationFrame(function () {
316
+ var first = options && options.avatar && dialog ? (dialog.querySelector('.home-mate-avatar-choice[aria-pressed="true"]:not(:disabled)') || dialog.querySelector('.home-mate-avatar-choice:not(:disabled)')) : null;
317
+ (first || close).focus();
318
+ });
312
319
  return true;
313
320
  }
314
321
 
@@ -354,7 +361,7 @@ export function syncHomeMateSettingsTarget(msg) {
354
361
  var mate = getMate(dialogMateId);
355
362
  if (!mate) closeHomeMateSettings();
356
363
  else {
357
- confirmHomeMateAvatar(mate);
364
+ confirmHomeMateAvatar(mate, msg && msg.requestId);
358
365
  dialog.querySelector(".home-mate-settings-header span").textContent = getMateName(mate);
359
366
  renderDialogContent();
360
367
  }
@@ -364,7 +371,7 @@ export function handleHomeMateAvatarError(msg) {
364
371
  if (!dialog || !msg) return false;
365
372
  var colorHandled = applyMateColorResult(msg);
366
373
  var profileHandled = applyMateProfile(msg);
367
- if (!colorHandled && !profileHandled && !failHomeMateAvatar(msg.mateId, msg.error)) return false;
374
+ if (!colorHandled && !profileHandled && !failHomeMateAvatar(msg.mateId, msg.error, msg.requestId)) return false;
368
375
  renderDialogContent();
369
376
  return true;
370
377
  }
@@ -1,3 +1,4 @@
1
+ import { renderBrowserLink, enhanceBrowserLinks } from './clay-browser-links.js';
1
2
  import { renderWireframeBlocks } from './wireframe-chat.js';
2
3
  import { copyToClipboard, escapeHtml } from './utils.js';
3
4
  import { refreshIcons } from './icons.js';
@@ -14,6 +15,8 @@ marked.use({ gfm: true, breaks: false });
14
15
  marked.use({
15
16
  renderer: {
16
17
  link({ href, title, tokens, text }) {
18
+ var browserLink = renderBrowserLink(href, title, text);
19
+ if (browserLink) return browserLink;
17
20
  var recordLink = renderClayRecordLink(href, text);
18
21
  if (recordLink) return recordLink;
19
22
  var localLink = renderLocalFileLink(href, title, text);
@@ -103,6 +106,7 @@ export function enhanceClaySessionLinks(root) {
103
106
  }
104
107
  enhanceClayLogLinks(root);
105
108
  enhanceClayFileLinks(root);
109
+ enhanceBrowserLinks(root);
106
110
  }
107
111
 
108
112
  /**