clay-server 3.4.0-beta.1 → 3.4.0-beta.3
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/project-shell-command.js +160 -0
- package/lib/project-user-message.js +10 -0
- package/lib/project.js +13 -0
- package/lib/public/antigravity-avatar.png +0 -0
- package/lib/public/app.js +10 -0
- package/lib/public/css/input.css +63 -0
- package/lib/public/index.html +8 -3
- package/lib/public/modules/app-messages.js +16 -1
- package/lib/public/modules/app-panels.js +4 -4
- package/lib/public/modules/app-projects.js +2 -0
- package/lib/public/modules/app-rendering.js +4 -4
- package/lib/public/modules/input.js +58 -28
- package/lib/public/modules/mate-sidebar.js +3 -3
- package/lib/public/modules/shell-command.js +148 -0
- package/lib/public/modules/sidebar-mates.js +1 -1
- package/lib/public/modules/tools.js +1 -1
- package/lib/sdk-bridge.js +16 -8
- package/lib/sdk-message-processor.js +10 -1
- package/lib/ws-schema.js +3 -0
- package/lib/yoke/acp-agent-profiles.js +0 -14
- package/lib/yoke/adapters/antigravity.js +417 -0
- package/lib/yoke/index.js +25 -20
- package/lib/yoke/vendor-registry.js +6 -6
- package/package.json +1 -1
- package/lib/public/gemini-avatar.svg +0 -11
- package/lib/yoke/adapters/gemini.js +0 -7
|
@@ -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");
|
|
@@ -1176,6 +1177,9 @@ function createProjectContext(opts) {
|
|
|
1176
1177
|
// --- Filesystem, settings, env (delegated to project-filesystem.js) ---
|
|
1177
1178
|
if (_filesystem.handleFilesystemMessage(ws, msg)) return;
|
|
1178
1179
|
|
|
1180
|
+
// --- Shell command context ---
|
|
1181
|
+
if (_shellCommand.handleShellCommand(ws, msg)) return;
|
|
1182
|
+
|
|
1179
1183
|
// --- Notes, terminals, context, user message (delegated to project-user-message.js) ---
|
|
1180
1184
|
if (_userMessage.handleUserMessage(ws, msg)) return;
|
|
1181
1185
|
}
|
|
@@ -1424,6 +1428,15 @@ function createProjectContext(opts) {
|
|
|
1424
1428
|
_email: _email,
|
|
1425
1429
|
});
|
|
1426
1430
|
|
|
1431
|
+
var _shellCommand = attachShellCommand({
|
|
1432
|
+
cwd: cwd,
|
|
1433
|
+
osUsers: osUsers,
|
|
1434
|
+
usersModule: usersModule,
|
|
1435
|
+
sendTo: sendTo,
|
|
1436
|
+
getSessionForWs: getSessionForWs,
|
|
1437
|
+
getOsUserInfoForWs: getOsUserInfoForWs,
|
|
1438
|
+
});
|
|
1439
|
+
|
|
1427
1440
|
// --- Filesystem handler (delegated to project-filesystem.js) ---
|
|
1428
1441
|
var _filesystem = attachFilesystem({
|
|
1429
1442
|
cwd: cwd,
|
|
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';
|
|
@@ -308,6 +309,9 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
308
309
|
returningFromMateDm: false,
|
|
309
310
|
pendingMateInterview: null,
|
|
310
311
|
pendingTermCommand: null,
|
|
312
|
+
shellCommandMode: false,
|
|
313
|
+
shellCommandRunning: false,
|
|
314
|
+
pendingShellCommandId: null,
|
|
311
315
|
mateProjectSlug: null,
|
|
312
316
|
myUserId: null,
|
|
313
317
|
isMultiUserMode: false,
|
|
@@ -793,6 +797,8 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
793
797
|
},
|
|
794
798
|
});
|
|
795
799
|
|
|
800
|
+
initShellCommand();
|
|
801
|
+
|
|
796
802
|
// --- @Mention module ---
|
|
797
803
|
initMention({
|
|
798
804
|
get ws() { return _getWsRef(); },
|
|
@@ -879,6 +885,10 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
879
885
|
if (termBtn) termBtn.style.display = "none";
|
|
880
886
|
var termSideBtn = document.getElementById("terminal-sidebar-btn");
|
|
881
887
|
if (termSideBtn) termSideBtn.style.display = "none";
|
|
888
|
+
var shellCommandBtn = document.getElementById("shell-command-btn");
|
|
889
|
+
if (shellCommandBtn) shellCommandBtn.style.display = "none";
|
|
890
|
+
var mobileShellCommandBtn = document.getElementById("input-more-shell");
|
|
891
|
+
if (mobileShellCommandBtn) mobileShellCommandBtn.style.display = "none";
|
|
882
892
|
}
|
|
883
893
|
if (!_perms.fileBrowser) {
|
|
884
894
|
var fbBtn = document.getElementById("file-browser-btn");
|
package/lib/public/css/input.css
CHANGED
|
@@ -826,6 +826,7 @@
|
|
|
826
826
|
|
|
827
827
|
#attach-file-btn,
|
|
828
828
|
#attach-image-btn,
|
|
829
|
+
#shell-command-btn,
|
|
829
830
|
#schedule-btn,
|
|
830
831
|
#input-more-btn {
|
|
831
832
|
width: 36px;
|
|
@@ -844,14 +845,76 @@
|
|
|
844
845
|
|
|
845
846
|
#attach-file-btn .lucide,
|
|
846
847
|
#attach-image-btn .lucide,
|
|
848
|
+
#shell-command-btn .lucide,
|
|
847
849
|
#schedule-btn .lucide,
|
|
848
850
|
#input-more-btn .lucide { width: 20px; height: 20px; flex-shrink: 0; }
|
|
849
851
|
|
|
850
852
|
#attach-file-btn:hover,
|
|
851
853
|
#attach-image-btn:hover,
|
|
854
|
+
#shell-command-btn:hover,
|
|
852
855
|
#schedule-btn:hover,
|
|
853
856
|
#input-more-btn:hover { background: rgba(var(--overlay-rgb), 0.06); color: var(--text); }
|
|
854
857
|
|
|
858
|
+
#shell-command-btn.active {
|
|
859
|
+
background: var(--accent-12);
|
|
860
|
+
color: var(--accent);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
#input-row.shell-command-mode {
|
|
864
|
+
box-shadow: 0 0 0 1px var(--accent);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
#input-row.shell-command-mode #input {
|
|
868
|
+
font-family: var(--font-mono, "SFMono-Regular", Consolas, monospace);
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
.shell-command-card {
|
|
872
|
+
width: min(760px, calc(100% - 24px));
|
|
873
|
+
margin: 10px auto;
|
|
874
|
+
border: 1px solid var(--border);
|
|
875
|
+
border-radius: 8px;
|
|
876
|
+
background: var(--input-bg);
|
|
877
|
+
overflow: hidden;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
.shell-command-card.error { border-color: rgba(239, 68, 68, 0.45); }
|
|
881
|
+
|
|
882
|
+
.shell-command-header {
|
|
883
|
+
display: flex;
|
|
884
|
+
align-items: center;
|
|
885
|
+
gap: 8px;
|
|
886
|
+
padding: 9px 12px;
|
|
887
|
+
border-bottom: 1px solid var(--border);
|
|
888
|
+
color: var(--text-secondary);
|
|
889
|
+
font-size: 12px;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
.shell-command-header code {
|
|
893
|
+
min-width: 0;
|
|
894
|
+
flex: 1;
|
|
895
|
+
overflow: hidden;
|
|
896
|
+
text-overflow: ellipsis;
|
|
897
|
+
white-space: nowrap;
|
|
898
|
+
color: var(--text);
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
.shell-command-icon { display: inline-flex; }
|
|
902
|
+
.shell-command-icon .lucide { width: 16px; height: 16px; }
|
|
903
|
+
.shell-command-status { flex-shrink: 0; color: var(--text-muted); }
|
|
904
|
+
|
|
905
|
+
.shell-command-card.running .shell-command-status { color: var(--accent); }
|
|
906
|
+
|
|
907
|
+
.shell-command-output {
|
|
908
|
+
max-height: 320px;
|
|
909
|
+
margin: 0;
|
|
910
|
+
padding: 12px;
|
|
911
|
+
overflow: auto;
|
|
912
|
+
color: var(--text-secondary);
|
|
913
|
+
font: 12px/1.5 var(--font-mono, "SFMono-Regular", Consolas, monospace);
|
|
914
|
+
white-space: pre-wrap;
|
|
915
|
+
word-break: break-word;
|
|
916
|
+
}
|
|
917
|
+
|
|
855
918
|
#ask-mate-btn {
|
|
856
919
|
width: 36px;
|
|
857
920
|
height: 36px;
|
package/lib/public/index.html
CHANGED
|
@@ -505,6 +505,7 @@
|
|
|
505
505
|
<button id="input-more-btn" type="button" aria-label="More options" title="More options" class="mobile-only"><i data-lucide="plus"></i></button>
|
|
506
506
|
<button id="attach-file-btn" type="button" aria-label="Attach file" title="Attach file" class="desktop-only"><i data-lucide="paperclip"></i></button>
|
|
507
507
|
<button id="attach-image-btn" type="button" aria-label="Attach image" title="Attach image" class="desktop-only"><i data-lucide="image"></i></button>
|
|
508
|
+
<button id="shell-command-btn" type="button" aria-label="Run shell command" aria-pressed="false" title="Run a shell command and add its output to the next agent message" class="desktop-only"><i data-lucide="square-terminal"></i></button>
|
|
508
509
|
<button id="stt-btn" type="button" aria-label="Voice input" title="Voice input"><i data-lucide="mic"></i></button>
|
|
509
510
|
<button id="schedule-btn" type="button" aria-label="Schedule message" title="Schedule message"><i data-lucide="clock"></i></button>
|
|
510
511
|
<button id="ask-mate-btn" type="button" aria-label="Ask Mate" title="Ask a Mate for advice on this session"><i data-lucide="at-sign"></i></button>
|
|
@@ -527,9 +528,9 @@
|
|
|
527
528
|
<img src="/codex-avatar.png" class="vendor-toggle-icon" alt="Codex">
|
|
528
529
|
<span class="vendor-toggle-label">Codex</span>
|
|
529
530
|
</button>
|
|
530
|
-
<button id="vendor-btn-
|
|
531
|
-
<img src="/
|
|
532
|
-
<span class="vendor-toggle-label">
|
|
531
|
+
<button id="vendor-btn-antigravity" class="vendor-toggle-btn" data-vendor="antigravity">
|
|
532
|
+
<img src="/antigravity-avatar.png" class="vendor-toggle-icon" alt="Antigravity">
|
|
533
|
+
<span class="vendor-toggle-label">Antigravity CLI</span>
|
|
533
534
|
</button>
|
|
534
535
|
<button id="vendor-btn-opencode" class="vendor-toggle-btn" data-vendor="opencode">
|
|
535
536
|
<img src="/opencode-avatar.svg" class="vendor-toggle-icon" alt="OpenCode">
|
|
@@ -615,6 +616,10 @@
|
|
|
615
616
|
<i data-lucide="image"></i>
|
|
616
617
|
<span>Image</span>
|
|
617
618
|
</button>
|
|
619
|
+
<button class="input-more-action" id="input-more-shell">
|
|
620
|
+
<i data-lucide="square-terminal"></i>
|
|
621
|
+
<span>Shell command</span>
|
|
622
|
+
</button>
|
|
618
623
|
</div>
|
|
619
624
|
<div class="input-more-divider"></div>
|
|
620
625
|
<div class="input-more-section-label">
|
|
@@ -22,7 +22,7 @@ import { handleFindInSessionResults } from './session-search.js';
|
|
|
22
22
|
import { syncPaneTitles, maybeRestoreSplitGroup, openGroup } from './split-view.js';
|
|
23
23
|
import { showPairDialog, handlePairCreated, handleSplitDelegation, showWorkerDelegationNotice, hideWorkerDelegationNotice } from './split-pair-ui.js';
|
|
24
24
|
import { renderWorkerProposal, updateWorkerProposal } from './worker-proposal.js';
|
|
25
|
-
import { handleInputSync, autoResize, builtinCommands, setScheduleBtnDisabled } from './input.js';
|
|
25
|
+
import { handleInputSync, autoResize, builtinCommands, setScheduleBtnDisabled, sendShellResultToAgent } from './input.js';
|
|
26
26
|
import { startThinking, appendThinking, stopThinking, resetThinkingGroup, createToolItem, updateToolExecuting, updateToolResult, markAllToolsDone, closeToolGroup, removeToolFromGroup, resetToolState, getTools, getPlanContent, setPlanContent, renderPlanBanner, renderPlanCard, getTodoTools, handleTodoWrite, handleTaskCreate, handleTaskUpdate, applyDeadSessionTodoCompaction, isPlanFilePath, enableMainInput, addTurnMeta, updateSubagentActivity, addSubagentToolEntry, markSubagentDone, initSubagentStop, updateSubagentProgress, updateSubagentTaskStatus, renderAskUserQuestion, markAskUserAnswered, renderPermissionRequest, markPermissionCancelled, markPermissionResolved, renderElicitationRequest, markElicitationResolved, renderUserDialogRequest, markUserDialogResolved, updateThinkingTokens } from './tools.js';
|
|
27
27
|
import { showDoneNotification, playDoneSound, isNotifAlertEnabled, isNotifSoundEnabled } from './notifications.js';
|
|
28
28
|
import { handleFsList, handleFsRead, handleFileChanged, handleDirChanged, handleFileHistory, handleGitDiff, handleFileAt, refreshIfOpen, getPendingNavigate, handleFsSearch, presentMarkdownEdit } from './filebrowser.js';
|
|
@@ -42,6 +42,7 @@ import { checkAdminAccess } from './admin.js';
|
|
|
42
42
|
import { mateAvatarUrl } from './avatar.js';
|
|
43
43
|
import { showImageModal, sendExtensionCommand, handleMcpToolCallMessage } from './app-misc.js';
|
|
44
44
|
import { handleMcpServersState } from './mcp-ui.js';
|
|
45
|
+
import { handleShellCommandResult } from './shell-command.js';
|
|
45
46
|
import { handleLoopRegistryUpdated, handleScheduleRunStarted, handleScheduleRunFinished, handleLoopScheduled, isSchedulerOpen, enterCraftingMode, exitCraftingMode, handleLoopRegistryFiles } from './scheduler.js';
|
|
46
47
|
|
|
47
48
|
// --- App module imports ---
|
|
@@ -1209,6 +1210,15 @@ export function processMessage(msg) {
|
|
|
1209
1210
|
addConflictMessage(msg);
|
|
1210
1211
|
break;
|
|
1211
1212
|
|
|
1213
|
+
case "session_writer_conflict":
|
|
1214
|
+
removeMatePreThinking();
|
|
1215
|
+
setActivity(null);
|
|
1216
|
+
stopThinking();
|
|
1217
|
+
markAllToolsDone();
|
|
1218
|
+
closeToolGroup();
|
|
1219
|
+
addSystemMessage(msg.text || "This session is already open in another process.", true);
|
|
1220
|
+
break;
|
|
1221
|
+
|
|
1212
1222
|
case "context_overflow":
|
|
1213
1223
|
removeMatePreThinking();
|
|
1214
1224
|
setActivity(null);
|
|
@@ -1371,6 +1381,11 @@ export function processMessage(msg) {
|
|
|
1371
1381
|
updateTerminalList(msg.terminals);
|
|
1372
1382
|
break;
|
|
1373
1383
|
|
|
1384
|
+
case "shell_command_result":
|
|
1385
|
+
handleShellCommandResult(msg);
|
|
1386
|
+
if (!msg.error) sendShellResultToAgent(msg);
|
|
1387
|
+
break;
|
|
1388
|
+
|
|
1374
1389
|
case "context_sources_state":
|
|
1375
1390
|
handleContextSourcesState(msg);
|
|
1376
1391
|
break;
|
|
@@ -89,7 +89,7 @@ var EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
|
|
|
89
89
|
var EFFORT_LEVELS_BY_VENDOR = {
|
|
90
90
|
claude: ["low", "medium", "high", "xhigh", "max"],
|
|
91
91
|
codex: ["minimal", "low", "medium", "high", "xhigh"],
|
|
92
|
-
|
|
92
|
+
antigravity: [],
|
|
93
93
|
opencode: [],
|
|
94
94
|
kiro: ["low", "medium", "high", "xhigh", "max"],
|
|
95
95
|
};
|
|
@@ -443,13 +443,13 @@ export function initPanels() {
|
|
|
443
443
|
var vendorToggleWrap = $("vendor-toggle-wrap");
|
|
444
444
|
var vendorBtnClaude = $("vendor-btn-claude");
|
|
445
445
|
var vendorBtnCodex = $("vendor-btn-codex");
|
|
446
|
-
var
|
|
446
|
+
var vendorBtnAntigravity = $("vendor-btn-antigravity");
|
|
447
447
|
var vendorBtnOpenCode = $("vendor-btn-opencode");
|
|
448
448
|
var vendorBtnKiro = $("vendor-btn-kiro");
|
|
449
449
|
var vendorBtns = {
|
|
450
450
|
claude: vendorBtnClaude,
|
|
451
451
|
codex: vendorBtnCodex,
|
|
452
|
-
|
|
452
|
+
antigravity: vendorBtnAntigravity,
|
|
453
453
|
opencode: vendorBtnOpenCode,
|
|
454
454
|
kiro: vendorBtnKiro,
|
|
455
455
|
};
|
|
@@ -481,7 +481,7 @@ export function initPanels() {
|
|
|
481
481
|
|
|
482
482
|
if (vendorBtnClaude) vendorBtnClaude.addEventListener("click", function() { onVendorClick("claude"); });
|
|
483
483
|
if (vendorBtnCodex) vendorBtnCodex.addEventListener("click", function() { onVendorClick("codex"); });
|
|
484
|
-
if (
|
|
484
|
+
if (vendorBtnAntigravity) vendorBtnAntigravity.addEventListener("click", function() { onVendorClick("antigravity"); });
|
|
485
485
|
if (vendorBtnOpenCode) vendorBtnOpenCode.addEventListener("click", function() { onVendorClick("opencode"); });
|
|
486
486
|
if (vendorBtnKiro) vendorBtnKiro.addEventListener("click", function() { onVendorClick("kiro"); });
|
|
487
487
|
|
|
@@ -24,6 +24,7 @@ import { isSchedulerOpen, closeScheduler, resetScheduler } from './scheduler.js'
|
|
|
24
24
|
import { connect, cancelReconnect, setStatus } from './app-connection.js';
|
|
25
25
|
import { setTurnCounter, setPrependAnchor, setActivityEl, setIsUserScrolledUp, hideSuggestionChips } from './app-rendering.js';
|
|
26
26
|
import { resetToolState, enableMainInput, resetTurnMetaCost } from './tools.js';
|
|
27
|
+
import { resetShellCommand } from './shell-command.js';
|
|
27
28
|
import { clearPendingImages } from './input.js';
|
|
28
29
|
import { clearAllMentionActive } from './sidebar-mates.js';
|
|
29
30
|
import { setRewindMode } from './rewind.js';
|
|
@@ -368,6 +369,7 @@ export function resetClientState() {
|
|
|
368
369
|
store.set({ currentFullText: "" });
|
|
369
370
|
resetToolState();
|
|
370
371
|
clearPendingImages();
|
|
372
|
+
resetShellCommand();
|
|
371
373
|
clearAllMentionActive();
|
|
372
374
|
setActivityEl(null);
|
|
373
375
|
store.set({ processing: false });
|
|
@@ -19,26 +19,26 @@ import { getScheduledMsgEl } from './app-rate-limit.js';
|
|
|
19
19
|
export var VENDOR_AVATARS = {
|
|
20
20
|
claude: "/claude-code-avatar.png",
|
|
21
21
|
codex: "/codex-avatar.png",
|
|
22
|
-
|
|
22
|
+
antigravity: "/antigravity-avatar.png",
|
|
23
23
|
opencode: "/opencode-avatar.svg",
|
|
24
24
|
kiro: "/kiro-avatar.svg",
|
|
25
25
|
};
|
|
26
26
|
export var VENDOR_NAMES = {
|
|
27
27
|
claude: "Claude Code",
|
|
28
28
|
codex: "Codex",
|
|
29
|
-
|
|
29
|
+
antigravity: "Antigravity CLI",
|
|
30
30
|
opencode: "OpenCode",
|
|
31
31
|
kiro: "Kiro CLI",
|
|
32
32
|
};
|
|
33
33
|
// Display order for every vendor Clay knows about, installed or not. Pickers
|
|
34
34
|
// render the full list so a missing CLI reads as "not installed yet" rather
|
|
35
35
|
// than "Clay doesn't support it".
|
|
36
|
-
export var VENDOR_ORDER = ["claude", "codex", "
|
|
36
|
+
export var VENDOR_ORDER = ["claude", "codex", "antigravity", "opencode", "kiro"];
|
|
37
37
|
// Where to send the user when they pick a vendor whose CLI isn't installed.
|
|
38
38
|
export var VENDOR_HOMEPAGES = {
|
|
39
39
|
claude: "https://claude.com/product/claude-code",
|
|
40
40
|
codex: "https://openai.com/codex/",
|
|
41
|
-
|
|
41
|
+
antigravity: "https://antigravity.google/product/antigravity-cli",
|
|
42
42
|
opencode: "https://opencode.ai/",
|
|
43
43
|
kiro: "https://kiro.dev/",
|
|
44
44
|
};
|
|
@@ -7,6 +7,7 @@ import { mateAvatarUrl } from './avatar.js';
|
|
|
7
7
|
import { tuiIsActive, tuiSubmitText } from './session-tui-view.js';
|
|
8
8
|
import { VENDOR_AVATARS, VENDOR_NAMES } from './app-rendering.js';
|
|
9
9
|
import { showToast } from './utils.js';
|
|
10
|
+
import { isShellCommandMode, submitShellCommand } from './shell-command.js';
|
|
10
11
|
|
|
11
12
|
var ctx;
|
|
12
13
|
|
|
@@ -75,6 +76,37 @@ export var builtinCommands = [
|
|
|
75
76
|
{ name: "status", desc: "Process status and resource usage" },
|
|
76
77
|
];
|
|
77
78
|
|
|
79
|
+
function commitVendorForTurn() {
|
|
80
|
+
var committedVendor = store.get('currentVendor');
|
|
81
|
+
var vendorToggle = document.getElementById("vendor-toggle-wrap");
|
|
82
|
+
var activeIndicator = document.getElementById("active-vendor-indicator");
|
|
83
|
+
var activeIcon = document.getElementById("active-vendor-icon");
|
|
84
|
+
if (committedVendor) {
|
|
85
|
+
if (vendorToggle) {
|
|
86
|
+
vendorToggle.classList.add("hidden");
|
|
87
|
+
vendorToggle.classList.remove("locked");
|
|
88
|
+
}
|
|
89
|
+
if (activeIndicator && activeIcon) {
|
|
90
|
+
activeIcon.src = VENDOR_AVATARS[committedVendor] || VENDOR_AVATARS.claude;
|
|
91
|
+
activeIcon.alt = VENDOR_NAMES[committedVendor] || VENDOR_NAMES.claude;
|
|
92
|
+
activeIndicator.title = (VENDOR_NAMES[committedVendor] || VENDOR_NAMES.claude) + " session";
|
|
93
|
+
activeIndicator.classList.remove("hidden");
|
|
94
|
+
}
|
|
95
|
+
} else if (vendorToggle) {
|
|
96
|
+
vendorToggle.classList.remove("hidden");
|
|
97
|
+
vendorToggle.classList.add("locked");
|
|
98
|
+
}
|
|
99
|
+
store.set({ vendorSelectionLocked: false, sessionHasHistory: true });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function showPreThinkingForTurn() {
|
|
103
|
+
if (ctx.isMateDm && ctx.isMateDm()) {
|
|
104
|
+
ctx.showMatePreThinking();
|
|
105
|
+
} else if (ctx.showClaudePreThinking) {
|
|
106
|
+
ctx.showClaudePreThinking();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
78
110
|
// --- Send ---
|
|
79
111
|
export function sendMessage() {
|
|
80
112
|
// Debate ended mode intercept: route to resume handler
|
|
@@ -92,6 +124,14 @@ export function sendMessage() {
|
|
|
92
124
|
ctx.handleDebateFloorSend();
|
|
93
125
|
return;
|
|
94
126
|
}
|
|
127
|
+
if (isShellCommandMode()) {
|
|
128
|
+
var shellText = ctx.inputEl.value.trim();
|
|
129
|
+
if (!shellText || !submitShellCommand(shellText)) return;
|
|
130
|
+
ctx.inputEl.value = "";
|
|
131
|
+
sendInputSync();
|
|
132
|
+
autoResize();
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
95
135
|
// DM mode intercept: if in DM mode, route to DM handler instead
|
|
96
136
|
if (ctx.isDmMode && ctx.isDmMode() && ctx.handleDmSend) {
|
|
97
137
|
ctx.handleDmSend();
|
|
@@ -304,36 +344,10 @@ export function sendMessage() {
|
|
|
304
344
|
// Bumping sessionHasHistory drives in-session lock states (e.g. the
|
|
305
345
|
// Codex model picker that becomes informational once the thread is
|
|
306
346
|
// bound to a model).
|
|
307
|
-
|
|
308
|
-
var _vtw2 = document.getElementById("vendor-toggle-wrap");
|
|
309
|
-
var _avi = document.getElementById("active-vendor-indicator");
|
|
310
|
-
var _avIcon = document.getElementById("active-vendor-icon");
|
|
311
|
-
if (_committedVendor) {
|
|
312
|
-
if (_vtw2) {
|
|
313
|
-
_vtw2.classList.add("hidden");
|
|
314
|
-
_vtw2.classList.remove("locked");
|
|
315
|
-
}
|
|
316
|
-
if (_avi && _avIcon) {
|
|
317
|
-
_avIcon.src = VENDOR_AVATARS[_committedVendor] || VENDOR_AVATARS.claude;
|
|
318
|
-
_avIcon.alt = VENDOR_NAMES[_committedVendor] || VENDOR_NAMES.claude;
|
|
319
|
-
_avi.title = (VENDOR_NAMES[_committedVendor] || VENDOR_NAMES.claude) + " session";
|
|
320
|
-
_avi.classList.remove("hidden");
|
|
321
|
-
}
|
|
322
|
-
} else if (_vtw2) {
|
|
323
|
-
// No committed vendor (defensive — shouldn't happen because the
|
|
324
|
-
// input is otherwise gated on a vendor pick). Fall back to the
|
|
325
|
-
// locked toggle so the running vendor is still visible.
|
|
326
|
-
_vtw2.classList.remove("hidden");
|
|
327
|
-
_vtw2.classList.add("locked");
|
|
328
|
-
}
|
|
329
|
-
store.set({ vendorSelectionLocked: false, sessionHasHistory: true });
|
|
347
|
+
commitVendorForTurn();
|
|
330
348
|
|
|
331
349
|
// Show pre-thinking dots before server responds
|
|
332
|
-
|
|
333
|
-
ctx.showMatePreThinking();
|
|
334
|
-
} else if (ctx.showClaudePreThinking) {
|
|
335
|
-
ctx.showClaudePreThinking();
|
|
336
|
-
}
|
|
350
|
+
showPreThinkingForTurn();
|
|
337
351
|
|
|
338
352
|
ctx.inputEl.value = "";
|
|
339
353
|
sendInputSync();
|
|
@@ -354,6 +368,22 @@ export function sendTextMessage(text) {
|
|
|
354
368
|
return true;
|
|
355
369
|
}
|
|
356
370
|
|
|
371
|
+
export function sendShellResultToAgent(msg) {
|
|
372
|
+
if (!ctx || !ctx.connected || !msg || msg.error || !msg.command) return false;
|
|
373
|
+
if (String(msg.sessionId) !== String(store.get("activeSessionId"))) return false;
|
|
374
|
+
var payload = {
|
|
375
|
+
type: "message",
|
|
376
|
+
text: "$ " + msg.command,
|
|
377
|
+
shellCommandResponse: true,
|
|
378
|
+
};
|
|
379
|
+
var selectedVendor = store.get("currentVendor") || null;
|
|
380
|
+
if (selectedVendor) payload.vendor = selectedVendor;
|
|
381
|
+
ctx.ws.send(JSON.stringify(payload));
|
|
382
|
+
commitVendorForTurn();
|
|
383
|
+
showPreThinkingForTurn();
|
|
384
|
+
return true;
|
|
385
|
+
}
|
|
386
|
+
|
|
357
387
|
export function autoResize() {
|
|
358
388
|
ctx.inputEl.style.height = "auto";
|
|
359
389
|
ctx.inputEl.style.height = Math.min(ctx.inputEl.scrollHeight, 120) + "px";
|
|
@@ -152,12 +152,12 @@ export function showMateSidebar(mateId, mateData) {
|
|
|
152
152
|
var vendorIcons = {
|
|
153
153
|
claude: "/claude-code-avatar.png",
|
|
154
154
|
codex: "/codex-avatar.png",
|
|
155
|
-
|
|
155
|
+
antigravity: "/antigravity-avatar.png",
|
|
156
156
|
opencode: "/opencode-avatar.svg",
|
|
157
157
|
kiro: "/kiro-avatar.svg",
|
|
158
158
|
};
|
|
159
|
-
var vendorNames = { claude: "Claude Code", codex: "Codex",
|
|
160
|
-
var vendorKeys = ["claude", "codex", "
|
|
159
|
+
var vendorNames = { claude: "Claude Code", codex: "Codex", antigravity: "Antigravity CLI", opencode: "OpenCode", kiro: "Kiro CLI" };
|
|
160
|
+
var vendorKeys = ["claude", "codex", "antigravity", "opencode", "kiro"];
|
|
161
161
|
mateVendorWrap.innerHTML = "";
|
|
162
162
|
for (var vi = 0; vi < vendorKeys.length; vi++) {
|
|
163
163
|
var vk = vendorKeys[vi];
|