agent-ws 1.0.2 → 1.0.4
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.
- package/README.md +4 -1
- package/dist/agent.js +120 -23
- package/dist/agent.js.map +2 -2
- package/dist/cli.js +125 -28
- package/dist/cli.js.map +2 -2
- package/dist/index.js +120 -23
- package/dist/index.js.map +2 -2
- package/package.json +1 -2
package/README.md
CHANGED
|
@@ -60,6 +60,7 @@ await agent.start();
|
|
|
60
60
|
-p, --port <port> WebSocket server port (default: 9999)
|
|
61
61
|
-H, --host <host> WebSocket server host (default: localhost)
|
|
62
62
|
-c, --claude-path <path> Path to Claude CLI (default: claude)
|
|
63
|
+
--codex-path <path> Path to Codex CLI (default: codex)
|
|
63
64
|
-t, --timeout <seconds> Process timeout in seconds (default: 300)
|
|
64
65
|
--log-level <level> Log level: debug, info, warn, error (default: info)
|
|
65
66
|
--origins <origins> Comma-separated allowed origins
|
|
@@ -97,6 +98,7 @@ Any WebSocket client can connect — browser frontends, backend services, script
|
|
|
97
98
|
```json
|
|
98
99
|
{ "type": "prompt", "prompt": "Build a login form", "requestId": "uuid", "model": "opus", "provider": "claude" }
|
|
99
100
|
{ "type": "prompt", "prompt": "...", "requestId": "uuid", "projectId": "my-app", "systemPrompt": "...", "thinkingTokens": 2048 }
|
|
101
|
+
{ "type": "prompt", "prompt": "Describe this image", "requestId": "uuid", "images": [{ "media_type": "image/png", "data": "<base64>" }] }
|
|
100
102
|
{ "type": "cancel", "requestId": "uuid" }
|
|
101
103
|
```
|
|
102
104
|
|
|
@@ -109,6 +111,7 @@ Any WebSocket client can connect — browser frontends, backend services, script
|
|
|
109
111
|
| `projectId` | no | Scopes CLI session by directory. Enables `--continue` for multi-turn. Alphanumeric, hyphens, underscores, dots only. |
|
|
110
112
|
| `systemPrompt` | no | Appended as system prompt (max 64KB) |
|
|
111
113
|
| `thinkingTokens` | no | Max thinking tokens. `0` disables thinking. Omit to let Claude decide. |
|
|
114
|
+
| `images` | no | Array of `{ media_type, data }` objects. Up to 4 images, max 10MB base64 each. Supported types: `image/png`, `image/jpeg`, `image/gif`, `image/webp`. |
|
|
112
115
|
|
|
113
116
|
### Agent → Client
|
|
114
117
|
|
|
@@ -128,7 +131,7 @@ Chunks with `thinking: true` contain Claude's reasoning. Clients can display the
|
|
|
128
131
|
- **Origin validation**: Optional `--origins` flag restricts allowed origins
|
|
129
132
|
- **No credentials**: Never stores or transmits API keys
|
|
130
133
|
- **Process isolation**: One CLI process per connection
|
|
131
|
-
- **Message limits**:
|
|
134
|
+
- **Message limits**: 50MB max WebSocket payload, 512KB max prompt, 10MB per image (4 max)
|
|
132
135
|
- **Heartbeat**: Dead connections are cleaned up every 30 seconds
|
|
133
136
|
|
|
134
137
|
## Development
|
package/dist/agent.js
CHANGED
|
@@ -32,7 +32,8 @@ var ClaudeRunner = class {
|
|
|
32
32
|
return;
|
|
33
33
|
}
|
|
34
34
|
this.kill();
|
|
35
|
-
const { prompt, model, systemPrompt, projectId, requestId, thinkingTokens } = options;
|
|
35
|
+
const { prompt, model, systemPrompt, projectId, requestId, thinkingTokens, images } = options;
|
|
36
|
+
const hasImages = images && images.length > 0;
|
|
36
37
|
const args = [
|
|
37
38
|
"--print",
|
|
38
39
|
"--verbose",
|
|
@@ -45,6 +46,9 @@ var ClaudeRunner = class {
|
|
|
45
46
|
""
|
|
46
47
|
// Disable tool use — we only want generated text
|
|
47
48
|
];
|
|
49
|
+
if (hasImages) {
|
|
50
|
+
args.push("--input-format", "stream-json");
|
|
51
|
+
}
|
|
48
52
|
if (projectId) {
|
|
49
53
|
args.push("--continue");
|
|
50
54
|
}
|
|
@@ -100,7 +104,20 @@ var ClaudeRunner = class {
|
|
|
100
104
|
}
|
|
101
105
|
this.log.debug({ pid: this.process.pid, requestId }, "Claude process spawned");
|
|
102
106
|
if (this.process.stdin) {
|
|
103
|
-
|
|
107
|
+
if (hasImages) {
|
|
108
|
+
const content = [];
|
|
109
|
+
for (const img of images) {
|
|
110
|
+
content.push({
|
|
111
|
+
type: "image",
|
|
112
|
+
source: { type: "base64", media_type: img.media_type, data: img.data }
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
content.push({ type: "text", text: prompt });
|
|
116
|
+
const msg = JSON.stringify({ type: "user", message: { role: "user", content } });
|
|
117
|
+
this.process.stdin.write(msg + "\n");
|
|
118
|
+
} else {
|
|
119
|
+
this.process.stdin.write(prompt);
|
|
120
|
+
}
|
|
104
121
|
this.process.stdin.end();
|
|
105
122
|
}
|
|
106
123
|
let handlersDone = false;
|
|
@@ -225,8 +242,8 @@ var ClaudeRunner = class {
|
|
|
225
242
|
|
|
226
243
|
// src/process/codex-runner.ts
|
|
227
244
|
import { spawn as spawn2 } from "node:child_process";
|
|
228
|
-
import { mkdirSync as mkdirSync2 } from "node:fs";
|
|
229
|
-
import { resolve as resolve2 } from "node:path";
|
|
245
|
+
import { mkdirSync as mkdirSync2, writeFileSync, unlinkSync } from "node:fs";
|
|
246
|
+
import { resolve as resolve2, join } from "node:path";
|
|
230
247
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
231
248
|
import { createInterface as createInterface2 } from "node:readline";
|
|
232
249
|
var DEFAULT_TIMEOUT_MS2 = 5 * 60 * 1e3;
|
|
@@ -255,7 +272,7 @@ var CodexRunner = class {
|
|
|
255
272
|
return;
|
|
256
273
|
}
|
|
257
274
|
this.kill();
|
|
258
|
-
const { prompt, model, systemPrompt, projectId, requestId } = options;
|
|
275
|
+
const { prompt, model, systemPrompt, projectId, requestId, images } = options;
|
|
259
276
|
let fullPrompt = prompt;
|
|
260
277
|
if (systemPrompt) {
|
|
261
278
|
fullPrompt = `${systemPrompt}
|
|
@@ -264,11 +281,28 @@ var CodexRunner = class {
|
|
|
264
281
|
|
|
265
282
|
${prompt}`;
|
|
266
283
|
}
|
|
284
|
+
const imagePaths = [];
|
|
285
|
+
if (images && images.length > 0) {
|
|
286
|
+
const imgDir = resolve2(tmpdir2(), "agent-ws-images");
|
|
287
|
+
mkdirSync2(imgDir, { recursive: true });
|
|
288
|
+
const safeId = requestId.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
|
|
289
|
+
for (let i = 0; i < images.length; i++) {
|
|
290
|
+
const img = images[i];
|
|
291
|
+
const rawExt = img.media_type.split("/")[1] || "png";
|
|
292
|
+
const ext = rawExt.replace(/[^a-zA-Z0-9]/g, "").slice(0, 10) || "png";
|
|
293
|
+
const imgPath = join(imgDir, `${safeId}-${i}.${ext}`);
|
|
294
|
+
writeFileSync(imgPath, Buffer.from(img.data, "base64"));
|
|
295
|
+
imagePaths.push(imgPath);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
267
298
|
const resuming = projectId && this.threadId;
|
|
268
299
|
const args = resuming ? ["exec", "resume", this.threadId, "--json", "--full-auto", "--skip-git-repo-check"] : ["exec", "--json", "--full-auto", "--skip-git-repo-check"];
|
|
269
300
|
if (model && !resuming) {
|
|
270
301
|
args.push("--model", model);
|
|
271
302
|
}
|
|
303
|
+
for (const imgPath of imagePaths) {
|
|
304
|
+
args.push("-i", imgPath);
|
|
305
|
+
}
|
|
272
306
|
args.push("-");
|
|
273
307
|
this.log.info({ requestId, model, promptLength: prompt.length }, "Spawning Codex process");
|
|
274
308
|
this.killed = false;
|
|
@@ -282,6 +316,14 @@ ${prompt}`;
|
|
|
282
316
|
}
|
|
283
317
|
mkdirSync2(cwd, { recursive: true });
|
|
284
318
|
}
|
|
319
|
+
const cleanupImages = () => {
|
|
320
|
+
for (const p of imagePaths) {
|
|
321
|
+
try {
|
|
322
|
+
unlinkSync(p);
|
|
323
|
+
} catch {
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
};
|
|
285
327
|
const ALLOWED_ENV_KEYS = [
|
|
286
328
|
"PATH",
|
|
287
329
|
"HOME",
|
|
@@ -305,6 +347,7 @@ ${prompt}`;
|
|
|
305
347
|
env
|
|
306
348
|
});
|
|
307
349
|
} catch (err) {
|
|
350
|
+
cleanupImages();
|
|
308
351
|
const message = err instanceof Error ? err.message : "Failed to start Codex";
|
|
309
352
|
this.log.error({ err, requestId }, "Failed to spawn Codex process");
|
|
310
353
|
handlers.onError(message, requestId);
|
|
@@ -343,6 +386,7 @@ ${prompt}`;
|
|
|
343
386
|
}
|
|
344
387
|
this.process.on("exit", (exitCode, signal) => {
|
|
345
388
|
this.process = null;
|
|
389
|
+
cleanupImages();
|
|
346
390
|
if (this.killed) {
|
|
347
391
|
this.log.debug({ requestId }, "Codex process was killed");
|
|
348
392
|
return;
|
|
@@ -358,6 +402,7 @@ ${prompt}`;
|
|
|
358
402
|
});
|
|
359
403
|
this.process.on("error", (err) => {
|
|
360
404
|
this.process = null;
|
|
405
|
+
cleanupImages();
|
|
361
406
|
this.log.error({ err, requestId }, "Codex process error");
|
|
362
407
|
finish(() => handlers.onError(err.message, requestId));
|
|
363
408
|
});
|
|
@@ -430,6 +475,14 @@ ${prompt}`;
|
|
|
430
475
|
// src/server/protocol.ts
|
|
431
476
|
var MAX_PROMPT_BYTES = 512 * 1024;
|
|
432
477
|
var MAX_SYSTEM_PROMPT_BYTES = 64 * 1024;
|
|
478
|
+
var MAX_IMAGES = 4;
|
|
479
|
+
var MAX_IMAGE_BASE64_BYTES = 10 * 1024 * 1024;
|
|
480
|
+
var ALLOWED_IMAGE_TYPES = /* @__PURE__ */ new Set([
|
|
481
|
+
"image/png",
|
|
482
|
+
"image/jpeg",
|
|
483
|
+
"image/gif",
|
|
484
|
+
"image/webp"
|
|
485
|
+
]);
|
|
433
486
|
var MAX_PROJECT_ID_LENGTH = 128;
|
|
434
487
|
var PROJECT_ID_PATTERN = /^[a-zA-Z0-9._-]+$/;
|
|
435
488
|
function parseClientMessage(raw) {
|
|
@@ -476,6 +529,30 @@ function parseClientMessage(raw) {
|
|
|
476
529
|
return { ok: false, error: "projectId contains invalid characters (allowed: alphanumeric, hyphens, underscores, dots)" };
|
|
477
530
|
}
|
|
478
531
|
}
|
|
532
|
+
let parsedImages;
|
|
533
|
+
const rawImages = obj["images"];
|
|
534
|
+
if (Array.isArray(rawImages) && rawImages.length > 0) {
|
|
535
|
+
if (rawImages.length > MAX_IMAGES) {
|
|
536
|
+
return { ok: false, error: `Too many images (max ${MAX_IMAGES})` };
|
|
537
|
+
}
|
|
538
|
+
parsedImages = [];
|
|
539
|
+
for (const img of rawImages) {
|
|
540
|
+
if (typeof img !== "object" || img === null) {
|
|
541
|
+
return { ok: false, error: "Each image must be an object with media_type and data" };
|
|
542
|
+
}
|
|
543
|
+
const imgObj = img;
|
|
544
|
+
if (typeof imgObj["media_type"] !== "string" || typeof imgObj["data"] !== "string") {
|
|
545
|
+
return { ok: false, error: "Each image must have string media_type and data fields" };
|
|
546
|
+
}
|
|
547
|
+
if (!ALLOWED_IMAGE_TYPES.has(imgObj["media_type"])) {
|
|
548
|
+
return { ok: false, error: `Unsupported image type: ${String(imgObj["media_type"]).slice(0, 50)} (allowed: png, jpeg, gif, webp)` };
|
|
549
|
+
}
|
|
550
|
+
if (imgObj["data"].length > MAX_IMAGE_BASE64_BYTES) {
|
|
551
|
+
return { ok: false, error: `Image exceeds maximum size of ${MAX_IMAGE_BASE64_BYTES} bytes` };
|
|
552
|
+
}
|
|
553
|
+
parsedImages.push({ media_type: imgObj["media_type"], data: imgObj["data"] });
|
|
554
|
+
}
|
|
555
|
+
}
|
|
479
556
|
return {
|
|
480
557
|
ok: true,
|
|
481
558
|
message: {
|
|
@@ -486,7 +563,8 @@ function parseClientMessage(raw) {
|
|
|
486
563
|
projectId: typeof projectId === "string" ? projectId : void 0,
|
|
487
564
|
requestId,
|
|
488
565
|
provider: provider === "codex" ? "codex" : "claude",
|
|
489
|
-
thinkingTokens: typeof thinkingTokens === "number" && thinkingTokens >= 0 ? thinkingTokens : void 0
|
|
566
|
+
thinkingTokens: typeof thinkingTokens === "number" && thinkingTokens >= 0 ? thinkingTokens : void 0,
|
|
567
|
+
images: parsedImages
|
|
490
568
|
}
|
|
491
569
|
};
|
|
492
570
|
}
|
|
@@ -510,7 +588,7 @@ function serializeMessage(message) {
|
|
|
510
588
|
|
|
511
589
|
// src/server/websocket.ts
|
|
512
590
|
var HEARTBEAT_INTERVAL_MS = 3e4;
|
|
513
|
-
var DEFAULT_MAX_PAYLOAD = 1024 * 1024;
|
|
591
|
+
var DEFAULT_MAX_PAYLOAD = 50 * 1024 * 1024;
|
|
514
592
|
var AgentWebSocketServer = class {
|
|
515
593
|
wss = null;
|
|
516
594
|
heartbeatInterval = null;
|
|
@@ -624,17 +702,12 @@ var AgentWebSocketServer = class {
|
|
|
624
702
|
state.activeRequestId = message.requestId;
|
|
625
703
|
if (message.provider === "codex") {
|
|
626
704
|
if (!state.codexRunner) {
|
|
627
|
-
state.codexRunner =
|
|
628
|
-
codexPath: this.options.codexPath,
|
|
629
|
-
timeoutMs: this.options.timeoutMs,
|
|
630
|
-
logger: this.log.child({ component: "codex-runner" }),
|
|
631
|
-
sessionDir: this.options.sessionDir
|
|
632
|
-
});
|
|
705
|
+
state.codexRunner = this.createCodexRunner();
|
|
633
706
|
}
|
|
634
707
|
state.activeRunner = state.codexRunner;
|
|
635
708
|
} else {
|
|
636
709
|
if (!state.claudeRunner) {
|
|
637
|
-
state.claudeRunner = this.
|
|
710
|
+
state.claudeRunner = this.createClaudeRunner();
|
|
638
711
|
}
|
|
639
712
|
state.activeRunner = state.claudeRunner;
|
|
640
713
|
}
|
|
@@ -664,14 +737,25 @@ var AgentWebSocketServer = class {
|
|
|
664
737
|
}
|
|
665
738
|
};
|
|
666
739
|
state.activeRunner.run(
|
|
667
|
-
{
|
|
740
|
+
{
|
|
741
|
+
prompt: message.prompt,
|
|
742
|
+
model: message.model,
|
|
743
|
+
systemPrompt: message.systemPrompt,
|
|
744
|
+
projectId: message.projectId,
|
|
745
|
+
requestId: message.requestId,
|
|
746
|
+
thinkingTokens: message.thinkingTokens,
|
|
747
|
+
images: message.images
|
|
748
|
+
},
|
|
668
749
|
handlers
|
|
669
750
|
);
|
|
670
751
|
}
|
|
671
752
|
handleCancel(ws, state) {
|
|
672
|
-
state.activeRunner?.kill();
|
|
673
753
|
const requestId = state.activeRequestId;
|
|
754
|
+
state.activeRunner?.kill();
|
|
674
755
|
state.activeRequestId = null;
|
|
756
|
+
if (requestId) {
|
|
757
|
+
this.sendMessage(ws, { type: "error", message: "Request cancelled", requestId });
|
|
758
|
+
}
|
|
675
759
|
this.log.info({ requestId }, "Request cancelled");
|
|
676
760
|
}
|
|
677
761
|
sendMessage(ws, message) {
|
|
@@ -681,17 +765,28 @@ var AgentWebSocketServer = class {
|
|
|
681
765
|
this.log.warn({ messageType: message.type, readyState: ws.readyState }, "Dropping message, WebSocket not OPEN");
|
|
682
766
|
}
|
|
683
767
|
}
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
768
|
+
createClaudeRunner() {
|
|
769
|
+
const factory = this.options.claudeRunnerFactory ?? this.options.runnerFactory;
|
|
770
|
+
if (factory) {
|
|
771
|
+
return factory(this.log);
|
|
687
772
|
}
|
|
688
|
-
|
|
773
|
+
return new ClaudeRunner({
|
|
689
774
|
claudePath: this.options.claudePath,
|
|
690
775
|
timeoutMs: this.options.timeoutMs,
|
|
691
|
-
logger: this.log.child({ component: "runner" }),
|
|
776
|
+
logger: this.log.child({ component: "claude-runner" }),
|
|
692
777
|
sessionDir: this.options.sessionDir
|
|
693
|
-
};
|
|
694
|
-
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
createCodexRunner() {
|
|
781
|
+
if (this.options.codexRunnerFactory) {
|
|
782
|
+
return this.options.codexRunnerFactory(this.log);
|
|
783
|
+
}
|
|
784
|
+
return new CodexRunner({
|
|
785
|
+
codexPath: this.options.codexPath,
|
|
786
|
+
timeoutMs: this.options.timeoutMs,
|
|
787
|
+
logger: this.log.child({ component: "codex-runner" }),
|
|
788
|
+
sessionDir: this.options.sessionDir
|
|
789
|
+
});
|
|
695
790
|
}
|
|
696
791
|
startHeartbeat() {
|
|
697
792
|
this.heartbeatInterval = setInterval(() => {
|
|
@@ -757,6 +852,8 @@ var AgentWS = class {
|
|
|
757
852
|
timeoutMs: options.timeoutMs,
|
|
758
853
|
allowedOrigins: options.allowedOrigins,
|
|
759
854
|
runnerFactory: options.runnerFactory,
|
|
855
|
+
claudeRunnerFactory: options.claudeRunnerFactory,
|
|
856
|
+
codexRunnerFactory: options.codexRunnerFactory,
|
|
760
857
|
agentName: options.agentName,
|
|
761
858
|
sessionDir: options.sessionDir
|
|
762
859
|
};
|
package/dist/agent.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/server/websocket.ts", "../src/process/claude-runner.ts", "../src/process/codex-runner.ts", "../src/server/protocol.ts", "../src/utils/logger.ts", "../src/agent.ts"],
|
|
4
|
-
"sourcesContent": ["import { WebSocketServer, WebSocket } from \"ws\";\nimport type { IncomingMessage } from \"node:http\";\nimport { ClaudeRunner, type ClaudeRunnerOptions, type Runner, type RunHandlers } from \"../process/claude-runner.js\";\nimport { CodexRunner } from \"../process/codex-runner.js\";\nimport {\n parseClientMessage,\n serializeMessage,\n type AgentMessage,\n type PromptMessage,\n} from \"./protocol.js\";\nimport type { Logger } from \"../utils/logger.js\";\n\nconst HEARTBEAT_INTERVAL_MS = 30_000;\nconst DEFAULT_MAX_PAYLOAD = 1024 * 1024; // 1MB\n\nexport type RunnerFactory = (log: Logger) => Runner;\n\ninterface ConnectionState {\n claudeRunner: Runner | null;\n codexRunner: Runner | null;\n activeRunner: Runner | null;\n isAlive: boolean;\n activeRequestId: string | null;\n}\n\nexport interface AgentWebSocketServerOptions {\n port: number;\n host: string;\n logger: Logger;\n claudePath?: string;\n codexPath?: string;\n timeoutMs?: number;\n allowedOrigins?: string[];\n maxPayload?: number;\n runnerFactory?: RunnerFactory;\n agentName?: string;\n sessionDir?: string;\n}\n\nexport class AgentWebSocketServer {\n private wss: WebSocketServer | null = null;\n private heartbeatInterval: NodeJS.Timeout | null = null;\n private readonly connections = new Map<WebSocket, ConnectionState>();\n private readonly log: Logger;\n private readonly options: AgentWebSocketServerOptions;\n\n constructor(options: AgentWebSocketServerOptions) {\n this.options = options;\n this.log = options.logger;\n }\n\n start(): Promise<void> {\n return new Promise((resolve, reject) => {\n this.wss = new WebSocketServer({\n port: this.options.port,\n host: this.options.host,\n maxPayload: this.options.maxPayload ?? DEFAULT_MAX_PAYLOAD,\n });\n\n this.wss.on(\"listening\", () => {\n this.log.info({ port: this.options.port, host: this.options.host }, \"WebSocket server started\");\n this.startHeartbeat();\n resolve();\n });\n\n this.wss.on(\"error\", (err: NodeJS.ErrnoException) => {\n if (err.code === \"EADDRINUSE\") {\n this.log.fatal({ port: this.options.port }, \"Port already in use\");\n } else {\n this.log.error({ err }, \"WebSocket server error\");\n }\n reject(err);\n });\n\n this.wss.on(\"connection\", (ws, req) => this.handleConnection(ws, req));\n });\n }\n\n stop(): void {\n if (this.heartbeatInterval) {\n clearInterval(this.heartbeatInterval);\n this.heartbeatInterval = null;\n }\n\n for (const [ws, state] of this.connections) {\n state.claudeRunner?.dispose();\n state.codexRunner?.dispose();\n ws.terminate();\n }\n this.connections.clear();\n\n if (this.wss) {\n this.wss.close();\n this.wss = null;\n }\n\n this.log.info(\"WebSocket server stopped\");\n }\n\n private handleConnection(ws: WebSocket, req: IncomingMessage): void {\n // Origin check\n if (this.options.allowedOrigins && this.options.allowedOrigins.length > 0) {\n const origin = req.headers.origin;\n if (!origin || !this.options.allowedOrigins.includes(origin)) {\n this.log.warn({ origin: origin ?? \"(none)\" }, \"Rejected connection: origin not in allowlist\");\n ws.close(4003, \"Origin not allowed\");\n return;\n }\n }\n\n const clientIp = req.socket.remoteAddress;\n this.log.info({ clientIp }, \"Client connected\");\n\n const state: ConnectionState = { claudeRunner: null, codexRunner: null, activeRunner: null, isAlive: true, activeRequestId: null };\n this.connections.set(ws, state);\n\n // Send connected message\n this.sendMessage(ws, {\n type: \"connected\",\n version: \"1.0\",\n agent: this.options.agentName ?? \"agent-ws\",\n });\n\n ws.on(\"pong\", () => {\n state.isAlive = true;\n });\n\n ws.on(\"message\", (data) => {\n const raw = data.toString();\n this.handleMessage(ws, state, raw);\n });\n\n ws.on(\"close\", () => {\n this.log.info({ clientIp }, \"Client disconnected\");\n state.claudeRunner?.dispose();\n state.codexRunner?.dispose();\n this.connections.delete(ws);\n });\n\n ws.on(\"error\", (err) => {\n this.log.error({ err, clientIp }, \"WebSocket error\");\n });\n }\n\n private handleMessage(ws: WebSocket, state: ConnectionState, raw: string): void {\n const result = parseClientMessage(raw);\n\n if (!result.ok) {\n this.sendMessage(ws, { type: \"error\", message: result.error });\n return;\n }\n\n const { message } = result;\n\n switch (message.type) {\n case \"prompt\":\n this.handlePrompt(ws, state, message);\n break;\n case \"cancel\":\n this.handleCancel(ws, state);\n break;\n }\n }\n\n private handlePrompt(ws: WebSocket, state: ConnectionState, message: PromptMessage): void {\n if (state.activeRequestId !== null) {\n this.sendMessage(ws, {\n type: \"error\",\n message: \"Request already in progress\",\n requestId: message.requestId,\n });\n return;\n }\n\n state.activeRequestId = message.requestId;\n\n // Select runner for the requested provider (lazy-created, preserved across switches)\n if (message.provider === \"codex\") {\n if (!state.codexRunner) {\n state.codexRunner = new CodexRunner({\n codexPath: this.options.codexPath,\n timeoutMs: this.options.timeoutMs,\n logger: this.log.child({ component: \"codex-runner\" }),\n sessionDir: this.options.sessionDir,\n });\n }\n state.activeRunner = state.codexRunner;\n } else {\n if (!state.claudeRunner) {\n state.claudeRunner = this.createRunner();\n }\n state.activeRunner = state.claudeRunner;\n }\n\n const handlers: RunHandlers = {\n onChunk: (content, requestId, thinking) => {\n try {\n this.sendMessage(ws, { type: \"chunk\", content, requestId, ...(thinking ? { thinking: true } : {}) });\n } catch (err) {\n this.log.warn({ err, requestId }, \"Error in onChunk handler\");\n }\n },\n onComplete: (requestId) => {\n try {\n state.activeRequestId = null;\n this.sendMessage(ws, { type: \"complete\", requestId });\n } catch (err) {\n this.log.warn({ err, requestId }, \"Error in onComplete handler\");\n }\n },\n onError: (errorMessage, requestId) => {\n try {\n state.activeRequestId = null;\n this.sendMessage(ws, { type: \"error\", message: errorMessage, requestId });\n } catch (err) {\n this.log.warn({ err, requestId }, \"Error in onError handler\");\n }\n },\n };\n\n state.activeRunner!.run(\n { prompt: message.prompt, model: message.model, systemPrompt: message.systemPrompt, projectId: message.projectId, requestId: message.requestId, thinkingTokens: message.thinkingTokens },\n handlers,\n );\n }\n\n private handleCancel(ws: WebSocket, state: ConnectionState): void {\n state.activeRunner?.kill();\n const requestId = state.activeRequestId;\n state.activeRequestId = null;\n this.log.info({ requestId }, \"Request cancelled\");\n }\n\n private sendMessage(ws: WebSocket, message: AgentMessage): void {\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(serializeMessage(message));\n } else {\n this.log.warn({ messageType: message.type, readyState: ws.readyState }, \"Dropping message, WebSocket not OPEN\");\n }\n }\n\n private createRunner(): Runner {\n if (this.options.runnerFactory) {\n return this.options.runnerFactory(this.log);\n }\n\n const runnerOptions: ClaudeRunnerOptions = {\n claudePath: this.options.claudePath,\n timeoutMs: this.options.timeoutMs,\n logger: this.log.child({ component: \"runner\" }),\n sessionDir: this.options.sessionDir,\n };\n return new ClaudeRunner(runnerOptions);\n }\n\n private startHeartbeat(): void {\n this.heartbeatInterval = setInterval(() => {\n for (const [ws, state] of this.connections) {\n if (!state.isAlive) {\n this.log.debug(\"Terminating dead connection\");\n state.claudeRunner?.dispose();\n state.codexRunner?.dispose();\n this.connections.delete(ws);\n ws.terminate();\n continue;\n }\n\n state.isAlive = false;\n try {\n ws.ping();\n } catch {\n this.log.debug(\"Ping failed, terminating connection\");\n state.claudeRunner?.dispose();\n state.codexRunner?.dispose();\n this.connections.delete(ws);\n ws.terminate();\n }\n }\n }, HEARTBEAT_INTERVAL_MS);\n }\n}\n", "import { spawn, type ChildProcess } from \"node:child_process\";\nimport { mkdirSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { tmpdir } from \"node:os\";\nimport { createInterface } from \"node:readline\";\nimport type { Logger } from \"../utils/logger.js\";\n\nexport interface RunOptions {\n prompt: string;\n model?: string;\n systemPrompt?: string;\n projectId?: string;\n requestId: string;\n thinkingTokens?: number;\n}\n\nexport interface RunHandlers {\n onChunk: (content: string, requestId: string, thinking?: boolean) => void;\n onComplete: (requestId: string) => void;\n onError: (message: string, requestId: string) => void;\n}\n\n/** Interface for runner injection (testing) */\nexport interface Runner {\n run(options: RunOptions, handlers: RunHandlers): void;\n kill(): void;\n dispose(): void;\n}\n\nexport interface ClaudeRunnerOptions {\n claudePath?: string;\n timeoutMs?: number;\n logger: Logger;\n sessionDir?: string;\n}\n\nconst DEFAULT_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes\n\nexport class ClaudeRunner implements Runner {\n private process: ChildProcess | null = null;\n private timeout: NodeJS.Timeout | null = null;\n private disposed = false;\n private killed = false;\n private readonly claudePath: string;\n private readonly timeoutMs: number;\n private readonly log: Logger;\n private readonly sessionDir: string;\n\n constructor(options: ClaudeRunnerOptions) {\n this.claudePath = options.claudePath ?? \"claude\";\n this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.log = options.logger;\n this.sessionDir = options.sessionDir ?? \"agent-ws-sessions\";\n }\n\n get isRunning(): boolean {\n return this.process !== null;\n }\n\n run(options: RunOptions, handlers: RunHandlers): void {\n if (this.disposed) {\n handlers.onError(\"Runner has been disposed\", options.requestId);\n return;\n }\n\n // Kill any existing process first\n this.kill();\n\n const { prompt, model, systemPrompt, projectId, requestId, thinkingTokens } = options;\n\n const args = [\n \"--print\",\n \"--verbose\",\n \"--output-format\", \"stream-json\",\n \"--max-turns\", \"1\", // Single-turn text output, no agentic loops\n \"--tools\", \"\", // Disable tool use \u2014 we only want generated text\n ];\n // Only resume session when a projectId is provided (scoped by CWD)\n if (projectId) {\n args.push(\"--continue\");\n }\n if (model) {\n args.push(\"--model\", model);\n }\n if (systemPrompt) {\n args.push(\"--append-system-prompt\", systemPrompt);\n }\n // Prompt is piped via stdin (no arg length limits, no flag-parsing issues)\n args.push(\"-\");\n\n this.log.info({ requestId, model, promptLength: prompt.length }, \"Spawning Claude process\");\n this.killed = false;\n\n // Use project-scoped CWD so --continue resumes the correct session\n // (Claude CLI scopes sessions by working directory)\n let cwd: string | undefined;\n if (projectId) {\n const base = resolve(tmpdir(), this.sessionDir);\n cwd = resolve(base, projectId);\n if (!cwd.startsWith(base + \"/\") && cwd !== base) {\n handlers.onError(\"Invalid projectId\", requestId);\n return;\n }\n mkdirSync(cwd, { recursive: true });\n }\n\n try {\n const ALLOWED_ENV_KEYS = [\n \"PATH\", \"HOME\", \"USER\", \"SHELL\", \"TERM\", \"LANG\", \"LC_ALL\",\n \"ANTHROPIC_API_KEY\", \"NODE_PATH\", \"XDG_CONFIG_HOME\",\n ];\n const env: Record<string, string> = {};\n if (thinkingTokens !== undefined) {\n env[\"MAX_THINKING_TOKENS\"] = String(thinkingTokens);\n }\n for (const key of ALLOWED_ENV_KEYS) {\n if (process.env[key]) env[key] = process.env[key]!;\n }\n\n this.process = spawn(this.claudePath, args, {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n cwd,\n env,\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : \"Failed to start Claude\";\n this.log.error({ err, requestId }, \"Failed to spawn Claude process\");\n handlers.onError(message, requestId);\n return;\n }\n\n this.log.debug({ pid: this.process.pid, requestId }, \"Claude process spawned\");\n\n // Write prompt to stdin and close it\n if (this.process.stdin) {\n this.process.stdin.write(prompt);\n this.process.stdin.end();\n }\n\n // Guard against double handler invocation (e.g. error + exit both firing)\n let handlersDone = false;\n const finish = (cb: () => void) => {\n if (handlersDone) return;\n handlersDone = true;\n this.clearTimeout();\n cb();\n };\n\n // Set up timeout\n this.timeout = setTimeout(() => {\n this.log.warn({ requestId }, \"Claude process timed out\");\n this.kill();\n finish(() => handlers.onError(\"Process timed out\", requestId));\n }, this.timeoutMs);\n\n // Parse NDJSON from stdout\n if (this.process.stdout) {\n const rl = createInterface({ input: this.process.stdout });\n rl.on(\"line\", (line) => {\n this.parseStreamLine(line, handlers, requestId);\n });\n }\n\n // Capture stderr \u2014 logs at warn level so errors are visible\n if (this.process.stderr) {\n const stderrRl = createInterface({ input: this.process.stderr });\n stderrRl.on(\"line\", (line) => {\n if (line.trim()) {\n this.log.warn({ requestId, stderr: line }, \"Claude stderr\");\n }\n });\n }\n\n // Handle exit\n this.process.on(\"exit\", (exitCode, signal) => {\n this.process = null;\n\n if (this.killed) {\n this.log.debug({ requestId }, \"Claude process was killed\");\n return;\n }\n\n if (exitCode === 0) {\n this.log.info({ requestId }, \"Claude process completed successfully\");\n finish(() => handlers.onComplete(requestId));\n } else {\n const reason = exitCode !== null\n ? `Claude CLI exited with code ${exitCode}`\n : `Claude CLI killed by signal ${signal ?? \"unknown\"}`;\n this.log.warn({ requestId, exitCode, signal }, reason);\n finish(() => handlers.onError(reason, requestId));\n }\n });\n\n this.process.on(\"error\", (err) => {\n this.process = null;\n this.log.error({ err, requestId }, \"Claude process error\");\n finish(() => handlers.onError(err.message, requestId));\n });\n }\n\n /**\n * Parse a single NDJSON line from Claude CLI's stream-json output.\n *\n * The stream-json format can emit several event types. We look for content\n * in these known patterns (in priority order):\n *\n * 1. Raw Anthropic API event: { type: \"content_block_delta\", delta: { type: \"text_delta\"|\"thinking_delta\", text|thinking } }\n * 2. Wrapped stream event: { type: \"stream_event\", event: { type: \"content_block_delta\", ... } }\n * 3. Complete assistant msg: { type: \"assistant\", message: { content: [{ type: \"text\"|\"thinking\", text|thinking }] } }\n */\n private parseStreamLine(line: string, handlers: RunHandlers, requestId: string): void {\n if (!line.trim()) return;\n\n try {\n const event = JSON.parse(line);\n\n // Pattern 1: Raw content_block_delta\n if (event.type === \"content_block_delta\") {\n if (event.delta?.type === \"text_delta\" && event.delta.text) {\n handlers.onChunk(event.delta.text, requestId);\n } else if (event.delta?.type === \"thinking_delta\" && event.delta.thinking) {\n handlers.onChunk(event.delta.thinking, requestId, true);\n }\n return;\n }\n\n // Pattern 2: Wrapped in stream_event\n if (event.type === \"stream_event\" && event.event) {\n const inner = event.event;\n if (inner.type === \"content_block_delta\") {\n if (inner.delta?.type === \"text_delta\" && inner.delta.text) {\n handlers.onChunk(inner.delta.text, requestId);\n } else if (inner.delta?.type === \"thinking_delta\" && inner.delta.thinking) {\n handlers.onChunk(inner.delta.thinking, requestId, true);\n }\n }\n return;\n }\n\n // Pattern 3: Complete assistant message\n if (event.type === \"assistant\" && Array.isArray(event.message?.content)) {\n for (const block of event.message.content) {\n if (block.type === \"text\" && block.text) {\n handlers.onChunk(block.text, requestId);\n } else if (block.type === \"thinking\" && block.thinking) {\n handlers.onChunk(block.thinking, requestId, true);\n }\n }\n return;\n }\n\n // Result event \u2014 ignore (we already streamed the content)\n if (event.type === \"result\") {\n return;\n }\n } catch {\n // Non-JSON line, skip\n }\n }\n\n kill(): void {\n this.clearTimeout();\n if (this.process) {\n this.log.debug({ pid: this.process.pid }, \"Killing Claude process\");\n this.killed = true;\n try {\n this.process.kill();\n } catch {\n // Process may already be dead\n }\n this.process = null;\n }\n }\n\n dispose(): void {\n this.disposed = true;\n this.kill();\n }\n\n private clearTimeout(): void {\n if (this.timeout) {\n clearTimeout(this.timeout);\n this.timeout = null;\n }\n }\n}\n", "import { spawn, type ChildProcess } from \"node:child_process\";\nimport { mkdirSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { tmpdir } from \"node:os\";\nimport { createInterface } from \"node:readline\";\nimport type { Logger } from \"../utils/logger.js\";\nimport type { Runner, RunOptions, RunHandlers } from \"./claude-runner.js\";\n\nexport interface CodexRunnerOptions {\n codexPath?: string;\n timeoutMs?: number;\n logger: Logger;\n sessionDir?: string;\n}\n\nconst DEFAULT_TIMEOUT_MS = 5 * 60 * 1000;\n\nexport class CodexRunner implements Runner {\n private process: ChildProcess | null = null;\n private timeout: NodeJS.Timeout | null = null;\n private disposed = false;\n private killed = false;\n private threadId: string | null = null;\n private readonly codexPath: string;\n private readonly timeoutMs: number;\n private readonly log: Logger;\n private readonly sessionDir: string;\n\n constructor(options: CodexRunnerOptions) {\n this.codexPath = options.codexPath ?? \"codex\";\n this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.log = options.logger;\n this.sessionDir = options.sessionDir ?? \"agent-ws-sessions\";\n }\n\n get isRunning(): boolean {\n return this.process !== null;\n }\n\n run(options: RunOptions, handlers: RunHandlers): void {\n if (this.disposed) {\n handlers.onError(\"Runner has been disposed\", options.requestId);\n return;\n }\n\n this.kill();\n\n const { prompt, model, systemPrompt, projectId, requestId } = options;\n\n // Build the full prompt: prepend system prompt since Codex doesn't have\n // a dedicated --append-system-prompt flag\n let fullPrompt = prompt;\n if (systemPrompt) {\n fullPrompt = `${systemPrompt}\\n\\n---\\n\\n${prompt}`;\n }\n\n // Resume existing thread if we have one and a projectId is set (session scoping)\n const resuming = projectId && this.threadId;\n const args: string[] = resuming\n ? [\"exec\", \"resume\", this.threadId!, \"--json\", \"--full-auto\", \"--skip-git-repo-check\"]\n : [\"exec\", \"--json\", \"--full-auto\", \"--skip-git-repo-check\"];\n if (model && !resuming) {\n args.push(\"--model\", model);\n }\n // Read prompt from stdin\n args.push(\"-\");\n\n this.log.info({ requestId, model, promptLength: prompt.length }, \"Spawning Codex process\");\n this.killed = false;\n\n let cwd: string | undefined;\n if (projectId) {\n const base = resolve(tmpdir(), this.sessionDir);\n cwd = resolve(base, projectId);\n if (!cwd.startsWith(base + \"/\") && cwd !== base) {\n handlers.onError(\"Invalid projectId\", requestId);\n return;\n }\n mkdirSync(cwd, { recursive: true });\n }\n\n const ALLOWED_ENV_KEYS = [\n \"PATH\", \"HOME\", \"USER\", \"SHELL\", \"TERM\", \"LANG\", \"LC_ALL\",\n \"OPENAI_API_KEY\", \"NODE_PATH\", \"XDG_CONFIG_HOME\",\n ];\n const env: Record<string, string> = {};\n for (const key of ALLOWED_ENV_KEYS) {\n if (process.env[key]) env[key] = process.env[key]!;\n }\n\n try {\n this.process = spawn(this.codexPath, args, {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n cwd,\n env,\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : \"Failed to start Codex\";\n this.log.error({ err, requestId }, \"Failed to spawn Codex process\");\n handlers.onError(message, requestId);\n return;\n }\n\n this.log.debug({ pid: this.process.pid, requestId }, \"Codex process spawned\");\n\n if (this.process.stdin) {\n this.process.stdin.write(fullPrompt);\n this.process.stdin.end();\n }\n\n // Guard against double handler invocation (e.g. error + exit both firing)\n let handlersDone = false;\n const finish = (cb: () => void) => {\n if (handlersDone) return;\n handlersDone = true;\n this.clearTimeout();\n cb();\n };\n\n this.timeout = setTimeout(() => {\n this.log.warn({ requestId }, \"Codex process timed out\");\n this.kill();\n finish(() => handlers.onError(\"Process timed out\", requestId));\n }, this.timeoutMs);\n\n if (this.process.stdout) {\n const rl = createInterface({ input: this.process.stdout });\n rl.on(\"line\", (line) => {\n this.parseStreamLine(line, handlers, requestId);\n });\n }\n\n if (this.process.stderr) {\n const stderrRl = createInterface({ input: this.process.stderr });\n stderrRl.on(\"line\", (line) => {\n if (line.trim()) {\n this.log.warn({ requestId, stderr: line }, \"Codex stderr\");\n }\n });\n }\n\n this.process.on(\"exit\", (exitCode, signal) => {\n this.process = null;\n\n if (this.killed) {\n this.log.debug({ requestId }, \"Codex process was killed\");\n return;\n }\n\n if (exitCode === 0) {\n this.log.info({ requestId }, \"Codex process completed successfully\");\n finish(() => handlers.onComplete(requestId));\n } else {\n const reason = exitCode !== null\n ? `Codex CLI exited with code ${exitCode}`\n : `Codex CLI killed by signal ${signal ?? \"unknown\"}`;\n this.log.warn({ requestId, exitCode, signal }, reason);\n finish(() => handlers.onError(reason, requestId));\n }\n });\n\n this.process.on(\"error\", (err) => {\n this.process = null;\n this.log.error({ err, requestId }, \"Codex process error\");\n finish(() => handlers.onError(err.message, requestId));\n });\n }\n\n /**\n * Parse JSONL output from `codex exec --json`.\n *\n * Event format (one JSON object per line):\n * { type: \"thread.started\", thread_id }\n * { type: \"turn.started\" }\n * { type: \"item.started\", item: { id, type, ... } }\n * { type: \"item.completed\", item: { id, type: \"agent_message\", text } }\n * { type: \"item.completed\", item: { id, type: \"command_execution\", command, exit_code } }\n * { type: \"turn.completed\", usage: { input_tokens, output_tokens, ... } }\n */\n private parseStreamLine(line: string, handlers: RunHandlers, requestId: string): void {\n if (!line.trim()) return;\n\n try {\n const event = JSON.parse(line);\n\n // Capture thread_id for session resumption\n if (event.type === \"thread.started\" && event.thread_id) {\n this.threadId = event.thread_id;\n this.log.debug({ threadId: this.threadId, requestId }, \"Captured Codex thread ID\");\n return;\n }\n\n // Agent message \u2014 the actual text content we want to stream\n if (event.type === \"item.completed\" && event.item?.type === \"agent_message\" && event.item.text) {\n handlers.onChunk(event.item.text, requestId);\n return;\n }\n\n // Reasoning trace \u2014 forward as thinking\n if (event.type === \"item.completed\" && event.item?.type === \"reasoning\" && event.item.text) {\n handlers.onChunk(event.item.text, requestId, true);\n return;\n }\n\n // Turn failed \u2014 surface as error\n if (event.type === \"turn.failed\") {\n const msg = event.error?.message || event.message || \"Codex turn failed\";\n handlers.onError(msg, requestId);\n return;\n }\n\n // error event\n if (event.type === \"error\") {\n const msg = event.message || event.error?.message || \"Codex error\";\n handlers.onError(msg, requestId);\n return;\n }\n } catch {\n // Non-JSON line, skip\n }\n }\n\n kill(): void {\n this.clearTimeout();\n if (this.process) {\n this.log.debug({ pid: this.process.pid }, \"Killing Codex process\");\n this.killed = true;\n try {\n this.process.kill();\n } catch {\n // Process may already be dead\n }\n this.process = null;\n }\n }\n\n dispose(): void {\n this.disposed = true;\n this.kill();\n }\n\n private clearTimeout(): void {\n if (this.timeout) {\n clearTimeout(this.timeout);\n this.timeout = null;\n }\n }\n}\n", "// Maximum prompt size: 512KB\nconst MAX_PROMPT_BYTES = 512 * 1024;\n// Maximum system prompt size: 64KB\nconst MAX_SYSTEM_PROMPT_BYTES = 64 * 1024;\n// Maximum projectId length\nconst MAX_PROJECT_ID_LENGTH = 128;\n// Allowed projectId characters: alphanumeric, hyphens, underscores, dots\nconst PROJECT_ID_PATTERN = /^[a-zA-Z0-9._-]+$/;\n\n// --- Client \u2192 Agent messages ---\n\nexport interface PromptMessage {\n type: \"prompt\";\n prompt: string;\n model?: string;\n systemPrompt?: string;\n projectId?: string;\n requestId: string;\n provider?: \"claude\" | \"codex\";\n thinkingTokens?: number;\n}\n\nexport interface CancelMessage {\n type: \"cancel\";\n requestId?: string;\n}\n\nexport type ClientMessage = PromptMessage | CancelMessage;\n\n// --- Agent \u2192 Client messages ---\n\nexport interface ConnectedMessage {\n type: \"connected\";\n version: string;\n agent: string;\n}\n\nexport interface ChunkMessage {\n type: \"chunk\";\n content: string;\n requestId: string;\n thinking?: boolean;\n}\n\nexport interface CompleteMessage {\n type: \"complete\";\n requestId: string;\n}\n\nexport interface ErrorMessage {\n type: \"error\";\n message: string;\n requestId?: string;\n}\n\nexport type AgentMessage =\n | ConnectedMessage\n | ChunkMessage\n | CompleteMessage\n | ErrorMessage;\n\n// --- Parsing & validation ---\n\nexport type ParseResult =\n | { ok: true; message: ClientMessage }\n | { ok: false; error: string };\n\nexport function parseClientMessage(raw: string): ParseResult {\n let data: unknown;\n\n try {\n data = JSON.parse(raw);\n } catch {\n return { ok: false, error: \"Invalid JSON\" };\n }\n\n if (typeof data !== \"object\" || data === null || Array.isArray(data)) {\n return { ok: false, error: \"Message must be a JSON object\" };\n }\n\n const obj = data as Record<string, unknown>;\n const type = obj[\"type\"];\n\n if (typeof type !== \"string\") {\n return { ok: false, error: \"Missing or invalid 'type' field\" };\n }\n\n switch (type) {\n case \"prompt\": {\n const prompt = obj[\"prompt\"];\n if (typeof prompt !== \"string\" || prompt.length === 0) {\n return { ok: false, error: \"Missing or empty 'prompt' field\" };\n }\n\n if (new TextEncoder().encode(prompt).byteLength > MAX_PROMPT_BYTES) {\n return { ok: false, error: `Prompt exceeds maximum size of ${MAX_PROMPT_BYTES} bytes` };\n }\n\n const requestId = obj[\"requestId\"];\n if (typeof requestId !== \"string\" || requestId.length === 0) {\n return { ok: false, error: \"Missing or empty 'requestId' field\" };\n }\n\n const model = obj[\"model\"];\n const systemPrompt = obj[\"systemPrompt\"];\n const projectId = obj[\"projectId\"];\n const provider = obj[\"provider\"];\n const thinkingTokens = obj[\"thinkingTokens\"];\n\n if (typeof systemPrompt === \"string\" && new TextEncoder().encode(systemPrompt).byteLength > MAX_SYSTEM_PROMPT_BYTES) {\n return { ok: false, error: `System prompt exceeds maximum size of ${MAX_SYSTEM_PROMPT_BYTES} bytes` };\n }\n\n if (typeof projectId === \"string\") {\n if (projectId.length > MAX_PROJECT_ID_LENGTH) {\n return { ok: false, error: `projectId exceeds maximum length of ${MAX_PROJECT_ID_LENGTH}` };\n }\n if (!PROJECT_ID_PATTERN.test(projectId)) {\n return { ok: false, error: \"projectId contains invalid characters (allowed: alphanumeric, hyphens, underscores, dots)\" };\n }\n }\n\n return {\n ok: true,\n message: {\n type: \"prompt\",\n prompt,\n model: typeof model === \"string\" ? model : undefined,\n systemPrompt: typeof systemPrompt === \"string\" ? systemPrompt : undefined,\n projectId: typeof projectId === \"string\" ? projectId : undefined,\n requestId,\n provider: provider === \"codex\" ? \"codex\" : \"claude\",\n thinkingTokens: typeof thinkingTokens === \"number\" && thinkingTokens >= 0 ? thinkingTokens : undefined,\n },\n };\n }\n\n case \"cancel\": {\n const requestId = obj[\"requestId\"];\n return {\n ok: true,\n message: {\n type: \"cancel\",\n requestId: typeof requestId === \"string\" ? requestId : undefined,\n },\n };\n }\n\n default:\n return { ok: false, error: `Unknown message type: ${String(type).slice(0, 50)}` };\n }\n}\n\n// --- Serialization ---\n\nexport function serializeMessage(message: AgentMessage): string {\n return JSON.stringify(message);\n}\n", "import pino from \"pino\";\n\nexport interface LoggerOptions {\n level?: string;\n pretty?: boolean;\n}\n\nexport function createLogger(options: LoggerOptions = {}): pino.Logger {\n const { level = \"info\", pretty = process.env[\"NODE_ENV\"] !== \"production\" } = options;\n\n if (pretty) {\n try {\n return pino({\n level,\n transport: {\n target: \"pino-pretty\",\n options: {\n colorize: true,\n translateTime: \"HH:MM:ss\",\n ignore: \"pid,hostname\",\n },\n },\n });\n } catch {\n // pino-pretty not available, fall back to plain JSON logging\n }\n }\n\n return pino({ level });\n}\n\nexport type Logger = pino.Logger;\n", "import { AgentWebSocketServer, type AgentWebSocketServerOptions, type RunnerFactory } from \"./server/websocket.js\";\nimport { createLogger, type Logger } from \"./utils/logger.js\";\n\nexport interface AgentWSOptions {\n port?: number;\n host?: string;\n claudePath?: string;\n codexPath?: string;\n timeoutMs?: number;\n logLevel?: string;\n allowedOrigins?: string[];\n runnerFactory?: RunnerFactory;\n agentName?: string;\n sessionDir?: string;\n}\n\nexport class AgentWS {\n private server: AgentWebSocketServer;\n private readonly log: Logger;\n\n constructor(options: AgentWSOptions = {}) {\n this.log = createLogger({ level: options.logLevel ?? \"info\" });\n\n const serverOptions: AgentWebSocketServerOptions = {\n port: options.port ?? 9999,\n host: options.host ?? \"localhost\",\n logger: this.log,\n claudePath: options.claudePath,\n codexPath: options.codexPath,\n timeoutMs: options.timeoutMs,\n allowedOrigins: options.allowedOrigins,\n runnerFactory: options.runnerFactory,\n agentName: options.agentName,\n sessionDir: options.sessionDir,\n };\n\n this.server = new AgentWebSocketServer(serverOptions);\n }\n\n async start(): Promise<void> {\n await this.server.start();\n }\n\n stop(): void {\n this.server.stop();\n }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { WebSocketServer, WebSocket } from \"ws\";\nimport type { IncomingMessage } from \"node:http\";\nimport { ClaudeRunner, type ClaudeRunnerOptions, type Runner, type RunHandlers } from \"../process/claude-runner.js\";\nimport { CodexRunner } from \"../process/codex-runner.js\";\nimport {\n parseClientMessage,\n serializeMessage,\n type AgentMessage,\n type PromptMessage,\n} from \"./protocol.js\";\nimport type { Logger } from \"../utils/logger.js\";\n\nconst HEARTBEAT_INTERVAL_MS = 30_000;\nconst DEFAULT_MAX_PAYLOAD = 50 * 1024 * 1024; // 50MB (images can be large)\n\nexport type RunnerFactory = (log: Logger) => Runner;\n\ninterface ConnectionState {\n claudeRunner: Runner | null;\n codexRunner: Runner | null;\n activeRunner: Runner | null;\n isAlive: boolean;\n activeRequestId: string | null;\n}\n\nexport interface AgentWebSocketServerOptions {\n port: number;\n host: string;\n logger: Logger;\n claudePath?: string;\n codexPath?: string;\n timeoutMs?: number;\n allowedOrigins?: string[];\n maxPayload?: number;\n /** @deprecated Use claudeRunnerFactory instead */\n runnerFactory?: RunnerFactory;\n claudeRunnerFactory?: RunnerFactory;\n codexRunnerFactory?: RunnerFactory;\n agentName?: string;\n sessionDir?: string;\n}\n\nexport class AgentWebSocketServer {\n private wss: WebSocketServer | null = null;\n private heartbeatInterval: NodeJS.Timeout | null = null;\n private readonly connections = new Map<WebSocket, ConnectionState>();\n private readonly log: Logger;\n private readonly options: AgentWebSocketServerOptions;\n\n constructor(options: AgentWebSocketServerOptions) {\n this.options = options;\n this.log = options.logger;\n }\n\n start(): Promise<void> {\n return new Promise((resolve, reject) => {\n this.wss = new WebSocketServer({\n port: this.options.port,\n host: this.options.host,\n maxPayload: this.options.maxPayload ?? DEFAULT_MAX_PAYLOAD,\n });\n\n this.wss.on(\"listening\", () => {\n this.log.info({ port: this.options.port, host: this.options.host }, \"WebSocket server started\");\n this.startHeartbeat();\n resolve();\n });\n\n this.wss.on(\"error\", (err: NodeJS.ErrnoException) => {\n if (err.code === \"EADDRINUSE\") {\n this.log.fatal({ port: this.options.port }, \"Port already in use\");\n } else {\n this.log.error({ err }, \"WebSocket server error\");\n }\n reject(err);\n });\n\n this.wss.on(\"connection\", (ws, req) => this.handleConnection(ws, req));\n });\n }\n\n stop(): void {\n if (this.heartbeatInterval) {\n clearInterval(this.heartbeatInterval);\n this.heartbeatInterval = null;\n }\n\n for (const [ws, state] of this.connections) {\n state.claudeRunner?.dispose();\n state.codexRunner?.dispose();\n ws.terminate();\n }\n this.connections.clear();\n\n if (this.wss) {\n this.wss.close();\n this.wss = null;\n }\n\n this.log.info(\"WebSocket server stopped\");\n }\n\n private handleConnection(ws: WebSocket, req: IncomingMessage): void {\n // Origin check\n if (this.options.allowedOrigins && this.options.allowedOrigins.length > 0) {\n const origin = req.headers.origin;\n if (!origin || !this.options.allowedOrigins.includes(origin)) {\n this.log.warn({ origin: origin ?? \"(none)\" }, \"Rejected connection: origin not in allowlist\");\n ws.close(4003, \"Origin not allowed\");\n return;\n }\n }\n\n const clientIp = req.socket.remoteAddress;\n this.log.info({ clientIp }, \"Client connected\");\n\n const state: ConnectionState = { claudeRunner: null, codexRunner: null, activeRunner: null, isAlive: true, activeRequestId: null };\n this.connections.set(ws, state);\n\n // Send connected message\n this.sendMessage(ws, {\n type: \"connected\",\n version: \"1.0\",\n agent: this.options.agentName ?? \"agent-ws\",\n });\n\n ws.on(\"pong\", () => {\n state.isAlive = true;\n });\n\n ws.on(\"message\", (data) => {\n const raw = data.toString();\n this.handleMessage(ws, state, raw);\n });\n\n ws.on(\"close\", () => {\n this.log.info({ clientIp }, \"Client disconnected\");\n state.claudeRunner?.dispose();\n state.codexRunner?.dispose();\n this.connections.delete(ws);\n });\n\n ws.on(\"error\", (err) => {\n this.log.error({ err, clientIp }, \"WebSocket error\");\n });\n }\n\n private handleMessage(ws: WebSocket, state: ConnectionState, raw: string): void {\n const result = parseClientMessage(raw);\n\n if (!result.ok) {\n this.sendMessage(ws, { type: \"error\", message: result.error });\n return;\n }\n\n const { message } = result;\n\n switch (message.type) {\n case \"prompt\":\n this.handlePrompt(ws, state, message);\n break;\n case \"cancel\":\n this.handleCancel(ws, state);\n break;\n }\n }\n\n private handlePrompt(ws: WebSocket, state: ConnectionState, message: PromptMessage): void {\n if (state.activeRequestId !== null) {\n this.sendMessage(ws, {\n type: \"error\",\n message: \"Request already in progress\",\n requestId: message.requestId,\n });\n return;\n }\n\n state.activeRequestId = message.requestId;\n\n // Select runner for the requested provider (lazy-created, preserved across switches)\n if (message.provider === \"codex\") {\n if (!state.codexRunner) {\n state.codexRunner = this.createCodexRunner();\n }\n state.activeRunner = state.codexRunner;\n } else {\n if (!state.claudeRunner) {\n state.claudeRunner = this.createClaudeRunner();\n }\n state.activeRunner = state.claudeRunner;\n }\n\n const handlers: RunHandlers = {\n onChunk: (content, requestId, thinking) => {\n try {\n this.sendMessage(ws, { type: \"chunk\", content, requestId, ...(thinking ? { thinking: true } : {}) });\n } catch (err) {\n this.log.warn({ err, requestId }, \"Error in onChunk handler\");\n }\n },\n onComplete: (requestId) => {\n try {\n state.activeRequestId = null;\n this.sendMessage(ws, { type: \"complete\", requestId });\n } catch (err) {\n this.log.warn({ err, requestId }, \"Error in onComplete handler\");\n }\n },\n onError: (errorMessage, requestId) => {\n try {\n state.activeRequestId = null;\n this.sendMessage(ws, { type: \"error\", message: errorMessage, requestId });\n } catch (err) {\n this.log.warn({ err, requestId }, \"Error in onError handler\");\n }\n },\n };\n\n state.activeRunner!.run(\n {\n prompt: message.prompt,\n model: message.model,\n systemPrompt: message.systemPrompt,\n projectId: message.projectId,\n requestId: message.requestId,\n thinkingTokens: message.thinkingTokens,\n images: message.images,\n },\n handlers,\n );\n }\n\n private handleCancel(ws: WebSocket, state: ConnectionState): void {\n const requestId = state.activeRequestId;\n state.activeRunner?.kill();\n state.activeRequestId = null;\n\n if (requestId) {\n this.sendMessage(ws, { type: \"error\", message: \"Request cancelled\", requestId });\n }\n this.log.info({ requestId }, \"Request cancelled\");\n }\n\n private sendMessage(ws: WebSocket, message: AgentMessage): void {\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(serializeMessage(message));\n } else {\n this.log.warn({ messageType: message.type, readyState: ws.readyState }, \"Dropping message, WebSocket not OPEN\");\n }\n }\n\n private createClaudeRunner(): Runner {\n const factory = this.options.claudeRunnerFactory ?? this.options.runnerFactory;\n if (factory) {\n return factory(this.log);\n }\n\n return new ClaudeRunner({\n claudePath: this.options.claudePath,\n timeoutMs: this.options.timeoutMs,\n logger: this.log.child({ component: \"claude-runner\" }),\n sessionDir: this.options.sessionDir,\n });\n }\n\n private createCodexRunner(): Runner {\n if (this.options.codexRunnerFactory) {\n return this.options.codexRunnerFactory(this.log);\n }\n\n return new CodexRunner({\n codexPath: this.options.codexPath,\n timeoutMs: this.options.timeoutMs,\n logger: this.log.child({ component: \"codex-runner\" }),\n sessionDir: this.options.sessionDir,\n });\n }\n\n private startHeartbeat(): void {\n this.heartbeatInterval = setInterval(() => {\n for (const [ws, state] of this.connections) {\n if (!state.isAlive) {\n this.log.debug(\"Terminating dead connection\");\n state.claudeRunner?.dispose();\n state.codexRunner?.dispose();\n this.connections.delete(ws);\n ws.terminate();\n continue;\n }\n\n state.isAlive = false;\n try {\n ws.ping();\n } catch {\n this.log.debug(\"Ping failed, terminating connection\");\n state.claudeRunner?.dispose();\n state.codexRunner?.dispose();\n this.connections.delete(ws);\n ws.terminate();\n }\n }\n }, HEARTBEAT_INTERVAL_MS);\n }\n}\n", "import { spawn, type ChildProcess } from \"node:child_process\";\nimport { mkdirSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { tmpdir } from \"node:os\";\nimport { createInterface } from \"node:readline\";\nimport type { Logger } from \"../utils/logger.js\";\nimport type { PromptImage } from \"../server/protocol.js\";\n\nexport interface RunOptions {\n prompt: string;\n model?: string;\n systemPrompt?: string;\n projectId?: string;\n requestId: string;\n thinkingTokens?: number;\n images?: PromptImage[];\n}\n\nexport interface RunHandlers {\n onChunk: (content: string, requestId: string, thinking?: boolean) => void;\n onComplete: (requestId: string) => void;\n onError: (message: string, requestId: string) => void;\n}\n\n/** Interface for runner injection (testing) */\nexport interface Runner {\n run(options: RunOptions, handlers: RunHandlers): void;\n kill(): void;\n dispose(): void;\n}\n\nexport interface ClaudeRunnerOptions {\n claudePath?: string;\n timeoutMs?: number;\n logger: Logger;\n sessionDir?: string;\n}\n\nconst DEFAULT_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes\n\nexport class ClaudeRunner implements Runner {\n private process: ChildProcess | null = null;\n private timeout: NodeJS.Timeout | null = null;\n private disposed = false;\n private killed = false;\n private readonly claudePath: string;\n private readonly timeoutMs: number;\n private readonly log: Logger;\n private readonly sessionDir: string;\n\n constructor(options: ClaudeRunnerOptions) {\n this.claudePath = options.claudePath ?? \"claude\";\n this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.log = options.logger;\n this.sessionDir = options.sessionDir ?? \"agent-ws-sessions\";\n }\n\n get isRunning(): boolean {\n return this.process !== null;\n }\n\n run(options: RunOptions, handlers: RunHandlers): void {\n if (this.disposed) {\n handlers.onError(\"Runner has been disposed\", options.requestId);\n return;\n }\n\n // Kill any existing process first\n this.kill();\n\n const { prompt, model, systemPrompt, projectId, requestId, thinkingTokens, images } = options;\n const hasImages = images && images.length > 0;\n\n const args = [\n \"--print\",\n \"--verbose\",\n \"--output-format\", \"stream-json\",\n \"--max-turns\", \"1\", // Single-turn text output, no agentic loops\n \"--tools\", \"\", // Disable tool use \u2014 we only want generated text\n ];\n // Use stream-json input when images are present (supports content blocks)\n if (hasImages) {\n args.push(\"--input-format\", \"stream-json\");\n }\n // Only resume session when a projectId is provided (scoped by CWD)\n if (projectId) {\n args.push(\"--continue\");\n }\n if (model) {\n args.push(\"--model\", model);\n }\n if (systemPrompt) {\n args.push(\"--append-system-prompt\", systemPrompt);\n }\n // Prompt is piped via stdin (no arg length limits, no flag-parsing issues)\n args.push(\"-\");\n\n this.log.info({ requestId, model, promptLength: prompt.length }, \"Spawning Claude process\");\n this.killed = false;\n\n // Use project-scoped CWD so --continue resumes the correct session\n // (Claude CLI scopes sessions by working directory)\n let cwd: string | undefined;\n if (projectId) {\n const base = resolve(tmpdir(), this.sessionDir);\n cwd = resolve(base, projectId);\n if (!cwd.startsWith(base + \"/\") && cwd !== base) {\n handlers.onError(\"Invalid projectId\", requestId);\n return;\n }\n mkdirSync(cwd, { recursive: true });\n }\n\n try {\n const ALLOWED_ENV_KEYS = [\n \"PATH\", \"HOME\", \"USER\", \"SHELL\", \"TERM\", \"LANG\", \"LC_ALL\",\n \"ANTHROPIC_API_KEY\", \"NODE_PATH\", \"XDG_CONFIG_HOME\",\n ];\n const env: Record<string, string> = {};\n if (thinkingTokens !== undefined) {\n env[\"MAX_THINKING_TOKENS\"] = String(thinkingTokens);\n }\n for (const key of ALLOWED_ENV_KEYS) {\n if (process.env[key]) env[key] = process.env[key]!;\n }\n\n this.process = spawn(this.claudePath, args, {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n cwd,\n env,\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : \"Failed to start Claude\";\n this.log.error({ err, requestId }, \"Failed to spawn Claude process\");\n handlers.onError(message, requestId);\n return;\n }\n\n this.log.debug({ pid: this.process.pid, requestId }, \"Claude process spawned\");\n\n // Write prompt to stdin and close it\n if (this.process.stdin) {\n if (hasImages) {\n // stream-json input: structured message with image content blocks\n const content: Array<Record<string, unknown>> = [];\n for (const img of images!) {\n content.push({\n type: \"image\",\n source: { type: \"base64\", media_type: img.media_type, data: img.data },\n });\n }\n content.push({ type: \"text\", text: prompt });\n const msg = JSON.stringify({ type: \"user\", message: { role: \"user\", content } });\n this.process.stdin.write(msg + \"\\n\");\n } else {\n this.process.stdin.write(prompt);\n }\n this.process.stdin.end();\n }\n\n // Guard against double handler invocation (e.g. error + exit both firing)\n let handlersDone = false;\n const finish = (cb: () => void) => {\n if (handlersDone) return;\n handlersDone = true;\n this.clearTimeout();\n cb();\n };\n\n // Set up timeout\n this.timeout = setTimeout(() => {\n this.log.warn({ requestId }, \"Claude process timed out\");\n this.kill();\n finish(() => handlers.onError(\"Process timed out\", requestId));\n }, this.timeoutMs);\n\n // Parse NDJSON from stdout\n if (this.process.stdout) {\n const rl = createInterface({ input: this.process.stdout });\n rl.on(\"line\", (line) => {\n this.parseStreamLine(line, handlers, requestId);\n });\n }\n\n // Capture stderr \u2014 logs at warn level so errors are visible\n if (this.process.stderr) {\n const stderrRl = createInterface({ input: this.process.stderr });\n stderrRl.on(\"line\", (line) => {\n if (line.trim()) {\n this.log.warn({ requestId, stderr: line }, \"Claude stderr\");\n }\n });\n }\n\n // Handle exit\n this.process.on(\"exit\", (exitCode, signal) => {\n this.process = null;\n\n if (this.killed) {\n this.log.debug({ requestId }, \"Claude process was killed\");\n return;\n }\n\n if (exitCode === 0) {\n this.log.info({ requestId }, \"Claude process completed successfully\");\n finish(() => handlers.onComplete(requestId));\n } else {\n const reason = exitCode !== null\n ? `Claude CLI exited with code ${exitCode}`\n : `Claude CLI killed by signal ${signal ?? \"unknown\"}`;\n this.log.warn({ requestId, exitCode, signal }, reason);\n finish(() => handlers.onError(reason, requestId));\n }\n });\n\n this.process.on(\"error\", (err) => {\n this.process = null;\n this.log.error({ err, requestId }, \"Claude process error\");\n finish(() => handlers.onError(err.message, requestId));\n });\n }\n\n /**\n * Parse a single NDJSON line from Claude CLI's stream-json output.\n *\n * The stream-json format can emit several event types. We look for content\n * in these known patterns (in priority order):\n *\n * 1. Raw Anthropic API event: { type: \"content_block_delta\", delta: { type: \"text_delta\"|\"thinking_delta\", text|thinking } }\n * 2. Wrapped stream event: { type: \"stream_event\", event: { type: \"content_block_delta\", ... } }\n * 3. Complete assistant msg: { type: \"assistant\", message: { content: [{ type: \"text\"|\"thinking\", text|thinking }] } }\n */\n private parseStreamLine(line: string, handlers: RunHandlers, requestId: string): void {\n if (!line.trim()) return;\n\n try {\n const event = JSON.parse(line);\n\n // Pattern 1: Raw content_block_delta\n if (event.type === \"content_block_delta\") {\n if (event.delta?.type === \"text_delta\" && event.delta.text) {\n handlers.onChunk(event.delta.text, requestId);\n } else if (event.delta?.type === \"thinking_delta\" && event.delta.thinking) {\n handlers.onChunk(event.delta.thinking, requestId, true);\n }\n return;\n }\n\n // Pattern 2: Wrapped in stream_event\n if (event.type === \"stream_event\" && event.event) {\n const inner = event.event;\n if (inner.type === \"content_block_delta\") {\n if (inner.delta?.type === \"text_delta\" && inner.delta.text) {\n handlers.onChunk(inner.delta.text, requestId);\n } else if (inner.delta?.type === \"thinking_delta\" && inner.delta.thinking) {\n handlers.onChunk(inner.delta.thinking, requestId, true);\n }\n }\n return;\n }\n\n // Pattern 3: Complete assistant message\n if (event.type === \"assistant\" && Array.isArray(event.message?.content)) {\n for (const block of event.message.content) {\n if (block.type === \"text\" && block.text) {\n handlers.onChunk(block.text, requestId);\n } else if (block.type === \"thinking\" && block.thinking) {\n handlers.onChunk(block.thinking, requestId, true);\n }\n }\n return;\n }\n\n // Result event \u2014 ignore (we already streamed the content)\n if (event.type === \"result\") {\n return;\n }\n } catch {\n // Non-JSON line, skip\n }\n }\n\n kill(): void {\n this.clearTimeout();\n if (this.process) {\n this.log.debug({ pid: this.process.pid }, \"Killing Claude process\");\n this.killed = true;\n try {\n this.process.kill();\n } catch {\n // Process may already be dead\n }\n this.process = null;\n }\n }\n\n dispose(): void {\n this.disposed = true;\n this.kill();\n }\n\n private clearTimeout(): void {\n if (this.timeout) {\n clearTimeout(this.timeout);\n this.timeout = null;\n }\n }\n}\n", "import { spawn, type ChildProcess } from \"node:child_process\";\nimport { mkdirSync, writeFileSync, unlinkSync } from \"node:fs\";\nimport { resolve, join } from \"node:path\";\nimport { tmpdir } from \"node:os\";\nimport { createInterface } from \"node:readline\";\nimport type { Logger } from \"../utils/logger.js\";\nimport type { Runner, RunOptions, RunHandlers } from \"./claude-runner.js\";\n\nexport interface CodexRunnerOptions {\n codexPath?: string;\n timeoutMs?: number;\n logger: Logger;\n sessionDir?: string;\n}\n\nconst DEFAULT_TIMEOUT_MS = 5 * 60 * 1000;\n\nexport class CodexRunner implements Runner {\n private process: ChildProcess | null = null;\n private timeout: NodeJS.Timeout | null = null;\n private disposed = false;\n private killed = false;\n private threadId: string | null = null;\n private readonly codexPath: string;\n private readonly timeoutMs: number;\n private readonly log: Logger;\n private readonly sessionDir: string;\n\n constructor(options: CodexRunnerOptions) {\n this.codexPath = options.codexPath ?? \"codex\";\n this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.log = options.logger;\n this.sessionDir = options.sessionDir ?? \"agent-ws-sessions\";\n }\n\n get isRunning(): boolean {\n return this.process !== null;\n }\n\n run(options: RunOptions, handlers: RunHandlers): void {\n if (this.disposed) {\n handlers.onError(\"Runner has been disposed\", options.requestId);\n return;\n }\n\n this.kill();\n\n // Note: thinkingTokens is intentionally not used \u2014 Codex does not support thinking tokens\n const { prompt, model, systemPrompt, projectId, requestId, images } = options;\n\n // Build the full prompt: prepend system prompt since Codex doesn't have\n // a dedicated --append-system-prompt flag\n let fullPrompt = prompt;\n if (systemPrompt) {\n fullPrompt = `${systemPrompt}\\n\\n---\\n\\n${prompt}`;\n }\n\n // Write images to temp files for the -i flag\n const imagePaths: string[] = [];\n if (images && images.length > 0) {\n const imgDir = resolve(tmpdir(), \"agent-ws-images\");\n mkdirSync(imgDir, { recursive: true });\n // Sanitize requestId for safe use in filenames (strip anything that isn't alphanumeric/hyphen/underscore)\n const safeId = requestId.replace(/[^a-zA-Z0-9_-]/g, \"_\").slice(0, 64);\n for (let i = 0; i < images.length; i++) {\n const img = images[i]!;\n const rawExt = img.media_type.split(\"/\")[1] || \"png\";\n const ext = rawExt.replace(/[^a-zA-Z0-9]/g, \"\").slice(0, 10) || \"png\";\n const imgPath = join(imgDir, `${safeId}-${i}.${ext}`);\n writeFileSync(imgPath, Buffer.from(img.data, \"base64\"));\n imagePaths.push(imgPath);\n }\n }\n\n // Resume existing thread if we have one and a projectId is set (session scoping)\n const resuming = projectId && this.threadId;\n const args: string[] = resuming\n ? [\"exec\", \"resume\", this.threadId!, \"--json\", \"--full-auto\", \"--skip-git-repo-check\"]\n : [\"exec\", \"--json\", \"--full-auto\", \"--skip-git-repo-check\"];\n if (model && !resuming) {\n args.push(\"--model\", model);\n }\n // Attach images via -i flags\n for (const imgPath of imagePaths) {\n args.push(\"-i\", imgPath);\n }\n // Read prompt from stdin\n args.push(\"-\");\n\n this.log.info({ requestId, model, promptLength: prompt.length }, \"Spawning Codex process\");\n this.killed = false;\n\n let cwd: string | undefined;\n if (projectId) {\n const base = resolve(tmpdir(), this.sessionDir);\n cwd = resolve(base, projectId);\n if (!cwd.startsWith(base + \"/\") && cwd !== base) {\n handlers.onError(\"Invalid projectId\", requestId);\n return;\n }\n mkdirSync(cwd, { recursive: true });\n }\n\n const cleanupImages = () => {\n for (const p of imagePaths) {\n try { unlinkSync(p); } catch { /* already cleaned */ }\n }\n };\n\n const ALLOWED_ENV_KEYS = [\n \"PATH\", \"HOME\", \"USER\", \"SHELL\", \"TERM\", \"LANG\", \"LC_ALL\",\n \"OPENAI_API_KEY\", \"NODE_PATH\", \"XDG_CONFIG_HOME\",\n ];\n const env: Record<string, string> = {};\n for (const key of ALLOWED_ENV_KEYS) {\n if (process.env[key]) env[key] = process.env[key]!;\n }\n\n try {\n this.process = spawn(this.codexPath, args, {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n cwd,\n env,\n });\n } catch (err) {\n cleanupImages();\n const message = err instanceof Error ? err.message : \"Failed to start Codex\";\n this.log.error({ err, requestId }, \"Failed to spawn Codex process\");\n handlers.onError(message, requestId);\n return;\n }\n\n this.log.debug({ pid: this.process.pid, requestId }, \"Codex process spawned\");\n\n if (this.process.stdin) {\n this.process.stdin.write(fullPrompt);\n this.process.stdin.end();\n }\n\n // Guard against double handler invocation (e.g. error + exit both firing)\n let handlersDone = false;\n const finish = (cb: () => void) => {\n if (handlersDone) return;\n handlersDone = true;\n this.clearTimeout();\n cb();\n };\n\n this.timeout = setTimeout(() => {\n this.log.warn({ requestId }, \"Codex process timed out\");\n this.kill();\n finish(() => handlers.onError(\"Process timed out\", requestId));\n }, this.timeoutMs);\n\n if (this.process.stdout) {\n const rl = createInterface({ input: this.process.stdout });\n rl.on(\"line\", (line) => {\n this.parseStreamLine(line, handlers, requestId);\n });\n }\n\n if (this.process.stderr) {\n const stderrRl = createInterface({ input: this.process.stderr });\n stderrRl.on(\"line\", (line) => {\n if (line.trim()) {\n this.log.warn({ requestId, stderr: line }, \"Codex stderr\");\n }\n });\n }\n\n this.process.on(\"exit\", (exitCode, signal) => {\n this.process = null;\n cleanupImages();\n\n if (this.killed) {\n this.log.debug({ requestId }, \"Codex process was killed\");\n return;\n }\n\n if (exitCode === 0) {\n this.log.info({ requestId }, \"Codex process completed successfully\");\n finish(() => handlers.onComplete(requestId));\n } else {\n const reason = exitCode !== null\n ? `Codex CLI exited with code ${exitCode}`\n : `Codex CLI killed by signal ${signal ?? \"unknown\"}`;\n this.log.warn({ requestId, exitCode, signal }, reason);\n finish(() => handlers.onError(reason, requestId));\n }\n });\n\n this.process.on(\"error\", (err) => {\n this.process = null;\n cleanupImages();\n this.log.error({ err, requestId }, \"Codex process error\");\n finish(() => handlers.onError(err.message, requestId));\n });\n }\n\n /**\n * Parse JSONL output from `codex exec --json`.\n *\n * Event format (one JSON object per line):\n * { type: \"thread.started\", thread_id }\n * { type: \"turn.started\" }\n * { type: \"item.started\", item: { id, type, ... } }\n * { type: \"item.completed\", item: { id, type: \"agent_message\", text } }\n * { type: \"item.completed\", item: { id, type: \"command_execution\", command, exit_code } }\n * { type: \"turn.completed\", usage: { input_tokens, output_tokens, ... } }\n */\n private parseStreamLine(line: string, handlers: RunHandlers, requestId: string): void {\n if (!line.trim()) return;\n\n try {\n const event = JSON.parse(line);\n\n // Capture thread_id for session resumption\n if (event.type === \"thread.started\" && event.thread_id) {\n this.threadId = event.thread_id;\n this.log.debug({ threadId: this.threadId, requestId }, \"Captured Codex thread ID\");\n return;\n }\n\n // Agent message \u2014 the actual text content we want to stream\n if (event.type === \"item.completed\" && event.item?.type === \"agent_message\" && event.item.text) {\n handlers.onChunk(event.item.text, requestId);\n return;\n }\n\n // Reasoning trace \u2014 forward as thinking\n if (event.type === \"item.completed\" && event.item?.type === \"reasoning\" && event.item.text) {\n handlers.onChunk(event.item.text, requestId, true);\n return;\n }\n\n // Turn failed \u2014 surface as error\n if (event.type === \"turn.failed\") {\n const msg = event.error?.message || event.message || \"Codex turn failed\";\n handlers.onError(msg, requestId);\n return;\n }\n\n // error event\n if (event.type === \"error\") {\n const msg = event.message || event.error?.message || \"Codex error\";\n handlers.onError(msg, requestId);\n return;\n }\n } catch {\n // Non-JSON line, skip\n }\n }\n\n kill(): void {\n this.clearTimeout();\n if (this.process) {\n this.log.debug({ pid: this.process.pid }, \"Killing Codex process\");\n this.killed = true;\n try {\n this.process.kill();\n } catch {\n // Process may already be dead\n }\n this.process = null;\n }\n }\n\n dispose(): void {\n this.disposed = true;\n this.kill();\n }\n\n private clearTimeout(): void {\n if (this.timeout) {\n clearTimeout(this.timeout);\n this.timeout = null;\n }\n }\n}\n", "// Maximum prompt size: 512KB\nconst MAX_PROMPT_BYTES = 512 * 1024;\n// Maximum system prompt size: 64KB\nconst MAX_SYSTEM_PROMPT_BYTES = 64 * 1024;\n// Maximum images per message\nconst MAX_IMAGES = 4;\n// Maximum single image size: 10MB base64\nconst MAX_IMAGE_BASE64_BYTES = 10 * 1024 * 1024;\n// Allowed image MIME types\nconst ALLOWED_IMAGE_TYPES = new Set([\n \"image/png\", \"image/jpeg\", \"image/gif\", \"image/webp\",\n]);\n// Maximum projectId length\nconst MAX_PROJECT_ID_LENGTH = 128;\n// Allowed projectId characters: alphanumeric, hyphens, underscores, dots\nconst PROJECT_ID_PATTERN = /^[a-zA-Z0-9._-]+$/;\n\n// --- Client \u2192 Agent messages ---\n\nexport interface PromptImage {\n media_type: string;\n data: string; // base64-encoded\n}\n\nexport interface PromptMessage {\n type: \"prompt\";\n prompt: string;\n model?: string;\n systemPrompt?: string;\n projectId?: string;\n requestId: string;\n provider?: \"claude\" | \"codex\";\n thinkingTokens?: number;\n images?: PromptImage[];\n}\n\nexport interface CancelMessage {\n type: \"cancel\";\n requestId?: string;\n}\n\nexport type ClientMessage = PromptMessage | CancelMessage;\n\n// --- Agent \u2192 Client messages ---\n\nexport interface ConnectedMessage {\n type: \"connected\";\n version: string;\n agent: string;\n}\n\nexport interface ChunkMessage {\n type: \"chunk\";\n content: string;\n requestId: string;\n thinking?: boolean;\n}\n\nexport interface CompleteMessage {\n type: \"complete\";\n requestId: string;\n}\n\nexport interface ErrorMessage {\n type: \"error\";\n message: string;\n requestId?: string;\n}\n\nexport type AgentMessage =\n | ConnectedMessage\n | ChunkMessage\n | CompleteMessage\n | ErrorMessage;\n\n// --- Parsing & validation ---\n\nexport type ParseResult =\n | { ok: true; message: ClientMessage }\n | { ok: false; error: string };\n\nexport function parseClientMessage(raw: string): ParseResult {\n let data: unknown;\n\n try {\n data = JSON.parse(raw);\n } catch {\n return { ok: false, error: \"Invalid JSON\" };\n }\n\n if (typeof data !== \"object\" || data === null || Array.isArray(data)) {\n return { ok: false, error: \"Message must be a JSON object\" };\n }\n\n const obj = data as Record<string, unknown>;\n const type = obj[\"type\"];\n\n if (typeof type !== \"string\") {\n return { ok: false, error: \"Missing or invalid 'type' field\" };\n }\n\n switch (type) {\n case \"prompt\": {\n const prompt = obj[\"prompt\"];\n if (typeof prompt !== \"string\" || prompt.length === 0) {\n return { ok: false, error: \"Missing or empty 'prompt' field\" };\n }\n\n if (new TextEncoder().encode(prompt).byteLength > MAX_PROMPT_BYTES) {\n return { ok: false, error: `Prompt exceeds maximum size of ${MAX_PROMPT_BYTES} bytes` };\n }\n\n const requestId = obj[\"requestId\"];\n if (typeof requestId !== \"string\" || requestId.length === 0) {\n return { ok: false, error: \"Missing or empty 'requestId' field\" };\n }\n\n const model = obj[\"model\"];\n const systemPrompt = obj[\"systemPrompt\"];\n const projectId = obj[\"projectId\"];\n const provider = obj[\"provider\"];\n const thinkingTokens = obj[\"thinkingTokens\"];\n\n if (typeof systemPrompt === \"string\" && new TextEncoder().encode(systemPrompt).byteLength > MAX_SYSTEM_PROMPT_BYTES) {\n return { ok: false, error: `System prompt exceeds maximum size of ${MAX_SYSTEM_PROMPT_BYTES} bytes` };\n }\n\n if (typeof projectId === \"string\") {\n if (projectId.length > MAX_PROJECT_ID_LENGTH) {\n return { ok: false, error: `projectId exceeds maximum length of ${MAX_PROJECT_ID_LENGTH}` };\n }\n if (!PROJECT_ID_PATTERN.test(projectId)) {\n return { ok: false, error: \"projectId contains invalid characters (allowed: alphanumeric, hyphens, underscores, dots)\" };\n }\n }\n\n // Parse images (optional array of { media_type, data })\n let parsedImages: PromptImage[] | undefined;\n const rawImages = obj[\"images\"];\n if (Array.isArray(rawImages) && rawImages.length > 0) {\n if (rawImages.length > MAX_IMAGES) {\n return { ok: false, error: `Too many images (max ${MAX_IMAGES})` };\n }\n parsedImages = [];\n for (const img of rawImages) {\n if (typeof img !== \"object\" || img === null) {\n return { ok: false, error: \"Each image must be an object with media_type and data\" };\n }\n const imgObj = img as Record<string, unknown>;\n if (typeof imgObj[\"media_type\"] !== \"string\" || typeof imgObj[\"data\"] !== \"string\") {\n return { ok: false, error: \"Each image must have string media_type and data fields\" };\n }\n if (!ALLOWED_IMAGE_TYPES.has(imgObj[\"media_type\"])) {\n return { ok: false, error: `Unsupported image type: ${String(imgObj[\"media_type\"]).slice(0, 50)} (allowed: png, jpeg, gif, webp)` };\n }\n if (imgObj[\"data\"].length > MAX_IMAGE_BASE64_BYTES) {\n return { ok: false, error: `Image exceeds maximum size of ${MAX_IMAGE_BASE64_BYTES} bytes` };\n }\n parsedImages.push({ media_type: imgObj[\"media_type\"], data: imgObj[\"data\"] });\n }\n }\n\n return {\n ok: true,\n message: {\n type: \"prompt\",\n prompt,\n model: typeof model === \"string\" ? model : undefined,\n systemPrompt: typeof systemPrompt === \"string\" ? systemPrompt : undefined,\n projectId: typeof projectId === \"string\" ? projectId : undefined,\n requestId,\n provider: provider === \"codex\" ? \"codex\" : \"claude\",\n thinkingTokens: typeof thinkingTokens === \"number\" && thinkingTokens >= 0 ? thinkingTokens : undefined,\n images: parsedImages,\n },\n };\n }\n\n case \"cancel\": {\n const requestId = obj[\"requestId\"];\n return {\n ok: true,\n message: {\n type: \"cancel\",\n requestId: typeof requestId === \"string\" ? requestId : undefined,\n },\n };\n }\n\n default:\n return { ok: false, error: `Unknown message type: ${String(type).slice(0, 50)}` };\n }\n}\n\n// --- Serialization ---\n\nexport function serializeMessage(message: AgentMessage): string {\n return JSON.stringify(message);\n}\n", "import pino from \"pino\";\n\nexport interface LoggerOptions {\n level?: string;\n pretty?: boolean;\n}\n\nexport function createLogger(options: LoggerOptions = {}): pino.Logger {\n const { level = \"info\", pretty = process.env[\"NODE_ENV\"] !== \"production\" } = options;\n\n if (pretty) {\n try {\n return pino({\n level,\n transport: {\n target: \"pino-pretty\",\n options: {\n colorize: true,\n translateTime: \"HH:MM:ss\",\n ignore: \"pid,hostname\",\n },\n },\n });\n } catch {\n // pino-pretty not available, fall back to plain JSON logging\n }\n }\n\n return pino({ level });\n}\n\nexport type Logger = pino.Logger;\n", "import { AgentWebSocketServer, type AgentWebSocketServerOptions, type RunnerFactory } from \"./server/websocket.js\";\nimport { createLogger, type Logger } from \"./utils/logger.js\";\n\nexport interface AgentWSOptions {\n port?: number;\n host?: string;\n claudePath?: string;\n codexPath?: string;\n timeoutMs?: number;\n logLevel?: string;\n allowedOrigins?: string[];\n /** @deprecated Use claudeRunnerFactory instead */\n runnerFactory?: RunnerFactory;\n claudeRunnerFactory?: RunnerFactory;\n codexRunnerFactory?: RunnerFactory;\n agentName?: string;\n sessionDir?: string;\n}\n\nexport class AgentWS {\n private server: AgentWebSocketServer;\n private readonly log: Logger;\n\n constructor(options: AgentWSOptions = {}) {\n this.log = createLogger({ level: options.logLevel ?? \"info\" });\n\n const serverOptions: AgentWebSocketServerOptions = {\n port: options.port ?? 9999,\n host: options.host ?? \"localhost\",\n logger: this.log,\n claudePath: options.claudePath,\n codexPath: options.codexPath,\n timeoutMs: options.timeoutMs,\n allowedOrigins: options.allowedOrigins,\n runnerFactory: options.runnerFactory,\n claudeRunnerFactory: options.claudeRunnerFactory,\n codexRunnerFactory: options.codexRunnerFactory,\n agentName: options.agentName,\n sessionDir: options.sessionDir,\n };\n\n this.server = new AgentWebSocketServer(serverOptions);\n }\n\n async start(): Promise<void> {\n await this.server.start();\n }\n\n stop(): void {\n this.server.stop();\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["spawn", "mkdirSync", "resolve", "tmpdir", "createInterface", "DEFAULT_TIMEOUT_MS", "resolve"]
|
|
7
7
|
}
|