@pixelbyte-software/pixcode 1.35.0 → 1.35.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 (150) hide show
  1. package/LICENSE +718 -718
  2. package/README.de.md +248 -248
  3. package/README.ja.md +240 -240
  4. package/README.ko.md +240 -240
  5. package/README.md +303 -303
  6. package/README.ru.md +248 -248
  7. package/README.tr.md +250 -250
  8. package/README.zh-CN.md +240 -240
  9. package/dist/api-docs.html +548 -548
  10. package/dist/assets/{index-Djuh0wHV.js → index-CBdsvGSR.js} +133 -133
  11. package/dist/clear-cache.html +85 -85
  12. package/dist/convert-icons.md +52 -52
  13. package/dist/generate-icons.js +48 -48
  14. package/dist/icons/codex-white.svg +3 -3
  15. package/dist/icons/codex.svg +3 -3
  16. package/dist/icons/cursor-white.svg +11 -11
  17. package/dist/icons/qwen-logo.svg +14 -14
  18. package/dist/index.html +58 -58
  19. package/dist/manifest.json +60 -60
  20. package/dist/openapi.yaml +1693 -1693
  21. package/dist/sw.js +124 -124
  22. package/dist-server/server/cli.js +96 -96
  23. package/dist-server/server/daemon/manager.js +33 -33
  24. package/dist-server/server/daemon-manager.js +64 -64
  25. package/dist-server/server/modules/orchestration/preview/preview-proxy.js +3 -3
  26. package/dist-server/server/modules/orchestration/preview/preview-proxy.js.map +1 -1
  27. package/dist-server/server/routes/commands.js +25 -25
  28. package/dist-server/server/routes/git.js +17 -17
  29. package/dist-server/server/routes/taskmaster.js +419 -419
  30. package/package.json +180 -180
  31. package/scripts/fix-node-pty.js +67 -67
  32. package/scripts/smoke/a2a-roundtrip.mjs +167 -167
  33. package/scripts/smoke/orchestration-api.mjs +172 -172
  34. package/scripts/smoke/orchestration-live-run.mjs +176 -176
  35. package/server/claude-sdk.js +898 -898
  36. package/server/cli.js +935 -935
  37. package/server/constants/config.js +4 -4
  38. package/server/cursor-cli.js +342 -342
  39. package/server/daemon/manager.js +564 -564
  40. package/server/daemon-manager.js +959 -959
  41. package/server/database/json-store.js +197 -197
  42. package/server/gemini-cli.js +535 -535
  43. package/server/gemini-response-handler.js +79 -79
  44. package/server/index.js +3135 -3135
  45. package/server/load-env.js +34 -34
  46. package/server/middleware/auth.js +173 -173
  47. package/server/modules/orchestration/a2a/adapter-registry.ts +108 -108
  48. package/server/modules/orchestration/a2a/adapters/abstract-a2a.adapter.ts +55 -55
  49. package/server/modules/orchestration/a2a/adapters/claude-code.adapter.ts +284 -284
  50. package/server/modules/orchestration/a2a/adapters/codex.adapter.ts +244 -244
  51. package/server/modules/orchestration/a2a/adapters/cursor.adapter.ts +249 -249
  52. package/server/modules/orchestration/a2a/adapters/gemini.adapter.ts +248 -248
  53. package/server/modules/orchestration/a2a/adapters/opencode.adapter.ts +248 -248
  54. package/server/modules/orchestration/a2a/adapters/qwen.adapter.ts +248 -248
  55. package/server/modules/orchestration/a2a/agent-card.ts +55 -55
  56. package/server/modules/orchestration/a2a/auth.middleware.ts +29 -29
  57. package/server/modules/orchestration/a2a/bus.ts +46 -46
  58. package/server/modules/orchestration/a2a/routes.ts +577 -577
  59. package/server/modules/orchestration/a2a/task-store.ts +178 -178
  60. package/server/modules/orchestration/a2a/types.ts +125 -125
  61. package/server/modules/orchestration/a2a/validator.ts +113 -113
  62. package/server/modules/orchestration/index.ts +66 -66
  63. package/server/modules/orchestration/preview/port-watcher.ts +112 -112
  64. package/server/modules/orchestration/preview/preview-proxy.ts +60 -60
  65. package/server/modules/orchestration/preview/types.ts +19 -19
  66. package/server/modules/orchestration/tasks/orchestration-task-store.ts +45 -45
  67. package/server/modules/orchestration/tasks/orchestration-task.routes.ts +73 -73
  68. package/server/modules/orchestration/tasks/orchestration-task.service.ts +145 -145
  69. package/server/modules/orchestration/tasks/orchestration-task.types.ts +29 -29
  70. package/server/modules/orchestration/workflows/built-in-workflows.ts +127 -127
  71. package/server/modules/orchestration/workflows/workflow-runner.ts +1206 -1206
  72. package/server/modules/orchestration/workflows/workflow-store.ts +97 -97
  73. package/server/modules/orchestration/workflows/workflow.routes.ts +169 -169
  74. package/server/modules/orchestration/workflows/workflow.types.ts +70 -70
  75. package/server/modules/orchestration/workflows/workspace-target.ts +120 -120
  76. package/server/modules/orchestration/workspace/docker-workspace.ts +135 -135
  77. package/server/modules/orchestration/workspace/path-safety.ts +55 -55
  78. package/server/modules/orchestration/workspace/types.ts +52 -52
  79. package/server/modules/orchestration/workspace/workspace-manager.ts +97 -97
  80. package/server/modules/orchestration/workspace/worktree-workspace.ts +125 -125
  81. package/server/modules/providers/index.ts +2 -2
  82. package/server/modules/providers/list/claude/claude-auth.provider.ts +145 -145
  83. package/server/modules/providers/list/claude/claude-mcp.provider.ts +135 -135
  84. package/server/modules/providers/list/claude/claude-sessions.provider.ts +306 -306
  85. package/server/modules/providers/list/claude/claude.provider.ts +15 -15
  86. package/server/modules/providers/list/codex/codex-auth.provider.ts +115 -115
  87. package/server/modules/providers/list/codex/codex-mcp.provider.ts +135 -135
  88. package/server/modules/providers/list/codex/codex-sessions.provider.ts +319 -319
  89. package/server/modules/providers/list/codex/codex.provider.ts +15 -15
  90. package/server/modules/providers/list/cursor/cursor-auth.provider.ts +143 -143
  91. package/server/modules/providers/list/cursor/cursor-mcp.provider.ts +108 -108
  92. package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +421 -421
  93. package/server/modules/providers/list/cursor/cursor.provider.ts +15 -15
  94. package/server/modules/providers/list/gemini/gemini-auth.provider.ts +163 -163
  95. package/server/modules/providers/list/gemini/gemini-mcp.provider.ts +110 -110
  96. package/server/modules/providers/list/gemini/gemini-sessions.provider.ts +227 -227
  97. package/server/modules/providers/list/gemini/gemini.provider.ts +15 -15
  98. package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +232 -232
  99. package/server/modules/providers/list/qwen/qwen-sessions.provider.ts +265 -265
  100. package/server/modules/providers/provider.registry.ts +40 -40
  101. package/server/modules/providers/provider.routes.ts +819 -819
  102. package/server/modules/providers/services/mcp.service.ts +86 -86
  103. package/server/modules/providers/services/provider-auth.service.ts +26 -26
  104. package/server/modules/providers/services/sessions.service.ts +45 -45
  105. package/server/modules/providers/shared/base/abstract.provider.ts +20 -20
  106. package/server/modules/providers/shared/mcp/mcp.provider.ts +151 -151
  107. package/server/modules/providers/tests/mcp.test.ts +293 -293
  108. package/server/openai-codex.js +462 -462
  109. package/server/opencode-cli.js +459 -459
  110. package/server/opencode-response-handler.js +107 -107
  111. package/server/projects.js +3105 -3105
  112. package/server/routes/agent.js +1365 -1365
  113. package/server/routes/auth.js +138 -138
  114. package/server/routes/codex.js +19 -19
  115. package/server/routes/commands.js +554 -554
  116. package/server/routes/cursor.js +52 -52
  117. package/server/routes/gemini.js +24 -24
  118. package/server/routes/git.js +1488 -1488
  119. package/server/routes/mcp-utils.js +31 -31
  120. package/server/routes/messages.js +61 -61
  121. package/server/routes/network.js +120 -120
  122. package/server/routes/plugins.js +318 -318
  123. package/server/routes/projects.js +915 -915
  124. package/server/routes/settings.js +286 -286
  125. package/server/routes/taskmaster.js +1496 -1496
  126. package/server/routes/telegram.js +125 -125
  127. package/server/routes/user.js +123 -123
  128. package/server/services/install-jobs.js +571 -571
  129. package/server/services/notification-orchestrator.js +242 -242
  130. package/server/services/provider-credentials.js +189 -189
  131. package/server/services/telegram/bot.js +279 -279
  132. package/server/services/telegram/translations.js +170 -170
  133. package/server/sessionManager.js +225 -225
  134. package/server/shared/interfaces.ts +54 -54
  135. package/server/shared/types.ts +172 -172
  136. package/server/shared/utils.ts +193 -193
  137. package/server/tsconfig.json +36 -36
  138. package/server/utils/colors.js +21 -21
  139. package/server/utils/commandParser.js +303 -303
  140. package/server/utils/frontmatter.js +18 -18
  141. package/server/utils/gitConfig.js +34 -34
  142. package/server/utils/mcp-detector.js +147 -147
  143. package/server/utils/plugin-loader.js +457 -457
  144. package/server/utils/plugin-process-manager.js +184 -184
  145. package/server/utils/runtime-paths.js +37 -37
  146. package/server/utils/taskmaster-websocket.js +128 -128
  147. package/server/utils/url-detection.js +71 -71
  148. package/server/vite-daemon.js +78 -78
  149. package/shared/modelConstants.js +162 -162
  150. package/shared/networkHosts.js +22 -22
@@ -1,284 +1,284 @@
1
- // server/modules/orchestration/a2a/adapters/claude-code.adapter.ts
2
- // Wraps the existing server/claude-sdk.js queryClaudeSDK() function.
3
- // claude-sdk.js was designed to stream SDK messages over a WebSocket
4
- // connection, so we feed it a "fake WS" that captures send() calls and
5
- // emits A2A bus events instead.
6
- //
7
- // IMPORTANT: claude-sdk.js calls ws.send(<NormalizedMessage object>) — it
8
- // does NOT JSON.stringify before send. Our shim therefore receives objects
9
- // (not strings) and dispatches on `frame.kind` (not `frame.type`). See
10
- // server/shared/types.ts for the MessageKind enum.
11
-
12
- import crypto from 'node:crypto';
13
-
14
- // eslint-disable-next-line boundaries/no-unknown -- claude-sdk.js is a top-level CLI runtime not yet classified by eslint.config.js; cleanup deferred (cascades into a server/services classification gap).
15
- import { abortClaudeSDKSession, queryClaudeSDK } from '@/claude-sdk.js';
16
- import { AbstractA2AAdapter } from '@/modules/orchestration/a2a/adapters/abstract-a2a.adapter.js';
17
- import type {
18
- AdapterContext,
19
- TaskHandle,
20
- } from '@/modules/orchestration/a2a/adapters/abstract-a2a.adapter.js';
21
- import type { AgentCard, Part, Task } from '@/modules/orchestration/a2a/types.js';
22
-
23
- interface FakeWS {
24
- send(data: unknown): void;
25
- readyState: number;
26
- }
27
-
28
- // WebSocket.OPEN per the ws library — claude-sdk.js gates send() on readyState === 1.
29
- const WS_OPEN = 1;
30
-
31
- function joinPartsToPrompt(parts: Part[]): string {
32
- return parts
33
- .map((p) => {
34
- if (p.kind === 'text') return p.text;
35
- if (p.kind === 'data') return JSON.stringify(p.data);
36
- // file parts: include name + uri/inline marker
37
- return `[file:${p.name}${p.uri ? ` uri=${p.uri}` : ''}]`;
38
- })
39
- .join('\n');
40
- }
41
-
42
- function newId(prefix: string): string {
43
- return `${prefix}_${crypto.randomBytes(8).toString('hex')}`;
44
- }
45
-
46
- export class ClaudeCodeA2AAdapter extends AbstractA2AAdapter {
47
- readonly id = 'claude-code';
48
-
49
- readonly agentCard: AgentCard = {
50
- name: 'pixcode-claude-code',
51
- description: 'Anthropic Claude Code, accessed via Pixcode',
52
- url: '/a2a/agents/claude-code',
53
- version: '1.0.0',
54
- capabilities: ['streaming', 'fileEdit', 'commandExec', 'mcp'],
55
- skills: [
56
- {
57
- id: 'architectural-review',
58
- description: 'Review code architecture and propose structural changes',
59
- },
60
- {
61
- id: 'typescript-edit',
62
- description: 'Edit TypeScript files with type-aware reasoning',
63
- },
64
- {
65
- id: 'multi-file-refactor',
66
- description: 'Coordinated edits across many files',
67
- },
68
- {
69
- id: 'test-run',
70
- description: 'Run test suites and react to results',
71
- },
72
- ],
73
- authentication: { type: 'bearer' },
74
- };
75
-
76
- private readonly active = new Map<string, { sessionId: string | null }>();
77
-
78
- async submitTask(task: Task, ctx: AdapterContext): Promise<TaskHandle> {
79
- // Foundation: only the last user message is fed in. Multi-turn resumption
80
- // (input-required tasks, workflow chaining) needs to pass options.sessionId
81
- // and append history; deferred to a follow-on plan.
82
- const promptText = joinPartsToPrompt(
83
- task.history[task.history.length - 1]?.parts ?? [],
84
- );
85
- const session = { sessionId: null as string | null };
86
- this.active.set(task.id, session);
87
-
88
- this.emitState(task.id, 'working');
89
-
90
- const fakeWS: FakeWS = {
91
- readyState: WS_OPEN,
92
- send: (data) => this.handleSdkFrame(task.id, data, session),
93
- };
94
-
95
- const finished = (async () => {
96
- try {
97
- await queryClaudeSDK(
98
- promptText,
99
- {
100
- cwd: ctx.cwd,
101
- permissionMode: ctx.permissionMode ?? 'default',
102
- toolsSettings: ctx.toolsSettings,
103
- },
104
- fakeWS,
105
- );
106
- // If cancelTask removed us from `active` first, suppress the spurious
107
- // 'completed' that would otherwise race the 'canceled' state.
108
- if (this.active.has(task.id)) {
109
- this.emitState(task.id, 'completed');
110
- }
111
- } catch (err) {
112
- if (this.active.has(task.id)) {
113
- this.emitState(task.id, 'failed', {
114
- code: 'ADAPTER_RUNTIME_ERROR',
115
- message: err instanceof Error ? err.message : String(err),
116
- });
117
- }
118
- } finally {
119
- this.active.delete(task.id);
120
- }
121
- })();
122
-
123
- return {
124
- cancel: () => this.cancelTask(task.id),
125
- finished,
126
- };
127
- }
128
-
129
- async cancelTask(taskId: string): Promise<void> {
130
- const session = this.active.get(taskId);
131
- if (!session) {
132
- this.emitState(taskId, 'canceled');
133
- return;
134
- }
135
- // Delete BEFORE awaiting so submitTask's IIFE guard (this.active.has)
136
- // suppresses the spurious 'completed' state when queryClaudeSDK's
137
- // for-await loop unwinds from the abort.
138
- this.active.delete(taskId);
139
- if (session.sessionId) {
140
- try {
141
- await abortClaudeSDKSession(session.sessionId);
142
- } catch {
143
- // swallow — adapter has already cleaned its own state
144
- }
145
- }
146
- this.emitState(taskId, 'canceled');
147
- }
148
-
149
- /**
150
- * claude-sdk.js calls `ws.send(<NormalizedMessage>)` with a JS OBJECT
151
- * (not a JSON string). We translate each frame into A2A bus events.
152
- * See server/shared/types.ts for the MessageKind union.
153
- */
154
- private handleSdkFrame(
155
- taskId: string,
156
- frame: unknown,
157
- session: { sessionId: string | null },
158
- ): void {
159
- if (!frame || typeof frame !== 'object') return;
160
- const f = frame as {
161
- kind?: string;
162
- sessionId?: unknown;
163
- newSessionId?: unknown;
164
- text?: unknown;
165
- content?: unknown;
166
- toolName?: unknown;
167
- toolInput?: unknown;
168
- toolResult?: unknown;
169
- };
170
-
171
- // session_created carries the new session id in `newSessionId`. Capture
172
- // it here so cancelTask can call abortClaudeSDKSession with the right id.
173
- if (
174
- f.kind === 'session_created' &&
175
- typeof f.newSessionId === 'string' &&
176
- !session.sessionId
177
- ) {
178
- session.sessionId = f.newSessionId;
179
- }
180
-
181
- switch (f.kind) {
182
- case 'session_created':
183
- case 'status':
184
- case 'stream_delta':
185
- case 'stream_end':
186
- // session_created and status are not user-facing.
187
- // stream_delta and stream_end CARRY user-visible delta text but are
188
- // not currently emitted by claude-sdk.js (it doesn't pass
189
- // includePartialMessages: true to query()). If that flag flips on
190
- // upstream, these cases must be re-routed to emit text Messages.
191
- return;
192
-
193
- case 'text':
194
- case 'thinking': {
195
- const text =
196
- typeof f.text === 'string'
197
- ? f.text
198
- : typeof f.content === 'string'
199
- ? f.content
200
- : null;
201
- if (text) {
202
- this.emitMessage(taskId, {
203
- messageId: newId('msg'),
204
- role: 'agent',
205
- parts: [{ kind: 'text', text }],
206
- taskId,
207
- });
208
- }
209
- return;
210
- }
211
-
212
- case 'tool_use': {
213
- this.emitArtifact(taskId, {
214
- artifactId: newId('art'),
215
- type: 'command-output',
216
- parts: [
217
- {
218
- kind: 'data',
219
- data: { toolName: f.toolName, toolInput: f.toolInput },
220
- },
221
- ],
222
- metadata: { source: 'claude-tool-use' },
223
- });
224
- return;
225
- }
226
-
227
- case 'tool_result': {
228
- this.emitArtifact(taskId, {
229
- artifactId: newId('art'),
230
- type: 'command-output',
231
- parts: [{ kind: 'data', data: { toolResult: f.toolResult } }],
232
- metadata: { source: 'claude-tool-result' },
233
- });
234
- return;
235
- }
236
-
237
- case 'permission_request':
238
- case 'permission_cancelled':
239
- case 'interactive_prompt':
240
- case 'task_notification':
241
- // Informational — surface as data artifact for visibility.
242
- this.emitArtifact(taskId, {
243
- artifactId: newId('art'),
244
- type: 'data',
245
- parts: [{ kind: 'data', data: f as Record<string, unknown> }],
246
- metadata: { source: `claude-${f.kind}` },
247
- });
248
- return;
249
-
250
- case 'error': {
251
- // claude-sdk.js catches internally and emits an error frame without
252
- // rethrowing, so the IIFE await would resolve cleanly. Force the
253
- // failed state here and remove from active so the IIFE's
254
- // 'completed' emit is suppressed by its active.has() guard.
255
- const message =
256
- typeof f.content === 'string'
257
- ? f.content
258
- : typeof f.text === 'string'
259
- ? f.text
260
- : 'Claude Code reported an error';
261
- this.emitState(taskId, 'failed', {
262
- code: 'CLAUDE_RUNTIME_ERROR',
263
- message,
264
- details: f as Record<string, unknown>,
265
- });
266
- this.active.delete(taskId);
267
- return;
268
- }
269
-
270
- case 'complete':
271
- // Lifecycle redundant with the IIFE's 'completed' emit; suppress to
272
- // avoid double-signaling. The IIFE owns terminal state transitions.
273
- return;
274
-
275
- default:
276
- // Unknown kind — surface for visibility
277
- this.emitArtifact(taskId, {
278
- artifactId: newId('art'),
279
- type: 'data',
280
- parts: [{ kind: 'data', data: f as Record<string, unknown> }],
281
- });
282
- }
283
- }
284
- }
1
+ // server/modules/orchestration/a2a/adapters/claude-code.adapter.ts
2
+ // Wraps the existing server/claude-sdk.js queryClaudeSDK() function.
3
+ // claude-sdk.js was designed to stream SDK messages over a WebSocket
4
+ // connection, so we feed it a "fake WS" that captures send() calls and
5
+ // emits A2A bus events instead.
6
+ //
7
+ // IMPORTANT: claude-sdk.js calls ws.send(<NormalizedMessage object>) — it
8
+ // does NOT JSON.stringify before send. Our shim therefore receives objects
9
+ // (not strings) and dispatches on `frame.kind` (not `frame.type`). See
10
+ // server/shared/types.ts for the MessageKind enum.
11
+
12
+ import crypto from 'node:crypto';
13
+
14
+ // eslint-disable-next-line boundaries/no-unknown -- claude-sdk.js is a top-level CLI runtime not yet classified by eslint.config.js; cleanup deferred (cascades into a server/services classification gap).
15
+ import { abortClaudeSDKSession, queryClaudeSDK } from '@/claude-sdk.js';
16
+ import { AbstractA2AAdapter } from '@/modules/orchestration/a2a/adapters/abstract-a2a.adapter.js';
17
+ import type {
18
+ AdapterContext,
19
+ TaskHandle,
20
+ } from '@/modules/orchestration/a2a/adapters/abstract-a2a.adapter.js';
21
+ import type { AgentCard, Part, Task } from '@/modules/orchestration/a2a/types.js';
22
+
23
+ interface FakeWS {
24
+ send(data: unknown): void;
25
+ readyState: number;
26
+ }
27
+
28
+ // WebSocket.OPEN per the ws library — claude-sdk.js gates send() on readyState === 1.
29
+ const WS_OPEN = 1;
30
+
31
+ function joinPartsToPrompt(parts: Part[]): string {
32
+ return parts
33
+ .map((p) => {
34
+ if (p.kind === 'text') return p.text;
35
+ if (p.kind === 'data') return JSON.stringify(p.data);
36
+ // file parts: include name + uri/inline marker
37
+ return `[file:${p.name}${p.uri ? ` uri=${p.uri}` : ''}]`;
38
+ })
39
+ .join('\n');
40
+ }
41
+
42
+ function newId(prefix: string): string {
43
+ return `${prefix}_${crypto.randomBytes(8).toString('hex')}`;
44
+ }
45
+
46
+ export class ClaudeCodeA2AAdapter extends AbstractA2AAdapter {
47
+ readonly id = 'claude-code';
48
+
49
+ readonly agentCard: AgentCard = {
50
+ name: 'pixcode-claude-code',
51
+ description: 'Anthropic Claude Code, accessed via Pixcode',
52
+ url: '/a2a/agents/claude-code',
53
+ version: '1.0.0',
54
+ capabilities: ['streaming', 'fileEdit', 'commandExec', 'mcp'],
55
+ skills: [
56
+ {
57
+ id: 'architectural-review',
58
+ description: 'Review code architecture and propose structural changes',
59
+ },
60
+ {
61
+ id: 'typescript-edit',
62
+ description: 'Edit TypeScript files with type-aware reasoning',
63
+ },
64
+ {
65
+ id: 'multi-file-refactor',
66
+ description: 'Coordinated edits across many files',
67
+ },
68
+ {
69
+ id: 'test-run',
70
+ description: 'Run test suites and react to results',
71
+ },
72
+ ],
73
+ authentication: { type: 'bearer' },
74
+ };
75
+
76
+ private readonly active = new Map<string, { sessionId: string | null }>();
77
+
78
+ async submitTask(task: Task, ctx: AdapterContext): Promise<TaskHandle> {
79
+ // Foundation: only the last user message is fed in. Multi-turn resumption
80
+ // (input-required tasks, workflow chaining) needs to pass options.sessionId
81
+ // and append history; deferred to a follow-on plan.
82
+ const promptText = joinPartsToPrompt(
83
+ task.history[task.history.length - 1]?.parts ?? [],
84
+ );
85
+ const session = { sessionId: null as string | null };
86
+ this.active.set(task.id, session);
87
+
88
+ this.emitState(task.id, 'working');
89
+
90
+ const fakeWS: FakeWS = {
91
+ readyState: WS_OPEN,
92
+ send: (data) => this.handleSdkFrame(task.id, data, session),
93
+ };
94
+
95
+ const finished = (async () => {
96
+ try {
97
+ await queryClaudeSDK(
98
+ promptText,
99
+ {
100
+ cwd: ctx.cwd,
101
+ permissionMode: ctx.permissionMode ?? 'default',
102
+ toolsSettings: ctx.toolsSettings,
103
+ },
104
+ fakeWS,
105
+ );
106
+ // If cancelTask removed us from `active` first, suppress the spurious
107
+ // 'completed' that would otherwise race the 'canceled' state.
108
+ if (this.active.has(task.id)) {
109
+ this.emitState(task.id, 'completed');
110
+ }
111
+ } catch (err) {
112
+ if (this.active.has(task.id)) {
113
+ this.emitState(task.id, 'failed', {
114
+ code: 'ADAPTER_RUNTIME_ERROR',
115
+ message: err instanceof Error ? err.message : String(err),
116
+ });
117
+ }
118
+ } finally {
119
+ this.active.delete(task.id);
120
+ }
121
+ })();
122
+
123
+ return {
124
+ cancel: () => this.cancelTask(task.id),
125
+ finished,
126
+ };
127
+ }
128
+
129
+ async cancelTask(taskId: string): Promise<void> {
130
+ const session = this.active.get(taskId);
131
+ if (!session) {
132
+ this.emitState(taskId, 'canceled');
133
+ return;
134
+ }
135
+ // Delete BEFORE awaiting so submitTask's IIFE guard (this.active.has)
136
+ // suppresses the spurious 'completed' state when queryClaudeSDK's
137
+ // for-await loop unwinds from the abort.
138
+ this.active.delete(taskId);
139
+ if (session.sessionId) {
140
+ try {
141
+ await abortClaudeSDKSession(session.sessionId);
142
+ } catch {
143
+ // swallow — adapter has already cleaned its own state
144
+ }
145
+ }
146
+ this.emitState(taskId, 'canceled');
147
+ }
148
+
149
+ /**
150
+ * claude-sdk.js calls `ws.send(<NormalizedMessage>)` with a JS OBJECT
151
+ * (not a JSON string). We translate each frame into A2A bus events.
152
+ * See server/shared/types.ts for the MessageKind union.
153
+ */
154
+ private handleSdkFrame(
155
+ taskId: string,
156
+ frame: unknown,
157
+ session: { sessionId: string | null },
158
+ ): void {
159
+ if (!frame || typeof frame !== 'object') return;
160
+ const f = frame as {
161
+ kind?: string;
162
+ sessionId?: unknown;
163
+ newSessionId?: unknown;
164
+ text?: unknown;
165
+ content?: unknown;
166
+ toolName?: unknown;
167
+ toolInput?: unknown;
168
+ toolResult?: unknown;
169
+ };
170
+
171
+ // session_created carries the new session id in `newSessionId`. Capture
172
+ // it here so cancelTask can call abortClaudeSDKSession with the right id.
173
+ if (
174
+ f.kind === 'session_created' &&
175
+ typeof f.newSessionId === 'string' &&
176
+ !session.sessionId
177
+ ) {
178
+ session.sessionId = f.newSessionId;
179
+ }
180
+
181
+ switch (f.kind) {
182
+ case 'session_created':
183
+ case 'status':
184
+ case 'stream_delta':
185
+ case 'stream_end':
186
+ // session_created and status are not user-facing.
187
+ // stream_delta and stream_end CARRY user-visible delta text but are
188
+ // not currently emitted by claude-sdk.js (it doesn't pass
189
+ // includePartialMessages: true to query()). If that flag flips on
190
+ // upstream, these cases must be re-routed to emit text Messages.
191
+ return;
192
+
193
+ case 'text':
194
+ case 'thinking': {
195
+ const text =
196
+ typeof f.text === 'string'
197
+ ? f.text
198
+ : typeof f.content === 'string'
199
+ ? f.content
200
+ : null;
201
+ if (text) {
202
+ this.emitMessage(taskId, {
203
+ messageId: newId('msg'),
204
+ role: 'agent',
205
+ parts: [{ kind: 'text', text }],
206
+ taskId,
207
+ });
208
+ }
209
+ return;
210
+ }
211
+
212
+ case 'tool_use': {
213
+ this.emitArtifact(taskId, {
214
+ artifactId: newId('art'),
215
+ type: 'command-output',
216
+ parts: [
217
+ {
218
+ kind: 'data',
219
+ data: { toolName: f.toolName, toolInput: f.toolInput },
220
+ },
221
+ ],
222
+ metadata: { source: 'claude-tool-use' },
223
+ });
224
+ return;
225
+ }
226
+
227
+ case 'tool_result': {
228
+ this.emitArtifact(taskId, {
229
+ artifactId: newId('art'),
230
+ type: 'command-output',
231
+ parts: [{ kind: 'data', data: { toolResult: f.toolResult } }],
232
+ metadata: { source: 'claude-tool-result' },
233
+ });
234
+ return;
235
+ }
236
+
237
+ case 'permission_request':
238
+ case 'permission_cancelled':
239
+ case 'interactive_prompt':
240
+ case 'task_notification':
241
+ // Informational — surface as data artifact for visibility.
242
+ this.emitArtifact(taskId, {
243
+ artifactId: newId('art'),
244
+ type: 'data',
245
+ parts: [{ kind: 'data', data: f as Record<string, unknown> }],
246
+ metadata: { source: `claude-${f.kind}` },
247
+ });
248
+ return;
249
+
250
+ case 'error': {
251
+ // claude-sdk.js catches internally and emits an error frame without
252
+ // rethrowing, so the IIFE await would resolve cleanly. Force the
253
+ // failed state here and remove from active so the IIFE's
254
+ // 'completed' emit is suppressed by its active.has() guard.
255
+ const message =
256
+ typeof f.content === 'string'
257
+ ? f.content
258
+ : typeof f.text === 'string'
259
+ ? f.text
260
+ : 'Claude Code reported an error';
261
+ this.emitState(taskId, 'failed', {
262
+ code: 'CLAUDE_RUNTIME_ERROR',
263
+ message,
264
+ details: f as Record<string, unknown>,
265
+ });
266
+ this.active.delete(taskId);
267
+ return;
268
+ }
269
+
270
+ case 'complete':
271
+ // Lifecycle redundant with the IIFE's 'completed' emit; suppress to
272
+ // avoid double-signaling. The IIFE owns terminal state transitions.
273
+ return;
274
+
275
+ default:
276
+ // Unknown kind — surface for visibility
277
+ this.emitArtifact(taskId, {
278
+ artifactId: newId('art'),
279
+ type: 'data',
280
+ parts: [{ kind: 'data', data: f as Record<string, unknown> }],
281
+ });
282
+ }
283
+ }
284
+ }