@oro.ad/nuxt-claude-devtools 1.0.6 → 1.1.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 (61) hide show
  1. package/README.md +149 -13
  2. package/dist/client/200.html +1 -1
  3. package/dist/client/404.html +1 -1
  4. package/dist/client/_nuxt/{Dgh4EhoJ.js → BRCY8pHC.js} +1 -1
  5. package/dist/client/_nuxt/BVHVIm9H.js +12 -0
  6. package/dist/client/_nuxt/BZrcCMrf.js +1 -0
  7. package/dist/client/_nuxt/BbEuL4Z6.js +1 -0
  8. package/dist/client/_nuxt/BmjlsnUc.js +1 -0
  9. package/dist/client/_nuxt/D2NL8Xro.js +7 -0
  10. package/dist/client/_nuxt/D9qGFoJm.js +4 -0
  11. package/dist/client/_nuxt/DImlDIT-.js +59 -0
  12. package/dist/client/_nuxt/{B6Pm7LNk.js → DV075BoS.js} +1 -1
  13. package/dist/client/_nuxt/{BngXb2T5.js → DYNukx3V.js} +1 -1
  14. package/dist/client/_nuxt/Dbw96V2H.js +7 -0
  15. package/dist/client/_nuxt/FllXIyfS.js +8 -0
  16. package/dist/client/_nuxt/MarkdownContent.WwTYmYZK.css +1 -0
  17. package/dist/client/_nuxt/TvBJGid1.js +1 -0
  18. package/dist/client/_nuxt/XJ4dJUK2.js +1 -0
  19. package/dist/client/_nuxt/builds/latest.json +1 -1
  20. package/dist/client/_nuxt/builds/meta/e8ae4dbb-462d-47a2-9aa2-50bed9498ab2.json +1 -0
  21. package/dist/client/_nuxt/e7kgpy_n.js +4 -0
  22. package/dist/client/_nuxt/entry.DwDQaFYc.css +1 -0
  23. package/dist/client/_nuxt/error-404.2GhCpCfF.css +1 -0
  24. package/dist/client/_nuxt/error-500.DqdIhFrl.css +1 -0
  25. package/dist/client/_nuxt/index.Bomb3OYy.css +1 -0
  26. package/dist/client/agents/index.html +1 -0
  27. package/dist/client/commands/index.html +1 -0
  28. package/dist/client/docs/index.html +1 -0
  29. package/dist/client/index.html +1 -1
  30. package/dist/client/mcp/index.html +1 -1
  31. package/dist/client/skills/index.html +1 -0
  32. package/dist/module.json +1 -1
  33. package/dist/module.mjs +22 -13
  34. package/dist/runtime/logger.d.ts +5 -0
  35. package/dist/runtime/logger.js +12 -0
  36. package/dist/runtime/server/agents-manager.d.ts +31 -0
  37. package/dist/runtime/server/agents-manager.js +193 -0
  38. package/dist/runtime/server/claude-session.d.ts +21 -4
  39. package/dist/runtime/server/claude-session.js +467 -25
  40. package/dist/runtime/server/commands-manager.d.ts +24 -0
  41. package/dist/runtime/server/commands-manager.js +132 -0
  42. package/dist/runtime/server/docs-manager.d.ts +48 -0
  43. package/dist/runtime/server/docs-manager.js +189 -0
  44. package/dist/runtime/server/history-manager.d.ts +24 -0
  45. package/dist/runtime/server/history-manager.js +184 -0
  46. package/dist/runtime/server/plugins/socket.io.d.ts +2 -0
  47. package/dist/runtime/server/plugins/socket.io.js +48 -0
  48. package/dist/runtime/server/skills-manager.d.ts +36 -0
  49. package/dist/runtime/server/skills-manager.js +210 -0
  50. package/dist/runtime/types.d.ts +156 -0
  51. package/dist/runtime/types.js +0 -0
  52. package/package.json +17 -1
  53. package/dist/client/_nuxt/BWmwj9se.js +0 -1
  54. package/dist/client/_nuxt/CRwOrvc3.js +0 -1
  55. package/dist/client/_nuxt/DMBGVttU.js +0 -1
  56. package/dist/client/_nuxt/DYOOVyPh.js +0 -1
  57. package/dist/client/_nuxt/JxSLzYFz.js +0 -4
  58. package/dist/client/_nuxt/builds/meta/f2b44466-6d7e-4b5c-bf6b-f6c7cf9e0d59.json +0 -1
  59. package/dist/client/_nuxt/entry.Ci1n7Rlt.css +0 -1
  60. package/dist/client/_nuxt/error-404.BLrjNXsr.css +0 -1
  61. package/dist/client/_nuxt/error-500.DLkAwcfL.css +0 -1
@@ -1,38 +1,56 @@
1
- import { createServer } from "node:http";
2
1
  import { execSync, spawn } from "node:child_process";
3
- import { Server as SocketServer } from "socket.io";
2
+ import { createLogger } from "../logger.js";
3
+ import { AgentsManager } from "./agents-manager.js";
4
+ import { CommandsManager } from "./commands-manager.js";
5
+ import { DocsManager } from "./docs-manager.js";
6
+ import { HistoryManager } from "./history-manager.js";
7
+ import { SkillsManager } from "./skills-manager.js";
8
+ const log = createLogger("session", { timestamp: true });
4
9
  function getErrorMessage(error) {
5
10
  if (error instanceof Error) return error.message;
6
11
  return String(error);
7
12
  }
8
- export const SOCKET_PORT = 3355;
9
- function log(message, data) {
10
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
11
- console.log(`[claude-session] [${timestamp}] ${message}`, data !== void 0 ? data : "");
13
+ function generateId() {
14
+ return `msg_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
12
15
  }
16
+ export const SOCKET_PATH = "/__claude_devtools_socket";
13
17
  export class ClaudeSession {
14
18
  config;
15
19
  io = null;
16
- httpServer = null;
17
20
  isProcessing = false;
18
21
  continueSession = false;
22
+ historyManager;
23
+ docsManager;
24
+ commandsManager;
25
+ agentsManager;
26
+ skillsManager;
27
+ // Claude CLI session ID (in-memory only, lost on hot-reload)
28
+ claudeSessionId = null;
29
+ // Stream parsing state
30
+ parseBuffer = "";
31
+ currentContentBlocks = [];
32
+ currentMessageId = "";
33
+ currentModel = "";
34
+ accumulatedText = "";
19
35
  constructor(config) {
20
36
  this.config = config;
37
+ this.historyManager = new HistoryManager(config.rootDir);
38
+ this.docsManager = new DocsManager(config.rootDir);
39
+ this.commandsManager = new CommandsManager(config.rootDir);
40
+ this.agentsManager = new AgentsManager(config.rootDir);
41
+ this.skillsManager = new SkillsManager(config.rootDir);
21
42
  }
22
- startSocketServer() {
23
- this.httpServer = createServer();
24
- this.io = new SocketServer(this.httpServer, {
25
- cors: {
26
- origin: "*",
27
- methods: ["GET", "POST"]
28
- }
29
- });
43
+ attachSocketIO(io) {
44
+ this.io = io;
45
+ log(`Socket.IO attached, setting up event handlers`);
30
46
  this.io.on("connection", (socket) => {
31
47
  log("Client connected", { socketId: socket.id });
32
48
  socket.emit("session:status", {
33
49
  active: true,
34
50
  processing: this.isProcessing
35
51
  });
52
+ const conversation = this.historyManager.getActiveConversation();
53
+ socket.emit("history:loaded", conversation);
36
54
  socket.on("message:send", (message) => {
37
55
  log("Message received", { length: message.length, preview: message.substring(0, 100) });
38
56
  this.sendMessage(message);
@@ -40,7 +58,37 @@ export class ClaudeSession {
40
58
  socket.on("session:reset", () => {
41
59
  log("Resetting session (new conversation)");
42
60
  this.continueSession = false;
61
+ this.claudeSessionId = null;
62
+ const conversation2 = this.historyManager.resetSession();
43
63
  this.io?.emit("session:status", { active: true, processing: false });
64
+ this.io?.emit("history:loaded", conversation2);
65
+ });
66
+ socket.on("history:load", () => {
67
+ const conversation2 = this.historyManager.getActiveConversation();
68
+ socket.emit("history:loaded", conversation2);
69
+ });
70
+ socket.on("history:list", () => {
71
+ const conversations = this.historyManager.getConversations();
72
+ socket.emit("history:list", conversations);
73
+ });
74
+ socket.on("history:switch", (id) => {
75
+ const conversation2 = this.historyManager.setActiveConversation(id);
76
+ if (conversation2) {
77
+ this.continueSession = false;
78
+ this.claudeSessionId = conversation2.claudeSessionId || null;
79
+ log("Switched to conversation", {
80
+ id: conversation2.id,
81
+ claudeSessionId: this.claudeSessionId,
82
+ messageCount: conversation2.messages.length
83
+ });
84
+ socket.emit("history:switched", conversation2);
85
+ }
86
+ });
87
+ socket.on("history:delete", (id) => {
88
+ const success = this.historyManager.deleteConversation(id);
89
+ socket.emit("history:deleted", { id, success });
90
+ const conversations = this.historyManager.getConversations();
91
+ socket.emit("history:list", conversations);
44
92
  });
45
93
  socket.on("mcp:list", () => {
46
94
  log("MCP list requested");
@@ -55,17 +103,349 @@ export class ClaudeSession {
55
103
  log("MCP remove requested", data);
56
104
  this.removeMcpServer(data, socket);
57
105
  });
106
+ socket.on("docs:list", () => {
107
+ log("Docs list requested");
108
+ const files = this.docsManager.getDocFiles();
109
+ socket.emit("docs:list", files);
110
+ });
111
+ socket.on("docs:get", (path) => {
112
+ log("Docs get requested", { path });
113
+ const file = this.docsManager.getDocFile(path);
114
+ socket.emit("docs:file", file);
115
+ });
116
+ socket.on("docs:save", (data) => {
117
+ log("Docs save requested", { path: data.path });
118
+ try {
119
+ const file = this.docsManager.saveDocFile(data.path, data.content);
120
+ socket.emit("docs:saved", { success: true, file });
121
+ const files = this.docsManager.getDocFiles();
122
+ socket.emit("docs:list", files);
123
+ } catch (error) {
124
+ socket.emit("docs:saved", { success: false, error: String(error) });
125
+ }
126
+ });
127
+ socket.on("docs:delete", (path) => {
128
+ log("Docs delete requested", { path });
129
+ const success = this.docsManager.deleteDocFile(path);
130
+ socket.emit("docs:deleted", { path, success });
131
+ const files = this.docsManager.getDocFiles();
132
+ socket.emit("docs:list", files);
133
+ });
134
+ socket.on("claudemd:get", () => {
135
+ log("CLAUDE.md get requested");
136
+ const data = this.docsManager.getClaudeMd();
137
+ socket.emit("claudemd:data", data);
138
+ });
139
+ socket.on("claudemd:save", (content) => {
140
+ log("CLAUDE.md save requested");
141
+ try {
142
+ const data = this.docsManager.saveClaudeMd(content);
143
+ socket.emit("claudemd:saved", { success: true, ...data });
144
+ } catch (error) {
145
+ socket.emit("claudemd:saved", { success: false, error: String(error) });
146
+ }
147
+ });
148
+ socket.on("llms:list", () => {
149
+ log("LLMS list requested");
150
+ const sources = this.docsManager.getLlmsSources();
151
+ socket.emit("llms:list", sources);
152
+ });
153
+ socket.on("llms:add", (data) => {
154
+ log("LLMS add requested", { url: data.url });
155
+ try {
156
+ const source = this.docsManager.addLlmsSource(data.url, data.title, data.description);
157
+ socket.emit("llms:added", { success: true, source });
158
+ const sources = this.docsManager.getLlmsSources();
159
+ socket.emit("llms:list", sources);
160
+ } catch (error) {
161
+ socket.emit("llms:added", { success: false, error: String(error) });
162
+ }
163
+ });
164
+ socket.on("llms:remove", (url) => {
165
+ log("LLMS remove requested", { url });
166
+ const success = this.docsManager.removeLlmsSource(url);
167
+ socket.emit("llms:removed", { url, success });
168
+ const sources = this.docsManager.getLlmsSources();
169
+ socket.emit("llms:list", sources);
170
+ });
171
+ socket.on("llms:update", (data) => {
172
+ log("LLMS update requested", { url: data.url });
173
+ const source = this.docsManager.updateLlmsSource(data.url, {
174
+ title: data.title,
175
+ description: data.description
176
+ });
177
+ socket.emit("llms:updated", { success: !!source, source });
178
+ });
179
+ socket.on("commands:list", () => {
180
+ log("Commands list requested");
181
+ const commands = this.commandsManager.getCommands();
182
+ socket.emit("commands:list", commands);
183
+ });
184
+ socket.on("commands:get", (name) => {
185
+ log("Command get requested", { name });
186
+ const command = this.commandsManager.getCommand(name);
187
+ socket.emit("commands:data", command);
188
+ });
189
+ socket.on("commands:save", (data) => {
190
+ log("Command save requested", { name: data.name });
191
+ try {
192
+ const command = this.commandsManager.saveCommand(data.name, data.content, {
193
+ description: data.description,
194
+ allowedTools: data.allowedTools,
195
+ disableModelInvocation: data.disableModelInvocation
196
+ });
197
+ socket.emit("commands:saved", { success: true, command });
198
+ const commands = this.commandsManager.getCommands();
199
+ socket.emit("commands:list", commands);
200
+ } catch (error) {
201
+ socket.emit("commands:saved", { success: false, error: String(error) });
202
+ }
203
+ });
204
+ socket.on("commands:delete", (name) => {
205
+ log("Command delete requested", { name });
206
+ const success = this.commandsManager.deleteCommand(name);
207
+ socket.emit("commands:deleted", { name, success });
208
+ const commands = this.commandsManager.getCommands();
209
+ socket.emit("commands:list", commands);
210
+ });
211
+ socket.on("agents:list", () => {
212
+ log("Agents list requested");
213
+ const agents = this.agentsManager.getAgents();
214
+ socket.emit("agents:list", agents);
215
+ });
216
+ socket.on("agents:get", (name) => {
217
+ log("Agent get requested", { name });
218
+ const agent = this.agentsManager.getAgent(name);
219
+ socket.emit("agents:data", agent);
220
+ });
221
+ socket.on("agents:save", (data) => {
222
+ log("Agent save requested", { name: data.name });
223
+ try {
224
+ const agent = this.agentsManager.saveAgent({
225
+ name: data.name,
226
+ description: data.description,
227
+ prompt: data.prompt,
228
+ model: data.model,
229
+ tools: data.tools,
230
+ disallowedTools: data.disallowedTools,
231
+ permissionMode: data.permissionMode,
232
+ skills: data.skills
233
+ });
234
+ socket.emit("agents:saved", { success: true, agent });
235
+ const agents = this.agentsManager.getAgents();
236
+ socket.emit("agents:list", agents);
237
+ } catch (error) {
238
+ socket.emit("agents:saved", { success: false, error: String(error) });
239
+ }
240
+ });
241
+ socket.on("agents:delete", (name) => {
242
+ log("Agent delete requested", { name });
243
+ const success = this.agentsManager.deleteAgent(name);
244
+ socket.emit("agents:deleted", { name, success });
245
+ const agents = this.agentsManager.getAgents();
246
+ socket.emit("agents:list", agents);
247
+ });
248
+ socket.on("skills:list", () => {
249
+ log("Skills list requested");
250
+ const skills = this.skillsManager.getSkills();
251
+ socket.emit("skills:list", skills);
252
+ });
253
+ socket.on("skills:get", (name) => {
254
+ log("Skill get requested", { name });
255
+ const skill = this.skillsManager.getSkill(name);
256
+ socket.emit("skills:get", skill);
257
+ });
258
+ socket.on("skills:save", (data) => {
259
+ log("Skill save requested", { name: data.name });
260
+ try {
261
+ const skill = this.skillsManager.saveSkill({
262
+ name: data.name,
263
+ description: data.description,
264
+ content: data.content,
265
+ argumentHint: data.argumentHint,
266
+ disableModelInvocation: data.disableModelInvocation,
267
+ userInvocable: data.userInvocable,
268
+ allowedTools: data.allowedTools,
269
+ model: data.model,
270
+ context: data.context,
271
+ agent: data.agent
272
+ });
273
+ socket.emit("skills:saved", { success: true, skill });
274
+ const skills = this.skillsManager.getSkills();
275
+ socket.emit("skills:list", skills);
276
+ } catch (error) {
277
+ socket.emit("skills:saved", { success: false, error: getErrorMessage(error) });
278
+ }
279
+ });
280
+ socket.on("skills:names", () => {
281
+ log("Skill names requested");
282
+ const names = this.skillsManager.getSkillNames();
283
+ socket.emit("skills:names", names);
284
+ });
285
+ socket.on("skills:delete", (name) => {
286
+ log("Skill delete requested", { name });
287
+ const success = this.skillsManager.deleteSkill(name);
288
+ socket.emit("skills:deleted", { name, success });
289
+ const skills = this.skillsManager.getSkills();
290
+ socket.emit("skills:list", skills);
291
+ });
58
292
  socket.on("disconnect", () => {
59
293
  log("Client disconnected", { socketId: socket.id });
60
294
  });
61
295
  });
62
- this.httpServer.listen(SOCKET_PORT, () => {
63
- log(`Socket.IO server started on port ${SOCKET_PORT}`);
64
- });
65
296
  }
66
297
  destroy() {
67
298
  this.io?.close();
68
- this.httpServer?.close();
299
+ }
300
+ resetStreamState() {
301
+ this.parseBuffer = "";
302
+ this.currentContentBlocks = [];
303
+ this.currentMessageId = generateId();
304
+ this.currentModel = "";
305
+ this.accumulatedText = "";
306
+ }
307
+ buildSystemPrompt() {
308
+ const sections = [];
309
+ const llmsSources = this.docsManager.getLlmsSources();
310
+ if (llmsSources.length > 0) {
311
+ sections.push("=== EXTERNAL DOCUMENTATION SOURCES ===");
312
+ sections.push("The following llms.txt sources are configured for this project.");
313
+ sections.push("You can fetch these URLs to get documentation context when needed:");
314
+ sections.push("");
315
+ for (const source of llmsSources) {
316
+ const title = source.title || source.domain;
317
+ const desc = source.description ? ` - ${source.description}` : "";
318
+ sections.push(`- ${title}${desc}`);
319
+ sections.push(` URL: ${source.url}`);
320
+ }
321
+ sections.push("");
322
+ }
323
+ if (this.historyManager.hasHistoryForRecovery()) {
324
+ const historyPrompt = this.historyManager.formatHistoryForSystemPrompt();
325
+ if (historyPrompt) {
326
+ sections.push(historyPrompt);
327
+ }
328
+ }
329
+ if (sections.length === 0) {
330
+ return null;
331
+ }
332
+ return sections.join("\n");
333
+ }
334
+ parseStreamChunk(data) {
335
+ this.parseBuffer += data;
336
+ const events = [];
337
+ const lines = this.parseBuffer.split("\n");
338
+ this.parseBuffer = lines.pop() || "";
339
+ for (const line of lines) {
340
+ if (line.trim()) {
341
+ try {
342
+ const event = JSON.parse(line);
343
+ events.push(event);
344
+ } catch (e) {
345
+ log("Failed to parse stream event", { line: line.substring(0, 100), error: e });
346
+ }
347
+ }
348
+ }
349
+ return events;
350
+ }
351
+ processStreamEvent(event) {
352
+ switch (event.type) {
353
+ case "system":
354
+ log("System event", { subtype: event.subtype });
355
+ break;
356
+ case "assistant": {
357
+ const assistantEvent = event;
358
+ this.currentMessageId = assistantEvent.message.id;
359
+ this.currentModel = assistantEvent.message.model;
360
+ for (const block of assistantEvent.message.content) {
361
+ if (block.type === "text" && block.text) {
362
+ const textBlock = {
363
+ type: "text",
364
+ text: block.text
365
+ };
366
+ this.currentContentBlocks.push(textBlock);
367
+ this.accumulatedText += block.text;
368
+ this.io?.emit("output:chunk", block.text);
369
+ this.io?.emit("stream:text_delta", {
370
+ index: this.currentContentBlocks.length - 1,
371
+ text: block.text
372
+ });
373
+ } else if (block.type === "tool_use" && block.id && block.name) {
374
+ const toolBlock = {
375
+ type: "tool_use",
376
+ id: block.id,
377
+ name: block.name,
378
+ input: block.input || {}
379
+ };
380
+ this.currentContentBlocks.push(toolBlock);
381
+ this.io?.emit("stream:tool_use", {
382
+ id: block.id,
383
+ name: block.name,
384
+ input: block.input || {}
385
+ });
386
+ }
387
+ }
388
+ break;
389
+ }
390
+ case "tool_use": {
391
+ const toolEvent = event;
392
+ const toolBlock = {
393
+ type: "tool_use",
394
+ id: toolEvent.tool_use_id,
395
+ name: toolEvent.name,
396
+ input: toolEvent.input
397
+ };
398
+ this.currentContentBlocks.push(toolBlock);
399
+ this.io?.emit("stream:tool_use", {
400
+ id: toolEvent.tool_use_id,
401
+ name: toolEvent.name,
402
+ input: toolEvent.input
403
+ });
404
+ break;
405
+ }
406
+ case "tool_result": {
407
+ const resultEvent = event;
408
+ this.currentContentBlocks.push({
409
+ type: "tool_result",
410
+ tool_use_id: resultEvent.tool_use_id,
411
+ content: resultEvent.content,
412
+ is_error: resultEvent.is_error
413
+ });
414
+ this.io?.emit("stream:tool_result", {
415
+ tool_use_id: resultEvent.tool_use_id,
416
+ name: resultEvent.name,
417
+ content: resultEvent.content,
418
+ is_error: resultEvent.is_error
419
+ });
420
+ break;
421
+ }
422
+ case "result": {
423
+ const resultEvent = event;
424
+ log("Result event", {
425
+ subtype: resultEvent.subtype,
426
+ cost: resultEvent.cost_usd,
427
+ duration: resultEvent.duration_ms,
428
+ session_id: resultEvent.session_id
429
+ });
430
+ if (resultEvent.session_id) {
431
+ this.claudeSessionId = resultEvent.session_id;
432
+ this.historyManager.setClaudeSessionId(resultEvent.session_id);
433
+ log("Saved Claude session ID", { sessionId: resultEvent.session_id });
434
+ }
435
+ this.io?.emit("stream:result", {
436
+ subtype: resultEvent.subtype,
437
+ result: resultEvent.result,
438
+ error: resultEvent.error,
439
+ session_id: resultEvent.session_id,
440
+ cost_usd: resultEvent.cost_usd,
441
+ duration_ms: resultEvent.duration_ms,
442
+ is_error: resultEvent.is_error
443
+ });
444
+ break;
445
+ }
446
+ default:
447
+ log("Unknown event type", { type: event.type });
448
+ }
69
449
  }
70
450
  sendMessage(message) {
71
451
  if (this.isProcessing) {
@@ -73,15 +453,41 @@ export class ClaudeSession {
73
453
  return;
74
454
  }
75
455
  this.isProcessing = true;
456
+ this.resetStreamState();
76
457
  this.io?.emit("session:status", { active: true, processing: true });
458
+ const userMessage = {
459
+ id: generateId(),
460
+ role: "user",
461
+ content: message,
462
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
463
+ };
464
+ this.historyManager.addMessage(userMessage);
465
+ this.io?.emit("stream:message_start", {
466
+ id: this.currentMessageId
467
+ });
77
468
  const args = [
78
469
  ...this.config.args,
79
470
  "-p",
80
471
  message,
472
+ "--output-format",
473
+ "stream-json",
474
+ "--verbose",
81
475
  "--dangerously-skip-permissions"
82
476
  ];
83
- if (this.continueSession) {
84
- args.push("--continue");
477
+ const storedSessionId = this.claudeSessionId || this.historyManager.getClaudeSessionId();
478
+ if (storedSessionId) {
479
+ args.push("--resume", storedSessionId);
480
+ log("Resuming Claude session", { sessionId: storedSessionId });
481
+ } else {
482
+ const systemPrompt = this.buildSystemPrompt();
483
+ if (systemPrompt) {
484
+ args.push("--system-prompt", systemPrompt);
485
+ log("Using system prompt with project context", {
486
+ promptLength: systemPrompt.length
487
+ });
488
+ } else if (this.continueSession) {
489
+ args.push("--continue");
490
+ }
85
491
  }
86
492
  log("Spawning Claude process", { command: this.config.command, args, cwd: this.config.rootDir });
87
493
  const child = spawn(this.config.command, args, {
@@ -97,7 +503,10 @@ export class ClaudeSession {
97
503
  child.stdout?.on("data", (data) => {
98
504
  const chunk = data.toString();
99
505
  log("stdout chunk", { length: chunk.length });
100
- this.io?.emit("output:chunk", chunk);
506
+ const events = this.parseStreamChunk(chunk);
507
+ for (const event of events) {
508
+ this.processStreamEvent(event);
509
+ }
101
510
  });
102
511
  child.stderr?.on("data", (data) => {
103
512
  const chunk = data.toString();
@@ -109,18 +518,46 @@ export class ClaudeSession {
109
518
  this.isProcessing = false;
110
519
  this.io?.emit("session:status", { active: true, processing: false });
111
520
  });
521
+ child.stdin?.on("error", (error) => {
522
+ log("stdin error (process may have exited)", { error: error.message });
523
+ });
112
524
  child.on("close", (code) => {
113
525
  log("Process closed", { exitCode: code });
114
526
  this.isProcessing = false;
115
527
  if (code === 0) {
116
528
  this.continueSession = true;
529
+ const assistantMessage = {
530
+ id: this.currentMessageId,
531
+ role: "assistant",
532
+ content: this.accumulatedText,
533
+ contentBlocks: this.currentContentBlocks.length > 0 ? [...this.currentContentBlocks] : void 0,
534
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
535
+ model: this.currentModel
536
+ };
537
+ this.historyManager.addMessage(assistantMessage);
538
+ this.io?.emit("stream:message_complete", {
539
+ id: this.currentMessageId,
540
+ model: this.currentModel,
541
+ content: this.accumulatedText,
542
+ contentBlocks: this.currentContentBlocks
543
+ });
117
544
  this.io?.emit("output:complete");
118
545
  } else {
119
- this.io?.emit("session:error", `Process exited with code ${code}`);
546
+ const sessionIdWasUsed = this.claudeSessionId || this.historyManager.getClaudeSessionId();
547
+ if (sessionIdWasUsed) {
548
+ log("Clearing expired Claude session ID", { sessionId: sessionIdWasUsed });
549
+ this.claudeSessionId = null;
550
+ this.historyManager.setClaudeSessionId("");
551
+ }
552
+ this.io?.emit("session:error", `Process exited with code ${code}. Session may have expired - try sending the message again.`);
120
553
  }
121
554
  this.io?.emit("session:status", { active: true, processing: false });
122
555
  });
123
- child.stdin?.end();
556
+ try {
557
+ child.stdin?.end();
558
+ } catch (e) {
559
+ log("Error closing stdin", { error: e });
560
+ }
124
561
  }
125
562
  getMcpServers() {
126
563
  const servers = [];
@@ -199,15 +636,20 @@ export class ClaudeSession {
199
636
  }
200
637
  }
201
638
  let sessionInstance = null;
202
- export function getClaudeSession(config) {
639
+ export function initClaudeSession(config) {
203
640
  if (!sessionInstance) {
204
641
  sessionInstance = new ClaudeSession(config);
642
+ log("Session initialized");
205
643
  }
206
644
  return sessionInstance;
207
645
  }
646
+ export function getClaudeSessionInstance() {
647
+ return sessionInstance;
648
+ }
208
649
  export function destroyClaudeSession() {
209
650
  if (sessionInstance) {
210
651
  sessionInstance.destroy();
211
652
  sessionInstance = null;
653
+ log("Session destroyed");
212
654
  }
213
655
  }
@@ -0,0 +1,24 @@
1
+ export interface SlashCommand {
2
+ name: string;
3
+ path: string;
4
+ description?: string;
5
+ allowedTools?: string[];
6
+ disableModelInvocation?: boolean;
7
+ content: string;
8
+ rawContent: string;
9
+ updatedAt: string;
10
+ }
11
+ export declare class CommandsManager {
12
+ private commandsDir;
13
+ constructor(projectPath: string);
14
+ private parseFrontmatter;
15
+ private buildFrontmatter;
16
+ getCommands(): SlashCommand[];
17
+ getCommand(name: string): SlashCommand | null;
18
+ saveCommand(name: string, content: string, options?: {
19
+ description?: string;
20
+ allowedTools?: string[];
21
+ disableModelInvocation?: boolean;
22
+ }): SlashCommand;
23
+ deleteCommand(name: string): boolean;
24
+ }