clay-server 5.1.0-beta.1 → 6.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. package/LICENSES/README.md +4 -0
  2. package/LICENSES/dicebear-avatars.md +16 -0
  3. package/bin/cli.js +9 -91
  4. package/lib/daemon-project-access.js +7 -1
  5. package/lib/daemon-settings.js +11 -0
  6. package/lib/daemon.js +10 -37
  7. package/lib/dicebear-avatar.js +62 -0
  8. package/lib/knowledge-db-service.js +175 -0
  9. package/lib/knowledge-db-validation.js +0 -0
  10. package/lib/knowledge-document-index.js +49 -0
  11. package/lib/knowledge-draft-store.js +56 -0
  12. package/lib/knowledge-file-store.js +199 -0
  13. package/lib/knowledge-markdown.js +128 -0
  14. package/lib/linear-store.js +35 -8
  15. package/lib/mate-knowledge-mcp-server.js +29 -6
  16. package/lib/mate-knowledge-service.js +23 -2
  17. package/lib/mate-knowledge-sync.js +36 -21
  18. package/lib/mate-workspace-color.js +4 -0
  19. package/lib/mates.js +18 -0
  20. package/lib/os-users.js +5 -16
  21. package/lib/project-connection.js +36 -9
  22. package/lib/project-debate-proposal.js +6 -0
  23. package/lib/project-debate-workspace.js +43 -0
  24. package/lib/project-debate.js +1 -0
  25. package/lib/project-filesystem.js +21 -39
  26. package/lib/project-knowledge.js +315 -136
  27. package/lib/project-mate-creation-proposal.js +1 -0
  28. package/lib/project-mate-interaction.js +2 -2
  29. package/lib/project-server-settings.js +31 -0
  30. package/lib/project-sessions.js +92 -182
  31. package/lib/project-user-knowledge.js +60 -0
  32. package/lib/project.js +59 -18
  33. package/lib/public/app.js +79 -71
  34. package/lib/public/clay-studio-wordmark-bold.svg +20 -0
  35. package/lib/public/css/clay-workspace.css +105 -0
  36. package/lib/public/css/debates-workbench.css +47 -0
  37. package/lib/public/css/home-debate-live.css +26 -0
  38. package/lib/public/css/home-hub.css +3 -1
  39. package/lib/public/css/home-mate-model-picker.css +15 -131
  40. package/lib/public/css/home-mate-settings.css +168 -16
  41. package/lib/public/css/icon-strip.css +249 -32
  42. package/lib/public/css/input.css +3 -1
  43. package/lib/public/css/mate-conversation.css +176 -0
  44. package/lib/public/css/mate-knowledge-workbench.css +118 -0
  45. package/lib/public/css/mate-workspace-color.css +16 -0
  46. package/lib/public/css/mates.css +28 -28
  47. package/lib/public/css/messages.css +1 -114
  48. package/lib/public/css/mobile-nav.css +16 -16
  49. package/lib/public/css/overlays.css +8 -0
  50. package/lib/public/css/project-logs.css +7 -0
  51. package/lib/public/css/project-mate-creation.css +105 -0
  52. package/lib/public/css/project-settings-modal.css +56 -0
  53. package/lib/public/css/right-workbench.css +3 -3
  54. package/lib/public/css/server-settings-system.css +125 -0
  55. package/lib/public/css/session-folders.css +80 -21
  56. package/lib/public/css/session-github.css +25 -16
  57. package/lib/public/css/settings-ui.css +247 -22
  58. package/lib/public/css/shared-browser.css +2 -0
  59. package/lib/public/css/sidebar.css +183 -107
  60. package/lib/public/css/title-bar.css +156 -1
  61. package/lib/public/css/worker-delegation.css +47 -0
  62. package/lib/public/css/worker-proposal.css +72 -150
  63. package/lib/public/css/you.css +74 -0
  64. package/lib/public/index.html +203 -547
  65. package/lib/public/modules/account-island.js +232 -0
  66. package/lib/public/modules/admin-settings-email.js +197 -0
  67. package/lib/public/modules/admin-settings-invitations.js +183 -0
  68. package/lib/public/modules/admin-settings-projects.js +198 -0
  69. package/lib/public/modules/admin-settings-ui.js +100 -0
  70. package/lib/public/modules/admin-settings-users.js +331 -0
  71. package/lib/public/modules/admin.js +19 -949
  72. package/lib/public/modules/app-cursors.js +19 -43
  73. package/lib/public/modules/app-debate-ui.js +4 -3
  74. package/lib/public/modules/app-dm.js +0 -2
  75. package/lib/public/modules/app-header.js +106 -0
  76. package/lib/public/modules/app-message-router.js +12 -3
  77. package/lib/public/modules/app-messages.js +47 -61
  78. package/lib/public/modules/app-notifications.js +4 -7
  79. package/lib/public/modules/app-projects.js +5 -38
  80. package/lib/public/modules/app-rate-limit.js +70 -12
  81. package/lib/public/modules/app-rendering.js +12 -13
  82. package/lib/public/modules/avatar.js +25 -9
  83. package/lib/public/modules/chat-render-runtime.js +16 -4
  84. package/lib/public/modules/command-palette.js +2 -15
  85. package/lib/public/modules/context-sources.js +27 -18
  86. package/lib/public/modules/cursor-sharing-preference.js +3 -3
  87. package/lib/public/modules/debate-controls.js +299 -0
  88. package/lib/public/modules/debate.js +2 -0
  89. package/lib/public/modules/debates-workbench.js +339 -0
  90. package/lib/public/modules/default-ai.js +88 -65
  91. package/lib/public/modules/header-tui-font.js +10 -20
  92. package/lib/public/modules/home-debate-controls.js +18 -289
  93. package/lib/public/modules/home-mate-avatar-editor.js +228 -0
  94. package/lib/public/modules/home-mate-color-picker.js +66 -0
  95. package/lib/public/modules/home-mate-creation.js +1 -1
  96. package/lib/public/modules/home-mate-instructions.js +90 -0
  97. package/lib/public/modules/home-mate-model-picker.js +80 -271
  98. package/lib/public/modules/home-mate-profile-editor.js +72 -0
  99. package/lib/public/modules/home-mate-settings.js +118 -125
  100. package/lib/public/modules/home-shell.js +3 -0
  101. package/lib/public/modules/home-surface-boot.js +1 -9
  102. package/lib/public/modules/icon-strip-tooltip.js +95 -0
  103. package/lib/public/modules/knowledge-db-edit-state.js +35 -0
  104. package/lib/public/modules/knowledge-db-workbench.js +234 -0
  105. package/lib/public/modules/knowledge-history-state.js +55 -0
  106. package/lib/public/modules/knowledge-markdown-editor.js +57 -0
  107. package/lib/public/modules/knowledge-preview-links.js +70 -0
  108. package/lib/public/modules/knowledge-workbench-shell.js +21 -0
  109. package/lib/public/modules/linear-settings.js +3 -2
  110. package/lib/public/modules/markdown.js +5 -6
  111. package/lib/public/modules/mate-knowledge-workbench-state.js +174 -0
  112. package/lib/public/modules/mate-knowledge-workbench.js +500 -0
  113. package/lib/public/modules/mate-knowledge.js +11 -3
  114. package/lib/public/modules/mate-management.js +54 -10
  115. package/lib/public/modules/mate-sidebar.js +11 -1
  116. package/lib/public/modules/mate-workspace-color.js +33 -0
  117. package/lib/public/modules/mention.js +5 -13
  118. package/lib/public/modules/notification-settings.js +81 -0
  119. package/lib/public/modules/notifications.js +44 -28
  120. package/lib/public/modules/personal-settings-dialog.js +51 -0
  121. package/lib/public/modules/profile.js +66 -24
  122. package/lib/public/modules/project-chat-identity.js +79 -0
  123. package/lib/public/modules/project-debate-planning-workspace.js +250 -0
  124. package/lib/public/modules/project-debate-workspace.js +185 -0
  125. package/lib/public/modules/project-header-identity.js +127 -0
  126. package/lib/public/modules/project-logs-render.js +9 -4
  127. package/lib/public/modules/project-mate-creation-workspace.js +414 -0
  128. package/lib/public/modules/project-mate-navigation.js +238 -0
  129. package/lib/public/modules/project-session-navigation.js +29 -0
  130. package/lib/public/modules/project-settings-environment.js +106 -0
  131. package/lib/public/modules/project-settings.js +72 -628
  132. package/lib/public/modules/project-switcher.js +6 -8
  133. package/lib/public/modules/projects-hub-view.js +5 -6
  134. package/lib/public/modules/projects-hub.js +2 -6
  135. package/lib/public/modules/qrcode.js +27 -8
  136. package/lib/public/modules/search-clay-chat.js +8 -11
  137. package/lib/public/modules/server-settings-controls.js +316 -0
  138. package/lib/public/modules/server-settings-environment.js +194 -0
  139. package/lib/public/modules/server-settings-navigation.js +39 -0
  140. package/lib/public/modules/server-settings-state.js +54 -0
  141. package/lib/public/modules/server-settings.js +116 -788
  142. package/lib/public/modules/server-storage-settings.js +84 -0
  143. package/lib/public/modules/session-activity-tooltip.js +12 -0
  144. package/lib/public/modules/session-create-form.js +86 -14
  145. package/lib/public/modules/session-create.js +50 -7
  146. package/lib/public/modules/session-github.js +2 -1
  147. package/lib/public/modules/session-list-visibility.js +41 -0
  148. package/lib/public/modules/session-presence-ui.js +3 -0
  149. package/lib/public/modules/session-title-marquee.js +51 -0
  150. package/lib/public/modules/settings-env-values.js +34 -0
  151. package/lib/public/modules/settings-select.js +121 -0
  152. package/lib/public/modules/shared-browser.js +10 -7
  153. package/lib/public/modules/sidebar-mates.js +3 -50
  154. package/lib/public/modules/sidebar-mobile.js +63 -60
  155. package/lib/public/modules/sidebar-projects.js +84 -97
  156. package/lib/public/modules/sidebar-session-folders-ui.js +2 -2
  157. package/lib/public/modules/sidebar-session-hierarchy.js +149 -70
  158. package/lib/public/modules/sidebar-sessions.js +49 -50
  159. package/lib/public/modules/sidebar.js +11 -13
  160. package/lib/public/modules/stt-picker.js +14 -5
  161. package/lib/public/modules/terminal-prefs.js +22 -0
  162. package/lib/public/modules/theme.js +88 -52
  163. package/lib/public/modules/thinking-lifecycle.js +7 -6
  164. package/lib/public/modules/tool-palette-order.js +4 -3
  165. package/lib/public/modules/tool-palette.js +13 -3
  166. package/lib/public/modules/tools.js +22 -22
  167. package/lib/public/modules/user-settings-account.js +318 -0
  168. package/lib/public/modules/user-settings-appearance.js +55 -0
  169. package/lib/public/modules/user-settings-editors.js +24 -0
  170. package/lib/public/modules/user-settings-integrations.js +104 -0
  171. package/lib/public/modules/user-settings-layout.js +80 -0
  172. package/lib/public/modules/user-settings-preferences.js +48 -0
  173. package/lib/public/modules/user-settings-profile.js +53 -0
  174. package/lib/public/modules/user-settings-themes.js +38 -0
  175. package/lib/public/modules/user-settings-voice.js +75 -0
  176. package/lib/public/modules/user-settings.js +53 -480
  177. package/lib/public/modules/worker-proposal.js +42 -8
  178. package/lib/public/modules/worker-runtime-picker.js +115 -0
  179. package/lib/public/modules/you-activity.js +121 -0
  180. package/lib/public/modules/you.js +251 -0
  181. package/lib/public/style.css +20 -8
  182. package/lib/public/vendor/codemirror/LICENSE +21 -0
  183. package/lib/public/vendor/codemirror/codemirror.css +344 -0
  184. package/lib/public/vendor/codemirror/codemirror.js +9884 -0
  185. package/lib/public/vendor/codemirror/continuelist.js +101 -0
  186. package/lib/public/vendor/codemirror/markdown.js +886 -0
  187. package/lib/public/vendor/codemirror/show-hint.css +37 -0
  188. package/lib/public/vendor/codemirror/show-hint.js +523 -0
  189. package/lib/sdk-bridge.js +15 -3
  190. package/lib/sdk-message-processor.js +16 -4
  191. package/lib/sdk-quota-snapshot.js +37 -0
  192. package/lib/server-admin.js +11 -0
  193. package/lib/server-appearance-settings.js +71 -0
  194. package/lib/server-home-chat-events.js +1 -1
  195. package/lib/server-home-chat.js +25 -3
  196. package/lib/server-home-debate-planning.js +4 -1
  197. package/lib/server-home-debates.js +2 -0
  198. package/lib/server-home-mate-creation.js +4 -1
  199. package/lib/server-linear.js +14 -8
  200. package/lib/server-mate-instructions.js +44 -0
  201. package/lib/server-mates.js +12 -3
  202. package/lib/server-notification-settings.js +67 -0
  203. package/lib/server-palette.js +1 -1
  204. package/lib/server-settings.js +69 -1
  205. package/lib/server-storage.js +56 -0
  206. package/lib/server-user-knowledge.js +119 -0
  207. package/lib/server.js +31 -3
  208. package/lib/session-visibility.js +7 -2
  209. package/lib/sessions.js +34 -9
  210. package/lib/startup-projects.js +40 -0
  211. package/lib/storage-usage.js +127 -0
  212. package/lib/user-knowledge-service.js +167 -0
  213. package/lib/users-permissions.js +7 -0
  214. package/lib/users-preferences.js +0 -74
  215. package/lib/users-project-mate-filter-preferences.js +39 -0
  216. package/lib/users.js +6 -4
  217. package/lib/workspace-query-access.js +4 -1
  218. package/lib/ws-schema.js +52 -4
  219. package/lib/yoke/adapters/codex.js +8 -45
  220. package/lib/yoke/codex-model-catalog.js +40 -0
  221. package/lib/yoke/codex-rate-limits.js +26 -0
  222. package/package.json +12 -3
@@ -0,0 +1,84 @@
1
+ import { store } from './store.js';
2
+
3
+ function size(bytes) {
4
+ if (bytes < 1024) return bytes + ' B';
5
+ var units = ['KiB', 'MiB', 'GiB', 'TiB'];
6
+ var index = -1;
7
+ do { bytes /= 1024; index++; } while (bytes >= 1024 && index < units.length - 1);
8
+ return bytes.toFixed(1) + ' ' + units[index];
9
+ }
10
+ function cell(row, text, header) {
11
+ var el = document.createElement(header ? 'th' : 'td');
12
+ el.textContent = text;
13
+ if (header) el.scope = 'row';
14
+ row.appendChild(el);
15
+ }
16
+ function render(data) {
17
+ var projectMode = data.attribution === 'project';
18
+ document.getElementById('settings-storage-description').textContent = projectMode ? 'Project disk usage grouped by project owner, alongside shared Clay data.' : 'Disk usage across Clay data, registered projects and mapped Linux home directories.';
19
+ document.getElementById('settings-storage-owner-heading').textContent = projectMode ? 'Projects' : 'Linux account';
20
+ document.querySelector('.settings-storage-table').setAttribute('aria-label', projectMode ? 'Allocated disk usage by project owner' : 'Allocated disk usage by filesystem owner');
21
+ document.getElementById('settings-storage-explanation').textContent = (projectMode ? 'Project files are attributed to their registered project owner. Nested projects use their own owner; conflicting shared paths or hard links are unattributed. ' : 'Allocated space is attributed by Linux file owner, including directory metadata. ') + 'Shared paths count once. Symlinks and nested mounts are skipped. Results may be cached for 30 seconds.';
22
+ document.getElementById('settings-storage-attribution-note').textContent = projectMode ? 'System / unattributed includes Clay data outside projects, unowned projects and conflicting shared files. Home directories are included only if registered as projects.' : 'System / unattributed includes unmapped or shared Linux identities. Unavailable means the account cannot be uniquely mapped; it does not mean zero usage.';
23
+ var body = document.getElementById('settings-storage-users');
24
+ body.replaceChildren();
25
+ data.users.slice().sort(function (a, b) { return b.bytes - a.bytes; }).forEach(function (user) {
26
+ var row = document.createElement('tr');
27
+ cell(row, user.name, true);
28
+ cell(row, projectMode ? String(user.projects || 0) : user.linuxUser || 'No Linux account');
29
+ cell(row, user.mapped ? (data.scanning || data.partial ? 'At least ' : '') + size(user.bytes) : 'Unavailable');
30
+ cell(row, user.mapped ? user.files.toLocaleString() : '—');
31
+ body.appendChild(row);
32
+ });
33
+ var shared = document.createElement('tr');
34
+ cell(shared, 'System / unattributed', true); cell(shared, projectMode ? 'Shared / unowned' : 'Other filesystem owners');
35
+ cell(shared, size(data.unattributedBytes)); cell(shared, data.unattributedFiles.toLocaleString());
36
+ body.appendChild(shared);
37
+ document.getElementById('settings-storage-total').textContent = (data.partial || data.scanning ? 'At least ' : '') + size(data.totalBytes);
38
+ var status = document.getElementById('settings-storage-status');
39
+ status.textContent = (data.scanning ? 'Scan in progress; these totals are not final. ' : '') + (data.partial ? 'Partial scan: some paths were unreadable, on another filesystem, or exceeded the scan limit. ' : '') +
40
+ 'Checked ' + new Date(data.checkedAt).toLocaleTimeString() + '. ' + data.roots + ' storage roots scanned.';
41
+ status.dataset.state = data.scanning ? 'pending' : data.partial ? 'error' : 'saved';
42
+ document.getElementById('settings-storage-results').hidden = false;
43
+ }
44
+ export async function loadStorageUsage() {
45
+ if (store.get('serverStorageLoading')) return;
46
+ clearTimeout(store.get('serverStorageTimer'));
47
+ var button = document.getElementById('settings-storage-refresh');
48
+ var status = document.getElementById('settings-storage-status');
49
+ store.set({ serverStorageLoading: true });
50
+ button.disabled = true;
51
+ button.textContent = 'Measuring…';
52
+ if (!store.get('serverStorageScanning')) status.textContent = 'Measuring disk usage. Large directories can take several seconds.';
53
+ status.dataset.state = 'pending';
54
+ try {
55
+ var response = await fetch('/api/server/storage', { credentials: 'same-origin' });
56
+ var data = await response.json();
57
+ if (!response.ok) {
58
+ if (response.status === 401 || response.status === 403) {
59
+ store.set({ serverStorageUsage: null });
60
+ document.getElementById('settings-storage-results').hidden = true;
61
+ }
62
+ throw new Error(data.error || 'Disk usage is unavailable. Try refreshing.');
63
+ }
64
+ store.set({ serverStorageUsage: data, serverStorageScanning: !!data.scanning });
65
+ if (data.users) render(data);
66
+ if (data.scanning) {
67
+ status.textContent = 'Scan in progress. Project files, including node_modules, are being measured; totals are not final.';
68
+ store.set({ serverStorageTimer: setTimeout(function () {
69
+ if (store.get('serverSettingsPage') === 'storage' && !document.getElementById('server-settings').classList.contains('hidden')) loadStorageUsage();
70
+ }, 1500) });
71
+ }
72
+ } catch (error) {
73
+ store.set({ serverStorageScanning: false });
74
+ status.textContent = error.message + (store.get('serverStorageUsage') ? ' Previous measurements remain below.' : '');
75
+ status.dataset.state = 'error';
76
+ } finally {
77
+ store.set({ serverStorageLoading: false });
78
+ button.disabled = !!store.get('serverStorageScanning');
79
+ button.textContent = button.disabled ? 'Measuring…' : 'Refresh usage';
80
+ }
81
+ }
82
+ export function initStorageUsage() {
83
+ document.getElementById('settings-storage-refresh').addEventListener('click', loadStorageUsage);
84
+ }
@@ -0,0 +1,12 @@
1
+ function formatSessionActivity(value) {
2
+ if (!value) return "";
3
+ var date = new Date(value);
4
+ if (isNaN(date.getTime())) return "";
5
+ try {
6
+ return "Last activity: " + new Intl.DateTimeFormat(undefined, { dateStyle: "medium", timeStyle: "short" }).format(date);
7
+ } catch (error) {
8
+ return "Last activity: " + date.toLocaleString();
9
+ }
10
+ }
11
+
12
+ export { formatSessionActivity };
@@ -1,6 +1,6 @@
1
1
  // The inline "new session" draft row that sits under a folder header (also when
2
- // the folder is collapsed): provider icon, a compact provider picker and icon
3
- // Cancel. The stable header action becomes Create. State and server round
2
+ // the folder is collapsed): provider icon, a compact provider picker, Create
3
+ // and icon Cancel. The header action remains New session. State and server round
4
4
  // trips are in session-create.js; this module only builds and repaints the DOM
5
5
  // from the store, so the row keeps its provider, focus and error across list
6
6
  // rerenders and server updates.
@@ -12,7 +12,7 @@ import { startNewSession } from './sidebar-sessions.js';
12
12
  import { closeMobileSheet } from './sidebar-mobile.js';
13
13
  import {
14
14
  createState, createOptions, installedVendorIds,
15
- openSessionCreate, closeSessionCreate, chooseVendor, setCreateMenu, submitSessionCreate, saveProjectDefault, retryOptions
15
+ beginSessionCreate, closeSessionCreate, chooseVendor, setCreateMenu, submitSessionCreate, submitMateSessionCreate, saveProjectDefault, retryOptions
16
16
  } from './session-create.js';
17
17
 
18
18
  // Every registered provider, in the established order, followed by any the
@@ -61,6 +61,15 @@ function focusHeaderButton(folderId) {
61
61
  requestAnimationFrame(applyFocus);
62
62
  }
63
63
 
64
+ function focusCreateRow(folderId) {
65
+ var rows = document.querySelectorAll('.session-create-row[data-folder-id="' + folderId + '"]');
66
+ for (var i = 0; i < rows.length; i++) {
67
+ if (!rows[i].getClientRects().length) continue;
68
+ var target = rows[i].querySelector('[data-focus-key="vendor"]') || rows[i].querySelector('[data-focus-key="create"]') || rows[i].querySelector('[data-focus-key="cancel"]');
69
+ if (target && !target.disabled) { target.focus({ preventScroll: true }); return; }
70
+ }
71
+ }
72
+
64
73
  function prefersReducedMotion() {
65
74
  return typeof window !== "undefined" && window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
66
75
  }
@@ -106,20 +115,25 @@ function createStatusMessage(form, options, valid) {
106
115
  function paintNewSessionButton(btn, folderId, label) {
107
116
  var form = createState();
108
117
  var active = !!(form && form.folderId === folderId);
118
+ var direct = active && form.mode === "mate-direct";
109
119
  var options = createOptions();
110
- var valid = active && options.loaded && installedVendorIds().indexOf(form.vendor) !== -1;
120
+ var valid = direct || (active && options.loaded && installedVendorIds().indexOf(form.vendor) !== -1);
111
121
  var pending = active && form.phase === "pending";
112
- var buttonText = !active ? "New session" : (pending ? "Creating…" : (!options.loaded ? "Loading…" : (!valid ? "Choose provider" : "Click to create")));
113
- var status = active ? createStatusMessage(form, options, valid) : "";
122
+ var uncertain = direct && form.phase === "uncertain";
123
+ var buttonText = direct ? (pending ? "Creating…" : (uncertain ? "Check sessions" : "Retry")) : "New session";
124
+ var status = active ? (direct ? (form.error || (pending ? "Creating session…" : "")) : createStatusMessage(form, options, valid)) : "";
114
125
  ensureNewSessionButtonContent(btn);
115
- btn.classList.toggle("is-create", active);
126
+ btn.classList.toggle("is-create", direct);
116
127
  btn.setAttribute("aria-expanded", String(active));
117
128
  btn.setAttribute("aria-busy", String(pending));
118
- btn.setAttribute("aria-label", active ? (pending ? "Creating session in " : (valid ? "Click to create session in " : buttonText + " for ")) + label : "New session in " + label);
129
+ var activeLabel = direct
130
+ ? (pending ? "Creating session in " : (uncertain ? "Check sessions in " : "Retry new session in "))
131
+ : "New session in ";
132
+ btn.setAttribute("aria-label", active ? activeLabel + label : "New session in " + label);
119
133
  var controls = btn.getAttribute("aria-controls");
120
134
  if (active && status && controls) btn.setAttribute("aria-describedby", controls + "-status");
121
135
  else btn.removeAttribute("aria-describedby");
122
- btn.disabled = active ? (pending || !valid) : !!(form && form.phase === "pending");
136
+ btn.disabled = direct ? (pending || uncertain) : !!(form && form.phase === "pending");
123
137
  setNewSessionButtonText(btn, buttonText, active && (!form.openedAt || Date.now() - form.openedAt < 400 || btn.isConnected));
124
138
  refreshIcons();
125
139
  }
@@ -136,8 +150,12 @@ export function renderNewSessionButton(folderId, label, surface) {
136
150
  btn.addEventListener("click", function (event) {
137
151
  event.stopPropagation();
138
152
  var live = createState();
139
- if (live && live.folderId === folderId) { submitAndClose(); return; }
140
- openSessionCreate(folderId);
153
+ if (live && live.folderId === folderId) {
154
+ if (live.mode === "mate-direct") submitMateSessionCreate();
155
+ else focusCreateRow(folderId);
156
+ return;
157
+ }
158
+ beginSessionCreate(folderId);
141
159
  focusHeaderButton(folderId);
142
160
  });
143
161
  return btn;
@@ -160,6 +178,10 @@ export function renderCreateRow(folderId, label, surface) {
160
178
  root.setAttribute("role", "group");
161
179
  root.setAttribute("aria-label", "New session in " + label);
162
180
  root.dataset.folderId = folderId;
181
+ if (form.mode === "mate-direct") {
182
+ paintMateDirectRow(root, form);
183
+ return root;
184
+ }
163
185
  root.addEventListener("keydown", function (event) {
164
186
  // Enter and Escape belong to the input method while composing.
165
187
  if (event.isComposing || event.keyCode === 229) return;
@@ -195,6 +217,43 @@ export function renderCreateRow(folderId, label, surface) {
195
217
  return root;
196
218
  }
197
219
 
220
+ function paintMateDirectRow(root, form) {
221
+ var pending = form.phase === "pending";
222
+ var uncertain = form.phase === "uncertain";
223
+ root.setAttribute("aria-busy", String(pending));
224
+ root.innerHTML = "";
225
+ var line = document.createElement("div");
226
+ line.className = "session-create-line session-create-direct-line";
227
+ var status = document.createElement("div");
228
+ status.id = root.id + "-status";
229
+ status.className = "session-create-status" + (form.error ? " is-error" : "");
230
+ status.setAttribute("role", form.error ? "alert" : "status");
231
+ status.textContent = form.error || "Creating session…";
232
+ line.appendChild(status);
233
+ if (!pending && !uncertain) {
234
+ var retry = document.createElement("button");
235
+ retry.type = "button";
236
+ retry.className = "session-create-retry";
237
+ retry.dataset.focusKey = "retry";
238
+ retry.textContent = "Retry";
239
+ retry.addEventListener("click", function () { submitMateSessionCreate(); });
240
+ line.appendChild(retry);
241
+ }
242
+ var cancel = iconButton("x", "Close new session status", "cancel", function () { closeAndFocus(form.folderId); });
243
+ cancel.disabled = pending;
244
+ line.appendChild(cancel);
245
+ root.appendChild(line);
246
+ if (form.wantFocus && !pending && !uncertain) {
247
+ queueMicrotask(function () {
248
+ if (!retry.isConnected) return;
249
+ retry.focus({ preventScroll: true });
250
+ var live = createState();
251
+ if (live) store.set({ sessionCreate: Object.assign({}, live, { wantFocus: false }) });
252
+ });
253
+ }
254
+ refreshIcons();
255
+ }
256
+
198
257
  function closeMenu(refocus) {
199
258
  var live = createState();
200
259
  if (!live || !live.menuOpen) return;
@@ -223,10 +282,16 @@ function submitAndClose() {
223
282
  function paintRow(root) {
224
283
  var form = createState();
225
284
  if (!form) return;
285
+ if (form.mode === "mate-direct") {
286
+ paintMateDirectRow(root, form);
287
+ return;
288
+ }
226
289
  var options = createOptions();
227
290
  var pending = form.phase === "pending";
228
291
  var active = document.activeElement;
229
292
  var focusKey = active && root.contains(active) ? active.dataset.focusKey : null;
293
+ var installed = installedVendorIds();
294
+ var valid = options.loaded && installed.indexOf(form.vendor) !== -1;
230
295
  // An explicit focus request wins over wherever focus happens to be.
231
296
  if (form.wantFocus && root.getClientRects().length) focusKey = form.focusKey || "vendor";
232
297
  root.setAttribute("aria-busy", String(pending));
@@ -234,8 +299,6 @@ function paintRow(root) {
234
299
 
235
300
  var line = document.createElement("div");
236
301
  line.className = "session-create-line";
237
- var installed = installedVendorIds();
238
-
239
302
  var icon = document.createElement("img");
240
303
  icon.className = "session-create-icon";
241
304
  icon.alt = "";
@@ -268,12 +331,21 @@ function paintRow(root) {
268
331
  });
269
332
  line.appendChild(picker);
270
333
 
334
+ var submit = document.createElement("button");
335
+ submit.type = "button";
336
+ submit.className = "session-create-submit";
337
+ submit.dataset.focusKey = "create";
338
+ submit.textContent = "Create";
339
+ submit.setAttribute("aria-label", "Create " + (root.getAttribute("aria-label") || "new session"));
340
+ submit.disabled = pending || !valid;
341
+ submit.addEventListener("click", function (event) { event.stopPropagation(); submitAndClose(); });
342
+ line.appendChild(submit);
343
+
271
344
  var cancel = iconButton("x", "Cancel new session", "cancel", function () { closeAndFocus(form.folderId); });
272
345
  cancel.disabled = pending;
273
346
  line.appendChild(cancel);
274
347
  root.appendChild(line);
275
348
 
276
- var valid = options.loaded && installed.indexOf(form.vendor) !== -1;
277
349
  var optionsError = options.error || "";
278
350
  var message = createStatusMessage(form, options, valid);
279
351
  var status = document.createElement("div");
@@ -2,15 +2,15 @@
2
2
  //
3
3
  // Every real folder header (custom folders and Unfiled, not the Favorites tag) has a quiet
4
4
  // "+ New session" button that opens one draft row directly under that header:
5
- // provider icon, a compact provider picker and icon Cancel; the same header
6
- // action that opened the draft becomes Create. Only the
5
+ // provider icon, a compact provider picker, Create and icon Cancel. Only the
7
6
  // provider is chosen; the model and effort stay whatever that provider's
8
7
  // existing defaults produce. The installed providers and the project default
9
8
  // come from the server; the saved project default is preselected, and a
10
9
  // default changes only through the explicit action beside an available
11
10
  // provider. All mutable state lives in the store (sessionCreate,
12
11
  // sessionCreateOptions, sessionCreateLocks, sessionCreateReveal); the DOM is
13
- // built in session-create-form.js.
12
+ // built in session-create-form.js. Provider-mode submission is an explicit
13
+ // Create action inside the draft row; the header only opens or focuses it.
14
14
 
15
15
  import { store } from './store.js';
16
16
  import { getWs } from './ws-ref.js';
@@ -18,6 +18,7 @@ import { showToast } from './utils.js';
18
18
  import { resolvePreferredVendor } from './default-vendor.js';
19
19
  import { sendFolderOp, currentFolderState } from './session-folders.js';
20
20
  import { queueSidebarCreationEffect } from './sidebar-creation-effect.js';
21
+ import { isMateWorkspace } from './project-mate-navigation.js';
21
22
 
22
23
  // Ids are unique without any module-level counter.
23
24
  function nextId(prefix) {
@@ -66,13 +67,29 @@ export function openSessionCreate(folderId) {
66
67
  }
67
68
  var options = createOptions();
68
69
  store.set({ sessionCreate: {
69
- folderId: folderId, vendor: options.loaded ? preferredVendor(options) : "", phase: "ready", requestId: null, timer: null, error: "", notice: "",
70
+ mode: "provider", folderId: folderId, vendor: options.loaded ? preferredVendor(options) : "", phase: "ready", requestId: null, timer: null, error: "", notice: "",
70
71
  defaultRequestId: null, defaultSaving: false, defaultVendor: null, focusKey: "vendor", wantFocus: false, scroll: true, restoreFocusKey: null,
71
72
  openedAt: Date.now(),
72
73
  } });
73
74
  requestOptions();
74
75
  }
75
76
 
77
+ export function beginSessionCreate(folderId) {
78
+ if (!isMateWorkspace(store.snap())) {
79
+ openSessionCreate(folderId);
80
+ return true;
81
+ }
82
+ var current = createState();
83
+ if (current && current.phase === "pending") return false;
84
+ if (!current || current.mode !== "mate-direct" || current.folderId !== folderId) {
85
+ store.set({ sessionCreate: {
86
+ mode: "mate-direct", folderId: folderId, phase: "ready", requestId: null, timer: null,
87
+ error: "", notice: "", focusKey: "retry", wantFocus: false, scroll: true, openedAt: Date.now(),
88
+ } });
89
+ }
90
+ return submitMateSessionCreate();
91
+ }
92
+
76
93
  export function closeSessionCreate() {
77
94
  var current = createState();
78
95
  if (!current || current.phase === "pending") return;
@@ -178,10 +195,35 @@ export function submitSessionCreate(startNewSession) {
178
195
  return true;
179
196
  }
180
197
 
198
+ export function submitMateSessionCreate() {
199
+ var form = createState();
200
+ if (!form || form.mode !== "mate-direct" || form.phase === "pending" || form.phase === "uncertain") return false;
201
+ var requestId = nextId("scm-");
202
+ var timer = setTimeout(function () { onPendingTimeout(requestId); }, PENDING_TIMEOUT_MS);
203
+ patch({ phase: "pending", requestId: requestId, error: "", notice: "", timer: timer, wantFocus: false });
204
+ if (!send({
205
+ type: "new_session",
206
+ mateDefaults: true,
207
+ forceNew: true,
208
+ requestId: requestId,
209
+ folderSlug: store.get('currentSlug'),
210
+ folderId: form.folderId === "unfiled" ? null : form.folderId,
211
+ })) {
212
+ clearTimeout(timer);
213
+ patch({ phase: "ready", requestId: null, timer: null, error: "Not connected. Reconnect and try again.", focusKey: "retry", wantFocus: true });
214
+ return false;
215
+ }
216
+ return true;
217
+ }
218
+
181
219
  function onPendingTimeout(requestId) {
182
220
  var form = createState();
183
221
  if (!form || form.requestId !== requestId || form.phase !== "pending") return;
184
222
  forgetLock(requestId);
223
+ if (form.mode === "mate-direct") {
224
+ patch({ phase: "uncertain", timer: null, error: "No response yet. The session may already have been created; check the list before trying again." });
225
+ return;
226
+ }
185
227
  patch({ phase: "ready", requestId: null, timer: null, error: "No response yet. The session may already have been created; check the list first." });
186
228
  }
187
229
 
@@ -200,7 +242,7 @@ export function handleNewSessionResult(msg) {
200
242
  clearTimeout(form.timer);
201
243
  if (!msg.ok) {
202
244
  restoreLock(msg.requestId);
203
- patch({ phase: "ready", requestId: null, timer: null, error: msg.error || "The session could not be created.", focusKey: "create", wantFocus: true });
245
+ patch({ phase: "ready", requestId: null, timer: null, error: msg.error || "The session could not be created.", focusKey: form.mode === "mate-direct" ? "retry" : "create", wantFocus: true });
204
246
  return true;
205
247
  }
206
248
  forgetLock(msg.requestId);
@@ -300,7 +342,8 @@ function onDisconnect(form) {
300
342
  if (!form) return;
301
343
  if (form.timer) clearTimeout(form.timer);
302
344
  if (form.phase === "pending") {
303
- patch({ phase: "ready", requestId: null, timer: null, error: "Connection lost. The session may already have been created; check the list first." });
345
+ if (form.mode === "mate-direct") patch({ phase: "uncertain", timer: null, error: "Connection lost. The session may already have been created; check the list before trying again." });
346
+ else patch({ phase: "ready", requestId: null, timer: null, error: "Connection lost. The session may already have been created; check the list first." });
304
347
  }
305
348
  if (form.defaultSaving) patch({ defaultSaving: false, defaultRequestId: null, defaultVendor: null, error: "Connection lost before the project default was confirmed." });
306
349
  }
@@ -319,7 +362,7 @@ store.subscribe(function (state, previous) {
319
362
  var form = state.sessionCreate;
320
363
  if (previous.connected && !state.connected) onDisconnect(form);
321
364
  // The providers are requested again after a reconnect.
322
- if (!previous.connected && state.connected && form) requestOptions();
365
+ if (!previous.connected && state.connected && form && form.mode !== "mate-direct") requestOptions();
323
366
  // Folder removed while its row was open: say so instead of letting a later
324
367
  // create fail, and never silently file the session elsewhere.
325
368
  if (form && state.sessionFolders !== previous.sessionFolders && form.folderId !== "unfiled") {
@@ -76,10 +76,11 @@ export function appendGithubWork(row, session, title) {
76
76
  row.insertBefore(column, title);
77
77
  column.appendChild(title);
78
78
  var links = document.createElement('span');
79
+ links.className = 'session-work-links-slot';
79
80
  links.innerHTML = html;
80
81
  links.addEventListener('click', function (event) { if (event.target.closest('a')) event.stopPropagation(); });
81
82
  links.addEventListener('contextmenu', function (event) { event.stopPropagation(); });
82
- column.appendChild(links);
83
+ row.appendChild(links);
83
84
  }
84
85
  export function syncGithubSessions(sessions) {
85
86
  bindGithubWorkPopover();
@@ -0,0 +1,41 @@
1
+ // Visibility policy for the ordinary desktop and mobile session lists.
2
+ // Debate sessions remain in the session cache for the Debate workbench and
3
+ // exact-session navigation; only their ordinary-list presentation is removed.
4
+
5
+ export function isDebateOwnedSession(session) {
6
+ var phase = session && session.homeDebatePhase;
7
+ return !!(session && (
8
+ session.homeDebatePlanning === true
9
+ || phase === "planning"
10
+ || phase === "live"
11
+ || phase === "ended"
12
+ || phase === "interrupted"
13
+ || (session.loop && session.loop.source === "debate")
14
+ ));
15
+ }
16
+
17
+ export function ordinarySessionListSessions(sessions) {
18
+ var source = Array.isArray(sessions) ? sessions : [];
19
+ var hiddenIds = new Set();
20
+ for (var i = 0; i < source.length; i++) {
21
+ if (isDebateOwnedSession(source[i])) hiddenIds.add(source[i].id);
22
+ }
23
+
24
+ // A debate Driver owns its Worker rows even if an older Worker payload did
25
+ // not copy the debate marker. Follow parent links so those children cannot
26
+ // leak into the ordinary list as orphan Workers.
27
+ var changed = true;
28
+ while (changed) {
29
+ changed = false;
30
+ for (var j = 0; j < source.length; j++) {
31
+ var session = source[j];
32
+ if (hiddenIds.has(session.id) || !hiddenIds.has(session.parentSessionId)) continue;
33
+ hiddenIds.add(session.id);
34
+ changed = true;
35
+ }
36
+ }
37
+
38
+ return source.filter(function (session) {
39
+ return !hiddenIds.has(session.id);
40
+ });
41
+ }
@@ -39,6 +39,9 @@ export function renderSessionPresence(row, sessionId) {
39
39
  }
40
40
 
41
41
  var anchor = row.querySelector(".session-row-trailing, .session-item-age, .mobile-worker-generation, .mobile-session-unread, .session-row-actions, .mobile-session-star");
42
+ // Desktop age/actions now live inside one trailing slot. insertBefore only
43
+ // accepts a direct child, so promote a nested match to its row-level owner.
44
+ while (anchor && anchor.parentNode !== row) anchor = anchor.parentNode;
42
45
  row.insertBefore(container, anchor || null);
43
46
  return container;
44
47
  }
@@ -0,0 +1,51 @@
1
+ function resetMarquee(state) {
2
+ if (state.frame) cancelAnimationFrame(state.frame);
3
+ state.frame = null;
4
+ state.row.classList.remove("session-title-marquee");
5
+ state.title.style.removeProperty("--session-marquee-distance");
6
+ state.title.style.removeProperty("--session-marquee-duration");
7
+ }
8
+
9
+ function measureMarquee(state) {
10
+ state.frame = null;
11
+ if (!state.active || !state.row.isConnected) return;
12
+ var titleRect = state.title.getBoundingClientRect();
13
+ var actionRect = state.actions ? state.actions.getBoundingClientRect() : null;
14
+ var rowRect = state.row.getBoundingClientRect();
15
+ var right = actionRect && actionRect.width ? actionRect.left - 5 : rowRect.right - 8;
16
+ var available = Math.max(0, right - titleRect.left);
17
+ var overflow = state.title.scrollWidth - available;
18
+ if (overflow <= 4) return;
19
+ state.title.style.setProperty("--session-marquee-distance", (-overflow) + "px");
20
+ state.title.style.setProperty("--session-marquee-duration", Math.max(5, overflow / 22) + "s");
21
+ state.row.classList.add("session-title-marquee");
22
+ }
23
+
24
+ function activate(state) {
25
+ state.active = true;
26
+ resetMarquee(state);
27
+ state.active = true;
28
+ state.frame = requestAnimationFrame(function () { measureMarquee(state); });
29
+ }
30
+
31
+ export function bindSessionTitleMarquee(row, title, actions) {
32
+ if (!row || !title) return;
33
+ var state = { row: row, title: title, actions: actions, active: false, frame: null };
34
+ function leave() { state.active = false; resetMarquee(state); }
35
+ function enter() { activate(state); }
36
+ row.addEventListener("mouseenter", enter);
37
+ row.addEventListener("focusin", enter);
38
+ row.addEventListener("mouseleave", leave);
39
+ row.addEventListener("focusout", function (event) {
40
+ if (!row.contains(event.relatedTarget)) leave();
41
+ });
42
+ window.addEventListener("resize", leave);
43
+ var observer = typeof MutationObserver === "undefined" ? null : new MutationObserver(function () {
44
+ if (!row.isConnected) {
45
+ leave();
46
+ observer.disconnect();
47
+ window.removeEventListener("resize", leave);
48
+ }
49
+ });
50
+ if (observer && document.body) observer.observe(document.body, { childList: true, subtree: true });
51
+ }
@@ -0,0 +1,34 @@
1
+ // Check if text looks like env format: first line starts with a valid VAR_NAME=
2
+ export function looksLikeEnv(text) {
3
+ var first = text.split("\n")[0].trim();
4
+ if (first.indexOf("export ") === 0) first = first.substring(7);
5
+ return /^[A-Za-z_][A-Za-z0-9_]*=/.test(first);
6
+ }
7
+
8
+ export function parseEnvString(str) {
9
+ var pairs = [];
10
+ if (!str) return pairs;
11
+ var lines = str.split("\n");
12
+ for (var i = 0; i < lines.length; i++) {
13
+ var line = lines[i].trim();
14
+ if (!line || line.charAt(0) === "#") continue;
15
+ // Strip leading "export "
16
+ if (line.indexOf("export ") === 0) line = line.substring(7);
17
+ var eq = line.indexOf("=");
18
+ if (eq === -1) continue;
19
+ var key = line.substring(0, eq).trim();
20
+ var val = line.substring(eq + 1).trim();
21
+ // Saved values are JSON-quoted so shell metacharacters remain data.
22
+ if (val.charAt(0) === '"' && val.charAt(val.length - 1) === '"') {
23
+ try { val = JSON.parse(val); } catch (e) { val = val.substring(1, val.length - 1); }
24
+ } else if (val.charAt(0) === "'" && val.charAt(val.length - 1) === "'") {
25
+ val = val.substring(1, val.length - 1);
26
+ }
27
+ if (key) pairs.push({ key: key, value: val });
28
+ }
29
+ return pairs;
30
+ }
31
+
32
+ export function serializeEnvValue(value) {
33
+ return JSON.stringify(value == null ? "" : String(value));
34
+ }