clay-server 4.0.0-beta.9 → 4.0.1-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 (188) hide show
  1. package/lib/capsule-display-floor.js +51 -0
  2. package/lib/capsule-frame-server.js +255 -0
  3. package/lib/capsule-mate-turn.js +105 -0
  4. package/lib/capsule-pig-logic.js +300 -0
  5. package/lib/capsule-server-runtimes.js +39 -0
  6. package/lib/capsules/pig/display.js +198 -0
  7. package/lib/capsules/pig/manifest.json +9 -0
  8. package/lib/capsules/pig/ui.json +84 -0
  9. package/lib/config.js +25 -1
  10. package/lib/daemon-projects.js +13 -8
  11. package/lib/daemon.js +127 -14
  12. package/lib/git-cli.js +95 -1
  13. package/lib/knowledge-import.js +436 -0
  14. package/lib/knowledge-record-store.js +188 -0
  15. package/lib/knowledge-search.js +240 -0
  16. package/lib/mate-knowledge-mcp-server.js +151 -0
  17. package/lib/mate-knowledge-migration.js +283 -0
  18. package/lib/mate-knowledge-service.js +488 -0
  19. package/lib/mate-knowledge-sync.js +482 -0
  20. package/lib/migrate-single-user.js +2 -0
  21. package/lib/notes-lifecycle.js +149 -0
  22. package/lib/notes.js +60 -3
  23. package/lib/os-users.js +3 -1
  24. package/lib/project-capsule-catalog.js +9 -1
  25. package/lib/project-capsule-turn.js +96 -0
  26. package/lib/project-connection.js +15 -1
  27. package/lib/project-file-history.js +223 -0
  28. package/lib/project-file-watch.js +77 -49
  29. package/lib/project-filesystem.js +31 -220
  30. package/lib/project-http.js +15 -1
  31. package/lib/project-knowledge.js +22 -0
  32. package/lib/project-log-context.js +172 -0
  33. package/lib/project-log-feedback-delivery.js +118 -0
  34. package/lib/project-logs-comments.js +137 -0
  35. package/lib/project-logs-context-state.js +42 -0
  36. package/lib/project-logs-mcp-server.js +328 -0
  37. package/lib/project-logs-query.js +185 -0
  38. package/lib/project-logs-root.js +75 -0
  39. package/lib/project-logs-schema.js +259 -0
  40. package/lib/project-logs-service.js +463 -0
  41. package/lib/project-logs-snapshot.js +205 -0
  42. package/lib/project-logs-store.js +495 -0
  43. package/lib/project-logs-versioning.js +132 -0
  44. package/lib/project-logs.js +400 -0
  45. package/lib/project-mate-interaction.js +14 -0
  46. package/lib/project-mate-knowledge.js +110 -0
  47. package/lib/project-memory.js +22 -0
  48. package/lib/project-message-delivery.js +70 -0
  49. package/lib/project-pair-lifecycle.js +460 -0
  50. package/lib/project-request-access.js +66 -0
  51. package/lib/project-session-handoff.js +1 -1
  52. package/lib/project-session-notes.js +64 -13
  53. package/lib/project-session-pair.js +159 -146
  54. package/lib/project-session-spawn.js +1 -1
  55. package/lib/project-sessions.js +71 -16
  56. package/lib/project-user-message.js +53 -4
  57. package/lib/project-worker-permission.js +430 -0
  58. package/lib/project-worker-proposal.js +208 -62
  59. package/lib/project.js +191 -6
  60. package/lib/public/app.js +64 -35
  61. package/lib/public/css/admin.css +2 -2
  62. package/lib/public/css/capsule-ui.css +38 -0
  63. package/lib/public/css/command-palette.css +45 -9
  64. package/lib/public/css/git-placard.css +154 -0
  65. package/lib/public/css/home-hub.css +4 -0
  66. package/lib/public/css/home-session-actions.css +66 -0
  67. package/lib/public/css/home-sidebar.css +66 -0
  68. package/lib/public/css/input.css +84 -3
  69. package/lib/public/css/messages.css +40 -1
  70. package/lib/public/css/mobile-nav.css +80 -0
  71. package/lib/public/css/notifications-center.css +67 -0
  72. package/lib/public/css/pane.css +97 -10
  73. package/lib/public/css/project-logs.css +198 -0
  74. package/lib/public/css/scheduler.css +1 -1
  75. package/lib/public/css/session-actions.css +16 -101
  76. package/lib/public/css/sidebar.css +103 -0
  77. package/lib/public/css/sticky-notes.css +237 -229
  78. package/lib/public/css/worker-proposal.css +36 -3
  79. package/lib/public/index.html +43 -11
  80. package/lib/public/modules/admin.js +8 -8
  81. package/lib/public/modules/app-connection.js +124 -6
  82. package/lib/public/modules/app-home-hub.js +12 -8
  83. package/lib/public/modules/app-message-cards.js +2 -1
  84. package/lib/public/modules/app-message-router.js +1 -1
  85. package/lib/public/modules/app-messages.js +92 -11
  86. package/lib/public/modules/app-misc.js +2 -14
  87. package/lib/public/modules/app-notifications.js +11 -7
  88. package/lib/public/modules/app-panels.js +3 -2
  89. package/lib/public/modules/app-projects.js +56 -18
  90. package/lib/public/modules/app-rendering.js +1 -0
  91. package/lib/public/modules/capsule-preference.js +44 -0
  92. package/lib/public/modules/chat-bubble-renderer.js +1 -1
  93. package/lib/public/modules/clay-log-links.js +70 -0
  94. package/lib/public/modules/command-palette.js +52 -15
  95. package/lib/public/modules/context-sources.js +0 -3
  96. package/lib/public/modules/git-agent-sessions.js +85 -0
  97. package/lib/public/modules/git-panel.js +111 -84
  98. package/lib/public/modules/git-placard.js +161 -0
  99. package/lib/public/modules/home-chat-empty-state.js +39 -0
  100. package/lib/public/modules/home-chat-identity.js +9 -0
  101. package/lib/public/modules/home-conversations-sheet.js +102 -48
  102. package/lib/public/modules/home-mate-chat.js +15 -35
  103. package/lib/public/modules/home-session-actions.js +6 -0
  104. package/lib/public/modules/home-sidebar-chat-list.js +101 -41
  105. package/lib/public/modules/home-sidebar.js +7 -0
  106. package/lib/public/modules/home-surface-boot.js +7 -3
  107. package/lib/public/modules/home-tool-frame.js +182 -0
  108. package/lib/public/modules/home-tools.js +138 -1
  109. package/lib/public/modules/input.js +38 -7
  110. package/lib/public/modules/markdown.js +2 -0
  111. package/lib/public/modules/mate-sidebar.js +0 -8
  112. package/lib/public/modules/message-delivery.js +97 -0
  113. package/lib/public/modules/paste-modal.js +84 -0
  114. package/lib/public/modules/project-activation.js +45 -0
  115. package/lib/public/modules/project-logs-render.js +405 -0
  116. package/lib/public/modules/project-logs.js +461 -0
  117. package/lib/public/modules/project-removal-target.js +26 -0
  118. package/lib/public/modules/scheduler.js +17 -3
  119. package/lib/public/modules/search-clay-chat.js +47 -6
  120. package/lib/public/modules/search-clay-controls.js +86 -0
  121. package/lib/public/modules/session-actions.js +29 -105
  122. package/lib/public/modules/session-hierarchy.js +54 -0
  123. package/lib/public/modules/sidebar-mobile.js +40 -15
  124. package/lib/public/modules/sidebar-session-hierarchy.js +214 -0
  125. package/lib/public/modules/sidebar-sessions.js +77 -11
  126. package/lib/public/modules/sidebar.js +17 -5
  127. package/lib/public/modules/split-group-helpers.js +8 -0
  128. package/lib/public/modules/split-pair-ui.js +15 -4
  129. package/lib/public/modules/split-view.js +5 -2
  130. package/lib/public/modules/split-worker-runtime.js +24 -0
  131. package/lib/public/modules/sticky-note-markdown.js +5 -0
  132. package/lib/public/modules/sticky-notes-browser.js +326 -0
  133. package/lib/public/modules/sticky-notes-card.js +360 -0
  134. package/lib/public/modules/sticky-notes-editor.js +291 -0
  135. package/lib/public/modules/sticky-notes-shared.js +90 -0
  136. package/lib/public/modules/sticky-notes.js +170 -946
  137. package/lib/public/modules/thinking-lifecycle.js +175 -0
  138. package/lib/public/modules/thinking-summary.js +28 -0
  139. package/lib/public/modules/thinking-view.js +75 -0
  140. package/lib/public/modules/tool-palette-order.js +143 -0
  141. package/lib/public/modules/tool-palette-overlays.js +159 -0
  142. package/lib/public/modules/tool-palette.js +21 -188
  143. package/lib/public/modules/tools.js +31 -123
  144. package/lib/public/modules/update-snooze.js +169 -0
  145. package/lib/public/modules/user-settings.js +4 -0
  146. package/lib/public/modules/worker-pane-lock.js +176 -0
  147. package/lib/public/modules/worker-proposal-state.js +16 -0
  148. package/lib/public/modules/worker-proposal.js +49 -20
  149. package/lib/public/style.css +3 -1
  150. package/lib/sdk-bridge.js +136 -47
  151. package/lib/sdk-message-processor.js +36 -7
  152. package/lib/server-experimental-settings.js +49 -0
  153. package/lib/server-global-ws.js +11 -0
  154. package/lib/server-home-chat-events.js +34 -1
  155. package/lib/server-home-chat.js +62 -4
  156. package/lib/server-home-clay-entry.js +1 -1
  157. package/lib/server-home-clay-session-links.js +34 -5
  158. package/lib/server-settings.js +5 -0
  159. package/lib/server-tools.js +127 -9
  160. package/lib/server.js +137 -22
  161. package/lib/session-driver-eligibility.js +38 -0
  162. package/lib/session-driver-orchestration.js +31 -0
  163. package/lib/session-hygiene.js +3 -0
  164. package/lib/session-notes-mcp-server.js +25 -5
  165. package/lib/session-pair-factory.js +247 -0
  166. package/lib/session-pair-mcp-server.js +46 -8
  167. package/lib/session-pair-prompts.js +79 -0
  168. package/lib/session-pair-turn-control.js +153 -0
  169. package/lib/session-provenance.js +119 -0
  170. package/lib/session-spawn-mcp-server.js +1 -1
  171. package/lib/session-split-groups.js +4 -1
  172. package/lib/session-title-policy.js +60 -0
  173. package/lib/session-visibility.js +41 -0
  174. package/lib/sessions.js +57 -10
  175. package/lib/tools-registry.js +89 -10
  176. package/lib/update-snooze.js +437 -0
  177. package/lib/users-experimental-preferences.js +30 -0
  178. package/lib/users-permissions.js +6 -6
  179. package/lib/users.js +22 -16
  180. package/lib/workspace-query-access.js +102 -0
  181. package/lib/workspace-query-service.js +24 -17
  182. package/lib/worktree.js +25 -38
  183. package/lib/ws-schema.js +44 -7
  184. package/lib/yoke/adapters/claude-worker.js +16 -6
  185. package/lib/yoke/adapters/claude.js +12 -0
  186. package/lib/yoke/adapters/codex.js +24 -14
  187. package/lib/yoke/vendor-registry.js +1 -1
  188. package/package.json +2 -2
@@ -0,0 +1,176 @@
1
+ // Driver-operated Split Worker surface.
2
+ //
3
+ // A configured Split Worker executes work its Driver delegated, so its pane is
4
+ // an execution surface rather than a chat the person types into. This module
5
+ // takes the composer's footprint away and replaces it with one compact,
6
+ // non-interactive status line.
7
+ //
8
+ // What stays, deliberately: the transcript, streaming status, tool and
9
+ // permission progress, scrolling, and the stop control — the human keeps their
10
+ // emergency brake. What goes: the text input, attachments, send, the composer
11
+ // context row, and any Worker-local permission-mode affordance.
12
+ //
13
+ // The role is read from pair state only (`splitGroups` + the displayed
14
+ // session), which is server-broadcast and therefore correct across reconnect,
15
+ // project switch, session switch, dissolve, replacement and role change. There
16
+ // is no second copy of the role and nothing in localStorage. A plain ad-hoc
17
+ // side-by-side split has no pair roles, so it is never locked and keeps its
18
+ // composer.
19
+ //
20
+ // Accessibility: locked controls are removed from the tab order with `disabled`
21
+ // and `tabindex="-1"`, and the composer region is `aria-hidden` while the
22
+ // status line carries the explanation, so a screen reader is told the pane is
23
+ // Driver-controlled instead of finding focusable inputs that do nothing.
24
+
25
+ import { store } from './store.js';
26
+
27
+ var STATUS_ID = "worker-pane-status";
28
+ var LOCK_CLASS = "worker-pane-locked";
29
+
30
+ // Every interactive composer affordance. Queried lazily: a pane iframe and the
31
+ // main window share this markup, and some ids are mobile-only.
32
+ var LOCKED_CONTROL_IDS = [
33
+ "input",
34
+ "send-btn",
35
+ "attach-file-btn",
36
+ "attach-image-btn",
37
+ "input-more-btn",
38
+ "shell-command-btn",
39
+ "stt-btn",
40
+ "schedule-btn",
41
+ "ask-mate-btn",
42
+ "context-sources-add",
43
+ "composer-add-worker-btn",
44
+ "composer-handoff-btn",
45
+ ];
46
+
47
+ function lockedControls() {
48
+ var found = [];
49
+ for (var i = 0; i < LOCKED_CONTROL_IDS.length; i++) {
50
+ var el = document.getElementById(LOCKED_CONTROL_IDS[i]);
51
+ if (el) found.push(el);
52
+ }
53
+ return found;
54
+ }
55
+
56
+ // The configured Worker of a live pair, for the session actually on screen.
57
+ // Returns false for an ad-hoc split, for the Driver, and for any ordinary
58
+ // session, so the composer is only ever taken away in the one real case.
59
+ export function isDriverOperatedView(state) {
60
+ var snapshot = state || store.snap();
61
+ var sessionId = snapshot.paneMode && snapshot.paneSessionId
62
+ ? snapshot.paneSessionId
63
+ : snapshot.activeSessionId;
64
+ if (!sessionId) return false;
65
+ var groups = snapshot.splitGroups || [];
66
+ for (var i = 0; i < groups.length; i++) {
67
+ var pair = groups[i] && groups[i].pair;
68
+ if (!pair) continue;
69
+ if (pair.workerId === sessionId) return true;
70
+ }
71
+ return false;
72
+ }
73
+
74
+ function ensureStatusLine(inputArea) {
75
+ var existing = document.getElementById(STATUS_ID);
76
+ if (existing) return existing;
77
+ var line = document.createElement("div");
78
+ line.id = STATUS_ID;
79
+ line.className = "worker-pane-status";
80
+ // A status region, not a live region: it states a standing condition rather
81
+ // than announcing a change, so it should not interrupt.
82
+ line.setAttribute("role", "status");
83
+ var mark = document.createElement("span");
84
+ mark.className = "worker-pane-status-mark";
85
+ mark.setAttribute("aria-hidden", "true");
86
+ var label = document.createElement("span");
87
+ label.className = "worker-pane-status-label";
88
+ label.textContent = "Controlled by Driver";
89
+ line.appendChild(mark);
90
+ line.appendChild(label);
91
+ inputArea.appendChild(line);
92
+ return line;
93
+ }
94
+
95
+ function applyLock(locked) {
96
+ var inputArea = document.getElementById("input-area");
97
+ var inputWrapper = document.getElementById("input-wrapper");
98
+ if (!inputArea) return;
99
+
100
+ document.body.classList.toggle(LOCK_CLASS, locked);
101
+
102
+ if (inputWrapper) {
103
+ // Hidden from assistive tech as well as sight; the status line speaks for
104
+ // the region instead.
105
+ if (locked) inputWrapper.setAttribute("aria-hidden", "true");
106
+ else inputWrapper.removeAttribute("aria-hidden");
107
+ }
108
+
109
+ var controls = lockedControls();
110
+ for (var i = 0; i < controls.length; i++) {
111
+ var el = controls[i];
112
+ if (locked) {
113
+ if (el.dataset.workerLockPrev === undefined) {
114
+ el.dataset.workerLockPrev = el.disabled ? "1" : "0";
115
+ }
116
+ el.disabled = true;
117
+ el.setAttribute("tabindex", "-1");
118
+ } else if (el.dataset.workerLockPrev !== undefined) {
119
+ // Restore exactly what was there before the lock, so an independently
120
+ // disabled send button stays disabled when the pair dissolves.
121
+ el.disabled = el.dataset.workerLockPrev === "1";
122
+ el.removeAttribute("tabindex");
123
+ delete el.dataset.workerLockPrev;
124
+ }
125
+ }
126
+
127
+ var status = document.getElementById(STATUS_ID);
128
+ if (locked) {
129
+ status = ensureStatusLine(inputArea);
130
+ status.classList.add("visible");
131
+ } else if (status) {
132
+ status.classList.remove("visible");
133
+ }
134
+ }
135
+
136
+ // Skip permissions and any other Worker-local permission-mode affordance are
137
+ // the Driver's to set, so they are removed from this pane only. The Driver pane
138
+ // and ordinary sessions are untouched.
139
+ function applyPermissionChrome(locked) {
140
+ var ids = ["skip-perms-pill", "config-chip-wrap"];
141
+ for (var i = 0; i < ids.length; i++) {
142
+ var el = document.getElementById(ids[i]);
143
+ if (!el) continue;
144
+ if (locked) {
145
+ if (el.dataset.workerLockHidden === undefined) {
146
+ el.dataset.workerLockHidden = el.classList.contains("hidden") ? "1" : "0";
147
+ }
148
+ el.classList.add("hidden");
149
+ el.setAttribute("aria-hidden", "true");
150
+ } else if (el.dataset.workerLockHidden !== undefined) {
151
+ if (el.dataset.workerLockHidden === "0") el.classList.remove("hidden");
152
+ el.removeAttribute("aria-hidden");
153
+ delete el.dataset.workerLockHidden;
154
+ }
155
+ }
156
+ }
157
+
158
+ export function syncWorkerPaneLock(state) {
159
+ var locked = isDriverOperatedView(state);
160
+ applyLock(locked);
161
+ applyPermissionChrome(locked);
162
+ return locked;
163
+ }
164
+
165
+ export function initWorkerPaneLock() {
166
+ syncWorkerPaneLock();
167
+ // Pair state is the source of truth, so every input that could change the
168
+ // role re-runs the same resolution: a dissolve, a replacement, a role swap,
169
+ // switching to another session, or a reconnect that reloads the groups.
170
+ store.subscribe(function (state, prev) {
171
+ if (state.splitGroups === prev.splitGroups
172
+ && state.activeSessionId === prev.activeSessionId
173
+ && state.paneSessionId === prev.paneSessionId) return;
174
+ syncWorkerPaneLock(state);
175
+ });
176
+ }
@@ -0,0 +1,16 @@
1
+ export function workerProposalSelection(msg) {
2
+ var hasSelection = typeof msg.selectedVendor === "string" ||
3
+ typeof msg.selectedModel === "string" || typeof msg.selectedEffort === "string";
4
+ return {
5
+ selected: hasSelection,
6
+ vendor: hasSelection ? (msg.selectedVendor || "") : (msg.recommendedVendor || ""),
7
+ model: hasSelection ? (msg.selectedModel || "") : (msg.recommendedModel || ""),
8
+ effort: hasSelection ? (msg.selectedEffort || "") : (msg.recommendedEffort || "medium"),
9
+ };
10
+ }
11
+
12
+ export function syncWorkerProposalSelection(msg, sync) {
13
+ var selection = workerProposalSelection(msg);
14
+ if (selection.selected && typeof sync === "function") sync(selection);
15
+ return selection;
16
+ }
@@ -2,6 +2,7 @@ import { getWs } from './ws-ref.js';
2
2
  import { addToMessages, scrollToBottom, VENDOR_AVATARS, VENDOR_NAMES } from './app-rendering.js';
3
3
  import { effortLevelsFor, effortDisplayName } from './app-panels.js';
4
4
  import { iconHtml, refreshIcons } from './icons.js';
5
+ import { workerProposalSelection, syncWorkerProposalSelection } from './worker-proposal-state.js';
5
6
 
6
7
  function optionValue(entry) {
7
8
  if (typeof entry === "string") return entry;
@@ -27,7 +28,7 @@ function planSteps(plan) {
27
28
  }).filter(Boolean).slice(0, 8);
28
29
  }
29
30
 
30
- function buildVendorSelect(msg) {
31
+ function buildVendorSelect(msg, preferred) {
31
32
  var select = document.createElement("select");
32
33
  select.className = "worker-proposal-select worker-proposal-vendor";
33
34
  var installed = (msg.options && msg.options.installedVendors) || [];
@@ -37,7 +38,7 @@ function buildVendorSelect(msg) {
37
38
  option.textContent = VENDOR_NAMES[installed[i]] || installed[i];
38
39
  select.appendChild(option);
39
40
  }
40
- if (installed.indexOf(msg.recommendedVendor) !== -1) select.value = msg.recommendedVendor;
41
+ if (installed.indexOf(preferred) !== -1) select.value = preferred;
41
42
  return select;
42
43
  }
43
44
 
@@ -98,29 +99,38 @@ function statusLabel(status) {
98
99
  if (status === "interrupted") return "Interrupted";
99
100
  if (status === "declined") return "Continuing here";
100
101
  if (status === "error") return "Needs attention";
101
- return "Suggested by Fable";
102
+ return "Awaiting your choice";
103
+ }
104
+
105
+ function decisionLabel(status, autoAccepted) {
106
+ if (autoAccepted) return "Driver recommendation auto-accepted under Full auto";
107
+ if (status === "pending") return "Runtime choice remains with you";
108
+ if (status === "declined") return "Runtime proposal declined by user";
109
+ return "Runtime configuration accepted by user";
102
110
  }
103
111
 
104
112
  function applyState(card, msg) {
105
113
  var status = msg.status || "pending";
106
114
  card.dataset.status = status;
107
- if (msg.autoApproved) card.dataset.autoApproved = "true";
108
- var autoApproved = card.dataset.autoApproved === "true";
115
+ if (msg.autoAccepted || msg.autoApproved) card.dataset.autoAccepted = "true";
116
+ if (msg.autoAccepted === false || msg.autoApproved === false) card.dataset.autoAccepted = "false";
117
+ var autoAccepted = card.dataset.autoAccepted === "true";
118
+ syncWorkerProposalSelection(msg, card._syncWorkerProposalSelection);
109
119
  var badge = card.querySelector(".worker-proposal-status");
110
- if (badge) badge.textContent = statusLabel(status) + (autoApproved ? " · auto-approved" : "");
120
+ if (badge) badge.textContent = statusLabel(status) + (autoAccepted ? " · auto-accepted" : "");
121
+ var decision = card.querySelector(".worker-proposal-decision");
122
+ if (decision) {
123
+ decision.textContent = decisionLabel(status, autoAccepted);
124
+ decision.classList.toggle("automatic", autoAccepted);
125
+ }
111
126
  var error = card.querySelector(".worker-proposal-error");
112
127
  if (error) {
113
128
  error.textContent = msg.error || "";
114
129
  error.classList.toggle("hidden", !msg.error);
115
130
  }
116
- var preview = card.querySelector(".worker-proposal-result");
117
- if (preview && msg.resultPreview) {
118
- preview.textContent = msg.resultPreview;
119
- preview.classList.remove("hidden");
120
- }
121
131
  setControlsDisabled(card, status !== "pending");
122
132
  var actions = card.querySelector(".worker-proposal-actions");
123
- if (actions) actions.classList.toggle("hidden", autoApproved);
133
+ if (actions) actions.classList.toggle("hidden", autoAccepted);
124
134
  }
125
135
 
126
136
  function sendDecision(card, accepted) {
@@ -150,10 +160,12 @@ export function renderWorkerProposal(msg) {
150
160
  card.className = "worker-proposal-card";
151
161
  card.dataset.proposalId = msg.proposalId;
152
162
  card.dataset.status = msg.status || "pending";
163
+ var runtimeSelection = workerProposalSelection(msg);
153
164
 
154
165
  var header = document.createElement("div");
155
166
  header.className = "worker-proposal-header";
156
- header.innerHTML = '<div class="worker-proposal-mark">' + iconHtml("git-branch") + '</div><div class="worker-proposal-heading"><span class="worker-proposal-kicker">DRIVER / WORKER</span><strong>Let a Worker handle the execution</strong></div><span class="worker-proposal-status"></span>';
167
+ var replacing = msg.action === "replace";
168
+ header.innerHTML = '<div class="worker-proposal-mark">' + iconHtml("git-branch") + '</div><div class="worker-proposal-heading"><span class="worker-proposal-kicker">DRIVER / SPLIT WORKER</span><strong>' + (replacing ? "Configure a replacement Split Worker" : "Configure a Split Worker") + '</strong></div><span class="worker-proposal-status"></span>';
157
169
  card.appendChild(header);
158
170
 
159
171
  var summary = document.createElement("p");
@@ -173,12 +185,24 @@ export function renderWorkerProposal(msg) {
173
185
  card.appendChild(plan);
174
186
  }
175
187
 
188
+ if (msg.recommendationRationale) {
189
+ var rationale = document.createElement("div");
190
+ rationale.className = "worker-proposal-rationale";
191
+ var rationaleLabel = document.createElement("span");
192
+ rationaleLabel.textContent = "Driver's recommendation rationale";
193
+ var rationaleText = document.createElement("p");
194
+ rationaleText.textContent = msg.recommendationRationale;
195
+ rationale.appendChild(rationaleLabel);
196
+ rationale.appendChild(rationaleText);
197
+ card.appendChild(rationale);
198
+ }
199
+
176
200
  var config = document.createElement("div");
177
201
  config.className = "worker-proposal-config";
178
202
  var vendorField = document.createElement("label");
179
203
  vendorField.className = "worker-proposal-field worker-proposal-vendor-field";
180
204
  vendorField.innerHTML = '<span>Worker</span><span class="worker-proposal-vendor-control"><img alt=""></span>';
181
- var vendorSelect = buildVendorSelect(msg);
205
+ var vendorSelect = buildVendorSelect(msg, runtimeSelection.vendor);
182
206
  vendorField.querySelector(".worker-proposal-vendor-control").appendChild(vendorSelect);
183
207
  config.appendChild(vendorField);
184
208
  var modelField = document.createElement("label");
@@ -194,6 +218,10 @@ export function renderWorkerProposal(msg) {
194
218
  config.appendChild(effortField);
195
219
  card.appendChild(config);
196
220
 
221
+ var decision = document.createElement("div");
222
+ decision.className = "worker-proposal-decision";
223
+ card.appendChild(decision);
224
+
197
225
  function syncVendor(preferredModel, preferredEffort) {
198
226
  var vendor = vendorSelect.value;
199
227
  var avatar = vendorField.querySelector("img");
@@ -201,31 +229,32 @@ export function renderWorkerProposal(msg) {
201
229
  fillModels(modelSelect, vendor, msg, preferredModel);
202
230
  fillEffort(card, vendor, modelSelect.value, msg, preferredEffort);
203
231
  }
232
+ card._syncWorkerProposalSelection = function (selection) {
233
+ if (selection.vendor && vendorSelect.value !== selection.vendor) vendorSelect.value = selection.vendor;
234
+ syncVendor(selection.model, selection.effort);
235
+ };
204
236
  vendorSelect.addEventListener("change", function () { syncVendor("", "medium"); });
205
237
  modelSelect.addEventListener("change", function () {
206
238
  var active = card.querySelector(".worker-proposal-effort-btn.active");
207
239
  fillEffort(card, vendorSelect.value, modelSelect.value, msg, active ? active.dataset.effort : "medium");
208
240
  });
209
- syncVendor(msg.recommendedModel || "", msg.recommendedEffort || "medium");
241
+ syncVendor(runtimeSelection.model, runtimeSelection.effort);
210
242
 
211
243
  var error = document.createElement("div");
212
244
  error.className = "worker-proposal-error hidden";
213
245
  card.appendChild(error);
214
- var result = document.createElement("div");
215
- result.className = "worker-proposal-result hidden";
216
- card.appendChild(result);
217
246
 
218
247
  var actions = document.createElement("div");
219
248
  actions.className = "worker-proposal-actions";
220
249
  var decline = document.createElement("button");
221
250
  decline.type = "button";
222
251
  decline.className = "worker-proposal-action secondary";
223
- decline.textContent = "Continue here";
252
+ decline.textContent = replacing ? "Keep current Worker" : "Continue here";
224
253
  decline.addEventListener("click", function () { sendDecision(card, false); });
225
254
  var accept = document.createElement("button");
226
255
  accept.type = "button";
227
256
  accept.className = "worker-proposal-action primary";
228
- accept.innerHTML = iconHtml("panel-right-open") + "<span>Run with Split Worker</span>";
257
+ accept.innerHTML = iconHtml("panel-right-open") + "<span>" + (replacing ? "Replace Split Worker" : "Run with Split Worker") + "</span>";
229
258
  accept.addEventListener("click", function () { sendDecision(card, true); });
230
259
  actions.appendChild(decline);
231
260
  actions.appendChild(accept);
@@ -14,6 +14,7 @@
14
14
  @import url("css/input.css?v=20260828-composer-depth1");
15
15
  @import url("css/filebrowser.css?v=20260829-terminal-canvas1");
16
16
  @import url("css/git-panel.css");
17
+ @import url("css/git-placard.css");
17
18
  @import url("css/diff.css");
18
19
  @import url("css/highlight.css");
19
20
  @import url("css/server-settings.css");
@@ -22,6 +23,7 @@
22
23
  @import url("css/mobile-nav.css?v=20260407");
23
24
  @import url("css/loop.css");
24
25
  @import url("css/scheduler.css");
26
+ @import url("css/project-logs.css");
25
27
  @import url("css/scheduler-modal.css");
26
28
  @import url("css/home-hub.css");
27
29
  @import url("css/home-sidebar.css");
@@ -39,7 +41,7 @@
39
41
  @import url("css/session-search.css");
40
42
  @import url("css/tooltip.css");
41
43
  @import url("css/mates.css?v=20260407");
42
- @import url("css/command-palette.css?v=20260901-clay-chat2");
44
+ @import url("css/command-palette.css?v=20260910-ask-clay-controls1");
43
45
  @import url("css/mention.css");
44
46
  @import url("css/debate.css");
45
47
  @import url("css/home-debate-planning.css");