claude-simple 0.2.7 → 0.2.8
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/README.md +0 -0
- package/dist/app.0.2.8.min.js +1080 -0
- package/dist/index.html +2 -2
- package/dist/{style.0.2.7.min.css → style.0.2.8.min.css} +1 -1
- package/gateway.js +0 -0
- package/package.json +1 -1
- package/public/addon-fit.min.js +0 -0
- package/public/agent-panel.js +0 -0
- package/public/api.js +2 -0
- package/public/app.js +0 -0
- package/public/base.css +0 -0
- package/public/cache.js +0 -0
- package/public/chat.js +37 -6
- package/public/files.js +0 -0
- package/public/git.js +0 -0
- package/public/icons/icon-180.png +0 -0
- package/public/icons/icon-192.png +0 -0
- package/public/icons/icon-512.png +0 -0
- package/public/icons/icon-maskable-512.png +0 -0
- package/public/index.html +0 -0
- package/public/lib.js +0 -0
- package/public/manifest.webmanifest +0 -0
- package/public/mobile.js +0 -0
- package/public/notifications.js +215 -0
- package/public/offline.html +0 -0
- package/public/pwa.js +0 -0
- package/public/router.js +0 -0
- package/public/settings.js +0 -0
- package/public/shell/boot.js +0 -0
- package/public/shell/dashboard.js +46 -26
- package/public/shell/hub.js +0 -0
- package/public/shell/layout.js +2 -3
- package/public/shell/notes.js +0 -0
- package/public/shell/session-context.js +0 -0
- package/public/shell/session-list.js +5 -14
- package/public/shell/session-nav.js +18 -1
- package/public/shell/templates.js +18 -0
- package/public/shell/util.js +0 -0
- package/public/shell.js +24 -6
- package/public/state.js +49 -7
- package/public/style.css +0 -0
- package/public/sw.js +1 -1
- package/public/terminal.js +0 -0
- package/public/wake-lock.js +0 -0
- package/public/xterm.min.css +0 -0
- package/public/xterm.min.js +0 -0
- package/src/agents/claude.js +0 -0
- package/src/agents/codex.js +0 -0
- package/src/agents/grok.js +180 -299
- package/src/agents/meta-agent.js +0 -0
- package/src/agents/models.js +0 -0
- package/src/agents/sessions.js +0 -0
- package/src/agents/stream-batch.js +0 -0
- package/src/app.js +0 -0
- package/src/auto-update.js +0 -0
- package/src/runtime/approvals.js +0 -0
- package/src/runtime/outbox.js +0 -0
- package/src/runtime/protocol.js +0 -0
- package/dist/app.0.2.7.min.js +0 -1039
package/src/agents/grok.js
CHANGED
|
@@ -1,327 +1,208 @@
|
|
|
1
1
|
import { spawn } from 'child_process';
|
|
2
2
|
import readline from 'readline';
|
|
3
3
|
import crypto from 'crypto';
|
|
4
|
-
import { createDeltaBatcher } from './stream-batch.js';
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
* grok --resume <sessionId> -p <prompt> --output-format streaming-json ...
|
|
13
|
-
*
|
|
14
|
-
* Stream events (line-delimited JSON):
|
|
15
|
-
* { type: "thought", data: "..." }
|
|
16
|
-
* { type: "text", data: "..." }
|
|
17
|
-
* { type: "tool_*", ... } // when CLI emits them
|
|
18
|
-
* { type: "end", sessionId, usage, ... }
|
|
19
|
-
*
|
|
20
|
-
* We forward deltas immediately so the UI updates live instead of dumping at end.
|
|
21
|
-
*/
|
|
5
|
+
// Grok's ACP transport is a long-lived JSON-RPC process. Keeping it alive is
|
|
6
|
+
// important: it owns the real Grok session and lets the edge, rather than an
|
|
7
|
+
// individual browser tab, serialize follow-up prompts.
|
|
8
|
+
const clients = new Map();
|
|
9
|
+
const queues = new Map();
|
|
10
|
+
const queueAliases = new Map();
|
|
22
11
|
|
|
23
|
-
function
|
|
24
|
-
|
|
25
|
-
if (permissionMode == null) return 'bypassPermissions';
|
|
26
|
-
if (permissionMode === 'default') return null;
|
|
27
|
-
return permissionMode;
|
|
12
|
+
function profileKey(options) {
|
|
13
|
+
return `${options.model || ''}\u001f${options.effort || ''}`;
|
|
28
14
|
}
|
|
29
15
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
args.push('--
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (options.newSessionId && !options.sessionId) {
|
|
52
|
-
args.push('--session-id', options.newSessionId);
|
|
16
|
+
class GrokACPClient {
|
|
17
|
+
constructor({ cliPath, model, effort }) {
|
|
18
|
+
const args = ['agent', '--always-approve'];
|
|
19
|
+
if (model) args.push('--model', model);
|
|
20
|
+
if (effort) args.push('--reasoning-effort', effort);
|
|
21
|
+
args.push('stdio');
|
|
22
|
+
this.child = spawn(cliPath || 'grok', args, { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
23
|
+
this.pending = new Map();
|
|
24
|
+
this.listeners = new Map();
|
|
25
|
+
this.nextID = 1;
|
|
26
|
+
this.ready = this.initialize();
|
|
27
|
+
this.child.stderr.on('data', d => console.warn(`[grok acp] ${String(d).trim()}`));
|
|
28
|
+
this.child.on('close', () => {
|
|
29
|
+
for (const { reject } of this.pending.values()) reject(new Error('Grok ACP process stopped'));
|
|
30
|
+
this.pending.clear();
|
|
31
|
+
for (const fn of this.listeners.values()) fn({ type: 'error', message: 'Grok ACP process stopped', agent: 'grok' });
|
|
32
|
+
this.listeners.clear();
|
|
33
|
+
});
|
|
34
|
+
const rl = readline.createInterface({ input: this.child.stdout, crlfDelay: Infinity });
|
|
35
|
+
rl.on('line', line => this.receive(line));
|
|
53
36
|
}
|
|
54
37
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
38
|
+
async initialize() {
|
|
39
|
+
const init = await this.request('initialize', {
|
|
40
|
+
protocolVersion: 1,
|
|
41
|
+
clientCapabilities: { fs: { readTextFile: true, writeTextFile: true }, terminal: true },
|
|
42
|
+
clientInfo: { name: 'claude-simple-ui', version: '0.2.7' },
|
|
43
|
+
});
|
|
44
|
+
const methods = new Set((init.authMethods || []).map(x => x?.id));
|
|
45
|
+
const methodId = process.env.XAI_API_KEY && methods.has('xai.api_key')
|
|
46
|
+
? 'xai.api_key'
|
|
47
|
+
: (methods.has('cached_token') ? 'cached_token' : null);
|
|
48
|
+
if (methodId) await this.request('authenticate', { methodId, _meta: { headless: true } });
|
|
63
49
|
}
|
|
64
|
-
return { value: v };
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function runGrok(command, options, send, { cliPath, activeSessions }) {
|
|
68
|
-
return new Promise((resolve) => {
|
|
69
|
-
const sessionKey = options.sessionId || crypto.randomUUID();
|
|
70
|
-
const bin = cliPath || 'grok';
|
|
71
|
-
const args = buildArgs(command, options);
|
|
72
|
-
|
|
73
|
-
const env = { ...process.env };
|
|
74
|
-
env.CI = env.CI || '1';
|
|
75
|
-
// Reduce pipe buffering if the runtime honors it
|
|
76
|
-
env.PYTHONUNBUFFERED = '1';
|
|
77
50
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
send({ type: 'error', message: `Failed to start grok: ${e.message}`, agent: 'grok' });
|
|
87
|
-
resolve();
|
|
51
|
+
receive(line) {
|
|
52
|
+
let msg;
|
|
53
|
+
try { msg = JSON.parse(line); } catch { return; }
|
|
54
|
+
if (msg.id != null && this.pending.has(msg.id)) {
|
|
55
|
+
const pending = this.pending.get(msg.id);
|
|
56
|
+
this.pending.delete(msg.id);
|
|
57
|
+
if (msg.error) pending.reject(new Error(msg.error.message || 'Grok ACP request failed'));
|
|
58
|
+
else pending.resolve(msg.result || {});
|
|
88
59
|
return;
|
|
89
60
|
}
|
|
90
|
-
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
let sessionId = options.sessionId || null;
|
|
98
|
-
let aborted = false;
|
|
99
|
-
let stderr = '';
|
|
100
|
-
let gotEnd = false;
|
|
101
|
-
let streamOpen = false; // live assistant bubble on the client
|
|
102
|
-
let thoughtOpen = false;
|
|
103
|
-
let textChars = 0;
|
|
104
|
-
let thoughtChars = 0;
|
|
105
|
-
|
|
106
|
-
const textBatch = createDeltaBatcher(send, 'assistant_delta', { agent: 'grok' }, { maxWait: 48, maxSize: 480 });
|
|
107
|
-
const thoughtBatch = createDeltaBatcher(send, 'thought_delta', { agent: 'grok' }, { maxWait: 140, maxSize: 600 });
|
|
108
|
-
|
|
109
|
-
child.stderr.on('data', d => { stderr += d.toString(); });
|
|
110
|
-
|
|
111
|
-
// Use 'readable' + line split for lower latency than waiting for large chunks
|
|
112
|
-
const rl = readline.createInterface({ input: child.stdout, crlfDelay: Infinity });
|
|
113
|
-
|
|
114
|
-
function ensureStream() {
|
|
115
|
-
if (!streamOpen) {
|
|
116
|
-
send({ type: 'assistant_stream_start', agent: 'grok', ts: Date.now() });
|
|
117
|
-
streamOpen = true;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function endStream() {
|
|
122
|
-
textBatch.flush();
|
|
123
|
-
if (streamOpen) {
|
|
124
|
-
send({ type: 'assistant_stream_end', agent: 'grok' });
|
|
125
|
-
streamOpen = false;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function flushThought() {
|
|
130
|
-
if (!thoughtBatch.pending()) return;
|
|
131
|
-
if (!thoughtOpen) {
|
|
132
|
-
send({ type: 'thought_stream_start', agent: 'grok' });
|
|
133
|
-
thoughtOpen = true;
|
|
134
|
-
}
|
|
135
|
-
const n = thoughtBatch.pending();
|
|
136
|
-
thoughtBatch.flush();
|
|
137
|
-
thoughtChars += n;
|
|
61
|
+
// Grok's documented ACP client sample does not need client-side tool RPC.
|
|
62
|
+
// Return a structured error for optional extension calls instead of leaving
|
|
63
|
+
// an agent request hanging forever.
|
|
64
|
+
if (msg.id != null && msg.method) {
|
|
65
|
+
this.write({ jsonrpc: '2.0', id: msg.id, error: { code: -32601, message: `Unsupported client method: ${msg.method}` } });
|
|
66
|
+
return;
|
|
138
67
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (thoughtOpen) {
|
|
144
|
-
send({ type: 'thought_stream_end', agent: 'grok' });
|
|
145
|
-
thoughtOpen = false;
|
|
146
|
-
}
|
|
147
|
-
endStream();
|
|
148
|
-
|
|
149
|
-
const name = raw.name || raw.tool || raw.toolName || raw.tool_name
|
|
150
|
-
|| raw.title || raw.data?.name || 'tool';
|
|
151
|
-
let input = raw.input ?? raw.arguments ?? raw.args ?? raw.rawInput
|
|
152
|
-
?? raw.data?.input ?? raw.data?.arguments ?? raw.data ?? {};
|
|
153
|
-
input = parseMaybeJson(input);
|
|
154
|
-
|
|
155
|
-
send({
|
|
156
|
-
type: 'assistant',
|
|
157
|
-
message: { content: [{ type: 'tool_use', name, input }] },
|
|
158
|
-
agent: 'grok',
|
|
159
|
-
ts: Date.now(),
|
|
160
|
-
});
|
|
68
|
+
if (msg.method === 'session/update') {
|
|
69
|
+
const sid = msg.params?.sessionId;
|
|
70
|
+
const fn = sid && this.listeners.get(sid);
|
|
71
|
+
if (fn) fn(msg.params?.update || {});
|
|
161
72
|
}
|
|
73
|
+
}
|
|
162
74
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
// Non-JSON progress line — surface lightly
|
|
168
|
-
send({ type: 'status', message: line.slice(0, 200), agent: 'grok' });
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const t = String(raw.type || '').toLowerCase();
|
|
173
|
-
|
|
174
|
-
// ── Text tokens (batched ~50ms) ──────────────────────────────────────
|
|
175
|
-
if ((t === 'text' || t === 'message' || t === 'output_text') && raw.data != null) {
|
|
176
|
-
flushThought();
|
|
177
|
-
if (thoughtOpen) {
|
|
178
|
-
send({ type: 'thought_stream_end', agent: 'grok' });
|
|
179
|
-
thoughtOpen = false;
|
|
180
|
-
}
|
|
181
|
-
const chunk = String(raw.data);
|
|
182
|
-
if (!chunk) return;
|
|
183
|
-
ensureStream();
|
|
184
|
-
textChars += chunk.length;
|
|
185
|
-
textBatch.push(chunk);
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// ── Thinking tokens (batched stream) ────────────────────────────────
|
|
190
|
-
if ((t === 'thought' || t === 'thinking' || t === 'reasoning') && raw.data != null) {
|
|
191
|
-
if (!thoughtOpen) {
|
|
192
|
-
send({ type: 'thought_stream_start', agent: 'grok' });
|
|
193
|
-
thoughtOpen = true;
|
|
194
|
-
}
|
|
195
|
-
thoughtBatch.push(String(raw.data));
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// ── Tools ───────────────────────────────────────────────────────────
|
|
200
|
-
if (
|
|
201
|
-
t === 'tool_use' || t === 'tool_call' || t === 'tool_start'
|
|
202
|
-
|| t === 'tool' || t === 'function_call' || t === 'tool_call_start'
|
|
203
|
-
|| t.startsWith('tool_')
|
|
204
|
-
) {
|
|
205
|
-
// Avoid treating tool_result as a new call
|
|
206
|
-
if (t === 'tool_result' || t === 'tool_call_update' || t === 'tool_end' || t === 'tool_call_end') {
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
emitTool(raw);
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// ── Session id early (some builds emit before end) ───────────────────
|
|
214
|
-
if ((t === 'session' || t === 'session_started') && (raw.sessionId || raw.session_id)) {
|
|
215
|
-
sessionId = raw.sessionId || raw.session_id;
|
|
216
|
-
entry.sessionId = sessionId;
|
|
217
|
-
activeSessions.set(sessionId, entry);
|
|
218
|
-
if (!options.sessionId) {
|
|
219
|
-
send({ type: 'session-created', sessionId, agent: 'grok' });
|
|
220
|
-
}
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// ── End of turn ─────────────────────────────────────────────────────
|
|
225
|
-
if (t === 'end' || t === 'result' || t === 'done') {
|
|
226
|
-
gotEnd = true;
|
|
227
|
-
flushThought();
|
|
228
|
-
if (thoughtOpen) {
|
|
229
|
-
send({ type: 'thought_stream_end', agent: 'grok' });
|
|
230
|
-
thoughtOpen = false;
|
|
231
|
-
}
|
|
232
|
-
endStream();
|
|
233
|
-
|
|
234
|
-
if (raw.sessionId || raw.session_id) {
|
|
235
|
-
sessionId = raw.sessionId || raw.session_id;
|
|
236
|
-
entry.sessionId = sessionId;
|
|
237
|
-
activeSessions.set(sessionId, entry);
|
|
238
|
-
if (!options.sessionId) {
|
|
239
|
-
send({ type: 'session-created', sessionId, agent: 'grok' });
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// Unlock UI immediately — process may linger after the turn ends.
|
|
244
|
-
entry.busy = false;
|
|
245
|
-
|
|
246
|
-
const u = raw.usage || {};
|
|
247
|
-
const costTicks = raw.total_cost_usd_ticks ?? u.cost_usd_ticks ?? u.costUsdTicks;
|
|
248
|
-
const costUSD = raw.total_cost_usd ?? u.cost_usd ?? u.costUSD
|
|
249
|
-
?? (typeof costTicks === 'number' ? costTicks / 1e10 : undefined);
|
|
75
|
+
write(data) {
|
|
76
|
+
if (!this.child.stdin?.writable) throw new Error('Grok ACP stdin is unavailable');
|
|
77
|
+
this.child.stdin.write(JSON.stringify(data) + '\n');
|
|
78
|
+
}
|
|
250
79
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
totalTokens: u.total_tokens || u.totalTokens
|
|
260
|
-
|| ((u.input_tokens || u.inputTokens || 0) + (u.output_tokens || u.outputTokens || 0)),
|
|
261
|
-
reasoningTokens: u.reasoning_tokens || u.reasoningTokens || 0,
|
|
262
|
-
costUSD,
|
|
263
|
-
},
|
|
264
|
-
sessionId: sid,
|
|
265
|
-
agent: 'grok',
|
|
266
|
-
stats: { textChars, thoughtChars },
|
|
267
|
-
});
|
|
268
|
-
// Early complete so client can send next turn without waiting for process exit.
|
|
269
|
-
send({ type: 'complete', exitCode: 0, sessionId: sid, agent: 'grok' });
|
|
270
|
-
return;
|
|
271
|
-
}
|
|
80
|
+
request(method, params) {
|
|
81
|
+
const id = this.nextID++;
|
|
82
|
+
return new Promise((resolve, reject) => {
|
|
83
|
+
this.pending.set(id, { resolve, reject });
|
|
84
|
+
try { this.write({ jsonrpc: '2.0', id, method, params }); }
|
|
85
|
+
catch (e) { this.pending.delete(id); reject(e); }
|
|
86
|
+
});
|
|
87
|
+
}
|
|
272
88
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
89
|
+
async ensureSession(options) {
|
|
90
|
+
await this.ready;
|
|
91
|
+
if (options.sessionId) {
|
|
92
|
+
try {
|
|
93
|
+
await this.request('session/load', { sessionId: options.sessionId, cwd: options.cwd, mcpServers: [] });
|
|
94
|
+
} catch {
|
|
95
|
+
await this.request('session/resume', { sessionId: options.sessionId, cwd: options.cwd, mcpServers: [] });
|
|
276
96
|
}
|
|
97
|
+
return options.sessionId;
|
|
98
|
+
}
|
|
99
|
+
const result = await this.request('session/new', {
|
|
100
|
+
cwd: options.cwd || process.cwd(),
|
|
101
|
+
mcpServers: [],
|
|
102
|
+
_meta: { yoloMode: true },
|
|
277
103
|
});
|
|
104
|
+
if (!result.sessionId) throw new Error('Grok ACP did not return a session id');
|
|
105
|
+
return result.sessionId;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
278
108
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
// Skip duplicate complete if turn already finished via streaming-json "end".
|
|
289
|
-
if (gotEnd && !aborted) {
|
|
290
|
-
resolve();
|
|
291
|
-
return;
|
|
292
|
-
}
|
|
109
|
+
function clientFor(options, cliPath) {
|
|
110
|
+
const key = profileKey(options);
|
|
111
|
+
let client = clients.get(key);
|
|
112
|
+
if (!client || client.child.exitCode != null) {
|
|
113
|
+
client = new GrokACPClient({ cliPath, model: options.model, effort: options.effort });
|
|
114
|
+
clients.set(key, client);
|
|
115
|
+
}
|
|
116
|
+
return client;
|
|
117
|
+
}
|
|
293
118
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
119
|
+
function emitUpdate(update, send, sessionId, state) {
|
|
120
|
+
const kind = update?.sessionUpdate;
|
|
121
|
+
if (kind === 'agent_message_chunk' && update.content?.text) {
|
|
122
|
+
if (!state.textOpen) { send({ type: 'assistant_stream_start', agent: 'grok', ts: Date.now() }); state.textOpen = true; }
|
|
123
|
+
send({ type: 'assistant_delta', data: update.content.text, agent: 'grok' });
|
|
124
|
+
} else if (kind === 'agent_thought_chunk' && update.content?.text) {
|
|
125
|
+
if (!state.thoughtOpen) { send({ type: 'thought_stream_start', agent: 'grok' }); state.thoughtOpen = true; }
|
|
126
|
+
send({ type: 'thought_delta', data: update.content.text, agent: 'grok' });
|
|
127
|
+
} else if (kind === 'tool_call' || kind === 'tool_call_update') {
|
|
128
|
+
if (state.textOpen) { send({ type: 'assistant_stream_end', agent: 'grok' }); state.textOpen = false; }
|
|
129
|
+
if (state.thoughtOpen) { send({ type: 'thought_stream_end', agent: 'grok' }); state.thoughtOpen = false; }
|
|
130
|
+
if (kind === 'tool_call') {
|
|
131
|
+
send({ type: 'assistant', agent: 'grok', message: { content: [{
|
|
132
|
+
type: 'tool_use', name: update.title || update.kind || 'tool', input: update.rawInput || update.input || {},
|
|
133
|
+
}] } });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
309
137
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
138
|
+
async function runOne(item, { cliPath, activeSessions, queueKey }) {
|
|
139
|
+
const { command, options, send } = item;
|
|
140
|
+
const client = clientFor(options, cliPath);
|
|
141
|
+
const localKey = options.sessionId || crypto.randomUUID();
|
|
142
|
+
const entry = { kind: 'grok-acp', startTime: Date.now(), busy: true, key: localKey, sessionId: options.sessionId || null };
|
|
143
|
+
activeSessions.set(localKey, entry);
|
|
144
|
+
const state = { textOpen: false, thoughtOpen: false };
|
|
145
|
+
let sessionId = options.sessionId;
|
|
146
|
+
try {
|
|
147
|
+
sessionId = await client.ensureSession(options);
|
|
148
|
+
queueAliases.set(sessionId, queueKey);
|
|
149
|
+
entry.sessionId = sessionId;
|
|
150
|
+
activeSessions.set(sessionId, entry);
|
|
151
|
+
entry.abort = () => { client.write({ jsonrpc: '2.0', method: 'session/cancel', params: { sessionId } }); };
|
|
152
|
+
if (!options.sessionId) send({ type: 'session-created', sessionId, agent: 'grok' });
|
|
153
|
+
client.listeners.set(sessionId, update => emitUpdate(update, send, sessionId, state));
|
|
154
|
+
const result = await client.request('session/prompt', {
|
|
155
|
+
sessionId,
|
|
156
|
+
prompt: [{ type: 'text', text: command }],
|
|
316
157
|
});
|
|
158
|
+
if (state.textOpen) send({ type: 'assistant_stream_end', agent: 'grok' });
|
|
159
|
+
if (state.thoughtOpen) send({ type: 'thought_stream_end', agent: 'grok' });
|
|
160
|
+
entry.busy = false;
|
|
161
|
+
send({ type: 'result', is_error: result.stopReason === 'error', usage: result._meta?.usage || null, sessionId, agent: 'grok' });
|
|
162
|
+
send({ type: 'complete', exitCode: result.stopReason === 'cancelled' ? 130 : 0, aborted: result.stopReason === 'cancelled', sessionId, agent: 'grok' });
|
|
163
|
+
} catch (e) {
|
|
164
|
+
entry.busy = false;
|
|
165
|
+
send({ type: 'error', message: e.message, sessionId, agent: 'grok' });
|
|
166
|
+
send({ type: 'complete', exitCode: 1, sessionId, agent: 'grok' });
|
|
167
|
+
} finally {
|
|
168
|
+
if (sessionId) client.listeners.delete(sessionId);
|
|
169
|
+
activeSessions.delete(localKey);
|
|
170
|
+
if (sessionId) activeSessions.delete(sessionId);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
317
173
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
174
|
+
/** Queue all Grok follow-ups at the edge. The promise resolves after this item runs. */
|
|
175
|
+
export function runGrok(command, options, send, { cliPath, activeSessions }) {
|
|
176
|
+
const key = (options.sessionId && queueAliases.get(options.sessionId)) || options.sessionId || `new:${options.cwd || ''}`;
|
|
177
|
+
const queue = queues.get(key) || { items: [], running: false };
|
|
178
|
+
queues.set(key, queue);
|
|
179
|
+
return new Promise(resolve => {
|
|
180
|
+
queue.items.push({ command, options, send, resolve });
|
|
181
|
+
if (queue.running) {
|
|
182
|
+
send({ type: 'status', message: `Grok follow-up queued (${queue.items.length - 1} ahead)`, sessionId: options.sessionId, agent: 'grok' });
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
queue.running = true;
|
|
186
|
+
const drain = async () => {
|
|
187
|
+
while (queue.items.length) {
|
|
188
|
+
const item = queue.items.shift();
|
|
189
|
+
await runOne(item, { cliPath, activeSessions, queueKey: key });
|
|
190
|
+
item.resolve();
|
|
191
|
+
}
|
|
192
|
+
queue.running = false;
|
|
193
|
+
queues.delete(key);
|
|
194
|
+
for (const [sid, alias] of queueAliases) if (alias === key) queueAliases.delete(sid);
|
|
322
195
|
};
|
|
323
|
-
|
|
324
|
-
// Tell UI a run started (typing indicator already on; optional status)
|
|
325
|
-
send({ type: 'status', message: 'Grok running…', agent: 'grok' });
|
|
196
|
+
void drain();
|
|
326
197
|
});
|
|
327
198
|
}
|
|
199
|
+
|
|
200
|
+
export async function interruptGrok(sessionId) {
|
|
201
|
+
for (const client of clients.values()) {
|
|
202
|
+
if (client.listeners.has(sessionId)) {
|
|
203
|
+
client.write({ jsonrpc: '2.0', method: 'session/cancel', params: { sessionId } });
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return false;
|
|
208
|
+
}
|
package/src/agents/meta-agent.js
CHANGED
|
File without changes
|
package/src/agents/models.js
CHANGED
|
File without changes
|
package/src/agents/sessions.js
CHANGED
|
File without changes
|
|
File without changes
|
package/src/app.js
CHANGED
|
File without changes
|
package/src/auto-update.js
CHANGED
|
File without changes
|
package/src/runtime/approvals.js
CHANGED
|
File without changes
|
package/src/runtime/outbox.js
CHANGED
|
File without changes
|
package/src/runtime/protocol.js
CHANGED
|
File without changes
|