clay-server 3.4.0-beta.12 → 3.4.0-beta.14
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-session-handoff.js +162 -0
- package/lib/project.js +14 -0
- package/lib/public/app.js +2 -0
- package/lib/public/css/menus.css +0 -56
- package/lib/public/css/pane.css +7 -1
- package/lib/public/css/session-actions.css +98 -0
- package/lib/public/index.html +4 -8
- package/lib/public/modules/agent-config-selects.js +69 -0
- package/lib/public/modules/app-header.js +0 -22
- package/lib/public/modules/app-messages.js +8 -0
- package/lib/public/modules/app-panels.js +1 -3
- package/lib/public/modules/model-picker.js +2 -15
- package/lib/public/modules/session-actions.js +294 -0
- package/lib/public/modules/split-pair-ui.js +16 -78
- package/lib/public/modules/split-view.js +3 -0
- package/lib/public/style.css +1 -0
- package/lib/session-handoff-context.js +110 -0
- package/lib/sessions.js +2 -0
- package/lib/ws-schema.js +5 -0
- package/lib/yoke/adapters/codex.js +3 -2
- package/lib/yoke/adapters/kiro.js +2 -2
- package/package.json +1 -1
package/lib/daemon.js
CHANGED
|
@@ -1041,64 +1041,6 @@ function shutdownProjects() {
|
|
|
1041
1041
|
|
|
1042
1042
|
// Worktree tracking extracted to daemon-projects.js
|
|
1043
1043
|
|
|
1044
|
-
// --- Register projects ---
|
|
1045
|
-
var projects = config.projects || [];
|
|
1046
|
-
for (var i = 0; i < projects.length; i++) {
|
|
1047
|
-
var p = projects[i];
|
|
1048
|
-
if (fs.existsSync(p.path)) {
|
|
1049
|
-
console.log("[daemon] Adding project:", p.slug, "→", p.path);
|
|
1050
|
-
relay.addProject(p.path, p.slug, p.title, p.icon, p.ownerId);
|
|
1051
|
-
// Discover and register worktrees for this project
|
|
1052
|
-
scanAndRegisterWorktrees(relay, p.path, p.slug, p.icon, p.ownerId);
|
|
1053
|
-
} else {
|
|
1054
|
-
console.log("[daemon] Skipping missing project:", p.path);
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
|
|
1058
|
-
// Migrate legacy mates storage before registration
|
|
1059
|
-
mates.migrateLegacyMates();
|
|
1060
|
-
|
|
1061
|
-
// Register existing mates as projects
|
|
1062
|
-
if (usersModule.isMultiUser()) {
|
|
1063
|
-
// Multi-user mode: iterate over all users and load each user's mates
|
|
1064
|
-
var allUsers = usersModule.getAllUsers();
|
|
1065
|
-
for (var ui = 0; ui < allUsers.length; ui++) {
|
|
1066
|
-
var mateCtx = mates.buildMateCtx(allUsers[ui].id);
|
|
1067
|
-
var userMates = mates.getAllMates(mateCtx);
|
|
1068
|
-
for (var mi = 0; mi < userMates.length; mi++) {
|
|
1069
|
-
var m = userMates[mi];
|
|
1070
|
-
var mateDir = mates.getMateDir(mateCtx, m.id);
|
|
1071
|
-
var mateSlug = "mate-" + m.id;
|
|
1072
|
-
var mateName = (m.profile && m.profile.displayName) || m.name || "New Mate";
|
|
1073
|
-
if (fs.existsSync(mateDir)) {
|
|
1074
|
-
var mateDef = m.builtinKey ? require("./builtin-mates").getBuiltinByKey(m.builtinKey) : null;
|
|
1075
|
-
var isHost = !!(mateDef && mateDef.hostAgent);
|
|
1076
|
-
console.log("[daemon] Adding mate project:", mateSlug + (isHost ? " (host agent)" : ""));
|
|
1077
|
-
relay.addProject(mateDir, mateSlug, mateName, null, m.createdBy, null, { isMate: true, mateDisplayName: mateName, isHostAgent: isHost });
|
|
1078
|
-
}
|
|
1079
|
-
}
|
|
1080
|
-
}
|
|
1081
|
-
} else {
|
|
1082
|
-
// Single-user mode: use null ctx
|
|
1083
|
-
var mateCtx = mates.buildMateCtx(null);
|
|
1084
|
-
var allMates = mates.getAllMates(mateCtx);
|
|
1085
|
-
for (var mi = 0; mi < allMates.length; mi++) {
|
|
1086
|
-
var m = allMates[mi];
|
|
1087
|
-
var mateDir = mates.getMateDir(mateCtx, m.id);
|
|
1088
|
-
var mateSlug = "mate-" + m.id;
|
|
1089
|
-
var mateName = (m.profile && m.profile.displayName) || m.name || "New Mate";
|
|
1090
|
-
if (fs.existsSync(mateDir)) {
|
|
1091
|
-
var mateDef2 = m.builtinKey ? require("./builtin-mates").getBuiltinByKey(m.builtinKey) : null;
|
|
1092
|
-
var isHost2 = !!(mateDef2 && mateDef2.hostAgent);
|
|
1093
|
-
console.log("[daemon] Adding mate project:", mateSlug + (isHost2 ? " (host agent)" : ""));
|
|
1094
|
-
relay.addProject(mateDir, mateSlug, mateName, null, m.createdBy, null, { isMate: true, mateDisplayName: mateName, isHostAgent: isHost2 });
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1099
|
-
// Sync ~/.clayrc on startup
|
|
1100
|
-
try { syncClayrc(config.projects); } catch (e) {}
|
|
1101
|
-
|
|
1102
1044
|
// --- IPC server ---
|
|
1103
1045
|
// Clean up stale socket/config left by a previously killed daemon
|
|
1104
1046
|
var existingConfig = loadConfig();
|
|
@@ -1334,6 +1276,64 @@ var ipc = createIPCServer(socketPath(), function (msg) {
|
|
|
1334
1276
|
}
|
|
1335
1277
|
});
|
|
1336
1278
|
|
|
1279
|
+
// --- Register projects ---
|
|
1280
|
+
var projects = config.projects || [];
|
|
1281
|
+
for (var i = 0; i < projects.length; i++) {
|
|
1282
|
+
var p = projects[i];
|
|
1283
|
+
if (fs.existsSync(p.path)) {
|
|
1284
|
+
console.log("[daemon] Adding project:", p.slug, "→", p.path);
|
|
1285
|
+
relay.addProject(p.path, p.slug, p.title, p.icon, p.ownerId);
|
|
1286
|
+
// Discover and register worktrees for this project
|
|
1287
|
+
scanAndRegisterWorktrees(relay, p.path, p.slug, p.icon, p.ownerId);
|
|
1288
|
+
} else {
|
|
1289
|
+
console.log("[daemon] Skipping missing project:", p.path);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
// Migrate legacy mates storage before registration
|
|
1294
|
+
mates.migrateLegacyMates();
|
|
1295
|
+
|
|
1296
|
+
// Register existing mates as projects
|
|
1297
|
+
if (usersModule.isMultiUser()) {
|
|
1298
|
+
// Multi-user mode: iterate over all users and load each user's mates
|
|
1299
|
+
var allUsers = usersModule.getAllUsers();
|
|
1300
|
+
for (var ui = 0; ui < allUsers.length; ui++) {
|
|
1301
|
+
var mateCtx = mates.buildMateCtx(allUsers[ui].id);
|
|
1302
|
+
var userMates = mates.getAllMates(mateCtx);
|
|
1303
|
+
for (var mi = 0; mi < userMates.length; mi++) {
|
|
1304
|
+
var m = userMates[mi];
|
|
1305
|
+
var mateDir = mates.getMateDir(mateCtx, m.id);
|
|
1306
|
+
var mateSlug = "mate-" + m.id;
|
|
1307
|
+
var mateName = (m.profile && m.profile.displayName) || m.name || "New Mate";
|
|
1308
|
+
if (fs.existsSync(mateDir)) {
|
|
1309
|
+
var mateDef = m.builtinKey ? require("./builtin-mates").getBuiltinByKey(m.builtinKey) : null;
|
|
1310
|
+
var isHost = !!(mateDef && mateDef.hostAgent);
|
|
1311
|
+
console.log("[daemon] Adding mate project:", mateSlug + (isHost ? " (host agent)" : ""));
|
|
1312
|
+
relay.addProject(mateDir, mateSlug, mateName, null, m.createdBy, null, { isMate: true, mateDisplayName: mateName, isHostAgent: isHost });
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
} else {
|
|
1317
|
+
// Single-user mode: use null ctx
|
|
1318
|
+
var mateCtx = mates.buildMateCtx(null);
|
|
1319
|
+
var allMates = mates.getAllMates(mateCtx);
|
|
1320
|
+
for (var mi = 0; mi < allMates.length; mi++) {
|
|
1321
|
+
var m = allMates[mi];
|
|
1322
|
+
var mateDir = mates.getMateDir(mateCtx, m.id);
|
|
1323
|
+
var mateSlug = "mate-" + m.id;
|
|
1324
|
+
var mateName = (m.profile && m.profile.displayName) || m.name || "New Mate";
|
|
1325
|
+
if (fs.existsSync(mateDir)) {
|
|
1326
|
+
var mateDef2 = m.builtinKey ? require("./builtin-mates").getBuiltinByKey(m.builtinKey) : null;
|
|
1327
|
+
var isHost2 = !!(mateDef2 && mateDef2.hostAgent);
|
|
1328
|
+
console.log("[daemon] Adding mate project:", mateSlug + (isHost2 ? " (host agent)" : ""));
|
|
1329
|
+
relay.addProject(mateDir, mateSlug, mateName, null, m.createdBy, null, { isMate: true, mateDisplayName: mateName, isHostAgent: isHost2 });
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
// Sync ~/.clayrc on startup
|
|
1335
|
+
try { syncClayrc(config.projects); } catch (e) {}
|
|
1336
|
+
|
|
1337
1337
|
// --- Cleanup stale SDK warmup CLI session files ---
|
|
1338
1338
|
//
|
|
1339
1339
|
// On adapter init the SDK is given a dummy "hi" prompt to discover models,
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
var yoke = require("./yoke");
|
|
2
|
+
var contextBuilder = require("./session-handoff-context");
|
|
3
|
+
|
|
4
|
+
function hasUserContext(session) {
|
|
5
|
+
var history = (session && session.history) || [];
|
|
6
|
+
for (var i = 0; i < history.length; i++) {
|
|
7
|
+
if (history[i] && history[i].type === "user_message" && history[i].text) return true;
|
|
8
|
+
if (history[i] && history[i].type === "handoff_context" && history[i].request) return true;
|
|
9
|
+
}
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function attachSessionHandoff(ctx) {
|
|
14
|
+
var sm = ctx.sm;
|
|
15
|
+
|
|
16
|
+
function sendResult(ws, ok, details) {
|
|
17
|
+
ctx.sendTo(ws, Object.assign({ type: "session_handoff_result", ok: ok }, details || {}));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function validate(ws, source, targetVendor) {
|
|
21
|
+
if (ctx.isMate) throw new Error("Session handoff is only available in projects.");
|
|
22
|
+
if (!source) throw new Error("No active session was found.");
|
|
23
|
+
if (ctx.splitStore && ctx.splitStore.groupForMember(source.localId)) {
|
|
24
|
+
throw new Error("Open the session by itself before continuing it in another agent.");
|
|
25
|
+
}
|
|
26
|
+
if (source.isProcessing || source._queryStarting) {
|
|
27
|
+
throw new Error("Wait for the current response to finish before continuing in another agent.");
|
|
28
|
+
}
|
|
29
|
+
if (!hasUserContext(source)) throw new Error("This session does not have any conversation to continue.");
|
|
30
|
+
if (!targetVendor) throw new Error("Choose an agent for the handoff.");
|
|
31
|
+
if (!ctx.adapters[targetVendor] || (sm.installedVendors || []).indexOf(targetVendor) === -1) {
|
|
32
|
+
throw new Error("The selected coding agent is not installed.");
|
|
33
|
+
}
|
|
34
|
+
var linuxUser = ctx.getLinuxUserForSession(source);
|
|
35
|
+
var vendorInfo = yoke.getVendorInfo(targetVendor);
|
|
36
|
+
if (linuxUser && vendorInfo && vendorInfo.osUserIsolation === false) {
|
|
37
|
+
throw new Error((vendorInfo.displayName || targetVendor) + " is not available for OS-isolated users.");
|
|
38
|
+
}
|
|
39
|
+
var ownerId = ws._clayUser && ctx.usersModule.isMultiUser() ? ws._clayUser.id : null;
|
|
40
|
+
if ((source.ownerId || null) !== ownerId) throw new Error("Session access denied.");
|
|
41
|
+
return linuxUser;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function quietRecord(session, entry) {
|
|
45
|
+
session.history.push(entry);
|
|
46
|
+
sm.appendToSessionFile(session, entry);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function handoff(ws, msg) {
|
|
50
|
+
var source = sm.sessions.get(ws._clayActiveSession) || null;
|
|
51
|
+
var targetVendor = typeof msg.targetVendor === "string" ? msg.targetVendor.trim() : "";
|
|
52
|
+
var targetModel = typeof msg.model === "string" ? msg.model.trim() : "";
|
|
53
|
+
var linuxUser;
|
|
54
|
+
try {
|
|
55
|
+
linuxUser = validate(ws, source, targetVendor);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
sendResult(ws, false, { error: err.message || String(err) });
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
var targetEffort = yoke.clampEffort(
|
|
62
|
+
targetVendor,
|
|
63
|
+
msg.effort || (sm.currentEffortByVendor && sm.currentEffortByVendor[targetVendor]) || sm.currentEffort || "medium"
|
|
64
|
+
) || null;
|
|
65
|
+
var target = sm.createSessionRaw({
|
|
66
|
+
ownerId: source.ownerId || null,
|
|
67
|
+
sessionVisibility: source.sessionVisibility || "shared",
|
|
68
|
+
vendor: targetVendor,
|
|
69
|
+
model: targetModel || null,
|
|
70
|
+
effort: targetEffort,
|
|
71
|
+
});
|
|
72
|
+
var targetName = (yoke.getVendorInfo(targetVendor) || {}).displayName || targetVendor;
|
|
73
|
+
target.title = (source.title || "Continued session") + " · " + targetName;
|
|
74
|
+
target.handoff = {
|
|
75
|
+
sourceSessionId: source.localId,
|
|
76
|
+
sourceVendor: source.vendor || sm.defaultVendor || "claude",
|
|
77
|
+
createdAt: Date.now(),
|
|
78
|
+
mode: "context",
|
|
79
|
+
sourceHistoryIndex: source.history.length,
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
var sourceEntry = {
|
|
83
|
+
type: "handoff_created",
|
|
84
|
+
targetSessionId: target.localId,
|
|
85
|
+
targetVendor: targetVendor,
|
|
86
|
+
targetTitle: target.title,
|
|
87
|
+
_ts: Date.now(),
|
|
88
|
+
};
|
|
89
|
+
var targetEntry = {
|
|
90
|
+
type: "handoff_context",
|
|
91
|
+
sourceSessionId: source.localId,
|
|
92
|
+
sourceVendor: target.handoff.sourceVendor,
|
|
93
|
+
sourceTitle: source.title || "Untitled session",
|
|
94
|
+
targetVendor: targetVendor,
|
|
95
|
+
request: contextBuilder.latestUserRequest(source.history),
|
|
96
|
+
_ts: Date.now(),
|
|
97
|
+
};
|
|
98
|
+
quietRecord(source, sourceEntry);
|
|
99
|
+
quietRecord(target, targetEntry);
|
|
100
|
+
|
|
101
|
+
var prompt = contextBuilder.buildHandoffContext({
|
|
102
|
+
cwd: ctx.cwd,
|
|
103
|
+
source: source,
|
|
104
|
+
targetVendor: targetVendor,
|
|
105
|
+
});
|
|
106
|
+
target.isProcessing = true;
|
|
107
|
+
target.lastActivity = Date.now();
|
|
108
|
+
target.sentToolResults = {};
|
|
109
|
+
sm.switchSession(target.localId, ws);
|
|
110
|
+
if (typeof ctx.onProcessingChanged === "function") ctx.onProcessingChanged();
|
|
111
|
+
sendResult(ws, true, {
|
|
112
|
+
sourceSessionId: source.localId,
|
|
113
|
+
targetSessionId: target.localId,
|
|
114
|
+
targetVendor: targetVendor,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
var sdk = ctx.getSdk();
|
|
118
|
+
function failStart(err) {
|
|
119
|
+
target.isProcessing = false;
|
|
120
|
+
sm.sendAndRecord(target, { type: "error", text: "Could not start the handoff session: " + (err.message || String(err)) });
|
|
121
|
+
if (typeof ctx.onProcessingChanged === "function") ctx.onProcessingChanged();
|
|
122
|
+
sm.broadcastSessionList();
|
|
123
|
+
}
|
|
124
|
+
if (!sdk || typeof sdk.startQuery !== "function") {
|
|
125
|
+
failStart(new Error("SDK bridge is not ready."));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
target._queryStartTs = Date.now();
|
|
129
|
+
var startPromise;
|
|
130
|
+
try {
|
|
131
|
+
startPromise = sdk.startQuery(target, prompt, undefined, linuxUser);
|
|
132
|
+
} catch (err) {
|
|
133
|
+
failStart(err);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
Promise.resolve(startPromise).catch(failStart);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function handleMessage(ws, msg) {
|
|
140
|
+
if (msg.type === "handoff_session_options") {
|
|
141
|
+
ctx.sendTo(ws, {
|
|
142
|
+
type: "handoff_session_options",
|
|
143
|
+
installedVendors: sm.installedVendors || [],
|
|
144
|
+
modelsByVendor: sm.modelsByVendor || {},
|
|
145
|
+
capabilitiesByVendor: sm.capabilitiesByVendor || {},
|
|
146
|
+
});
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
if (msg.type === "handoff_session") {
|
|
150
|
+
handoff(ws, msg);
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return { handleMessage: handleMessage };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
module.exports = {
|
|
160
|
+
attachSessionHandoff: attachSessionHandoff,
|
|
161
|
+
hasUserContext: hasUserContext,
|
|
162
|
+
};
|
package/lib/project.js
CHANGED
|
@@ -36,6 +36,7 @@ var { createLocalMcp } = require("./mcp-local");
|
|
|
36
36
|
var { attachEmail: attachEmailModule } = require("./project-email");
|
|
37
37
|
var { attachSessionSpawn } = require("./project-session-spawn");
|
|
38
38
|
var { attachSessionPair } = require("./project-session-pair");
|
|
39
|
+
var { attachSessionHandoff } = require("./project-session-handoff");
|
|
39
40
|
var { attachSessionNotes, composeSystemPrompts } = require("./project-session-notes");
|
|
40
41
|
var { attachSessionDocument } = require("./project-session-document");
|
|
41
42
|
var { attachSplitGroups } = require("./session-split-groups");
|
|
@@ -570,6 +571,18 @@ function createProjectContext(opts) {
|
|
|
570
571
|
getLinuxUserForSession: getLinuxUserForSession,
|
|
571
572
|
getPairToolDefs: function (boundSession) { return _sessionPair.getToolDefs(boundSession); },
|
|
572
573
|
});
|
|
574
|
+
var _sessionHandoff = attachSessionHandoff({
|
|
575
|
+
cwd: cwd,
|
|
576
|
+
sm: sm,
|
|
577
|
+
isMate: isMate,
|
|
578
|
+
splitStore: _splitGroups.store,
|
|
579
|
+
getSdk: function () { return sdk; },
|
|
580
|
+
sendTo: sendTo,
|
|
581
|
+
usersModule: usersModule,
|
|
582
|
+
adapters: adapters,
|
|
583
|
+
getLinuxUserForSession: getLinuxUserForSession,
|
|
584
|
+
onProcessingChanged: onProcessingChanged,
|
|
585
|
+
});
|
|
573
586
|
var _debate = null;
|
|
574
587
|
var _debateProposal = attachDebateProposal({
|
|
575
588
|
cwd: cwd,
|
|
@@ -1101,6 +1114,7 @@ function createProjectContext(opts) {
|
|
|
1101
1114
|
|
|
1102
1115
|
// --- Sessions, config, project mgmt (delegated to project-sessions.js) ---
|
|
1103
1116
|
if (_sessionPair.handleMessage(ws, msg)) return;
|
|
1117
|
+
if (_sessionHandoff.handleMessage(ws, msg)) return;
|
|
1104
1118
|
if (_splitGroups.handleMessage(ws, msg)) return;
|
|
1105
1119
|
if (_models.handleMessage(ws, msg)) return;
|
|
1106
1120
|
if (_sessions.handleSessionsMessage(ws, msg)) return;
|
package/lib/public/app.js
CHANGED
|
@@ -60,6 +60,7 @@ 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';
|
|
@@ -639,6 +640,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
639
640
|
|
|
640
641
|
// --- Header module (rename, info popover, history) ---
|
|
641
642
|
initHeader();
|
|
643
|
+
initSessionActions();
|
|
642
644
|
|
|
643
645
|
// --- Skill Install module ---
|
|
644
646
|
initSkillInstall();
|
package/lib/public/css/menus.css
CHANGED
|
@@ -43,62 +43,6 @@
|
|
|
43
43
|
#header-rename-btn:hover { color: var(--text-secondary); background: rgba(var(--overlay-rgb),0.04); border-color: var(--border); }
|
|
44
44
|
#header-rename-btn .lucide { width: 13px; height: 13px; }
|
|
45
45
|
|
|
46
|
-
#header-add-worker-btn {
|
|
47
|
-
display: inline-flex;
|
|
48
|
-
align-items: center;
|
|
49
|
-
justify-content: center;
|
|
50
|
-
gap: 7px;
|
|
51
|
-
width: auto;
|
|
52
|
-
height: 28px;
|
|
53
|
-
border: 1px solid color-mix(in srgb, var(--accent2) 28%, var(--border));
|
|
54
|
-
background: var(--accent2-8);
|
|
55
|
-
color: var(--accent2);
|
|
56
|
-
cursor: pointer;
|
|
57
|
-
border-radius: 8px;
|
|
58
|
-
flex-shrink: 0;
|
|
59
|
-
padding: 0 9px 0 7px;
|
|
60
|
-
margin-left: 0;
|
|
61
|
-
font-family: inherit;
|
|
62
|
-
font-size: 11px;
|
|
63
|
-
font-weight: 700;
|
|
64
|
-
line-height: 1;
|
|
65
|
-
white-space: nowrap;
|
|
66
|
-
transition: color 0.15s, background 0.15s, border-color 0.15s;
|
|
67
|
-
}
|
|
68
|
-
#header-add-worker-btn:hover {
|
|
69
|
-
color: var(--accent2-hover);
|
|
70
|
-
background: var(--accent2-15);
|
|
71
|
-
border-color: color-mix(in srgb, var(--accent2) 48%, var(--border));
|
|
72
|
-
}
|
|
73
|
-
.header-worker-icon {
|
|
74
|
-
position: relative;
|
|
75
|
-
display: inline-flex;
|
|
76
|
-
align-items: center;
|
|
77
|
-
justify-content: center;
|
|
78
|
-
width: 16px;
|
|
79
|
-
height: 16px;
|
|
80
|
-
flex-shrink: 0;
|
|
81
|
-
}
|
|
82
|
-
.header-worker-icon > .lucide { width: 15px; height: 15px; }
|
|
83
|
-
.header-worker-add-badge {
|
|
84
|
-
position: absolute;
|
|
85
|
-
right: -4px;
|
|
86
|
-
bottom: -3px;
|
|
87
|
-
display: inline-flex;
|
|
88
|
-
align-items: center;
|
|
89
|
-
justify-content: center;
|
|
90
|
-
width: 9px;
|
|
91
|
-
height: 9px;
|
|
92
|
-
border-radius: 50%;
|
|
93
|
-
background: var(--accent2);
|
|
94
|
-
color: var(--bg);
|
|
95
|
-
box-shadow: 0 0 0 2px var(--bg);
|
|
96
|
-
}
|
|
97
|
-
.header-worker-add-badge .lucide { width: 7px; height: 7px; stroke-width: 3; }
|
|
98
|
-
.header-worker-label { letter-spacing: 0.01em; }
|
|
99
|
-
#header-add-worker-btn.hidden { display: none; }
|
|
100
|
-
body.dm-mode #header-add-worker-btn { display: none; }
|
|
101
|
-
|
|
102
46
|
/* "Close terminal" button for a live TUI session. Matches the session-control
|
|
103
47
|
chrome; reds out on hover since it stops the running agent while preserving
|
|
104
48
|
the resumable session. */
|
package/lib/public/css/pane.css
CHANGED
|
@@ -100,7 +100,8 @@ body.pane-mode #input-area {
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
.split-pane-title {
|
|
103
|
-
flex: 1;
|
|
103
|
+
flex: 0 1 auto;
|
|
104
|
+
max-width: 46%;
|
|
104
105
|
min-width: 0;
|
|
105
106
|
overflow: hidden;
|
|
106
107
|
text-overflow: ellipsis;
|
|
@@ -283,12 +284,17 @@ button.split-pair-role:hover { filter: brightness(1.25); }
|
|
|
283
284
|
color: var(--text-dimmer);
|
|
284
285
|
cursor: pointer;
|
|
285
286
|
flex-shrink: 0;
|
|
287
|
+
margin-left: auto;
|
|
286
288
|
}
|
|
287
289
|
|
|
288
290
|
.split-pane-context.has-data {
|
|
289
291
|
display: inline-flex;
|
|
290
292
|
}
|
|
291
293
|
|
|
294
|
+
.split-pane-context:not(.has-data) + .split-pane-close {
|
|
295
|
+
margin-left: auto;
|
|
296
|
+
}
|
|
297
|
+
|
|
292
298
|
.split-pane-context:hover {
|
|
293
299
|
background: var(--bg-alt);
|
|
294
300
|
color: var(--text);
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#header-session-actions-btn {
|
|
2
|
+
display: inline-flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
justify-content: center;
|
|
5
|
+
width: 28px;
|
|
6
|
+
height: 28px;
|
|
7
|
+
padding: 0;
|
|
8
|
+
border: 1px solid transparent;
|
|
9
|
+
border-radius: 8px;
|
|
10
|
+
background: transparent;
|
|
11
|
+
color: var(--text-dimmer);
|
|
12
|
+
cursor: pointer;
|
|
13
|
+
flex-shrink: 0;
|
|
14
|
+
transition: color 0.15s, background 0.15s, border-color 0.15s;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
#header-session-actions-btn:hover,
|
|
18
|
+
#header-session-actions-btn[aria-expanded="true"] {
|
|
19
|
+
color: var(--text);
|
|
20
|
+
background: rgba(var(--overlay-rgb), 0.05);
|
|
21
|
+
border-color: var(--border);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
#header-session-actions-btn.hidden { display: none; }
|
|
25
|
+
#header-session-actions-btn .lucide { width: 16px; height: 16px; }
|
|
26
|
+
|
|
27
|
+
.session-actions-menu {
|
|
28
|
+
position: fixed;
|
|
29
|
+
z-index: 10020;
|
|
30
|
+
width: 286px;
|
|
31
|
+
padding: 6px;
|
|
32
|
+
border: 1px solid var(--border);
|
|
33
|
+
border-radius: 12px;
|
|
34
|
+
background: color-mix(in srgb, var(--bg) 96%, transparent);
|
|
35
|
+
box-shadow: 0 14px 38px rgba(var(--shadow-rgb), 0.32), 0 2px 8px rgba(var(--shadow-rgb), 0.2);
|
|
36
|
+
backdrop-filter: blur(18px);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.session-actions-row {
|
|
40
|
+
width: 100%;
|
|
41
|
+
border: 0;
|
|
42
|
+
background: transparent;
|
|
43
|
+
color: var(--text);
|
|
44
|
+
font: inherit;
|
|
45
|
+
text-align: left;
|
|
46
|
+
cursor: pointer;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.session-actions-row {
|
|
50
|
+
display: grid;
|
|
51
|
+
grid-template-columns: 30px minmax(0, 1fr) 16px;
|
|
52
|
+
align-items: center;
|
|
53
|
+
gap: 8px;
|
|
54
|
+
min-height: 54px;
|
|
55
|
+
padding: 7px 8px;
|
|
56
|
+
border-radius: 8px;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.session-actions-row:hover { background: var(--bg-alt); }
|
|
60
|
+
.session-actions-row:disabled { cursor: default; opacity: 0.42; }
|
|
61
|
+
.session-actions-row:disabled:hover { background: transparent; }
|
|
62
|
+
|
|
63
|
+
.session-actions-row-icon {
|
|
64
|
+
display: grid;
|
|
65
|
+
place-items: center;
|
|
66
|
+
width: 28px;
|
|
67
|
+
height: 28px;
|
|
68
|
+
border: 1px solid var(--border-subtle);
|
|
69
|
+
border-radius: 8px;
|
|
70
|
+
background: rgba(var(--overlay-rgb), 0.035);
|
|
71
|
+
color: var(--accent2);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.session-actions-row-icon .lucide { width: 15px; height: 15px; }
|
|
75
|
+
.session-actions-row-copy { display: flex; min-width: 0; flex-direction: column; gap: 2px; }
|
|
76
|
+
.session-actions-row-copy strong { font-size: 12px; font-weight: 750; }
|
|
77
|
+
.session-actions-row-copy > span { color: var(--text-dimmer); font-size: 10px; line-height: 1.25; }
|
|
78
|
+
.session-actions-row-chevron { color: var(--text-dimmer); }
|
|
79
|
+
.session-actions-row-chevron .lucide { width: 13px; height: 13px; }
|
|
80
|
+
|
|
81
|
+
.handoff-modal { width: min(420px, calc(100vw - 32px)); }
|
|
82
|
+
.handoff-modal-desc { margin: -10px 0 14px; color: var(--text-dimmer); font-size: 12px; line-height: 1.45; }
|
|
83
|
+
.handoff-modal-source {
|
|
84
|
+
display: flex;
|
|
85
|
+
align-items: center;
|
|
86
|
+
gap: 10px;
|
|
87
|
+
margin-bottom: 14px;
|
|
88
|
+
padding: 10px;
|
|
89
|
+
border: 1px solid var(--border-subtle);
|
|
90
|
+
border-radius: 9px;
|
|
91
|
+
background: var(--bg-alt);
|
|
92
|
+
}
|
|
93
|
+
.handoff-modal-source > img { width: 26px; height: 26px; object-fit: contain; border-radius: 6px; }
|
|
94
|
+
.handoff-modal-source > span { display: grid; min-width: 0; gap: 1px; }
|
|
95
|
+
.handoff-modal-source strong { overflow: hidden; color: var(--text); font-size: 12px; text-overflow: ellipsis; white-space: nowrap; }
|
|
96
|
+
.handoff-modal-source span span { color: var(--text-dimmer); font-size: 10px; }
|
|
97
|
+
.handoff-modal-field + .handoff-modal-field { margin-top: 10px; }
|
|
98
|
+
.handoff-modal-field .wt-modal-label { margin-top: 0; }
|
package/lib/public/index.html
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
(function(){try{var k="clay-theme-vars",v=localStorage.getItem(k),r=document.documentElement;if(v){var o=JSON.parse(v),p;for(p in o)r.style.setProperty(p,o[p]);var vt=localStorage.getItem(k.replace("-vars","-variant"));if(vt==="light"){r.classList.add("light-theme");r.classList.remove("dark-theme")}else{r.classList.add("dark-theme");r.classList.remove("light-theme")}var m=document.querySelector('meta[name="theme-color"]');if(m&&o["--bg"])m.setAttribute("content",o["--bg"])}else{var sl=window.matchMedia&&window.matchMedia("(prefers-color-scheme: light)").matches;if(sl){r.classList.add("light-theme");r.classList.remove("dark-theme")}}}catch(e){}})();
|
|
31
31
|
</script>
|
|
32
32
|
<script>if(window.navigator.standalone||window.matchMedia("(display-mode:standalone)").matches){document.documentElement.classList.add("pwa-standalone")}</script>
|
|
33
|
-
<link rel="stylesheet" href="style.css?v=
|
|
33
|
+
<link rel="stylesheet" href="style.css?v=20260826a">
|
|
34
34
|
<style>
|
|
35
35
|
@media(max-width:768px){
|
|
36
36
|
/* User messages: vertical stack, avatar on top, right-aligned */
|
|
@@ -388,12 +388,8 @@
|
|
|
388
388
|
<div id="ralph-sticky" class="hidden"></div>
|
|
389
389
|
<div id="debate-sticky" class="hidden"></div>
|
|
390
390
|
<div class="status">
|
|
391
|
-
<button id="header-
|
|
392
|
-
<
|
|
393
|
-
<i data-lucide="bot"></i>
|
|
394
|
-
<span class="header-worker-add-badge"><i data-lucide="plus"></i></span>
|
|
395
|
-
</span>
|
|
396
|
-
<span class="header-worker-label">Add Worker</span>
|
|
391
|
+
<button id="header-session-actions-btn" type="button" aria-label="Session actions" title="Session actions" aria-haspopup="menu" aria-expanded="false">
|
|
392
|
+
<i data-lucide="ellipsis"></i>
|
|
397
393
|
</button>
|
|
398
394
|
<button type="button" id="header-tui-font-btn" class="header-tui-font-btn hidden" title="Terminal font" aria-label="Terminal font">
|
|
399
395
|
<i data-lucide="type"></i>
|
|
@@ -2364,7 +2360,7 @@
|
|
|
2364
2360
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-fit@0/lib/addon-fit.min.js"></script>
|
|
2365
2361
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-web-links@0/lib/addon-web-links.min.js"></script>
|
|
2366
2362
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-webgl@0/lib/addon-webgl.min.js"></script>
|
|
2367
|
-
<script type="module" src="app.js?v=
|
|
2363
|
+
<script type="module" src="app.js?v=20260826a"></script>
|
|
2368
2364
|
<div id="pwa-install-modal" class="pwa-modal hidden">
|
|
2369
2365
|
<div class="pwa-modal-backdrop"></div>
|
|
2370
2366
|
<div class="pwa-modal-card">
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { VENDOR_NAMES, VENDOR_ORDER, isExperimentalVendor } from './app-rendering.js';
|
|
2
|
+
import { effortLevelsFor, effortDisplayName } from './app-panels.js';
|
|
3
|
+
|
|
4
|
+
function optionValue(entry) {
|
|
5
|
+
if (typeof entry === "string") return entry;
|
|
6
|
+
return entry && (entry.value || entry.id) || "";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function optionLabel(entry) {
|
|
10
|
+
if (typeof entry === "string") return entry;
|
|
11
|
+
return entry && (entry.displayName || entry.name || entry.value || entry.id) || "";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function buildAgentVendorSelect(installed, preferred) {
|
|
15
|
+
var select = document.createElement("select");
|
|
16
|
+
select.className = "wt-modal-input";
|
|
17
|
+
for (var i = 0; i < VENDOR_ORDER.length; i++) {
|
|
18
|
+
var vendor = VENDOR_ORDER[i];
|
|
19
|
+
if (installed.indexOf(vendor) === -1) continue;
|
|
20
|
+
var option = document.createElement("option");
|
|
21
|
+
option.value = vendor;
|
|
22
|
+
option.textContent = (isExperimentalVendor(vendor) ? "🧪 " : "") + (VENDOR_NAMES[vendor] || vendor);
|
|
23
|
+
if (isExperimentalVendor(vendor)) option.title = "Experimental integration; not yet validated through direct use testing";
|
|
24
|
+
select.appendChild(option);
|
|
25
|
+
}
|
|
26
|
+
if (preferred && installed.indexOf(preferred) !== -1) select.value = preferred;
|
|
27
|
+
return select;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function fillAgentModels(select, vendor, options, preferred) {
|
|
31
|
+
var models = (options.modelsByVendor && options.modelsByVendor[vendor]) || [];
|
|
32
|
+
select.innerHTML = "";
|
|
33
|
+
var automatic = document.createElement("option");
|
|
34
|
+
automatic.value = "";
|
|
35
|
+
automatic.textContent = "Automatic";
|
|
36
|
+
select.appendChild(automatic);
|
|
37
|
+
for (var i = 0; i < models.length; i++) {
|
|
38
|
+
var option = document.createElement("option");
|
|
39
|
+
option.value = optionValue(models[i]);
|
|
40
|
+
option.textContent = optionLabel(models[i]);
|
|
41
|
+
select.appendChild(option);
|
|
42
|
+
}
|
|
43
|
+
select.value = preferred || "";
|
|
44
|
+
if (select.selectedIndex === -1) select.value = "";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function buildAgentEffortSelect() {
|
|
48
|
+
var select = document.createElement("select");
|
|
49
|
+
select.className = "wt-modal-input";
|
|
50
|
+
return select;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function fillAgentEffort(select, vendor, options, modelValue, preferred) {
|
|
54
|
+
var previous = preferred === undefined ? select.value : preferred;
|
|
55
|
+
var models = (options.modelsByVendor && options.modelsByVendor[vendor]) || [];
|
|
56
|
+
var levels = effortLevelsFor(vendor, models, modelValue);
|
|
57
|
+
select.innerHTML = "";
|
|
58
|
+
var automatic = document.createElement("option");
|
|
59
|
+
automatic.value = "";
|
|
60
|
+
automatic.textContent = "Default";
|
|
61
|
+
select.appendChild(automatic);
|
|
62
|
+
for (var i = 0; i < levels.length; i++) {
|
|
63
|
+
var option = document.createElement("option");
|
|
64
|
+
option.value = levels[i];
|
|
65
|
+
option.textContent = effortDisplayName(levels[i]);
|
|
66
|
+
select.appendChild(option);
|
|
67
|
+
}
|
|
68
|
+
select.value = levels.indexOf(previous) !== -1 ? previous : "";
|
|
69
|
+
}
|
|
@@ -12,7 +12,6 @@ import { saveToolState, resetToolState, restoreToolState } from './tools.js';
|
|
|
12
12
|
import { getSessionUsage, setSessionUsage, getContextData, setContextData, updateContextPanel, updateUsagePanel } from './app-panels.js';
|
|
13
13
|
import { onHistoryPrepended as onSessionSearchHistoryPrepended } from './session-search.js';
|
|
14
14
|
import { getCachedSessions } from './sidebar-sessions.js';
|
|
15
|
-
import { openPairDialog } from './split-pair-ui.js';
|
|
16
15
|
import { showConfirm } from './app-misc.js';
|
|
17
16
|
|
|
18
17
|
// --- Module-owned state ---
|
|
@@ -23,27 +22,6 @@ export function initHeader() {
|
|
|
23
22
|
var headerRenameBtn = document.getElementById("header-rename-btn");
|
|
24
23
|
var headerTitleEl = document.getElementById("header-title");
|
|
25
24
|
var headerInfoBtn = document.getElementById("header-info-btn");
|
|
26
|
-
var headerAddWorkerBtn = document.getElementById("header-add-worker-btn");
|
|
27
|
-
|
|
28
|
-
// --- Add Worker: the current session becomes the Driver of a new pair ---
|
|
29
|
-
if (headerAddWorkerBtn) {
|
|
30
|
-
headerAddWorkerBtn.addEventListener("click", function () {
|
|
31
|
-
var sessionId = store.get('activeSessionId');
|
|
32
|
-
if (!sessionId || store.get('splitPanes') || store.get('dmMode')) return;
|
|
33
|
-
openPairDialog({
|
|
34
|
-
sessionId: sessionId,
|
|
35
|
-
title: headerTitleEl ? headerTitleEl.textContent : "",
|
|
36
|
-
vendor: store.get('currentVendor') || "claude",
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
// A split shows the GROUP in the title bar; adding a worker there makes
|
|
40
|
-
// no sense (the group already has two members), so hide the button.
|
|
41
|
-
store.subscribe(function (state, prev) {
|
|
42
|
-
if (state.splitPanes !== prev.splitPanes) {
|
|
43
|
-
headerAddWorkerBtn.classList.toggle("hidden", !!state.splitPanes);
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
25
|
var headerFullAccessBtn = document.getElementById("header-full-access-btn");
|
|
48
26
|
|
|
49
27
|
function updateFullAccessButton() {
|