clay-server 5.5.0-beta.2 → 5.5.0-beta.3
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.
- package/lib/clay-product-control-input.js +2 -2
- package/lib/clay-product-control-tools.js +1 -1
- package/lib/clay-product-control.js +4 -3
- package/lib/daemon-project-access.js +10 -2
- package/lib/daemon-projects.js +16 -4
- package/lib/daemon-worktree-controls.js +171 -0
- package/lib/daemon.js +14 -63
- package/lib/personal-provider-install.js +85 -0
- package/lib/personal-provider-settings.js +94 -0
- package/lib/project-logs-service.js +2 -2
- package/lib/project-pair-task-control.js +1 -1
- package/lib/project-pending-message-queue.js +6 -2
- package/lib/project-product-control.js +1 -0
- package/lib/project-session-actions.js +46 -3
- package/lib/project-session-pair.js +2 -0
- package/lib/project-sessions.js +11 -3
- package/lib/project-user-message.js +5 -1
- package/lib/project-vendor-login.js +95 -32
- package/lib/project-worker-auth.js +71 -0
- package/lib/project-worker-conversation.js +11 -2
- package/lib/project.js +9 -0
- package/lib/public/app.js +22 -0
- package/lib/public/css/icon-strip.css +5 -0
- package/lib/public/css/overlays.css +18 -0
- package/lib/public/css/settings-shell.css +14 -0
- package/lib/public/css/settings-ui.css +6 -0
- package/lib/public/css/split-view-layout.css +16 -11
- package/lib/public/modules/agent-message-bubbles.js +33 -4
- package/lib/public/modules/app-messages.js +7 -1
- package/lib/public/modules/app-projects.js +3 -4
- package/lib/public/modules/clay-control-copy.js +1 -0
- package/lib/public/modules/clay-tool-presentation.js +1 -1
- package/lib/public/modules/generation-stop.js +4 -4
- package/lib/public/modules/pane-activation.js +2 -2
- package/lib/public/modules/pane-bridge.js +4 -2
- package/lib/public/modules/personal-provider-context.js +11 -0
- package/lib/public/modules/project-settings.js +1 -1
- package/lib/public/modules/settings-section-nav.js +58 -0
- package/lib/public/modules/settings-shell-registry.js +4 -2
- package/lib/public/modules/settings-shell.js +17 -6
- package/lib/public/modules/sidebar-projects.js +23 -9
- package/lib/public/modules/split-pane-force-stop.js +47 -0
- package/lib/public/modules/split-pane-header-state.js +11 -1
- package/lib/public/modules/split-pane-renderer.js +2 -2
- package/lib/public/modules/split-view-workers.js +32 -17
- package/lib/public/modules/stt-settings.js +21 -4
- package/lib/public/modules/user-settings-layout.js +11 -8
- package/lib/public/modules/user-settings-provider-install.js +42 -0
- package/lib/public/modules/user-settings-providers.js +102 -0
- package/lib/public/modules/user-settings-voice.js +50 -41
- package/lib/public/modules/vendor-login-browser.js +1 -1
- package/lib/public/modules/vendor-login.js +72 -12
- package/lib/public/modules/worker-activity-indicator.js +49 -0
- package/lib/public/modules/worker-message-previews.js +47 -11
- package/lib/public/modules/worker-stop-controls.js +36 -0
- package/lib/public/modules/worktree-controls.js +86 -0
- package/lib/public/pane-assets/{app-SEMVZ4AY.js → app-ANJS2LSK.js} +1 -1
- package/lib/public/pane-assets/{chunk-4YE4RU27.js → chunk-32YIYF7R.js} +1 -1
- package/lib/public/pane-assets/{chunk-BNNPJJVG.js → chunk-3EJ5IAHZ.js} +1 -1
- package/lib/public/pane-assets/{chunk-C6P7PICY.js → chunk-3TIVBAKM.js} +1 -1
- package/lib/public/pane-assets/{chunk-XYZGVYWW.js → chunk-4QFNLHW5.js} +1 -1
- package/lib/public/pane-assets/{chunk-FK3YLGAP.js → chunk-5XMZMIFG.js} +1 -1
- package/lib/public/pane-assets/chunk-6S2C7Q2N.js +1 -0
- package/lib/public/pane-assets/chunk-7B2GJI57.js +1 -0
- package/lib/public/pane-assets/{chunk-QBCM7QEA.js → chunk-BOFNCPTN.js} +1 -1
- package/lib/public/pane-assets/{chunk-CQNPEH2M.js → chunk-CXWAJEN5.js} +1 -1
- package/lib/public/pane-assets/{chunk-5UBN6GR7.js → chunk-EWTXBZJO.js} +1 -1
- package/lib/public/pane-assets/{chunk-YR5GH3GI.js → chunk-G35ZOJGP.js} +1 -1
- package/lib/public/pane-assets/chunk-HYT24UBT.js +1 -0
- package/lib/public/pane-assets/chunk-ISQEPZMY.js +1 -0
- package/lib/public/pane-assets/{chunk-E4VM2IDB.js → chunk-JM46WGDH.js} +1 -1
- package/lib/public/pane-assets/chunk-LBJG4MK2.js +174 -0
- package/lib/public/pane-assets/{chunk-K4PAN77B.js → chunk-P3AK4ORJ.js} +1 -1
- package/lib/public/pane-assets/chunk-QKPGE7DN.js +1 -0
- package/lib/public/pane-assets/chunk-TS5JUEVH.js +1 -0
- package/lib/public/pane-assets/{chunk-QWQB2YRF.js → chunk-UGH5MCNK.js} +1 -1
- package/lib/public/pane-assets/{chunk-YVVT3CTV.js → chunk-V5DLPSLT.js} +1 -1
- package/lib/public/pane-assets/chunk-VFKDU64W.js +1 -0
- package/lib/public/pane-assets/{chunk-U7EHYRJZ.js → chunk-XSSTR6GC.js} +1 -1
- package/lib/public/pane-assets/{chunk-M5CWIIIT.js → chunk-XVESKMU3.js} +1 -1
- package/lib/public/pane-assets/manifest.json +394 -386
- package/lib/public/pane-assets/style-VBVCWV44.css +1 -0
- package/lib/sdk-bridge.js +40 -6
- package/lib/sdk-force-stop.js +62 -0
- package/lib/sdk-message-processor.js +2 -1
- package/lib/server-worktree-controls.js +34 -0
- package/lib/server.js +11 -1
- package/lib/session-force-stop-support.js +15 -0
- package/lib/sessions.js +6 -1
- package/lib/terminal-manager.js +1 -1
- package/lib/terminal.js +13 -1
- package/lib/vendor-login-browser-flow.js +1 -1
- package/lib/worker-activity.js +26 -0
- package/lib/worker-message-previews.js +12 -7
- package/lib/worktree-branch.js +57 -0
- package/lib/worktree-identity.js +39 -0
- package/lib/worktree-preferences.js +25 -0
- package/lib/worktree-ref-transaction.js +36 -0
- package/lib/yoke/adapters/antigravity.js +3 -0
- package/lib/yoke/force-termination.js +26 -0
- package/lib/yoke/interface.js +14 -0
- package/lib/yoke/process-force-termination.js +24 -0
- package/lib/yoke/provider-install-recipes.js +20 -0
- package/lib/yoke/vendor-registry.js +20 -1
- package/package.json +1 -1
- package/lib/public/pane-assets/chunk-6AGSOAOD.js +0 -1
- package/lib/public/pane-assets/chunk-AATAIAXL.js +0 -1
- package/lib/public/pane-assets/chunk-D3CE5FZL.js +0 -1
- package/lib/public/pane-assets/chunk-GKSLBGVE.js +0 -168
- package/lib/public/pane-assets/chunk-J5PTSKJ6.js +0 -1
- package/lib/public/pane-assets/chunk-QPBO6ID4.js +0 -1
- package/lib/public/pane-assets/chunk-RA6VUJTY.js +0 -1
- package/lib/public/pane-assets/chunk-XOZUM5ET.js +0 -1
- package/lib/public/pane-assets/style-DZX6JAMF.css +0 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { activateSettingsSections, deactivateSettingsSections } from './settings-section-nav.js';
|
|
1
2
|
// Unified full-screen settings: one shell for Personal, Project and Server
|
|
2
3
|
// settings. Scope modules register a controller; the shell owns routing,
|
|
3
4
|
// access, project target binding, focus, close restoration and draft guards.
|
|
@@ -134,6 +135,7 @@ function applyRoute(route, focus) {
|
|
|
134
135
|
var resolved = resolveRoute(route, shell.access);
|
|
135
136
|
var ctrl = controllers[resolved.scope];
|
|
136
137
|
if (!ctrl) return;
|
|
138
|
+
deactivateSettingsSections();
|
|
137
139
|
if (shell.scope && shell.scope !== resolved.scope && controllers[shell.scope].leave) controllers[shell.scope].leave();
|
|
138
140
|
var entering = shell.scope !== resolved.scope;
|
|
139
141
|
shell.scope = resolved.scope;
|
|
@@ -152,7 +154,8 @@ function applyRoute(route, focus) {
|
|
|
152
154
|
markActivePage(page);
|
|
153
155
|
store.set({ settingsLastRoute: { scope: resolved.scope, page: page, slug: shell.slug } });
|
|
154
156
|
enhanceSettingsSelects(root); refreshIcons(root);
|
|
155
|
-
if (focus) focusHeading(ctrl);
|
|
157
|
+
if (focus && !resolved.anchor) focusHeading(ctrl);
|
|
158
|
+
activateSettingsSections(page, resolved.anchor);
|
|
156
159
|
}
|
|
157
160
|
|
|
158
161
|
function onScopeSelect(scope) {
|
|
@@ -160,12 +163,18 @@ function onScopeSelect(scope) {
|
|
|
160
163
|
guard('scope', function () { applyRoute({ scope: scope, page: shell.pages[scope], slug: scope === 'project' ? currentRoute().slug : null }, true); });
|
|
161
164
|
}
|
|
162
165
|
function onPageSelect(page) {
|
|
163
|
-
|
|
166
|
+
var route = resolveRoute({scope:shell.scope,page:page}, shell.access);
|
|
167
|
+
if (route.page === shell.pages[shell.scope]) {
|
|
168
|
+
if (!route.anchor) focusHeading(controller());
|
|
169
|
+
activateSettingsSections(route.page, route.anchor); return;
|
|
170
|
+
}
|
|
164
171
|
guard('page', function () {
|
|
165
172
|
var ctrl = controller();
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
173
|
+
deactivateSettingsSections();
|
|
174
|
+
shell.pages[shell.scope] = route.page; ctrl.activate(route.page, {access:shell.access,scope:shell.scope});
|
|
175
|
+
markActivePage(route.page); store.set({settingsLastRoute:{scope:shell.scope,page:route.page,slug:shell.slug}});
|
|
176
|
+
if (!route.anchor) focusHeading(ctrl);
|
|
177
|
+
activateSettingsSections(route.page, route.anchor);
|
|
169
178
|
}, shell.pages[shell.scope]);
|
|
170
179
|
}
|
|
171
180
|
// A project switch retargets only the settings view, never the workspace.
|
|
@@ -181,7 +190,8 @@ export function navigateSettings(route) {
|
|
|
181
190
|
var resolved = resolveRoute(route, shell.access);
|
|
182
191
|
if (resolved.scope === shell.scope && resolved.page === shell.pages[shell.scope] && (resolved.scope !== 'project' || !route.slug || route.slug === shell.slug)) {
|
|
183
192
|
setShellStatus(redirectNote(resolved), resolved.redirected ? 'error' : undefined);
|
|
184
|
-
focusHeading(controller());
|
|
193
|
+
if (!resolved.anchor) focusHeading(controller());
|
|
194
|
+
activateSettingsSections(resolved.page, resolved.anchor); return;
|
|
185
195
|
}
|
|
186
196
|
guard(resolved.scope === shell.scope ? (route.slug && route.slug !== shell.slug ? 'project' : 'page') : 'scope', function () { applyRoute(route, true); }, shell.pages[shell.scope]);
|
|
187
197
|
}
|
|
@@ -251,6 +261,7 @@ export function closeSettings(opts) {
|
|
|
251
261
|
function finish() {
|
|
252
262
|
shell.token += 1; binding.invalidate();
|
|
253
263
|
var ctrl = controller();
|
|
264
|
+
deactivateSettingsSections();
|
|
254
265
|
if (ctrl && ctrl.leave) ctrl.leave();
|
|
255
266
|
Object.keys(controllers).forEach(function (id) { if (controllers[id].close) controllers[id].close(); });
|
|
256
267
|
root.classList.add('hidden');
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { reviewWorktreeRemoval } from './worktree-controls.js';
|
|
1
2
|
// sidebar-projects.js - Project icon strip, context menus, emoji picker, drag-and-drop, worktree modal
|
|
2
3
|
// Extracted from sidebar.js (PR-36)
|
|
3
4
|
|
|
@@ -433,8 +434,9 @@ export function showProjectCtxMenu(anchorEl, slug, name, icon, position, options
|
|
|
433
434
|
}
|
|
434
435
|
|
|
435
436
|
var isWorktree = slug.indexOf("--") !== -1;
|
|
436
|
-
var
|
|
437
|
-
|
|
437
|
+
var ownsWorktree = !isWorktree || !store.get('isMultiUserMode') || !!(targetProject && targetProject.projectOwnerId === store.get('myUserId'));
|
|
438
|
+
if (!ownsWorktree) iconItem.disabled = true;
|
|
439
|
+
var canDeleteTarget = ownsWorktree && (!store.get('permissions') || store.get('permissions').deleteProject !== false);
|
|
438
440
|
if (!isWorktree || canDeleteTarget) {
|
|
439
441
|
var sep2 = document.createElement("div");
|
|
440
442
|
sep2.className = "project-ctx-separator";
|
|
@@ -465,15 +467,26 @@ export function showProjectCtxMenu(anchorEl, slug, name, icon, position, options
|
|
|
465
467
|
var deleteItem = document.createElement("button");
|
|
466
468
|
deleteItem.className = "project-ctx-item project-ctx-delete";
|
|
467
469
|
deleteItem.setAttribute("role", "menuitem");
|
|
468
|
-
deleteItem.innerHTML = iconHtml("trash-2") + " <span>" + (isWorktree ? "
|
|
470
|
+
deleteItem.innerHTML = iconHtml("trash-2") + " <span>" + (isWorktree ? "Exclude worktree (keep files)" : "Remove Project") + "</span>";
|
|
469
471
|
deleteItem.addEventListener("click", function (e) {
|
|
470
472
|
e.stopPropagation();
|
|
471
473
|
closeProjectCtxMenu();
|
|
472
|
-
if (
|
|
474
|
+
if (isWorktree) reviewWorktreeRemoval(slug, "exclude", anchorEl);
|
|
475
|
+
else if (getWs() && store.get('connected')) {
|
|
473
476
|
getWs().send(JSON.stringify({ type: "remove_project_check", slug: slug, name: name }));
|
|
474
477
|
}
|
|
475
478
|
});
|
|
476
479
|
menu.appendChild(deleteItem);
|
|
480
|
+
if (isWorktree) {
|
|
481
|
+
var diskItem = document.createElement("button");
|
|
482
|
+
diskItem.className = "project-ctx-item project-ctx-delete";
|
|
483
|
+
diskItem.setAttribute("role", "menuitem");
|
|
484
|
+
diskItem.innerHTML = iconHtml("trash-2") + " <span>Delete worktree…</span>";
|
|
485
|
+
diskItem.addEventListener("click", function (event) {
|
|
486
|
+
event.stopPropagation(); closeProjectCtxMenu(); reviewWorktreeRemoval(slug, "delete", anchorEl);
|
|
487
|
+
});
|
|
488
|
+
menu.appendChild(diskItem);
|
|
489
|
+
}
|
|
477
490
|
}
|
|
478
491
|
|
|
479
492
|
document.body.appendChild(menu);
|
|
@@ -1103,7 +1116,7 @@ export function renderIconStrip(projects, currentSlug) {
|
|
|
1103
1116
|
} else {
|
|
1104
1117
|
var abbrev = document.createElement("span");
|
|
1105
1118
|
abbrev.className = "wt-branch-abbrev";
|
|
1106
|
-
abbrev.
|
|
1119
|
+
abbrev.innerHTML = iconHtml("git-branch");
|
|
1107
1120
|
wtEl.appendChild(abbrev);
|
|
1108
1121
|
}
|
|
1109
1122
|
|
|
@@ -1124,13 +1137,13 @@ export function renderIconStrip(projects, currentSlug) {
|
|
|
1124
1137
|
});
|
|
1125
1138
|
})(wt.slug);
|
|
1126
1139
|
|
|
1127
|
-
(function (slug, name, elem
|
|
1140
|
+
(function (slug, name, elem) {
|
|
1128
1141
|
elem.addEventListener("contextmenu", function (e) {
|
|
1129
1142
|
e.preventDefault();
|
|
1130
1143
|
e.stopPropagation();
|
|
1131
|
-
showProjectCtxMenu(elem, slug, name, null, null, { shareTargetSlug: true
|
|
1144
|
+
showProjectCtxMenu(elem, slug, name, null, null, { shareTargetSlug: true });
|
|
1132
1145
|
});
|
|
1133
|
-
})(wt.slug, wt.name, wtEl
|
|
1146
|
+
})(wt.slug, wt.name, wtEl);
|
|
1134
1147
|
|
|
1135
1148
|
if (!isWtActive && (wt.pendingPermissions > 0 || getPendingTuiAttention(wt.slug) > 0)) {
|
|
1136
1149
|
wtEl.classList.add("has-pending-perm");
|
|
@@ -1250,7 +1263,8 @@ function createMobileProjectItem(p, currentSlug, isWorktree) {
|
|
|
1250
1263
|
abbrev.textContent = p.icon;
|
|
1251
1264
|
parseEmojis(abbrev);
|
|
1252
1265
|
} else {
|
|
1253
|
-
abbrev.
|
|
1266
|
+
if (p.isWorktree) abbrev.innerHTML = iconHtml("git-branch");
|
|
1267
|
+
else abbrev.textContent = getProjectAbbrev(p.name);
|
|
1254
1268
|
}
|
|
1255
1269
|
el.appendChild(abbrev);
|
|
1256
1270
|
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { iconHtml } from './icons.js';
|
|
2
|
+
import { livePaneBinding, samePaneBinding } from './pane-binding.js';
|
|
3
|
+
import { syncPaneHeaderState } from './split-pane-header-state.js';
|
|
4
|
+
import { showConfirm, hideConfirm } from './app-misc.js';
|
|
5
|
+
|
|
6
|
+
export function createPaneForceStop(paneEl, frame) {
|
|
7
|
+
var button = document.createElement('button');
|
|
8
|
+
button.type = 'button'; button.className = 'split-pane-force-stop';
|
|
9
|
+
button.setAttribute('aria-label', 'Force stop session'); button.title = 'Force stop session and all its Workers';
|
|
10
|
+
button.innerHTML = iconHtml('power');
|
|
11
|
+
button.addEventListener('click', function (event) {
|
|
12
|
+
event.stopPropagation();
|
|
13
|
+
if (!syncPaneHeaderState(paneEl) || button.disabled || !frame.contentWindow || !frame.clayPane || !livePaneBinding(frame)) return;
|
|
14
|
+
var binding = frame.clayPane.identity().binding, sourceDocument = frame.contentDocument, settled = false;
|
|
15
|
+
var title = paneEl.querySelector('.split-pane-title');
|
|
16
|
+
var text = 'Force stop all work in ' + (title && title.textContent || 'this session') + ' and all of its Workers? Queued messages will be paused. The pane and conversation history will stay open. Commands or external requests already sent may still be running.';
|
|
17
|
+
function current() {
|
|
18
|
+
return paneEl.isConnected && !paneEl.hidden && !paneEl.inert && frame.contentDocument === sourceDocument &&
|
|
19
|
+
syncPaneHeaderState(paneEl) && !button.disabled && livePaneBinding(frame, { paneBinding: binding }) && samePaneBinding(binding, frame.clayPane.identity().binding);
|
|
20
|
+
}
|
|
21
|
+
var modal = document.getElementById('confirm-modal'), panel = modal && modal.querySelector('.confirm-dialog');
|
|
22
|
+
var cancel = document.getElementById('confirm-cancel'), ok = document.getElementById('confirm-ok');
|
|
23
|
+
function cleanup() {
|
|
24
|
+
settled = true;
|
|
25
|
+
window.removeEventListener('keydown', onKey, true);
|
|
26
|
+
if (panel) { panel.removeAttribute('role'); panel.removeAttribute('aria-modal'); panel.removeAttribute('aria-labelledby'); }
|
|
27
|
+
if (current()) button.focus();
|
|
28
|
+
}
|
|
29
|
+
function onKey(event) {
|
|
30
|
+
if (event.key === 'Escape') { event.preventDefault(); event.stopImmediatePropagation(); hideConfirm(); return; }
|
|
31
|
+
if (event.key !== 'Tab') return;
|
|
32
|
+
event.preventDefault(); event.stopImmediatePropagation();
|
|
33
|
+
var controls = [cancel, ok].filter(Boolean), index = controls.indexOf(document.activeElement);
|
|
34
|
+
controls[(index + (event.shiftKey ? controls.length - 1 : 1)) % controls.length].focus();
|
|
35
|
+
}
|
|
36
|
+
showConfirm(text, function () {
|
|
37
|
+
if (settled) return;
|
|
38
|
+
var valid = current(); cleanup();
|
|
39
|
+
if (!valid) return;
|
|
40
|
+
frame.contentWindow.postMessage({ type: 'clay-pane-force-stop', paneBinding: binding }, window.location.origin);
|
|
41
|
+
}, 'Force stop all work', true, cleanup);
|
|
42
|
+
if (panel) { panel.setAttribute('role', 'dialog'); panel.setAttribute('aria-modal', 'true'); panel.setAttribute('aria-labelledby', 'confirm-text'); }
|
|
43
|
+
window.addEventListener('keydown', onKey, true);
|
|
44
|
+
if (cancel) cancel.focus();
|
|
45
|
+
});
|
|
46
|
+
return button;
|
|
47
|
+
}
|
|
@@ -20,7 +20,7 @@ export function syncPaneHeaderState(paneEl) {
|
|
|
20
20
|
header.setAttribute('aria-busy', ready ? 'false' : 'true');
|
|
21
21
|
header.dataset.paneLoading = ready ? 'false' : 'true';
|
|
22
22
|
header.inert = !ready;
|
|
23
|
-
var actions = header.querySelectorAll('.split-pane-info, .split-pane-search, .split-pane-context, .split-pane-sidebar, .split-view-arrange-trigger');
|
|
23
|
+
var actions = header.querySelectorAll('.split-pane-info, .split-pane-force-stop, .split-pane-search, .split-pane-context, .split-pane-sidebar, .split-view-arrange-trigger');
|
|
24
24
|
for (var i = 0; i < actions.length; i++) {
|
|
25
25
|
var action = actions[i];
|
|
26
26
|
if (!ready) {
|
|
@@ -31,6 +31,16 @@ export function syncPaneHeaderState(paneEl) {
|
|
|
31
31
|
delete action.dataset.panePriorDisabled;
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
+
var force = header.querySelector('.split-pane-force-stop');
|
|
35
|
+
if (force) {
|
|
36
|
+
var session = getCachedSessions().find(function (item) { return item.id === Number(paneEl.dataset.sessionId); });
|
|
37
|
+
var support = session && session.forceTermination;
|
|
38
|
+
var reason = support && support.reason || 'Force termination is not yet supported for this vendor';
|
|
39
|
+
force.disabled = !ready || !support || support.supported !== true;
|
|
40
|
+
force.title = support && support.supported === true ? 'Force stop session and all its Workers' : reason;
|
|
41
|
+
force.setAttribute('aria-label', 'Force stop session' + (support && support.supported === true ? '' : ': ' + reason));
|
|
42
|
+
force.setAttribute('aria-disabled', force.disabled ? 'true' : 'false');
|
|
43
|
+
}
|
|
34
44
|
var close = paneEl.querySelector('.split-pane-close');
|
|
35
45
|
if (close) {
|
|
36
46
|
var host = paneEl.parentNode;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createPaneForceStop } from './split-pane-force-stop.js';
|
|
1
2
|
import { renderPaneRuntime } from './split-pane-runtime.js';
|
|
2
3
|
import { store } from './store.js';
|
|
3
4
|
import { getWs } from './ws-ref.js';
|
|
@@ -201,7 +202,6 @@ function createPaneSearchPopover(paneEl, frame, searchButton) {
|
|
|
201
202
|
throw error;
|
|
202
203
|
}
|
|
203
204
|
}
|
|
204
|
-
|
|
205
205
|
export function handlePaneSearchMessage(event, host) {
|
|
206
206
|
var frames = host ? host.querySelectorAll(".split-pane-frame") : [];
|
|
207
207
|
for (var i = 0; i < frames.length; i++) {
|
|
@@ -287,7 +287,6 @@ function isConfiguredWorker(groups, sessionId) {
|
|
|
287
287
|
export function updatePaneFullAccessButton(button, session) {
|
|
288
288
|
if (button) renderPermissionControl(button, permissionState(session));
|
|
289
289
|
}
|
|
290
|
-
|
|
291
290
|
export function createSplitPane(pane, closePane) {
|
|
292
291
|
var paneEl = document.createElement("section"); paneEl.className = "split-pane";
|
|
293
292
|
var header = document.createElement("header"); header.className = "split-pane-header"; header.title = pane.title;
|
|
@@ -345,6 +344,7 @@ export function createSplitPane(pane, closePane) {
|
|
|
345
344
|
}); header.appendChild(sidebarButton);
|
|
346
345
|
var search = document.createElement("button"); search.type = "button"; search.className = "split-pane-search"; search.setAttribute("aria-label", "Search this session"); search.title = "Search this session"; search.innerHTML = iconHtml("search");
|
|
347
346
|
header.insertBefore(search, close);
|
|
347
|
+
header.insertBefore(createPaneForceStop(paneEl, frame), close);
|
|
348
348
|
function refreshReadyHeader() {
|
|
349
349
|
if (paneEl.classList.contains('split-pane-cached')) return;
|
|
350
350
|
var id = Number(frame.dataset.requestedSessionId || frame.dataset.sessionId), current = sessionById(id);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { appendWorkerStopControls, syncWorkerForceStop } from './worker-stop-controls.js';
|
|
2
|
+
import { createWorkerActivityIndicator, syncWorkerActivityIndicator, setWorkerActivityRuntime } from './worker-activity-indicator.js';
|
|
1
3
|
import { syncWorkerMessagePreviews } from './worker-message-previews.js';
|
|
2
4
|
import { appendWorkerRemoval, reconcileWorkerRemoval } from './split-view-worker-removal.js';
|
|
3
5
|
import { workerCursorKey, workerCursorAdvances, shouldLoadWorkerPage, captureWorkerAnchor, restoreWorkerAnchor } from './split-view-worker-scroll.js';
|
|
@@ -52,14 +54,14 @@ function presentationSource(container, driver, connectionErrorBinding) {
|
|
|
52
54
|
var inSingleton = !view && store.get('projectSplitViewSingletonOrigin') === driver.sessionOriginId;
|
|
53
55
|
return inView || inSingleton ? { frame: frame, live: live, binding: binding, view: view, singleton: inSingleton } : null;
|
|
54
56
|
}
|
|
55
|
-
export function workerRemovalTarget(container, driverOrigin, workerOrigin, generation) {
|
|
57
|
+
export function workerRemovalTarget(container, driverOrigin, workerOrigin, generation, stopping) {
|
|
56
58
|
var driver = exactDriver(driverOrigin), worker = exactWorker(driver, workerOrigin, generation);
|
|
57
59
|
var source = presentationSource(container, driver);
|
|
58
60
|
if (!worker || worker.hidden || !driver || driver.hidden || (driver.ownerId || null) !== (store.get('myUserId') || null) ||
|
|
59
61
|
document.hidden || !source || !store.get('connected') || !container.isConnected || container.hidden || container.inert ||
|
|
60
|
-
source.frame.hidden || source.frame.inert || store.get('permissions') && store.get('permissions').sessionDelete === false) return null;
|
|
62
|
+
source.frame.hidden || source.frame.inert || !stopping && store.get('permissions') && store.get('permissions').sessionDelete === false) return null;
|
|
61
63
|
return { projectSlug: store.get('currentSlug'), ownerId: store.get('myUserId') || null, driverId: driver.id,
|
|
62
|
-
driverOriginId: driverOrigin, workerId: worker.id, workerOriginId: workerOrigin, generation: generation,
|
|
64
|
+
driverOriginId: driverOrigin, workerId: worker.id, workerOriginId: workerOrigin, generation: generation, forceTermination: worker.forceTermination,
|
|
63
65
|
bindingGeneration: source.binding.generation, bindingRequestId: source.binding.requestId,
|
|
64
66
|
viewRevision: store.get('projectSplitViewRevision'), socketGeneration: store.get('projectSplitViewSocketGeneration') };
|
|
65
67
|
}
|
|
@@ -204,6 +206,12 @@ function ensurePopover() {
|
|
|
204
206
|
element.setAttribute("role", "dialog"); element.setAttribute("aria-label", "Read-only Worker conversation"); document.body.appendChild(element);
|
|
205
207
|
var header = document.createElement("header"); header.className = "split-view-worker-popover-header";
|
|
206
208
|
var title = document.createElement("strong"); title.className = "worker-popover-title"; header.appendChild(title);
|
|
209
|
+
appendWorkerStopControls(header, function () {
|
|
210
|
+
var state = store.get('splitViewWorkerConversation');
|
|
211
|
+
if (!state || state.status === 'error') return null;
|
|
212
|
+
var pane = document.querySelector('.split-pane[data-session-origin="' + CSS.escape(state.driverOriginId) + '"]');
|
|
213
|
+
return workerRemovalTarget(pane, state.driverOriginId, state.workerOriginId, state.generation, true);
|
|
214
|
+
});
|
|
207
215
|
var closeButton = document.createElement("button"); closeButton.type = "button"; closeButton.setAttribute("aria-label", "Close Worker conversation"); closeButton.innerHTML = iconHtml("x");
|
|
208
216
|
closeButton.addEventListener("click", function () { close(true); }); header.appendChild(closeButton); element.appendChild(header);
|
|
209
217
|
var source = document.createElement("div"); source.className = "split-view-worker-popover-source"; element.appendChild(source);
|
|
@@ -227,6 +235,9 @@ function ensurePopover() {
|
|
|
227
235
|
body.addEventListener('touchstart', scrollIntent, { passive: true });
|
|
228
236
|
var footer = document.createElement("footer"); footer.className = "split-view-worker-popover-footer"; element.appendChild(footer);
|
|
229
237
|
}
|
|
238
|
+
syncWorkerForceStop(element, worker);
|
|
239
|
+
var stopButtons = element.querySelectorAll('.worker-conversation-stop');
|
|
240
|
+
for (var i = 0; i < stopButtons.length; i++) stopButtons[i].disabled = stopButtons[i].dataset.workerForceStop === 'true' && stopButtons[i].disabled || !store.get('connected') || state.status === 'error';
|
|
230
241
|
return element;
|
|
231
242
|
}
|
|
232
243
|
function renderContent() {
|
|
@@ -330,9 +341,9 @@ export function handleWorkerConversationChanged(message) {
|
|
|
330
341
|
message.subscriptionId !== state.subscriptionId || message.parentSessionOriginId !== state.driverOriginId ||
|
|
331
342
|
message.workerSessionOriginId !== state.workerOriginId || message.generation !== state.generation || !ensurePopover()) return false;
|
|
332
343
|
document.querySelectorAll('.split-view-worker-chip').forEach(function (chip) {
|
|
333
|
-
if (chip.dataset.workerOrigin === state.workerOriginId) {
|
|
334
|
-
|
|
335
|
-
|
|
344
|
+
if (chip.dataset.workerParent === state.driverOriginId && chip.dataset.workerOrigin === state.workerOriginId && Number(chip.dataset.workerGeneration) === state.generation) {
|
|
345
|
+
setWorkerActivityRuntime(chip.dataset.messagePreview, message.isProcessing === true);
|
|
346
|
+
syncWorkerActivityIndicator(chip, { isProcessing: message.isProcessing === true });
|
|
336
347
|
}
|
|
337
348
|
});
|
|
338
349
|
refreshWorkerConversation();
|
|
@@ -341,11 +352,6 @@ export function handleWorkerConversationChanged(message) {
|
|
|
341
352
|
export function handleWorkerSessionActivity(message) {
|
|
342
353
|
var worker = sessions().find(function (item) { return item.id === message.id && item.sessionRole === 'worker'; });
|
|
343
354
|
if (!worker || !store.get('connected')) return;
|
|
344
|
-
document.querySelectorAll('.split-view-worker-chip').forEach(function (chip) {
|
|
345
|
-
if (chip.dataset.workerOrigin !== worker.sessionOriginId) return;
|
|
346
|
-
var dot = chip.querySelector('.split-view-worker-dot');
|
|
347
|
-
if (dot) { dot.dataset.state = 'working'; dot.setAttribute('aria-label', 'Worker activity received'); }
|
|
348
|
-
});
|
|
349
355
|
var state = store.get('splitViewWorkerConversation');
|
|
350
356
|
if (state && state.workerOriginId === worker.sessionOriginId && state.generation === worker.workerGeneration) refreshWorkerConversation();
|
|
351
357
|
}
|
|
@@ -359,8 +365,12 @@ export function renderPaneWorkers(container, driver) {
|
|
|
359
365
|
var source = presentationSource(container, driver);
|
|
360
366
|
root.hidden = !source || data.items.length === 0;
|
|
361
367
|
if (!source) { if (store.get('splitViewWorkerConversation') && store.get('splitViewWorkerConversation').driverOriginId === driver.sessionOriginId) ensurePopover(); return; }
|
|
362
|
-
|
|
363
|
-
|
|
368
|
+
var signature = JSON.stringify([store.get('myUserId'), store.get('currentSlug'), driver.sessionOriginId, driver.title, store.get('projectSplitViewSocketGeneration'), data.items.map(function (worker) {
|
|
369
|
+
return [worker.id, worker.sessionOriginId, worker.workerGeneration, worker.vendor, worker.model, worker.title, data.currentIds.has(worker.id)];
|
|
370
|
+
})]);
|
|
371
|
+
var rebuild = root.dataset.workerSignature !== signature;
|
|
372
|
+
if (rebuild) { root.textContent = ''; root.dataset.workerSignature = signature; }
|
|
373
|
+
if (rebuild) data.items.forEach(function (worker) {
|
|
364
374
|
var pill = document.createElement("span"); pill.className = "split-view-worker-pill";
|
|
365
375
|
var button = document.createElement("button"); button.type = "button"; button.className = "split-view-worker-chip";
|
|
366
376
|
button.dataset.workerParent = driver.sessionOriginId; button.dataset.workerOrigin = worker.sessionOriginId; button.dataset.workerGeneration = String(worker.workerGeneration);
|
|
@@ -369,8 +379,7 @@ export function renderPaneWorkers(container, driver) {
|
|
|
369
379
|
button.title = (VENDOR_NAMES[worker.vendor] || worker.vendor || "Unknown vendor") + " · " + modelLabel + " · Split Worker #" + worker.id +
|
|
370
380
|
(current ? "" : " · Archived · Gen " + worker.workerGeneration) + " · " + (worker.title || "Worker");
|
|
371
381
|
button.setAttribute("aria-label", button.title + " conversation");
|
|
372
|
-
var dot =
|
|
373
|
-
dot.setAttribute('aria-label', !store.get('connected') ? 'Disconnected' : worker.isProcessing ? 'Worker is working' : 'Worker is idle');
|
|
382
|
+
var dot = createWorkerActivityIndicator();
|
|
374
383
|
if (VENDOR_AVATARS[worker.vendor]) {
|
|
375
384
|
var vendorIcon = document.createElement("img"); vendorIcon.className = "split-view-worker-vendor";
|
|
376
385
|
vendorIcon.src = VENDOR_AVATARS[worker.vendor]; vendorIcon.alt = ""; vendorIcon.setAttribute("aria-hidden", "true"); button.appendChild(vendorIcon);
|
|
@@ -389,8 +398,15 @@ export function renderPaneWorkers(container, driver) {
|
|
|
389
398
|
button.addEventListener("click", function () { if (presentationSource(container, exactDriver(driver.sessionOriginId))) open(button, driver, worker); }); pill.appendChild(button); root.appendChild(pill);
|
|
390
399
|
appendWorkerRemoval(pill, button, container, driver, worker);
|
|
391
400
|
});
|
|
401
|
+
root.querySelectorAll('.split-view-worker-chip').forEach(function (chip) {
|
|
402
|
+
var worker = data.items.find(function (item) { return item.sessionOriginId === chip.dataset.workerOrigin; });
|
|
403
|
+
syncWorkerActivityIndicator(chip, worker);
|
|
404
|
+
var conversation = store.get('splitViewWorkerConversation');
|
|
405
|
+
chip.setAttribute('aria-expanded', conversation && conversation.driverOriginId === driver.sessionOriginId && conversation.workerOriginId === chip.dataset.workerOrigin ? 'true' : 'false');
|
|
406
|
+
});
|
|
392
407
|
introduceWorkerViewer(container, driver);
|
|
393
408
|
syncWorkerMessagePreviews();
|
|
409
|
+
if (rebuild) refreshIcons();
|
|
394
410
|
var conversation = store.get('splitViewWorkerConversation');
|
|
395
411
|
if (conversation) ensurePopover();
|
|
396
412
|
}
|
|
@@ -417,8 +433,7 @@ store.subscribe(function (state, previous) {
|
|
|
417
433
|
if (state.paneBindingSessions !== previous.paneBindingSessions || state.connected !== previous.connected) {
|
|
418
434
|
document.querySelectorAll('.split-view-worker-chip').forEach(function (chip) {
|
|
419
435
|
var worker = sessions().find(function (item) { return item.sessionOriginId === chip.dataset.workerOrigin; });
|
|
420
|
-
|
|
421
|
-
if (dot) dot.dataset.state = !state.connected ? 'disconnected' : worker && worker.isProcessing ? 'working' : 'ready';
|
|
436
|
+
syncWorkerActivityIndicator(chip, worker);
|
|
422
437
|
});
|
|
423
438
|
if (state.connected && state.splitViewWorkerConversation) refreshWorkerConversation();
|
|
424
439
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getWs } from './ws-ref.js';
|
|
1
2
|
import { store } from './store.js';
|
|
2
3
|
import { stopSTT } from './stt-controller.js';
|
|
3
4
|
export var speechLanguages = [
|
|
@@ -16,35 +17,51 @@ export function voiceProvider(id) {
|
|
|
16
17
|
var settings = store.get('speechSettings');
|
|
17
18
|
return settings && settings.providers.find(function (provider) { return provider.id === id; });
|
|
18
19
|
}
|
|
20
|
+
function context() { return JSON.stringify([store.get('myUserId'), store.get('speechSettingsContextGeneration')]); }
|
|
21
|
+
function lostAccess() {
|
|
22
|
+
store.set({speechPopup:null,speechRetry:null,speechSettings:null,speechSettingsSaving:false,speechSettingsError:'Sign in again to manage audio transcription keys.',speechSettingsContextGeneration:(store.get('speechSettingsContextGeneration') || 0)+1,speechSettingsRequest:(store.get('speechSettingsRequest') || 0)+1});
|
|
23
|
+
}
|
|
19
24
|
export async function loadSpeechSettings() {
|
|
20
25
|
if (store.get('speechSettingsSaving')) return;
|
|
26
|
+
var actor = context(), socket = getWs();
|
|
21
27
|
var request = (store.get('speechSettingsRequest') || 0) + 1;
|
|
22
28
|
store.set({ speechSettingsRequest: request });
|
|
23
29
|
try {
|
|
24
30
|
var response = await fetch('/api/speech/settings');
|
|
31
|
+
if ((response.status === 401 || response.status === 403) && actor === context() && socket === getWs() && store.get('speechSettingsRequest') === request) { lostAccess(); return; }
|
|
25
32
|
if (!response.ok) throw new Error('Voice settings unavailable. Please try again.');
|
|
26
33
|
var settings = await response.json();
|
|
27
|
-
if (store.get('speechSettingsRequest') !== request) return;
|
|
34
|
+
if (actor !== context() || socket !== getWs() || store.get('speechSettingsRequest') !== request) return;
|
|
28
35
|
var previous = store.get('speechSettings');
|
|
29
36
|
if ((previous && JSON.stringify(previous) !== JSON.stringify(settings)) ||
|
|
30
37
|
(!previous && settings.selected !== 'browser')) stopSTT();
|
|
31
38
|
if (!previous || JSON.stringify(previous) !== JSON.stringify(settings)) store.set({ speechSettings: settings, speechLang: settings.language || 'en-US', speechSettingsError: '' });
|
|
32
39
|
else if (store.get('speechSettingsError')) store.set({ speechSettingsError: '' });
|
|
33
|
-
} catch (error) { if (store.get('speechSettingsRequest') === request) store.set({ speechSettingsError: 'Voice settings unavailable. Please try again.' }); }
|
|
40
|
+
} catch (error) { if (actor === context() && socket === getWs() && store.get('speechSettingsRequest') === request) store.set({ speechSettingsError: 'Voice settings unavailable. Please try again.' }); }
|
|
34
41
|
}
|
|
35
42
|
export async function saveSpeechSettings(change) {
|
|
36
43
|
if (store.get('speechSettingsSaving')) throw new Error('Voice settings are being saved. Please wait.');
|
|
44
|
+
var actor = context(), socket = getWs(), request = (store.get('speechSettingsRequest') || 0) + 1;
|
|
37
45
|
stopSTT();
|
|
38
|
-
store.set({ speechRetry: null, speechSettingsSaving: true, speechSettingsRequest:
|
|
46
|
+
store.set({ speechRetry: null, speechSettingsSaving: true, speechSettingsRequest: request });
|
|
39
47
|
try {
|
|
40
48
|
var response = await fetch('/api/speech/settings', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(change) });
|
|
41
49
|
var result = await response.json();
|
|
50
|
+
if (actor !== context() || socket !== getWs() || store.get('speechSettingsRequest') !== request) throw new Error('Account or connection changed. Reopen API connections.');
|
|
51
|
+
if (response.status === 401 || response.status === 403) { lostAccess(); throw new Error('Sign in again to manage audio transcription keys.'); }
|
|
42
52
|
if (!response.ok) throw new Error(result.error || 'Could not save voice settings.');
|
|
43
53
|
store.set({ speechSettings: result, speechLang: result.language || 'en-US', speechSettingsError: '' });
|
|
44
|
-
} finally { store.set({ speechSettingsSaving: false }); }
|
|
54
|
+
} finally { if (actor === context() && store.get('speechSettingsRequest') === request) store.set({ speechSettingsSaving: false }); }
|
|
45
55
|
try { window.top.document.dispatchEvent(new window.top.Event('clay:speech-settings-saved')); } catch (error) { /* Standalone host. */ }
|
|
46
56
|
}
|
|
47
57
|
export async function saveSpeechLanguage(code) {
|
|
48
58
|
await saveSpeechSettings({ language: code });
|
|
49
59
|
window.dispatchEvent(new CustomEvent('clay:speech-language-saved', { detail: code }));
|
|
50
60
|
}
|
|
61
|
+
|
|
62
|
+
store.subscribe(function (state, previous) {
|
|
63
|
+
if (state.myUserId !== previous.myUserId || state.projectSplitViewSocketGeneration !== previous.projectSplitViewSocketGeneration || state.currentSlug !== previous.currentSlug || state.connected === false && previous.connected !== false) {
|
|
64
|
+
stopSTT();
|
|
65
|
+
store.set({speechPopup:null,speechRetry:null,speechSettings:null,speechSettingsSaving:false,speechSettingsError:state.connected === false ? 'Reconnect before loading audio transcription providers.' : 'Account or connection changed. Reload providers to continue.',speechSettingsContextGeneration:(state.speechSettingsContextGeneration || 0)+1,speechSettingsRequest:(state.speechSettingsRequest || 0)+1});
|
|
66
|
+
}
|
|
67
|
+
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { initPersonalProviders, loadPersonalProviders, leavePersonalProviders } from './user-settings-providers.js';
|
|
1
2
|
import { initPersonalIntegrations, loadPersonalIntegrations } from './user-settings-integrations.js';
|
|
2
3
|
import { getWs } from './ws-ref.js';
|
|
3
4
|
import { store } from './store.js';
|
|
@@ -20,13 +21,14 @@ export function buildUserSettingsLayout() {
|
|
|
20
21
|
var push=document.getElementById('settings-notif-push').closest('label');
|
|
21
22
|
heading(notifications,'This device','Browser permission and push delivery are configured separately on each browser.');notifications.appendChild(push);
|
|
22
23
|
var accountNotifications=document.createElement('h3');accountNotifications.textContent='Account preferences';notifications.querySelector('.settings-card').before(accountNotifications);
|
|
23
|
-
var chat = section('us-chat');
|
|
24
|
-
var
|
|
25
|
-
|
|
24
|
+
var chat = section('us-chat');
|
|
25
|
+
var providers = document.createElement('div'); providers.className = 'us-section'; providers.dataset.section = 'us-providers';
|
|
26
|
+
providers.innerHTML = '<h2>AI providers</h2><p class="settings-hint">Set up coding agents on the server and manage your personal audio transcription API keys.</p><div class="settings-section-heading"><h3 id="coding-agents" data-settings-section>Coding agents</h3><span id="us-provider-refresh"></span></div><div id="us-coding-agents"></div><h3 id="api-connections" data-settings-section>API connections</h3><div id="us-voice-settings"></div>';
|
|
27
|
+
root.querySelector('.us-modal-content').appendChild(providers);
|
|
26
28
|
var ai = document.createElement('div'); ai.className = 'us-section'; ai.dataset.section = 'us-ai';
|
|
27
29
|
ai.innerHTML = '<h2>AI preferences</h2><p class="settings-hint">Defaults for new Clay interviews and built-in AI assistance. Project defaults, individual Mates and existing conversation runtimes stay separate.</p><div id="us-default-ai"></div>';
|
|
28
30
|
root.querySelector('.us-modal-content').appendChild(ai);
|
|
29
|
-
heading(ai, 'Execution behavior');
|
|
31
|
+
heading(ai, 'Execution behavior'); ai.lastElementChild.id = 'execution-behavior';
|
|
30
32
|
['us-auto-continue'].forEach(function (id) {ai.appendChild(document.getElementById(id).closest('.settings-card'));});
|
|
31
33
|
document.getElementById('us-auto-continue').closest('label').querySelector('.settings-hint').textContent = 'When a rate-limited conversation becomes eligible to retry, Clay can send a continuation automatically. This may resume work and consume usage without another message from you.';
|
|
32
34
|
heading(ai, 'Command permissions', 'These are personal Claude CLI allow rules, separate from model selection and Clay session approval modes. Managed defaults are shown for reference. Server access restrictions still apply. On an OS-user server these install in your OS home; otherwise they install in the shared server process home and can affect other Claude CLI launches there.');
|
|
@@ -36,7 +38,8 @@ export function buildUserSettingsLayout() {
|
|
|
36
38
|
root.querySelector('.us-modal-content').appendChild(agent);
|
|
37
39
|
section('us-mates').remove();
|
|
38
40
|
var experiments = document.createElement('details'); experiments.className = 'us-experiments'; experiments.innerHTML = '<summary>Experimental features</summary><p class="settings-hint">Personal opt-ins for features that may change.</p>';
|
|
39
|
-
experiments.appendChild(document.getElementById('us-capsules-enabled').closest('.settings-card'));
|
|
41
|
+
experiments.appendChild(document.getElementById('us-capsules-enabled').closest('.settings-card'));ai.appendChild(experiments);experiments.id = 'experimental-features';section('us-experimental').remove();chat.remove();
|
|
42
|
+
initPersonalProviders();
|
|
40
43
|
section('us-email').remove();
|
|
41
44
|
initPersonalIntegrations();
|
|
42
45
|
var account = section('us-account');heading(account, 'Sign out');account.appendChild(document.getElementById('us-logout-btn').closest('.settings-card'));section('us-logout').remove();
|
|
@@ -47,12 +50,12 @@ export function buildUserSettingsLayout() {
|
|
|
47
50
|
}
|
|
48
51
|
export function loadUserSettingsExtras(page) {
|
|
49
52
|
if (page === 'us-integrations') loadPersonalIntegrations();
|
|
50
|
-
if (page === 'us-
|
|
53
|
+
if (page === 'us-providers') { loadSpeechSettings(); loadPersonalProviders(); } else leavePersonalProviders();
|
|
51
54
|
if (page === 'us-agent') mountAgentBehavior(document.getElementById('us-agent-behavior'));
|
|
52
55
|
if (page === 'us-ai') { mountDefaultAi(document.getElementById('us-default-ai')); requestDefaultAi(); var ws=getWs();if(ws && ws.readyState===1)ws.send(JSON.stringify({type:'get_claude_allow_list'})); }
|
|
53
56
|
}
|
|
54
57
|
export function leaveAgentBehaviorPage() {unmountAgentBehavior();resetAgentBehavior();}
|
|
55
|
-
export function finishUserSettingsExtras() {leaveAgentBehaviorPage();closeSpeechPicker();unmountDefaultAi(document.getElementById('us-default-ai'));}
|
|
58
|
+
export function finishUserSettingsExtras() {leavePersonalProviders();leaveAgentBehaviorPage();closeSpeechPicker();unmountDefaultAi(document.getElementById('us-default-ai'));}
|
|
56
59
|
// Returns the draft prompt for leaving one personal page (or every page when
|
|
57
60
|
// pageId is omitted); the shell shows it with the shared confirmation.
|
|
58
61
|
export function personalLeavePrompt(pageId) {
|
|
@@ -64,7 +67,7 @@ export function personalLeavePrompt(pageId) {
|
|
|
64
67
|
var key = document.getElementById('us-linear-key');
|
|
65
68
|
var keyDraft = key && key.value && store.get('personalLinearEditing') && !(key.form && key.form.hidden);
|
|
66
69
|
var dirty = leaving('us-integrations') && (store.get('personalEmailDirty') || store.get('personalLinearDirty') || keyDraft) ||
|
|
67
|
-
leaving('us-
|
|
70
|
+
leaving('us-providers') && store.get('userVoiceDirty') ||
|
|
68
71
|
leaving('us-notifications') && store.get('notificationDraft') ||
|
|
69
72
|
leaving('us-appearance') && (store.get('appearanceDraft') || store.get('userTerminalDirty')) ||
|
|
70
73
|
leaving('us-account') && (store.get('userProfileDirty') || pinDirty) ||
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { getWs } from './ws-ref.js';
|
|
3
|
+
import { personalProviderContext } from './personal-provider-context.js';
|
|
4
|
+
function send(type,provider,reviewId,render) {
|
|
5
|
+
var ws = getWs();
|
|
6
|
+
if (!ws || ws.readyState !== 1 || !store.get('personalProvidersOpen') || store.get('personalProviderContext') !== personalProviderContext()) return false;
|
|
7
|
+
var requestId = type === 'stop' || type === 'cancel' ? store.get('personalProviderInstallRequest') : crypto.randomUUID();
|
|
8
|
+
store.set({personalProviderInstallRequest:requestId});
|
|
9
|
+
ws.send(JSON.stringify({requestId:requestId,type:'personal_provider_install_'+type,vendor:provider.id,personalRequestId:store.get('personalProviderRevision'),reviewId:reviewId}));
|
|
10
|
+
if (type === 'review' || type === 'start') setTimeout(function () { if (store.get('personalProviderInstallRequest') === requestId && store.get('personalProviderInstallAwaiting') === requestId) { store.set({personalProviderInstallBusy:false,personalProviderInstall:{vendor:provider.id,state:'unknown',message:'Installation response timed out. Reopen settings and refresh status; never assume an installation completed.'}}); if (render) render(); } },10000);
|
|
11
|
+
store.set({personalProviderInstallAwaiting:requestId}); return true;
|
|
12
|
+
}
|
|
13
|
+
export function resetProviderInstall() { store.set({personalProviderInstall:null,personalProviderInstallBusy:false,personalProviderInstallRequest:null,personalProviderInstallAwaiting:null}); }
|
|
14
|
+
export function handleProviderInstall(message,render) {
|
|
15
|
+
if (message.type !== 'personal_provider_install') return false;
|
|
16
|
+
if (!store.get('personalProvidersOpen') || !store.get('personalProviderRevision') || message.requestId !== store.get('personalProviderInstallRequest') || message.personalRequestId !== store.get('personalProviderRevision') || store.get('personalProviderContext') !== personalProviderContext()) return true;
|
|
17
|
+
store.set({personalProviderInstallAwaiting:null,personalProviderInstall:message,personalProviderInstallBusy:['review','started','stopping','unknown'].indexOf(message.state)>=0}); render();
|
|
18
|
+
var editor=document.getElementById('provider-setup-'+message.vendor); var status=editor && editor.querySelector('[data-install-status]'); if (status) status.focus({preventScroll:true}); return true;
|
|
19
|
+
}
|
|
20
|
+
export function renderProviderInstall(editor,provider,render) {
|
|
21
|
+
if (!provider.canInstall) return;
|
|
22
|
+
var item = store.get('personalProviderInstall');
|
|
23
|
+
var actions = document.createElement('div'); actions.className = 'settings-form-actions';
|
|
24
|
+
if (!item || item.vendor !== provider.id || ['review','started','stopping','unknown'].indexOf(item.state)<0) {
|
|
25
|
+
var review = document.createElement('button'); review.type='button'; review.className='settings-btn-sm'; review.textContent='Review installation'; review.disabled=!!store.get('personalProviderInstallBusy') || !!store.get('personalProviderSigningIn');
|
|
26
|
+
review.onclick=function () { if (send('review',provider,null,render)) { store.set({personalProviderInstallBusy:true}); review.disabled=true; } }; actions.appendChild(review);
|
|
27
|
+
}
|
|
28
|
+
if (item && item.vendor === provider.id) {
|
|
29
|
+
var status=document.createElement('p'); status.className='settings-status'; status.role='status'; status.tabIndex=-1; status.dataset.installStatus=''; status.textContent=item.message; editor.appendChild(status);
|
|
30
|
+
if (item.state==='review') {
|
|
31
|
+
var details=document.createElement('p'); details.className='settings-hint'; details.textContent='Runtime account: '+item.runtime.user+' (UID '+item.runtime.uid+'), HOME '+item.runtime.home+'. '+item.effects; editor.appendChild(details);
|
|
32
|
+
var command=document.createElement('pre'); command.className='settings-hint'; command.style.whiteSpace='pre-wrap'; command.style.overflowWrap='anywhere'; command.textContent=item.command; editor.appendChild(command);
|
|
33
|
+
var source=document.createElement('a'); source.className='settings-link'; source.href=item.source; source.target='_blank'; source.rel='noopener noreferrer'; source.textContent='Official setup instructions'; editor.appendChild(source);
|
|
34
|
+
var confirm=document.createElement('button'); confirm.type='button'; confirm.className='settings-btn-sm'; confirm.textContent='Install for this account'; confirm.onclick=function () { if (send('start',provider,item.reviewId,render)) { confirm.disabled=true; } }; actions.appendChild(confirm);
|
|
35
|
+
var cancel=document.createElement('button'); cancel.type='button'; cancel.className='settings-btn-sm'; cancel.textContent='Cancel installation'; cancel.onclick=function () { send('cancel',provider,item.reviewId); resetProviderInstall(); render(); }; actions.appendChild(cancel);
|
|
36
|
+
}
|
|
37
|
+
if (item.state==='started') {
|
|
38
|
+
var stop=document.createElement('button'); stop.type='button'; stop.className='settings-btn-sm'; stop.textContent='Stop installation'; stop.onclick=function () { send('stop',provider); stop.disabled=true; }; actions.appendChild(stop);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
editor.appendChild(actions);
|
|
42
|
+
}
|