@vibe80/vibe80 0.1.1

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 (123) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +52 -0
  3. package/bin/vibe80.js +176 -0
  4. package/client/dist/assets/DiffPanel-C_IGzKI5.js +1 -0
  5. package/client/dist/assets/ExplorerPanel-BtlyAT00.js +11 -0
  6. package/client/dist/assets/LogsPanel-BW79JWzR.js +1 -0
  7. package/client/dist/assets/SettingsPanel-b9B7ygP_.js +1 -0
  8. package/client/dist/assets/TerminalPanel-C3fc1HbK.js +1 -0
  9. package/client/dist/assets/browser-e3WgtMs-.js +8 -0
  10. package/client/dist/assets/index-CgqGyssr.css +32 -0
  11. package/client/dist/assets/index-DnwKjoj7.js +706 -0
  12. package/client/dist/assets/vibe80_dark-D7OVPKcU.svg +51 -0
  13. package/client/dist/assets/vibe80_light-BJK37ybI.svg +50 -0
  14. package/client/dist/favicon.ico +0 -0
  15. package/client/dist/favicon.png +0 -0
  16. package/client/dist/favicon.svg +35 -0
  17. package/client/dist/index.html +14 -0
  18. package/client/index.html +16 -0
  19. package/client/package.json +34 -0
  20. package/client/public/favicon.ico +0 -0
  21. package/client/public/favicon.png +0 -0
  22. package/client/public/favicon.svg +35 -0
  23. package/client/public/pwa-192x192.png +0 -0
  24. package/client/public/pwa-512x512.png +0 -0
  25. package/client/src/App.jsx +3131 -0
  26. package/client/src/assets/logo_small.png +0 -0
  27. package/client/src/assets/vibe80_dark.svg +51 -0
  28. package/client/src/assets/vibe80_light.svg +50 -0
  29. package/client/src/components/Chat/ChatComposer.jsx +228 -0
  30. package/client/src/components/Chat/ChatMessages.jsx +811 -0
  31. package/client/src/components/Chat/ChatToolbar.jsx +109 -0
  32. package/client/src/components/Chat/useChatComposer.js +462 -0
  33. package/client/src/components/Diff/DiffPanel.jsx +129 -0
  34. package/client/src/components/Explorer/ExplorerPanel.jsx +449 -0
  35. package/client/src/components/Logs/LogsPanel.jsx +80 -0
  36. package/client/src/components/SessionGate/SessionGate.jsx +874 -0
  37. package/client/src/components/Settings/SettingsPanel.jsx +212 -0
  38. package/client/src/components/Terminal/TerminalPanel.jsx +39 -0
  39. package/client/src/components/Topbar/Topbar.jsx +101 -0
  40. package/client/src/components/WorktreeTabs.css +419 -0
  41. package/client/src/components/WorktreeTabs.jsx +604 -0
  42. package/client/src/hooks/useAttachments.jsx +125 -0
  43. package/client/src/hooks/useBacklog.js +254 -0
  44. package/client/src/hooks/useChatClear.js +90 -0
  45. package/client/src/hooks/useChatCollapse.js +42 -0
  46. package/client/src/hooks/useChatCommands.js +294 -0
  47. package/client/src/hooks/useChatExport.js +144 -0
  48. package/client/src/hooks/useChatMessagesState.js +69 -0
  49. package/client/src/hooks/useChatSend.js +158 -0
  50. package/client/src/hooks/useChatSocket.js +1239 -0
  51. package/client/src/hooks/useDiffNavigation.js +19 -0
  52. package/client/src/hooks/useExplorerActions.js +1184 -0
  53. package/client/src/hooks/useGitIdentity.js +114 -0
  54. package/client/src/hooks/useLayoutMode.js +31 -0
  55. package/client/src/hooks/useLocalPreferences.js +131 -0
  56. package/client/src/hooks/useMessageSync.js +30 -0
  57. package/client/src/hooks/useNotifications.js +132 -0
  58. package/client/src/hooks/usePaneNavigation.js +67 -0
  59. package/client/src/hooks/usePanelState.js +13 -0
  60. package/client/src/hooks/useProviderSelection.js +70 -0
  61. package/client/src/hooks/useRepoBranchesModels.js +218 -0
  62. package/client/src/hooks/useRepoStatus.js +350 -0
  63. package/client/src/hooks/useRpcLogActions.js +19 -0
  64. package/client/src/hooks/useRpcLogView.js +58 -0
  65. package/client/src/hooks/useSessionHandoff.js +97 -0
  66. package/client/src/hooks/useSessionLifecycle.js +287 -0
  67. package/client/src/hooks/useSessionReset.js +63 -0
  68. package/client/src/hooks/useSessionResync.js +77 -0
  69. package/client/src/hooks/useTerminalSession.js +328 -0
  70. package/client/src/hooks/useToolbarExport.js +27 -0
  71. package/client/src/hooks/useTurnInterrupt.js +43 -0
  72. package/client/src/hooks/useVibe80Forms.js +128 -0
  73. package/client/src/hooks/useWorkspaceAuth.js +932 -0
  74. package/client/src/hooks/useWorktreeCloseConfirm.js +46 -0
  75. package/client/src/hooks/useWorktrees.js +396 -0
  76. package/client/src/i18n.jsx +87 -0
  77. package/client/src/index.css +5147 -0
  78. package/client/src/locales/en.json +37 -0
  79. package/client/src/locales/fr.json +321 -0
  80. package/client/src/main.jsx +16 -0
  81. package/client/vite.config.js +62 -0
  82. package/docs/api/asyncapi.json +1511 -0
  83. package/docs/api/openapi.json +3242 -0
  84. package/git_hooks/prepare-commit-msg +35 -0
  85. package/package.json +36 -0
  86. package/server/package.json +29 -0
  87. package/server/scripts/rotate-workspace-secret.js +101 -0
  88. package/server/src/claudeClient.js +454 -0
  89. package/server/src/clientEvents.js +594 -0
  90. package/server/src/clientFactory.js +164 -0
  91. package/server/src/codexClient.js +468 -0
  92. package/server/src/config.js +27 -0
  93. package/server/src/helpers.js +138 -0
  94. package/server/src/index.js +1641 -0
  95. package/server/src/middleware/auth.js +93 -0
  96. package/server/src/middleware/debug.js +89 -0
  97. package/server/src/middleware/errorTypes.js +60 -0
  98. package/server/src/providerLogger.js +60 -0
  99. package/server/src/routes/files.js +114 -0
  100. package/server/src/routes/git.js +183 -0
  101. package/server/src/routes/health.js +13 -0
  102. package/server/src/routes/sessions.js +407 -0
  103. package/server/src/routes/workspaces.js +296 -0
  104. package/server/src/routes/worktrees.js +993 -0
  105. package/server/src/runAs.js +458 -0
  106. package/server/src/runtimeStore.js +32 -0
  107. package/server/src/services/auth.js +157 -0
  108. package/server/src/services/claudeThreadDirectory.js +33 -0
  109. package/server/src/services/session.js +918 -0
  110. package/server/src/services/workspace.js +858 -0
  111. package/server/src/storage/index.js +17 -0
  112. package/server/src/storage/redis.js +412 -0
  113. package/server/src/storage/sqlite.js +649 -0
  114. package/server/src/worktreeManager.js +717 -0
  115. package/server/tests/README.md +13 -0
  116. package/server/tests/factories/workspaceFactory.js +13 -0
  117. package/server/tests/fixtures/workspaceCredentials.json +4 -0
  118. package/server/tests/integration/routes/workspaces-routes.test.js +626 -0
  119. package/server/tests/setup/env.js +9 -0
  120. package/server/tests/unit/helpers.test.js +95 -0
  121. package/server/tests/unit/services/auth.test.js +181 -0
  122. package/server/tests/unit/services/workspace.test.js +115 -0
  123. package/server/vitest.config.js +23 -0
@@ -0,0 +1,594 @@
1
+ /**
2
+ * Client event handlers for Codex (app-server) and Claude (CLI) clients.
3
+ *
4
+ * Each function receives a `context` object and a `deps` object:
5
+ *
6
+ * context = { sessionId, worktreeId, provider, client }
7
+ * - worktreeId: null/"main" for the main session, a string for a worktree
8
+ *
9
+ * deps = {
10
+ * getSession, broadcastToSession, appendMessage, broadcastDiff,
11
+ * updateWorktreeStatus, updateWorktreeThreadId, appendRpcLog,
12
+ * getProviderLabel, storage, debugApiWsLog,
13
+ * }
14
+ */
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Codex (app-server JSONRPC) events
18
+ // ---------------------------------------------------------------------------
19
+
20
+ export function attachCodexEvents(context, deps) {
21
+ const { sessionId, worktreeId, provider, client } = context;
22
+ const {
23
+ getSession,
24
+ broadcastToSession,
25
+ appendMessage,
26
+ broadcastDiff,
27
+ updateWorktreeStatus,
28
+ updateWorktreeThreadId,
29
+ appendRpcLog,
30
+ getProviderLabel,
31
+ storage,
32
+ debugApiWsLog,
33
+ } = deps;
34
+
35
+ const isWorktree = worktreeId != null;
36
+ const scopedWorktreeId = worktreeId || "main";
37
+ const withWorktreeScope = (payload) => ({
38
+ ...payload,
39
+ worktreeId: scopedWorktreeId,
40
+ });
41
+ let lastAuthError = null;
42
+
43
+ client.on("thread_starting", () => {
44
+ void (async () => {
45
+ const session = await getSession(sessionId);
46
+ if (!session) return;
47
+ if (isWorktree) {
48
+ await updateWorktreeStatus(session, worktreeId, "processing");
49
+ broadcastToSession(sessionId, {
50
+ type: "worktree_status",
51
+ worktreeId,
52
+ status: "processing",
53
+ });
54
+ } else if (session.activeProvider === provider) {
55
+ await updateWorktreeStatus(session, "main", "processing");
56
+ broadcastToSession(sessionId, {
57
+ type: "worktree_status",
58
+ worktreeId: "main",
59
+ status: "processing",
60
+ });
61
+ broadcastToSession(sessionId, {
62
+ type: "provider_status",
63
+ status: "starting",
64
+ provider,
65
+ });
66
+ broadcastToSession(sessionId, {
67
+ type: "status",
68
+ message: `Starting ${getProviderLabel(session)}...`,
69
+ provider,
70
+ });
71
+ }
72
+ })();
73
+ });
74
+
75
+ client.on("ready", ({ threadId }) => {
76
+ void (async () => {
77
+ const session = await getSession(sessionId);
78
+ if (!session) return;
79
+ if (isWorktree) {
80
+ if (threadId) {
81
+ await updateWorktreeThreadId(session, worktreeId, threadId);
82
+ }
83
+ await updateWorktreeStatus(session, worktreeId, "ready");
84
+ broadcastToSession(sessionId, {
85
+ type: "worktree_ready",
86
+ worktreeId,
87
+ threadId,
88
+ provider,
89
+ });
90
+ } else {
91
+ if (threadId) {
92
+ const updated = { ...session, threadId, lastActivityAt: Date.now() };
93
+ await storage.saveSession(sessionId, updated);
94
+ await updateWorktreeThreadId(session, "main", threadId);
95
+ }
96
+ if (session.activeProvider === provider) {
97
+ await updateWorktreeStatus(session, "main", "ready");
98
+ broadcastToSession(sessionId, {
99
+ type: "worktree_status",
100
+ worktreeId: "main",
101
+ status: "ready",
102
+ });
103
+ broadcastToSession(sessionId, { type: "ready", threadId, provider });
104
+ broadcastToSession(sessionId, {
105
+ type: "provider_status",
106
+ status: "ready",
107
+ provider,
108
+ });
109
+ }
110
+ }
111
+ })();
112
+ });
113
+
114
+ client.on("log", (message) => {
115
+ if (!message) return;
116
+ const prefix = isWorktree
117
+ ? `[codex:${sessionId}:wt-${worktreeId}]`
118
+ : `[codex:${sessionId}]`;
119
+ console.log(`${prefix} ${message}`);
120
+ if (
121
+ message.includes("401 Unauthorized") ||
122
+ message.includes("Unauthorized")
123
+ ) {
124
+ lastAuthError =
125
+ "Erreur d'authentification Codex: vérifiez votre fichier auth.json";
126
+ if (!isWorktree) {
127
+ void (async () => {
128
+ const session = await getSession(sessionId);
129
+ if (session?.activeProvider === provider) {
130
+ broadcastToSession(sessionId, {
131
+ type: "error",
132
+ message: lastAuthError,
133
+ details: message,
134
+ });
135
+ }
136
+ })();
137
+ }
138
+ }
139
+ });
140
+
141
+ client.on("exit", ({ code, signal, reason }) => {
142
+ void (async () => {
143
+ const session = await getSession(sessionId);
144
+ if (isWorktree) {
145
+ if (session) {
146
+ if (reason === "gc_idle") {
147
+ await updateWorktreeStatus(session, worktreeId, "stopped");
148
+ broadcastToSession(sessionId, {
149
+ type: "worktree_status",
150
+ worktreeId,
151
+ status: "stopped",
152
+ error: null,
153
+ });
154
+ } else {
155
+ await updateWorktreeStatus(session, worktreeId, "error");
156
+ broadcastToSession(sessionId, {
157
+ type: "worktree_status",
158
+ worktreeId,
159
+ status: "error",
160
+ error: lastAuthError || "Codex app-server stopped.",
161
+ });
162
+ }
163
+ }
164
+ console.error("Worktree Codex app-server stopped.", {
165
+ code,
166
+ signal,
167
+ reason,
168
+ sessionId,
169
+ worktreeId,
170
+ });
171
+ } else {
172
+ if (session?.activeProvider === provider) {
173
+ await updateWorktreeStatus(session, "main", "error");
174
+ broadcastToSession(sessionId, {
175
+ type: "worktree_status",
176
+ worktreeId: "main",
177
+ status: "error",
178
+ error: lastAuthError || "Codex app-server stopped.",
179
+ });
180
+ const errorMessage = lastAuthError || "Codex app-server stopped.";
181
+ broadcastToSession(sessionId, { type: "error", message: errorMessage });
182
+ }
183
+ console.error("Codex app-server stopped.", { code, signal, sessionId });
184
+ }
185
+ })();
186
+ });
187
+
188
+ client.on("notification", (message) => {
189
+ void (async () => {
190
+ const session = await getSession(sessionId);
191
+ if (!session) return;
192
+ if (!isWorktree && session.activeProvider !== provider) return;
193
+
194
+ switch (message.method) {
195
+ case "thread/started": {
196
+ const threadId = message?.params?.thread?.id;
197
+ if (threadId) {
198
+ if (isWorktree) {
199
+ await updateWorktreeThreadId(session, worktreeId, threadId);
200
+ } else {
201
+ const updated = {
202
+ ...session,
203
+ threadId,
204
+ lastActivityAt: Date.now(),
205
+ };
206
+ await storage.saveSession(sessionId, updated);
207
+ await updateWorktreeThreadId(session, "main", threadId);
208
+ }
209
+ }
210
+ break;
211
+ }
212
+ case "codex/event/agent_reasoning": {
213
+ const text = message?.params?.msg?.text || "";
214
+ if (text) {
215
+ broadcastToSession(
216
+ sessionId,
217
+ withWorktreeScope({ type: "agent_reasoning", text, provider })
218
+ );
219
+ }
220
+ break;
221
+ }
222
+ case "item/agentMessage/delta": {
223
+ const { delta, itemId, turnId } = message.params;
224
+ const payload = {
225
+ type: "assistant_delta",
226
+ delta,
227
+ itemId,
228
+ turnId,
229
+ provider,
230
+ };
231
+ broadcastToSession(sessionId, withWorktreeScope(payload));
232
+ break;
233
+ }
234
+ case "item/commandExecution/outputDelta": {
235
+ const { delta, itemId, turnId, threadId } = message.params;
236
+ const payload = {
237
+ type: "command_execution_delta",
238
+ delta,
239
+ itemId,
240
+ turnId,
241
+ threadId,
242
+ provider,
243
+ };
244
+ broadcastToSession(sessionId, withWorktreeScope(payload));
245
+ break;
246
+ }
247
+ case "item/completed": {
248
+ const { item, turnId } = message.params;
249
+ if (item?.type === "agentMessage") {
250
+ await appendMessage(session, worktreeId, {
251
+ id: item.id,
252
+ role: "assistant",
253
+ text: item.text,
254
+ provider,
255
+ });
256
+ const payload = {
257
+ type: "assistant_message",
258
+ text: item.text,
259
+ itemId: item.id,
260
+ turnId,
261
+ provider,
262
+ };
263
+ broadcastToSession(sessionId, withWorktreeScope(payload));
264
+ void broadcastDiff(sessionId, worktreeId);
265
+ }
266
+ if (item?.type === "commandExecution") {
267
+ await appendMessage(session, worktreeId, {
268
+ id: item.id,
269
+ role: "tool_result",
270
+ text: item.aggregatedOutput || "",
271
+ provider,
272
+ toolResult: {
273
+ callId: item.id,
274
+ name: item.command || "command",
275
+ output: item.aggregatedOutput || "",
276
+ success: item.status === "completed",
277
+ },
278
+ });
279
+ const payload = {
280
+ type: "command_execution_completed",
281
+ item,
282
+ itemId: item.id,
283
+ turnId,
284
+ provider,
285
+ };
286
+ broadcastToSession(sessionId, withWorktreeScope(payload));
287
+ }
288
+ break;
289
+ }
290
+ case "turn/completed": {
291
+ const { turn, threadId } = message.params;
292
+ if (isWorktree) {
293
+ await updateWorktreeStatus(session, worktreeId, "ready");
294
+ broadcastToSession(sessionId, {
295
+ type: "worktree_status",
296
+ worktreeId,
297
+ status: "ready",
298
+ });
299
+ } else if (session.activeProvider === provider) {
300
+ await updateWorktreeStatus(session, "main", "ready");
301
+ broadcastToSession(sessionId, {
302
+ type: "worktree_status",
303
+ worktreeId: "main",
304
+ status: "ready",
305
+ });
306
+ }
307
+ const payload = {
308
+ type: "turn_completed",
309
+ threadId,
310
+ turnId: turn.id,
311
+ status: turn.status,
312
+ error: turn.error || null,
313
+ provider,
314
+ };
315
+ broadcastToSession(sessionId, withWorktreeScope(payload));
316
+ break;
317
+ }
318
+ case "turn/started": {
319
+ const { turn, threadId } = message.params;
320
+ if (isWorktree) {
321
+ await updateWorktreeStatus(session, worktreeId, "processing");
322
+ broadcastToSession(sessionId, {
323
+ type: "worktree_status",
324
+ worktreeId,
325
+ status: "processing",
326
+ });
327
+ } else if (session.activeProvider === provider) {
328
+ await updateWorktreeStatus(session, "main", "processing");
329
+ broadcastToSession(sessionId, {
330
+ type: "worktree_status",
331
+ worktreeId: "main",
332
+ status: "processing",
333
+ });
334
+ }
335
+ const payload = {
336
+ type: "turn_started",
337
+ threadId,
338
+ turnId: turn.id,
339
+ status: turn.status,
340
+ provider,
341
+ };
342
+ broadcastToSession(sessionId, withWorktreeScope(payload));
343
+ break;
344
+ }
345
+ case "item/started": {
346
+ const { item, turnId, threadId } = message.params;
347
+ const payload = {
348
+ type: "item_started",
349
+ threadId,
350
+ turnId,
351
+ item,
352
+ provider,
353
+ };
354
+ broadcastToSession(sessionId, withWorktreeScope(payload));
355
+ break;
356
+ }
357
+ case "error": {
358
+ const { error, threadId, turnId, willRetry } = message.params;
359
+ const payload = {
360
+ type: "turn_error",
361
+ threadId,
362
+ turnId,
363
+ willRetry,
364
+ message: error?.message || "Unknown error",
365
+ provider,
366
+ };
367
+ broadcastToSession(sessionId, withWorktreeScope(payload));
368
+ break;
369
+ }
370
+ case "account/login/completed": {
371
+ if (!isWorktree) {
372
+ const { success, error, loginId } = message.params;
373
+ broadcastToSession(sessionId, {
374
+ type: "account_login_completed",
375
+ success: Boolean(success),
376
+ error: error || null,
377
+ loginId: loginId || null,
378
+ provider,
379
+ });
380
+ }
381
+ break;
382
+ }
383
+ default:
384
+ break;
385
+ }
386
+ })();
387
+ });
388
+
389
+ client.on("rpc_out", (payload) => {
390
+ void (async () => {
391
+ if (!debugApiWsLog) return;
392
+ const entry = {
393
+ direction: "stdin",
394
+ timestamp: Date.now(),
395
+ payload,
396
+ provider,
397
+ };
398
+ entry.worktreeId = scopedWorktreeId;
399
+ await appendRpcLog(sessionId, entry);
400
+ broadcastToSession(sessionId, { type: "rpc_log", entry });
401
+ })();
402
+ });
403
+
404
+ client.on("rpc_in", (payload) => {
405
+ void (async () => {
406
+ if (!debugApiWsLog) return;
407
+ const entry = {
408
+ direction: "stdout",
409
+ timestamp: Date.now(),
410
+ payload,
411
+ provider,
412
+ };
413
+ entry.worktreeId = scopedWorktreeId;
414
+ await appendRpcLog(sessionId, entry);
415
+ broadcastToSession(sessionId, { type: "rpc_log", entry });
416
+ })();
417
+ });
418
+ }
419
+
420
+ // ---------------------------------------------------------------------------
421
+ // Claude CLI events
422
+ // ---------------------------------------------------------------------------
423
+
424
+ export function attachClaudeEvents(context, deps) {
425
+ const { sessionId, worktreeId, provider, client } = context;
426
+ const {
427
+ getSession,
428
+ broadcastToSession,
429
+ appendMessage,
430
+ broadcastDiff,
431
+ updateWorktreeThreadId,
432
+ appendRpcLog,
433
+ storage,
434
+ debugApiWsLog,
435
+ } = deps;
436
+
437
+ const isWorktree = worktreeId != null;
438
+ const scopedWorktreeId = worktreeId || "main";
439
+ const withWorktreeScope = (payload) => ({
440
+ ...payload,
441
+ worktreeId: scopedWorktreeId,
442
+ });
443
+
444
+ client.on("ready", ({ threadId }) => {
445
+ void (async () => {
446
+ const session = await getSession(sessionId);
447
+ if (!session) return;
448
+ if (isWorktree) {
449
+ if (threadId) {
450
+ await updateWorktreeThreadId(session, worktreeId, threadId);
451
+ }
452
+ broadcastToSession(sessionId, {
453
+ type: "worktree_ready",
454
+ worktreeId,
455
+ threadId,
456
+ provider,
457
+ });
458
+ } else if (session.activeProvider === provider) {
459
+ if (threadId) {
460
+ const updated = { ...session, threadId, lastActivityAt: Date.now() };
461
+ await storage.saveSession(sessionId, updated);
462
+ await updateWorktreeThreadId(session, "main", threadId);
463
+ }
464
+ broadcastToSession(sessionId, { type: "ready", threadId, provider });
465
+ }
466
+ })();
467
+ });
468
+
469
+ client.on("turn_started", ({ turnId, status }) => {
470
+ void (async () => {
471
+ const session = await getSession(sessionId);
472
+ if (!session) return;
473
+ if (!isWorktree && session.activeProvider !== provider) return;
474
+
475
+ const payload = {
476
+ type: "turn_started",
477
+ turnId,
478
+ status: status || "processing",
479
+ provider,
480
+ };
481
+ broadcastToSession(sessionId, withWorktreeScope(payload));
482
+ })();
483
+ });
484
+
485
+ client.on("log", (message) => {
486
+ if (message) {
487
+ const prefix = isWorktree
488
+ ? `[claude:${sessionId}:wt-${worktreeId}]`
489
+ : `[claude:${sessionId}]`;
490
+ console.log(`${prefix} ${message}`);
491
+ }
492
+ });
493
+
494
+ client.on("stdout_json", ({ message }) => {
495
+ void (async () => {
496
+ if (!debugApiWsLog) return;
497
+ const entry = {
498
+ direction: "stdout",
499
+ timestamp: Date.now(),
500
+ payload: message,
501
+ provider,
502
+ };
503
+ entry.worktreeId = scopedWorktreeId;
504
+ await appendRpcLog(sessionId, entry);
505
+ broadcastToSession(sessionId, { type: "rpc_log", entry });
506
+ })();
507
+ });
508
+
509
+ client.on("assistant_message", ({ id, text, turnId }) => {
510
+ void (async () => {
511
+ const session = await getSession(sessionId);
512
+ if (!session) return;
513
+ if (!isWorktree && session.activeProvider !== provider) return;
514
+
515
+ await appendMessage(session, worktreeId, {
516
+ id,
517
+ role: "assistant",
518
+ text,
519
+ provider,
520
+ });
521
+ const payload = {
522
+ type: "assistant_message",
523
+ text,
524
+ itemId: id,
525
+ turnId,
526
+ provider,
527
+ };
528
+ broadcastToSession(sessionId, withWorktreeScope(payload));
529
+ void broadcastDiff(sessionId, worktreeId);
530
+ })();
531
+ });
532
+
533
+ client.on("command_execution_completed", (payload) => {
534
+ void (async () => {
535
+ const session = await getSession(sessionId);
536
+ if (!session) return;
537
+ if (!isWorktree && session.activeProvider !== provider) return;
538
+
539
+ await appendMessage(session, worktreeId, {
540
+ id: payload.itemId,
541
+ role: "tool_result",
542
+ text: payload.item?.aggregatedOutput || "",
543
+ provider,
544
+ toolResult: {
545
+ callId: payload.itemId,
546
+ name: payload.item?.command || "tool",
547
+ output: payload.item?.aggregatedOutput || "",
548
+ success: payload.item?.status === "completed",
549
+ },
550
+ });
551
+ const msg = {
552
+ type: "command_execution_completed",
553
+ item: payload.item,
554
+ itemId: payload.itemId,
555
+ turnId: payload.turnId,
556
+ provider,
557
+ };
558
+ broadcastToSession(sessionId, withWorktreeScope(msg));
559
+ })();
560
+ });
561
+
562
+ client.on("turn_completed", ({ turnId, status }) => {
563
+ void (async () => {
564
+ const session = await getSession(sessionId);
565
+ if (!session) return;
566
+ if (!isWorktree && session.activeProvider !== provider) return;
567
+
568
+ const payload = {
569
+ type: "turn_completed",
570
+ turnId,
571
+ status: status || "success",
572
+ error: null,
573
+ provider,
574
+ };
575
+ broadcastToSession(sessionId, withWorktreeScope(payload));
576
+ })();
577
+ });
578
+
579
+ client.on("turn_error", ({ turnId, message }) => {
580
+ void (async () => {
581
+ if (!isWorktree) {
582
+ const session = await getSession(sessionId);
583
+ if (!session || session.activeProvider !== provider) return;
584
+ }
585
+ const payload = {
586
+ type: "turn_error",
587
+ turnId,
588
+ message: message || "Claude CLI error.",
589
+ provider,
590
+ };
591
+ broadcastToSession(sessionId, withWorktreeScope(payload));
592
+ })();
593
+ });
594
+ }