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.
Files changed (114) hide show
  1. package/lib/clay-product-control-input.js +2 -2
  2. package/lib/clay-product-control-tools.js +1 -1
  3. package/lib/clay-product-control.js +4 -3
  4. package/lib/daemon-project-access.js +10 -2
  5. package/lib/daemon-projects.js +16 -4
  6. package/lib/daemon-worktree-controls.js +171 -0
  7. package/lib/daemon.js +14 -63
  8. package/lib/personal-provider-install.js +85 -0
  9. package/lib/personal-provider-settings.js +94 -0
  10. package/lib/project-logs-service.js +2 -2
  11. package/lib/project-pair-task-control.js +1 -1
  12. package/lib/project-pending-message-queue.js +6 -2
  13. package/lib/project-product-control.js +1 -0
  14. package/lib/project-session-actions.js +46 -3
  15. package/lib/project-session-pair.js +2 -0
  16. package/lib/project-sessions.js +11 -3
  17. package/lib/project-user-message.js +5 -1
  18. package/lib/project-vendor-login.js +95 -32
  19. package/lib/project-worker-auth.js +71 -0
  20. package/lib/project-worker-conversation.js +11 -2
  21. package/lib/project.js +9 -0
  22. package/lib/public/app.js +22 -0
  23. package/lib/public/css/icon-strip.css +5 -0
  24. package/lib/public/css/overlays.css +18 -0
  25. package/lib/public/css/settings-shell.css +14 -0
  26. package/lib/public/css/settings-ui.css +6 -0
  27. package/lib/public/css/split-view-layout.css +16 -11
  28. package/lib/public/modules/agent-message-bubbles.js +33 -4
  29. package/lib/public/modules/app-messages.js +7 -1
  30. package/lib/public/modules/app-projects.js +3 -4
  31. package/lib/public/modules/clay-control-copy.js +1 -0
  32. package/lib/public/modules/clay-tool-presentation.js +1 -1
  33. package/lib/public/modules/generation-stop.js +4 -4
  34. package/lib/public/modules/pane-activation.js +2 -2
  35. package/lib/public/modules/pane-bridge.js +4 -2
  36. package/lib/public/modules/personal-provider-context.js +11 -0
  37. package/lib/public/modules/project-settings.js +1 -1
  38. package/lib/public/modules/settings-section-nav.js +58 -0
  39. package/lib/public/modules/settings-shell-registry.js +4 -2
  40. package/lib/public/modules/settings-shell.js +17 -6
  41. package/lib/public/modules/sidebar-projects.js +23 -9
  42. package/lib/public/modules/split-pane-force-stop.js +47 -0
  43. package/lib/public/modules/split-pane-header-state.js +11 -1
  44. package/lib/public/modules/split-pane-renderer.js +2 -2
  45. package/lib/public/modules/split-view-workers.js +32 -17
  46. package/lib/public/modules/stt-settings.js +21 -4
  47. package/lib/public/modules/user-settings-layout.js +11 -8
  48. package/lib/public/modules/user-settings-provider-install.js +42 -0
  49. package/lib/public/modules/user-settings-providers.js +102 -0
  50. package/lib/public/modules/user-settings-voice.js +50 -41
  51. package/lib/public/modules/vendor-login-browser.js +1 -1
  52. package/lib/public/modules/vendor-login.js +72 -12
  53. package/lib/public/modules/worker-activity-indicator.js +49 -0
  54. package/lib/public/modules/worker-message-previews.js +47 -11
  55. package/lib/public/modules/worker-stop-controls.js +36 -0
  56. package/lib/public/modules/worktree-controls.js +86 -0
  57. package/lib/public/pane-assets/{app-SEMVZ4AY.js → app-ANJS2LSK.js} +1 -1
  58. package/lib/public/pane-assets/{chunk-4YE4RU27.js → chunk-32YIYF7R.js} +1 -1
  59. package/lib/public/pane-assets/{chunk-BNNPJJVG.js → chunk-3EJ5IAHZ.js} +1 -1
  60. package/lib/public/pane-assets/{chunk-C6P7PICY.js → chunk-3TIVBAKM.js} +1 -1
  61. package/lib/public/pane-assets/{chunk-XYZGVYWW.js → chunk-4QFNLHW5.js} +1 -1
  62. package/lib/public/pane-assets/{chunk-FK3YLGAP.js → chunk-5XMZMIFG.js} +1 -1
  63. package/lib/public/pane-assets/chunk-6S2C7Q2N.js +1 -0
  64. package/lib/public/pane-assets/chunk-7B2GJI57.js +1 -0
  65. package/lib/public/pane-assets/{chunk-QBCM7QEA.js → chunk-BOFNCPTN.js} +1 -1
  66. package/lib/public/pane-assets/{chunk-CQNPEH2M.js → chunk-CXWAJEN5.js} +1 -1
  67. package/lib/public/pane-assets/{chunk-5UBN6GR7.js → chunk-EWTXBZJO.js} +1 -1
  68. package/lib/public/pane-assets/{chunk-YR5GH3GI.js → chunk-G35ZOJGP.js} +1 -1
  69. package/lib/public/pane-assets/chunk-HYT24UBT.js +1 -0
  70. package/lib/public/pane-assets/chunk-ISQEPZMY.js +1 -0
  71. package/lib/public/pane-assets/{chunk-E4VM2IDB.js → chunk-JM46WGDH.js} +1 -1
  72. package/lib/public/pane-assets/chunk-LBJG4MK2.js +174 -0
  73. package/lib/public/pane-assets/{chunk-K4PAN77B.js → chunk-P3AK4ORJ.js} +1 -1
  74. package/lib/public/pane-assets/chunk-QKPGE7DN.js +1 -0
  75. package/lib/public/pane-assets/chunk-TS5JUEVH.js +1 -0
  76. package/lib/public/pane-assets/{chunk-QWQB2YRF.js → chunk-UGH5MCNK.js} +1 -1
  77. package/lib/public/pane-assets/{chunk-YVVT3CTV.js → chunk-V5DLPSLT.js} +1 -1
  78. package/lib/public/pane-assets/chunk-VFKDU64W.js +1 -0
  79. package/lib/public/pane-assets/{chunk-U7EHYRJZ.js → chunk-XSSTR6GC.js} +1 -1
  80. package/lib/public/pane-assets/{chunk-M5CWIIIT.js → chunk-XVESKMU3.js} +1 -1
  81. package/lib/public/pane-assets/manifest.json +394 -386
  82. package/lib/public/pane-assets/style-VBVCWV44.css +1 -0
  83. package/lib/sdk-bridge.js +40 -6
  84. package/lib/sdk-force-stop.js +62 -0
  85. package/lib/sdk-message-processor.js +2 -1
  86. package/lib/server-worktree-controls.js +34 -0
  87. package/lib/server.js +11 -1
  88. package/lib/session-force-stop-support.js +15 -0
  89. package/lib/sessions.js +6 -1
  90. package/lib/terminal-manager.js +1 -1
  91. package/lib/terminal.js +13 -1
  92. package/lib/vendor-login-browser-flow.js +1 -1
  93. package/lib/worker-activity.js +26 -0
  94. package/lib/worker-message-previews.js +12 -7
  95. package/lib/worktree-branch.js +57 -0
  96. package/lib/worktree-identity.js +39 -0
  97. package/lib/worktree-preferences.js +25 -0
  98. package/lib/worktree-ref-transaction.js +36 -0
  99. package/lib/yoke/adapters/antigravity.js +3 -0
  100. package/lib/yoke/force-termination.js +26 -0
  101. package/lib/yoke/interface.js +14 -0
  102. package/lib/yoke/process-force-termination.js +24 -0
  103. package/lib/yoke/provider-install-recipes.js +20 -0
  104. package/lib/yoke/vendor-registry.js +20 -1
  105. package/package.json +1 -1
  106. package/lib/public/pane-assets/chunk-6AGSOAOD.js +0 -1
  107. package/lib/public/pane-assets/chunk-AATAIAXL.js +0 -1
  108. package/lib/public/pane-assets/chunk-D3CE5FZL.js +0 -1
  109. package/lib/public/pane-assets/chunk-GKSLBGVE.js +0 -168
  110. package/lib/public/pane-assets/chunk-J5PTSKJ6.js +0 -1
  111. package/lib/public/pane-assets/chunk-QPBO6ID4.js +0 -1
  112. package/lib/public/pane-assets/chunk-RA6VUJTY.js +0 -1
  113. package/lib/public/pane-assets/chunk-XOZUM5ET.js +0 -1
  114. package/lib/public/pane-assets/style-DZX6JAMF.css +0 -1
@@ -83,7 +83,7 @@ function attachProjectWorkerConversation(ctx) {
83
83
  var activity = require('./project-pair-lifecycle-status').activityStatus(worker);
84
84
  return { id: worker.localId, sessionOriginId: worker.sessionOriginId, parentSessionOriginId: driverOrigin, generation: binding.input.generation,
85
85
  association: configured.indexOf(worker.localId) >= 0 ? 'current' : 'archived',
86
- runtime: require('./session-activity-state').currentState(worker), delegated: activity.delegated,
86
+ recentActivity: require('./worker-activity').project(worker), runtime: require('./session-activity-state').currentState(worker), delegated: activity.delegated,
87
87
  currentTaskId: activity.currentTaskId, interrupted: !!activity.interruption, lastTurnInterrupted: activity.lastTurnInterrupted };
88
88
  }) };
89
89
  } };
@@ -221,7 +221,16 @@ function attachProjectWorkerConversation(ctx) {
221
221
  send(ws, response);
222
222
  return true;
223
223
  }
224
- return { handleMessage: handleMessage, supervision: supervision };
224
+ function currentPair(ws, target) {
225
+ var user = actor(ws), pair = user && exactPair(ws, target, user);
226
+ if (!pair || pair.driver.destroying || pair.worker.destroying) return null;
227
+ var group = ctx.getWorkerGroup && ctx.getWorkerGroup(pair.driver.localId);
228
+ var roles = require('./session-split-group-roles').resolveLiveRoles(sm, group);
229
+ if (!roles || roles.driver !== pair.driver || roles.workers.indexOf(pair.worker) < 0 ||
230
+ pair.worker._pairGeneration != null && pair.worker._pairGeneration !== target.generation) return null;
231
+ return { driver: pair.driver, worker: pair.worker, group: group, linuxUser: user.linuxUser };
232
+ }
233
+ return { handleMessage: handleMessage, supervision: supervision, currentPair: currentPair };
225
234
  }
226
235
 
227
236
  module.exports = { attachProjectWorkerConversation: attachProjectWorkerConversation };
package/lib/project.js CHANGED
@@ -614,6 +614,7 @@ function createProjectContext(opts) {
614
614
  // --- Session manager ---
615
615
  var _gitAttribution = null;
616
616
  var sm = createSessionManager({
617
+ onAuthRequired: function (session, msg) { return _workerAuth && _workerAuth.emit(session, msg); },
617
618
  isMate: isMate,
618
619
  cwd: cwd,
619
620
  send: send,
@@ -718,6 +719,11 @@ function createProjectContext(opts) {
718
719
  getLinuxUserForSession: getLinuxUserForSession,
719
720
  getOsUserInfoForWs: getOsUserInfoForWs,
720
721
  });
722
+ var _workerAuth = require('./project-worker-auth').attach({
723
+ sm: sm, clients: clients, slug: slug, osUsers: osUsers, sendTo: sendTo,
724
+ currentPair: _workerConversation.currentPair, requestAccess: _requestAccess,
725
+ getOsUserInfoForWs: getOsUserInfoForWs,
726
+ });
721
727
 
722
728
  var _projMode = typeof opts.onGetProjectDefaultMode === "function" ? opts.onGetProjectDefaultMode(slug) : null;
723
729
  var _srvMode = typeof opts.onGetServerDefaultMode === "function" ? opts.onGetServerDefaultMode() : null;
@@ -2248,6 +2254,7 @@ function createProjectContext(opts) {
2248
2254
  if (_scheduledTaskExecution) _scheduledTaskExecution.stopForSession(session, "The human stopped this scheduled run.");
2249
2255
  return stopped;
2250
2256
  },
2257
+ onPendingQueueRetire: function (session) { return _pendingMessageQueue.retireClaim(session); },
2251
2258
  onPendingQueuePause: function (session, actor) { return _pendingMessageQueue.pause(session, actor); },
2252
2259
  getLinuxUserForSession: getLinuxUserForSession,
2253
2260
  ensureProjectAccessForSession: ensureProjectAccessForSession,
@@ -2453,6 +2460,7 @@ function createProjectContext(opts) {
2453
2460
 
2454
2461
  // --- Vendor login flow (delegated to project-vendor-login.js) ---
2455
2462
  var _vendorLogin = attachVendorLogin({
2463
+ workerAuth: _workerAuth,
2456
2464
  onVendorAuthChanged: function () { _sessions.invalidateVendorUsage(); },
2457
2465
  requestAccess: _requestAccess,
2458
2466
  clients: clients,
@@ -2464,6 +2472,7 @@ function createProjectContext(opts) {
2464
2472
  send: send,
2465
2473
  sendTo: sendTo,
2466
2474
  usersModule: usersModule,
2475
+ resolveOsUserInfo: resolveOsUserInfo,
2467
2476
  getOsUserInfoForWs: getOsUserInfoForWs,
2468
2477
  getOsUserInfoForLinuxUser: getOsUserInfoForLinuxUser,
2469
2478
  getLinuxUserForSession: getLinuxUserForSession,
package/lib/public/app.js CHANGED
@@ -489,6 +489,27 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
489
489
  scheduledMessageCorrelation: null,
490
490
  activeSessionId: null,
491
491
  speechActive: null,
492
+ settingsSectionPage: null,
493
+ settingsSectionScope: null,
494
+ settingsSectionActive: null,
495
+ settingsPageScroll: {},
496
+ personalProvidersOpen: false,
497
+ personalProvidersLoading: false,
498
+ personalProvidersError: '',
499
+ personalProvidersNotice: '',
500
+ personalProviderSignInResults: {},
501
+ personalProviderCatalog: null,
502
+ personalProviderContext: null,
503
+ personalProviderRequest: null,
504
+ personalProviderRevision: null,
505
+ personalProviderEditing: null,
506
+ personalProviderRuntimeDetailsOpen: false,
507
+ personalProviderSigningIn: null,
508
+ personalProviderInstall: null,
509
+ personalProviderInstallBusy: false,
510
+ personalProviderInstallRequest: null,
511
+ personalProviderInstallAwaiting: null,
512
+ speechSettingsContextGeneration: 0,
492
513
  speechSettings: null,
493
514
  speechSettingsRequest: 0,
494
515
  speechSettingsSaving: false,
@@ -509,6 +530,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
509
530
  agentMessagePreviews: {},
510
531
  agentMessageBubble: null,
511
532
  workerMessagePreviewSubscription: null,
533
+ workerActivityIndicators: {},
512
534
  sessionVendorBound: false,
513
535
  cliSessionId: null,
514
536
  isHeadlessMode: false,
@@ -1764,3 +1764,8 @@ body.mate-workspace-active #composer-handoff-btn {
1764
1764
  body.mate-workspace-active #knowledge-workbench-btn { display: flex !important; }
1765
1765
  #mate-project-debates-btn { display: none !important; }
1766
1766
  body.mate-workspace-active #mate-project-debates-btn { display: flex !important; }
1767
+
1768
+ .icon-strip-wt-item .wt-branch-abbrev svg {
1769
+ width: 14px;
1770
+ height: 14px;
1771
+ }
@@ -1207,3 +1207,21 @@ button.top-bar-pill.pill-accent:hover { background: color-mix(in srgb, var(--acc
1207
1207
  .mate-delete-challenge input { height: 36px; box-sizing: border-box; padding: 8px 10px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg); color: var(--text); font: inherit; }
1208
1208
  .mate-delete-challenge input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
1209
1209
  .mate-delete-confirm .confirm-btn:disabled { opacity: .4; cursor: not-allowed; }
1210
+
1211
+ .confirm-text.worktree-removal-review {
1212
+ white-space: pre-line;
1213
+ overflow-wrap: anywhere;
1214
+ max-height: 65vh;
1215
+ overflow-y: auto;
1216
+ }
1217
+
1218
+ .worktree-branch-option {
1219
+ display: flex;
1220
+ align-items: center;
1221
+ gap: 7px;
1222
+ margin-top: 12px;
1223
+ white-space: normal;
1224
+ cursor: pointer;
1225
+ }
1226
+ .worktree-branch-option input { margin: 0; accent-color: var(--accent); flex-shrink: 0; }
1227
+ .worktree-branch-option input:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
@@ -46,6 +46,7 @@
46
46
  .settings-shell-nav .settings-nav-item.active { background: rgba(var(--overlay-rgb), .08); color: var(--text); }
47
47
  .settings-shell-nav .settings-nav-item:focus-visible { outline-offset: -2px; }
48
48
  .settings-shell-nav .settings-nav-dropdown { display: none; }
49
+ .settings-shell-content :is(.settings-hint, .settings-status) { overflow-wrap:anywhere; }
49
50
  .settings-shell-content { flex: 1 1 auto; min-width: 0; overflow-y: auto; padding: 28px 40px calc(var(--safe-bottom) + 56px); }
50
51
  .settings-shell-status { max-width: 760px; margin: 0 0 16px; padding: 8px 12px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg-alt); color: var(--text-secondary); font-size: 13px; }
51
52
  .settings-shell-status[data-state="error"] { border-color: color-mix(in srgb, var(--error) 40%, var(--border)); color: var(--error); }
@@ -101,3 +102,16 @@
101
102
  @media (prefers-reduced-motion: reduce) {
102
103
  #settings-shell * { transition: none !important; animation: none !important; }
103
104
  }
105
+
106
+ /* Section links share the settings navigation recipe across covering scopes. */
107
+ .settings-shell-content [data-settings-section] { scroll-margin-top:16px; }
108
+ .settings-shell-content .ab-narrow [data-settings-section] { scroll-margin-top:60px; }
109
+ #user-settings #api-connections { margin-top:32px; }
110
+ #user-settings #us-voice-settings h4 { font-size:13px; font-weight:600; margin:20px 0 8px; }
111
+ #user-settings [data-section="us-providers"] .us-voice-row-controls > .settings-btn-sm { min-width:76px; font-size:13px; }
112
+ #user-settings [data-coding-provider] > .settings-card { padding:0 0 12px 34px; }
113
+ @media(max-width:600px) {
114
+ #user-settings [data-section="us-providers"] .us-voice-row-controls { display:grid; grid-template-columns:24px minmax(0,1fr) auto; gap:8px; }
115
+ #user-settings [data-section="us-providers"] .us-integration-state { grid-column:2; grid-row:2; }
116
+ #user-settings [data-section="us-providers"] .us-voice-row-controls > button { grid-column:3; grid-row:1 / span 2; }
117
+ }
@@ -518,3 +518,9 @@
518
518
  #user-settings .email-setup-inline .email-setup-input { height:36px; font-size:13px; }
519
519
  #user-settings .email-setup-inline .email-setup-actions { flex-wrap:wrap; }
520
520
  #user-settings .email-setup-inline .email-setup-btn { min-height:32px; font-size:13px; }
521
+
522
+ :is(#user-settings, #project-settings, #server-settings) .settings-link { font-size:13px; color:var(--link); overflow-wrap:anywhere; }
523
+
524
+ :is(.us-section, .ps-section, .server-settings-section) .settings-section-heading { display:flex; align-items:center; justify-content:space-between; gap:12px; margin:26px 0 8px; min-height:32px; }
525
+ :is(#user-settings, #project-settings, #server-settings) .settings-section-heading h3 { margin:0; }
526
+ :is(#user-settings, #project-settings, #server-settings) .settings-runtime-details summary { cursor:pointer; width:fit-content; }
@@ -2,7 +2,7 @@
2
2
  .split-view-group-open, .split-view-group-more, .split-view-create-trigger,
3
3
  .split-view-arrange button,
4
4
  .split-view-worker-popover button, .split-pane-search,
5
- .split-pane-info, .split-pane-sidebar,
5
+ .split-pane-force-stop, .split-pane-info, .split-pane-sidebar,
6
6
  .split-view-canvas .split-view-arrange-trigger {
7
7
  appearance: none;
8
8
  border: 0;
@@ -19,7 +19,7 @@
19
19
  background: var(--sidebar-hover);
20
20
  color: var(--text);
21
21
  }
22
- .split-pane-info:hover, .split-pane-sidebar:hover {
22
+ .split-pane-force-stop:hover, .split-pane-info:hover, .split-pane-sidebar:hover {
23
23
  background: var(--sidebar-hover);
24
24
  color: var(--text);
25
25
  }
@@ -30,10 +30,11 @@
30
30
  outline: 2px solid var(--accent);
31
31
  outline-offset: -2px;
32
32
  }
33
- .split-pane-info:focus-visible, .split-pane-sidebar:focus-visible {
33
+ .split-pane-force-stop:focus-visible, .split-pane-info:focus-visible, .split-pane-sidebar:focus-visible {
34
34
  outline: 2px solid var(--accent);
35
35
  outline-offset: -2px;
36
36
  }
37
+ .split-pane-force-stop:disabled, .worker-conversation-stop:disabled { opacity: .45; cursor: default; }
37
38
  .split-view-arrange button:disabled { opacity: .45; cursor: default; }
38
39
  .split-view-create-trigger {
39
40
  display: flex;
@@ -46,7 +47,7 @@
46
47
  text-align: left;
47
48
  }
48
49
  .split-pane-search, .split-view-canvas .split-view-arrange-trigger,
49
- .split-pane-info, .split-pane-sidebar,
50
+ .split-pane-force-stop, .split-pane-info, .split-pane-sidebar,
50
51
  .split-view-worker-popover-header button {
51
52
  display: inline-flex;
52
53
  align-items: center;
@@ -56,7 +57,7 @@
56
57
  padding: 5px;
57
58
  flex: 0 0 24px;
58
59
  }
59
- .split-pane-search svg, .split-pane-info svg, .split-pane-sidebar svg,
60
+ .split-pane-force-stop svg, .split-pane-search svg, .split-pane-info svg, .split-pane-sidebar svg,
60
61
  .split-view-worker-popover-header button svg { width: 14px; height: 14px; }
61
62
  .split-pane-info { width: 20px; flex-basis: 20px; padding: 3px; }
62
63
  .split-view-canvas .split-pane-header button[hidden] { display: none; }
@@ -258,9 +259,12 @@ body.split-view-resizing .split-pane-frame { pointer-events: none; }
258
259
  .split-view-worker-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
259
260
  .split-view-worker-identity { flex-shrink: 0; white-space: nowrap; }
260
261
  .split-view-worker-generation { flex-shrink: 0; color: var(--text-dimmer); white-space: nowrap; }
261
- .split-view-worker-dot { width: 5px; height: 5px; flex: 0 0 5px; border-radius: 50%; background: var(--text-dimmer); }
262
- .split-view-worker-dot[data-state="working"] { background: var(--brand-green); }
263
- .split-view-worker-dot[data-state="waiting"] { background: var(--brand-gold); }
262
+ .split-view-worker-loop { display: inline-flex; align-items: center; justify-content: center; width: 5px; height: 5px; flex: 0 0 5px; color: var(--text-dimmer); transition: transform 350ms ease-in-out; }
263
+ .split-view-worker-loop svg { width: 9px; height: 9px; flex-shrink: 0; }
264
+ .split-view-worker-loop[data-state="working"] { color: var(--brand-green); }
265
+ @media (prefers-reduced-motion: reduce) { .split-view-worker-loop { transition: none; } }
266
+ .agent-message-bubble-activity { color: var(--text-muted); font-size: 10px; margin-bottom: 5px; line-height: 1.4; white-space: normal; }
267
+ .agent-message-bubble-text[hidden] { display: none; }
264
268
  @keyframes split-view-working { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }
265
269
  .split-view-group { margin: 2px 0 8px; }
266
270
  .split-view-group-heading { display: flex; align-items: center; gap: 2px; min-height: 34px; border-radius: 6px; }
@@ -410,7 +414,7 @@ body.wide-view .readonly-session-transcript .msg-user .bubble,
410
414
  .split-view-worker-page-status { display: block; margin-top: 3px; line-height: 1.4; }
411
415
  .split-view-worker-popover-footer button { margin-left: 8px; padding: 4px 6px; font-size: 10px; }
412
416
  .split-view-worker-popover[hidden], .split-view-menu[hidden], .split-view-arrange[hidden], .split-view-workers[hidden], .split-view-drop-preview[hidden], .split-view-drag-ghost[hidden] { display: none; }
413
- @media (prefers-reduced-motion: reduce) { .split-view-worker-dot[data-state="working"] { animation: none; } }
417
+
414
418
  @media (pointer: coarse) { .split-view-worker-chip { min-height: 30px; } .split-view-divider { flex-basis: 10px; } }
415
419
 
416
420
  /* Keep iframe nodes mounted while tree geometry changes around them. */
@@ -455,8 +459,6 @@ body.wide-view .readonly-session-transcript .msg-user .bubble,
455
459
  #split-view-arrange-context-list [aria-selected="true"] { background: var(--accent-bg); color: var(--accent); }
456
460
  .split-view-arrange [data-focus-layout] { width: 100%; min-height: 29px; margin-top: 5px; border-top: 1px solid var(--border-subtle); font-size: 11px; }
457
461
 
458
- /* Light reflects received WebSocket output, independent of processing state. */
459
- .split-view-worker-dot.io { background: var(--brand-green); box-shadow: 0 0 5px var(--brand-green); }
460
462
 
461
463
  /* Header whitespace is a handle; text and controls retain their own actions. */
462
464
  .split-pane-drag-space { flex: 1 1 12px; min-width: 12px; align-self: stretch; cursor: grab; touch-action: none; }
@@ -565,3 +567,6 @@ body.is-dragging .split-pane-header, body.is-dragging .split-pane-drag-space { c
565
567
  .agent-message-bubble-avatar { width: 16px; height: 16px; border-radius: 4px; object-fit: contain; flex: none; }
566
568
  .agent-message-bubble-vendor { overflow: hidden; text-overflow: ellipsis; }
567
569
  .agent-message-bubble-time { margin-left: auto; flex: none; font-variant-numeric: tabular-nums; }
570
+
571
+ .split-view-canvas .split-pane-header .split-pane-force-stop { order: 11; }
572
+ .split-pane-force-stop:hover { color: var(--error); }
@@ -2,13 +2,14 @@ import { dismissTooltip } from './tooltip.js';
2
2
  import { renderMarkdown } from './markdown.js';
3
3
  import { store } from './store.js';
4
4
 
5
- var timer = null;
5
+ var timer = null, recencyTimer = null;
6
6
  function context() { return JSON.stringify([store.get('myUserId') || null, store.get('currentSlug'), store.get('projectSplitViewSocketGeneration') || 0]); }
7
7
  export function messagePreviewKey(kind, identities) { return JSON.stringify([context(), kind].concat(identities)); }
8
8
  function element() { return document.getElementById('agent-message-bubble'); }
9
9
  function anchor(key) { return Array.from(document.querySelectorAll('[data-message-preview]')).find(function (node) { return node.dataset.messagePreview === key && node.isConnected && node.getClientRects().length && !node.closest('[hidden], .split-pane-cached'); }); }
10
10
  function remove() {
11
11
  if (timer) clearTimeout(timer); timer = null;
12
+ if (recencyTimer) clearTimeout(recencyTimer); recencyTimer = null;
12
13
  var node = element(); if (node) node.remove();
13
14
  document.querySelectorAll('[aria-describedby="agent-message-bubble"]').forEach(function (chip) { chip.removeAttribute('aria-describedby'); });
14
15
  if (store.get('agentMessageBubble')) store.set({ agentMessageBubble: null });
@@ -37,9 +38,18 @@ function renderMetadata(node, latest) {
37
38
  var date = new Date(latest.timestamp); time.dateTime = date.toISOString();
38
39
  time.textContent = date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
39
40
  time.setAttribute('aria-label', 'Message time: ' + date.toLocaleString());
40
- } else time.textContent = 'Time unavailable';
41
+ } else time.textContent = !latest.text && latest.activity ? 'No recent message' : 'Time unavailable';
41
42
  meta.appendChild(time);
42
43
  }
44
+ function renderActivity(node, activity, workerActivity, clock) {
45
+ var line = node.querySelector('.agent-message-bubble-activity');
46
+ if (!activity && !workerActivity) { if (line) line.remove(); return; }
47
+ if (!line) { line = document.createElement('div'); line.className = 'agent-message-bubble-activity'; node.insertBefore(line, node.querySelector('.agent-message-bubble-text')); }
48
+ if (!activity) { line.textContent = 'Activity time unavailable'; return; }
49
+ var elapsed = clock ? Math.floor((clock.age + performance.now() - clock.received) / 1000) : -1;
50
+ var age = elapsed < 0 ? 'Activity time unavailable' : 'last activity ' + (elapsed < 60 ? elapsed + 's' : elapsed < 3600 ? Math.floor(elapsed / 60) + 'm' : Math.floor(elapsed / 3600) + 'h') + ' ago';
51
+ line.textContent = activity.summary + ' · ' + age;
52
+ }
43
53
  function show(key, held, fresh) {
44
54
  var latest = (store.get('agentMessagePreviews') || {})[key], chip = anchor(key);
45
55
  if (!latest || !chip || store.get('splitViewWorkerConversation') || store.get('backgroundTaskViewer') || store.get('paneSuspended') || document.hidden || !store.get('connected')) return;
@@ -52,7 +62,13 @@ function show(key, held, fresh) {
52
62
  node.setAttribute('role', 'tooltip'); var text = document.createElement('div'); text.className = 'agent-message-bubble-text'; node.appendChild(text); document.body.appendChild(node);
53
63
  }
54
64
  renderMetadata(node, latest);
55
- node.querySelector('.agent-message-bubble-text').innerHTML = DOMPurify.sanitize(renderMarkdown(latest.text), { ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'code', 'pre', 'ul', 'ol', 'li', 'blockquote', 'del', 's'], ALLOWED_ATTR: [], ALLOW_DATA_ATTR: false }); node.dataset.held = String(!!held); node.dataset.live = String(expires > Date.now());
65
+ renderActivity(node, latest.activity, latest.workerActivity, latest.activityClock);
66
+ if (recencyTimer) clearTimeout(recencyTimer); recencyTimer = null;
67
+ if (latest.activity) recencyTimer = setTimeout(function () {
68
+ recencyTimer = null; var current = element(), state = store.get('agentMessageBubble');
69
+ if (current && state && state.key === key) show(key, state.held, false);
70
+ }, 1000);
71
+ node.querySelector('.agent-message-bubble-text').innerHTML = DOMPurify.sanitize(renderMarkdown(latest.text || ''), { ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'code', 'pre', 'ul', 'ol', 'li', 'blockquote', 'del', 's'], ALLOWED_ATTR: [], ALLOW_DATA_ATTR: false }); node.querySelector('.agent-message-bubble-text').hidden = !latest.text; node.dataset.held = String(!!held); node.dataset.live = String(expires > Date.now());
56
72
  dismissTooltip();
57
73
  chip.setAttribute('aria-describedby', node.id);
58
74
  store.set({ agentMessageBubble: { key: key, expiresAt: expires, held: !!held } }); position();
@@ -65,13 +81,26 @@ export function rememberAgentMessage(key, preview, live) {
65
81
  if (!preview || typeof preview.id !== 'string' || typeof preview.text !== 'string' || !preview.text.trim()) return;
66
82
  var all = Object.assign({}, store.get('agentMessagePreviews') || {}), previous = all[key];
67
83
  var fresh = !previous || previous.id !== preview.id;
68
- all[key] = { id: preview.id, text: preview.text.slice(0, 1200), vendor: preview.vendor || previous && previous.vendor || null, timestamp: typeof preview.timestamp === 'number' && Number.isFinite(preview.timestamp) && preview.timestamp > 0 ? preview.timestamp : previous && previous.id === preview.id ? previous.timestamp : null };
84
+ all[key] = { activityClock: previous && previous.activityClock || null, workerActivity: previous && previous.workerActivity, activity: previous && previous.activity || null, id: preview.id, text: preview.text.slice(0, 1200), vendor: preview.vendor || previous && previous.vendor || null, timestamp: typeof preview.timestamp === 'number' && Number.isFinite(preview.timestamp) && preview.timestamp > 0 ? preview.timestamp : previous && previous.id === preview.id ? previous.timestamp : null };
69
85
  var keys = Object.keys(all); while (keys.length > 128) delete all[keys.shift()];
70
86
  store.set({ agentMessagePreviews: all });
71
87
  var state = store.get('agentMessageBubble'), chip = anchor(key);
72
88
  var held = !!(chip && (chip.matches(':hover') || chip.contains(document.activeElement)));
73
89
  if (live && fresh || held || state && state.key === key) show(key, held || !!(state && state.key === key && state.held), !!live && fresh);
74
90
  }
91
+ export function forgetAgentMessage(key) {
92
+ var all = Object.assign({}, store.get('agentMessagePreviews') || {}); delete all[key];
93
+ store.set({ agentMessagePreviews: all });
94
+ var state = store.get('agentMessageBubble'); if (state && state.key === key) remove();
95
+ }
96
+ export function rememberAgentActivity(key, activity, vendor, observedAt) {
97
+ var all = Object.assign({}, store.get('agentMessagePreviews') || {}), previous = all[key];
98
+ if (!activity && !previous) return;
99
+ all[key] = Object.assign({ text: '', timestamp: null, vendor: vendor || null }, previous || {}, { activity: activity, workerActivity: true, activityClock: activity && Number.isFinite(observedAt) && observedAt >= activity.timestamp ? { age: observedAt - activity.timestamp, received: performance.now() } : null });
100
+ store.set({ agentMessagePreviews: all });
101
+ var state = store.get('agentMessageBubble'), chip = anchor(key), held = !!(chip && (chip.matches(':hover') || chip.contains(document.activeElement)));
102
+ if (held || state && state.key === key) show(key, held || !!(state && state.held), false);
103
+ }
75
104
  export function bindAgentMessageBubble(chip, key) {
76
105
  chip.dataset.messagePreview = key;
77
106
  if (chip.dataset.previewBound) return;
@@ -1,3 +1,4 @@
1
+ import { handlePersonalProviderMessage } from './user-settings-providers.js';
1
2
  import { handleSplitSessionCreated, finishPickerCreation } from './split-view-picker-actions.js';
2
3
  import { handleWorkerMessagePreview } from './worker-message-previews.js';
3
4
  import { handleBackgroundTaskPreview } from './background-task-previews.js';
@@ -125,7 +126,7 @@ import { showDebateSticky, showDebateConcludeConfirm, showDebateUserFloor, exitD
125
126
  import { handleSkillInstallWs } from './app-skills-install.js';
126
127
  import { handleNotificationsState, handleNotificationCreated, handleNotificationDismissed, handleNotificationDismissedAll, showUpdateBanner } from './app-notifications.js';
127
128
  import { handleUpdateSnoozed } from './update-snooze.js';
128
- import { autoStartLoginIfNeeded, handleVendorLoginReady, handleVendorLoginState, handleVendorLoginError, handleAuthRefreshed } from './vendor-login.js';
129
+ import { autoStartLoginIfNeeded, handleWorkerAuthRequired, handleVendorLoginReady, handleVendorLoginState, handleVendorLoginError, handleAuthRefreshed } from './vendor-login.js';
129
130
  import { handleLoginBrowserMessage } from './vendor-login-browser.js';
130
131
  import { handleDebatePreparing, handleDebateBriefReady, renderDebateBriefReady, handleDebateStarted, renderDebateStarted, handleDebateTurn, handleDebateActivity, handleDebateStream, handleDebateTurnDone, handleDebateCommentQueued, handleDebateCommentInjected, renderDebateCommentInjected, handleDebateResumed, handleDebateEnded, renderDebateEnded, handleDebateError, isDebateActive, renderMcpDebateProposal, renderDebateUserResume } from './debate.js';
131
132
  import { handleMentionStart, handleMentionActivity, handleMentionStream, handleMentionDone, handleMentionError, renderMentionUser, renderMentionResponse, renderUserMention } from './mention.js';
@@ -143,6 +144,7 @@ export function processMessage(msg) {
143
144
  if (handleOwnerNavigation(msg)) return;
144
145
  if (["update_available", "up_to_date", "update_started", "update_check_failed"].indexOf(msg.type) !== -1) handleServerUpdate(msg);
145
146
  if (/^(set_pin|set_keep_awake|set_inherit_groups|set_image_retention|set_update_channel|restart_server|shutdown_server)_result$/.test(msg.type)) handleServerSettingResult(msg);
147
+ if (handlePersonalProviderMessage(msg)) return;
146
148
  if (handleProjectDebateMessage(msg)) return;
147
149
  if (handleMcpConnectionMessage(msg)) return;
148
150
  if (handleSharedBrowserMessage(msg)) return;
@@ -1618,6 +1620,10 @@ export function processMessage(msg) {
1618
1620
  window.location.reload();
1619
1621
  break;
1620
1622
 
1623
+ case "worker_auth_required":
1624
+ handleWorkerAuthRequired(msg);
1625
+ break;
1626
+
1621
1627
  case "auth_required":
1622
1628
  removeMatePreThinking();
1623
1629
  setActivity(null);
@@ -1,3 +1,4 @@
1
+ import { reviewWorktreeRemoval } from './worktree-controls.js';
1
2
  import { prepareContextualWorkspaceDeparture } from './contextual-clay-workspace.js';
2
3
  import { checkComposerDeparture, saveSessionRichDraft } from './session-rich-drafts.js';
3
4
  // app-projects.js - Project list, switching, add/remove project modals
@@ -359,14 +360,12 @@ export function handleRemoveProjectCheckResult(msg) {
359
360
  var slug = msg.slug || pendingRemoveSlug;
360
361
  var name = msg.name || pendingRemoveName || slug;
361
362
  if (!slug) return;
363
+ if (slug.indexOf("--") !== -1) { pendingRemoveSlug = null; pendingRemoveName = null; reviewWorktreeRemoval(slug, "exclude"); return; }
362
364
 
363
365
  if (msg.count > 0) {
364
366
  showRemoveProjectTaskDialog(slug, name, msg.count);
365
367
  } else {
366
- var isWt = slug.indexOf("--") !== -1;
367
- var confirmMsg = isWt
368
- ? 'Delete worktree "' + name + '"? The branch and working directory will be removed from disk.'
369
- : 'Remove "' + name + '"? You can re-add it later.';
368
+ var confirmMsg = 'Remove "' + name + '"? You can re-add it later.';
370
369
  showConfirm(confirmMsg, function () {
371
370
  var iconEl = document.querySelector('.icon-strip-item[data-slug="' + slug + '"]');
372
371
  if (iconEl) {
@@ -1,5 +1,6 @@
1
1
  var domains = { session: 'conversation', project: 'project', mate: 'Mate profile', knowledge: 'Mate Knowledge', records: 'project record', schedule: 'schedule', conversation: 'Mate conversation', workspace: 'workspace', screen: 'screen', assignment: 'Driver work', studio: 'Studio workbench' };
2
2
  var actions = {
3
+ force_stop: 'force terminate supported session and Worker runtimes and pause queued messages',
3
4
  rename: 'rename this conversation so it is easier to find', stop: 'request Stop so this work can settle',
4
5
  model: 'change the model for this conversation', effort: 'adjust reasoning effort for this conversation',
5
6
  permission: 'change the reviewed permission mode', delete: 'remove the reviewed item',
@@ -294,7 +294,7 @@ export function clayToolPresentation(name, input, outcome) {
294
294
  else { phase = 'returned'; label = 'Project change request returned'; }
295
295
  } else if (def.rule === 'product-control') {
296
296
  if (status === 'queued') { phase = 'pending'; label = input.action === 'stop' ? 'Stop requested · runtime pending' : input.action === 'queue_add' ? 'Message queued for project Driver' : 'Conversation change queued'; }
297
- else if (status === 'completed') label = input.action === 'queue_add' ? 'Message delivered to project Driver' : input.action === 'queue_edit' ? 'Queued message updated' : input.action === 'queue_cancel' ? 'Queued message cancelled' : input.action === 'queue_reorder' ? 'Message queue reordered' : input.action === 'vendor' ? 'Driver provider selected' : 'Conversation change completed';
297
+ else if (status === 'completed') label = input.action === 'force_stop' ? 'Session and Worker runtimes exited · external effects unconfirmed' : input.action === 'queue_add' ? 'Message delivered to project Driver' : input.action === 'queue_edit' ? 'Queued message updated' : input.action === 'queue_cancel' ? 'Queued message cancelled' : input.action === 'queue_reorder' ? 'Message queue reordered' : input.action === 'vendor' ? 'Driver provider selected' : 'Conversation change completed';
298
298
  else { phase = 'returned'; label = 'Conversation change request returned'; }
299
299
  } else if (def.rule === 'observation') {
300
300
  if (status !== 'pending') { label = 'Preference submission returned'; phase = 'returned'; }
@@ -2,10 +2,10 @@ import { store } from './store.js';
2
2
  import { getWs } from './ws-ref.js';
3
3
  import { isDriverOperatedView } from './worker-pane-lock.js';
4
4
 
5
- export function stopGeneration() {
5
+ export function stopGeneration(force) {
6
6
  var ws = getWs();
7
- if (!store.get('processing') || !store.get('connected') || !ws || ws.readyState !== 1) return false;
8
- var message = { type: 'stop' };
7
+ if ((!store.get('processing') && !isDriverOperatedView() && force !== true) || !store.get('connected') || !ws || ws.readyState !== 1) return false;
8
+ var message = { type: force === true ? 'force_stop' : 'stop' };
9
9
  if (store.get('paneMode')) {
10
10
  var binding = store.get('paneCommittedBinding');
11
11
  if (!store.get('paneReady') || !binding || binding.sessionId !== store.get('activeSessionId') || binding.sessionOriginId !== store.get('sessionOriginId')) return false;
@@ -39,7 +39,7 @@ export function initGenerationStop() {
39
39
  }
40
40
  }
41
41
  function sync() {
42
- button.classList.toggle('hidden', !store.get('processing'));
42
+ button.classList.toggle('hidden', !store.get('processing') && !isDriverOperatedView());
43
43
  button.disabled = !store.get('connected');
44
44
  button.title = store.get('connected') ? 'Stop generating' : 'Reconnect to stop generating';
45
45
  place();
@@ -168,13 +168,13 @@ export function paneOutgoing(data) {
168
168
  if (store.get('deferredSessionSwitch')) {
169
169
  var source = store.get('deferredSessionSwitch');
170
170
  if (source.draining) return null;
171
- if (message.type === 'stop') return JSON.stringify(Object.assign({}, message, { sessionId: source.sessionId, sessionOriginId: source.origin }));
171
+ if (message.type === 'stop' || message.type === 'force_stop') return JSON.stringify(Object.assign({}, message, { sessionId: source.sessionId, sessionOriginId: source.origin }));
172
172
  return null;
173
173
  }
174
174
  if (!enabled()) return data;
175
175
  var binding = store.get('paneCommittedBinding');
176
176
  if (store.get('panePendingBinding') || store.get('paneSuspended') || binding && !store.get('paneReady')) return null;
177
- if (!binding) return ['stop', 'user_message', 'permission_response', 'ask_user_response', 'elicitation_response', 'user_dialog_response', 'input_sync'].indexOf(message.type) >= 0 ? null : data;
177
+ if (!binding) return ['stop', 'force_stop', 'user_message', 'permission_response', 'ask_user_response', 'elicitation_response', 'user_dialog_response', 'input_sync'].indexOf(message.type) >= 0 ? null : data;
178
178
  if (message.paneBinding && !samePaneBinding(binding, message.paneBinding)) return null;
179
179
  var gesture = (store.get('paneActionEvents') || []).find(function (entry) { return entry.event.isTrusted && entry.event.eventPhase && samePaneBinding(entry.binding, binding); });
180
180
  var action = store.get('paneCallbackBinding') || gesture && gesture.binding;
@@ -1,3 +1,4 @@
1
+ import { stopGeneration } from './generation-stop.js';
1
2
  import { forwardStudioPresentation } from './clay-studio-render.js';
2
3
  // Pane-side postMessage bridge for the split-view shell. A chrome-less pane
3
4
  // iframe reports its context usage up to the parent (which renders a chip in
@@ -73,11 +74,12 @@ export function initPaneBridge() {
73
74
  window.addEventListener("message", function (event) {
74
75
  if (event.origin !== window.location.origin) return;
75
76
  var msg = event.data;
76
- if (!msg || (msg.type !== "clay-pane-toggle-context" && msg.type !== "clay-pane-toggle-search" && msg.type !== "clay-pane-search-command" && msg.type !== "clay-pane-layout-resize")) return;
77
+ if (!msg || (msg.type !== "clay-pane-force-stop" && msg.type !== "clay-pane-toggle-context" && msg.type !== "clay-pane-toggle-search" && msg.type !== "clay-pane-search-command" && msg.type !== "clay-pane-layout-resize")) return;
77
78
  var committed = store.get('paneCommittedBinding');
78
79
  if (event.source !== window.parent || !committed || !paneMessageBinding() ||
79
80
  !samePaneBinding(committed, msg.paneBinding) || !samePaneBinding(paneMessageBinding(), msg.paneBinding)) return;
80
- if (msg.type === "clay-pane-layout-resize") {
81
+ if (msg.type === "clay-pane-force-stop") stopGeneration(true);
82
+ else if (msg.type === "clay-pane-layout-resize") {
81
83
  if (msg.phase === 'begin') beginPaneLayoutScrollLock(msg.token, msg.scrollTop);
82
84
  else if (msg.phase === 'end') endPaneLayoutScrollLock(msg.token);
83
85
  } else if (msg.type === "clay-pane-toggle-context") togglePaneContextPanel();
@@ -0,0 +1,11 @@
1
+ import { store } from './store.js';
2
+ import { getWs } from './ws-ref.js';
3
+ export function personalProviderContext() {
4
+ return JSON.stringify([store.get('myUserId'), store.get('currentSlug'), store.get('projectSplitViewSocketGeneration')]);
5
+ }
6
+ export function personalLoginCurrent(message) {
7
+ if (!message.personalRequestId) return true;
8
+ return store.get('settingsOpen') && store.get('personalProvidersOpen') && store.get('connected') !== false &&
9
+ message.personalRequestId === store.get('personalProviderRevision') && store.get('personalProviderContext') === personalProviderContext() &&
10
+ getWs() && getWs().readyState === 1;
11
+ }
@@ -246,7 +246,7 @@ function updateIconPreview(icon) {
246
246
  if (preview) {
247
247
  preview.textContent = icon || "";
248
248
  if (icon) parseEmojis(preview);
249
- else { preview.innerHTML = '<i data-lucide="folder"></i>'; refreshIcons(); }
249
+ else { preview.innerHTML = '<i data-lucide="' + (settingsProject() && settingsProject().isWorktree ? 'git-branch' : 'folder') + '"></i>'; refreshIcons(); }
250
250
  }
251
251
  if (removeBtn) {
252
252
  removeBtn.classList.toggle("hidden", !icon);
@@ -0,0 +1,58 @@
1
+ // Personal provider anchors reuse the Agent behavior page's continuous-page
2
+ // navigation and shell placement. Other pages retain their own lifecycle.
3
+ import { store } from './store.js';
4
+ import { attachSectionNav } from './agent-behavior-nav.js';
5
+ import { setPageSubnav } from './settings-shell-view.js';
6
+
7
+ var mounted = null;
8
+
9
+ export function deactivateSettingsSections() {
10
+ if (!mounted) return;
11
+ var positions = Object.assign({}, store.get('settingsPageScroll') || {});
12
+ positions['user-settings:us-providers'] = mounted.scroller.scrollTop;
13
+ store.set({ settingsPageScroll: positions });
14
+ mounted.controller.destroy();
15
+ setPageSubnav(null, null);
16
+ mounted.nav.remove();
17
+ mounted.layout.classList.remove('ab-layout', 'ab-narrow');
18
+ mounted = null;
19
+ }
20
+
21
+ export function activateSettingsSections(page, anchor) {
22
+ var area = document.getElementById('settings-shell-content');
23
+ var pane = document.querySelector('.settings-pane:not(.hidden)');
24
+ if (!area || !pane) return;
25
+ if (page !== 'us-providers' || pane.id !== 'user-settings') {
26
+ // Agent behavior owns its catalog-derived anchors. Legacy shortcuts on
27
+ // other pages only reveal and focus their destination.
28
+ var heading = anchor && document.getElementById(anchor);
29
+ if (heading && pane.contains(heading)) {
30
+ if (heading.tagName === 'DETAILS') heading.open = true;
31
+ heading.tabIndex = -1; heading.scrollIntoView({ block: 'start' }); heading.focus({ preventScroll: true });
32
+ }
33
+ return;
34
+ }
35
+ if (!mounted) {
36
+ var layout = pane.querySelector('.us-section[data-section="us-providers"]');
37
+ var nav = document.createElement('nav'); nav.className = 'ab-nav'; nav.setAttribute('aria-label', 'AI providers sections');
38
+ var list = document.createElement('ul'); list.className = 'ab-nav-list'; nav.appendChild(list);
39
+ var sections = Array.from(layout.querySelectorAll('[data-settings-section]')).map(function (heading) {
40
+ var item = document.createElement('li'); var link = document.createElement('a');
41
+ link.className = 'ab-nav-link'; link.href = '#' + heading.id; link.dataset.section = heading.id;
42
+ link.dataset.settingsAnchor = heading.id; link.textContent = heading.textContent; item.appendChild(link); list.appendChild(item);
43
+ heading.tabIndex = -1;
44
+ return { id: heading.id, el: heading, heading: heading };
45
+ });
46
+ layout.classList.add('ab-layout');
47
+ var last = sections[sections.length - 1];
48
+ if (last && last.el.nextElementSibling) last.el.nextElementSibling.style.minHeight = area.clientHeight + 'px';
49
+ var controller = attachSectionNav({ layout: layout, nav: nav, scroller: area, sections: sections, onPlace: function (narrow) {
50
+ if (narrow) { setPageSubnav(null, null); layout.insertBefore(nav, layout.firstChild); }
51
+ else setPageSubnav('us-providers', nav);
52
+ } });
53
+ mounted = { layout: layout, nav: nav, scroller: area, controller: controller };
54
+ area.scrollTop = (store.get('settingsPageScroll') || {})['user-settings:us-providers'] || 0;
55
+ controller.refresh();
56
+ }
57
+ if (anchor) mounted.controller.go(anchor);
58
+ }
@@ -10,7 +10,7 @@ export var SETTINGS_SCOPES = [
10
10
 
11
11
  export var PERSONAL_PAGES = [
12
12
  { id: 'us-account', label: 'Account' }, { id: 'us-appearance', label: 'Appearance' },
13
- { id: 'us-chat', label: 'Conversation' }, { id: 'us-notifications', label: 'Notifications' },
13
+ { id: 'us-providers', label: 'AI providers' }, { id: 'us-notifications', label: 'Notifications' },
14
14
  { id: 'us-ai', label: 'AI preferences' }, { id: 'us-agent', label: 'Agent behavior' }, { id: 'us-integrations', label: 'Integrations' }
15
15
  ];
16
16
 
@@ -31,7 +31,7 @@ export var SERVER_PAGES = [
31
31
  ];
32
32
 
33
33
  var PERSONAL_ALIASES = {
34
- 'us-mates': 'us-chat', 'us-experimental': 'us-chat', 'us-connections': 'us-integrations',
34
+ 'us-chat': 'us-providers', 'us-mates': 'us-ai', 'us-experimental': 'us-ai', 'us-voice': 'us-providers', 'us-connections': 'us-integrations',
35
35
  'us-email': 'us-integrations', 'us-logout': 'us-account'
36
36
  };
37
37
  var SERVER_ALIASES = { 'admin-invites': 'admin-users' };
@@ -66,6 +66,7 @@ export function scopeAvailable(scope, access) {
66
66
  }
67
67
 
68
68
  export function resolvePage(scope, page, access) {
69
+ page = (page || '').split('#')[0];
69
70
  var alias = scope === 'personal' ? PERSONAL_ALIASES[page] : scope === 'server' ? SERVER_ALIASES[page] : null;
70
71
  var id = alias || page;
71
72
  var pages = pagesForScope(scope, access);
@@ -82,6 +83,7 @@ export function resolveRoute(route, access) {
82
83
  scope: scope,
83
84
  page: resolvePage(scope, requested, access),
84
85
  slug: scope === 'project' ? (route.slug || null) : null,
86
+ anchor: route.anchor || (route.page || '').split('#')[1] || (scope === 'personal' ? ({'us-chat':'api-connections','us-voice':'api-connections','us-experimental':'experimental-features','us-mates':'execution-behavior'})[route.page] : null) || null,
85
87
  requestedScope: route.scope || 'personal',
86
88
  redirected: scope !== (route.scope || 'personal')
87
89
  };