doer-agent 0.7.5 → 0.7.7
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/agent-fs-rpc.js +2 -49
- package/dist/agent-http-proxy-rpc.js +375 -0
- package/dist/agent-runtime-utils.js +3 -0
- package/dist/agent.js +9 -1
- package/package.json +1 -1
package/dist/agent-fs-rpc.js
CHANGED
|
@@ -2,7 +2,7 @@ import path from "node:path";
|
|
|
2
2
|
import { mkdir, open, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
3
3
|
import crypto from "node:crypto";
|
|
4
4
|
import { StringCodec } from "nats";
|
|
5
|
-
import {
|
|
5
|
+
import { extract as extractTar } from "tar";
|
|
6
6
|
import { validateImageBytes } from "./agent-runtime-utils.js";
|
|
7
7
|
const fsRpcCodec = StringCodec();
|
|
8
8
|
function normalizeFsRpcPath(workspaceRoot, rawPath) {
|
|
@@ -11,13 +11,10 @@ function normalizeFsRpcPath(workspaceRoot, rawPath) {
|
|
|
11
11
|
const useAbsolute = path.isAbsolute(normalizedRaw);
|
|
12
12
|
const rel = normalizedRaw.replace(/^\/+/, "") || ".";
|
|
13
13
|
const abs = useAbsolute ? path.resolve(normalizedRaw) : path.resolve(workspaceRoot, rel);
|
|
14
|
-
if (
|
|
14
|
+
if (abs !== workspaceRoot && !abs.startsWith(workspaceRoot + path.sep)) {
|
|
15
15
|
throw new Error("path escapes workspace root");
|
|
16
16
|
}
|
|
17
17
|
const formatPath = (target) => {
|
|
18
|
-
if (useAbsolute) {
|
|
19
|
-
return target.split(path.sep).join("/") || "/";
|
|
20
|
-
}
|
|
21
18
|
return path.relative(workspaceRoot, target).split(path.sep).join("/") || ".";
|
|
22
19
|
};
|
|
23
20
|
return { abs, formatPath };
|
|
@@ -30,7 +27,6 @@ function parseFsRpcAction(value) {
|
|
|
30
27
|
value === "write_text" ||
|
|
31
28
|
value === "download_file" ||
|
|
32
29
|
value === "delete_path" ||
|
|
33
|
-
value === "archive_dir" ||
|
|
34
30
|
value === "extract_archive") {
|
|
35
31
|
return value;
|
|
36
32
|
}
|
|
@@ -92,18 +88,6 @@ function inferMimeType(filePath) {
|
|
|
92
88
|
function sha256Hex(bytes) {
|
|
93
89
|
return crypto.createHash("sha256").update(bytes).digest("hex");
|
|
94
90
|
}
|
|
95
|
-
async function createTarGzipBuffer(cwd, entries) {
|
|
96
|
-
const stream = createTar({
|
|
97
|
-
cwd,
|
|
98
|
-
gzip: true,
|
|
99
|
-
portable: true,
|
|
100
|
-
}, entries);
|
|
101
|
-
const chunks = [];
|
|
102
|
-
for await (const chunk of stream) {
|
|
103
|
-
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
104
|
-
}
|
|
105
|
-
return Buffer.concat(chunks);
|
|
106
|
-
}
|
|
107
91
|
async function executeFsRpc(args) {
|
|
108
92
|
const action = parseFsRpcAction(args.request.action);
|
|
109
93
|
const { abs, formatPath } = normalizeFsRpcPath(args.workspaceRoot, args.request.path);
|
|
@@ -146,37 +130,6 @@ async function executeFsRpc(args) {
|
|
|
146
130
|
total: items.length,
|
|
147
131
|
};
|
|
148
132
|
}
|
|
149
|
-
if (action === "archive_dir") {
|
|
150
|
-
const entry = await stat(abs);
|
|
151
|
-
if (!entry.isDirectory()) {
|
|
152
|
-
throw new Error("path is not a directory");
|
|
153
|
-
}
|
|
154
|
-
const rawArchivePath = typeof args.request.archivePath === "string" ? args.request.archivePath : "";
|
|
155
|
-
if (!rawArchivePath) {
|
|
156
|
-
throw new Error("archivePath is required");
|
|
157
|
-
}
|
|
158
|
-
const archiveTarget = normalizeFsRpcPath(args.workspaceRoot, rawArchivePath);
|
|
159
|
-
try {
|
|
160
|
-
const manifestEntry = await stat(path.join(abs, "SKILL.md"));
|
|
161
|
-
if (!manifestEntry.isFile()) {
|
|
162
|
-
throw new Error("Selected skill directory must contain SKILL.md");
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
catch {
|
|
166
|
-
throw new Error("Selected skill directory must contain SKILL.md");
|
|
167
|
-
}
|
|
168
|
-
await mkdir(path.dirname(archiveTarget.abs), { recursive: true });
|
|
169
|
-
const archiveBytes = await createTarGzipBuffer(abs, ["."]);
|
|
170
|
-
await writeFile(archiveTarget.abs, archiveBytes);
|
|
171
|
-
const archiveStat = await stat(archiveTarget.abs);
|
|
172
|
-
return {
|
|
173
|
-
ok: true,
|
|
174
|
-
action,
|
|
175
|
-
path: formatPath(abs),
|
|
176
|
-
archivePath: archiveTarget.formatPath(archiveTarget.abs),
|
|
177
|
-
size: archiveStat.size,
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
133
|
if (action === "upload_file") {
|
|
181
134
|
const entry = await stat(abs);
|
|
182
135
|
if (!entry.isFile()) {
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import { appendFile, readFile, writeFile, mkdir, unlink } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { StringCodec } from "nats";
|
|
4
|
+
const proxyRpcCodec = StringCodec();
|
|
5
|
+
const PROXY_ID_PATTERN = /^[a-z0-9][a-z0-9-]{1,30}[a-z0-9]$/;
|
|
6
|
+
const MAX_PROXY_BODY_BYTES = 5 * 1024 * 1024;
|
|
7
|
+
const MAX_PROXY_LOGS = 100;
|
|
8
|
+
function getProxyRegistryPath(workspaceRoot) {
|
|
9
|
+
return path.join(workspaceRoot, ".doer-agent", "http-proxies.json");
|
|
10
|
+
}
|
|
11
|
+
function getProxyLogsPath(workspaceRoot, proxyId) {
|
|
12
|
+
return path.join(workspaceRoot, ".doer-agent", "http-proxy-logs", `${proxyId}.jsonl`);
|
|
13
|
+
}
|
|
14
|
+
function slugify(value) {
|
|
15
|
+
const slug = value
|
|
16
|
+
.trim()
|
|
17
|
+
.toLowerCase()
|
|
18
|
+
.replace(/[^a-z0-9-]+/g, "-")
|
|
19
|
+
.replace(/^-+|-+$/g, "")
|
|
20
|
+
.replace(/-{2,}/g, "-")
|
|
21
|
+
.slice(0, 32);
|
|
22
|
+
return PROXY_ID_PATTERN.test(slug) ? slug : `p${Date.now().toString(36)}`;
|
|
23
|
+
}
|
|
24
|
+
function normalizeProxyId(value) {
|
|
25
|
+
const id = typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
26
|
+
if (!PROXY_ID_PATTERN.test(id)) {
|
|
27
|
+
throw new Error("invalid proxyId");
|
|
28
|
+
}
|
|
29
|
+
return id;
|
|
30
|
+
}
|
|
31
|
+
function normalizeName(value) {
|
|
32
|
+
if (typeof value !== "string") {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const name = value.trim();
|
|
36
|
+
return name ? name.slice(0, 120) : null;
|
|
37
|
+
}
|
|
38
|
+
function normalizeHost(value) {
|
|
39
|
+
const host = typeof value === "string" && value.trim() ? value.trim() : "127.0.0.1";
|
|
40
|
+
if (host !== "127.0.0.1" && host !== "localhost") {
|
|
41
|
+
throw new Error("proxy host must be localhost or 127.0.0.1");
|
|
42
|
+
}
|
|
43
|
+
return host;
|
|
44
|
+
}
|
|
45
|
+
function normalizePort(value) {
|
|
46
|
+
const port = Number(value);
|
|
47
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
48
|
+
throw new Error("port must be between 1 and 65535");
|
|
49
|
+
}
|
|
50
|
+
return port;
|
|
51
|
+
}
|
|
52
|
+
function normalizeMethod(value) {
|
|
53
|
+
const method = typeof value === "string" ? value.trim().toUpperCase() : "GET";
|
|
54
|
+
if (!/^[A-Z]+$/.test(method)) {
|
|
55
|
+
throw new Error("invalid method");
|
|
56
|
+
}
|
|
57
|
+
return method;
|
|
58
|
+
}
|
|
59
|
+
function normalizePath(value) {
|
|
60
|
+
const raw = typeof value === "string" && value ? value : "/";
|
|
61
|
+
return raw.startsWith("/") ? raw : `/${raw}`;
|
|
62
|
+
}
|
|
63
|
+
function normalizeHeaders(value) {
|
|
64
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
65
|
+
return {};
|
|
66
|
+
}
|
|
67
|
+
const out = {};
|
|
68
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
69
|
+
if (typeof raw !== "string") {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const normalizedKey = key.trim().toLowerCase();
|
|
73
|
+
if (!normalizedKey || normalizedKey === "host" || normalizedKey === "connection") {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
out[normalizedKey] = raw;
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
function normalizeProxyRecord(value) {
|
|
81
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
const row = value;
|
|
85
|
+
const id = typeof row.id === "string" ? row.id.trim().toLowerCase() : "";
|
|
86
|
+
const host = normalizeHost(row.host);
|
|
87
|
+
const port = Number(row.port);
|
|
88
|
+
const createdAt = typeof row.createdAt === "string" ? row.createdAt : new Date().toISOString();
|
|
89
|
+
const updatedAt = typeof row.updatedAt === "string" ? row.updatedAt : createdAt;
|
|
90
|
+
if (!PROXY_ID_PATTERN.test(id) || !Number.isInteger(port) || port < 1 || port > 65535) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
id,
|
|
95
|
+
name: normalizeName(row.name),
|
|
96
|
+
host,
|
|
97
|
+
port,
|
|
98
|
+
enabled: row.enabled !== false,
|
|
99
|
+
createdAt,
|
|
100
|
+
updatedAt,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function normalizeProxyLogRecord(value) {
|
|
104
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
const row = value;
|
|
108
|
+
const id = typeof row.id === "string" && row.id.trim() ? row.id.trim() : "";
|
|
109
|
+
const at = typeof row.at === "string" && row.at.trim() ? row.at.trim() : "";
|
|
110
|
+
const method = typeof row.method === "string" && row.method.trim() ? row.method.trim().toUpperCase() : "";
|
|
111
|
+
const requestPath = typeof row.path === "string" && row.path.trim() ? row.path.trim() : "/";
|
|
112
|
+
const status = typeof row.status === "number" && Number.isInteger(row.status) ? row.status : null;
|
|
113
|
+
const durationMs = typeof row.durationMs === "number" && Number.isFinite(row.durationMs) ? Math.max(0, Math.round(row.durationMs)) : 0;
|
|
114
|
+
const requestBytes = typeof row.requestBytes === "number" && Number.isFinite(row.requestBytes) ? Math.max(0, Math.round(row.requestBytes)) : 0;
|
|
115
|
+
const responseBytes = typeof row.responseBytes === "number" && Number.isFinite(row.responseBytes) ? Math.max(0, Math.round(row.responseBytes)) : 0;
|
|
116
|
+
if (!id || !at || !method) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
id,
|
|
121
|
+
at,
|
|
122
|
+
method,
|
|
123
|
+
path: requestPath,
|
|
124
|
+
status,
|
|
125
|
+
durationMs,
|
|
126
|
+
requestBytes,
|
|
127
|
+
responseBytes,
|
|
128
|
+
error: typeof row.error === "string" && row.error.trim() ? row.error.trim().slice(0, 500) : null,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
async function readProxyRegistry(workspaceRoot) {
|
|
132
|
+
const raw = await readFile(getProxyRegistryPath(workspaceRoot), "utf8").catch(() => "");
|
|
133
|
+
if (!raw) {
|
|
134
|
+
return [];
|
|
135
|
+
}
|
|
136
|
+
const parsed = JSON.parse(raw);
|
|
137
|
+
const rows = Array.isArray(parsed) ? parsed : Array.isArray(parsed?.proxies) ? parsed.proxies : [];
|
|
138
|
+
return rows
|
|
139
|
+
.map((row) => {
|
|
140
|
+
try {
|
|
141
|
+
return normalizeProxyRecord(row);
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
.filter((row) => Boolean(row));
|
|
148
|
+
}
|
|
149
|
+
async function writeProxyRegistry(workspaceRoot, proxies) {
|
|
150
|
+
const registryPath = getProxyRegistryPath(workspaceRoot);
|
|
151
|
+
await mkdir(path.dirname(registryPath), { recursive: true });
|
|
152
|
+
await writeFile(registryPath, `${JSON.stringify({ proxies }, null, 2)}\n`, "utf8");
|
|
153
|
+
}
|
|
154
|
+
function normalizeLimit(value, fallback) {
|
|
155
|
+
const numeric = Number(value);
|
|
156
|
+
if (!Number.isFinite(numeric)) {
|
|
157
|
+
return fallback;
|
|
158
|
+
}
|
|
159
|
+
return Math.max(1, Math.min(Math.floor(numeric), 1000));
|
|
160
|
+
}
|
|
161
|
+
async function readProxyLogs(workspaceRoot, proxyId, limit) {
|
|
162
|
+
const raw = await readFile(getProxyLogsPath(workspaceRoot, proxyId), "utf8").catch(() => "");
|
|
163
|
+
if (!raw) {
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
return raw
|
|
167
|
+
.split("\n")
|
|
168
|
+
.map((line) => line.trim())
|
|
169
|
+
.filter(Boolean)
|
|
170
|
+
.slice(-limit)
|
|
171
|
+
.map((line) => {
|
|
172
|
+
try {
|
|
173
|
+
return normalizeProxyLogRecord(JSON.parse(line));
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
.filter((log) => Boolean(log));
|
|
180
|
+
}
|
|
181
|
+
async function createProxy(workspaceRoot, request) {
|
|
182
|
+
const proxies = await readProxyRegistry(workspaceRoot);
|
|
183
|
+
const name = normalizeName(request.name);
|
|
184
|
+
const port = normalizePort(request.port);
|
|
185
|
+
const host = normalizeHost(request.host);
|
|
186
|
+
const requestedId = typeof request.proxyId === "string" && request.proxyId.trim()
|
|
187
|
+
? normalizeProxyId(request.proxyId)
|
|
188
|
+
: slugify(name || `p-${port}`);
|
|
189
|
+
let id = requestedId;
|
|
190
|
+
for (let index = 2; proxies.some((proxy) => proxy.id === id); index += 1) {
|
|
191
|
+
id = `${requestedId.slice(0, 26)}-${index}`;
|
|
192
|
+
}
|
|
193
|
+
const now = new Date().toISOString();
|
|
194
|
+
const proxy = {
|
|
195
|
+
id,
|
|
196
|
+
name,
|
|
197
|
+
host,
|
|
198
|
+
port,
|
|
199
|
+
enabled: request.enabled !== false,
|
|
200
|
+
createdAt: now,
|
|
201
|
+
updatedAt: now,
|
|
202
|
+
};
|
|
203
|
+
await writeProxyRegistry(workspaceRoot, [...proxies, proxy].sort((a, b) => b.createdAt.localeCompare(a.createdAt)));
|
|
204
|
+
return proxy;
|
|
205
|
+
}
|
|
206
|
+
async function updateProxy(workspaceRoot, request) {
|
|
207
|
+
const proxyId = normalizeProxyId(request.proxyId);
|
|
208
|
+
const proxies = await readProxyRegistry(workspaceRoot);
|
|
209
|
+
const proxy = proxies.find((item) => item.id === proxyId);
|
|
210
|
+
if (!proxy) {
|
|
211
|
+
throw new Error("proxy not found");
|
|
212
|
+
}
|
|
213
|
+
const updated = {
|
|
214
|
+
...proxy,
|
|
215
|
+
name: request.name === undefined ? proxy.name : normalizeName(request.name),
|
|
216
|
+
host: request.host === undefined ? proxy.host : normalizeHost(request.host),
|
|
217
|
+
port: request.port === undefined ? proxy.port : normalizePort(request.port),
|
|
218
|
+
enabled: request.enabled === undefined ? proxy.enabled : request.enabled !== false,
|
|
219
|
+
updatedAt: new Date().toISOString(),
|
|
220
|
+
};
|
|
221
|
+
await writeProxyRegistry(workspaceRoot, proxies.map((item) => (item.id === proxyId ? updated : item)));
|
|
222
|
+
return updated;
|
|
223
|
+
}
|
|
224
|
+
async function deleteProxy(workspaceRoot, proxyId) {
|
|
225
|
+
const proxies = await readProxyRegistry(workspaceRoot);
|
|
226
|
+
await writeProxyRegistry(workspaceRoot, proxies.filter((proxy) => proxy.id !== proxyId));
|
|
227
|
+
await unlink(getProxyLogsPath(workspaceRoot, proxyId)).catch(() => undefined);
|
|
228
|
+
}
|
|
229
|
+
async function appendProxyLog(workspaceRoot, proxyId, log) {
|
|
230
|
+
const entry = {
|
|
231
|
+
id: `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`,
|
|
232
|
+
at: new Date().toISOString(),
|
|
233
|
+
...log,
|
|
234
|
+
};
|
|
235
|
+
const logsPath = getProxyLogsPath(workspaceRoot, proxyId);
|
|
236
|
+
await mkdir(path.dirname(logsPath), { recursive: true });
|
|
237
|
+
await appendFile(logsPath, `${JSON.stringify(entry)}\n`, "utf8");
|
|
238
|
+
}
|
|
239
|
+
async function readProxyLogsForRequest(workspaceRoot, request) {
|
|
240
|
+
const proxyId = normalizeProxyId(request.proxyId);
|
|
241
|
+
const proxy = (await readProxyRegistry(workspaceRoot)).find((item) => item.id === proxyId);
|
|
242
|
+
if (!proxy) {
|
|
243
|
+
throw new Error("proxy not found");
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
proxy,
|
|
247
|
+
events: await readProxyLogs(workspaceRoot, proxyId, normalizeLimit(request.limit, MAX_PROXY_LOGS)),
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
async function handleProxyFetch(workspaceRoot, request) {
|
|
251
|
+
const proxyId = normalizeProxyId(request.proxyId);
|
|
252
|
+
const proxy = (await readProxyRegistry(workspaceRoot)).find((item) => item.id === proxyId);
|
|
253
|
+
const startedAt = Date.now();
|
|
254
|
+
const method = normalizeMethod(request.method);
|
|
255
|
+
const requestPath = normalizePath(request.path);
|
|
256
|
+
const bodyBase64 = typeof request.bodyBase64 === "string" ? request.bodyBase64 : "";
|
|
257
|
+
const body = bodyBase64 ? Buffer.from(bodyBase64, "base64") : undefined;
|
|
258
|
+
const requestBytes = body?.byteLength ?? 0;
|
|
259
|
+
let status = null;
|
|
260
|
+
let responseBytes = 0;
|
|
261
|
+
let errorMessage = null;
|
|
262
|
+
const finishLog = async () => {
|
|
263
|
+
await appendProxyLog(workspaceRoot, proxyId, {
|
|
264
|
+
method,
|
|
265
|
+
path: requestPath,
|
|
266
|
+
status,
|
|
267
|
+
durationMs: Date.now() - startedAt,
|
|
268
|
+
requestBytes,
|
|
269
|
+
responseBytes,
|
|
270
|
+
error: errorMessage,
|
|
271
|
+
}).catch(() => undefined);
|
|
272
|
+
};
|
|
273
|
+
if (!proxy) {
|
|
274
|
+
throw new Error("proxy not found");
|
|
275
|
+
}
|
|
276
|
+
if (!proxy.enabled) {
|
|
277
|
+
errorMessage = "proxy disabled";
|
|
278
|
+
await finishLog();
|
|
279
|
+
throw new Error("proxy disabled");
|
|
280
|
+
}
|
|
281
|
+
const headers = normalizeHeaders(request.headers);
|
|
282
|
+
if ((body?.byteLength ?? 0) > MAX_PROXY_BODY_BYTES) {
|
|
283
|
+
errorMessage = "proxy request body too large";
|
|
284
|
+
await finishLog();
|
|
285
|
+
throw new Error("proxy request body too large");
|
|
286
|
+
}
|
|
287
|
+
try {
|
|
288
|
+
const url = new URL(requestPath, `http://${proxy.host}:${proxy.port}`);
|
|
289
|
+
const response = await fetch(url, {
|
|
290
|
+
method,
|
|
291
|
+
headers,
|
|
292
|
+
body: method === "GET" || method === "HEAD" ? undefined : body,
|
|
293
|
+
redirect: "manual",
|
|
294
|
+
});
|
|
295
|
+
status = response.status;
|
|
296
|
+
const responseBuffer = Buffer.from(await response.arrayBuffer());
|
|
297
|
+
responseBytes = responseBuffer.byteLength;
|
|
298
|
+
if (responseBuffer.byteLength > MAX_PROXY_BODY_BYTES) {
|
|
299
|
+
errorMessage = "proxy response body too large";
|
|
300
|
+
await finishLog();
|
|
301
|
+
throw new Error("proxy response body too large");
|
|
302
|
+
}
|
|
303
|
+
const responseHeaders = {};
|
|
304
|
+
response.headers.forEach((value, key) => {
|
|
305
|
+
responseHeaders[key] = value;
|
|
306
|
+
});
|
|
307
|
+
await finishLog();
|
|
308
|
+
return {
|
|
309
|
+
status: response.status,
|
|
310
|
+
statusText: response.statusText,
|
|
311
|
+
headers: responseHeaders,
|
|
312
|
+
bodyBase64: responseBuffer.toString("base64"),
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
catch (error) {
|
|
316
|
+
if (!errorMessage) {
|
|
317
|
+
errorMessage = error instanceof Error ? error.message : String(error);
|
|
318
|
+
await finishLog();
|
|
319
|
+
}
|
|
320
|
+
throw error;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
async function executeProxyRpc(args) {
|
|
324
|
+
const action = args.request.action === "create" || args.request.action === "update" || args.request.action === "delete" || args.request.action === "logs" || args.request.action === "handle"
|
|
325
|
+
? args.request.action
|
|
326
|
+
: "list";
|
|
327
|
+
if (action === "list") {
|
|
328
|
+
return { ok: true, action, proxies: await readProxyRegistry(args.workspaceRoot) };
|
|
329
|
+
}
|
|
330
|
+
if (action === "create") {
|
|
331
|
+
return { ok: true, action, proxy: await createProxy(args.workspaceRoot, args.request) };
|
|
332
|
+
}
|
|
333
|
+
if (action === "update") {
|
|
334
|
+
return { ok: true, action, proxy: await updateProxy(args.workspaceRoot, args.request) };
|
|
335
|
+
}
|
|
336
|
+
if (action === "delete") {
|
|
337
|
+
await deleteProxy(args.workspaceRoot, normalizeProxyId(args.request.proxyId));
|
|
338
|
+
return { ok: true, action };
|
|
339
|
+
}
|
|
340
|
+
if (action === "logs") {
|
|
341
|
+
return { ok: true, action, ...await readProxyLogsForRequest(args.workspaceRoot, args.request) };
|
|
342
|
+
}
|
|
343
|
+
return { ok: true, action, response: await handleProxyFetch(args.workspaceRoot, args.request) };
|
|
344
|
+
}
|
|
345
|
+
export async function handleHttpProxyRpcMessage(args) {
|
|
346
|
+
let requestId = "unknown";
|
|
347
|
+
try {
|
|
348
|
+
const request = JSON.parse(proxyRpcCodec.decode(args.msg.data));
|
|
349
|
+
requestId = typeof request.requestId === "string" ? request.requestId : "unknown";
|
|
350
|
+
const payload = await executeProxyRpc({ workspaceRoot: args.workspaceRoot, request });
|
|
351
|
+
args.msg.respond(proxyRpcCodec.encode(JSON.stringify({ requestId, ...payload })));
|
|
352
|
+
}
|
|
353
|
+
catch (error) {
|
|
354
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
355
|
+
args.onError?.(`http proxy rpc failed requestId=${requestId} error=${message}`);
|
|
356
|
+
args.msg.respond(proxyRpcCodec.encode(JSON.stringify({ requestId, ok: false, error: message })));
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
export function subscribeToHttpProxyRpc(args) {
|
|
360
|
+
args.nc.subscribe(args.subject, {
|
|
361
|
+
callback: (error, msg) => {
|
|
362
|
+
if (error) {
|
|
363
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
364
|
+
args.onError(`http proxy rpc subscription error: ${message}`);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
void handleHttpProxyRpcMessage({
|
|
368
|
+
msg,
|
|
369
|
+
workspaceRoot: args.workspaceRoot,
|
|
370
|
+
onError: args.onError,
|
|
371
|
+
});
|
|
372
|
+
},
|
|
373
|
+
});
|
|
374
|
+
args.onInfo(`http proxy rpc subscribed subject=${args.subject}`);
|
|
375
|
+
}
|
|
@@ -25,6 +25,9 @@ export function buildAgentFsRpcSubject(userId, agentId) {
|
|
|
25
25
|
export function buildAgentDaemonRpcSubject(userId, agentId) {
|
|
26
26
|
return `doer.agent.daemon.rpc.${sanitizeUserId(userId)}.${agentId.trim()}`;
|
|
27
27
|
}
|
|
28
|
+
export function buildAgentHttpProxyRpcSubject(userId, agentId) {
|
|
29
|
+
return `doer.agent.http.proxy.rpc.${sanitizeUserId(userId)}.${agentId.trim()}`;
|
|
30
|
+
}
|
|
28
31
|
export function buildAgentMaintenanceRpcSubject(userId, agentId) {
|
|
29
32
|
return `doer.agent.maintenance.rpc.${sanitizeUserId(userId)}.${agentId.trim()}`;
|
|
30
33
|
}
|
package/dist/agent.js
CHANGED
|
@@ -13,8 +13,9 @@ import { connectBootstrapWithRetry } from "./agent-jetstream.js";
|
|
|
13
13
|
import { runConnectedAgentSession } from "./agent-session-loop.js";
|
|
14
14
|
import { subscribeToSkillRpc } from "./agent-skill-rpc.js";
|
|
15
15
|
import { subscribeToMaintenanceRpc } from "./agent-maintenance-rpc.js";
|
|
16
|
+
import { subscribeToHttpProxyRpc } from "./agent-http-proxy-rpc.js";
|
|
16
17
|
import { sendSignalToTaskProcess } from "./agent-task-execution.js";
|
|
17
|
-
import { buildAgentCodexAppEventsSubject, buildAgentCodexAppRpcSubject, buildAgentDaemonRpcSubject, buildAgentFsRpcSubject, buildAgentGitRpcSubject, buildAgentMaintenanceRpcSubject, buildAgentSettingsRpcSubject, buildAgentSkillRpcSubject, formatLocalTimestamp, parseArgs, resolveAgentVersion, resolveArgOrEnv, resolveContainerReachableServerBaseUrl, sanitizeUserId, sleep, } from "./agent-runtime-utils.js";
|
|
18
|
+
import { buildAgentCodexAppEventsSubject, buildAgentCodexAppRpcSubject, buildAgentDaemonRpcSubject, buildAgentFsRpcSubject, buildAgentGitRpcSubject, buildAgentHttpProxyRpcSubject, buildAgentMaintenanceRpcSubject, buildAgentSettingsRpcSubject, buildAgentSkillRpcSubject, formatLocalTimestamp, parseArgs, resolveAgentVersion, resolveArgOrEnv, resolveContainerReachableServerBaseUrl, sanitizeUserId, sleep, } from "./agent-runtime-utils.js";
|
|
18
19
|
import { createRuntimeEnvHelpers } from "./agent-runtime-env.js";
|
|
19
20
|
import { createEventPersistenceHelpers, heartbeatAgentSession, postJson, } from "./agent-runtime-io.js";
|
|
20
21
|
import { handleSettingsRpcMessage } from "./agent-settings-rpc.js";
|
|
@@ -306,6 +307,13 @@ async function main() {
|
|
|
306
307
|
onInfo: writeAgentInfo,
|
|
307
308
|
onError: writeAgentError,
|
|
308
309
|
});
|
|
310
|
+
subscribeToHttpProxyRpc({
|
|
311
|
+
nc: jetstream.nc,
|
|
312
|
+
subject: buildAgentHttpProxyRpcSubject(userId, initialAgentId),
|
|
313
|
+
workspaceRoot: resolveWorkspaceRoot(),
|
|
314
|
+
onInfo: writeAgentInfo,
|
|
315
|
+
onError: writeAgentError,
|
|
316
|
+
});
|
|
309
317
|
},
|
|
310
318
|
onInfraError: writeAgentInfraError,
|
|
311
319
|
sleep,
|