icopilot 2.2.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 (203) hide show
  1. package/CHANGELOG.md +250 -0
  2. package/LICENSE +21 -0
  3. package/README.md +214 -0
  4. package/bin/icopilot.js +6 -0
  5. package/dist/acp/router.js +123 -0
  6. package/dist/acp/schema.js +53 -0
  7. package/dist/agents/aggregator.js +187 -0
  8. package/dist/agents/custom-agents.js +97 -0
  9. package/dist/agents/goal-driven.js +411 -0
  10. package/dist/agents/multi-repo.js +350 -0
  11. package/dist/agents/parallel-runner.js +181 -0
  12. package/dist/agents/router.js +144 -0
  13. package/dist/agents/self-heal.js +481 -0
  14. package/dist/agents/tdd-agent.js +278 -0
  15. package/dist/api/github-models.js +158 -0
  16. package/dist/bridge/ide-bridge.js +479 -0
  17. package/dist/cloud/routine-executor.js +34 -0
  18. package/dist/cloud/routine-scheduler.js +67 -0
  19. package/dist/cloud/routine-storage.js +297 -0
  20. package/dist/commands/acp-cmd.js +143 -0
  21. package/dist/commands/actions-cmd.js +624 -0
  22. package/dist/commands/agent-cmd.js +144 -0
  23. package/dist/commands/alias-cmd.js +132 -0
  24. package/dist/commands/bookmark-cmd.js +77 -0
  25. package/dist/commands/changelog-cmd.js +99 -0
  26. package/dist/commands/changes-cmd.js +120 -0
  27. package/dist/commands/clipboard-cmd.js +217 -0
  28. package/dist/commands/cloud-routine-cmd.js +265 -0
  29. package/dist/commands/codegen-cmd.js +544 -0
  30. package/dist/commands/compare-cmd.js +116 -0
  31. package/dist/commands/context-cmd.js +247 -0
  32. package/dist/commands/context-viz-cmd.js +43 -0
  33. package/dist/commands/conventions-cmd.js +116 -0
  34. package/dist/commands/cost-cmd.js +51 -0
  35. package/dist/commands/deps-cmd.js +294 -0
  36. package/dist/commands/diagram-cmd.js +658 -0
  37. package/dist/commands/diff-review-cmd.js +92 -0
  38. package/dist/commands/doc-cmd.js +412 -0
  39. package/dist/commands/doctor-cmd.js +152 -0
  40. package/dist/commands/editor-cmd.js +49 -0
  41. package/dist/commands/env-cmd.js +86 -0
  42. package/dist/commands/explain-cmd.js +78 -0
  43. package/dist/commands/explain-shell-cmd.js +22 -0
  44. package/dist/commands/explore-cmd.js +231 -0
  45. package/dist/commands/feedback-cmd.js +98 -0
  46. package/dist/commands/fix-cmd.js +17 -0
  47. package/dist/commands/generate-cmd.js +38 -0
  48. package/dist/commands/git-extra.js +197 -0
  49. package/dist/commands/git-log-cmd.js +98 -0
  50. package/dist/commands/git-undo-cmd.js +137 -0
  51. package/dist/commands/git.js +155 -0
  52. package/dist/commands/history-cmd.js +122 -0
  53. package/dist/commands/index-cmd.js +65 -0
  54. package/dist/commands/init-cmd.js +73 -0
  55. package/dist/commands/lint-cmd.js +133 -0
  56. package/dist/commands/memory-cmd.js +98 -0
  57. package/dist/commands/metrics-cmd.js +97 -0
  58. package/dist/commands/mode-prefix.js +30 -0
  59. package/dist/commands/multi-cmd.js +44 -0
  60. package/dist/commands/notify-cmd.js +204 -0
  61. package/dist/commands/profile-cmd.js +101 -0
  62. package/dist/commands/prompts.js +17 -0
  63. package/dist/commands/rag-cmd.js +60 -0
  64. package/dist/commands/readme-cmd.js +564 -0
  65. package/dist/commands/reasoning-cmd.js +34 -0
  66. package/dist/commands/refactor-cmd.js +96 -0
  67. package/dist/commands/release-cmd.js +450 -0
  68. package/dist/commands/repo-cmd.js +195 -0
  69. package/dist/commands/route-cmd.js +21 -0
  70. package/dist/commands/schedule-cmd.js +109 -0
  71. package/dist/commands/search-cmd.js +47 -0
  72. package/dist/commands/security-cmd.js +156 -0
  73. package/dist/commands/settings-cmd.js +238 -0
  74. package/dist/commands/skill-cmd.js +338 -0
  75. package/dist/commands/slash.js +2721 -0
  76. package/dist/commands/snippets-cmd.js +83 -0
  77. package/dist/commands/space-cmd.js +92 -0
  78. package/dist/commands/stash-cmd.js +156 -0
  79. package/dist/commands/stats-cmd.js +36 -0
  80. package/dist/commands/style-cmd.js +85 -0
  81. package/dist/commands/suggest-cmd.js +40 -0
  82. package/dist/commands/summary-cmd.js +138 -0
  83. package/dist/commands/task-cmd.js +58 -0
  84. package/dist/commands/team-memory-cmd.js +97 -0
  85. package/dist/commands/template-cmd.js +475 -0
  86. package/dist/commands/test-cmd.js +146 -0
  87. package/dist/commands/todo-cmd.js +172 -0
  88. package/dist/commands/tokens-cmd.js +277 -0
  89. package/dist/commands/trigger-cmd.js +147 -0
  90. package/dist/commands/undo-cmd.js +18 -0
  91. package/dist/commands/voice-cmd.js +89 -0
  92. package/dist/commands/watch-cmd.js +110 -0
  93. package/dist/commands/web-cmd.js +183 -0
  94. package/dist/commands/worktree-cmd.js +119 -0
  95. package/dist/config-profile.js +66 -0
  96. package/dist/config.js +288 -0
  97. package/dist/context/compactor.js +53 -0
  98. package/dist/context/dep-context.js +329 -0
  99. package/dist/context/file-refs.js +54 -0
  100. package/dist/context/git-context.js +229 -0
  101. package/dist/context/image-input.js +66 -0
  102. package/dist/context/memory.js +55 -0
  103. package/dist/context/persistent-memory.js +104 -0
  104. package/dist/context/pinned.js +96 -0
  105. package/dist/context/priority.js +150 -0
  106. package/dist/context/read-only.js +48 -0
  107. package/dist/context/smart-files.js +286 -0
  108. package/dist/context/team-memory.js +156 -0
  109. package/dist/extensions/loader.js +149 -0
  110. package/dist/extensions/marketplace.js +49 -0
  111. package/dist/extensions/slack-provider.js +181 -0
  112. package/dist/extensions/team.js +56 -0
  113. package/dist/extensions/teams-provider.js +222 -0
  114. package/dist/extensions/voice.js +18 -0
  115. package/dist/hooks/lifecycle.js +215 -0
  116. package/dist/hooks/precommit.js +463 -0
  117. package/dist/index/embeddings.js +23 -0
  118. package/dist/index/indexer.js +86 -0
  119. package/dist/index/retrieve.js +20 -0
  120. package/dist/index/store.js +95 -0
  121. package/dist/index.js +286 -0
  122. package/dist/intelligence/dead-code.js +457 -0
  123. package/dist/intelligence/error-watch.js +263 -0
  124. package/dist/intelligence/navigation.js +141 -0
  125. package/dist/intelligence/stack-trace.js +210 -0
  126. package/dist/intelligence/symbol-index.js +410 -0
  127. package/dist/knowledge/auto-memory.js +412 -0
  128. package/dist/knowledge/conventions.js +475 -0
  129. package/dist/knowledge/corrections.js +213 -0
  130. package/dist/knowledge/rag.js +450 -0
  131. package/dist/knowledge/style-learner.js +324 -0
  132. package/dist/logger.js +35 -0
  133. package/dist/mcp/client.js +144 -0
  134. package/dist/mcp/config.js +24 -0
  135. package/dist/mcp/index.js +89 -0
  136. package/dist/modes/auto-compact.js +20 -0
  137. package/dist/modes/autopilot.js +157 -0
  138. package/dist/modes/background.js +82 -0
  139. package/dist/modes/interactive.js +187 -0
  140. package/dist/modes/oneshot.js +36 -0
  141. package/dist/modes/tui.js +265 -0
  142. package/dist/modes/turn.js +342 -0
  143. package/dist/notifications/manager.js +107 -0
  144. package/dist/plugins/marketplace.js +244 -0
  145. package/dist/providers/custom-provider.js +298 -0
  146. package/dist/providers/local-model.js +121 -0
  147. package/dist/routing/profiles.js +44 -0
  148. package/dist/routing/router.js +18 -0
  149. package/dist/sandbox/container.js +151 -0
  150. package/dist/security/audit.js +237 -0
  151. package/dist/security/content-filter.js +449 -0
  152. package/dist/security/proxy.js +301 -0
  153. package/dist/security/retention.js +281 -0
  154. package/dist/security/roles.js +252 -0
  155. package/dist/server/api-server.js +679 -0
  156. package/dist/session/bookmarks.js +72 -0
  157. package/dist/session/cloud-session.js +291 -0
  158. package/dist/session/handoff.js +405 -0
  159. package/dist/session/manager.js +35 -0
  160. package/dist/session/session.js +296 -0
  161. package/dist/session/share.js +313 -0
  162. package/dist/session/undo-journal.js +91 -0
  163. package/dist/snippets/store.js +60 -0
  164. package/dist/spaces/space-config.js +156 -0
  165. package/dist/spaces/space.js +220 -0
  166. package/dist/stats/store.js +101 -0
  167. package/dist/tools/apply-patch.js +134 -0
  168. package/dist/tools/auto-check.js +218 -0
  169. package/dist/tools/diff-edit.js +150 -0
  170. package/dist/tools/diff-prompt.js +36 -0
  171. package/dist/tools/edit-file.js +66 -0
  172. package/dist/tools/file-ops.js +205 -0
  173. package/dist/tools/glob.js +17 -0
  174. package/dist/tools/grep.js +56 -0
  175. package/dist/tools/image.js +194 -0
  176. package/dist/tools/list-directory.js +228 -0
  177. package/dist/tools/memory.js +17 -0
  178. package/dist/tools/multi-edit.js +299 -0
  179. package/dist/tools/policy.js +95 -0
  180. package/dist/tools/registry.js +484 -0
  181. package/dist/tools/retry.js +74 -0
  182. package/dist/tools/run-in-terminal.js +162 -0
  183. package/dist/tools/safety.js +64 -0
  184. package/dist/tools/sandbox.js +15 -0
  185. package/dist/tools/search-symbols.js +212 -0
  186. package/dist/tools/shell.js +118 -0
  187. package/dist/tools/web.js +167 -0
  188. package/dist/ui/prompt.js +37 -0
  189. package/dist/ui/render.js +96 -0
  190. package/dist/ui/screen.js +13 -0
  191. package/dist/ui/theme.js +56 -0
  192. package/dist/util/browser.js +34 -0
  193. package/dist/util/completion.js +350 -0
  194. package/dist/util/cost.js +28 -0
  195. package/dist/util/keybindings.js +113 -0
  196. package/dist/util/lazy.js +26 -0
  197. package/dist/util/perf.js +25 -0
  198. package/dist/util/token-worker.js +11 -0
  199. package/dist/util/tokens.js +50 -0
  200. package/dist/workflows/builtins.js +128 -0
  201. package/dist/workflows/engine.js +496 -0
  202. package/dist/workflows/file-trigger.js +197 -0
  203. package/package.json +79 -0
@@ -0,0 +1,479 @@
1
+ import { createHash, randomBytes } from 'node:crypto';
2
+ import { EventEmitter } from 'node:events';
3
+ import { createServer } from 'node:http';
4
+ import net from 'node:net';
5
+ const WEBSOCKET_GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
6
+ const DEFAULT_HOST = '127.0.0.1';
7
+ export const DEFAULT_BRIDGE_PORT = 7891;
8
+ export class IDEBridge {
9
+ handlers = new Set();
10
+ createTransport;
11
+ transport = null;
12
+ cleanupTransport = [];
13
+ port = null;
14
+ constructor(options = {}) {
15
+ this.createTransport = options.createTransport ?? createClientTransport;
16
+ }
17
+ async connect(port = DEFAULT_BRIDGE_PORT) {
18
+ if (this.transport && this.transport.isOpen() && this.port === port)
19
+ return;
20
+ await this.disconnect();
21
+ const transport = await this.createTransport(port);
22
+ this.transport = transport;
23
+ this.port = port;
24
+ this.cleanupTransport = [
25
+ transport.onMessage((raw) => {
26
+ const message = parseBridgeMessage(raw);
27
+ if (!message)
28
+ return;
29
+ for (const handler of this.handlers)
30
+ handler(message);
31
+ }),
32
+ transport.onClose(() => {
33
+ this.transport = null;
34
+ }),
35
+ ];
36
+ }
37
+ async disconnect() {
38
+ const transport = this.transport;
39
+ this.transport = null;
40
+ this.port = null;
41
+ for (const cleanup of this.cleanupTransport.splice(0))
42
+ cleanup();
43
+ if (!transport)
44
+ return;
45
+ transport.close();
46
+ }
47
+ send(message) {
48
+ if (!this.transport || !this.transport.isOpen()) {
49
+ throw new Error('IDE bridge is not connected.');
50
+ }
51
+ this.transport.send(JSON.stringify(message));
52
+ }
53
+ onMessage(handler) {
54
+ this.handlers.add(handler);
55
+ return () => {
56
+ this.handlers.delete(handler);
57
+ };
58
+ }
59
+ isConnected() {
60
+ return Boolean(this.transport?.isOpen());
61
+ }
62
+ getPort() {
63
+ return this.port;
64
+ }
65
+ }
66
+ export class BridgeServer {
67
+ handlers = new Set();
68
+ heartbeatMs;
69
+ server = null;
70
+ heartbeatTimer = null;
71
+ connections = new Set();
72
+ port = null;
73
+ constructor(options = {}) {
74
+ this.heartbeatMs = options.heartbeatMs ?? 30_000;
75
+ }
76
+ async start(port = DEFAULT_BRIDGE_PORT) {
77
+ if (this.server && this.port === port)
78
+ return this.port;
79
+ if (this.server)
80
+ await this.stop();
81
+ const server = createServer((_request, response) => {
82
+ response.statusCode = 426;
83
+ response.setHeader('Connection', 'close');
84
+ response.end('Upgrade Required');
85
+ });
86
+ server.on('upgrade', (request, socket, head) => {
87
+ if (!isWebSocketUpgrade(request)) {
88
+ socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
89
+ socket.destroy();
90
+ return;
91
+ }
92
+ const key = request.headers['sec-websocket-key'];
93
+ if (typeof key !== 'string' || !key.trim()) {
94
+ socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
95
+ socket.destroy();
96
+ return;
97
+ }
98
+ const accept = createHash('sha1').update(`${key}${WEBSOCKET_GUID}`).digest('base64');
99
+ socket.write([
100
+ 'HTTP/1.1 101 Switching Protocols',
101
+ 'Upgrade: websocket',
102
+ 'Connection: Upgrade',
103
+ `Sec-WebSocket-Accept: ${accept}`,
104
+ '\r\n',
105
+ ].join('\r\n'));
106
+ const connection = new RawWebSocketConnection(socket, false, head);
107
+ this.connections.add(connection);
108
+ connection.onMessage((raw) => {
109
+ const message = parseBridgeMessage(raw);
110
+ if (!message)
111
+ return;
112
+ for (const handler of this.handlers)
113
+ handler(message);
114
+ });
115
+ connection.onClose(() => {
116
+ this.connections.delete(connection);
117
+ });
118
+ });
119
+ await new Promise((resolve, reject) => {
120
+ const onError = (error) => {
121
+ server.off('error', onError);
122
+ reject(error);
123
+ };
124
+ server.once('error', onError);
125
+ server.listen(port, DEFAULT_HOST, () => {
126
+ server.off('error', onError);
127
+ resolve();
128
+ });
129
+ });
130
+ this.server = server;
131
+ this.port = server.address()?.port ?? port;
132
+ this.startHeartbeat();
133
+ return this.port;
134
+ }
135
+ async stop() {
136
+ if (this.heartbeatTimer) {
137
+ clearInterval(this.heartbeatTimer);
138
+ this.heartbeatTimer = null;
139
+ }
140
+ for (const connection of this.connections) {
141
+ connection.close();
142
+ connection.terminate();
143
+ }
144
+ this.connections.clear();
145
+ const server = this.server;
146
+ this.server = null;
147
+ this.port = null;
148
+ if (!server)
149
+ return;
150
+ await new Promise((resolve, reject) => {
151
+ server.close((error) => {
152
+ if (error) {
153
+ reject(error);
154
+ return;
155
+ }
156
+ resolve();
157
+ });
158
+ });
159
+ }
160
+ broadcast(message) {
161
+ const payload = JSON.stringify(message);
162
+ for (const connection of this.connections) {
163
+ connection.sendText(payload);
164
+ }
165
+ }
166
+ onMessage(handler) {
167
+ this.handlers.add(handler);
168
+ return () => {
169
+ this.handlers.delete(handler);
170
+ };
171
+ }
172
+ isRunning() {
173
+ return this.server !== null;
174
+ }
175
+ getPort() {
176
+ return this.port;
177
+ }
178
+ getConnectionCount() {
179
+ return this.connections.size;
180
+ }
181
+ startHeartbeat() {
182
+ if (this.heartbeatTimer)
183
+ clearInterval(this.heartbeatTimer);
184
+ this.heartbeatTimer = setInterval(() => {
185
+ for (const connection of this.connections) {
186
+ if (!connection.beginHeartbeat()) {
187
+ connection.terminate();
188
+ this.connections.delete(connection);
189
+ continue;
190
+ }
191
+ connection.sendPing();
192
+ }
193
+ }, this.heartbeatMs);
194
+ this.heartbeatTimer.unref();
195
+ }
196
+ }
197
+ class RawWebSocketConnection extends EventEmitter {
198
+ socket;
199
+ maskOutgoing;
200
+ buffer = Buffer.alloc(0);
201
+ open = true;
202
+ alive = true;
203
+ closeSent = false;
204
+ constructor(socket, maskOutgoing, initialData) {
205
+ super();
206
+ this.socket = socket;
207
+ this.maskOutgoing = maskOutgoing;
208
+ socket.on('data', (chunk) => {
209
+ this.alive = true;
210
+ this.buffer = Buffer.concat([this.buffer, Buffer.from(chunk)]);
211
+ this.drainBuffer();
212
+ });
213
+ socket.on('close', () => {
214
+ this.handleClose();
215
+ });
216
+ socket.on('end', () => {
217
+ this.handleClose();
218
+ });
219
+ socket.on('error', () => {
220
+ this.handleClose();
221
+ });
222
+ if (initialData && initialData.length > 0) {
223
+ this.buffer = Buffer.concat([this.buffer, Buffer.from(initialData)]);
224
+ this.drainBuffer();
225
+ }
226
+ }
227
+ onMessage(handler) {
228
+ this.on('message', handler);
229
+ }
230
+ onClose(handler) {
231
+ this.on('close-connection', handler);
232
+ }
233
+ sendText(data) {
234
+ this.writeFrame(0x1, Buffer.from(data, 'utf8'));
235
+ }
236
+ sendPing(payload = Buffer.alloc(0)) {
237
+ this.writeFrame(0x9, payload);
238
+ }
239
+ sendPong(payload = Buffer.alloc(0)) {
240
+ this.writeFrame(0xa, payload);
241
+ }
242
+ beginHeartbeat() {
243
+ const wasAlive = this.alive;
244
+ this.alive = false;
245
+ return wasAlive;
246
+ }
247
+ close(code = 1000, reason = '') {
248
+ if (this.closeSent)
249
+ return;
250
+ const reasonBuffer = Buffer.from(reason, 'utf8');
251
+ const payload = Buffer.allocUnsafe(2 + reasonBuffer.length);
252
+ payload.writeUInt16BE(code, 0);
253
+ reasonBuffer.copy(payload, 2);
254
+ this.closeSent = true;
255
+ this.writeFrame(0x8, payload);
256
+ }
257
+ terminate() {
258
+ if (!this.open)
259
+ return;
260
+ this.open = false;
261
+ this.socket.destroy();
262
+ }
263
+ handleClose() {
264
+ if (!this.open)
265
+ return;
266
+ this.open = false;
267
+ this.emit('close-connection');
268
+ }
269
+ drainBuffer() {
270
+ while (this.buffer.length >= 2) {
271
+ const firstByte = this.buffer[0];
272
+ const secondByte = this.buffer[1];
273
+ const opcode = firstByte & 0x0f;
274
+ const masked = (secondByte & 0x80) !== 0;
275
+ let payloadLength = secondByte & 0x7f;
276
+ let offset = 2;
277
+ if (payloadLength === 126) {
278
+ if (this.buffer.length < offset + 2)
279
+ return;
280
+ payloadLength = this.buffer.readUInt16BE(offset);
281
+ offset += 2;
282
+ }
283
+ else if (payloadLength === 127) {
284
+ if (this.buffer.length < offset + 8)
285
+ return;
286
+ const rawLength = this.buffer.readBigUInt64BE(offset);
287
+ if (rawLength > BigInt(Number.MAX_SAFE_INTEGER)) {
288
+ this.terminate();
289
+ return;
290
+ }
291
+ payloadLength = Number(rawLength);
292
+ offset += 8;
293
+ }
294
+ const maskLength = masked ? 4 : 0;
295
+ const frameLength = offset + maskLength + payloadLength;
296
+ if (this.buffer.length < frameLength)
297
+ return;
298
+ let mask;
299
+ if (masked) {
300
+ mask = this.buffer.subarray(offset, offset + 4);
301
+ offset += 4;
302
+ }
303
+ const payload = Buffer.from(this.buffer.subarray(offset, offset + payloadLength));
304
+ this.buffer = this.buffer.subarray(frameLength);
305
+ if (mask) {
306
+ for (let index = 0; index < payload.length; index += 1) {
307
+ payload[index] ^= mask[index % 4];
308
+ }
309
+ }
310
+ if (opcode === 0x1) {
311
+ this.emit('message', payload.toString('utf8'));
312
+ continue;
313
+ }
314
+ if (opcode === 0x8) {
315
+ if (!this.closeSent) {
316
+ this.closeSent = true;
317
+ this.writeFrame(0x8, payload);
318
+ }
319
+ this.socket.end();
320
+ continue;
321
+ }
322
+ if (opcode === 0x9) {
323
+ this.sendPong(payload);
324
+ continue;
325
+ }
326
+ if (opcode === 0xa) {
327
+ this.alive = true;
328
+ }
329
+ }
330
+ }
331
+ writeFrame(opcode, payload) {
332
+ if (!this.open || this.socket.destroyed)
333
+ return;
334
+ const headerLength = 2 +
335
+ (payload.length < 126 ? 0 : payload.length < 65_536 ? 2 : 8) +
336
+ (this.maskOutgoing ? 4 : 0);
337
+ const frame = Buffer.allocUnsafe(headerLength + payload.length);
338
+ let offset = 0;
339
+ frame[offset] = 0x80 | (opcode & 0x0f);
340
+ offset += 1;
341
+ const lengthByteOffset = offset;
342
+ if (payload.length < 126) {
343
+ frame[offset] = payload.length;
344
+ offset += 1;
345
+ }
346
+ else if (payload.length < 65_536) {
347
+ frame[offset] = 126;
348
+ offset += 1;
349
+ frame.writeUInt16BE(payload.length, offset);
350
+ offset += 2;
351
+ }
352
+ else {
353
+ frame[offset] = 127;
354
+ offset += 1;
355
+ frame.writeBigUInt64BE(BigInt(payload.length), offset);
356
+ offset += 8;
357
+ }
358
+ if (this.maskOutgoing) {
359
+ const mask = randomBytes(4);
360
+ frame[lengthByteOffset] |= 0x80;
361
+ mask.copy(frame, offset);
362
+ offset += 4;
363
+ for (let index = 0; index < payload.length; index += 1) {
364
+ frame[offset + index] = payload[index] ^ mask[index % 4];
365
+ }
366
+ this.socket.write(frame);
367
+ return;
368
+ }
369
+ payload.copy(frame, offset);
370
+ this.socket.write(frame);
371
+ }
372
+ }
373
+ async function createClientTransport(port) {
374
+ const socket = net.createConnection({ host: DEFAULT_HOST, port });
375
+ const key = randomBytes(16).toString('base64');
376
+ return await new Promise((resolve, reject) => {
377
+ let settled = false;
378
+ let handshakeBuffer = Buffer.alloc(0);
379
+ let connection = null;
380
+ const fail = (error) => {
381
+ if (settled)
382
+ return;
383
+ settled = true;
384
+ socket.destroy();
385
+ reject(error);
386
+ };
387
+ socket.once('error', fail);
388
+ socket.once('connect', () => {
389
+ socket.write([
390
+ 'GET / HTTP/1.1',
391
+ `Host: ${DEFAULT_HOST}:${port}`,
392
+ 'Upgrade: websocket',
393
+ 'Connection: Upgrade',
394
+ `Sec-WebSocket-Key: ${key}`,
395
+ 'Sec-WebSocket-Version: 13',
396
+ '\r\n',
397
+ ].join('\r\n'));
398
+ });
399
+ socket.on('data', (chunk) => {
400
+ if (settled)
401
+ return;
402
+ handshakeBuffer = Buffer.concat([handshakeBuffer, Buffer.from(chunk)]);
403
+ const headerEnd = handshakeBuffer.indexOf('\r\n\r\n');
404
+ if (headerEnd === -1)
405
+ return;
406
+ const headerText = handshakeBuffer.subarray(0, headerEnd).toString('utf8');
407
+ const expectedAccept = createHash('sha1').update(`${key}${WEBSOCKET_GUID}`).digest('base64');
408
+ if (!headerText.startsWith('HTTP/1.1 101') ||
409
+ !headerText.toLowerCase().includes(`sec-websocket-accept: ${expectedAccept.toLowerCase()}`)) {
410
+ fail(new Error(`WebSocket handshake failed on port ${port}.`));
411
+ return;
412
+ }
413
+ settled = true;
414
+ socket.removeListener('error', fail);
415
+ socket.removeAllListeners('data');
416
+ connection = new RawWebSocketConnection(socket, true, handshakeBuffer.subarray(headerEnd + 4));
417
+ resolve({
418
+ send(data) {
419
+ connection?.sendText(data);
420
+ },
421
+ close(code, reason) {
422
+ connection?.close(code, reason);
423
+ connection?.terminate();
424
+ },
425
+ onMessage(handler) {
426
+ const listener = (data) => handler(data);
427
+ connection?.on('message', listener);
428
+ return () => {
429
+ connection?.off('message', listener);
430
+ };
431
+ },
432
+ onClose(handler) {
433
+ const listener = () => handler();
434
+ connection?.on('close-connection', listener);
435
+ return () => {
436
+ connection?.off('close-connection', listener);
437
+ };
438
+ },
439
+ isOpen() {
440
+ return socket.readyState === 'open';
441
+ },
442
+ });
443
+ });
444
+ socket.once('close', () => {
445
+ if (!settled)
446
+ fail(new Error(`Unable to connect to IDE bridge on port ${port}.`));
447
+ });
448
+ });
449
+ }
450
+ function isWebSocketUpgrade(request) {
451
+ const upgrade = request.headers.upgrade;
452
+ const connection = request.headers.connection;
453
+ return (typeof upgrade === 'string' &&
454
+ upgrade.toLowerCase() === 'websocket' &&
455
+ typeof connection === 'string' &&
456
+ connection
457
+ .toLowerCase()
458
+ .split(',')
459
+ .some((value) => value.trim() === 'upgrade'));
460
+ }
461
+ function parseBridgeMessage(raw) {
462
+ try {
463
+ const parsed = JSON.parse(raw);
464
+ if (!parsed || typeof parsed !== 'object')
465
+ return null;
466
+ if (parsed.type !== 'openFile' &&
467
+ parsed.type !== 'editFile' &&
468
+ parsed.type !== 'showDiff' &&
469
+ parsed.type !== 'navigate' &&
470
+ parsed.type !== 'diagnostic' &&
471
+ parsed.type !== 'status') {
472
+ return null;
473
+ }
474
+ return { type: parsed.type, payload: parsed.payload };
475
+ }
476
+ catch {
477
+ return null;
478
+ }
479
+ }
@@ -0,0 +1,34 @@
1
+ import { streamChat } from '../api/github-models.js';
2
+ import { Session } from '../session/session.js';
3
+ export class CloudRoutineExecutor {
4
+ async execute(routine) {
5
+ const session = new Session({ mode: 'ask' });
6
+ await session.initializeGitContext();
7
+ const messages = [
8
+ {
9
+ role: 'user',
10
+ content: routine.prompt,
11
+ },
12
+ ];
13
+ let output = '';
14
+ const result = await streamChat({
15
+ model: session.state.model,
16
+ messages,
17
+ onToken: (token) => {
18
+ output += token;
19
+ },
20
+ });
21
+ session.push({
22
+ role: 'user',
23
+ content: routine.prompt,
24
+ });
25
+ session.push({
26
+ role: 'assistant',
27
+ content: result.content || output,
28
+ });
29
+ }
30
+ }
31
+ export function createCloudRoutineExecutor() {
32
+ const executor = new CloudRoutineExecutor();
33
+ return (routine) => executor.execute(routine);
34
+ }
@@ -0,0 +1,67 @@
1
+ import { CloudRoutineStore } from './routine-storage.js';
2
+ export class CloudRoutineScheduler {
3
+ store;
4
+ executor = null;
5
+ pollingInterval;
6
+ intervalHandle = null;
7
+ isRunning = false;
8
+ constructor(pollingInterval = 60000) {
9
+ this.store = new CloudRoutineStore();
10
+ this.pollingInterval = pollingInterval;
11
+ }
12
+ setExecutor(executor) {
13
+ this.executor = executor;
14
+ }
15
+ start() {
16
+ if (this.isRunning)
17
+ return;
18
+ this.isRunning = true;
19
+ this.scheduleNextCheck();
20
+ }
21
+ stop() {
22
+ this.isRunning = false;
23
+ if (this.intervalHandle) {
24
+ clearTimeout(this.intervalHandle);
25
+ this.intervalHandle = null;
26
+ }
27
+ }
28
+ scheduleNextCheck() {
29
+ if (!this.isRunning)
30
+ return;
31
+ this.intervalHandle = setTimeout(() => {
32
+ void this.executeDue();
33
+ this.scheduleNextCheck();
34
+ }, this.pollingInterval);
35
+ }
36
+ async executeDue() {
37
+ if (!this.executor)
38
+ return;
39
+ const dueRoutines = this.store.findDueRoutines();
40
+ for (const routine of dueRoutines) {
41
+ try {
42
+ const startTime = Date.now();
43
+ await this.executor(routine);
44
+ const duration = Date.now() - startTime;
45
+ this.store.logExecution(routine.id, 'success', undefined, undefined, duration);
46
+ this.store.updateLastRun(routine.id, new Date().toISOString());
47
+ }
48
+ catch (err) {
49
+ const error = err instanceof Error ? err.message : String(err);
50
+ this.store.logExecution(routine.id, 'error', undefined, error, 0);
51
+ }
52
+ }
53
+ }
54
+ getStore() {
55
+ return this.store;
56
+ }
57
+ isSchedulerRunning() {
58
+ return this.isRunning;
59
+ }
60
+ }
61
+ let schedulerInstance = null;
62
+ export function getCloudRoutineScheduler() {
63
+ if (!schedulerInstance) {
64
+ schedulerInstance = new CloudRoutineScheduler();
65
+ }
66
+ return schedulerInstance;
67
+ }