clay-server 3.4.0-beta.1 → 3.4.0-beta.11
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-projects.js +3 -3
- package/lib/daemon.js +1 -1
- package/lib/project-connection.js +20 -5
- package/lib/project-debate.js +4 -0
- package/lib/project-mate-interaction.js +21 -1
- package/lib/project-memory.js +3 -0
- package/lib/project-shell-command.js +160 -0
- package/lib/project-user-message.js +10 -0
- package/lib/project.js +19 -4
- package/lib/public/antigravity-avatar.png +0 -0
- package/lib/public/app.js +28 -0
- package/lib/public/copilot-avatar.svg +5 -0
- package/lib/public/css/command-palette.css +22 -2
- package/lib/public/css/icon-strip.css +29 -13
- package/lib/public/css/input.css +75 -0
- package/lib/public/css/mates.css +6 -0
- package/lib/public/css/menus.css +48 -14
- package/lib/public/css/messages.css +9 -0
- package/lib/public/css/pane.css +4 -0
- package/lib/public/css/pwa-mobile.css +17 -0
- package/lib/public/css/title-bar.css +19 -0
- package/lib/public/grok-avatar.svg +4 -0
- package/lib/public/index.html +38 -7
- package/lib/public/junie-avatar.svg +11 -0
- package/lib/public/kimi-avatar.svg +4 -0
- package/lib/public/modules/app-header.js +4 -0
- package/lib/public/modules/app-messages.js +28 -5
- package/lib/public/modules/app-panels.js +28 -8
- package/lib/public/modules/app-projects.js +3 -1
- package/lib/public/modules/app-rendering.js +19 -4
- package/lib/public/modules/input.js +58 -28
- package/lib/public/modules/mate-sidebar.js +11 -3
- package/lib/public/modules/notifications.js +7 -1
- package/lib/public/modules/pane-bridge.js +11 -0
- package/lib/public/modules/pane-links.js +23 -0
- package/lib/public/modules/project-switcher.js +7 -6
- package/lib/public/modules/shell-command.js +148 -0
- package/lib/public/modules/sidebar-mates.js +1 -1
- package/lib/public/modules/sidebar-mobile.js +2 -1
- package/lib/public/modules/sidebar-projects.js +34 -43
- package/lib/public/modules/sidebar-sessions.js +6 -6
- package/lib/public/modules/split-pair-ui.js +3 -2
- package/lib/public/modules/tools.js +6 -1
- package/lib/public/modules/vendor-priority.js +14 -0
- package/lib/public/modules/vendor-selection.js +20 -0
- package/lib/public/modules/worktree-location.js +17 -0
- package/lib/public/qwen-avatar.svg +4 -0
- package/lib/public/style.css +3 -2
- package/lib/sdk-bridge.js +49 -25
- package/lib/sdk-message-processor.js +10 -1
- package/lib/session-notes-mcp-server.js +7 -1
- package/lib/worktree.js +9 -4
- package/lib/ws-schema.js +3 -0
- package/lib/yoke/acp-agent-profiles.js +64 -14
- package/lib/yoke/adapters/antigravity.js +417 -0
- package/lib/yoke/adapters/codex.js +98 -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/qwen.js +7 -0
- package/lib/yoke/codex-app-server.js +25 -8
- package/lib/yoke/index.js +67 -28
- package/lib/yoke/instructions.js +3 -0
- package/lib/yoke/vendor-registry.js +61 -6
- package/package.json +1 -1
- package/lib/public/gemini-avatar.svg +0 -11
- package/lib/yoke/adapters/gemini.js +0 -7
package/lib/daemon-projects.js
CHANGED
|
@@ -41,10 +41,10 @@ function scanAndRegisterWorktrees(relay, parentPath, parentSlug, parentIcon, par
|
|
|
41
41
|
if (worktreeRegistry[parentSlug][j] === wtSlug) { alreadyRegistered = true; break; }
|
|
42
42
|
}
|
|
43
43
|
if (alreadyRegistered) continue;
|
|
44
|
-
var wtMeta = { parentSlug: parentSlug, branch: wt.branch || wt.dirName,
|
|
44
|
+
var wtMeta = { parentSlug: parentSlug, branch: wt.branch || wt.dirName, external: wt.external };
|
|
45
45
|
relay.addProject(wt.path, wtSlug, wt.branch || wt.dirName, parentIcon, parentOwnerId, wtMeta);
|
|
46
46
|
worktreeRegistry[parentSlug].push(wtSlug);
|
|
47
|
-
console.log("[daemon] Registered worktree:", wtSlug, "->", wt.path, wt.
|
|
47
|
+
console.log("[daemon] Registered worktree:", wtSlug, "->", wt.path, wt.external ? "(external)" : "(inside project folder)");
|
|
48
48
|
}
|
|
49
49
|
daemonSync.register(getWorktreeSyncKey(parentSlug), function () {
|
|
50
50
|
rescanWorktrees(relay, parentPath, parentSlug, parentIcon, parentOwnerId);
|
|
@@ -79,7 +79,7 @@ function rescanWorktrees(relay, parentPath, parentSlug, parentIcon, parentOwnerI
|
|
|
79
79
|
if (existingSlugs[ei] === wtSlug) { found = true; break; }
|
|
80
80
|
}
|
|
81
81
|
if (!found) {
|
|
82
|
-
var wtMeta = { parentSlug: parentSlug, branch: wt.branch || wt.dirName,
|
|
82
|
+
var wtMeta = { parentSlug: parentSlug, branch: wt.branch || wt.dirName, external: wt.external };
|
|
83
83
|
relay.addProject(wt.path, wtSlug, wt.branch || wt.dirName, parentIcon, parentOwnerId, wtMeta);
|
|
84
84
|
if (!worktreeRegistry[parentSlug]) worktreeRegistry[parentSlug] = [];
|
|
85
85
|
worktreeRegistry[parentSlug].push(wtSlug);
|
package/lib/daemon.js
CHANGED
|
@@ -978,7 +978,7 @@ var relay = createServer({
|
|
|
978
978
|
if (!result.ok) return result;
|
|
979
979
|
// Register the new worktree as ephemeral project
|
|
980
980
|
var wtSlug = parentSlug + "--" + dirName;
|
|
981
|
-
var wtMeta = { parentSlug: parentSlug, branch: branchName,
|
|
981
|
+
var wtMeta = { parentSlug: parentSlug, branch: branchName, external: false };
|
|
982
982
|
relay.addProject(result.path, wtSlug, branchName, parent.icon, parent.ownerId, wtMeta);
|
|
983
983
|
registerWorktreeSlug(parentSlug, wtSlug);
|
|
984
984
|
console.log("[daemon] Created worktree:", wtSlug, "->", result.path);
|
|
@@ -22,6 +22,17 @@ function dispatchMessageSafely(handleMessage, sendTo, slug, ws, msg) {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
function buildInitialModelInfo(sm, initialVendor, initialModel, initialModels) {
|
|
26
|
+
return {
|
|
27
|
+
type: "model_info",
|
|
28
|
+
model: initialModel || "",
|
|
29
|
+
models: initialModels || [],
|
|
30
|
+
vendor: initialVendor,
|
|
31
|
+
availableVendors: sm.availableVendors || [],
|
|
32
|
+
installedVendors: sm.installedVendors || [],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
25
36
|
/**
|
|
26
37
|
* Attach connection/disconnection handlers to a project context.
|
|
27
38
|
*
|
|
@@ -116,7 +127,7 @@ function attachConnection(ctx) {
|
|
|
116
127
|
if (!_warmedUp) {
|
|
117
128
|
_warmedUp = true;
|
|
118
129
|
if (typeof warmup === "function") {
|
|
119
|
-
try { warmup(); }
|
|
130
|
+
try { warmup(wsUser && wsUser.linuxUser ? wsUser.linuxUser : null); }
|
|
120
131
|
catch (e) { console.error("[project-connection] warmup failed for " + slug + ":", e && e.message ? e.message : e); }
|
|
121
132
|
}
|
|
122
133
|
}
|
|
@@ -167,9 +178,9 @@ function attachConnection(ctx) {
|
|
|
167
178
|
sendTo(ws, { type: "slash_commands", commands: sm.slashCommands });
|
|
168
179
|
}
|
|
169
180
|
var initialModel = (restoredActive && restoredActive.model) || sm.currentModel || "";
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
181
|
+
// Vendor installation state is needed even before a new project has a
|
|
182
|
+
// model. Always send it so reconnects cannot fall back to "Not installed."
|
|
183
|
+
sendTo(ws, buildInitialModelInfo(sm, initialVendor, initialModel, initialModels));
|
|
173
184
|
sendTo(ws, { type: "config_state", model: initialModel, mode: sm.currentPermissionMode || "default", effort: initialEffort || "medium", betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
|
|
174
185
|
sendTo(ws, { type: "last_vendor", vendor: sm.lastVendor || "" });
|
|
175
186
|
sendTo(ws, Object.assign({ type: "codex_config" }, getCodexConfig(sm)));
|
|
@@ -333,4 +344,8 @@ function attachConnection(ctx) {
|
|
|
333
344
|
};
|
|
334
345
|
}
|
|
335
346
|
|
|
336
|
-
module.exports = {
|
|
347
|
+
module.exports = {
|
|
348
|
+
attachConnection: attachConnection,
|
|
349
|
+
buildInitialModelInfo: buildInitialModelInfo,
|
|
350
|
+
dispatchMessageSafely: dispatchMessageSafely,
|
|
351
|
+
};
|
package/lib/project-debate.js
CHANGED
|
@@ -604,6 +604,7 @@ function attachDebate(ctx) {
|
|
|
604
604
|
var _modMate = matesModule.getMate(mateCtx, debate.moderatorId);
|
|
605
605
|
ctx.sdk.createMentionSession({
|
|
606
606
|
vendor: _modMate ? _modMate.vendor : null,
|
|
607
|
+
linuxUser: mateCtx.linuxUser || undefined,
|
|
607
608
|
claudeMd: claudeMd,
|
|
608
609
|
initialContext: digests,
|
|
609
610
|
initialMessage: quickBriefPrompt,
|
|
@@ -799,6 +800,7 @@ function attachDebate(ctx) {
|
|
|
799
800
|
var _modMate2 = matesModule.getMate(mateCtx, debate.moderatorId);
|
|
800
801
|
ctx.sdk.createMentionSession({
|
|
801
802
|
vendor: _modMate2 ? _modMate2.vendor : null,
|
|
803
|
+
linuxUser: mateCtx.linuxUser || undefined,
|
|
802
804
|
claudeMd: claudeMd,
|
|
803
805
|
initialContext: moderatorContext,
|
|
804
806
|
initialMessage: "Begin the debate on: " + debate.topic,
|
|
@@ -994,6 +996,7 @@ function attachDebate(ctx) {
|
|
|
994
996
|
var _panMate = matesModule.getMate(debate.mateCtx, mateId);
|
|
995
997
|
ctx.sdk.createMentionSession({
|
|
996
998
|
vendor: _panMate ? _panMate.vendor : null,
|
|
999
|
+
linuxUser: debate.mateCtx && debate.mateCtx.linuxUser ? debate.mateCtx.linuxUser : undefined,
|
|
997
1000
|
claudeMd: claudeMd,
|
|
998
1001
|
initialContext: panelistContext + historyContext,
|
|
999
1002
|
initialMessage: "The moderator addresses you:\n\n" + moderatorText,
|
|
@@ -1530,6 +1533,7 @@ function attachDebate(ctx) {
|
|
|
1530
1533
|
var _modMate3 = matesModule.getMate(mateCtx, debate.moderatorId);
|
|
1531
1534
|
ctx.sdk.createMentionSession({
|
|
1532
1535
|
vendor: _modMate3 ? _modMate3.vendor : null,
|
|
1536
|
+
linuxUser: mateCtx.linuxUser || undefined,
|
|
1533
1537
|
claudeMd: claudeMd,
|
|
1534
1538
|
initialContext: moderatorContext,
|
|
1535
1539
|
initialMessage: resumePrompt,
|
|
@@ -179,6 +179,7 @@ function attachMateInteraction(ctx) {
|
|
|
179
179
|
var _digestQueue = [];
|
|
180
180
|
var _digestBusy = false;
|
|
181
181
|
var _digestWorkerTurns = 0;
|
|
182
|
+
var _digestWorkerLinuxUser = null;
|
|
182
183
|
var DIGEST_WORKER_MAX_TURNS = 20;
|
|
183
184
|
|
|
184
185
|
function enqueueDigest(job) {
|
|
@@ -191,6 +192,13 @@ function attachMateInteraction(ctx) {
|
|
|
191
192
|
_digestBusy = true;
|
|
192
193
|
var job = _digestQueue.shift();
|
|
193
194
|
|
|
195
|
+
if (_digestWorker && _digestWorkerLinuxUser !== (job.linuxUser || null)) {
|
|
196
|
+
try { _digestWorker.close(); } catch (e) {}
|
|
197
|
+
_digestWorker = null;
|
|
198
|
+
_digestWorkerTurns = 0;
|
|
199
|
+
_digestWorkerLinuxUser = null;
|
|
200
|
+
}
|
|
201
|
+
|
|
194
202
|
var mateDir = matesModule.getMateDir(job.mateCtx, job.mateId);
|
|
195
203
|
var knowledgeDir = path.join(mateDir, "knowledge");
|
|
196
204
|
|
|
@@ -315,6 +323,7 @@ function attachMateInteraction(ctx) {
|
|
|
315
323
|
try { _digestWorker.close(); } catch (e) {}
|
|
316
324
|
_digestWorker = null;
|
|
317
325
|
_digestWorkerTurns = 0;
|
|
326
|
+
_digestWorkerLinuxUser = null;
|
|
318
327
|
}
|
|
319
328
|
|
|
320
329
|
var responseText = "";
|
|
@@ -328,12 +337,14 @@ function attachMateInteraction(ctx) {
|
|
|
328
337
|
console.error("[digest-worker] Error:", err);
|
|
329
338
|
_digestWorker = null;
|
|
330
339
|
_digestWorkerTurns = 0;
|
|
340
|
+
_digestWorkerLinuxUser = null;
|
|
331
341
|
if (job.onError) job.onError(err);
|
|
332
342
|
processDigestQueue();
|
|
333
343
|
},
|
|
334
344
|
});
|
|
335
345
|
} else {
|
|
336
346
|
sdk.createMentionSession({
|
|
347
|
+
linuxUser: job.linuxUser || undefined,
|
|
337
348
|
claudeMd: "",
|
|
338
349
|
model: "haiku",
|
|
339
350
|
initialContext: "[Digest Worker] You generate memory digests. Respond with ONLY JSON.",
|
|
@@ -344,10 +355,15 @@ function attachMateInteraction(ctx) {
|
|
|
344
355
|
onError: function (err) {
|
|
345
356
|
console.error("[digest-worker] Create error:", err);
|
|
346
357
|
_digestWorker = null;
|
|
358
|
+
_digestWorkerLinuxUser = null;
|
|
347
359
|
if (job.onError) job.onError(err);
|
|
348
360
|
processDigestQueue();
|
|
349
361
|
},
|
|
350
|
-
}).then(function (ws) {
|
|
362
|
+
}).then(function (ws) {
|
|
363
|
+
_digestWorker = ws;
|
|
364
|
+
_digestWorkerTurns = 1;
|
|
365
|
+
_digestWorkerLinuxUser = job.linuxUser || null;
|
|
366
|
+
}).catch(function (err) {
|
|
351
367
|
if (job.onError) job.onError(err || new Error("digest worker creation failed"));
|
|
352
368
|
processDigestQueue();
|
|
353
369
|
});
|
|
@@ -378,6 +394,7 @@ function attachMateInteraction(ctx) {
|
|
|
378
394
|
|
|
379
395
|
enqueueDigest({
|
|
380
396
|
mateCtx: mateCtx,
|
|
397
|
+
linuxUser: mateCtx.linuxUser || undefined,
|
|
381
398
|
mateId: mateId,
|
|
382
399
|
type: "mention",
|
|
383
400
|
conversationContent: conversationContent,
|
|
@@ -473,6 +490,7 @@ function attachMateInteraction(ctx) {
|
|
|
473
490
|
|
|
474
491
|
enqueueDigest({
|
|
475
492
|
mateCtx: mateCtx,
|
|
493
|
+
linuxUser: mateCtx.linuxUser || undefined,
|
|
476
494
|
mateId: mateId,
|
|
477
495
|
type: "dm",
|
|
478
496
|
priorSummary: priorSummary || "",
|
|
@@ -716,6 +734,7 @@ function attachMateInteraction(ctx) {
|
|
|
716
734
|
// Create new persistent mention session
|
|
717
735
|
sdk.createMentionSession({
|
|
718
736
|
vendor: mate.vendor || null,
|
|
737
|
+
linuxUser: getLinuxUserForSession(session) || undefined,
|
|
719
738
|
claudeMd: claudeMd,
|
|
720
739
|
initialContext: mentionContext,
|
|
721
740
|
initialMessage: mentionFullInput,
|
|
@@ -887,6 +906,7 @@ function attachMateInteraction(ctx) {
|
|
|
887
906
|
].join("\n");
|
|
888
907
|
|
|
889
908
|
sdk.createMentionSession({
|
|
909
|
+
linuxUser: mateCtx.linuxUser || undefined,
|
|
890
910
|
claudeMd: "",
|
|
891
911
|
model: "haiku",
|
|
892
912
|
initialContext: synthesisContext,
|
package/lib/project-memory.js
CHANGED
|
@@ -339,6 +339,7 @@ function attachMemory(ctx) {
|
|
|
339
339
|
var gateText = "";
|
|
340
340
|
var _gateSession = null;
|
|
341
341
|
sdk.createMentionSession({
|
|
342
|
+
linuxUser: mateCtx.linuxUser || undefined,
|
|
342
343
|
claudeMd: "",
|
|
343
344
|
model: "haiku",
|
|
344
345
|
initialContext: gateContext,
|
|
@@ -448,6 +449,7 @@ function attachMemory(ctx) {
|
|
|
448
449
|
var updateText = "";
|
|
449
450
|
var _updateSession = null;
|
|
450
451
|
sdk.createMentionSession({
|
|
452
|
+
linuxUser: mateCtx.linuxUser || undefined,
|
|
451
453
|
claudeMd: "",
|
|
452
454
|
model: "haiku",
|
|
453
455
|
initialContext: updateContext,
|
|
@@ -553,6 +555,7 @@ function attachMemory(ctx) {
|
|
|
553
555
|
var initText = "";
|
|
554
556
|
var _initSession = null;
|
|
555
557
|
sdk.createMentionSession({
|
|
558
|
+
linuxUser: mateCtx.linuxUser || undefined,
|
|
556
559
|
claudeMd: "",
|
|
557
560
|
model: "haiku",
|
|
558
561
|
initialContext: initContext,
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
var { spawn } = require("child_process");
|
|
2
|
+
var { buildUserEnv } = require("./build-user-env");
|
|
3
|
+
var { wrapSpawnAsUser } = require("./os-users");
|
|
4
|
+
|
|
5
|
+
var MAX_COMMAND_LENGTH = 16 * 1024;
|
|
6
|
+
var MAX_OUTPUT_LENGTH = 64 * 1024;
|
|
7
|
+
var COMMAND_TIMEOUT_MS = 30 * 1000;
|
|
8
|
+
|
|
9
|
+
function stripTerminalCodes(value) {
|
|
10
|
+
return String(value || "")
|
|
11
|
+
.replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g, "")
|
|
12
|
+
.replace(/\x1b\[[0-?]*[ -\/]*[@-~]/g, "")
|
|
13
|
+
.replace(/\r/g, "");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function attachShellCommand(ctx) {
|
|
17
|
+
var runningBySocket = new WeakMap();
|
|
18
|
+
|
|
19
|
+
function sendResult(ws, data) {
|
|
20
|
+
ctx.sendTo(ws, Object.assign({ type: "shell_command_result" }, data));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function handleShellCommand(ws, msg) {
|
|
24
|
+
if (msg.type !== "shell_command") return false;
|
|
25
|
+
|
|
26
|
+
var requestId = typeof msg.requestId === "string" ? msg.requestId.slice(0, 100) : "";
|
|
27
|
+
var command = typeof msg.command === "string" ? msg.command.trim() : "";
|
|
28
|
+
if (!requestId || !command) {
|
|
29
|
+
sendResult(ws, { requestId: requestId, error: "Enter a command to run." });
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
if (command.length > MAX_COMMAND_LENGTH) {
|
|
33
|
+
sendResult(ws, { requestId: requestId, error: "Command is too long." });
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (ws._clayUser) {
|
|
38
|
+
var permissions = ctx.usersModule.getEffectivePermissions(ws._clayUser, ctx.osUsers);
|
|
39
|
+
if (!permissions.terminal) {
|
|
40
|
+
sendResult(ws, { requestId: requestId, error: "Terminal access is not permitted." });
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (runningBySocket.get(ws)) {
|
|
45
|
+
sendResult(ws, { requestId: requestId, error: "Another shell command is still running." });
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
var session = ctx.getSessionForWs(ws);
|
|
50
|
+
if (!session) {
|
|
51
|
+
sendResult(ws, { requestId: requestId, error: "No active session." });
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
var osUserInfo = ctx.getOsUserInfoForWs(ws);
|
|
56
|
+
var shell = (osUserInfo && osUserInfo.shell)
|
|
57
|
+
|| process.env.SHELL
|
|
58
|
+
|| (process.platform === "win32" ? process.env.COMSPEC || "cmd.exe" : "/bin/sh");
|
|
59
|
+
var args = process.platform === "win32" ? ["/d", "/s", "/c", command] : ["-lc", command];
|
|
60
|
+
var spawnOptions = {
|
|
61
|
+
cwd: ctx.cwd,
|
|
62
|
+
env: buildUserEnv(osUserInfo),
|
|
63
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
64
|
+
};
|
|
65
|
+
if (osUserInfo) {
|
|
66
|
+
spawnOptions.uid = osUserInfo.uid;
|
|
67
|
+
spawnOptions.gid = osUserInfo.gid;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
var wrapped = wrapSpawnAsUser(shell, args, spawnOptions);
|
|
71
|
+
var child;
|
|
72
|
+
try {
|
|
73
|
+
child = spawn(wrapped.command, wrapped.args, wrapped.options);
|
|
74
|
+
} catch (e) {
|
|
75
|
+
sendResult(ws, { requestId: requestId, error: e.message || "Failed to start command." });
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
runningBySocket.set(ws, child);
|
|
80
|
+
var chunks = [];
|
|
81
|
+
var outputLength = 0;
|
|
82
|
+
var truncated = false;
|
|
83
|
+
var timedOut = false;
|
|
84
|
+
var finished = false;
|
|
85
|
+
|
|
86
|
+
function collect(data) {
|
|
87
|
+
if (outputLength >= MAX_OUTPUT_LENGTH) {
|
|
88
|
+
truncated = true;
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
var text = data.toString("utf8");
|
|
92
|
+
var remaining = MAX_OUTPUT_LENGTH - outputLength;
|
|
93
|
+
if (text.length > remaining) {
|
|
94
|
+
text = text.slice(0, remaining);
|
|
95
|
+
truncated = true;
|
|
96
|
+
}
|
|
97
|
+
chunks.push(text);
|
|
98
|
+
outputLength += text.length;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
child.stdout.on("data", collect);
|
|
102
|
+
child.stderr.on("data", collect);
|
|
103
|
+
|
|
104
|
+
var timer = setTimeout(function () {
|
|
105
|
+
timedOut = true;
|
|
106
|
+
try { child.kill("SIGTERM"); } catch (e) {}
|
|
107
|
+
setTimeout(function () {
|
|
108
|
+
if (!finished) {
|
|
109
|
+
try { child.kill("SIGKILL"); } catch (e) {}
|
|
110
|
+
}
|
|
111
|
+
}, 1000).unref();
|
|
112
|
+
}, COMMAND_TIMEOUT_MS);
|
|
113
|
+
timer.unref();
|
|
114
|
+
|
|
115
|
+
child.on("error", function (error) {
|
|
116
|
+
if (finished) return;
|
|
117
|
+
finished = true;
|
|
118
|
+
clearTimeout(timer);
|
|
119
|
+
runningBySocket.delete(ws);
|
|
120
|
+
sendResult(ws, { requestId: requestId, command: command, error: error.message || "Command failed." });
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
child.on("close", function (code, signal) {
|
|
124
|
+
if (finished) return;
|
|
125
|
+
finished = true;
|
|
126
|
+
clearTimeout(timer);
|
|
127
|
+
runningBySocket.delete(ws);
|
|
128
|
+
|
|
129
|
+
var output = stripTerminalCodes(chunks.join(""));
|
|
130
|
+
if (truncated) output += "\n… output truncated at 64 KB";
|
|
131
|
+
if (timedOut) output += (output ? "\n" : "") + "Command timed out after 30 seconds.";
|
|
132
|
+
var exitCode = typeof code === "number" ? code : null;
|
|
133
|
+
var context = [
|
|
134
|
+
"[Shell command executed by the user]",
|
|
135
|
+
"$ " + command,
|
|
136
|
+
output || "(no output)",
|
|
137
|
+
"[Exit code: " + (exitCode == null ? (signal || "unknown") : exitCode) + "]",
|
|
138
|
+
].join("\n");
|
|
139
|
+
if (!session.pendingShellContexts) session.pendingShellContexts = [];
|
|
140
|
+
session.pendingShellContexts.push(context);
|
|
141
|
+
|
|
142
|
+
sendResult(ws, {
|
|
143
|
+
requestId: requestId,
|
|
144
|
+
sessionId: session.localId,
|
|
145
|
+
command: command,
|
|
146
|
+
output: output,
|
|
147
|
+
exitCode: exitCode,
|
|
148
|
+
signal: signal || null,
|
|
149
|
+
timedOut: timedOut,
|
|
150
|
+
truncated: truncated,
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return { handleShellCommand: handleShellCommand };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
module.exports = { attachShellCommand: attachShellCommand };
|
|
@@ -374,6 +374,16 @@ function attachUserMessage(ctx) {
|
|
|
374
374
|
fullText = mentionPrefix + "\n\n" + fullText;
|
|
375
375
|
}
|
|
376
376
|
|
|
377
|
+
// Inject one-shot shell results captured from the composer command mode.
|
|
378
|
+
if (session.pendingShellContexts && session.pendingShellContexts.length > 0) {
|
|
379
|
+
var shellPrefix = session.pendingShellContexts.join("\n\n");
|
|
380
|
+
session.pendingShellContexts = [];
|
|
381
|
+
fullText = shellPrefix + "\n\n" + fullText;
|
|
382
|
+
}
|
|
383
|
+
if (msg.shellCommandResponse) {
|
|
384
|
+
fullText += "\n\n[Respond to the shell command result above now.]";
|
|
385
|
+
}
|
|
386
|
+
|
|
377
387
|
// Inject active terminal context sources (delta only: send new output since last message)
|
|
378
388
|
var TERM_CONTEXT_MAX = 8192; // 8KB max per terminal per message
|
|
379
389
|
var TERM_HEAD_SIZE = 2048; // keep first 2KB for error context
|
package/lib/project.js
CHANGED
|
@@ -28,6 +28,7 @@ var { attachKnowledge } = require("./project-knowledge");
|
|
|
28
28
|
var { attachFilesystem } = require("./project-filesystem");
|
|
29
29
|
var { attachSessions } = require("./project-sessions");
|
|
30
30
|
var { attachUserMessage } = require("./project-user-message");
|
|
31
|
+
var { attachShellCommand } = require("./project-shell-command");
|
|
31
32
|
var { attachConnection } = require("./project-connection");
|
|
32
33
|
var { attachMcp } = require("./project-mcp");
|
|
33
34
|
var { createLocalMcp } = require("./mcp-local");
|
|
@@ -162,7 +163,7 @@ function createProjectContext(opts) {
|
|
|
162
163
|
var updateChannel = opts.updateChannel || "stable";
|
|
163
164
|
var osUsers = opts.osUsers || false;
|
|
164
165
|
var projectOwnerId = opts.projectOwnerId || null;
|
|
165
|
-
var worktreeMeta = opts.worktreeMeta || null; // { parentSlug, branch,
|
|
166
|
+
var worktreeMeta = opts.worktreeMeta || null; // { parentSlug, branch, external }
|
|
166
167
|
var isMate = opts.isMate || false;
|
|
167
168
|
var onCreateWorktree = opts.onCreateWorktree || null;
|
|
168
169
|
var serverPort = opts.port || 2633;
|
|
@@ -834,6 +835,7 @@ function createProjectContext(opts) {
|
|
|
834
835
|
getNotificationsModule: function () { return _notifications; },
|
|
835
836
|
mateDisplayName: opts.mateDisplayName || "",
|
|
836
837
|
isMate: isMate,
|
|
838
|
+
osUsers: osUsers,
|
|
837
839
|
dangerouslySkipPermissions: dangerouslySkipPermissions,
|
|
838
840
|
mcpServers: getLocalMcpServers,
|
|
839
841
|
getRemoteMcpServers: function () { return _mcp.getMcpServers(); },
|
|
@@ -1077,6 +1079,7 @@ function createProjectContext(opts) {
|
|
|
1077
1079
|
try {
|
|
1078
1080
|
var readyResult = await vendorAdapter.init({
|
|
1079
1081
|
cwd: cwd,
|
|
1082
|
+
linuxUser: modelLinuxUser || undefined,
|
|
1080
1083
|
clayPort: serverPort,
|
|
1081
1084
|
clayTls: serverTls,
|
|
1082
1085
|
clayAuthToken: serverAuthToken,
|
|
@@ -1176,6 +1179,9 @@ function createProjectContext(opts) {
|
|
|
1176
1179
|
// --- Filesystem, settings, env (delegated to project-filesystem.js) ---
|
|
1177
1180
|
if (_filesystem.handleFilesystemMessage(ws, msg)) return;
|
|
1178
1181
|
|
|
1182
|
+
// --- Shell command context ---
|
|
1183
|
+
if (_shellCommand.handleShellCommand(ws, msg)) return;
|
|
1184
|
+
|
|
1179
1185
|
// --- Notes, terminals, context, user message (delegated to project-user-message.js) ---
|
|
1180
1186
|
if (_userMessage.handleUserMessage(ws, msg)) return;
|
|
1181
1187
|
}
|
|
@@ -1424,6 +1430,15 @@ function createProjectContext(opts) {
|
|
|
1424
1430
|
_email: _email,
|
|
1425
1431
|
});
|
|
1426
1432
|
|
|
1433
|
+
var _shellCommand = attachShellCommand({
|
|
1434
|
+
cwd: cwd,
|
|
1435
|
+
osUsers: osUsers,
|
|
1436
|
+
usersModule: usersModule,
|
|
1437
|
+
sendTo: sendTo,
|
|
1438
|
+
getSessionForWs: getSessionForWs,
|
|
1439
|
+
getOsUserInfoForWs: getOsUserInfoForWs,
|
|
1440
|
+
});
|
|
1441
|
+
|
|
1427
1442
|
// --- Filesystem handler (delegated to project-filesystem.js) ---
|
|
1428
1443
|
var _filesystem = attachFilesystem({
|
|
1429
1444
|
cwd: cwd,
|
|
@@ -1673,8 +1688,8 @@ function createProjectContext(opts) {
|
|
|
1673
1688
|
getProject: function () { return project; },
|
|
1674
1689
|
// Exposed so the first websocket connection can lazily warm up the
|
|
1675
1690
|
// adapters for this project (see project-connection handleConnection).
|
|
1676
|
-
warmup: function () {
|
|
1677
|
-
sdk.warmup();
|
|
1691
|
+
warmup: function (linuxUser) {
|
|
1692
|
+
sdk.warmup(osUsers ? linuxUser : null);
|
|
1678
1693
|
sdk.startIdleReaper();
|
|
1679
1694
|
if (!osUsers && !sessionTitleMigrationScheduled) {
|
|
1680
1695
|
sessionTitleMigrationScheduled = true;
|
|
@@ -1800,7 +1815,7 @@ function createProjectContext(opts) {
|
|
|
1800
1815
|
status.isWorktree = true;
|
|
1801
1816
|
status.parentSlug = worktreeMeta.parentSlug;
|
|
1802
1817
|
status.branch = worktreeMeta.branch;
|
|
1803
|
-
status.
|
|
1818
|
+
status.worktreeExternal = worktreeMeta.external === true;
|
|
1804
1819
|
}
|
|
1805
1820
|
if (usersModule.isMultiUser()) {
|
|
1806
1821
|
var seen = {};
|
|
Binary file
|
package/lib/public/app.js
CHANGED
|
@@ -51,6 +51,7 @@ import { initTooltips, registerTooltip } from './modules/tooltip.js';
|
|
|
51
51
|
import { initMateWizard, openMateWizard, closeMateWizard, handleMateCreated } from './modules/mate-wizard.js';
|
|
52
52
|
import { initCommandPalette, handlePaletteSessionSwitch, setPaletteVersion } from './modules/command-palette.js';
|
|
53
53
|
import { initLongPress } from './modules/longpress.js';
|
|
54
|
+
import { initShellCommand } from './modules/shell-command.js';
|
|
54
55
|
import { initConnection, connect as _connConnect, setStatus as _connSetStatus, scheduleReconnect as _connScheduleReconnect, cancelReconnect as _connCancelReconnect } from './modules/app-connection.js';
|
|
55
56
|
import { processMessage as _msgProcessMessage } from './modules/app-messages.js';
|
|
56
57
|
import { getWs as _getWsRef, setWs as _setWsRef } from './modules/ws-ref.js';
|
|
@@ -294,6 +295,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
294
295
|
slashCommands: [],
|
|
295
296
|
processing: false,
|
|
296
297
|
activeSessionId: null,
|
|
298
|
+
sessionVendorBound: false,
|
|
297
299
|
cliSessionId: null,
|
|
298
300
|
isHeadlessMode: false,
|
|
299
301
|
savedMainSlug: null,
|
|
@@ -308,6 +310,9 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
308
310
|
returningFromMateDm: false,
|
|
309
311
|
pendingMateInterview: null,
|
|
310
312
|
pendingTermCommand: null,
|
|
313
|
+
shellCommandMode: false,
|
|
314
|
+
shellCommandRunning: false,
|
|
315
|
+
pendingShellCommandId: null,
|
|
311
316
|
mateProjectSlug: null,
|
|
312
317
|
myUserId: null,
|
|
313
318
|
isMultiUserMode: false,
|
|
@@ -552,6 +557,22 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
552
557
|
var newMsgBtnDefault = "\u2193 Latest";
|
|
553
558
|
var newMsgBtnActivity = "\u2193 New activity";
|
|
554
559
|
|
|
560
|
+
// The composer grows for mobile tabs, safe areas, multiline input, and the
|
|
561
|
+
// keyboard. Keep the jump button immediately above its real height instead
|
|
562
|
+
// of relying on one fixed bottom offset.
|
|
563
|
+
function positionNewMsgButton() {
|
|
564
|
+
var inputArea = $("input-area");
|
|
565
|
+
if (!inputArea) return;
|
|
566
|
+
newMsgBtn.style.bottom = (inputArea.offsetHeight + 12) + "px";
|
|
567
|
+
}
|
|
568
|
+
var inputAreaForNewMsgBtn = $("input-area");
|
|
569
|
+
if (inputAreaForNewMsgBtn && window.ResizeObserver) {
|
|
570
|
+
new ResizeObserver(positionNewMsgButton).observe(inputAreaForNewMsgBtn);
|
|
571
|
+
} else {
|
|
572
|
+
window.addEventListener("resize", positionNewMsgButton);
|
|
573
|
+
}
|
|
574
|
+
positionNewMsgButton();
|
|
575
|
+
|
|
555
576
|
messagesEl.addEventListener("scroll", function () {
|
|
556
577
|
// While sticky-bottom is armed (e.g. just after history_done or a "New
|
|
557
578
|
// activity" click), suppress "user scrolled up" detection. Growth-induced
|
|
@@ -793,6 +814,8 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
793
814
|
},
|
|
794
815
|
});
|
|
795
816
|
|
|
817
|
+
initShellCommand();
|
|
818
|
+
|
|
796
819
|
// --- @Mention module ---
|
|
797
820
|
initMention({
|
|
798
821
|
get ws() { return _getWsRef(); },
|
|
@@ -879,6 +902,10 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
879
902
|
if (termBtn) termBtn.style.display = "none";
|
|
880
903
|
var termSideBtn = document.getElementById("terminal-sidebar-btn");
|
|
881
904
|
if (termSideBtn) termSideBtn.style.display = "none";
|
|
905
|
+
var shellCommandBtn = document.getElementById("shell-command-btn");
|
|
906
|
+
if (shellCommandBtn) shellCommandBtn.style.display = "none";
|
|
907
|
+
var mobileShellCommandBtn = document.getElementById("input-more-shell");
|
|
908
|
+
if (mobileShellCommandBtn) mobileShellCommandBtn.style.display = "none";
|
|
882
909
|
}
|
|
883
910
|
if (!_perms.fileBrowser) {
|
|
884
911
|
var fbBtn = document.getElementById("file-browser-btn");
|
|
@@ -922,6 +949,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
922
949
|
messagesEl: messagesEl,
|
|
923
950
|
sessionListEl: sessionListEl,
|
|
924
951
|
scrollToBottom: scrollToBottom,
|
|
952
|
+
forceScrollToBottom: forceScrollToBottom,
|
|
925
953
|
basePath: basePath,
|
|
926
954
|
toggleUsagePanel: toggleUsagePanel,
|
|
927
955
|
toggleStatusPanel: toggleStatusPanel,
|
|
@@ -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>
|
|
@@ -434,6 +434,7 @@
|
|
|
434
434
|
on hover/active, and a 4-direction drop-shadow + white glow on the
|
|
435
435
|
emoji img for readability on either background. */
|
|
436
436
|
.project-switcher-item .cmd-palette-item-icon {
|
|
437
|
+
position: relative;
|
|
437
438
|
width: 44px;
|
|
438
439
|
height: 44px;
|
|
439
440
|
font-size: 28px;
|
|
@@ -447,6 +448,26 @@
|
|
|
447
448
|
flex-shrink: 0;
|
|
448
449
|
transition: background 0.15s, color 0.15s;
|
|
449
450
|
}
|
|
451
|
+
.project-switcher-external-badge {
|
|
452
|
+
position: absolute;
|
|
453
|
+
right: -5px;
|
|
454
|
+
bottom: -5px;
|
|
455
|
+
width: 17px;
|
|
456
|
+
height: 17px;
|
|
457
|
+
display: flex;
|
|
458
|
+
align-items: center;
|
|
459
|
+
justify-content: center;
|
|
460
|
+
border: 2px solid var(--bg);
|
|
461
|
+
border-radius: 50%;
|
|
462
|
+
background: var(--warning);
|
|
463
|
+
color: var(--bg);
|
|
464
|
+
pointer-events: none;
|
|
465
|
+
}
|
|
466
|
+
.project-switcher-item .cmd-palette-item-icon .project-switcher-external-badge .lucide {
|
|
467
|
+
width: 9px;
|
|
468
|
+
height: 9px;
|
|
469
|
+
stroke-width: 2.8;
|
|
470
|
+
}
|
|
450
471
|
.project-switcher-item .cmd-palette-item-icon .lucide {
|
|
451
472
|
width: 22px;
|
|
452
473
|
height: 22px;
|
|
@@ -515,8 +536,7 @@
|
|
|
515
536
|
letter-spacing: 0.5px;
|
|
516
537
|
}
|
|
517
538
|
|
|
518
|
-
/* Disabled
|
|
519
|
-
valid switch target, so skip highlight, skip click, visually dim. */
|
|
539
|
+
/* Disabled tiles are skipped by keyboard and pointer selection. */
|
|
520
540
|
.project-switcher-item.disabled {
|
|
521
541
|
opacity: 0.35;
|
|
522
542
|
cursor: not-allowed;
|
|
@@ -1257,20 +1257,27 @@
|
|
|
1257
1257
|
z-index: 1;
|
|
1258
1258
|
}
|
|
1259
1259
|
|
|
1260
|
-
/*
|
|
1261
|
-
.
|
|
1262
|
-
|
|
1263
|
-
cursor: not-allowed;
|
|
1264
|
-
}
|
|
1265
|
-
|
|
1266
|
-
.icon-strip-wt-item.wt-disabled::after {
|
|
1267
|
-
content: "\1F6AB";
|
|
1260
|
+
/* Worktree registered outside the main project folder. It remains usable;
|
|
1261
|
+
the badge communicates location without treating it as an error. */
|
|
1262
|
+
.worktree-external-badge {
|
|
1268
1263
|
position: absolute;
|
|
1269
|
-
|
|
1264
|
+
right: 5px;
|
|
1265
|
+
bottom: 1px;
|
|
1266
|
+
width: 14px;
|
|
1267
|
+
height: 14px;
|
|
1268
|
+
display: flex;
|
|
1269
|
+
align-items: center;
|
|
1270
|
+
justify-content: center;
|
|
1271
|
+
border-radius: 50%;
|
|
1272
|
+
border: 2px solid var(--sidebar-bg);
|
|
1273
|
+
background: var(--warning);
|
|
1274
|
+
color: var(--bg);
|
|
1270
1275
|
z-index: 3;
|
|
1271
|
-
|
|
1276
|
+
pointer-events: none;
|
|
1272
1277
|
}
|
|
1273
1278
|
|
|
1279
|
+
.worktree-external-badge .lucide { width: 8px; height: 8px; stroke-width: 2.8; }
|
|
1280
|
+
|
|
1274
1281
|
/* Group add button */
|
|
1275
1282
|
.icon-strip-group-add {
|
|
1276
1283
|
width: 30px;
|
|
@@ -1420,11 +1427,20 @@ select.wt-modal-input {
|
|
|
1420
1427
|
opacity: 0.85;
|
|
1421
1428
|
}
|
|
1422
1429
|
|
|
1423
|
-
.mobile-
|
|
1424
|
-
|
|
1425
|
-
|
|
1430
|
+
.mobile-worktree-external-badge {
|
|
1431
|
+
width: 20px;
|
|
1432
|
+
height: 20px;
|
|
1433
|
+
display: inline-flex;
|
|
1434
|
+
align-items: center;
|
|
1435
|
+
justify-content: center;
|
|
1436
|
+
border-radius: 7px;
|
|
1437
|
+
background: color-mix(in srgb, var(--warning) 14%, transparent);
|
|
1438
|
+
color: var(--warning);
|
|
1439
|
+
flex-shrink: 0;
|
|
1426
1440
|
}
|
|
1427
1441
|
|
|
1442
|
+
.mobile-worktree-external-badge .lucide { width: 12px; height: 12px; stroke-width: 2.4; }
|
|
1443
|
+
|
|
1428
1444
|
.mobile-folder-chevron {
|
|
1429
1445
|
font-size: 8px;
|
|
1430
1446
|
color: var(--text-dimmer);
|