@townco/ui 0.1.0 → 0.1.6

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 (125) hide show
  1. package/dist/core/hooks/use-chat-input.d.ts +17 -17
  2. package/dist/core/hooks/use-chat-input.js +64 -55
  3. package/dist/core/hooks/use-chat-messages.d.ts +11 -11
  4. package/dist/core/hooks/use-chat-messages.js +121 -114
  5. package/dist/core/hooks/use-chat-session.d.ts +5 -5
  6. package/dist/core/hooks/use-chat-session.js +78 -80
  7. package/dist/core/hooks/use-media-query.d.ts +5 -5
  8. package/dist/core/hooks/use-media-query.js +38 -38
  9. package/dist/core/index.d.ts +1 -1
  10. package/dist/core/index.js +1 -1
  11. package/dist/core/schemas/chat.d.ts +83 -56
  12. package/dist/core/schemas/chat.js +27 -25
  13. package/dist/core/store/chat-store.d.ts +28 -22
  14. package/dist/core/store/chat-store.js +59 -50
  15. package/dist/gui/components/Button.d.ts +23 -7
  16. package/dist/gui/components/Button.js +40 -27
  17. package/dist/gui/components/Card.d.ts +26 -7
  18. package/dist/gui/components/Card.js +54 -8
  19. package/dist/gui/components/ChatHeader.d.ts +58 -31
  20. package/dist/gui/components/ChatHeader.js +171 -66
  21. package/dist/gui/components/ChatInput.d.ts +58 -36
  22. package/dist/gui/components/ChatInput.js +191 -121
  23. package/dist/gui/components/ChatInterface.d.ts +9 -6
  24. package/dist/gui/components/ChatInterface.js +162 -90
  25. package/dist/gui/components/ChatLayout.d.ts +71 -41
  26. package/dist/gui/components/ChatLayout.js +214 -87
  27. package/dist/gui/components/ChatPanelTabContent.d.ts +18 -9
  28. package/dist/gui/components/ChatPanelTabContent.js +88 -10
  29. package/dist/gui/components/ChatPreview.d.ts +9 -6
  30. package/dist/gui/components/ChatPreview.js +212 -162
  31. package/dist/gui/components/ChatSecondaryPanel.d.ts +14 -11
  32. package/dist/gui/components/ChatSecondaryPanel.js +115 -38
  33. package/dist/gui/components/ChatSidebar.d.ts +26 -13
  34. package/dist/gui/components/ChatSidebar.js +48 -14
  35. package/dist/gui/components/ChatStatus.d.ts +4 -2
  36. package/dist/gui/components/ChatStatus.js +45 -34
  37. package/dist/gui/components/ChatView.d.ts +5 -3
  38. package/dist/gui/components/ChatView.js +38 -9
  39. package/dist/gui/components/ConfigPanel.d.ts +16 -12
  40. package/dist/gui/components/ConfigPanel.js +218 -41
  41. package/dist/gui/components/Conversation.d.ts +17 -14
  42. package/dist/gui/components/Conversation.js +143 -83
  43. package/dist/gui/components/Dialog.d.ts +57 -11
  44. package/dist/gui/components/Dialog.js +84 -8
  45. package/dist/gui/components/DropdownMenu.d.ts +101 -20
  46. package/dist/gui/components/DropdownMenu.js +161 -14
  47. package/dist/gui/components/HeightTransition.d.ts +12 -7
  48. package/dist/gui/components/HeightTransition.js +88 -77
  49. package/dist/gui/components/Input.d.ts +13 -6
  50. package/dist/gui/components/Input.js +27 -16
  51. package/dist/gui/components/InputBox.d.ts +19 -12
  52. package/dist/gui/components/InputBox.js +86 -14
  53. package/dist/gui/components/Label.d.ts +7 -1
  54. package/dist/gui/components/Label.js +12 -2
  55. package/dist/gui/components/MarkdownRenderer.d.ts +6 -4
  56. package/dist/gui/components/MarkdownRenderer.js +178 -81
  57. package/dist/gui/components/Message.d.ts +25 -18
  58. package/dist/gui/components/Message.js +44 -23
  59. package/dist/gui/components/MessageContent.d.ts +29 -22
  60. package/dist/gui/components/MessageContent.js +157 -85
  61. package/dist/gui/components/PlaygroundLayout.d.ts +9 -5
  62. package/dist/gui/components/PlaygroundLayout.js +43 -12
  63. package/dist/gui/components/Reasoning.d.ts +30 -24
  64. package/dist/gui/components/Reasoning.js +187 -60
  65. package/dist/gui/components/Response.d.ts +11 -9
  66. package/dist/gui/components/Response.js +229 -90
  67. package/dist/gui/components/Select.d.ts +69 -10
  68. package/dist/gui/components/Select.js +118 -12
  69. package/dist/gui/components/Sonner.d.ts +3 -1
  70. package/dist/gui/components/Sonner.js +29 -18
  71. package/dist/gui/components/StatusBar.d.ts +9 -5
  72. package/dist/gui/components/StatusBar.js +56 -9
  73. package/dist/gui/components/Tabs.d.ts +24 -4
  74. package/dist/gui/components/Tabs.js +32 -4
  75. package/dist/gui/components/Task.d.ts +28 -24
  76. package/dist/gui/components/Task.js +164 -31
  77. package/dist/gui/components/Textarea.d.ts +15 -7
  78. package/dist/gui/components/Textarea.js +63 -46
  79. package/dist/gui/components/ThinkingBlock.d.ts +20 -10
  80. package/dist/gui/components/ThinkingBlock.js +134 -35
  81. package/dist/gui/components/TodoList.d.ts +12 -10
  82. package/dist/gui/components/TodoList.js +22 -7
  83. package/dist/gui/components/TodoListItem.d.ts +9 -6
  84. package/dist/gui/components/TodoListItem.js +18 -4
  85. package/dist/gui/components/index.d.ts +59 -8
  86. package/dist/gui/components/index.js +42 -8
  87. package/dist/gui/lib/utils.js +1 -1
  88. package/dist/index.d.ts +1 -1
  89. package/dist/index.js +1 -1
  90. package/dist/index.test.js +0 -1
  91. package/dist/sdk/client/acp-client.d.ts +88 -76
  92. package/dist/sdk/client/acp-client.js +215 -217
  93. package/dist/sdk/index.d.ts +1 -1
  94. package/dist/sdk/index.js +1 -1
  95. package/dist/sdk/schemas/agent.d.ts +111 -64
  96. package/dist/sdk/schemas/agent.js +24 -24
  97. package/dist/sdk/schemas/message.d.ts +245 -147
  98. package/dist/sdk/schemas/message.js +40 -40
  99. package/dist/sdk/schemas/session.d.ts +219 -135
  100. package/dist/sdk/schemas/session.js +27 -27
  101. package/dist/sdk/transports/http.d.ts +55 -55
  102. package/dist/sdk/transports/http.js +472 -469
  103. package/dist/sdk/transports/stdio.d.ts +20 -20
  104. package/dist/sdk/transports/stdio.js +289 -286
  105. package/dist/sdk/transports/types.d.ts +42 -42
  106. package/dist/sdk/transports/websocket.d.ts +12 -12
  107. package/dist/sdk/transports/websocket.js +52 -46
  108. package/dist/tui/components/ChatView.d.ts +4 -2
  109. package/dist/tui/components/ChatView.js +51 -18
  110. package/dist/tui/components/GameOfLife.js +64 -35
  111. package/dist/tui/components/InputBox.d.ts +18 -11
  112. package/dist/tui/components/InputBox.js +70 -10
  113. package/dist/tui/components/MessageList.d.ts +4 -2
  114. package/dist/tui/components/MessageList.js +37 -10
  115. package/dist/tui/components/MultiSelect.d.ts +15 -9
  116. package/dist/tui/components/MultiSelect.js +116 -69
  117. package/dist/tui/components/ReadlineInput.d.ts +12 -6
  118. package/dist/tui/components/ReadlineInput.js +252 -237
  119. package/dist/tui/components/SingleSelect.d.ts +15 -9
  120. package/dist/tui/components/SingleSelect.js +84 -43
  121. package/dist/tui/components/StatusBar.d.ts +11 -6
  122. package/dist/tui/components/StatusBar.js +102 -67
  123. package/dist/tui/index.d.ts +1 -1
  124. package/dist/tui/index.js +1 -1
  125. package/package.json +2 -3
@@ -5,24 +5,24 @@ import type { StdioTransportOptions, Transport } from "./types.js";
5
5
  * Uses JSON-RPC 2.0 over stdio to communicate with agents
6
6
  */
7
7
  export declare class StdioTransport implements Transport {
8
- private options;
9
- private agentProcess;
10
- private connection;
11
- private connected;
12
- private sessionUpdateCallbacks;
13
- private errorCallbacks;
14
- private messageQueue;
15
- private currentSessionId;
16
- private chunkResolvers;
17
- private streamComplete;
18
- constructor(options: StdioTransportOptions);
19
- connect(): Promise<void>;
20
- disconnect(): Promise<void>;
21
- send(message: Message): Promise<void>;
22
- receive(): AsyncIterableIterator<MessageChunk>;
23
- isConnected(): boolean;
24
- onSessionUpdate(callback: (update: SessionUpdate) => void): () => void;
25
- onError(callback: (error: Error) => void): () => void;
26
- private notifySessionUpdate;
27
- private notifyError;
8
+ private options;
9
+ private agentProcess;
10
+ private connection;
11
+ private connected;
12
+ private sessionUpdateCallbacks;
13
+ private errorCallbacks;
14
+ private messageQueue;
15
+ private currentSessionId;
16
+ private chunkResolvers;
17
+ private streamComplete;
18
+ constructor(options: StdioTransportOptions);
19
+ connect(): Promise<void>;
20
+ disconnect(): Promise<void>;
21
+ send(message: Message): Promise<void>;
22
+ receive(): AsyncIterableIterator<MessageChunk>;
23
+ isConnected(): boolean;
24
+ onSessionUpdate(callback: (update: SessionUpdate) => void): () => void;
25
+ onError(callback: (error: Error) => void): () => void;
26
+ private notifySessionUpdate;
27
+ private notifyError;
28
28
  }
@@ -1,294 +1,297 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { Readable, Writable } from "node:stream";
3
- import { ClientSideConnection, ndJsonStream, PROTOCOL_VERSION, } from "@agentclientprotocol/sdk";
3
+ import {
4
+ ClientSideConnection,
5
+ ndJsonStream,
6
+ PROTOCOL_VERSION,
7
+ } from "@agentclientprotocol/sdk";
4
8
  /**
5
9
  * Stdio transport implementation using Agent Client Protocol SDK
6
10
  * Uses JSON-RPC 2.0 over stdio to communicate with agents
7
11
  */
8
12
  export class StdioTransport {
9
- options;
10
- agentProcess = null;
11
- connection = null;
12
- connected = false;
13
- sessionUpdateCallbacks = new Set();
14
- errorCallbacks = new Set();
15
- messageQueue = [];
16
- currentSessionId = null;
17
- chunkResolvers = [];
18
- streamComplete = false;
19
- constructor(options) {
20
- this.options = options;
21
- }
22
- async connect() {
23
- if (this.connected) {
24
- return;
25
- }
26
- try {
27
- // Spawn the agent process
28
- this.agentProcess = spawn(this.options.agentPath, this.options.agentArgs || [], {
29
- cwd: this.options.workingDirectory || process.cwd(),
30
- env: { ...process.env, ...this.options.environment },
31
- stdio: ["pipe", "pipe", "pipe"],
32
- });
33
- if (!this.agentProcess.stdin ||
34
- !this.agentProcess.stdout ||
35
- !this.agentProcess.stderr) {
36
- throw new Error("Failed to create stdio pipes for agent process");
37
- }
38
- // Convert Node.js streams to Web streams
39
- const outputStream = Writable.toWeb(this.agentProcess.stdin);
40
- const inputStream = Readable.toWeb(this.agentProcess.stdout);
41
- // Create the bidirectional stream using ndJsonStream
42
- const stream = ndJsonStream(outputStream, inputStream);
43
- // Create ACP client implementation factory
44
- const self = this;
45
- const clientFactory = (_agent) => {
46
- return {
47
- async requestPermission(params) {
48
- // For now, auto-approve all permissions
49
- // In a real implementation, this should prompt the user
50
- console.log(`Permission requested:`, params);
51
- // @ts-expect-error - Type mismatch with ACP SDK v0.5.1
52
- return { outcome: "approved" };
53
- },
54
- async sessionUpdate(params) {
55
- // Handle session updates from the agent
56
- const paramsExtended = params;
57
- const update = paramsExtended.update;
58
- const sessionUpdate = {
59
- sessionId: self.currentSessionId || params.sessionId,
60
- status: "active",
61
- };
62
- // Queue message chunks if present - content is in update.content
63
- if (update?.content) {
64
- const content = update.content;
65
- let chunk = null;
66
- if (content.type === "text" && content.text) {
67
- chunk = {
68
- id: params.sessionId,
69
- role: "assistant",
70
- contentDelta: { type: "text", text: content.text },
71
- isComplete: false,
72
- };
73
- }
74
- else if (content.type === "tool_call") {
75
- chunk = {
76
- id: params.sessionId,
77
- role: "assistant",
78
- contentDelta: content,
79
- isComplete: false,
80
- };
81
- }
82
- if (chunk) {
83
- // Resolve any waiting receive() calls immediately
84
- const resolver = self.chunkResolvers.shift();
85
- if (resolver) {
86
- resolver(chunk);
87
- }
88
- else {
89
- // Only queue if no resolver is waiting
90
- self.messageQueue.push(chunk);
91
- }
92
- }
93
- }
94
- self.notifySessionUpdate(sessionUpdate);
95
- },
96
- async writeTextFile(params) {
97
- const fs = await import("node:fs/promises");
98
- await fs.writeFile(params.path, params.content, "utf-8");
99
- return {};
100
- },
101
- async readTextFile(params) {
102
- const fs = await import("node:fs/promises");
103
- const content = await fs.readFile(params.path, "utf-8");
104
- return { content };
105
- },
106
- };
107
- };
108
- // Create the client-side connection
109
- this.connection = new ClientSideConnection(clientFactory, stream);
110
- // Handle stderr for debugging
111
- this.agentProcess.stderr.on("data", (data) => {
112
- console.error(`Agent stderr: ${data}`);
113
- });
114
- // Handle process exit
115
- this.agentProcess.on("exit", (code, signal) => {
116
- this.connected = false;
117
- const error = new Error(`Agent process exited with code ${code} and signal ${signal}`);
118
- this.notifyError(error);
119
- });
120
- // Listen for connection closure
121
- this.connection.closed.then(() => {
122
- this.connected = false;
123
- });
124
- // Initialize the connection
125
- const initResponse = await this.connection.initialize({
126
- protocolVersion: PROTOCOL_VERSION,
127
- clientCapabilities: {
128
- fs: {
129
- readTextFile: true,
130
- writeTextFile: true,
131
- },
132
- },
133
- });
134
- console.log("ACP connection initialized:", initResponse);
135
- this.connected = true;
136
- }
137
- catch (error) {
138
- this.connected = false;
139
- const err = error instanceof Error ? error : new Error(String(error));
140
- this.notifyError(err);
141
- throw err;
142
- }
143
- }
144
- async disconnect() {
145
- if (!this.connected) {
146
- return;
147
- }
148
- try {
149
- // Terminate the agent process (this will close the connection)
150
- if (this.agentProcess) {
151
- this.agentProcess.kill();
152
- this.agentProcess = null;
153
- }
154
- // Wait for connection to close
155
- if (this.connection) {
156
- await this.connection.closed;
157
- }
158
- this.connection = null;
159
- this.connected = false;
160
- this.currentSessionId = null;
161
- }
162
- catch (error) {
163
- const err = error instanceof Error ? error : new Error(String(error));
164
- this.notifyError(err);
165
- throw err;
166
- }
167
- }
168
- async send(message) {
169
- if (!this.connected || !this.connection) {
170
- throw new Error("Transport not connected");
171
- }
172
- try {
173
- // Reset stream state for new message
174
- this.streamComplete = false;
175
- this.messageQueue = [];
176
- // If no session, create one first
177
- if (!this.currentSessionId) {
178
- const sessionResponse = await this.connection.newSession({
179
- cwd: this.options.workingDirectory || process.cwd(),
180
- mcpServers: [],
181
- });
182
- this.currentSessionId = sessionResponse.sessionId;
183
- }
184
- // Convert our message format to ACP prompt format
185
- const textContent = message.content
186
- .filter((c) => c.type === "text")
187
- .map((c) => c.text)
188
- .join("\n");
189
- // Send the prompt - ACP expects "prompt" field with ContentBlock[]
190
- const promptResponse = await this.connection.prompt({
191
- sessionId: this.currentSessionId,
192
- prompt: [
193
- {
194
- type: "text",
195
- text: textContent,
196
- },
197
- ],
198
- });
199
- console.log("Prompt response:", promptResponse);
200
- // Mark stream as complete after prompt finishes
201
- this.streamComplete = true;
202
- // Resolve any waiting receive() calls with completion
203
- const resolver = this.chunkResolvers.shift();
204
- if (resolver) {
205
- resolver({
206
- id: this.currentSessionId || "unknown",
207
- role: "assistant",
208
- contentDelta: { type: "text", text: "" },
209
- isComplete: true,
210
- });
211
- }
212
- }
213
- catch (error) {
214
- this.streamComplete = true;
215
- const err = error instanceof Error ? error : new Error(String(error));
216
- this.notifyError(err);
217
- throw err;
218
- }
219
- }
220
- async *receive() {
221
- // Keep yielding chunks until stream is complete
222
- while (!this.streamComplete) {
223
- // Check if there are queued messages
224
- if (this.messageQueue.length > 0) {
225
- const chunk = this.messageQueue.shift();
226
- if (chunk) {
227
- yield chunk;
228
- }
229
- }
230
- else {
231
- // Wait for next chunk to arrive
232
- const chunk = await new Promise((resolve) => {
233
- this.chunkResolvers.push(resolve);
234
- });
235
- if (chunk.isComplete) {
236
- yield chunk;
237
- return;
238
- }
239
- else {
240
- yield chunk;
241
- }
242
- }
243
- }
244
- // Yield any remaining queued messages
245
- while (this.messageQueue.length > 0) {
246
- const chunk = this.messageQueue.shift();
247
- if (chunk) {
248
- yield chunk;
249
- }
250
- }
251
- // Mark the stream as complete
252
- yield {
253
- id: this.currentSessionId || "unknown",
254
- role: "assistant",
255
- contentDelta: { type: "text", text: "" },
256
- isComplete: true,
257
- };
258
- }
259
- isConnected() {
260
- return this.connected;
261
- }
262
- onSessionUpdate(callback) {
263
- this.sessionUpdateCallbacks.add(callback);
264
- return () => {
265
- this.sessionUpdateCallbacks.delete(callback);
266
- };
267
- }
268
- onError(callback) {
269
- this.errorCallbacks.add(callback);
270
- return () => {
271
- this.errorCallbacks.delete(callback);
272
- };
273
- }
274
- notifySessionUpdate(update) {
275
- for (const callback of this.sessionUpdateCallbacks) {
276
- try {
277
- callback(update);
278
- }
279
- catch (error) {
280
- console.error("Error in session update callback:", error);
281
- }
282
- }
283
- }
284
- notifyError(error) {
285
- for (const callback of this.errorCallbacks) {
286
- try {
287
- callback(error);
288
- }
289
- catch (err) {
290
- console.error("Error in error callback:", err);
291
- }
292
- }
293
- }
13
+ options;
14
+ agentProcess = null;
15
+ connection = null;
16
+ connected = false;
17
+ sessionUpdateCallbacks = new Set();
18
+ errorCallbacks = new Set();
19
+ messageQueue = [];
20
+ currentSessionId = null;
21
+ chunkResolvers = [];
22
+ streamComplete = false;
23
+ constructor(options) {
24
+ this.options = options;
25
+ }
26
+ async connect() {
27
+ if (this.connected) {
28
+ return;
29
+ }
30
+ try {
31
+ // Spawn the agent process
32
+ this.agentProcess = spawn(
33
+ this.options.agentPath,
34
+ this.options.agentArgs || [],
35
+ {
36
+ cwd: this.options.workingDirectory || process.cwd(),
37
+ env: { ...process.env, ...this.options.environment },
38
+ stdio: ["pipe", "pipe", "pipe"],
39
+ },
40
+ );
41
+ if (
42
+ !this.agentProcess.stdin ||
43
+ !this.agentProcess.stdout ||
44
+ !this.agentProcess.stderr
45
+ ) {
46
+ throw new Error("Failed to create stdio pipes for agent process");
47
+ }
48
+ // Convert Node.js streams to Web streams
49
+ const outputStream = Writable.toWeb(this.agentProcess.stdin);
50
+ const inputStream = Readable.toWeb(this.agentProcess.stdout);
51
+ // Create the bidirectional stream using ndJsonStream
52
+ const stream = ndJsonStream(outputStream, inputStream);
53
+ // Create ACP client implementation factory
54
+ const self = this;
55
+ const clientFactory = (_agent) => {
56
+ return {
57
+ async requestPermission(params) {
58
+ // For now, auto-approve all permissions
59
+ // In a real implementation, this should prompt the user
60
+ console.log(`Permission requested:`, params);
61
+ // @ts-expect-error - Type mismatch with ACP SDK v0.5.1
62
+ return { outcome: "approved" };
63
+ },
64
+ async sessionUpdate(params) {
65
+ // Handle session updates from the agent
66
+ const paramsExtended = params;
67
+ const update = paramsExtended.update;
68
+ const sessionUpdate = {
69
+ sessionId: self.currentSessionId || params.sessionId,
70
+ status: "active",
71
+ };
72
+ // Queue message chunks if present - content is in update.content
73
+ if (update?.content) {
74
+ const content = update.content;
75
+ let chunk = null;
76
+ if (content.type === "text" && content.text) {
77
+ chunk = {
78
+ id: params.sessionId,
79
+ role: "assistant",
80
+ contentDelta: { type: "text", text: content.text },
81
+ isComplete: false,
82
+ };
83
+ } else if (content.type === "tool_call") {
84
+ chunk = {
85
+ id: params.sessionId,
86
+ role: "assistant",
87
+ contentDelta: content,
88
+ isComplete: false,
89
+ };
90
+ }
91
+ if (chunk) {
92
+ // Resolve any waiting receive() calls immediately
93
+ const resolver = self.chunkResolvers.shift();
94
+ if (resolver) {
95
+ resolver(chunk);
96
+ } else {
97
+ // Only queue if no resolver is waiting
98
+ self.messageQueue.push(chunk);
99
+ }
100
+ }
101
+ }
102
+ self.notifySessionUpdate(sessionUpdate);
103
+ },
104
+ async writeTextFile(params) {
105
+ const fs = await import("node:fs/promises");
106
+ await fs.writeFile(params.path, params.content, "utf-8");
107
+ return {};
108
+ },
109
+ async readTextFile(params) {
110
+ const fs = await import("node:fs/promises");
111
+ const content = await fs.readFile(params.path, "utf-8");
112
+ return { content };
113
+ },
114
+ };
115
+ };
116
+ // Create the client-side connection
117
+ this.connection = new ClientSideConnection(clientFactory, stream);
118
+ // Handle stderr for debugging
119
+ this.agentProcess.stderr.on("data", (data) => {
120
+ console.error(`Agent stderr: ${data}`);
121
+ });
122
+ // Handle process exit
123
+ this.agentProcess.on("exit", (code, signal) => {
124
+ this.connected = false;
125
+ const error = new Error(
126
+ `Agent process exited with code ${code} and signal ${signal}`,
127
+ );
128
+ this.notifyError(error);
129
+ });
130
+ // Listen for connection closure
131
+ this.connection.closed.then(() => {
132
+ this.connected = false;
133
+ });
134
+ // Initialize the connection
135
+ const initResponse = await this.connection.initialize({
136
+ protocolVersion: PROTOCOL_VERSION,
137
+ clientCapabilities: {
138
+ fs: {
139
+ readTextFile: true,
140
+ writeTextFile: true,
141
+ },
142
+ },
143
+ });
144
+ console.log("ACP connection initialized:", initResponse);
145
+ this.connected = true;
146
+ } catch (error) {
147
+ this.connected = false;
148
+ const err = error instanceof Error ? error : new Error(String(error));
149
+ this.notifyError(err);
150
+ throw err;
151
+ }
152
+ }
153
+ async disconnect() {
154
+ if (!this.connected) {
155
+ return;
156
+ }
157
+ try {
158
+ // Terminate the agent process (this will close the connection)
159
+ if (this.agentProcess) {
160
+ this.agentProcess.kill();
161
+ this.agentProcess = null;
162
+ }
163
+ // Wait for connection to close
164
+ if (this.connection) {
165
+ await this.connection.closed;
166
+ }
167
+ this.connection = null;
168
+ this.connected = false;
169
+ this.currentSessionId = null;
170
+ } catch (error) {
171
+ const err = error instanceof Error ? error : new Error(String(error));
172
+ this.notifyError(err);
173
+ throw err;
174
+ }
175
+ }
176
+ async send(message) {
177
+ if (!this.connected || !this.connection) {
178
+ throw new Error("Transport not connected");
179
+ }
180
+ try {
181
+ // Reset stream state for new message
182
+ this.streamComplete = false;
183
+ this.messageQueue = [];
184
+ // If no session, create one first
185
+ if (!this.currentSessionId) {
186
+ const sessionResponse = await this.connection.newSession({
187
+ cwd: this.options.workingDirectory || process.cwd(),
188
+ mcpServers: [],
189
+ });
190
+ this.currentSessionId = sessionResponse.sessionId;
191
+ }
192
+ // Convert our message format to ACP prompt format
193
+ const textContent = message.content
194
+ .filter((c) => c.type === "text")
195
+ .map((c) => c.text)
196
+ .join("\n");
197
+ // Send the prompt - ACP expects "prompt" field with ContentBlock[]
198
+ const promptResponse = await this.connection.prompt({
199
+ sessionId: this.currentSessionId,
200
+ prompt: [
201
+ {
202
+ type: "text",
203
+ text: textContent,
204
+ },
205
+ ],
206
+ });
207
+ console.log("Prompt response:", promptResponse);
208
+ // Mark stream as complete after prompt finishes
209
+ this.streamComplete = true;
210
+ // Resolve any waiting receive() calls with completion
211
+ const resolver = this.chunkResolvers.shift();
212
+ if (resolver) {
213
+ resolver({
214
+ id: this.currentSessionId || "unknown",
215
+ role: "assistant",
216
+ contentDelta: { type: "text", text: "" },
217
+ isComplete: true,
218
+ });
219
+ }
220
+ } catch (error) {
221
+ this.streamComplete = true;
222
+ const err = error instanceof Error ? error : new Error(String(error));
223
+ this.notifyError(err);
224
+ throw err;
225
+ }
226
+ }
227
+ async *receive() {
228
+ // Keep yielding chunks until stream is complete
229
+ while (!this.streamComplete) {
230
+ // Check if there are queued messages
231
+ if (this.messageQueue.length > 0) {
232
+ const chunk = this.messageQueue.shift();
233
+ if (chunk) {
234
+ yield chunk;
235
+ }
236
+ } else {
237
+ // Wait for next chunk to arrive
238
+ const chunk = await new Promise((resolve) => {
239
+ this.chunkResolvers.push(resolve);
240
+ });
241
+ if (chunk.isComplete) {
242
+ yield chunk;
243
+ return;
244
+ } else {
245
+ yield chunk;
246
+ }
247
+ }
248
+ }
249
+ // Yield any remaining queued messages
250
+ while (this.messageQueue.length > 0) {
251
+ const chunk = this.messageQueue.shift();
252
+ if (chunk) {
253
+ yield chunk;
254
+ }
255
+ }
256
+ // Mark the stream as complete
257
+ yield {
258
+ id: this.currentSessionId || "unknown",
259
+ role: "assistant",
260
+ contentDelta: { type: "text", text: "" },
261
+ isComplete: true,
262
+ };
263
+ }
264
+ isConnected() {
265
+ return this.connected;
266
+ }
267
+ onSessionUpdate(callback) {
268
+ this.sessionUpdateCallbacks.add(callback);
269
+ return () => {
270
+ this.sessionUpdateCallbacks.delete(callback);
271
+ };
272
+ }
273
+ onError(callback) {
274
+ this.errorCallbacks.add(callback);
275
+ return () => {
276
+ this.errorCallbacks.delete(callback);
277
+ };
278
+ }
279
+ notifySessionUpdate(update) {
280
+ for (const callback of this.sessionUpdateCallbacks) {
281
+ try {
282
+ callback(update);
283
+ } catch (error) {
284
+ console.error("Error in session update callback:", error);
285
+ }
286
+ }
287
+ }
288
+ notifyError(error) {
289
+ for (const callback of this.errorCallbacks) {
290
+ try {
291
+ callback(error);
292
+ } catch (err) {
293
+ console.error("Error in error callback:", err);
294
+ }
295
+ }
296
+ }
294
297
  }