clay-server 5.2.1-beta.2 → 5.3.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/lib/clay-control-presentation-binding.js +5 -3
  2. package/lib/clay-product-control-tools.js +3 -3
  3. package/lib/clay-product-control.js +4 -2
  4. package/lib/clay-screen-controls.js +18 -3
  5. package/lib/clay-split-view-awareness.js +35 -0
  6. package/lib/clay-split-view-controls.js +122 -0
  7. package/lib/clay-split-view-input.js +20 -0
  8. package/lib/clay-split-view-render-proof.js +22 -0
  9. package/lib/pane-asset-manifest.js +63 -0
  10. package/lib/project-connection.js +19 -10
  11. package/lib/project-session-actions.js +1 -0
  12. package/lib/project-session-delete.js +2 -2
  13. package/lib/project-session-folders.js +1 -0
  14. package/lib/project-sessions.js +6 -1
  15. package/lib/project-split-view-document.js +33 -0
  16. package/lib/project-split-view-names.js +67 -0
  17. package/lib/project-split-view-organization.js +23 -0
  18. package/lib/project-split-views.js +479 -0
  19. package/lib/project-worker-conversation.js +158 -0
  20. package/lib/project-worker-creation-proposal.js +3 -4
  21. package/lib/project-worker-proposal.js +1 -1
  22. package/lib/project-worker-removal.js +101 -0
  23. package/lib/project.js +28 -1
  24. package/lib/public/app.js +32 -13
  25. package/lib/public/css/base.css +9 -0
  26. package/lib/public/css/filebrowser.css +0 -1
  27. package/lib/public/css/pane-header-controls.css +100 -0
  28. package/lib/public/css/pane.css +24 -5
  29. package/lib/public/css/popup-chat.css +123 -0
  30. package/lib/public/css/right-workbench.css +50 -18
  31. package/lib/public/css/split-view-layout.css +460 -0
  32. package/lib/public/css/split-view-worker-activity.css +97 -0
  33. package/lib/public/css/sticky-notes.css +13 -0
  34. package/lib/public/index.html +3 -3
  35. package/lib/public/modules/app-connection.js +4 -1
  36. package/lib/public/modules/app-messages.js +25 -8
  37. package/lib/public/modules/chat-render-runtime.js +45 -0
  38. package/lib/public/modules/clay-control-copy.js +3 -0
  39. package/lib/public/modules/clay-navigation-render.js +1 -1
  40. package/lib/public/modules/clay-owner-navigation.js +15 -5
  41. package/lib/public/modules/clay-split-view-screen.js +74 -0
  42. package/lib/public/modules/clay-tool-activity.js +5 -0
  43. package/lib/public/modules/command-palette.js +30 -28
  44. package/lib/public/modules/contextual-clay-session.js +26 -2
  45. package/lib/public/modules/contextual-clay.js +5 -2
  46. package/lib/public/modules/contextual-mate-sessions.js +2 -0
  47. package/lib/public/modules/generation-stop.js +7 -1
  48. package/lib/public/modules/knowledge-workbench-entry.js +43 -0
  49. package/lib/public/modules/markdown.js +11 -0
  50. package/lib/public/modules/mate-knowledge-workbench.js +2 -7
  51. package/lib/public/modules/mate-sidebar.js +1 -1
  52. package/lib/public/modules/palette-clay-conversation.js +87 -0
  53. package/lib/public/modules/palette-render-helpers.js +25 -0
  54. package/lib/public/modules/pane-activation.js +46 -7
  55. package/lib/public/modules/pane-binding.js +12 -2
  56. package/lib/public/modules/pane-bridge.js +12 -3
  57. package/lib/public/modules/pane-deferred-bootstrap.js +32 -0
  58. package/lib/public/modules/pane-feature-assets.js +35 -0
  59. package/lib/public/modules/pane-search-receipt.js +12 -0
  60. package/lib/public/modules/permission-control.js +6 -6
  61. package/lib/public/modules/popup-chat-rendering.js +244 -0
  62. package/lib/public/modules/project-activation.js +2 -2
  63. package/lib/public/modules/qrcode.js +12 -1
  64. package/lib/public/modules/right-workbench.js +68 -0
  65. package/lib/public/modules/search-hints.js +79 -0
  66. package/lib/public/modules/session-context-menu.js +35 -2
  67. package/lib/public/modules/session-folder-context.js +24 -13
  68. package/lib/public/modules/session-folder-delete.js +18 -1
  69. package/lib/public/modules/session-folder-layout.js +7 -6
  70. package/lib/public/modules/session-search-pane-state.js +30 -0
  71. package/lib/public/modules/session-search.js +53 -13
  72. package/lib/public/modules/sidebar-mobile.js +3 -3
  73. package/lib/public/modules/sidebar-projects.js +1 -0
  74. package/lib/public/modules/sidebar-session-folders-ui.js +3 -3
  75. package/lib/public/modules/sidebar-sessions.js +57 -24
  76. package/lib/public/modules/sidebar.js +20 -3
  77. package/lib/public/modules/split-group-helpers.js +1 -1
  78. package/lib/public/modules/split-group-restore.js +8 -11
  79. package/lib/public/modules/split-pair-ui.js +18 -0
  80. package/lib/public/modules/split-pane-header-state.js +45 -0
  81. package/lib/public/modules/split-pane-reconciler.js +23 -4
  82. package/lib/public/modules/split-pane-renderer.js +330 -23
  83. package/lib/public/modules/split-pane-retention.js +157 -0
  84. package/lib/public/modules/split-session-boundary.js +21 -0
  85. package/lib/public/modules/split-view-create-action.js +15 -0
  86. package/lib/public/modules/split-view-create-menu.js +83 -0
  87. package/lib/public/modules/split-view-create-request.js +23 -0
  88. package/lib/public/modules/split-view-layout-client.js +198 -0
  89. package/lib/public/modules/split-view-layout-icons.js +37 -0
  90. package/lib/public/modules/split-view-layout-scope.js +38 -0
  91. package/lib/public/modules/split-view-organization.js +73 -0
  92. package/lib/public/modules/split-view-presentation-close.js +35 -0
  93. package/lib/public/modules/split-view-presentation-drag.js +254 -0
  94. package/lib/public/modules/split-view-presentation-drop.js +57 -0
  95. package/lib/public/modules/split-view-presentation-lookup.js +44 -0
  96. package/lib/public/modules/split-view-presentation-renderer.js +494 -0
  97. package/lib/public/modules/split-view-presentations.js +495 -0
  98. package/lib/public/modules/split-view-shell-controls.js +28 -0
  99. package/lib/public/modules/split-view-sidebar-menu.js +42 -0
  100. package/lib/public/modules/split-view-sidebar-renderer.js +30 -0
  101. package/lib/public/modules/split-view-singleton.js +21 -0
  102. package/lib/public/modules/split-view-worker-activity.js +94 -0
  103. package/lib/public/modules/split-view-worker-controls.js +30 -0
  104. package/lib/public/modules/split-view-worker-removal.js +137 -0
  105. package/lib/public/modules/split-view-worker-scroll.js +57 -0
  106. package/lib/public/modules/split-view-worker-transcript.js +46 -0
  107. package/lib/public/modules/split-view-workers.js +352 -0
  108. package/lib/public/modules/split-view.js +61 -22
  109. package/lib/public/modules/sticky-notes.js +9 -2
  110. package/lib/public/modules/tools.js +25 -5
  111. package/lib/public/modules/workbench-layout-preference.js +76 -0
  112. package/lib/public/pane-assets/app-OJEQXBBS.js +1 -0
  113. package/lib/public/pane-assets/chunk-4SMIVAB4.js +1 -0
  114. package/lib/public/pane-assets/chunk-623V5CEO.js +1 -0
  115. package/lib/public/pane-assets/chunk-6SM2WQJX.js +1 -0
  116. package/lib/public/pane-assets/chunk-7H36SUZA.js +2 -0
  117. package/lib/public/pane-assets/chunk-DGOFGEUK.js +1 -0
  118. package/lib/public/pane-assets/chunk-GML2R5JE.js +167 -0
  119. package/lib/public/pane-assets/chunk-I57URYSL.js +1 -0
  120. package/lib/public/pane-assets/chunk-I5P3MWQG.js +1 -0
  121. package/lib/public/pane-assets/chunk-UVGOPFEX.js +2 -0
  122. package/lib/public/pane-assets/manifest.json +551 -0
  123. package/lib/public/pane-assets/style-GN7R6XKV.css +1 -0
  124. package/lib/public/style.css +5 -1
  125. package/lib/server-owner-ui.js +6 -2
  126. package/lib/server-owner-view-observation.js +34 -0
  127. package/lib/server-palette.js +1 -0
  128. package/lib/server-pane-bootstrap.js +24 -0
  129. package/lib/server-settings.js +2 -0
  130. package/lib/server-static-assets.js +52 -0
  131. package/lib/server-workbench-layout-settings.js +56 -0
  132. package/lib/server.js +6 -45
  133. package/lib/session-activity-state.js +28 -0
  134. package/lib/session-list-delivery.js +17 -0
  135. package/lib/session-pair-turn-control.js +1 -6
  136. package/lib/session-search.js +3 -0
  137. package/lib/session-split-group-anchors.js +2 -2
  138. package/lib/session-split-group-roles.js +5 -8
  139. package/lib/session-split-group-v2-store.js +0 -3
  140. package/lib/sessions.js +16 -6
  141. package/lib/split-view-layout.js +132 -0
  142. package/lib/users-workbench-layout-preferences.js +33 -0
  143. package/lib/users.js +3 -0
  144. package/lib/workspace-query-service.js +1 -5
  145. package/lib/ws-schema.js +15 -0
  146. package/package.json +6 -3
@@ -0,0 +1,158 @@
1
+ // Exact, read-only Split Worker transcript snapshots for an owning Driver.
2
+ // This does not switch sessions or expose Worker controls.
3
+ var provenance = require("./session-provenance");
4
+
5
+ function attachProjectWorkerConversation(ctx) {
6
+ var sm = ctx.sm;
7
+ var users = ctx.usersModule;
8
+ var MAX_MESSAGES = 120;
9
+ var MAX_TEXT = 8000;
10
+ var MAX_PAGE_TEXT = 64000;
11
+
12
+ function send(ws, value) { ctx.sendTo(ws, value); }
13
+ function sessionForOrigin(origin) {
14
+ var found = null;
15
+ var ambiguous = false;
16
+ sm.sessions.forEach(function (session) {
17
+ if (session.sessionOriginId !== origin) return;
18
+ if (found && found !== session) ambiguous = true;
19
+ else if (!found) found = session;
20
+ });
21
+ return ambiguous ? null : found;
22
+ }
23
+ function actor(ws) {
24
+ if (!ws || !ctx.clients || !ctx.clients.has(ws) || ws._clayPaneActivation || ctx.isMate || !ctx.canAccessProject(ws)) return null;
25
+ var user = ws._clayUser && users.findUserById(ws._clayUser.id);
26
+ if (users.isMultiUser() && !user) return null;
27
+ if (ctx.osUsers && (!user || !user.linuxUser || !ctx.getOsUserInfoForWs({ _clayUser: user }))) return null;
28
+ return { id: user && user.id || null, linuxUser: ctx.osUsers && user ? user.linuxUser : null };
29
+ }
30
+ function exactPair(ws, msg, currentActor) {
31
+ if (!msg || typeof msg.parentSessionOriginId !== "string" || typeof msg.workerSessionOriginId !== "string" ||
32
+ !Number.isInteger(msg.generation) || msg.generation < 1) return null;
33
+ var driver = sessionForOrigin(msg.parentSessionOriginId), worker = sessionForOrigin(msg.workerSessionOriginId);
34
+ if (!driver || !worker || driver === worker || sm.sessions.get(driver.localId) !== driver || sm.sessions.get(worker.localId) !== worker) return null;
35
+ if (!ctx.canAccessSession(ws, driver) || !ctx.canAccessSession(ws, worker)) return null;
36
+ if ((driver.ownerId || null) !== (worker.ownerId || null) || currentActor.id !== (worker.ownerId || null)) return null;
37
+ var record = worker.sessionProvenance;
38
+ if (!record || record.kind !== "worker" || record.parentSessionOriginId !== driver.sessionOriginId || record.generation !== msg.generation) return null;
39
+ if (provenance.isWorker(driver) || !driver.sessionOriginId || !worker.sessionOriginId) return null;
40
+ if (ctx.osUsers) {
41
+ var driverLinux = ctx.getLinuxUserForSession(driver), workerLinux = ctx.getLinuxUserForSession(worker);
42
+ if (!driverLinux || driverLinux !== workerLinux || driverLinux !== currentActor.linuxUser) return null;
43
+ }
44
+ return { driver: driver, worker: worker };
45
+ }
46
+ // Share the exact owner/OS/provenance checks used by the human Worker reader.
47
+ function supervision(ws, driverOrigin) {
48
+ var user = actor(ws), driver = sessionForOrigin(driverOrigin);
49
+ if (!user || !driver || provenance.isWorker(driver) || (driver.ownerId || null) !== user.id || !ctx.canAccessSession(ws, driver)) throw new Error('Driver supervision access changed.');
50
+ if (ctx.osUsers && ctx.getLinuxUserForSession(driver) !== user.linuxUser) throw new Error('Driver OS identity changed.');
51
+ var linked = provenance.ownedWorkersByDriver(Array.from(sm.sessions.values()))[driver.localId] || [];
52
+ var group = ctx.getWorkerGroup && ctx.getWorkerGroup(driver.localId);
53
+ var splitRoles = require('./session-split-group-roles');
54
+ var roles = splitRoles.normalizePair(group && group.pair, group && group.members);
55
+ if (group && !roles.ok) throw new Error('Worker configuration is unavailable.');
56
+ var groupIdentity = JSON.stringify(group && [group.id, group.members, group.pair]);
57
+ var configured = roles.ok && roles.driverId === driver.localId ? roles.workerIds : [];
58
+ var bindings = linked.slice().sort(function (a, b) { return Number(configured.indexOf(b.localId) >= 0) - Number(configured.indexOf(a.localId) >= 0); }).slice(0, 100).map(function (worker) {
59
+ var input = { parentSessionOriginId: driverOrigin, workerSessionOriginId: worker.sessionOriginId, generation: worker.sessionProvenance.generation };
60
+ var pair = exactPair(ws, input, user);
61
+ if (!pair || pair.driver !== driver || pair.worker !== worker || worker.destroying || worker._pairGeneration != null && worker._pairGeneration !== input.generation) throw new Error('Exact Worker supervision identity is unavailable.');
62
+ return { input: input, worker: worker };
63
+ });
64
+ function revalidate() {
65
+ var latest = actor(ws);
66
+ if (!latest || latest.id !== user.id || latest.linuxUser !== user.linuxUser || sessionForOrigin(driverOrigin) !== driver) throw new Error('Driver supervision identity changed.');
67
+ if (!ctx.canAccessSession(ws, driver) || ctx.osUsers && ctx.getLinuxUserForSession(driver) !== user.linuxUser) throw new Error('Driver access or OS identity changed.');
68
+ var nextGroup = ctx.getWorkerGroup && ctx.getWorkerGroup(driver.localId);
69
+ if (JSON.stringify(nextGroup && [nextGroup.id, nextGroup.members, nextGroup.pair]) !== groupIdentity) throw new Error('Worker configuration changed.');
70
+ var nextLinked = provenance.ownedWorkersByDriver(Array.from(sm.sessions.values()))[driver.localId] || [];
71
+ if (linked.length !== nextLinked.length || linked.some(function (worker, i) { return worker !== nextLinked[i]; })) throw new Error('Linked Worker membership changed.');
72
+ bindings.forEach(function (binding) {
73
+ var pair = exactPair(ws, binding.input, latest);
74
+ if (!pair || pair.driver !== driver || pair.worker !== binding.worker || pair.worker.destroying || pair.worker._pairGeneration != null && pair.worker._pairGeneration !== binding.input.generation) throw new Error('Exact Worker generation changed.');
75
+ });
76
+ }
77
+ return { revalidate: revalidate, read: function () {
78
+ revalidate();
79
+ return { observedAt: Date.now(), limit: 100, truncated: linked.length > bindings.length, workers: bindings.map(function (binding) {
80
+ var worker = binding.worker;
81
+ if (worker._pairDelegation && !splitRoles.tokenMatchesWorker(worker, worker._pairDelegation)) throw new Error('Delegated Worker generation changed.');
82
+ var activity = require('./project-pair-lifecycle-status').activityStatus(worker);
83
+ return { id: worker.localId, sessionOriginId: worker.sessionOriginId, parentSessionOriginId: driverOrigin, generation: binding.input.generation,
84
+ association: configured.indexOf(worker.localId) >= 0 ? 'current' : 'archived',
85
+ runtime: require('./session-activity-state').currentState(worker), delegated: activity.delegated,
86
+ currentTaskId: activity.currentTaskId, interrupted: !!activity.interruption, lastTurnInterrupted: activity.lastTurnInterrupted };
87
+ }) };
88
+ } };
89
+ }
90
+ function textContent(value) {
91
+ if (typeof value === "string") return value;
92
+ if (!Array.isArray(value)) return "";
93
+ return value.map(function (part) { return part && typeof part.text === "string" ? part.text : ""; }).join("\n");
94
+ }
95
+ function toolIdentity(entry) {
96
+ var id = entry.id || entry.tool_use_id || entry.toolId;
97
+ var name = entry.toolName || entry.name;
98
+ return { toolId: typeof id === "string" && id.length <= 160 ? id : null,
99
+ toolName: typeof name === "string" ? name.slice(0, 120) : null };
100
+ }
101
+ function record(history, index) {
102
+ var entry = history[index] || {}, type = entry.type;
103
+ if (type === "user_message") return { role: "user", text: textContent(entry.text || entry.content) };
104
+ if (type === "delta" || type === "result" || type === "assistant_message") return { role: "assistant", text: textContent(entry.text || entry.content) };
105
+ if (type === "tool_start" || type === "tool_use") return Object.assign({ role: "tool", text: "Used " + (toolIdentity(entry).toolName || "tool") }, toolIdentity(entry));
106
+ if (type === "tool_result") return Object.assign({ role: "tool", text: textContent(entry.content) || "No recorded tool output.", isError: entry.is_error === true }, toolIdentity(entry));
107
+ return null;
108
+ }
109
+ function snapshot(history, before) {
110
+ var index, offset;
111
+ if (before && typeof before === "object" && Number.isInteger(before.index) && Number.isInteger(before.offset)) {
112
+ index = Math.min(before.index, history.length - 1); offset = Math.max(0, before.offset);
113
+ } else {
114
+ var end = Number.isInteger(before) && before >= 0 ? Math.min(before, history.length) : history.length;
115
+ index = end - 1;
116
+ var latest = index >= 0 ? record(history, index) : null;
117
+ offset = latest ? latest.text.length : 0;
118
+ }
119
+ var rows = [], budget = MAX_PAGE_TEXT, nextBefore = null;
120
+ while (index >= 0 && rows.length < MAX_MESSAGES && budget > 0) {
121
+ var item = record(history, index);
122
+ if (!item) { index -= 1; offset = index >= 0 && record(history, index) ? record(history, index).text.length : 0; continue; }
123
+ offset = Math.min(offset, item.text.length);
124
+ if (!item.text.length || !offset) { index -= 1; offset = index >= 0 && record(history, index) ? record(history, index).text.length : 0; continue; }
125
+ var metadataSize = (item.toolName || "").length + (item.toolId || "").length;
126
+ if (budget <= metadataSize) { nextBefore = { index: index, offset: offset }; break; }
127
+ var start = Math.max(0, offset - Math.min(MAX_TEXT, budget - metadataSize));
128
+ rows.push({ role: item.role, text: item.text.slice(start, offset), recordIndex: index, recordType: history[index].type,
129
+ start: start, end: offset, toolName: item.toolName || null, toolId: item.toolId || null, isError: item.isError === true });
130
+ budget -= offset - start + metadataSize;
131
+ if (start > 0) { nextBefore = { index: index, offset: start }; break; }
132
+ index -= 1;
133
+ if (index >= 0) { var prior = record(history, index); offset = prior ? prior.text.length : 0; }
134
+ }
135
+ rows.reverse();
136
+ if (!nextBefore && index >= 0) nextBefore = { index: index, offset: offset };
137
+ return { messages: rows, nextBefore: nextBefore, hasMore: nextBefore !== null };
138
+ }
139
+ function handleMessage(ws, msg) {
140
+ if (!msg || msg.type !== "split_worker_conversation_request") return false;
141
+ var requestId = typeof msg.requestId === "string" ? msg.requestId.slice(0, 160) : "";
142
+ var response = { type: "split_worker_conversation", requestId: requestId, pageBefore: msg.before == null ? null : msg.before,
143
+ parentSessionOriginId: msg.parentSessionOriginId || null, workerSessionOriginId: msg.workerSessionOriginId || null,
144
+ generation: msg.generation || null, messages: [], hasMore: false, nextBefore: null };
145
+ var current = actor(ws), pair = current && exactPair(ws, msg, current);
146
+ if (!requestId || !pair) response.error = "This Worker conversation is no longer available.";
147
+ else {
148
+ var before = msg.before == null ? pair.worker.history.length : msg.before;
149
+ if (!(Number.isInteger(before) && before >= 0) && !(before && typeof before === "object" && Number.isInteger(before.index) && Number.isInteger(before.offset) && before.index >= 0 && before.offset >= 0)) response.error = "Worker conversation page is invalid.";
150
+ else Object.assign(response, snapshot(pair.worker.history || [], before));
151
+ }
152
+ send(ws, response);
153
+ return true;
154
+ }
155
+ return { handleMessage: handleMessage, supervision: supervision };
156
+ }
157
+
158
+ module.exports = { attachProjectWorkerConversation: attachProjectWorkerConversation };
@@ -50,9 +50,8 @@ function attachWorkerCreationProposal(ctx) {
50
50
  if (!roles || roles.driver !== session || roles.role.kind === "adhoc") {
51
51
  return { error: "The exact Driver/Split Worker membership is not eligible for another Worker." };
52
52
  }
53
- if (roles.workers.length >= 2) return { error: "A Driver can have at most two Split Workers." };
54
- if (roles.workers.length !== 1) return { error: "The existing Split Worker membership is invalid." };
55
- if (!session.sessionOriginId || !roles.workers[0].sessionOriginId || !Number.isInteger(generationOf(roles.workers[0]))) {
53
+ if (roles.workers.length < 1) return { error: "The existing Split Worker membership is invalid." };
54
+ if (!session.sessionOriginId || roles.workers.some(function (worker) { return !worker.sessionOriginId || !Number.isInteger(generationOf(worker)); })) {
56
55
  return { error: "The exact Driver and Split Worker identities are not durable enough to add another Worker." };
57
56
  }
58
57
  return {
@@ -79,7 +78,7 @@ function attachWorkerCreationProposal(ctx) {
79
78
  var group = store.groupForMember(session.localId);
80
79
  if (!group || group.id !== snapshot.groupId || requireSameObject && group !== snapshot.group) return { ok: false };
81
80
  var roles = splitRoles.resolveLiveRoles(sm, group);
82
- if (!roles || roles.role.kind === "adhoc" || roles.driver !== session || roles.role.workerIds.length !== 1 ||
81
+ if (!roles || roles.role.kind === "adhoc" || roles.driver !== session || roles.role.workerIds.length < 1 ||
83
82
  roles.role.workerIds.join(",") !== snapshot.workerIds.join(",")) return { ok: false };
84
83
  for (var i = 0; i < snapshot.workers.length; i++) {
85
84
  var expected = snapshot.workers[i];
@@ -463,7 +463,7 @@ function attachWorkerProposal(ctx) {
463
463
 
464
464
  function getSystemPrompt(session) {
465
465
  if (creationProposal.canMountProposal(session) && !isEligible(session)) {
466
- return "This Driver may configure one additional Split Worker through propose_worker. With two Workers, pass workerId to every targeted partner, queue, interrupt, replacement, close, permission, and targeted status operation. Call partner_status without workerId to discover current Worker handles. Give each Worker independent file ownership and bounded work. Do not assign simultaneous overlapping file edits unless the work is explicitly separated, such as by distinct paths or worktrees.";
466
+ return "This Driver may configure additional Split Workers through propose_worker. With multiple Workers, pass workerId to every targeted partner, queue, interrupt, replacement, close, permission, and targeted status operation. Call partner_status without workerId to discover current Worker handles. Give each Worker independent file ownership and bounded work. Do not assign simultaneous overlapping file edits unless the work is explicitly separated, such as by distinct paths or worktrees.";
467
467
  }
468
468
  if (!isEligible(session) || !driverOrchestration.isHighTierDriverSession(session, sm)) return "";
469
469
  return "For implementation-heavy work, use propose_worker before substantial execution. Work is implementation-heavy when it spans multiple modules, crosses client/server/data boundaries, requires a migration, or contains independent investigation and execution. Do not skip delegation merely because you can implement it yourself; preserve the Driver for decomposition, runtime selection, judgment, review, and integration. The user's explicit request to work directly takes precedence, and work that must remain sequential in this session stays here. Recommend an exact available vendor, model, and effort, and give a concise recommendationRationale explaining why all three fit the task. Clay always records and shows the runtime configuration card. In full-access mode Clay may auto-accept that exact validated recommendation; otherwise it waits for the user's explicit choice. After posting, end the turn. If accepted, Clay creates the exact Driver/Split Worker pair and delegates the proposed task once. If declined, continue in this Driver session. Do not call send_to_partner while unpaired.";
@@ -0,0 +1,101 @@
1
+ var crypto = require('crypto');
2
+ var provenance = require('./session-provenance');
3
+
4
+ function active(worker) {
5
+ return !!(worker.isProcessing || worker._queryStarting || worker._responseRecoveryStopPending);
6
+ }
7
+
8
+ // Human socket endpoint only. Receipts expire after one minute and are never replayed.
9
+ function attachWorkerRemoval(ctx) {
10
+ var receipts = new WeakMap();
11
+ function exact(ws, msg) {
12
+ if (ctx.isMate || !ctx.clients.has(ws) || ws.readyState !== 1 || ws._clayPaneActivation || msg.projectSlug !== ctx.slug) throw new Error('The project connection changed.');
13
+ var user = ws._clayUser && ctx.usersModule.findUserById(ws._clayUser.id);
14
+ if (ctx.usersModule.isMultiUser() && (!user || !ctx.usersModule.canAccessProject(user.id, ctx.getProjectAccess()))) throw new Error('Project access is unavailable.');
15
+ var owner = user && user.id || null;
16
+ if (msg.ownerId !== owner || !ctx.sessionDelete.hasDeletePermission(ws)) throw new Error('Worker removal is not permitted.');
17
+ var driver = ctx.sm.sessions.get(msg.driverId), worker = ctx.sm.sessions.get(msg.workerId);
18
+ if (!driver || !worker || driver === worker || driver.destroying || worker.destroying || driver.hidden || worker.hidden || provenance.isWorker(driver) ||
19
+ driver.sessionOriginId !== msg.driverOriginId || worker.sessionOriginId !== msg.workerOriginId ||
20
+ !msg.driverOriginId || !msg.workerOriginId || (driver.ownerId || null) !== owner || (worker.ownerId || null) !== owner) throw new Error('The exact Driver or Worker changed.');
21
+ var matches = 0;
22
+ ctx.sm.sessions.forEach(function (session) { if (session.sessionOriginId === msg.driverOriginId || session.sessionOriginId === msg.workerOriginId) matches++; });
23
+ var record = worker.sessionProvenance;
24
+ if (matches !== 2 || !record || record.kind !== 'worker' || record.parentSessionOriginId !== driver.sessionOriginId ||
25
+ !Number.isInteger(msg.generation) || msg.generation < 1 || record.generation !== msg.generation ||
26
+ worker._pairGeneration && worker._pairGeneration !== msg.generation) throw new Error('The Worker generation changed.');
27
+ if (ctx.osUsers) {
28
+ var linux = user && user.linuxUser;
29
+ if (!linux || !ctx.getOsUserInfoForWs(ws) || ctx.getLinuxUserForSession(driver) !== linux || ctx.getLinuxUserForSession(worker) !== linux) throw new Error('The OS identity changed.');
30
+ }
31
+ if (user && (!ctx.usersModule.getEffectivePermissions(user, ctx.osUsers).sessionDelete ||
32
+ !ctx.usersModule.canAccessSession(user.id, driver, ctx.getProjectAccess()))) throw new Error('Driver access or removal permission changed.');
33
+ var plan = ctx.sessionDelete.planDeletion(ws, [worker], ctx.getProjectAccess());
34
+ if (!plan.ok || plan.ids.length !== 1 || plan.ids[0] !== worker.localId) throw new Error(plan.error || 'The exact removal scope changed.');
35
+ return { driver: driver, worker: worker, plan: plan };
36
+ }
37
+ function respond(ws, msg, status, error, token) {
38
+ ctx.sendTo(ws, { type: 'split_worker_remove_result', requestId: msg.requestId, projectSlug: ctx.slug,
39
+ workerOriginId: msg.workerOriginId, generation: msg.generation, status: status, error: error || null, token: token || null });
40
+ }
41
+ async function perform(ws, msg, receipt) {
42
+ try {
43
+ var pair = exact(ws, msg), worker = pair.worker, driver = pair.driver;
44
+ if (receipt.driver && (receipt.driver !== driver || receipt.worker !== worker)) throw new Error('The reviewed sessions were replaced.');
45
+ if (receipt.confirmed && (receipt.query !== worker.queryInstance || receipt.delegation !== worker._pairDelegation ||
46
+ receipt.stopRevision !== worker._humanStopRevision || receipt.driverStopRevision !== driver._humanStopRevision)) throw new Error('The reviewed Worker execution changed.');
47
+ var sourceSession = ws._clayActiveSession;
48
+ if (receipt.sourceSession !== sourceSession) throw new Error('The source conversation changed.');
49
+ if (active(worker)) {
50
+ if (!receipt.confirmed) {
51
+ receipt.driver = driver; receipt.worker = worker; receipt.query = worker.queryInstance; receipt.delegation = worker._pairDelegation;
52
+ receipt.stopRevision = worker._humanStopRevision; receipt.driverStopRevision = driver._humanStopRevision;
53
+ receipt.token = crypto.randomUUID(); receipt.status = 'confirmation_required';
54
+ respond(ws, msg, receipt.status, null, receipt.token); return;
55
+ }
56
+ var runtime = worker.queryInstance, process = worker.worker;
57
+ ctx.actions.stop(worker, ws._clayUser || null);
58
+ var stopRevision = worker._humanStopRevision, driverRevision = driver._humanStopRevision;
59
+ var deadline = Date.now() + (ctx.stopTimeoutMs || 5000);
60
+ while (active(worker)) {
61
+ if (Date.now() >= deadline) throw new Error('Stop completion is unknown. The Worker session was retained.');
62
+ await new Promise(function (resolve) { setTimeout(resolve, 25); });
63
+ var current = exact(ws, msg);
64
+ if (receipt.cancelled || current.worker !== worker || current.driver !== driver || ws._clayActiveSession !== sourceSession ||
65
+ worker._humanStopRevision !== stopRevision || driver._humanStopRevision !== driverRevision ||
66
+ worker.queryInstance && worker.queryInstance !== runtime || worker.worker && worker.worker !== process) throw new Error('Removal interrupted by a changed execution or source. The session was retained.');
67
+ }
68
+ }
69
+ var finalPair = exact(ws, msg);
70
+ if (receipt.cancelled || finalPair.worker !== worker || finalPair.driver !== driver || ws._clayActiveSession !== sourceSession) throw new Error('Removal was cancelled or its target changed.');
71
+ ctx.sessionDelete.executeOwnedDeletion(finalPair.plan, { requireDurableRemoval: true });
72
+ if (ctx.sm.sessions.has(worker.localId)) throw new Error('Worker removal was not observed.');
73
+ receipt.status = 'removed'; respond(ws, msg, 'removed');
74
+ } catch (error) { receipt.status = 'failed'; respond(ws, msg, 'failed', error.message || String(error)); }
75
+ }
76
+ function handleMessage(ws, msg) {
77
+ if (!msg || msg.type !== 'split_worker_remove') return false;
78
+ if (typeof msg.requestId !== 'string' || !/^[a-zA-Z0-9_-]{1,100}$/.test(msg.requestId)) return true;
79
+ var records = receipts.get(ws);
80
+ if (!records) { records = new Map(); receipts.set(ws, records); }
81
+ records.forEach(function (value, key) { if (value.expires < Date.now() && value.status !== 'removing') records.delete(key); });
82
+ var receipt = records.get(msg.requestId);
83
+ var fingerprint = JSON.stringify([msg.projectSlug, msg.ownerId, msg.driverId, msg.driverOriginId, msg.workerId, msg.workerOriginId, msg.generation]);
84
+ if (msg.action === 'cancel') { if (receipt && receipt.fingerprint === fingerprint) receipt.cancelled = true; return true; }
85
+ if (receipt) {
86
+ if (receipt.status === 'removing' && receipt.fingerprint === fingerprint) return true;
87
+ if (msg.action !== 'confirm' || receipt.status !== 'confirmation_required' || receipt.expires < Date.now() || receipt.cancelled ||
88
+ receipt.fingerprint !== fingerprint || receipt.token !== msg.token) { respond(ws, msg, 'failed', 'This removal request is stale or already used.'); return true; }
89
+ receipt.confirmed = true;
90
+ } else {
91
+ if (msg.action !== 'request' || records.size >= 32) { respond(ws, msg, 'failed', 'Start a fresh removal request.'); return true; }
92
+ receipt = { fingerprint: fingerprint, sourceSession: ws._clayActiveSession, expires: Date.now() + 60000, confirmed: false };
93
+ records.set(msg.requestId, receipt);
94
+ }
95
+ receipt.status = 'removing';
96
+ receipt.promise = perform(ws, msg, receipt);
97
+ return true;
98
+ }
99
+ return { handleMessage: handleMessage, exact: exact };
100
+ }
101
+ module.exports = { attachWorkerRemoval: attachWorkerRemoval, active: active };
package/lib/project.js CHANGED
@@ -71,6 +71,8 @@ var { attachProjectLogs } = require("./project-logs");
71
71
  var { attachProjectLogFeedbackDelivery } = require("./project-log-feedback-delivery");
72
72
  var { attachProjectMateKnowledge } = require("./project-mate-knowledge");
73
73
  var { attachSplitGroups } = require("./session-split-groups");
74
+ var { attachProjectSplitViews } = require("./project-split-views");
75
+ var { attachProjectWorkerConversation } = require("./project-worker-conversation");
74
76
  var multiWorkerFeature = require("./multi-worker-feature");
75
77
  var toolControlMcp = require("./tool-control-mcp-server");
76
78
  var capsuleFloor = require("./capsule-display-floor");
@@ -696,6 +698,24 @@ function createProjectContext(opts) {
696
698
  }
697
699
  },
698
700
  });
701
+ var _splitViews = attachProjectSplitViews({
702
+ sm: sm, slug: slug, clients: clients, sendTo: sendTo, usersModule: usersModule,
703
+ isMate: isMate, osUsers: osUsers,
704
+ canAccessProject: function (ws) { return _requestAccess.canAccessProject(ws, slug); },
705
+ canAccessSession: function (ws, session) { return _requestAccess.canReadSession(ws, session); },
706
+ getLinuxUserForSession: getLinuxUserForSession,
707
+ getOsUserInfoForWs: getOsUserInfoForWs,
708
+ });
709
+ var _workerConversation = attachProjectWorkerConversation({
710
+ getWorkerGroup: function (id) { return _splitGroups.store.groupForMember(id); },
711
+ sm: sm, slug: slug, clients: clients, sendTo: sendTo, usersModule: usersModule,
712
+ isMate: isMate,
713
+ canAccessProject: function (ws) { return _requestAccess.canAccessProject(ws, slug); },
714
+ canAccessSession: function (ws, session) { return _requestAccess.canReadSession(ws, session); },
715
+ osUsers: osUsers,
716
+ getLinuxUserForSession: getLinuxUserForSession,
717
+ getOsUserInfoForWs: getOsUserInfoForWs,
718
+ });
699
719
 
700
720
  var _projMode = typeof opts.onGetProjectDefaultMode === "function" ? opts.onGetProjectDefaultMode(slug) : null;
701
721
  var _srvMode = typeof opts.onGetServerDefaultMode === "function" ? opts.onGetServerDefaultMode() : null;
@@ -1859,7 +1879,7 @@ function createProjectContext(opts) {
1859
1879
  if (_studioPresentation.handleMessage(ws, msg)) return;
1860
1880
 
1861
1881
  // --- DM messages (delegated to server-level handler) ---
1862
- if (msg.type === "home_mate_question_response" || msg.type === "mate_context_prepare" || msg.type === "clay_ui_ready" || msg.type === "clay_navigation_claim" || msg.type === "clay_navigation_ack" || msg.type === "clay_navigation_rendered" || msg.type === "home_debate_question_response" || msg.type === "home_debate_control" || msg.type === "home_mate_creation_question_response" || msg.type === "default_ai_get" || msg.type === "default_ai_catalog_get" || msg.type === "default_ai_set" || msg.type === "default_vendor_get" || msg.type === "default_vendor_set" || msg.type === "context_view_get" || msg.type === "context_view_set" || msg.type === "cursor_sharing_get" || msg.type === "cursor_sharing_set") {
1882
+ if (msg.type === "home_mate_question_response" || msg.type === "mate_context_prepare" || msg.type === "clay_ui_ready" || msg.type === "clay_view_observed" || msg.type === "clay_navigation_claim" || msg.type === "clay_navigation_ack" || msg.type === "clay_navigation_rendered" || msg.type === "home_debate_question_response" || msg.type === "home_debate_control" || msg.type === "home_mate_creation_question_response" || msg.type === "default_ai_get" || msg.type === "default_ai_catalog_get" || msg.type === "default_ai_set" || msg.type === "default_vendor_get" || msg.type === "default_vendor_set" || msg.type === "context_view_get" || msg.type === "context_view_set" || msg.type === "cursor_sharing_get" || msg.type === "cursor_sharing_set") {
1863
1883
  if (typeof opts.onDmMessage === "function") opts.onDmMessage(ws, msg, slug);
1864
1884
  return;
1865
1885
  }
@@ -1968,6 +1988,8 @@ function createProjectContext(opts) {
1968
1988
  if (_sessionHandoff.handleMessage(ws, msg)) return;
1969
1989
  if (_driverContinuation.handleMessage(ws, msg)) return;
1970
1990
  if (_splitGroups.handleMessage(ws, msg)) return;
1991
+ if (_splitViews.handleMessage(ws, msg)) return;
1992
+ if (_workerConversation.handleMessage(ws, msg)) return;
1971
1993
  if (_models.handleMessage(ws, msg)) return;
1972
1994
  if (_scheduledTasks.handleMessage(ws, msg)) return;
1973
1995
  if (_loopInterview.handleMessage(ws, msg)) return;
@@ -2155,6 +2177,7 @@ function createProjectContext(opts) {
2155
2177
  });
2156
2178
  sm.setSessionHydrator(_sessionSkillCatalog.hydrate);
2157
2179
  var _sessions = attachSessions({
2180
+ onSessionFoldersChanged: function (accountId) { _splitViews.onFoldersChanged(accountId); },
2158
2181
  splitStore: _splitGroups.store,
2159
2182
  adapters: adapters,
2160
2183
  isPendingQueuePaused: function (session) { return _pendingMessageQueue.isPaused(session); },
@@ -2767,6 +2790,8 @@ function createProjectContext(opts) {
2767
2790
  _mcp: _mcp,
2768
2791
  _notifications: _notifications,
2769
2792
  _splitGroups: _splitGroups,
2793
+ _splitViews: _splitViews,
2794
+ _workerConversation: _workerConversation,
2770
2795
  _vendorLogin: _vendorLogin,
2771
2796
  autonomousRun: _autonomousRun,
2772
2797
  resolveSessionForView: _sessions.resolveSessionForView,
@@ -3031,6 +3056,8 @@ function createProjectContext(opts) {
3031
3056
  },
3032
3057
  cancelMateSchedule: function (session, record) { return _mateLifecycle.cancel(session, record); },
3033
3058
  getSessionManager: function () { return sm; },
3059
+ getSplitViewControl: _splitViews.forConnection,
3060
+ getViewWorkerSupervision: _workerConversation.supervision,
3034
3061
  createDelegatedSession: _delegatedSession.createDelegatedSession,
3035
3062
  refreshEnvironmentRuntime: function () { return sdk.refreshEnvironmentRuntime(); },
3036
3063
  getWorkspaceProductOperations: function () { return isMate ? null : _workspaceOperations; },
package/lib/public/app.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { initializeAfterPaneReady } from './modules/pane-deferred-bootstrap.js';
1
2
  import { initStudioBridge } from './modules/clay-studio-bridge.js';
2
3
  import { initSharedBrowser } from './modules/shared-browser.js';
3
4
  import { initTopbarWorkbenchControls } from './modules/topbar-workbench-controls.js';
@@ -27,7 +28,7 @@ import {
27
28
  } from './modules/sidebar-mobile.js';
28
29
  import { initMateSidebar, showMateSidebar, hideMateSidebar, renderMateSessionList, updateMateSidebarProfile, handleMateSearchResults } from './modules/mate-sidebar.js';
29
30
  import { initMateKnowledge, requestKnowledgeList, renderKnowledgeList, handleKnowledgeContent, hideKnowledge } from './modules/mate-knowledge.js';
30
- import { initMateKnowledgeWorkbench } from './modules/mate-knowledge-workbench.js';
31
+ import { initMateKnowledgeWorkbench } from './modules/knowledge-workbench-entry.js';
31
32
  import { initMateMemory, renderMemoryList, hideMemory } from './modules/mate-memory.js';
32
33
  import { initRewind, setRewindMode, showRewindModal, clearPendingRewindUuid, addRewindButton, onRewindComplete, onRewindError } from './modules/rewind.js';
33
34
  import { initNotifications, showDoneNotification, playDoneSound, isNotifAlertEnabled, isNotifSoundEnabled } from './modules/notifications.js';
@@ -114,6 +115,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
114
115
  // --- Base path for multi-project routing ---
115
116
  var bootParams = new URLSearchParams(location.search);
116
117
  var paneMode = bootParams.get("pane") === "1";
118
+ if (paneMode && typeof performance !== "undefined" && performance.mark) performance.mark("clay:app-evaluated");
117
119
  var paneSessionId = Number(bootParams.get("session"));
118
120
  if (!Number.isSafeInteger(paneSessionId) || paneSessionId < 1) paneSessionId = null;
119
121
  if (paneMode) document.body.classList.add("pane-mode");
@@ -307,6 +309,10 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
307
309
  panePinPending: paneMode && !!paneSessionId,
308
310
  paneCommittedBinding: null, panePendingBinding: null, paneReady: false, paneSuspended: false,
309
311
  paneActivationGeneration: 0, paneActivationTimer: null, panePointerBinding: null, paneRichDrafts: {},
312
+ paneLayoutScrollLock: null,
313
+ paneLastUserScrollTop: null,
314
+ paneUserScrollIntent: null,
315
+ paneUserScrollRevision: 0,
310
316
  sessionRichDrafts: {},
311
317
  sessionActionEpoch: 0, composerUploadingCount: 0, deferredSessionSwitch: null,
312
318
  paneCallbackBinding: null, paneActionEvents: [], paneHeldKeys: {}, rewindAction: null, handoffAction: null,
@@ -317,6 +323,27 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
317
323
  paneBindingSessions: [],
318
324
  splitPanes: null,
319
325
  splitGroups: [],
326
+ splitViewWorkerConversation: null,
327
+ splitViewWorkerRemoval: null,
328
+ projectSplitViews: [],
329
+ projectSplitViewRevision: 0,
330
+ projectSplitViewContextKey: null,
331
+ projectSplitViewHydrated: false,
332
+ projectSplitViewPending: null,
333
+ projectSplitViewError: "",
334
+ projectSplitViewActiveId: null,
335
+ projectSplitViewFocusedOrigin: null,
336
+ projectSplitViewPreviewTree: null,
337
+ projectSplitViewDrag: null,
338
+ splitViewOrganizationDrag: null,
339
+ projectSplitViewDividerDrag: null,
340
+ projectSplitViewTranscriptScroll: {},
341
+ projectSplitViewSuppressClick: null,
342
+ projectSplitViewSocketGeneration: 0,
343
+ projectSplitViewRequestSocket: null,
344
+ projectSplitViewRequestGeneration: null,
345
+ projectSplitViewRequestOwnerId: null,
346
+ projectSplitViewRequestSlug: null,
320
347
  sessionFolders: null,
321
348
  sessionFoldersSlug: null,
322
349
  sessionFolderCreate: null,
@@ -763,19 +790,12 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
763
790
  // buttons exist in the DOM when sidebar.js (and later terminal.js /
764
791
  // mcp-ui.js / etc.) attach click handlers by ID.
765
792
  initToolPalettes();
766
- initProjectLogs();
767
- initYou();
768
- initYouActivity();
769
- initIssues();
770
- initIssuesContext();
771
- initIssueLinks();
772
- initScheduledTasks();
773
- initDebatesWorkbench();
793
+ initializeAfterPaneReady([initProjectLogs, initYou, initYouActivity, initIssues, initIssuesContext, initIssueLinks, initScheduledTasks, initDebatesWorkbench]);
774
794
  initSidebar(sidebarCtx);
775
795
  var wsGetter = function () { return _getWsRef(); };
776
796
  initMateSidebar(wsGetter);
777
797
  initMateKnowledge(wsGetter);
778
- initMateKnowledgeWorkbench();
798
+ initializeAfterPaneReady([initMateKnowledgeWorkbench]);
779
799
  initMateMemory(wsGetter, { onShow: function () { hideKnowledge(); hideNotes(); } });
780
800
  initCommandPalette({
781
801
  switchProject: function (slug) { switchProject(slug); },
@@ -1289,9 +1309,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
1289
1309
  fileTreeEl: $("file-tree"),
1290
1310
  fileViewerEl: $("file-viewer"),
1291
1311
  });
1292
- initGitPanel();
1293
- initTopbarWorkbenchControls();
1294
- initSharedBrowser();
1312
+ initializeAfterPaneReady([initGitPanel, initTopbarWorkbenchControls, initSharedBrowser]);
1295
1313
  initStudioBridge();
1296
1314
  initWorkerPaneLock();
1297
1315
  initPaneActivation();
@@ -1502,6 +1520,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
1502
1520
 
1503
1521
  // --- Init ---
1504
1522
  lucide.createIcons();
1523
+ if (paneMode && typeof performance !== "undefined" && performance.mark) performance.mark("clay:bootstrap-complete");
1505
1524
  connect();
1506
1525
  if (slugMatch && location.hash === "#scheduler") {
1507
1526
  // Restore scheduler view after refresh
@@ -278,6 +278,15 @@ html, body {
278
278
  overflow: hidden;
279
279
  }
280
280
 
281
+ /* Seat the content corner inside the connected navigation/sidebar surround. */
282
+ @media (min-width: 769px) {
283
+ body:not(.pane-mode) #main-area { background: var(--sidebar-bg); }
284
+ body:not(.pane-mode) #main-column {
285
+ background: var(--bg);
286
+ border-top-left-radius: 12px;
287
+ }
288
+ }
289
+
281
290
  /* --- Main Panels (app + file-viewer + terminal side-by-side) --- */
282
291
  #main-panels {
283
292
  flex: 1;
@@ -391,7 +391,6 @@
391
391
  /* --- Panel fullscreen --- */
392
392
  @media (min-width: 1024px) {
393
393
  /* Browser, Terminal and Files expand below the session title; see right-workbench.css. */
394
- #main-column:has(.panel-fullscreen:not(.hidden):not(#shared-browser-panel, #terminal-container, #file-viewer)) > .title-bar-content { display: none; }
395
394
  #main-panels:has(.panel-fullscreen:not(.hidden)) > #app { display: none; }
396
395
  }
397
396
  @media (max-width: 1023px) {
@@ -0,0 +1,100 @@
1
+ /* Loading actions retain their place while their unavailable state is explicit. */
2
+ .split-pane-header[data-pane-loading="true"] button {
3
+ opacity: .38;
4
+ color: var(--text-muted);
5
+ background: transparent;
6
+ box-shadow: none;
7
+ cursor: default;
8
+ }
9
+ .split-pane-header[data-pane-loading="true"] .split-pane-title { cursor: default; }
10
+
11
+ /* Parent-owned search stays above Worker rows without moving the conversation. */
12
+ .split-pane-search-popover {
13
+ position: absolute;
14
+ top: var(--split-pane-search-top, 28px);
15
+ right: 8px;
16
+ z-index: 20;
17
+ display: flex;
18
+ align-items: center;
19
+ gap: 4px;
20
+ box-sizing: border-box;
21
+ width: min(420px, calc(100% - 16px));
22
+ padding: 5px 6px;
23
+ border: 1px solid var(--border);
24
+ border-top: 0;
25
+ border-radius: 0 0 8px 8px;
26
+ background: var(--bg-alt);
27
+ color: var(--text);
28
+ box-shadow: 0 3px 8px rgba(var(--shadow-rgb), .14);
29
+ }
30
+ .split-pane-search-popover[hidden] { display: none; }
31
+ .split-pane-search-input {
32
+ flex: 1 1 120px;
33
+ width: 0;
34
+ min-width: 32px;
35
+ height: 26px;
36
+ box-sizing: border-box;
37
+ padding: 3px 7px;
38
+ border: 1px solid var(--border-subtle);
39
+ border-radius: 5px;
40
+ background: var(--bg);
41
+ color: var(--text);
42
+ font: inherit;
43
+ font-size: 12px;
44
+ }
45
+ .split-pane-search-input::placeholder { color: var(--text-muted); }
46
+ .split-pane-search-count {
47
+ flex: 0 1 auto;
48
+ max-width: 72px;
49
+ overflow: hidden;
50
+ color: var(--text-muted);
51
+ font-size: 10px;
52
+ text-overflow: ellipsis;
53
+ white-space: nowrap;
54
+ }
55
+ .split-pane-search-action {
56
+ display: inline-flex;
57
+ align-items: center;
58
+ justify-content: center;
59
+ flex: 0 0 24px;
60
+ width: 24px;
61
+ height: 26px;
62
+ padding: 4px;
63
+ border: 0;
64
+ border-radius: 4px;
65
+ background: transparent;
66
+ color: var(--text-muted);
67
+ cursor: pointer;
68
+ }
69
+ .split-pane-search-action svg { width: 14px; height: 14px; }
70
+ .split-pane-search-action:hover:not(:disabled) { background: var(--sidebar-hover); color: var(--text); }
71
+ .split-pane-search-action:disabled { opacity: .45; cursor: default; }
72
+ .split-pane-search-input:focus-visible,
73
+ .split-pane-search-action:focus-visible,
74
+ #sidebar-shell-expand-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
75
+
76
+ /* The shell remains reachable while its native conversation header is hidden. */
77
+ #sidebar-shell-expand-btn {
78
+ display: none;
79
+ align-items: center;
80
+ justify-content: center;
81
+ width: 24px;
82
+ height: 24px;
83
+ padding: 4px;
84
+ border: 0;
85
+ border-radius: 5px;
86
+ background: transparent;
87
+ color: var(--text-secondary);
88
+ cursor: pointer;
89
+ }
90
+ #sidebar-shell-expand-btn svg { width: 16px; height: 16px; }
91
+ #sidebar-shell-expand-btn:hover { background: var(--sidebar-hover); color: var(--text); }
92
+ #sidebar-shell-expand-btn[hidden] { display: none; }
93
+ @media (min-width: 769px) {
94
+ #layout.sidebar-collapsed #sidebar-shell-expand-btn:not([hidden]) { display: inline-flex; }
95
+ }
96
+ @media (pointer: coarse) {
97
+ .split-pane-search-input { height: 32px; font-size: 16px; }
98
+ .split-pane-search-action { flex-basis: 32px; width: 32px; height: 32px; }
99
+ #sidebar-shell-expand-btn { width: 28px; height: 28px; }
100
+ }