claude-relay 2.4.2 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/bin/cli.js +1 -2350
  2. package/package.json +7 -42
  3. package/LICENSE +0 -21
  4. package/README.md +0 -281
  5. package/lib/cli-sessions.js +0 -270
  6. package/lib/config.js +0 -222
  7. package/lib/daemon.js +0 -423
  8. package/lib/ipc.js +0 -112
  9. package/lib/pages.js +0 -714
  10. package/lib/project.js +0 -1224
  11. package/lib/public/app.js +0 -2157
  12. package/lib/public/apple-touch-icon.png +0 -0
  13. package/lib/public/css/base.css +0 -145
  14. package/lib/public/css/diff.css +0 -128
  15. package/lib/public/css/filebrowser.css +0 -1076
  16. package/lib/public/css/highlight.css +0 -144
  17. package/lib/public/css/input.css +0 -512
  18. package/lib/public/css/menus.css +0 -683
  19. package/lib/public/css/messages.css +0 -1159
  20. package/lib/public/css/overlays.css +0 -731
  21. package/lib/public/css/rewind.css +0 -529
  22. package/lib/public/css/sidebar.css +0 -794
  23. package/lib/public/favicon.svg +0 -26
  24. package/lib/public/icon-192.png +0 -0
  25. package/lib/public/icon-512.png +0 -0
  26. package/lib/public/icon-mono.svg +0 -19
  27. package/lib/public/index.html +0 -460
  28. package/lib/public/manifest.json +0 -27
  29. package/lib/public/modules/diff.js +0 -398
  30. package/lib/public/modules/events.js +0 -21
  31. package/lib/public/modules/filebrowser.js +0 -1375
  32. package/lib/public/modules/fileicons.js +0 -172
  33. package/lib/public/modules/icons.js +0 -54
  34. package/lib/public/modules/input.js +0 -578
  35. package/lib/public/modules/markdown.js +0 -149
  36. package/lib/public/modules/notifications.js +0 -643
  37. package/lib/public/modules/qrcode.js +0 -70
  38. package/lib/public/modules/rewind.js +0 -334
  39. package/lib/public/modules/sidebar.js +0 -628
  40. package/lib/public/modules/state.js +0 -3
  41. package/lib/public/modules/terminal.js +0 -658
  42. package/lib/public/modules/theme.js +0 -622
  43. package/lib/public/modules/tools.js +0 -1410
  44. package/lib/public/modules/utils.js +0 -56
  45. package/lib/public/style.css +0 -10
  46. package/lib/public/sw.js +0 -75
  47. package/lib/push.js +0 -125
  48. package/lib/sdk-bridge.js +0 -771
  49. package/lib/server.js +0 -577
  50. package/lib/sessions.js +0 -402
  51. package/lib/terminal-manager.js +0 -187
  52. package/lib/terminal.js +0 -24
  53. package/lib/themes/ayu-light.json +0 -9
  54. package/lib/themes/catppuccin-latte.json +0 -9
  55. package/lib/themes/catppuccin-mocha.json +0 -9
  56. package/lib/themes/claude-light.json +0 -9
  57. package/lib/themes/claude.json +0 -9
  58. package/lib/themes/dracula.json +0 -9
  59. package/lib/themes/everforest-light.json +0 -9
  60. package/lib/themes/everforest.json +0 -9
  61. package/lib/themes/github-light.json +0 -9
  62. package/lib/themes/gruvbox-dark.json +0 -9
  63. package/lib/themes/gruvbox-light.json +0 -9
  64. package/lib/themes/monokai.json +0 -9
  65. package/lib/themes/nord-light.json +0 -9
  66. package/lib/themes/nord.json +0 -9
  67. package/lib/themes/one-dark.json +0 -9
  68. package/lib/themes/one-light.json +0 -9
  69. package/lib/themes/rose-pine-dawn.json +0 -9
  70. package/lib/themes/rose-pine.json +0 -9
  71. package/lib/themes/solarized-dark.json +0 -9
  72. package/lib/themes/solarized-light.json +0 -9
  73. package/lib/themes/tokyo-night-light.json +0 -9
  74. package/lib/themes/tokyo-night.json +0 -9
  75. package/lib/updater.js +0 -96
package/lib/sdk-bridge.js DELETED
@@ -1,771 +0,0 @@
1
- const crypto = require("crypto");
2
- var fs = require("fs");
3
- var path = require("path");
4
- var os = require("os");
5
-
6
- // Async message queue for streaming input to SDK
7
- function createMessageQueue() {
8
- var queue = [];
9
- var waiting = null;
10
- var ended = false;
11
- return {
12
- push: function(msg) {
13
- if (waiting) {
14
- var resolve = waiting;
15
- waiting = null;
16
- resolve({ value: msg, done: false });
17
- } else {
18
- queue.push(msg);
19
- }
20
- },
21
- end: function() {
22
- ended = true;
23
- if (waiting) {
24
- var resolve = waiting;
25
- waiting = null;
26
- resolve({ value: undefined, done: true });
27
- }
28
- },
29
- [Symbol.asyncIterator]: function() {
30
- return {
31
- next: function() {
32
- if (queue.length > 0) {
33
- return Promise.resolve({ value: queue.shift(), done: false });
34
- }
35
- if (ended) {
36
- return Promise.resolve({ value: undefined, done: true });
37
- }
38
- return new Promise(function(resolve) {
39
- waiting = resolve;
40
- });
41
- },
42
- };
43
- },
44
- };
45
- }
46
-
47
- function createSDKBridge(opts) {
48
- var cwd = opts.cwd;
49
- var slug = opts.slug || "";
50
- var sm = opts.sessionManager; // session manager instance
51
- var send = opts.send; // broadcast to all clients
52
- var pushModule = opts.pushModule;
53
- var getSDK = opts.getSDK;
54
- var dangerouslySkipPermissions = opts.dangerouslySkipPermissions || false;
55
-
56
- // --- Skill discovery helpers ---
57
-
58
- function discoverSkillDirs() {
59
- var skills = {};
60
- var dirs = [
61
- path.join(os.homedir(), ".claude", "skills"),
62
- path.join(cwd, ".claude", "skills"),
63
- ];
64
- for (var d = 0; d < dirs.length; d++) {
65
- var base = dirs[d];
66
- var entries;
67
- try {
68
- entries = fs.readdirSync(base, { withFileTypes: true });
69
- } catch (e) {
70
- continue; // directory doesn't exist
71
- }
72
- for (var i = 0; i < entries.length; i++) {
73
- var entry = entries[i];
74
- if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
75
- var skillDir = path.join(base, entry.name);
76
- var skillMd = path.join(skillDir, "SKILL.md");
77
- try {
78
- fs.accessSync(skillMd, fs.constants.R_OK);
79
- // project skills override global skills with same name
80
- skills[entry.name] = skillDir;
81
- } catch (e) {
82
- // no SKILL.md, skip
83
- }
84
- }
85
- }
86
- return skills;
87
- }
88
-
89
- function mergeSkills(sdkSkills, fsSkills) {
90
- var merged = new Set();
91
- if (Array.isArray(sdkSkills)) {
92
- for (var i = 0; i < sdkSkills.length; i++) {
93
- merged.add(sdkSkills[i]);
94
- }
95
- }
96
- var fsNames = Object.keys(fsSkills);
97
- for (var i = 0; i < fsNames.length; i++) {
98
- merged.add(fsNames[i]);
99
- }
100
- return merged;
101
- }
102
-
103
- function sendAndRecord(session, obj) {
104
- sm.sendAndRecord(session, obj);
105
- }
106
-
107
- function processSDKMessage(session, parsed) {
108
- // Extract session_id from any message that carries it
109
- if (parsed.session_id && !session.cliSessionId) {
110
- session.cliSessionId = parsed.session_id;
111
- sm.saveSessionFile(session);
112
- if (session.localId === sm.activeSessionId) {
113
- send({ type: "session_id", cliSessionId: session.cliSessionId });
114
- }
115
- } else if (parsed.session_id) {
116
- session.cliSessionId = parsed.session_id;
117
- }
118
-
119
- // Capture message UUIDs for rewind support
120
- if (parsed.uuid) {
121
- if (parsed.type === "user" && !parsed.parent_tool_use_id) {
122
- session.messageUUIDs.push({ uuid: parsed.uuid, type: "user", historyIndex: session.history.length });
123
- sendAndRecord(session, { type: "message_uuid", uuid: parsed.uuid, messageType: "user" });
124
- } else if (parsed.type === "assistant") {
125
- session.messageUUIDs.push({ uuid: parsed.uuid, type: "assistant", historyIndex: session.history.length });
126
- sendAndRecord(session, { type: "message_uuid", uuid: parsed.uuid, messageType: "assistant" });
127
- }
128
- }
129
-
130
- // Cache slash_commands and model from CLI init message
131
- if (parsed.type === "system" && parsed.subtype === "init") {
132
- var fsSkills = discoverSkillDirs();
133
- sm.skillNames = mergeSkills(parsed.skills, fsSkills);
134
- if (parsed.slash_commands) {
135
- // Union: SDK slash_commands + merged skills (deduplicated)
136
- var seen = new Set();
137
- var combined = [];
138
- var all = parsed.slash_commands.concat(Array.from(sm.skillNames));
139
- for (var k = 0; k < all.length; k++) {
140
- if (!seen.has(all[k])) {
141
- seen.add(all[k]);
142
- combined.push(all[k]);
143
- }
144
- }
145
- sm.slashCommands = combined;
146
- send({ type: "slash_commands", commands: sm.slashCommands });
147
- }
148
- if (parsed.model) {
149
- sm.currentModel = parsed.model;
150
- send({ type: "model_info", model: parsed.model, models: sm.availableModels || [] });
151
- }
152
- }
153
-
154
- if (parsed.type === "stream_event" && parsed.event) {
155
- var evt = parsed.event;
156
-
157
- if (evt.type === "content_block_start") {
158
- var block = evt.content_block;
159
- var idx = evt.index;
160
-
161
- if (block.type === "tool_use") {
162
- session.blocks[idx] = { type: "tool_use", id: block.id, name: block.name, inputJson: "" };
163
- sendAndRecord(session, { type: "tool_start", id: block.id, name: block.name });
164
- } else if (block.type === "thinking") {
165
- session.blocks[idx] = { type: "thinking", thinkingText: "" };
166
- sendAndRecord(session, { type: "thinking_start" });
167
- } else if (block.type === "text") {
168
- session.blocks[idx] = { type: "text" };
169
- }
170
- }
171
-
172
- if (evt.type === "content_block_delta" && evt.delta) {
173
- var idx = evt.index;
174
-
175
- if (evt.delta.type === "text_delta" && typeof evt.delta.text === "string") {
176
- session.streamedText = true;
177
- if (session.responsePreview.length < 200) {
178
- session.responsePreview += evt.delta.text;
179
- }
180
- sendAndRecord(session, { type: "delta", text: evt.delta.text });
181
- } else if (evt.delta.type === "input_json_delta" && session.blocks[idx]) {
182
- session.blocks[idx].inputJson += evt.delta.partial_json;
183
- } else if (evt.delta.type === "thinking_delta" && session.blocks[idx]) {
184
- session.blocks[idx].thinkingText += evt.delta.thinking;
185
- sendAndRecord(session, { type: "thinking_delta", text: evt.delta.thinking });
186
- }
187
- }
188
-
189
- if (evt.type === "content_block_stop") {
190
- var idx = evt.index;
191
- var block = session.blocks[idx];
192
-
193
- if (block && block.type === "tool_use") {
194
- var input = {};
195
- try { input = JSON.parse(block.inputJson); } catch {}
196
- sendAndRecord(session, { type: "tool_executing", id: block.id, name: block.name, input: input });
197
-
198
- // Track active Task tools for sub-agent done detection
199
- if (block.name === "Task") {
200
- if (!session.activeTaskToolIds) session.activeTaskToolIds = {};
201
- session.activeTaskToolIds[block.id] = true;
202
- }
203
-
204
- if (pushModule && block.name === "AskUserQuestion" && input.questions) {
205
- var q = input.questions[0];
206
- pushModule.sendPush({
207
- type: "ask_user",
208
- slug: slug,
209
- title: "Claude has a question",
210
- body: q ? q.question : "Waiting for your response",
211
- tag: "claude-ask",
212
- });
213
- }
214
- } else if (block && block.type === "thinking") {
215
- sendAndRecord(session, { type: "thinking_stop" });
216
- }
217
-
218
- delete session.blocks[idx];
219
- }
220
-
221
- } else if ((parsed.type === "assistant" || parsed.type === "user") && parsed.message && parsed.message.content) {
222
- // Sub-agent messages: extract tool_use blocks for activity display
223
- if (parsed.parent_tool_use_id) {
224
- processSubagentMessage(session, parsed);
225
- return;
226
- }
227
-
228
- var content = parsed.message.content;
229
-
230
- // Fallback: if assistant text wasn't streamed via deltas, send it now
231
- if (parsed.type === "assistant" && !session.streamedText && Array.isArray(content)) {
232
- var assistantText = content
233
- .filter(function(c) { return c.type === "text"; })
234
- .map(function(c) { return c.text; })
235
- .join("");
236
- if (assistantText) {
237
- sendAndRecord(session, { type: "delta", text: assistantText });
238
- }
239
- }
240
-
241
- // Check for local slash command output in user messages
242
- if (parsed.type === "user") {
243
- var fullText = "";
244
- if (typeof content === "string") {
245
- fullText = content;
246
- } else if (Array.isArray(content)) {
247
- fullText = content
248
- .filter(function(c) { return c.type === "text"; })
249
- .map(function(c) { return c.text; })
250
- .join("\n");
251
- }
252
- if (fullText.indexOf("local-command-stdout") !== -1) {
253
- var m = fullText.match(/<local-command-stdout>([\s\S]*?)<\/local-command-stdout>/);
254
- if (m) {
255
- sendAndRecord(session, { type: "slash_command_result", text: m[1].trim() });
256
- }
257
- }
258
- }
259
-
260
- if (Array.isArray(content)) {
261
- for (var i = 0; i < content.length; i++) {
262
- var block = content[i];
263
- if (block.type === "tool_result" && !session.sentToolResults[block.tool_use_id]) {
264
- // Clear active Task tool when its result arrives
265
- if (session.activeTaskToolIds && session.activeTaskToolIds[block.tool_use_id]) {
266
- sendAndRecord(session, {
267
- type: "subagent_done",
268
- parentToolId: block.tool_use_id,
269
- });
270
- delete session.activeTaskToolIds[block.tool_use_id];
271
- }
272
- var resultText = "";
273
- if (typeof block.content === "string") {
274
- resultText = block.content;
275
- } else if (Array.isArray(block.content)) {
276
- resultText = block.content
277
- .filter(function(c) { return c.type === "text"; })
278
- .map(function(c) { return c.text; })
279
- .join("\n");
280
- }
281
- session.sentToolResults[block.tool_use_id] = true;
282
- sendAndRecord(session, {
283
- type: "tool_result",
284
- id: block.tool_use_id,
285
- content: resultText,
286
- is_error: block.is_error || false,
287
- });
288
- }
289
- }
290
- }
291
-
292
- } else if (parsed.type === "result") {
293
- session.blocks = {};
294
- session.sentToolResults = {};
295
- session.pendingPermissions = {};
296
- session.pendingAskUser = {};
297
- session.activeTaskToolIds = {};
298
- session.isProcessing = false;
299
- sendAndRecord(session, {
300
- type: "result",
301
- cost: parsed.total_cost_usd,
302
- duration: parsed.duration_ms,
303
- usage: parsed.usage || null,
304
- modelUsage: parsed.modelUsage || null,
305
- sessionId: parsed.session_id,
306
- });
307
- sendAndRecord(session, { type: "done", code: 0 });
308
- if (pushModule) {
309
- var preview = (session.responsePreview || "").replace(/\s+/g, " ").trim();
310
- if (preview.length > 140) preview = preview.substring(0, 140) + "...";
311
- pushModule.sendPush({
312
- type: "done",
313
- slug: slug,
314
- title: session.title || "Claude",
315
- body: preview || "Response ready",
316
- tag: "claude-done",
317
- });
318
- }
319
- // Reset for next turn in the same query
320
- session.responsePreview = "";
321
- session.streamedText = false;
322
- sm.broadcastSessionList();
323
-
324
- } else if (parsed.type === "system" && parsed.subtype === "status") {
325
- if (parsed.status === "compacting") {
326
- sendAndRecord(session, { type: "compacting", active: true });
327
- } else if (session.compacting) {
328
- sendAndRecord(session, { type: "compacting", active: false });
329
- }
330
- session.compacting = parsed.status === "compacting";
331
-
332
- } else if (parsed.type === "tool_progress") {
333
- // Sub-agent tool_progress: forward as activity update
334
- var parentId = parsed.parent_tool_use_id;
335
- if (parentId) {
336
- sendAndRecord(session, {
337
- type: "subagent_activity",
338
- parentToolId: parentId,
339
- text: parsed.content || "",
340
- });
341
- }
342
-
343
- } else if (parsed.type === "task_notification") {
344
- // Sub-agent finished
345
- var parentId = parsed.parent_tool_use_id;
346
- if (parentId) {
347
- sendAndRecord(session, {
348
- type: "subagent_done",
349
- parentToolId: parentId,
350
- });
351
- }
352
-
353
- } else if (parsed.type && parsed.type !== "system" && parsed.type !== "user") {
354
- }
355
- }
356
-
357
- // --- Sub-agent message processing ---
358
-
359
- function toolActivityTextForSubagent(name, input) {
360
- if (name === "Bash" && input && input.description) return input.description;
361
- if (name === "Read" && input && input.file_path) return "Reading " + input.file_path.split("/").pop();
362
- if (name === "Edit" && input && input.file_path) return "Editing " + input.file_path.split("/").pop();
363
- if (name === "Write" && input && input.file_path) return "Writing " + input.file_path.split("/").pop();
364
- if (name === "Grep" && input && input.pattern) return "Searching for " + input.pattern;
365
- if (name === "Glob" && input && input.pattern) return "Finding " + input.pattern;
366
- if (name === "WebSearch" && input && input.query) return "Searching: " + input.query;
367
- if (name === "WebFetch") return "Fetching URL...";
368
- if (name === "Task" && input && input.description) return input.description;
369
- return "Running " + name + "...";
370
- }
371
-
372
- function processSubagentMessage(session, parsed) {
373
- var parentId = parsed.parent_tool_use_id;
374
- var content = parsed.message.content;
375
- if (!Array.isArray(content)) return;
376
-
377
- if (parsed.type === "assistant") {
378
- // Extract tool_use blocks from sub-agent assistant messages
379
- for (var i = 0; i < content.length; i++) {
380
- var block = content[i];
381
- if (block.type === "tool_use") {
382
- var activityText = toolActivityTextForSubagent(block.name, block.input);
383
- sendAndRecord(session, {
384
- type: "subagent_tool",
385
- parentToolId: parentId,
386
- toolName: block.name,
387
- toolId: block.id,
388
- text: activityText,
389
- });
390
- } else if (block.type === "thinking") {
391
- sendAndRecord(session, {
392
- type: "subagent_activity",
393
- parentToolId: parentId,
394
- text: "Thinking...",
395
- });
396
- } else if (block.type === "text" && block.text) {
397
- sendAndRecord(session, {
398
- type: "subagent_activity",
399
- parentToolId: parentId,
400
- text: "Writing response...",
401
- });
402
- }
403
- }
404
- }
405
- // user messages with parent_tool_use_id contain tool_results — skip silently
406
- }
407
-
408
- // --- SDK query lifecycle ---
409
-
410
- function handleCanUseTool(session, toolName, input, opts) {
411
- // AskUserQuestion: wait for user answers via WebSocket
412
- if (toolName === "AskUserQuestion") {
413
- return new Promise(function(resolve) {
414
- session.pendingAskUser[opts.toolUseID] = {
415
- resolve: resolve,
416
- input: input,
417
- };
418
- if (opts.signal) {
419
- opts.signal.addEventListener("abort", function() {
420
- delete session.pendingAskUser[opts.toolUseID];
421
- resolve({ behavior: "deny", message: "Cancelled" });
422
- });
423
- }
424
- });
425
- }
426
-
427
- // Auto-approve if tool was previously allowed for session
428
- if (session.allowedTools && session.allowedTools[toolName]) {
429
- return Promise.resolve({ behavior: "allow", updatedInput: input });
430
- }
431
-
432
- // Regular tool permission request: send to client and wait
433
- return new Promise(function(resolve) {
434
- var requestId = crypto.randomUUID();
435
- session.pendingPermissions[requestId] = {
436
- resolve: resolve,
437
- requestId: requestId,
438
- toolName: toolName,
439
- toolInput: input,
440
- toolUseId: opts.toolUseID,
441
- decisionReason: opts.decisionReason || "",
442
- };
443
-
444
- var permMsg = {
445
- type: "permission_request",
446
- requestId: requestId,
447
- toolName: toolName,
448
- toolInput: input,
449
- toolUseId: opts.toolUseID,
450
- decisionReason: opts.decisionReason || "",
451
- };
452
- sendAndRecord(session, permMsg);
453
-
454
- if (pushModule) {
455
- pushModule.sendPush({
456
- type: "permission_request",
457
- slug: slug,
458
- requestId: requestId,
459
- title: permissionPushTitle(toolName, input),
460
- body: permissionPushBody(toolName, input),
461
- });
462
- }
463
-
464
- if (opts.signal) {
465
- opts.signal.addEventListener("abort", function() {
466
- delete session.pendingPermissions[requestId];
467
- sendAndRecord(session, { type: "permission_cancel", requestId: requestId });
468
- resolve({ behavior: "deny", message: "Request cancelled" });
469
- });
470
- }
471
- });
472
- }
473
-
474
- async function processQueryStream(session) {
475
- try {
476
- for await (var msg of session.queryInstance) {
477
- processSDKMessage(session, msg);
478
- }
479
- } catch (err) {
480
- if (session.isProcessing) {
481
- session.isProcessing = false;
482
- if (err.name === "AbortError" || (session.abortController && session.abortController.signal.aborted)) {
483
- sendAndRecord(session, { type: "info", text: "Interrupted \u00b7 What should Claude do instead?" });
484
- sendAndRecord(session, { type: "done", code: 0 });
485
- } else {
486
- sendAndRecord(session, { type: "error", text: "Claude process error: " + err.message });
487
- sendAndRecord(session, { type: "done", code: 1 });
488
- if (pushModule) {
489
- pushModule.sendPush({
490
- type: "error",
491
- slug: slug,
492
- title: "Connection Lost",
493
- body: "Claude process disconnected: " + (err.message || "unknown error"),
494
- tag: "claude-error",
495
- });
496
- }
497
- }
498
- sm.broadcastSessionList();
499
- }
500
- } finally {
501
- session.queryInstance = null;
502
- session.messageQueue = null;
503
- session.abortController = null;
504
- session.pendingPermissions = {};
505
- session.pendingAskUser = {};
506
- }
507
- }
508
-
509
- async function getOrCreateRewindQuery(session) {
510
- if (session.queryInstance) return { query: session.queryInstance, isTemp: false, cleanup: function() {} };
511
-
512
- var sdk;
513
- try {
514
- sdk = await getSDK();
515
- } catch (e) {
516
- send({ type: "error", text: "Failed to load Claude SDK: " + (e.message || e) });
517
- throw e;
518
- }
519
- var mq = createMessageQueue();
520
-
521
- var tempQuery = sdk.query({
522
- prompt: mq,
523
- options: {
524
- cwd: cwd,
525
- settingSources: ["user", "project", "local"],
526
- enableFileCheckpointing: true,
527
- resume: session.cliSessionId,
528
- },
529
- });
530
-
531
- // Drain messages in background (stream stays alive until mq.end())
532
- (async function() {
533
- try { for await (var msg of tempQuery) {} } catch(e) {}
534
- })();
535
-
536
- return {
537
- query: tempQuery,
538
- isTemp: true,
539
- cleanup: function() { try { mq.end(); } catch(e) {} },
540
- };
541
- }
542
-
543
- async function startQuery(session, text, images) {
544
- var sdk;
545
- try {
546
- sdk = await getSDK();
547
- } catch (e) {
548
- session.isProcessing = false;
549
- send({ type: "error", text: "Failed to load Claude SDK: " + (e.message || e) });
550
- sendAndRecord(session, { type: "done", code: 1 });
551
- sm.broadcastSessionList();
552
- return;
553
- }
554
-
555
- session.messageQueue = createMessageQueue();
556
- session.blocks = {};
557
- session.sentToolResults = {};
558
- session.activeTaskToolIds = {};
559
- session.streamedText = false;
560
- session.responsePreview = "";
561
-
562
- // Build initial user message
563
- var content = [];
564
- if (images && images.length > 0) {
565
- for (var i = 0; i < images.length; i++) {
566
- content.push({
567
- type: "image",
568
- source: { type: "base64", media_type: images[i].mediaType, data: images[i].data },
569
- });
570
- }
571
- }
572
- if (text) {
573
- content.push({ type: "text", text: text });
574
- }
575
-
576
- session.messageQueue.push({
577
- type: "user",
578
- message: { role: "user", content: content },
579
- });
580
-
581
- session.abortController = new AbortController();
582
-
583
- var queryOptions = {
584
- cwd: cwd,
585
- settingSources: ["user", "project", "local"],
586
- includePartialMessages: true,
587
- enableFileCheckpointing: true,
588
- extraArgs: { "replay-user-messages": null },
589
- abortController: session.abortController,
590
- canUseTool: function(toolName, input, toolOpts) {
591
- return handleCanUseTool(session, toolName, input, toolOpts);
592
- },
593
- };
594
-
595
- if (dangerouslySkipPermissions) {
596
- queryOptions.permissionMode = "bypassPermissions";
597
- queryOptions.allowDangerouslySkipPermissions = true;
598
- }
599
-
600
- if (session.cliSessionId) {
601
- queryOptions.resume = session.cliSessionId;
602
- if (session.lastRewindUuid) {
603
- queryOptions.resumeSessionAt = session.lastRewindUuid;
604
- delete session.lastRewindUuid;
605
- }
606
- }
607
-
608
- try {
609
- session.queryInstance = sdk.query({
610
- prompt: session.messageQueue,
611
- options: queryOptions,
612
- });
613
- } catch (e) {
614
- session.isProcessing = false;
615
- session.queryInstance = null;
616
- session.messageQueue = null;
617
- session.abortController = null;
618
- send({ type: "error", text: "Failed to start query: " + (e.message || e) });
619
- sendAndRecord(session, { type: "done", code: 1 });
620
- sm.broadcastSessionList();
621
- return;
622
- }
623
-
624
- processQueryStream(session).catch(function(err) {
625
- });
626
- }
627
-
628
- function pushMessage(session, text, images) {
629
- var content = [];
630
- if (images && images.length > 0) {
631
- for (var i = 0; i < images.length; i++) {
632
- content.push({
633
- type: "image",
634
- source: { type: "base64", media_type: images[i].mediaType, data: images[i].data },
635
- });
636
- }
637
- }
638
- if (text) {
639
- content.push({ type: "text", text: text });
640
- }
641
- session.messageQueue.push({
642
- type: "user",
643
- message: { role: "user", content: content },
644
- });
645
- }
646
-
647
- function permissionPushTitle(toolName, input) {
648
- if (!input) return "Claude wants to use " + toolName;
649
- var file = input.file_path ? input.file_path.split(/[/\\]/).pop() : "";
650
- switch (toolName) {
651
- case "Bash": return "Claude wants to run a command";
652
- case "Edit": return "Claude wants to edit " + (file || "a file");
653
- case "Write": return "Claude wants to write " + (file || "a file");
654
- case "Read": return "Claude wants to read " + (file || "a file");
655
- case "Grep": return "Claude wants to search files";
656
- case "Glob": return "Claude wants to find files";
657
- case "WebFetch": return "Claude wants to fetch a URL";
658
- case "WebSearch": return "Claude wants to search the web";
659
- case "Task": return "Claude wants to launch an agent";
660
- default: return "Claude wants to use " + toolName;
661
- }
662
- }
663
-
664
- function permissionPushBody(toolName, input) {
665
- if (!input) return "";
666
- var text = "";
667
- if (toolName === "Bash" && input.command) {
668
- text = input.command;
669
- } else if (toolName === "Edit" && input.file_path) {
670
- text = input.file_path.split(/[/\\]/).pop() + ": " + (input.old_string || "").substring(0, 40) + " \u2192 " + (input.new_string || "").substring(0, 40);
671
- } else if (toolName === "Write" && input.file_path) {
672
- text = input.file_path;
673
- } else if (input.file_path) {
674
- text = input.file_path;
675
- } else if (input.command) {
676
- text = input.command;
677
- } else if (input.url) {
678
- text = input.url;
679
- } else if (input.query) {
680
- text = input.query;
681
- } else if (input.pattern) {
682
- text = input.pattern;
683
- } else if (input.description) {
684
- text = input.description;
685
- }
686
- if (text.length > 120) text = text.substring(0, 120) + "...";
687
- return text;
688
- }
689
-
690
- // SDK warmup: grab slash_commands, model, and available models from SDK init
691
- async function warmup() {
692
- try {
693
- var sdk = await getSDK();
694
- var ac = new AbortController();
695
- var mq = createMessageQueue();
696
- mq.push({ type: "user", message: { role: "user", content: [{ type: "text", text: "hi" }] } });
697
- mq.end();
698
- var warmupOptions = { cwd: cwd, settingSources: ["user", "project", "local"], abortController: ac };
699
- if (dangerouslySkipPermissions) {
700
- warmupOptions.permissionMode = "bypassPermissions";
701
- warmupOptions.allowDangerouslySkipPermissions = true;
702
- }
703
- var stream = sdk.query({
704
- prompt: mq,
705
- options: warmupOptions,
706
- });
707
- for await (var msg of stream) {
708
- if (msg.type === "system" && msg.subtype === "init") {
709
- var fsSkills = discoverSkillDirs();
710
- sm.skillNames = mergeSkills(msg.skills, fsSkills);
711
- if (msg.slash_commands) {
712
- // Union: SDK slash_commands + merged skills (deduplicated)
713
- var seen = new Set();
714
- var combined = [];
715
- var all = msg.slash_commands.concat(Array.from(sm.skillNames));
716
- for (var k = 0; k < all.length; k++) {
717
- if (!seen.has(all[k])) {
718
- seen.add(all[k]);
719
- combined.push(all[k]);
720
- }
721
- }
722
- sm.slashCommands = combined;
723
- send({ type: "slash_commands", commands: sm.slashCommands });
724
- }
725
- if (msg.model) {
726
- sm.currentModel = msg.model;
727
- }
728
- // Fetch available models before aborting
729
- try {
730
- var models = await stream.supportedModels();
731
- sm.availableModels = models || [];
732
- } catch (e) {}
733
- send({ type: "model_info", model: sm.currentModel || "", models: sm.availableModels || [] });
734
- ac.abort();
735
- break;
736
- }
737
- }
738
- } catch (e) {
739
- if (e && e.name !== "AbortError" && !(e.message && e.message.indexOf("aborted") !== -1)) {
740
- send({ type: "error", text: "Failed to load Claude SDK: " + (e.message || e) });
741
- }
742
- }
743
- }
744
-
745
- async function setModel(session, model) {
746
- if (!session.queryInstance) return;
747
- try {
748
- await session.queryInstance.setModel(model);
749
- sm.currentModel = model;
750
- send({ type: "model_info", model: model, models: sm.availableModels || [] });
751
- } catch (e) {
752
- send({ type: "error", text: "Failed to switch model: " + (e.message || e) });
753
- }
754
- }
755
-
756
- return {
757
- createMessageQueue: createMessageQueue,
758
- processSDKMessage: processSDKMessage,
759
- handleCanUseTool: handleCanUseTool,
760
- processQueryStream: processQueryStream,
761
- getOrCreateRewindQuery: getOrCreateRewindQuery,
762
- startQuery: startQuery,
763
- pushMessage: pushMessage,
764
- setModel: setModel,
765
- permissionPushTitle: permissionPushTitle,
766
- permissionPushBody: permissionPushBody,
767
- warmup: warmup,
768
- };
769
- }
770
-
771
- module.exports = { createSDKBridge, createMessageQueue };