clay-server 3.4.0-beta.9 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/daemon.js +58 -58
- package/lib/project-connection.js +19 -6
- package/lib/project-models.js +220 -0
- package/lib/project-session-handoff.js +162 -0
- package/lib/project-session-pair.js +14 -7
- package/lib/project-session-spawn.js +1 -1
- package/lib/project-sessions.js +13 -45
- package/lib/project-worker-proposal.js +51 -14
- package/lib/project.js +33 -77
- package/lib/public/app.js +11 -0
- package/lib/public/copilot-avatar.svg +5 -0
- package/lib/public/css/filebrowser.css +6 -1
- package/lib/public/css/input.css +56 -0
- package/lib/public/css/mates.css +6 -0
- package/lib/public/css/menus.css +35 -56
- package/lib/public/css/pane.css +18 -3
- package/lib/public/css/session-actions.css +98 -0
- package/lib/public/grok-avatar.svg +4 -0
- package/lib/public/index.html +26 -9
- package/lib/public/junie-avatar.svg +11 -0
- package/lib/public/kimi-avatar.svg +4 -0
- package/lib/public/modules/agent-config-selects.js +69 -0
- package/lib/public/modules/app-connection.js +6 -0
- package/lib/public/modules/app-header.js +0 -22
- package/lib/public/modules/app-messages.js +48 -9
- package/lib/public/modules/app-panels.js +33 -82
- package/lib/public/modules/app-projects.js +2 -0
- package/lib/public/modules/app-rendering.js +16 -1
- package/lib/public/modules/background-tasks-ui.js +55 -0
- package/lib/public/modules/filebrowser-tabs.js +65 -0
- package/lib/public/modules/filebrowser.js +23 -3
- package/lib/public/modules/mate-sidebar.js +10 -2
- package/lib/public/modules/model-picker.js +263 -0
- package/lib/public/modules/pane-bridge.js +6 -0
- package/lib/public/modules/session-actions.js +294 -0
- package/lib/public/modules/sidebar-mobile.js +2 -1
- package/lib/public/modules/sidebar-sessions.js +17 -1
- package/lib/public/modules/split-pair-ui.js +16 -77
- package/lib/public/modules/split-view.js +11 -1
- package/lib/public/modules/tools.js +5 -0
- package/lib/public/modules/vendor-priority.js +6 -1
- package/lib/public/modules/worker-proposal.js +6 -1
- package/lib/public/qwen-avatar.svg +4 -0
- package/lib/public/style.css +1 -0
- package/lib/sdk-bridge.js +57 -34
- package/lib/sdk-message-processor.js +26 -4
- package/lib/session-handoff-context.js +110 -0
- package/lib/session-notes-mcp-server.js +1 -0
- package/lib/sessions.js +4 -0
- package/lib/ws-schema.js +9 -1
- package/lib/yoke/acp-agent-profiles.js +64 -0
- package/lib/yoke/adapters/acp.js +2 -1
- package/lib/yoke/adapters/claude.js +34 -0
- package/lib/yoke/adapters/codex.js +38 -3
- package/lib/yoke/adapters/copilot.js +7 -0
- package/lib/yoke/adapters/grok.js +7 -0
- package/lib/yoke/adapters/junie.js +7 -0
- package/lib/yoke/adapters/kimi.js +7 -0
- package/lib/yoke/adapters/kiro.js +4 -3
- package/lib/yoke/adapters/qwen.js +7 -0
- package/lib/yoke/codex-background-tasks.js +84 -0
- package/lib/yoke/index.js +43 -14
- package/lib/yoke/instructions.js +3 -0
- package/lib/yoke/interface.js +21 -0
- package/lib/yoke/vendor-registry.js +55 -0
- package/package.json +1 -1
package/lib/project-sessions.js
CHANGED
|
@@ -658,10 +658,6 @@ function attachSessions(ctx) {
|
|
|
658
658
|
// (resume_tui_session), and born-GUI never auto-converts to a terminal.
|
|
659
659
|
resolveSessionForView(xmTarget, ws);
|
|
660
660
|
}
|
|
661
|
-
// If the target session's vendor doesn't own the currently cached
|
|
662
|
-
// model, clear sm.currentModel so the UI and next query don't leak
|
|
663
|
-
// the previous session's vendor-specific model into this one. A
|
|
664
|
-
// session-specific model always wins and survives daemon restarts.
|
|
665
661
|
var switchTargetSess = sm.sessions.get(msg.id);
|
|
666
662
|
var switchTargetVendor = switchTargetSess && (switchTargetSess.vendor || sm.defaultVendor || "claude");
|
|
667
663
|
if (switchTargetSess && !switchTargetSess.effort) {
|
|
@@ -671,22 +667,6 @@ function attachSessions(ctx) {
|
|
|
671
667
|
) || null;
|
|
672
668
|
sm.saveSessionFile(switchTargetSess);
|
|
673
669
|
}
|
|
674
|
-
if (switchTargetSess && switchTargetSess.model) {
|
|
675
|
-
sm.currentModel = switchTargetSess.model;
|
|
676
|
-
} else if (switchTargetSess && sm.currentModel) {
|
|
677
|
-
var targetVendor = switchTargetSess.vendor || sm.defaultVendor || null;
|
|
678
|
-
var tvModels = (targetVendor && sm.modelsByVendor && sm.modelsByVendor[targetVendor]) || [];
|
|
679
|
-
var found = false;
|
|
680
|
-
var _curLc = sm.currentModel.toLowerCase();
|
|
681
|
-
for (var tvi = 0; tvi < tvModels.length; tvi++) {
|
|
682
|
-
var tvEntry = tvModels[tvi];
|
|
683
|
-
var tvVal = typeof tvEntry === "string" ? tvEntry : (tvEntry && (tvEntry.value || tvEntry.id)) || "";
|
|
684
|
-
if (tvVal === sm.currentModel || (tvVal && (tvVal.toLowerCase().indexOf(_curLc) !== -1 || _curLc.indexOf(tvVal.toLowerCase()) !== -1))) { found = true; break; }
|
|
685
|
-
}
|
|
686
|
-
if (tvModels.length > 0 && !found) {
|
|
687
|
-
sm.currentModel = "";
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
670
|
// Check access in multi-user mode
|
|
691
671
|
if (usersModule.isMultiUser() && ws._clayUser) {
|
|
692
672
|
var switchTarget = sm.sessions.get(msg.id);
|
|
@@ -958,14 +938,6 @@ function attachSessions(ctx) {
|
|
|
958
938
|
return true;
|
|
959
939
|
}
|
|
960
940
|
|
|
961
|
-
if (msg.type === "set_model" && msg.model) {
|
|
962
|
-
var session = getSessionForWs(ws);
|
|
963
|
-
if (session) {
|
|
964
|
-
sdk.setModel(session, msg.model);
|
|
965
|
-
}
|
|
966
|
-
return true;
|
|
967
|
-
}
|
|
968
|
-
|
|
969
941
|
if (msg.type === "reload_skills") {
|
|
970
942
|
var session = getSessionForWs(ws);
|
|
971
943
|
if (session && sdk.reloadSkills) {
|
|
@@ -997,11 +969,7 @@ function attachSessions(ctx) {
|
|
|
997
969
|
" is bound to '" + vendorSession.vendor + "', refused rebind to '" + msg.vendor + "'");
|
|
998
970
|
} else {
|
|
999
971
|
vendorSession.vendor = msg.vendor;
|
|
1000
|
-
|
|
1001
|
-
// instead of leaking the previous vendor's model into a fresh session.
|
|
1002
|
-
if (sm.currentModel) {
|
|
1003
|
-
sm.currentModel = "";
|
|
1004
|
-
}
|
|
972
|
+
if (vendorSession.vendor !== msg.vendor) vendorSession.model = "";
|
|
1005
973
|
sm.saveSessionFile(vendorSession);
|
|
1006
974
|
sm.broadcastSessionList();
|
|
1007
975
|
}
|
|
@@ -1012,11 +980,11 @@ function attachSessions(ctx) {
|
|
|
1012
980
|
type: "model_info",
|
|
1013
981
|
model: "",
|
|
1014
982
|
models: vendorModels,
|
|
1015
|
-
vendor: msg.vendor,
|
|
983
|
+
vendor: msg.vendor, sessionId: vendorSession ? vendorSession.localId : null,
|
|
1016
984
|
availableVendors: sm.availableVendors || [],
|
|
1017
985
|
installedVendors: sm.installedVendors || [],
|
|
1018
986
|
});
|
|
1019
|
-
send({ type: "config_state",
|
|
987
|
+
send({ type: "config_state", vendor: null, sessionId: null, mode: sm.currentPermissionMode || "default", effort: sm.currentEffort || "medium", betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
|
|
1020
988
|
}
|
|
1021
989
|
return true;
|
|
1022
990
|
}
|
|
@@ -1052,7 +1020,7 @@ function attachSessions(ctx) {
|
|
|
1052
1020
|
sm.broadcastSessionList();
|
|
1053
1021
|
sdk.setPermissionMode(session, msg.mode);
|
|
1054
1022
|
}
|
|
1055
|
-
send({ type: "config_state",
|
|
1023
|
+
send({ type: "config_state", vendor: null, sessionId: null, mode: sm.currentPermissionMode, effort: sm.currentEffort || "medium", betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
|
|
1056
1024
|
return true;
|
|
1057
1025
|
}
|
|
1058
1026
|
|
|
@@ -1113,7 +1081,7 @@ function attachSessions(ctx) {
|
|
|
1113
1081
|
if (session) {
|
|
1114
1082
|
sdk.setPermissionMode(session, msg.mode);
|
|
1115
1083
|
}
|
|
1116
|
-
send({ type: "config_state",
|
|
1084
|
+
send({ type: "config_state", vendor: null, sessionId: null, mode: sm.currentPermissionMode, effort: sm.currentEffort || "medium", betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
|
|
1117
1085
|
return true;
|
|
1118
1086
|
}
|
|
1119
1087
|
|
|
@@ -1126,7 +1094,7 @@ function attachSessions(ctx) {
|
|
|
1126
1094
|
if (session) {
|
|
1127
1095
|
sdk.setPermissionMode(session, msg.mode);
|
|
1128
1096
|
}
|
|
1129
|
-
send({ type: "config_state",
|
|
1097
|
+
send({ type: "config_state", vendor: null, sessionId: null, mode: sm.currentPermissionMode, effort: sm.currentEffort || "medium", betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
|
|
1130
1098
|
return true;
|
|
1131
1099
|
}
|
|
1132
1100
|
|
|
@@ -1142,7 +1110,7 @@ function attachSessions(ctx) {
|
|
|
1142
1110
|
sdk.setEffort(session, sessionEffort).catch(function (e) {
|
|
1143
1111
|
sendTo(ws, { type: "error", text: "Failed to change reasoning effort: " + (e.message || e) });
|
|
1144
1112
|
});
|
|
1145
|
-
sm.sendToSession(session, { type: "config_state", model: session.model || sm.
|
|
1113
|
+
sm.sendToSession(session, { type: "config_state", model: session.model || ((sm.defaultModelByVendor || {})[effortVendor]) || "", vendor: effortVendor, sessionId: session.localId, mode: session.permissionMode || sm.currentPermissionMode || "default", effort: sessionEffort, betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
|
|
1146
1114
|
}
|
|
1147
1115
|
return true;
|
|
1148
1116
|
}
|
|
@@ -1152,7 +1120,7 @@ function attachSessions(ctx) {
|
|
|
1152
1120
|
opts.onSetServerDefaultEffort(msg.effort);
|
|
1153
1121
|
}
|
|
1154
1122
|
sm.currentEffort = msg.effort;
|
|
1155
|
-
send({ type: "config_state",
|
|
1123
|
+
send({ type: "config_state", vendor: null, sessionId: null, mode: sm.currentPermissionMode || "default", effort: sm.currentEffort, betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
|
|
1156
1124
|
return true;
|
|
1157
1125
|
}
|
|
1158
1126
|
|
|
@@ -1161,20 +1129,20 @@ function attachSessions(ctx) {
|
|
|
1161
1129
|
opts.onSetProjectDefaultEffort(slug, msg.effort);
|
|
1162
1130
|
}
|
|
1163
1131
|
sm.currentEffort = msg.effort;
|
|
1164
|
-
send({ type: "config_state",
|
|
1132
|
+
send({ type: "config_state", vendor: null, sessionId: null, mode: sm.currentPermissionMode || "default", effort: sm.currentEffort, betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
|
|
1165
1133
|
return true;
|
|
1166
1134
|
}
|
|
1167
1135
|
|
|
1168
1136
|
if (msg.type === "set_betas") {
|
|
1169
1137
|
sm.currentBetas = msg.betas || [];
|
|
1170
|
-
send({ type: "config_state",
|
|
1138
|
+
send({ type: "config_state", vendor: null, sessionId: null, mode: sm.currentPermissionMode || "default", effort: sm.currentEffort || "medium", betas: sm.currentBetas, thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
|
|
1171
1139
|
return true;
|
|
1172
1140
|
}
|
|
1173
1141
|
|
|
1174
1142
|
if (msg.type === "set_thinking") {
|
|
1175
1143
|
sm.currentThinking = msg.thinking || "adaptive";
|
|
1176
1144
|
if (msg.budgetTokens) sm.currentThinkingBudget = msg.budgetTokens;
|
|
1177
|
-
send({ type: "config_state",
|
|
1145
|
+
send({ type: "config_state", vendor: null, sessionId: null, mode: sm.currentPermissionMode || "default", effort: sm.currentEffort || "medium", betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
|
|
1178
1146
|
return true;
|
|
1179
1147
|
}
|
|
1180
1148
|
|
|
@@ -1456,7 +1424,7 @@ function attachSessions(ctx) {
|
|
|
1456
1424
|
if (decision === "allow_accept_edits") {
|
|
1457
1425
|
sdk.setPermissionMode(session, "acceptEdits");
|
|
1458
1426
|
sm.currentPermissionMode = "acceptEdits";
|
|
1459
|
-
send({ type: "config_state",
|
|
1427
|
+
send({ type: "config_state", vendor: null, sessionId: null, mode: sm.currentPermissionMode, effort: sm.currentEffort || "medium", betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
|
|
1460
1428
|
pending.resolve({ behavior: "allow", updatedInput: pending.toolInput });
|
|
1461
1429
|
sm.sendAndRecord(session, { type: "permission_resolved", requestId: requestId, decision: decision });
|
|
1462
1430
|
return true;
|
|
@@ -1485,7 +1453,7 @@ function attachSessions(ctx) {
|
|
|
1485
1453
|
|
|
1486
1454
|
// Update permission mode for the new session
|
|
1487
1455
|
sm.currentPermissionMode = "acceptEdits";
|
|
1488
|
-
send({ type: "config_state",
|
|
1456
|
+
send({ type: "config_state", vendor: null, sessionId: null, mode: sm.currentPermissionMode, effort: sm.currentEffort || "medium", betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
|
|
1489
1457
|
|
|
1490
1458
|
// Build prompt from plan content (sent from client) or plan file path
|
|
1491
1459
|
var clientPlanContent = msg.planContent || "";
|
|
@@ -40,7 +40,7 @@ function attachWorkerProposal(ctx) {
|
|
|
40
40
|
function isEligible(session) {
|
|
41
41
|
if (ctx.isMate || !session || session.mode === "tui") return false;
|
|
42
42
|
if (store.groupForMember(session.localId)) return false;
|
|
43
|
-
return isFableSession(session, sm.modelsByVendor, sm.
|
|
43
|
+
return isFableSession(session, sm.modelsByVendor, (sm.defaultModelByVendor || {})[session.vendor || "claude"]);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
function safeModelsByVendor(installed) {
|
|
@@ -112,7 +112,7 @@ function attachWorkerProposal(ctx) {
|
|
|
112
112
|
}
|
|
113
113
|
var models = options.modelsByVendor[vendor] || [];
|
|
114
114
|
var model = modelIsAvailable(options, vendor, args.recommendedModel) ? (args.recommendedModel || "") : "";
|
|
115
|
-
var currentModel = session.model || sm.
|
|
115
|
+
var currentModel = session.model || (sm.defaultModelByVendor || {})[session.vendor || "claude"] || "";
|
|
116
116
|
if (vendor === session.vendor && model === currentModel) model = "";
|
|
117
117
|
if (!model && models.length > 0) {
|
|
118
118
|
for (var j = 0; j < models.length; j++) {
|
|
@@ -154,6 +154,18 @@ function attachWorkerProposal(ctx) {
|
|
|
154
154
|
}, patch));
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
function skipPermissionsEnabled(session) {
|
|
158
|
+
return !!session && (session.permissionMode === "bypassPermissions" || session.dangerouslySkipPermissions === true);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function autoApprovalWs(session) {
|
|
162
|
+
return {
|
|
163
|
+
_clayActiveSession: session.localId,
|
|
164
|
+
_clayUser: session.ownerId ? { id: session.ownerId } : null,
|
|
165
|
+
_autoApproval: true,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
157
169
|
async function propose(args, session) {
|
|
158
170
|
if (!isEligible(session)) return toolResult({ error: "Worker suggestions are only available in an unpaired Fable session." });
|
|
159
171
|
if (hasPendingProposal(session)) return toolResult({ error: "A Worker suggestion is already awaiting a decision." });
|
|
@@ -166,6 +178,7 @@ function attachWorkerProposal(ctx) {
|
|
|
166
178
|
var options = proposalOptions();
|
|
167
179
|
if (options.installedVendors.length === 0) return toolResult({ error: "No coding agent is installed for a Worker session." });
|
|
168
180
|
var recommendation = chooseRecommendation(args, session, options);
|
|
181
|
+
var autoApprove = skipPermissionsEnabled(session);
|
|
169
182
|
var proposal = {
|
|
170
183
|
type: "worker_proposal",
|
|
171
184
|
proposalId: "worker_" + crypto.randomUUID(),
|
|
@@ -178,7 +191,22 @@ function attachWorkerProposal(ctx) {
|
|
|
178
191
|
recommendedEffort: recommendation.effort,
|
|
179
192
|
options: options,
|
|
180
193
|
};
|
|
194
|
+
if (autoApprove) proposal.autoApproved = true;
|
|
181
195
|
sm.sendAndRecord(session, proposal);
|
|
196
|
+
if (autoApprove) {
|
|
197
|
+
var accepted = await acceptProposal(session, proposal, {
|
|
198
|
+
vendor: recommendation.vendor,
|
|
199
|
+
model: recommendation.model,
|
|
200
|
+
effort: recommendation.effort,
|
|
201
|
+
autoApproved: true,
|
|
202
|
+
}, autoApprovalWs(session));
|
|
203
|
+
if (!accepted.ok) return toolResult({ error: accepted.error || "Could not start the Worker." });
|
|
204
|
+
return toolResult({
|
|
205
|
+
status: "running",
|
|
206
|
+
proposalId: proposal.proposalId,
|
|
207
|
+
instruction: "The Worker was auto-approved and started because skip permissions is enabled. Its result will return for review.",
|
|
208
|
+
});
|
|
209
|
+
}
|
|
182
210
|
return toolResult({
|
|
183
211
|
status: "posted",
|
|
184
212
|
proposalId: proposal.proposalId,
|
|
@@ -220,6 +248,8 @@ function attachWorkerProposal(ctx) {
|
|
|
220
248
|
var followup;
|
|
221
249
|
if (result.status === "complete") {
|
|
222
250
|
followup = "[Worker execution completed]\nReview and verify the Worker's result. The Worker session remains available: if the implementation needs corrections or additional edits, send a follow-up with send_to_partner instead of taking over the Worker-owned files yourself. If that Worker is no longer available, create a replacement with spawn_sessions for the remaining implementation.\n\n" + (result.response || "The Worker completed without a text summary.");
|
|
251
|
+
} else if (result.status === "interrupted") {
|
|
252
|
+
followup = "[Worker execution interrupted]\nThe user interrupted the Worker mid-turn. Its work is PARTIAL and unverified — do not treat it as finished. Review what was done and decide next steps with the user.";
|
|
223
253
|
} else if (result.status === "running") {
|
|
224
254
|
followup = "[Worker execution is still running]\nUse read_partner to inspect progress before completing the task.";
|
|
225
255
|
} else {
|
|
@@ -238,30 +268,24 @@ function attachWorkerProposal(ctx) {
|
|
|
238
268
|
return session;
|
|
239
269
|
}
|
|
240
270
|
|
|
241
|
-
async function
|
|
242
|
-
var session = sessionForResponse(ws);
|
|
243
|
-
var proposal = findProposal(session, msg.proposalId);
|
|
244
|
-
if (!proposal) throw new Error("Worker suggestion not found");
|
|
245
|
-
if (proposal.status !== "pending") throw new Error("Worker suggestion has already been resolved");
|
|
246
|
-
if (!msg.accepted) {
|
|
247
|
-
updateProposal(session, proposal, { status: "declined" });
|
|
248
|
-
await resumeDriver(session, "[Worker suggestion declined]\nContinue this task in the current session using the plan you already prepared.");
|
|
249
|
-
return { ok: true, status: "declined" };
|
|
250
|
-
}
|
|
271
|
+
async function acceptProposal(session, proposal, msg, ws) {
|
|
251
272
|
var options = proposal.options || proposalOptions();
|
|
252
273
|
var vendor = msg.vendor || proposal.recommendedVendor;
|
|
253
274
|
var model = msg.model || "";
|
|
254
275
|
if (options.installedVendors.indexOf(vendor) === -1) throw new Error("Selected Worker vendor is not installed");
|
|
255
276
|
if (!modelIsAvailable(options, vendor, model)) throw new Error("Selected Worker model is unavailable");
|
|
256
277
|
var effort = yoke.clampEffort(vendor, msg.effort || proposal.recommendedEffort || "medium") || "";
|
|
257
|
-
|
|
278
|
+
var startingPatch = { status: "starting", selectedVendor: vendor, selectedModel: model, selectedEffort: effort };
|
|
279
|
+
if (msg.autoApproved) startingPatch.autoApproved = true;
|
|
280
|
+
updateProposal(session, proposal, startingPatch);
|
|
258
281
|
try {
|
|
259
282
|
var created = ctx.createPairRecord(ws, {
|
|
260
283
|
driver: { sessionId: session.localId },
|
|
261
284
|
worker: { vendor: vendor, model: model, effort: effort },
|
|
262
285
|
});
|
|
263
286
|
updateProposal(session, proposal, { status: "running", groupId: created.group.id, workerId: created.worker.localId });
|
|
264
|
-
|
|
287
|
+
if (ws._autoApproval) sm.sendToSession(session, { type: "pair_session_created", ok: true, group: created.group });
|
|
288
|
+
else ctx.sendTo(ws, { type: "pair_session_created", ok: true, group: created.group });
|
|
265
289
|
runWorker(session, proposal).catch(function (err) {
|
|
266
290
|
updateProposal(session, proposal, { status: "error", error: err.message || String(err) });
|
|
267
291
|
resumeDriver(session, "[Worker execution failed]\n" + (err.message || String(err))).catch(function () {});
|
|
@@ -273,6 +297,19 @@ function attachWorkerProposal(ctx) {
|
|
|
273
297
|
}
|
|
274
298
|
}
|
|
275
299
|
|
|
300
|
+
async function respondToProposal(ws, msg) {
|
|
301
|
+
var session = sessionForResponse(ws);
|
|
302
|
+
var proposal = findProposal(session, msg.proposalId);
|
|
303
|
+
if (!proposal) throw new Error("Worker suggestion not found");
|
|
304
|
+
if (proposal.status !== "pending") throw new Error("Worker suggestion has already been resolved");
|
|
305
|
+
if (!msg.accepted) {
|
|
306
|
+
updateProposal(session, proposal, { status: "declined" });
|
|
307
|
+
await resumeDriver(session, "[Worker suggestion declined]\nContinue this task in the current session using the plan you already prepared.");
|
|
308
|
+
return { ok: true, status: "declined" };
|
|
309
|
+
}
|
|
310
|
+
return acceptProposal(session, proposal, msg, ws);
|
|
311
|
+
}
|
|
312
|
+
|
|
276
313
|
function handleMessage(ws, msg) {
|
|
277
314
|
if (msg.type !== "worker_proposal_response") return false;
|
|
278
315
|
respondToProposal(ws, msg).catch(function (err) {
|
package/lib/project.js
CHANGED
|
@@ -27,6 +27,7 @@ var { attachImage } = require("./project-image");
|
|
|
27
27
|
var { attachKnowledge } = require("./project-knowledge");
|
|
28
28
|
var { attachFilesystem } = require("./project-filesystem");
|
|
29
29
|
var { attachSessions } = require("./project-sessions");
|
|
30
|
+
var { attachModels } = require("./project-models");
|
|
30
31
|
var { attachUserMessage } = require("./project-user-message");
|
|
31
32
|
var { attachShellCommand } = require("./project-shell-command");
|
|
32
33
|
var { attachConnection } = require("./project-connection");
|
|
@@ -35,6 +36,7 @@ var { createLocalMcp } = require("./mcp-local");
|
|
|
35
36
|
var { attachEmail: attachEmailModule } = require("./project-email");
|
|
36
37
|
var { attachSessionSpawn } = require("./project-session-spawn");
|
|
37
38
|
var { attachSessionPair } = require("./project-session-pair");
|
|
39
|
+
var { attachSessionHandoff } = require("./project-session-handoff");
|
|
38
40
|
var { attachSessionNotes, composeSystemPrompts } = require("./project-session-notes");
|
|
39
41
|
var { attachSessionDocument } = require("./project-session-document");
|
|
40
42
|
var { attachSplitGroups } = require("./session-split-groups");
|
|
@@ -470,10 +472,9 @@ function createProjectContext(opts) {
|
|
|
470
472
|
|
|
471
473
|
var _projModel = typeof opts.onGetProjectDefaultModel === "function" ? opts.onGetProjectDefaultModel(slug) : null;
|
|
472
474
|
var _srvModel = typeof opts.onGetServerDefaultModel === "function" ? opts.onGetServerDefaultModel() : null;
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
if (sm._savedDefaultModel) sm.currentModel = sm._savedDefaultModel;
|
|
475
|
+
var savedDefaultModel = (_projModel && _projModel.model) || (_srvModel && _srvModel.model) || null;
|
|
476
|
+
sm.defaultModelByVendor = sm.defaultModelByVendor || {};
|
|
477
|
+
if (savedDefaultModel) sm.defaultModelByVendor[sm.defaultVendor || "claude"] = savedDefaultModel;
|
|
477
478
|
|
|
478
479
|
// --- Local MCP (direct process management for localhost clients) ---
|
|
479
480
|
var _localMcp = createLocalMcp();
|
|
@@ -569,6 +570,18 @@ function createProjectContext(opts) {
|
|
|
569
570
|
getLinuxUserForSession: getLinuxUserForSession,
|
|
570
571
|
getPairToolDefs: function (boundSession) { return _sessionPair.getToolDefs(boundSession); },
|
|
571
572
|
});
|
|
573
|
+
var _sessionHandoff = attachSessionHandoff({
|
|
574
|
+
cwd: cwd,
|
|
575
|
+
sm: sm,
|
|
576
|
+
isMate: isMate,
|
|
577
|
+
splitStore: _splitGroups.store,
|
|
578
|
+
getSdk: function () { return sdk; },
|
|
579
|
+
sendTo: sendTo,
|
|
580
|
+
usersModule: usersModule,
|
|
581
|
+
adapters: adapters,
|
|
582
|
+
getLinuxUserForSession: getLinuxUserForSession,
|
|
583
|
+
onProcessingChanged: onProcessingChanged,
|
|
584
|
+
});
|
|
572
585
|
var _debate = null;
|
|
573
586
|
var _debateProposal = attachDebateProposal({
|
|
574
587
|
cwd: cwd,
|
|
@@ -1053,79 +1066,6 @@ function createProjectContext(opts) {
|
|
|
1053
1066
|
return;
|
|
1054
1067
|
}
|
|
1055
1068
|
|
|
1056
|
-
// --- Vendor model switching ---
|
|
1057
|
-
if (msg.type === "get_vendor_models") {
|
|
1058
|
-
(async function() {
|
|
1059
|
-
if (msg.vendor) {
|
|
1060
|
-
try {
|
|
1061
|
-
var modelLinuxUser = getLinuxUserForWs(ws);
|
|
1062
|
-
var vendorAdapter = adapters[msg.vendor] || null;
|
|
1063
|
-
if (!vendorAdapter) {
|
|
1064
|
-
vendorAdapter = await yoke.lazyCreateAdapter(adapters, msg.vendor, {
|
|
1065
|
-
cwd: cwd,
|
|
1066
|
-
linuxUser: modelLinuxUser || undefined,
|
|
1067
|
-
clayPort: serverPort,
|
|
1068
|
-
clayTls: serverTls,
|
|
1069
|
-
clayAuthToken: serverAuthToken,
|
|
1070
|
-
slug: slug,
|
|
1071
|
-
});
|
|
1072
|
-
}
|
|
1073
|
-
var needsReadyMetadata = !sm.capabilitiesByVendor || !sm.capabilitiesByVendor[msg.vendor]
|
|
1074
|
-
|| !sm.modelsByVendor || !sm.modelsByVendor[msg.vendor];
|
|
1075
|
-
if (vendorAdapter && needsReadyMetadata && typeof vendorAdapter.init === "function") {
|
|
1076
|
-
// Init warms the adapter, but a slow/failed init must not block
|
|
1077
|
-
// model listing (e.g. Codex models are a fixed list). Keep going
|
|
1078
|
-
// to supportedModels() even if init throws.
|
|
1079
|
-
try {
|
|
1080
|
-
var readyResult = await vendorAdapter.init({
|
|
1081
|
-
cwd: cwd,
|
|
1082
|
-
linuxUser: modelLinuxUser || undefined,
|
|
1083
|
-
clayPort: serverPort,
|
|
1084
|
-
clayTls: serverTls,
|
|
1085
|
-
clayAuthToken: serverAuthToken,
|
|
1086
|
-
slug: slug,
|
|
1087
|
-
});
|
|
1088
|
-
sm.capabilitiesByVendor = sm.capabilitiesByVendor || {};
|
|
1089
|
-
sm.capabilitiesByVendor[msg.vendor] = readyResult.capabilities || {};
|
|
1090
|
-
sm.modelsByVendor = sm.modelsByVendor || {};
|
|
1091
|
-
if (Array.isArray(readyResult.models)) sm.modelsByVendor[msg.vendor] = readyResult.models;
|
|
1092
|
-
} catch (e) {
|
|
1093
|
-
console.error("[project] " + msg.vendor + " init failed (continuing to model list):", e.message || e);
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
if (vendorAdapter) {
|
|
1097
|
-
sm.availableVendors = Object.keys(adapters);
|
|
1098
|
-
sm.modelsByVendor = sm.modelsByVendor || {};
|
|
1099
|
-
if (!sm.modelsByVendor[msg.vendor] && typeof vendorAdapter.supportedModels === "function") {
|
|
1100
|
-
sm.modelsByVendor[msg.vendor] = await vendorAdapter.supportedModels();
|
|
1101
|
-
}
|
|
1102
|
-
}
|
|
1103
|
-
} catch (e) {
|
|
1104
|
-
console.error("[project] get_vendor_models lazy init failed for " + msg.vendor + ":", e.message || e);
|
|
1105
|
-
}
|
|
1106
|
-
}
|
|
1107
|
-
var vendorModels = (sm.modelsByVendor && sm.modelsByVendor[msg.vendor]) || [];
|
|
1108
|
-
var firstModel = vendorModels[0] || "";
|
|
1109
|
-
// model value can be string or {value, displayName} object
|
|
1110
|
-
var defaultModel = typeof firstModel === "string" ? firstModel : (firstModel.value || "");
|
|
1111
|
-
// Preserve the user's current model selection if it belongs to this
|
|
1112
|
-
// vendor, rather than always snapping back to the vendor's default.
|
|
1113
|
-
var modelToSend = defaultModel;
|
|
1114
|
-
if (sm.currentModel) {
|
|
1115
|
-
for (var mi = 0; mi < vendorModels.length; mi++) {
|
|
1116
|
-
var mv = typeof vendorModels[mi] === "string" ? vendorModels[mi] : (vendorModels[mi].value || "");
|
|
1117
|
-
if (mv === sm.currentModel) {
|
|
1118
|
-
modelToSend = sm.currentModel;
|
|
1119
|
-
break;
|
|
1120
|
-
}
|
|
1121
|
-
}
|
|
1122
|
-
}
|
|
1123
|
-
var vendorCapabilities = (sm.capabilitiesByVendor && sm.capabilitiesByVendor[msg.vendor]) || {};
|
|
1124
|
-
sendTo(ws, { type: "model_info", model: modelToSend, models: vendorModels, vendor: msg.vendor, capabilities: vendorCapabilities, availableVendors: sm.availableVendors || [], installedVendors: sm.installedVendors || [] });
|
|
1125
|
-
})();
|
|
1126
|
-
return;
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
1069
|
// --- Debate ---
|
|
1130
1070
|
if (msg.type === "debate_start") {
|
|
1131
1071
|
handleDebateStart(ws, msg);
|
|
@@ -1173,7 +1113,9 @@ function createProjectContext(opts) {
|
|
|
1173
1113
|
|
|
1174
1114
|
// --- Sessions, config, project mgmt (delegated to project-sessions.js) ---
|
|
1175
1115
|
if (_sessionPair.handleMessage(ws, msg)) return;
|
|
1116
|
+
if (_sessionHandoff.handleMessage(ws, msg)) return;
|
|
1176
1117
|
if (_splitGroups.handleMessage(ws, msg)) return;
|
|
1118
|
+
if (_models.handleMessage(ws, msg)) return;
|
|
1177
1119
|
if (_sessions.handleSessionsMessage(ws, msg)) return;
|
|
1178
1120
|
|
|
1179
1121
|
// --- Filesystem, settings, env (delegated to project-filesystem.js) ---
|
|
@@ -1386,6 +1328,20 @@ function createProjectContext(opts) {
|
|
|
1386
1328
|
_notifications: _notifications,
|
|
1387
1329
|
});
|
|
1388
1330
|
|
|
1331
|
+
var _models = attachModels({
|
|
1332
|
+
cwd: cwd,
|
|
1333
|
+
slug: slug,
|
|
1334
|
+
sm: sm,
|
|
1335
|
+
sdk: sdk,
|
|
1336
|
+
adapters: adapters,
|
|
1337
|
+
sendTo: sendTo,
|
|
1338
|
+
getSessionForWs: getSessionForWs,
|
|
1339
|
+
getLinuxUserForWs: getLinuxUserForWs,
|
|
1340
|
+
serverPort: serverPort,
|
|
1341
|
+
serverTls: serverTls,
|
|
1342
|
+
serverAuthToken: serverAuthToken,
|
|
1343
|
+
});
|
|
1344
|
+
|
|
1389
1345
|
// --- User message handler (delegated to project-user-message.js) ---
|
|
1390
1346
|
var _userMessage = attachUserMessage({
|
|
1391
1347
|
cwd: cwd,
|
package/lib/public/app.js
CHANGED
|
@@ -60,10 +60,12 @@ import { initRateLimit, handleRateLimitEvent as _rlHandleRateLimitEvent, updateR
|
|
|
60
60
|
import { initCursors, handleRemoteCursorMove as _curHandleRemoteCursorMove, handleRemoteCursorLeave as _curHandleRemoteCursorLeave, handleRemoteSelection as _curHandleRemoteSelection, clearRemoteCursors as _curClearRemoteCursors, initCursorToggle } from './modules/app-cursors.js';
|
|
61
61
|
import { initFavicon, updateFavicon as _favUpdateFavicon, setSendBtnMode as _favSetSendBtnMode, blinkIO as _favBlinkIO, blinkSessionDot as _favBlinkSessionDot, updateCrossProjectBlink as _favUpdateCrossProjectBlink, startUrgentBlink as _favStartUrgentBlink, stopUrgentBlink as _favStopUrgentBlink, setActivity as _favSetActivity, drawFaviconAnimFrame as _favDrawFaviconAnimFrame } from './modules/app-favicon.js';
|
|
62
62
|
import { initHeader, closeSessionInfoPopover as _hdrCloseSessionInfoPopover, updateHistorySentinel as _hdrUpdateHistorySentinel, requestMoreHistory as _hdrRequestMoreHistory, prependOlderHistory as _hdrPrependOlderHistory } from './modules/app-header.js';
|
|
63
|
+
import { initSessionActions } from './modules/session-actions.js';
|
|
63
64
|
import { initMisc, flushPendingExtMessages, showImageModal as _miscShowImageModal, closeImageModal as _miscCloseImageModal, showPasteModal as _miscShowPasteModal, closePasteModal as _miscClosePasteModal, showConfirm as _miscShowConfirm, hideConfirm as _miscHideConfirm, showForceChangePinOverlay as _miscShowForceChangePinOverlay, sendExtensionCommand as _miscSendExtensionCommand, handleExtensionResult as _miscHandleExtensionResult } from './modules/app-misc.js';
|
|
64
65
|
import { initSkillInstall, requireSkills as _siRequireSkills, requireClayMateInterview as _siRequireClayMateInterview, handleSkillInstallWs as _siHandleSkillInstallWs } from './modules/app-skills-install.js';
|
|
65
66
|
import { initDebateUi, showDebateConcludeConfirm as _debShowDebateConcludeConfirm, exitDebateConcludeMode as _debExitDebateConcludeMode, handleDebateConcludeSend as _debHandleDebateConcludeSend, showDebateEndedMode as _debShowDebateEndedMode, exitDebateEndedMode as _debExitDebateEndedMode, showDebateUserFloor as _debShowDebateUserFloor, exitDebateFloorMode as _debExitDebateFloorMode, handleDebateFloorSend as _debHandleDebateFloorSend, renderDebateUserFloorDone as _debRenderDebateUserFloorDone, showDebateSticky as _debShowDebateSticky, showDebateBottomBar as _debShowDebateBottomBar, removeDebateBottomBar as _debRemoveDebateBottomBar, sendDebateStickyComment as _debSendDebateStickyComment, updateDebateRound as _debUpdateDebateRound } from './modules/app-debate-ui.js';
|
|
66
67
|
import { initLoopUi, updateLoopInputVisibility as _loopUpdateLoopInputVisibility, updateLoopButton as _loopUpdateLoopButton, showLoopBanner as _loopShowLoopBanner, updateLoopBanner as _loopUpdateLoopBanner, updateRalphBars as _loopUpdateRalphBars, showRalphCraftingBar as _loopShowRalphCraftingBar, showRalphApprovalBar as _loopShowRalphApprovalBar, updateRalphApprovalStatus as _loopUpdateRalphApprovalStatus, openRalphPreviewModal as _loopOpenRalphPreviewModal, showExecModal as _loopShowExecModal, closeExecModal as _loopCloseExecModal, updateExecModalStatus as _loopUpdateExecModalStatus } from './modules/app-loop-ui.js';
|
|
68
|
+
import { initBackgroundTasksUi } from './modules/background-tasks-ui.js';
|
|
67
69
|
import { initLoopWizard, openRalphWizard as _loopOpenRalphWizard, closeRalphWizard as _loopCloseRalphWizard, getWizardSource as _loopGetWizardSource } from './modules/app-loop-wizard.js';
|
|
68
70
|
import { initAppNotifications, handleNotificationsState as _notifHandleState, handleNotificationCreated as _notifHandleCreated, handleNotificationDismissed as _notifHandleDismissed, handleNotificationDismissedAll as _notifHandleDismissedAll } from './modules/app-notifications.js';
|
|
69
71
|
import { initWhatsNew, handleWhatsNewState as _wnHandleState, handleWhatsNewSeenResult as _wnHandleSeenResult } from './modules/whats-new.js';
|
|
@@ -295,6 +297,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
295
297
|
slashCommands: [],
|
|
296
298
|
processing: false,
|
|
297
299
|
activeSessionId: null,
|
|
300
|
+
activeBackgroundTasks: [],
|
|
298
301
|
sessionVendorBound: false,
|
|
299
302
|
cliSessionId: null,
|
|
300
303
|
isHeadlessMode: false,
|
|
@@ -339,6 +342,12 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
339
342
|
markdownSlideLevelExplicit: false,
|
|
340
343
|
currentModel: "",
|
|
341
344
|
currentModels: [],
|
|
345
|
+
modelListVendor: "",
|
|
346
|
+
modelListStatus: "idle",
|
|
347
|
+
modelListError: "",
|
|
348
|
+
modelRequestId: "",
|
|
349
|
+
modelSelectionPending: null,
|
|
350
|
+
modelSelectionError: "",
|
|
342
351
|
// Project's last-used vendor; seeds the sidebar's "New session" button.
|
|
343
352
|
lastVendor: "",
|
|
344
353
|
// Static adapter metadata sent before any vendor is initialized.
|
|
@@ -633,6 +642,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
633
642
|
|
|
634
643
|
// --- Header module (rename, info popover, history) ---
|
|
635
644
|
initHeader();
|
|
645
|
+
initSessionActions();
|
|
636
646
|
|
|
637
647
|
// --- Skill Install module ---
|
|
638
648
|
initSkillInstall();
|
|
@@ -641,6 +651,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
641
651
|
initDebateUi();
|
|
642
652
|
|
|
643
653
|
initLoopUi();
|
|
654
|
+
initBackgroundTasksUi();
|
|
644
655
|
initLoopWizard();
|
|
645
656
|
|
|
646
657
|
// --- Notifications module ---
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="GitHub Copilot CLI">
|
|
2
|
+
<rect width="64" height="64" rx="14" fill="#24292f"/>
|
|
3
|
+
<path d="M15 25c0-8 7-14 17-14s17 6 17 14v15c0 8-7 13-17 13s-17-5-17-13zm8 1v13c0 4 4 7 9 7s9-3 9-7V26c0-4-4-7-9-7s-9 3-9 7z" fill="#fff"/>
|
|
4
|
+
<circle cx="27" cy="30" r="3" fill="#24292f"/><circle cx="37" cy="30" r="3" fill="#24292f"/>
|
|
5
|
+
</svg>
|
|
@@ -960,7 +960,7 @@
|
|
|
960
960
|
width: 50%;
|
|
961
961
|
max-width: 720px;
|
|
962
962
|
min-width: 360px;
|
|
963
|
-
border-
|
|
963
|
+
border-right: 1px solid var(--border);
|
|
964
964
|
background: var(--bg);
|
|
965
965
|
display: flex;
|
|
966
966
|
flex-direction: column;
|
|
@@ -1011,6 +1011,11 @@
|
|
|
1011
1011
|
min-height: 44px;
|
|
1012
1012
|
}
|
|
1013
1013
|
|
|
1014
|
+
.file-viewer-tabs { display: flex; min-width: 0; flex: 1; overflow-x: auto; gap: 2px; }
|
|
1015
|
+
.file-viewer-tab { display: inline-flex; align-items: center; gap: 6px; max-width: 170px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; border: 0; border-radius: 5px; padding: 4px 7px; background: transparent; color: var(--text-secondary); font: inherit; font-size: 12px; cursor: pointer; }
|
|
1016
|
+
.file-viewer-tab.active { background: var(--sidebar-hover); color: var(--text); }
|
|
1017
|
+
.file-viewer-tab-close { font-size: 16px; line-height: 12px; color: var(--text-muted); }
|
|
1018
|
+
|
|
1014
1019
|
.file-viewer-path {
|
|
1015
1020
|
flex: 1;
|
|
1016
1021
|
font-family: "Roboto Mono", monospace;
|
package/lib/public/css/input.css
CHANGED
|
@@ -667,6 +667,50 @@
|
|
|
667
667
|
position: relative;
|
|
668
668
|
}
|
|
669
669
|
|
|
670
|
+
.background-tasks-bar {
|
|
671
|
+
max-width: var(--content-width);
|
|
672
|
+
margin: 0 auto 8px;
|
|
673
|
+
border: 1px solid var(--border);
|
|
674
|
+
border-radius: 8px;
|
|
675
|
+
background: var(--input-bg);
|
|
676
|
+
overflow: hidden;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
.background-tasks-toggle {
|
|
680
|
+
width: 100%;
|
|
681
|
+
display: flex;
|
|
682
|
+
align-items: center;
|
|
683
|
+
gap: 7px;
|
|
684
|
+
padding: 8px 10px;
|
|
685
|
+
border: 0;
|
|
686
|
+
background: transparent;
|
|
687
|
+
color: var(--text-secondary);
|
|
688
|
+
cursor: pointer;
|
|
689
|
+
font: inherit;
|
|
690
|
+
text-align: left;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
.background-tasks-chip, .session-background-task-count {
|
|
694
|
+
display: inline-flex;
|
|
695
|
+
align-items: center;
|
|
696
|
+
justify-content: center;
|
|
697
|
+
min-width: 16px;
|
|
698
|
+
height: 16px;
|
|
699
|
+
padding: 0 4px;
|
|
700
|
+
border-radius: 8px;
|
|
701
|
+
background: var(--accent);
|
|
702
|
+
color: var(--bg);
|
|
703
|
+
font-size: 10px;
|
|
704
|
+
font-weight: 700;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
.background-tasks-list { padding: 0 10px 8px; }
|
|
708
|
+
.background-task-row { display: flex; align-items: center; gap: 8px; padding: 6px 0; border-top: 1px solid var(--border-subtle); }
|
|
709
|
+
.background-task-description { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
710
|
+
.background-task-type { padding: 2px 6px; border-radius: 5px; background: var(--code-bg); color: var(--text-muted); font-size: 11px; }
|
|
711
|
+
.background-task-stop { border: 1px solid var(--border); border-radius: 5px; padding: 3px 7px; background: transparent; color: var(--text-secondary); cursor: pointer; font: inherit; font-size: 12px; }
|
|
712
|
+
.background-task-stop:hover { color: var(--text); border-color: var(--text-muted); }
|
|
713
|
+
|
|
670
714
|
#input-row {
|
|
671
715
|
display: flex;
|
|
672
716
|
flex-direction: column;
|
|
@@ -790,6 +834,7 @@
|
|
|
790
834
|
opacity: 0.35;
|
|
791
835
|
cursor: default;
|
|
792
836
|
}
|
|
837
|
+
.vendor-toggle-btn.disabled:not(.active) { display: none; }
|
|
793
838
|
.vendor-toggle-icon {
|
|
794
839
|
width: 16px;
|
|
795
840
|
height: 16px;
|
|
@@ -799,6 +844,17 @@
|
|
|
799
844
|
.vendor-toggle-label {
|
|
800
845
|
pointer-events: none;
|
|
801
846
|
}
|
|
847
|
+
.vendor-toggle-btn.experimental::after,
|
|
848
|
+
.vendor-experimental-badge {
|
|
849
|
+
content: "🧪";
|
|
850
|
+
font-size: 10px;
|
|
851
|
+
line-height: 1;
|
|
852
|
+
font-weight: 400;
|
|
853
|
+
filter: saturate(0.85);
|
|
854
|
+
}
|
|
855
|
+
.vendor-experimental-badge {
|
|
856
|
+
margin-left: auto;
|
|
857
|
+
}
|
|
802
858
|
@media (min-width: 901px) {
|
|
803
859
|
.vendor-toggle-btn:not(.active) .vendor-toggle-label { display: none; }
|
|
804
860
|
.vendor-toggle-btn:not(.active) { padding: 0 8px; }
|
package/lib/public/css/mates.css
CHANGED
|
@@ -682,6 +682,12 @@
|
|
|
682
682
|
.mate-vendor-label {
|
|
683
683
|
pointer-events: none;
|
|
684
684
|
}
|
|
685
|
+
.mate-vendor-btn.experimental::after {
|
|
686
|
+
content: "🧪";
|
|
687
|
+
font-size: 9px;
|
|
688
|
+
line-height: 1;
|
|
689
|
+
font-weight: 400;
|
|
690
|
+
}
|
|
685
691
|
.mate-vendor-btn:not(.active) .mate-vendor-label { display: none; }
|
|
686
692
|
|
|
687
693
|
/* Collapse button inside mate header: white to match header style */
|