@rallycry/conveyor-agent 7.0.0 → 7.0.2

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.
@@ -0,0 +1,184 @@
1
+ import {
2
+ createHarness
3
+ } from "./chunk-KNBG2634.js";
4
+ import {
5
+ createServiceLogger
6
+ } from "./chunk-CYZPFJGN.js";
7
+
8
+ // src/runner/tag-audit-handler.ts
9
+ var logger = createServiceLogger("TagAudit");
10
+ var FALLBACK_MODEL = "claude-sonnet-4-20250514";
11
+ function buildTagAuditPrompt(request) {
12
+ const parts = [];
13
+ parts.push(`# Tag Audit for project: ${request.projectName}
14
+ `);
15
+ parts.push("## Current Tags\n");
16
+ if (request.tags.length === 0) {
17
+ parts.push("No tags currently exist.\n");
18
+ } else {
19
+ for (const tag of request.tags) {
20
+ parts.push(`### ${tag.name} (id: ${tag.id})`);
21
+ parts.push(`- Description: ${tag.description ?? "(none)"}`);
22
+ parts.push(`- Active tasks: ${tag.activeTaskCount}`);
23
+ if (tag.contextPaths) {
24
+ parts.push(`- Context paths: ${JSON.stringify(tag.contextPaths)}`);
25
+ }
26
+ parts.push("");
27
+ }
28
+ }
29
+ if (request.fileHeatmap.length > 0) {
30
+ parts.push("## File Access Heatmap\n");
31
+ parts.push("Files most frequently read by agents, broken down by tag:\n");
32
+ for (const entry of request.fileHeatmap.slice(0, 50)) {
33
+ const tagBreakdown = Object.entries(entry.byTag).map(([tag, count]) => `${tag}: ${count}`).join(", ");
34
+ parts.push(`- \`${entry.filePath}\` (total: ${entry.totalReads}) \u2014 ${tagBreakdown}`);
35
+ }
36
+ parts.push("");
37
+ }
38
+ parts.push("Analyze the tags above and the codebase, then output your recommendations.");
39
+ return parts.join("\n");
40
+ }
41
+ var TAG_AUDIT_SYSTEM_PROMPT = `You are analyzing tags for a software project. Tags are used to organize tasks, associate context paths (files/directories that agents should read), and track work areas.
42
+
43
+ Given the current tags (with descriptions, context paths, and active task counts) and a file access heatmap (which files agents read most, broken down by tag), analyze the project and generate recommendations.
44
+
45
+ You have full access to the codebase. Read relevant files \u2014 especially .claude/rules/, README files, and any context paths referenced by existing tags \u2014 to validate your recommendations.
46
+
47
+ Generate recommendations of these types:
48
+ - **create_tag**: Suggest new tags for uncovered areas of the codebase
49
+ - **update_description**: Improve a tag's description to be more useful
50
+ - **add_context_link**: Add context paths (files/directories) that agents should read when working on tasks with this tag
51
+ - **documentation_gap**: Identify areas where documentation or context is missing
52
+ - **merge_tags**: Suggest merging overlapping tags
53
+ - **rename_tag**: Suggest a better name for a tag
54
+
55
+ Each recommendation must include:
56
+ - \`id\`: A unique UUID
57
+ - \`type\`: One of the types above
58
+ - \`tagName\`: The tag name this applies to
59
+ - \`tagId\`: The existing tag's ID (if modifying an existing tag, omit for create_tag)
60
+ - \`suggestion\`: A short description of the recommendation
61
+ - \`reasoning\`: Why this recommendation would help
62
+ - \`payload\`: Type-specific data:
63
+ - create_tag: \`{ name: string, description: string, contextPaths?: string[] }\`
64
+ - update_description: \`{ description: string }\`
65
+ - add_context_link: \`{ path: string, description?: string }\`
66
+ - documentation_gap: \`{ area: string, suggestedContent?: string }\`
67
+ - merge_tags: \`{ sourceTagIds: string[], targetName: string }\`
68
+ - rename_tag: \`{ newName: string }\`
69
+
70
+ After your analysis, output a JSON block in this exact format:
71
+
72
+ \`\`\`json
73
+ {
74
+ "recommendations": [ ...array of recommendations... ],
75
+ "summary": "Brief summary of findings"
76
+ }
77
+ \`\`\`
78
+
79
+ Be specific, actionable, and concise. Focus on the most impactful recommendations.`;
80
+ async function fetchModel(connection) {
81
+ try {
82
+ const ctx = await connection.call("getProjectAgentContext", {
83
+ projectId: connection.projectId
84
+ });
85
+ return ctx.model || FALLBACK_MODEL;
86
+ } catch {
87
+ return FALLBACK_MODEL;
88
+ }
89
+ }
90
+ function extractJsonFromResponse(text) {
91
+ const codeBlockMatch = text.match(/```(?:json)?\s*\n?([\s\S]*?)```/);
92
+ const jsonStr = codeBlockMatch ? codeBlockMatch[1].trim() : text;
93
+ try {
94
+ const parsed = JSON.parse(jsonStr);
95
+ if (parsed && Array.isArray(parsed.recommendations) && typeof parsed.summary === "string") {
96
+ return parsed;
97
+ }
98
+ } catch {
99
+ const objectMatch = text.match(/\{[\s\S]*"recommendations"[\s\S]*\}/);
100
+ if (objectMatch) {
101
+ try {
102
+ const parsed = JSON.parse(objectMatch[0]);
103
+ if (parsed && Array.isArray(parsed.recommendations)) {
104
+ return { recommendations: parsed.recommendations, summary: parsed.summary ?? "" };
105
+ }
106
+ } catch {
107
+ }
108
+ }
109
+ }
110
+ throw new Error("Could not parse tag audit recommendations from Claude response");
111
+ }
112
+ async function collectResponseFromEvents(events, connection, requestId) {
113
+ const responseParts = [];
114
+ for await (const event of events) {
115
+ if (event.type === "assistant") {
116
+ const { message } = event;
117
+ for (const block of message.content) {
118
+ if (block.type === "text" && block.text) {
119
+ responseParts.push(block.text);
120
+ } else if (block.type === "tool_use" && block.name) {
121
+ const inputStr = typeof block.input === "string" ? block.input : JSON.stringify(block.input);
122
+ await connection.call("reportTagAuditProgress", {
123
+ projectId: connection.projectId,
124
+ requestId,
125
+ activity: {
126
+ tool: block.name,
127
+ input: inputStr.slice(0, 1e4),
128
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
129
+ }
130
+ }).catch(() => {
131
+ });
132
+ }
133
+ }
134
+ }
135
+ if (event.type === "result") break;
136
+ }
137
+ return responseParts.join("\n\n").trim();
138
+ }
139
+ async function handleTagAudit(request, connection, projectDir) {
140
+ const { requestId } = request;
141
+ logger.info("Starting tag audit", { requestId, tagCount: request.tags.length });
142
+ await connection.call("reportTagAuditProgress", {
143
+ projectId: connection.projectId,
144
+ requestId,
145
+ activity: {
146
+ tool: "audit",
147
+ input: "Starting tag audit...",
148
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
149
+ }
150
+ }).catch(() => {
151
+ });
152
+ const model = await fetchModel(connection);
153
+ const harness = createHarness();
154
+ const events = harness.executeQuery({
155
+ prompt: buildTagAuditPrompt(request),
156
+ options: {
157
+ model,
158
+ systemPrompt: TAG_AUDIT_SYSTEM_PROMPT,
159
+ cwd: projectDir,
160
+ permissionMode: "bypassPermissions",
161
+ allowDangerouslySkipPermissions: true,
162
+ tools: { type: "preset", preset: "claude_code" },
163
+ mcpServers: {},
164
+ maxTurns: 5,
165
+ maxBudgetUsd: 3
166
+ }
167
+ });
168
+ const responseText = await collectResponseFromEvents(events, connection, requestId);
169
+ if (!responseText) throw new Error("No response from Claude");
170
+ const { recommendations, summary } = extractJsonFromResponse(responseText);
171
+ logger.info("Tag audit complete", { requestId, recommendationCount: recommendations.length });
172
+ await connection.call("reportTagAuditResult", {
173
+ projectId: connection.projectId,
174
+ requestId,
175
+ recommendations,
176
+ summary,
177
+ complete: true
178
+ });
179
+ }
180
+ export {
181
+ buildTagAuditPrompt,
182
+ handleTagAudit
183
+ };
184
+ //# sourceMappingURL=tag-audit-handler-4RRGIHVB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/runner/tag-audit-handler.ts"],"sourcesContent":["import { createHarness } from \"../harness/index.js\";\nimport type { ProjectConnection } from \"../connection/project-connection.js\";\nimport type { TagAuditRunnerRequest } from \"@project/shared\";\nimport { createServiceLogger } from \"../utils/logger.js\";\n\nconst logger = createServiceLogger(\"TagAudit\");\n\nconst FALLBACK_MODEL = \"claude-sonnet-4-20250514\";\n\n// ── Prompt builder ──────────────────────────────────────────────────────\n\nexport function buildTagAuditPrompt(request: TagAuditRunnerRequest): string {\n const parts: string[] = [];\n\n parts.push(`# Tag Audit for project: ${request.projectName}\\n`);\n\n parts.push(\"## Current Tags\\n\");\n if (request.tags.length === 0) {\n parts.push(\"No tags currently exist.\\n\");\n } else {\n for (const tag of request.tags) {\n parts.push(`### ${tag.name} (id: ${tag.id})`);\n parts.push(`- Description: ${tag.description ?? \"(none)\"}`);\n parts.push(`- Active tasks: ${tag.activeTaskCount}`);\n if (tag.contextPaths) {\n parts.push(`- Context paths: ${JSON.stringify(tag.contextPaths)}`);\n }\n parts.push(\"\");\n }\n }\n\n if (request.fileHeatmap.length > 0) {\n parts.push(\"## File Access Heatmap\\n\");\n parts.push(\"Files most frequently read by agents, broken down by tag:\\n\");\n for (const entry of request.fileHeatmap.slice(0, 50)) {\n const tagBreakdown = Object.entries(entry.byTag)\n .map(([tag, count]) => `${tag}: ${count}`)\n .join(\", \");\n parts.push(`- \\`${entry.filePath}\\` (total: ${entry.totalReads}) — ${tagBreakdown}`);\n }\n parts.push(\"\");\n }\n\n parts.push(\"Analyze the tags above and the codebase, then output your recommendations.\");\n\n return parts.join(\"\\n\");\n}\n\nconst TAG_AUDIT_SYSTEM_PROMPT = `You are analyzing tags for a software project. Tags are used to organize tasks, associate context paths (files/directories that agents should read), and track work areas.\n\nGiven the current tags (with descriptions, context paths, and active task counts) and a file access heatmap (which files agents read most, broken down by tag), analyze the project and generate recommendations.\n\nYou have full access to the codebase. Read relevant files — especially .claude/rules/, README files, and any context paths referenced by existing tags — to validate your recommendations.\n\nGenerate recommendations of these types:\n- **create_tag**: Suggest new tags for uncovered areas of the codebase\n- **update_description**: Improve a tag's description to be more useful\n- **add_context_link**: Add context paths (files/directories) that agents should read when working on tasks with this tag\n- **documentation_gap**: Identify areas where documentation or context is missing\n- **merge_tags**: Suggest merging overlapping tags\n- **rename_tag**: Suggest a better name for a tag\n\nEach recommendation must include:\n- \\`id\\`: A unique UUID\n- \\`type\\`: One of the types above\n- \\`tagName\\`: The tag name this applies to\n- \\`tagId\\`: The existing tag's ID (if modifying an existing tag, omit for create_tag)\n- \\`suggestion\\`: A short description of the recommendation\n- \\`reasoning\\`: Why this recommendation would help\n- \\`payload\\`: Type-specific data:\n - create_tag: \\`{ name: string, description: string, contextPaths?: string[] }\\`\n - update_description: \\`{ description: string }\\`\n - add_context_link: \\`{ path: string, description?: string }\\`\n - documentation_gap: \\`{ area: string, suggestedContent?: string }\\`\n - merge_tags: \\`{ sourceTagIds: string[], targetName: string }\\`\n - rename_tag: \\`{ newName: string }\\`\n\nAfter your analysis, output a JSON block in this exact format:\n\n\\`\\`\\`json\n{\n \"recommendations\": [ ...array of recommendations... ],\n \"summary\": \"Brief summary of findings\"\n}\n\\`\\`\\`\n\nBe specific, actionable, and concise. Focus on the most impactful recommendations.`;\n\n// ── Fetch agent context ─────────────────────────────────────────────────\n\nasync function fetchModel(connection: ProjectConnection): Promise<string> {\n try {\n const ctx = await connection.call(\"getProjectAgentContext\", {\n projectId: connection.projectId,\n });\n return ctx.model || FALLBACK_MODEL;\n } catch {\n return FALLBACK_MODEL;\n }\n}\n\n// ── JSON extraction ─────────────────────────────────────────────────────\n\nfunction extractJsonFromResponse(text: string): { recommendations: unknown[]; summary: string } {\n // Try to find a JSON code block first\n const codeBlockMatch = text.match(/```(?:json)?\\s*\\n?([\\s\\S]*?)```/);\n const jsonStr = codeBlockMatch ? codeBlockMatch[1].trim() : text;\n\n try {\n const parsed = JSON.parse(jsonStr);\n if (parsed && Array.isArray(parsed.recommendations) && typeof parsed.summary === \"string\") {\n return parsed;\n }\n } catch {\n // Try to find a JSON object in the raw text\n const objectMatch = text.match(/\\{[\\s\\S]*\"recommendations\"[\\s\\S]*\\}/);\n if (objectMatch) {\n try {\n const parsed = JSON.parse(objectMatch[0]);\n if (parsed && Array.isArray(parsed.recommendations)) {\n return { recommendations: parsed.recommendations, summary: parsed.summary ?? \"\" };\n }\n } catch {\n // Fall through\n }\n }\n }\n\n throw new Error(\"Could not parse tag audit recommendations from Claude response\");\n}\n\n// ── Event stream processing ─────────────────────────────────────────────\n\nasync function collectResponseFromEvents(\n events: AsyncIterable<{ type: string }>,\n connection: ProjectConnection,\n requestId: string,\n): Promise<string> {\n const responseParts: string[] = [];\n for await (const event of events) {\n if (event.type === \"assistant\") {\n const { message } = event as {\n type: string;\n message: {\n content: Array<{ type: string; text?: string; name?: string; input?: unknown }>;\n };\n };\n for (const block of message.content) {\n if (block.type === \"text\" && block.text) {\n responseParts.push(block.text);\n } else if (block.type === \"tool_use\" && block.name) {\n const inputStr =\n typeof block.input === \"string\" ? block.input : JSON.stringify(block.input);\n await connection\n .call(\"reportTagAuditProgress\", {\n projectId: connection.projectId,\n requestId,\n activity: {\n tool: block.name,\n input: inputStr.slice(0, 10_000),\n timestamp: new Date().toISOString(),\n },\n })\n .catch(() => {});\n }\n }\n }\n if (event.type === \"result\") break;\n }\n return responseParts.join(\"\\n\\n\").trim();\n}\n\n// ── Main handler ────────────────────────────────────────────────────────\n\nexport async function handleTagAudit(\n request: TagAuditRunnerRequest,\n connection: ProjectConnection,\n projectDir: string,\n): Promise<void> {\n const { requestId } = request;\n\n logger.info(\"Starting tag audit\", { requestId, tagCount: request.tags.length });\n\n await connection\n .call(\"reportTagAuditProgress\", {\n projectId: connection.projectId,\n requestId,\n activity: {\n tool: \"audit\",\n input: \"Starting tag audit...\",\n timestamp: new Date().toISOString(),\n },\n })\n .catch(() => {});\n\n const model = await fetchModel(connection);\n const harness = createHarness();\n const events = harness.executeQuery({\n prompt: buildTagAuditPrompt(request),\n options: {\n model,\n systemPrompt: TAG_AUDIT_SYSTEM_PROMPT,\n cwd: projectDir,\n permissionMode: \"bypassPermissions\",\n allowDangerouslySkipPermissions: true,\n tools: { type: \"preset\" as const, preset: \"claude_code\" as const },\n mcpServers: {},\n maxTurns: 5,\n maxBudgetUsd: 3,\n },\n });\n\n const responseText = await collectResponseFromEvents(events, connection, requestId);\n if (!responseText) throw new Error(\"No response from Claude\");\n\n const { recommendations, summary } = extractJsonFromResponse(responseText);\n logger.info(\"Tag audit complete\", { requestId, recommendationCount: recommendations.length });\n\n await connection.call(\"reportTagAuditResult\", {\n projectId: connection.projectId,\n requestId,\n recommendations: recommendations as Array<Record<string, unknown>>,\n summary,\n complete: true,\n });\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
@@ -0,0 +1,212 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ createServiceLogger
4
+ } from "./chunk-CYZPFJGN.js";
5
+
6
+ // src/tunnel-client.ts
7
+ import { WebSocket } from "ws";
8
+ import * as http from "http";
9
+ import { URL } from "url";
10
+ var logger = createServiceLogger("TunnelClient");
11
+ var DEFAULT_PORT = 3050;
12
+ var RECONNECT_MIN_MS = 2e3;
13
+ var RECONNECT_MAX_MS = 3e4;
14
+ var apiUrl = process.env.CONVEYOR_API_URL;
15
+ var taskToken = process.env.CONVEYOR_TASK_TOKEN;
16
+ if (!apiUrl || !taskToken) {
17
+ logger.error("CONVEYOR_API_URL and CONVEYOR_TASK_TOKEN are required");
18
+ process.exit(1);
19
+ }
20
+ var activeRequests = /* @__PURE__ */ new Map();
21
+ var activeWebSockets = /* @__PURE__ */ new Map();
22
+ var reconnectDelay = RECONNECT_MIN_MS;
23
+ var shuttingDown = false;
24
+ function buildTunnelUrl() {
25
+ const base = apiUrl.replace(/^http/, "ws");
26
+ return `${base}/api/tunnel?token=${encodeURIComponent(taskToken)}`;
27
+ }
28
+ function connect() {
29
+ if (shuttingDown) return;
30
+ const url = buildTunnelUrl();
31
+ logger.info("Connecting...");
32
+ const ws = new WebSocket(url);
33
+ ws.on("open", () => {
34
+ logger.info("Connected");
35
+ reconnectDelay = RECONNECT_MIN_MS;
36
+ });
37
+ ws.on("message", (data) => {
38
+ handleMessage(ws, data);
39
+ });
40
+ ws.on("close", (code) => {
41
+ logger.info(`Disconnected (code=${code}), reconnecting in ${reconnectDelay}ms`);
42
+ cleanupAll();
43
+ if (!shuttingDown) {
44
+ setTimeout(connect, reconnectDelay);
45
+ reconnectDelay = Math.min(reconnectDelay * 2, RECONNECT_MAX_MS);
46
+ }
47
+ });
48
+ ws.on("error", (err) => {
49
+ logger.error(`WebSocket error: ${err.message}`);
50
+ });
51
+ }
52
+ function handleMessage(tunnel, raw) {
53
+ const buf = Buffer.isBuffer(raw) ? raw : Buffer.from(raw);
54
+ if (buf.length > 0 && buf[0] !== 123) {
55
+ if (buf.length <= 4) return;
56
+ const id = buf.readUInt32BE(0);
57
+ const req = activeRequests.get(id);
58
+ if (req) req.write(buf.subarray(4));
59
+ return;
60
+ }
61
+ let frame;
62
+ try {
63
+ frame = JSON.parse(buf.toString("utf-8"));
64
+ } catch {
65
+ return;
66
+ }
67
+ switch (frame.type) {
68
+ case "http-req":
69
+ handleHttpReq(tunnel, frame);
70
+ break;
71
+ case "http-req-end":
72
+ handleHttpReqEnd(frame);
73
+ break;
74
+ case "ws-upgrade":
75
+ handleWsUpgrade(tunnel, frame);
76
+ break;
77
+ case "ws-data":
78
+ handleWsData(frame);
79
+ break;
80
+ case "ws-close":
81
+ handleWsClose(frame);
82
+ break;
83
+ }
84
+ }
85
+ function handleHttpReq(tunnel, frame) {
86
+ const port = frame.port ?? DEFAULT_PORT;
87
+ const url = new URL(frame.path, `http://127.0.0.1:${port}`);
88
+ const req = http.request(
89
+ {
90
+ hostname: "127.0.0.1",
91
+ port,
92
+ path: url.pathname + url.search,
93
+ method: frame.method,
94
+ headers: frame.headers
95
+ },
96
+ (res) => {
97
+ const headers = {};
98
+ for (const [key, val] of Object.entries(res.headers)) {
99
+ if (typeof val === "string") headers[key] = val;
100
+ else if (Array.isArray(val)) headers[key] = val.join(", ");
101
+ }
102
+ sendJson(tunnel, {
103
+ type: "http-res",
104
+ id: frame.id,
105
+ statusCode: res.statusCode ?? 200,
106
+ headers
107
+ });
108
+ res.on("data", (chunk) => {
109
+ const out = Buffer.alloc(4 + chunk.length);
110
+ out.writeUInt32BE(frame.id, 0);
111
+ chunk.copy(out, 4);
112
+ if (tunnel.readyState === WebSocket.OPEN) tunnel.send(out);
113
+ });
114
+ res.on("end", () => {
115
+ sendJson(tunnel, { type: "http-end", id: frame.id });
116
+ activeRequests.delete(frame.id);
117
+ });
118
+ res.on("error", () => {
119
+ sendJson(tunnel, { type: "http-end", id: frame.id });
120
+ activeRequests.delete(frame.id);
121
+ });
122
+ }
123
+ );
124
+ req.on("error", (err) => {
125
+ logger.error(`HTTP request error (id=${frame.id}): ${err.message}`);
126
+ sendJson(tunnel, {
127
+ type: "http-res",
128
+ id: frame.id,
129
+ statusCode: 502,
130
+ headers: { "content-type": "application/json" }
131
+ });
132
+ const body = Buffer.from(JSON.stringify({ error: `Upstream error: ${err.message}` }));
133
+ const out = Buffer.alloc(4 + body.length);
134
+ out.writeUInt32BE(frame.id, 0);
135
+ body.copy(out, 4);
136
+ if (tunnel.readyState === WebSocket.OPEN) tunnel.send(out);
137
+ sendJson(tunnel, { type: "http-end", id: frame.id });
138
+ activeRequests.delete(frame.id);
139
+ });
140
+ activeRequests.set(frame.id, req);
141
+ }
142
+ function handleHttpReqEnd(frame) {
143
+ const req = activeRequests.get(frame.id);
144
+ if (req) req.end();
145
+ }
146
+ function handleWsUpgrade(tunnel, frame) {
147
+ const port = frame.port ?? DEFAULT_PORT;
148
+ const url = `ws://127.0.0.1:${port}${frame.path}`;
149
+ const upstream = new WebSocket(url, { headers: frame.headers });
150
+ upstream.on("open", () => {
151
+ sendJson(tunnel, { type: "ws-open", id: frame.id });
152
+ activeWebSockets.set(frame.id, upstream);
153
+ });
154
+ upstream.on("message", (data) => {
155
+ sendJson(tunnel, {
156
+ type: "ws-data",
157
+ id: frame.id,
158
+ data: typeof data === "string" ? data : data.toString("utf-8")
159
+ });
160
+ });
161
+ upstream.on("close", () => {
162
+ sendJson(tunnel, { type: "ws-close", id: frame.id });
163
+ activeWebSockets.delete(frame.id);
164
+ });
165
+ upstream.on("error", (err) => {
166
+ logger.error(`WS upstream error (id=${frame.id}): ${err.message}`);
167
+ sendJson(tunnel, { type: "ws-close", id: frame.id });
168
+ activeWebSockets.delete(frame.id);
169
+ });
170
+ }
171
+ function handleWsData(frame) {
172
+ const ws = activeWebSockets.get(frame.id);
173
+ if (ws && ws.readyState === WebSocket.OPEN) {
174
+ ws.send(frame.data);
175
+ }
176
+ }
177
+ function handleWsClose(frame) {
178
+ const ws = activeWebSockets.get(frame.id);
179
+ if (ws) {
180
+ ws.close(1e3);
181
+ activeWebSockets.delete(frame.id);
182
+ }
183
+ }
184
+ function sendJson(ws, obj) {
185
+ if (ws.readyState === WebSocket.OPEN) {
186
+ ws.send(JSON.stringify(obj));
187
+ }
188
+ }
189
+ function cleanupAll() {
190
+ for (const [id, req] of activeRequests) {
191
+ req.destroy();
192
+ activeRequests.delete(id);
193
+ }
194
+ for (const [id, ws] of activeWebSockets) {
195
+ ws.close(1e3);
196
+ activeWebSockets.delete(id);
197
+ }
198
+ }
199
+ process.on("SIGTERM", () => {
200
+ logger.info("SIGTERM received, shutting down");
201
+ shuttingDown = true;
202
+ cleanupAll();
203
+ process.exit(0);
204
+ });
205
+ process.on("SIGINT", () => {
206
+ logger.info("SIGINT received, shutting down");
207
+ shuttingDown = true;
208
+ cleanupAll();
209
+ process.exit(0);
210
+ });
211
+ connect();
212
+ //# sourceMappingURL=tunnel-client.js.map
@@ -0,0 +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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rallycry/conveyor-agent",
3
- "version": "7.0.0",
3
+ "version": "7.0.2",
4
4
  "description": "Conveyor Agent Runner v7.0 - Agent-as-User architecture with BaseService patterns",
5
5
  "keywords": [
6
6
  "agent",
@@ -11,7 +11,8 @@
11
11
  ],
12
12
  "license": "MIT",
13
13
  "bin": {
14
- "conveyor-agent": "dist/cli.js"
14
+ "conveyor-agent": "dist/cli.js",
15
+ "conveyor-tunnel-client": "dist/tunnel-client.js"
15
16
  },
16
17
  "files": [
17
18
  "dist"
@@ -37,10 +38,12 @@
37
38
  "@anthropic-ai/claude-agent-sdk": "^0.2.89",
38
39
  "socket.io-client": "^4.7.4",
39
40
  "winston": "^3.11.0",
41
+ "ws": "^8.18.0",
40
42
  "zod": "^3.25.76"
41
43
  },
42
44
  "devDependencies": {
43
45
  "@project/shared": "workspace:*",
46
+ "@types/ws": "^8.18.1",
44
47
  "tsup": "^8.0.0",
45
48
  "typescript": "^5.3.0",
46
49
  "vitest": "^4.0.17"