@rallycry/conveyor-agent 7.0.13 → 7.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-3O4P2KST.js → chunk-23JCJ2GV.js} +469 -298
- package/dist/chunk-23JCJ2GV.js.map +1 -0
- package/dist/cli.js +58 -54
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +22 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/tunnel-client.js +18 -4
- package/dist/tunnel-client.js.map +1 -1
- package/package.json +2 -1
- package/dist/chunk-3O4P2KST.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/tunnel-client.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * Pod-side tunnel client.\n *\n * Connects to the API's WebSocket tunnel endpoint and proxies HTTP/WS\n * requests from the API to localhost ports inside the pod.\n *\n * Protocol matches tunnel-server.ts:\n * API → Pod (this client receives):\n * { type: \"http-req\", id, method, path, headers, port? }\n * <binary: 4-byte BE requestId + body chunk>\n * { type: \"http-req-end\", id }\n * { type: \"ws-upgrade\", id, path, headers, port? }\n * { type: \"ws-data\", id, data }\n * { type: \"ws-close\", id }\n *\n * Pod → API (this client sends):\n * { type: \"http-res\", id, statusCode, headers }\n * <binary: 4-byte BE requestId + response chunk>\n * { type: \"http-end\", id }\n * { type: \"ws-open\", id }\n * { type: \"ws-data\", id, data }\n * { type: \"ws-close\", id }\n */\nimport { WebSocket } from \"ws\";\nimport * as http from \"node:http\";\nimport { URL } from \"node:url\";\nimport { createServiceLogger } from \"./utils/logger.js\";\n\nconst logger = createServiceLogger(\"TunnelClient\");\n\nconst DEFAULT_PORT = 3050;\nconst RECONNECT_MIN_MS = 2_000;\nconst RECONNECT_MAX_MS = 30_000;\n\nconst apiUrl = process.env.CONVEYOR_API_URL;\nconst taskToken = process.env.CONVEYOR_TASK_TOKEN;\n\nif (!apiUrl || !taskToken) {\n logger.error(\"CONVEYOR_API_URL and CONVEYOR_TASK_TOKEN are required\");\n process.exit(1);\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ninterface HttpReqFrame {\n type: \"http-req\";\n id: number;\n method: string;\n path: string;\n headers: Record<string, string>;\n port?: number;\n}\n\ninterface HttpReqEndFrame {\n type: \"http-req-end\";\n id: number;\n}\n\ninterface WsUpgradeFrame {\n type: \"ws-upgrade\";\n id: number;\n path: string;\n headers: Record<string, string>;\n port?: number;\n}\n\ninterface WsDataFrame {\n type: \"ws-data\";\n id: number;\n data: string;\n}\n\ninterface WsCloseFrame {\n type: \"ws-close\";\n id: number;\n}\n\ntype IncomingFrame = HttpReqFrame | HttpReqEndFrame | WsUpgradeFrame | WsDataFrame | WsCloseFrame;\n\n// ---------------------------------------------------------------------------\n// Active request/websocket tracking\n// ---------------------------------------------------------------------------\n\nconst activeRequests = new Map<number, http.ClientRequest>();\nconst activeWebSockets = new Map<number, WebSocket>();\n\n// ---------------------------------------------------------------------------\n// Connection\n// ---------------------------------------------------------------------------\n\nlet reconnectDelay = RECONNECT_MIN_MS;\nlet shuttingDown = false;\n\nfunction buildTunnelUrl(): string {\n // apiUrl and taskToken are guaranteed non-null by the guard at module init\n const base = (apiUrl as string).replace(/^http/, \"ws\");\n return `${base}/api/tunnel?token=${encodeURIComponent(taskToken as string)}`;\n}\n\nfunction connect(): void {\n if (shuttingDown) return;\n\n const url = buildTunnelUrl();\n logger.info(\"Connecting...\");\n const ws = new WebSocket(url);\n\n ws.on(\"open\", () => {\n logger.info(\"Connected\");\n reconnectDelay = RECONNECT_MIN_MS;\n });\n\n ws.on(\"message\", (data: Buffer | string) => {\n handleMessage(ws, data);\n });\n\n ws.on(\"close\", (code: number) => {\n logger.info(`Disconnected (code=${code}), reconnecting in ${reconnectDelay}ms`);\n cleanupAll();\n if (!shuttingDown) {\n setTimeout(connect, reconnectDelay);\n reconnectDelay = Math.min(reconnectDelay * 2, RECONNECT_MAX_MS);\n }\n });\n\n ws.on(\"error\", (err: Error) => {\n logger.error(`WebSocket error: ${err.message}`);\n });\n}\n\n// ---------------------------------------------------------------------------\n// Frame handling\n// ---------------------------------------------------------------------------\n\nfunction handleMessage(tunnel: WebSocket, raw: Buffer | string): void {\n // Binary frame: 4-byte BE request ID + body chunk (request body from client)\n const buf = Buffer.isBuffer(raw) ? raw : Buffer.from(raw);\n\n if (buf.length > 0 && buf[0] !== 0x7b) {\n // Binary data frame — forward body chunk to active request\n if (buf.length <= 4) return;\n const id = buf.readUInt32BE(0);\n const req = activeRequests.get(id);\n if (req) req.write(buf.subarray(4));\n return;\n }\n\n // JSON control frame\n let frame: IncomingFrame;\n try {\n frame = JSON.parse(buf.toString(\"utf-8\"));\n } catch {\n return;\n }\n\n switch (frame.type) {\n case \"http-req\":\n handleHttpReq(tunnel, frame);\n break;\n case \"http-req-end\":\n handleHttpReqEnd(frame);\n break;\n case \"ws-upgrade\":\n handleWsUpgrade(tunnel, frame);\n break;\n case \"ws-data\":\n handleWsData(frame);\n break;\n case \"ws-close\":\n handleWsClose(frame);\n break;\n }\n}\n\nfunction handleHttpReq(tunnel: WebSocket, frame: HttpReqFrame): void {\n const port = frame.port ?? DEFAULT_PORT;\n const url = new URL(frame.path, `http://127.0.0.1:${port}`);\n\n const req = http.request(\n {\n hostname: \"127.0.0.1\",\n port,\n path: url.pathname + url.search,\n method: frame.method,\n headers: frame.headers,\n },\n (res) => {\n // Send response headers\n const headers: Record<string, string> = {};\n for (const [key, val] of Object.entries(res.headers)) {\n if (typeof val === \"string\") headers[key] = val;\n else if (Array.isArray(val)) headers[key] = val.join(\", \");\n }\n\n sendJson(tunnel, {\n type: \"http-res\",\n id: frame.id,\n statusCode: res.statusCode ?? 200,\n headers,\n });\n\n // Stream response body as binary frames\n res.on(\"data\", (chunk: Buffer) => {\n const out = Buffer.alloc(4 + chunk.length);\n out.writeUInt32BE(frame.id, 0);\n chunk.copy(out, 4);\n if (tunnel.readyState === WebSocket.OPEN) tunnel.send(out);\n });\n\n res.on(\"end\", () => {\n sendJson(tunnel, { type: \"http-end\", id: frame.id });\n activeRequests.delete(frame.id);\n });\n\n res.on(\"error\", () => {\n sendJson(tunnel, { type: \"http-end\", id: frame.id });\n activeRequests.delete(frame.id);\n });\n },\n );\n\n req.on(\"error\", (err) => {\n logger.error(`HTTP request error (id=${frame.id}): ${err.message}`);\n sendJson(tunnel, {\n type: \"http-res\",\n id: frame.id,\n statusCode: 502,\n headers: { \"content-type\": \"application/json\" },\n });\n const body = Buffer.from(JSON.stringify({ error: `Upstream error: ${err.message}` }));\n const out = Buffer.alloc(4 + body.length);\n out.writeUInt32BE(frame.id, 0);\n body.copy(out, 4);\n if (tunnel.readyState === WebSocket.OPEN) tunnel.send(out);\n sendJson(tunnel, { type: \"http-end\", id: frame.id });\n activeRequests.delete(frame.id);\n });\n\n activeRequests.set(frame.id, req);\n}\n\nfunction handleHttpReqEnd(frame: HttpReqEndFrame): void {\n const req = activeRequests.get(frame.id);\n if (req) req.end();\n}\n\nfunction handleWsUpgrade(tunnel: WebSocket, frame: WsUpgradeFrame): void {\n const port = frame.port ?? DEFAULT_PORT;\n const url = `ws://127.0.0.1:${port}${frame.path}`;\n\n const upstream = new WebSocket(url, { headers: frame.headers });\n\n upstream.on(\"open\", () => {\n sendJson(tunnel, { type: \"ws-open\", id: frame.id });\n activeWebSockets.set(frame.id, upstream);\n });\n\n upstream.on(\"message\", (data: Buffer | string) => {\n sendJson(tunnel, {\n type: \"ws-data\",\n id: frame.id,\n data: typeof data === \"string\" ? data : data.toString(\"utf-8\"),\n });\n });\n\n upstream.on(\"close\", () => {\n sendJson(tunnel, { type: \"ws-close\", id: frame.id });\n activeWebSockets.delete(frame.id);\n });\n\n upstream.on(\"error\", (err: Error) => {\n logger.error(`WS upstream error (id=${frame.id}): ${err.message}`);\n sendJson(tunnel, { type: \"ws-close\", id: frame.id });\n activeWebSockets.delete(frame.id);\n });\n}\n\nfunction handleWsData(frame: WsDataFrame): void {\n const ws = activeWebSockets.get(frame.id);\n if (ws && ws.readyState === WebSocket.OPEN) {\n ws.send(frame.data);\n }\n}\n\nfunction handleWsClose(frame: WsCloseFrame): void {\n const ws = activeWebSockets.get(frame.id);\n if (ws) {\n ws.close(1000);\n activeWebSockets.delete(frame.id);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction sendJson(ws: WebSocket, obj: Record<string, unknown>): void {\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(JSON.stringify(obj));\n }\n}\n\nfunction cleanupAll(): void {\n for (const [id, req] of activeRequests) {\n req.destroy();\n activeRequests.delete(id);\n }\n for (const [id, ws] of activeWebSockets) {\n ws.close(1000);\n activeWebSockets.delete(id);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Lifecycle\n// ---------------------------------------------------------------------------\n\nprocess.on(\"SIGTERM\", () => {\n logger.info(\"SIGTERM received, shutting down\");\n shuttingDown = true;\n cleanupAll();\n process.exit(0);\n});\n\nprocess.on(\"SIGINT\", () => {\n logger.info(\"SIGINT received, shutting down\");\n shuttingDown = true;\n cleanupAll();\n process.exit(0);\n});\n\nconnect();\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/tunnel-client.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * Pod-side tunnel client.\n *\n * Connects to the API's WebSocket tunnel endpoint and proxies HTTP/WS\n * requests from the API to localhost ports inside the pod.\n *\n * Protocol matches tunnel-server.ts:\n * API → Pod (this client receives):\n * { type: \"http-req\", id, method, path, headers, port? }\n * <binary: 4-byte BE requestId + body chunk>\n * { type: \"http-req-end\", id }\n * { type: \"ws-upgrade\", id, path, headers, port? }\n * { type: \"ws-data\", id, data }\n * { type: \"ws-close\", id }\n *\n * Pod → API (this client sends):\n * { type: \"http-res\", id, statusCode, headers }\n * <binary: 4-byte BE requestId + response chunk>\n * { type: \"http-end\", id }\n * { type: \"ws-open\", id }\n * { type: \"ws-data\", id, data }\n * { type: \"ws-close\", id }\n */\nimport { WebSocket } from \"ws\";\nimport * as http from \"node:http\";\nimport { URL } from \"node:url\";\nimport { createServiceLogger } from \"./utils/logger.js\";\n\nconst logger = createServiceLogger(\"TunnelClient\");\n\nconst DEFAULT_PORT = 3050;\nconst RECONNECT_MIN_MS = 2_000;\nconst RECONNECT_MAX_MS = 30_000;\n\nconst apiUrl = process.env.CONVEYOR_API_URL;\nconst taskToken = process.env.CONVEYOR_TASK_TOKEN;\n\nif (!apiUrl || !taskToken) {\n logger.error(\"CONVEYOR_API_URL and CONVEYOR_TASK_TOKEN are required\");\n process.exit(1);\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ninterface HttpReqFrame {\n type: \"http-req\";\n id: number;\n method: string;\n path: string;\n headers: Record<string, string>;\n port?: number;\n}\n\ninterface HttpReqEndFrame {\n type: \"http-req-end\";\n id: number;\n}\n\ninterface WsUpgradeFrame {\n type: \"ws-upgrade\";\n id: number;\n path: string;\n headers: Record<string, string>;\n port?: number;\n}\n\ninterface WsDataFrame {\n type: \"ws-data\";\n id: number;\n data: string;\n /** When true, `data` is base64-encoded binary; otherwise it's a text frame. */\n binary?: boolean;\n}\n\ninterface WsCloseFrame {\n type: \"ws-close\";\n id: number;\n}\n\ntype IncomingFrame = HttpReqFrame | HttpReqEndFrame | WsUpgradeFrame | WsDataFrame | WsCloseFrame;\n\n// ---------------------------------------------------------------------------\n// Active request/websocket tracking\n// ---------------------------------------------------------------------------\n\nconst activeRequests = new Map<number, http.ClientRequest>();\nconst activeWebSockets = new Map<number, WebSocket>();\n\n// ---------------------------------------------------------------------------\n// Connection\n// ---------------------------------------------------------------------------\n\nlet reconnectDelay = RECONNECT_MIN_MS;\nlet shuttingDown = false;\n\nfunction buildTunnelUrl(): string {\n // apiUrl and taskToken are guaranteed non-null by the guard at module init\n const base = (apiUrl as string).replace(/^http/, \"ws\");\n return `${base}/api/tunnel?token=${encodeURIComponent(taskToken as string)}`;\n}\n\nfunction connect(): void {\n if (shuttingDown) return;\n\n const url = buildTunnelUrl();\n logger.info(\"Connecting...\");\n const ws = new WebSocket(url);\n\n ws.on(\"open\", () => {\n logger.info(\"Connected\");\n reconnectDelay = RECONNECT_MIN_MS;\n });\n\n ws.on(\"message\", (data: Buffer | string) => {\n handleMessage(ws, data);\n });\n\n ws.on(\"close\", (code: number) => {\n logger.info(`Disconnected (code=${code}), reconnecting in ${reconnectDelay}ms`);\n cleanupAll();\n if (!shuttingDown) {\n setTimeout(connect, reconnectDelay);\n reconnectDelay = Math.min(reconnectDelay * 2, RECONNECT_MAX_MS);\n }\n });\n\n ws.on(\"error\", (err: Error) => {\n logger.error(`WebSocket error: ${err.message}`);\n });\n}\n\n// ---------------------------------------------------------------------------\n// Frame handling\n// ---------------------------------------------------------------------------\n\nfunction handleMessage(tunnel: WebSocket, raw: Buffer | string): void {\n // Binary frame: 4-byte BE request ID + body chunk (request body from client)\n const buf = Buffer.isBuffer(raw) ? raw : Buffer.from(raw);\n\n if (buf.length > 0 && buf[0] !== 0x7b) {\n // Binary data frame — forward body chunk to active request\n if (buf.length <= 4) return;\n const id = buf.readUInt32BE(0);\n const req = activeRequests.get(id);\n if (req) req.write(buf.subarray(4));\n return;\n }\n\n // JSON control frame\n let frame: IncomingFrame;\n try {\n frame = JSON.parse(buf.toString(\"utf-8\"));\n } catch {\n return;\n }\n\n switch (frame.type) {\n case \"http-req\":\n handleHttpReq(tunnel, frame);\n break;\n case \"http-req-end\":\n handleHttpReqEnd(frame);\n break;\n case \"ws-upgrade\":\n handleWsUpgrade(tunnel, frame);\n break;\n case \"ws-data\":\n handleWsData(frame);\n break;\n case \"ws-close\":\n handleWsClose(frame);\n break;\n }\n}\n\nfunction handleHttpReq(tunnel: WebSocket, frame: HttpReqFrame): void {\n const port = frame.port ?? DEFAULT_PORT;\n const url = new URL(frame.path, `http://127.0.0.1:${port}`);\n\n const req = http.request(\n {\n hostname: \"127.0.0.1\",\n port,\n path: url.pathname + url.search,\n method: frame.method,\n headers: frame.headers,\n },\n (res) => {\n // Send response headers\n const headers: Record<string, string> = {};\n for (const [key, val] of Object.entries(res.headers)) {\n if (typeof val === \"string\") headers[key] = val;\n else if (Array.isArray(val)) headers[key] = val.join(\", \");\n }\n\n sendJson(tunnel, {\n type: \"http-res\",\n id: frame.id,\n statusCode: res.statusCode ?? 200,\n headers,\n });\n\n // Stream response body as binary frames\n res.on(\"data\", (chunk: Buffer) => {\n const out = Buffer.alloc(4 + chunk.length);\n out.writeUInt32BE(frame.id, 0);\n chunk.copy(out, 4);\n if (tunnel.readyState === WebSocket.OPEN) tunnel.send(out);\n });\n\n res.on(\"end\", () => {\n sendJson(tunnel, { type: \"http-end\", id: frame.id });\n activeRequests.delete(frame.id);\n });\n\n res.on(\"error\", () => {\n sendJson(tunnel, { type: \"http-end\", id: frame.id });\n activeRequests.delete(frame.id);\n });\n },\n );\n\n req.on(\"error\", (err) => {\n logger.error(`HTTP request error (id=${frame.id}): ${err.message}`);\n sendJson(tunnel, {\n type: \"http-res\",\n id: frame.id,\n statusCode: 502,\n headers: { \"content-type\": \"application/json\" },\n });\n const body = Buffer.from(JSON.stringify({ error: `Upstream error: ${err.message}` }));\n const out = Buffer.alloc(4 + body.length);\n out.writeUInt32BE(frame.id, 0);\n body.copy(out, 4);\n if (tunnel.readyState === WebSocket.OPEN) tunnel.send(out);\n sendJson(tunnel, { type: \"http-end\", id: frame.id });\n activeRequests.delete(frame.id);\n });\n\n activeRequests.set(frame.id, req);\n}\n\nfunction handleHttpReqEnd(frame: HttpReqEndFrame): void {\n const req = activeRequests.get(frame.id);\n if (req) req.end();\n}\n\nfunction handleWsUpgrade(tunnel: WebSocket, frame: WsUpgradeFrame): void {\n const port = frame.port ?? DEFAULT_PORT;\n const url = `ws://127.0.0.1:${port}${frame.path}`;\n\n // Strip hop-by-hop headers and the Sec-WebSocket-* headers — the upstream\n // WebSocket constructor generates its own. Forwarding the browser's key\n // would only confuse the upstream.\n const passthrough: Record<string, string> = {};\n for (const [k, v] of Object.entries(frame.headers)) {\n const key = k.toLowerCase();\n if (key === \"host\") continue;\n if (key === \"connection\") continue;\n if (key === \"upgrade\") continue;\n if (key.startsWith(\"sec-websocket-\")) continue;\n passthrough[k] = v;\n }\n\n const upstream = new WebSocket(url, { headers: passthrough });\n\n upstream.on(\"open\", () => {\n sendJson(tunnel, { type: \"ws-open\", id: frame.id });\n activeWebSockets.set(frame.id, upstream);\n });\n\n upstream.on(\"message\", (data: Buffer | ArrayBuffer | Buffer[], isBinary: boolean) => {\n const buf = Buffer.isBuffer(data)\n ? data\n : Array.isArray(data)\n ? Buffer.concat(data)\n : Buffer.from(data);\n sendJson(tunnel, {\n type: \"ws-data\",\n id: frame.id,\n data: isBinary ? buf.toString(\"base64\") : buf.toString(\"utf-8\"),\n binary: isBinary,\n });\n });\n\n upstream.on(\"close\", () => {\n sendJson(tunnel, { type: \"ws-close\", id: frame.id });\n activeWebSockets.delete(frame.id);\n });\n\n upstream.on(\"error\", (err: Error) => {\n logger.error(`WS upstream error (id=${frame.id}): ${err.message}`);\n sendJson(tunnel, { type: \"ws-close\", id: frame.id });\n activeWebSockets.delete(frame.id);\n });\n}\n\nfunction handleWsData(frame: WsDataFrame): void {\n const ws = activeWebSockets.get(frame.id);\n if (!ws || ws.readyState !== WebSocket.OPEN) return;\n if (frame.binary && frame.data) {\n ws.send(Buffer.from(frame.data, \"base64\"), { binary: true });\n } else {\n ws.send(frame.data);\n }\n}\n\nfunction handleWsClose(frame: WsCloseFrame): void {\n const ws = activeWebSockets.get(frame.id);\n if (ws) {\n ws.close(1000);\n activeWebSockets.delete(frame.id);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction sendJson(ws: WebSocket, obj: Record<string, unknown>): void {\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(JSON.stringify(obj));\n }\n}\n\nfunction cleanupAll(): void {\n for (const [id, req] of activeRequests) {\n req.destroy();\n activeRequests.delete(id);\n }\n for (const [id, ws] of activeWebSockets) {\n ws.close(1000);\n activeWebSockets.delete(id);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Lifecycle\n// ---------------------------------------------------------------------------\n\nprocess.on(\"SIGTERM\", () => {\n logger.info(\"SIGTERM received, shutting down\");\n shuttingDown = true;\n cleanupAll();\n process.exit(0);\n});\n\nprocess.on(\"SIGINT\", () => {\n logger.info(\"SIGINT received, shutting down\");\n shuttingDown = true;\n cleanupAll();\n process.exit(0);\n});\n\nconnect();\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rallycry/conveyor-agent",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.1.1",
|
|
4
4
|
"description": "Conveyor Agent Runner v7.0 - Agent-as-User architecture with BaseService patterns",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"scripts": {
|
|
27
27
|
"prepublishOnly": "tsup",
|
|
28
28
|
"build": "tsup",
|
|
29
|
+
"build:js": "tsup --no-dts --no-clean",
|
|
29
30
|
"dev": "tsup --watch",
|
|
30
31
|
"test": "vitest run --passWithNoTests",
|
|
31
32
|
"test:unit": "vitest run --passWithNoTests",
|