@acnlabs/acn-cli 1.0.7 → 1.0.9
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 +6 -1
- package/dist/index.js +127 -158
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -51,8 +51,12 @@ Register this agent with ACN. Saves `api_key` and `agent_id` automatically.
|
|
|
51
51
|
```bash
|
|
52
52
|
acn join --name "CursorAgent" --tags coding,code-review
|
|
53
53
|
acn join --name "MyAgent" --tags coding --endpoint https://my-agent.example.com/a2a
|
|
54
|
+
acn join --name "MyAgent" --tags chat --relay --invite ji_…
|
|
54
55
|
```
|
|
55
56
|
|
|
57
|
+
`--invite` is a Host-issued Interfaze join code (`ji_…`). It is **not** an
|
|
58
|
+
owner account id. Omit it when you are not connecting through a `/join` page.
|
|
59
|
+
|
|
56
60
|
### `acn heartbeat`
|
|
57
61
|
|
|
58
62
|
Send a heartbeat to remain `online`. **Most agents do not need to run
|
|
@@ -108,7 +112,8 @@ acn listen --runtime http \
|
|
|
108
112
|
|
|
109
113
|
`--chat-token` is deprecated/ignored. Task / Org wakes still use `--wake-url` /
|
|
110
114
|
`--wake-exec`. Chat envelopes skip wake and use the complete → writeback path
|
|
111
|
-
instead.
|
|
115
|
+
instead. CLI **1.0.9+** completes official hops via Host; `--chat-complete-*`
|
|
116
|
+
is BYO only and may be omitted for official-only.
|
|
112
117
|
|
|
113
118
|
**Coverage:** only traffic that arrives over the Mode B relay. Open Task Pool
|
|
114
119
|
rows that were never pushed as A2A still need `acn tasks list` / reconcile.
|
package/dist/index.js
CHANGED
|
@@ -31,7 +31,7 @@ var require_package = __commonJS({
|
|
|
31
31
|
"package.json"(exports2, module2) {
|
|
32
32
|
module2.exports = {
|
|
33
33
|
name: "@acnlabs/acn-cli",
|
|
34
|
-
version: "1.0.
|
|
34
|
+
version: "1.0.9",
|
|
35
35
|
description: "Official CLI for ACN (Agent Collaboration Network) \u2014 zero-integration agent access",
|
|
36
36
|
main: "dist/index.js",
|
|
37
37
|
bin: {
|
|
@@ -365,6 +365,9 @@ function joinCommand() {
|
|
|
365
365
|
).option(
|
|
366
366
|
"--base-url <url>",
|
|
367
367
|
"Override ACN origin (no /api/v1). Overrides --region and ACN_BASE_URL for this join."
|
|
368
|
+
).option(
|
|
369
|
+
"--invite <code>",
|
|
370
|
+
"Host-issued human join invite (ji_\u2026). Stored as metadata only \u2014 not an owner account."
|
|
368
371
|
).action(
|
|
369
372
|
async (opts) => {
|
|
370
373
|
if (opts.region && opts.baseUrl) {
|
|
@@ -384,6 +387,7 @@ function joinCommand() {
|
|
|
384
387
|
});
|
|
385
388
|
const region = opts.region?.trim().toLowerCase() === "cn" || opts.region?.trim().toLowerCase() === "global" ? opts.region.trim().toLowerCase() : inferRegion(base_url);
|
|
386
389
|
const tags = opts.tags.split(",").map((s) => s.trim()).filter(Boolean);
|
|
390
|
+
const invite = opts.invite?.trim();
|
|
387
391
|
const body = {
|
|
388
392
|
name: opts.name,
|
|
389
393
|
description: opts.description ?? `${opts.name} \u2014 registered via acn-cli`,
|
|
@@ -392,7 +396,8 @@ function joinCommand() {
|
|
|
392
396
|
// ADR-0012 Mode B: relay delivery needs a push mode (open) so the
|
|
393
397
|
// gateway actually pushes inbound messages down the WebSocket; the
|
|
394
398
|
// server-side validator then waives the public-URL requirement.
|
|
395
|
-
...opts.relay ? { delivery: "relay", communication_policy: { mode: "open" } } : {}
|
|
399
|
+
...opts.relay ? { delivery: "relay", communication_policy: { mode: "open" } } : {},
|
|
400
|
+
...invite ? { invite } : {}
|
|
396
401
|
};
|
|
397
402
|
try {
|
|
398
403
|
const res = await acnPost("/agents/join", body, {
|
|
@@ -1751,112 +1756,11 @@ var DedupeStore = class {
|
|
|
1751
1756
|
|
|
1752
1757
|
// src/commands/official-hop-door.ts
|
|
1753
1758
|
var import_node_http = __toESM(require("http"));
|
|
1754
|
-
function
|
|
1759
|
+
function canCompleteOfficialHop(opts) {
|
|
1755
1760
|
return Boolean(
|
|
1756
1761
|
opts.inferencePath === "official" && opts.hopId?.trim() && asHostInferenceUrl(opts.hostInferenceUrl) && opts.jwt?.trim()
|
|
1757
1762
|
);
|
|
1758
1763
|
}
|
|
1759
|
-
function readBody(req) {
|
|
1760
|
-
return new Promise((resolve, reject) => {
|
|
1761
|
-
const chunks = [];
|
|
1762
|
-
req.on("data", (c) => chunks.push(Buffer.from(c)));
|
|
1763
|
-
req.on("end", () => resolve(Buffer.concat(chunks)));
|
|
1764
|
-
req.on("error", reject);
|
|
1765
|
-
});
|
|
1766
|
-
}
|
|
1767
|
-
function send(res, status, body, contentType = "application/json") {
|
|
1768
|
-
res.writeHead(status, {
|
|
1769
|
-
"content-type": contentType,
|
|
1770
|
-
"content-length": String(body.length)
|
|
1771
|
-
});
|
|
1772
|
-
res.end(body);
|
|
1773
|
-
}
|
|
1774
|
-
async function handleDoorRequest(req, res, opts) {
|
|
1775
|
-
const path = (req.url ?? "").split("?")[0];
|
|
1776
|
-
if (req.method !== "POST" || path !== "/v1/chat/completions" && path !== "/chat/completions") {
|
|
1777
|
-
send(res, 404, Buffer.from('{"error":"not_found"}'));
|
|
1778
|
-
return;
|
|
1779
|
-
}
|
|
1780
|
-
let payload;
|
|
1781
|
-
try {
|
|
1782
|
-
const raw = await readBody(req);
|
|
1783
|
-
payload = JSON.parse(raw.length ? raw.toString("utf-8") : "{}");
|
|
1784
|
-
} catch {
|
|
1785
|
-
send(res, 400, Buffer.from('{"error":"invalid_json"}'));
|
|
1786
|
-
return;
|
|
1787
|
-
}
|
|
1788
|
-
if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
|
|
1789
|
-
send(res, 400, Buffer.from('{"error":"invalid_json"}'));
|
|
1790
|
-
return;
|
|
1791
|
-
}
|
|
1792
|
-
const body = { ...payload };
|
|
1793
|
-
delete body.agent_id;
|
|
1794
|
-
body.hop_id = opts.hopId;
|
|
1795
|
-
const headers = {
|
|
1796
|
-
authorization: `Bearer ${opts.jwt}`,
|
|
1797
|
-
"content-type": "application/json",
|
|
1798
|
-
"X-Hop-Id": opts.hopId
|
|
1799
|
-
};
|
|
1800
|
-
if (opts.agentId) headers["X-Agent-Id"] = opts.agentId;
|
|
1801
|
-
try {
|
|
1802
|
-
const upstream = await opts.fetchFn(opts.upstream, {
|
|
1803
|
-
method: "POST",
|
|
1804
|
-
headers,
|
|
1805
|
-
body: JSON.stringify(body)
|
|
1806
|
-
});
|
|
1807
|
-
const out = Buffer.from(await upstream.arrayBuffer());
|
|
1808
|
-
const ct = upstream.headers.get("content-type") || "application/json";
|
|
1809
|
-
send(res, upstream.status, out, ct);
|
|
1810
|
-
} catch (err) {
|
|
1811
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1812
|
-
send(
|
|
1813
|
-
res,
|
|
1814
|
-
502,
|
|
1815
|
-
Buffer.from(JSON.stringify({ error: `upstream_unreachable:${msg.slice(0, 120)}` }))
|
|
1816
|
-
);
|
|
1817
|
-
}
|
|
1818
|
-
}
|
|
1819
|
-
function closeServer(server) {
|
|
1820
|
-
return new Promise((resolve) => {
|
|
1821
|
-
server.closeAllConnections?.();
|
|
1822
|
-
server.close(() => resolve());
|
|
1823
|
-
});
|
|
1824
|
-
}
|
|
1825
|
-
async function startOfficialHopDoor(opts) {
|
|
1826
|
-
const dest = asHostInferenceUrl(opts.hostInferenceUrl);
|
|
1827
|
-
const hopId = opts.hopId.trim();
|
|
1828
|
-
const jwt = opts.jwt.trim();
|
|
1829
|
-
if (!dest || !hopId || !jwt) return null;
|
|
1830
|
-
const fetchFn = opts.fetchFn ?? fetch;
|
|
1831
|
-
const upstream = `${dest}/chat/completions`;
|
|
1832
|
-
const server = import_node_http.default.createServer((req, res) => {
|
|
1833
|
-
void handleDoorRequest(req, res, {
|
|
1834
|
-
upstream,
|
|
1835
|
-
hopId,
|
|
1836
|
-
agentId: opts.agentId,
|
|
1837
|
-
jwt,
|
|
1838
|
-
fetchFn
|
|
1839
|
-
});
|
|
1840
|
-
});
|
|
1841
|
-
try {
|
|
1842
|
-
await new Promise((resolve, reject) => {
|
|
1843
|
-
server.once("error", reject);
|
|
1844
|
-
server.listen(0, "127.0.0.1", () => resolve());
|
|
1845
|
-
});
|
|
1846
|
-
} catch {
|
|
1847
|
-
return null;
|
|
1848
|
-
}
|
|
1849
|
-
const addr = server.address();
|
|
1850
|
-
const port = typeof addr === "object" && addr ? addr.port : 0;
|
|
1851
|
-
if (!port) {
|
|
1852
|
-
await closeServer(server);
|
|
1853
|
-
return null;
|
|
1854
|
-
}
|
|
1855
|
-
return {
|
|
1856
|
-
baseUrl: `http://127.0.0.1:${port}/v1`,
|
|
1857
|
-
close: () => closeServer(server)
|
|
1858
|
-
};
|
|
1859
|
-
}
|
|
1860
1764
|
|
|
1861
1765
|
// src/commands/chat-writeback.ts
|
|
1862
1766
|
var DEFAULT_COMPLETE_TIMEOUT_MS = 12e4;
|
|
@@ -1879,8 +1783,8 @@ function validateChatWritebackOptions(opts) {
|
|
|
1879
1783
|
}
|
|
1880
1784
|
const hasUrl = Boolean(opts.chatCompleteUrl?.trim());
|
|
1881
1785
|
const hasExec = Boolean(opts.chatCompleteExec?.trim());
|
|
1882
|
-
if (hasUrl
|
|
1883
|
-
return
|
|
1786
|
+
if (hasUrl && hasExec) {
|
|
1787
|
+
return "--chat-writeback: --chat-complete-url and --chat-complete-exec are mutually exclusive (omit both for official-only; BYO hops need exactly one).";
|
|
1884
1788
|
}
|
|
1885
1789
|
return null;
|
|
1886
1790
|
}
|
|
@@ -1914,6 +1818,28 @@ function extractContent(payload) {
|
|
|
1914
1818
|
}
|
|
1915
1819
|
return null;
|
|
1916
1820
|
}
|
|
1821
|
+
function extractChatCompletionContent(payload) {
|
|
1822
|
+
const rec = asRecord2(payload);
|
|
1823
|
+
if (!rec) return null;
|
|
1824
|
+
const choices = rec.choices;
|
|
1825
|
+
if (Array.isArray(choices) && choices.length > 0) {
|
|
1826
|
+
const first = asRecord2(choices[0]);
|
|
1827
|
+
if (first) {
|
|
1828
|
+
const msg = asRecord2(first.message);
|
|
1829
|
+
const fromMsg = msg?.content;
|
|
1830
|
+
if (typeof fromMsg === "string" && fromMsg.trim()) {
|
|
1831
|
+
const t = fromMsg.trim();
|
|
1832
|
+
if (t.toLowerCase() !== "accepted") return t;
|
|
1833
|
+
}
|
|
1834
|
+
const fromText = first.text;
|
|
1835
|
+
if (typeof fromText === "string" && fromText.trim()) {
|
|
1836
|
+
const t = fromText.trim();
|
|
1837
|
+
if (t.toLowerCase() !== "accepted") return t;
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
return extractContent(payload);
|
|
1842
|
+
}
|
|
1917
1843
|
function asNonNegInt(v) {
|
|
1918
1844
|
if (typeof v === "number" && Number.isFinite(v) && v >= 0) {
|
|
1919
1845
|
return Math.floor(v);
|
|
@@ -2133,44 +2059,72 @@ function spawnCompleteExec(event, opts, deps, jwt, door) {
|
|
|
2133
2059
|
child.stdin?.end(body);
|
|
2134
2060
|
});
|
|
2135
2061
|
}
|
|
2136
|
-
async function
|
|
2137
|
-
|
|
2062
|
+
async function completeOfficialViaHost(event, opts, deps, jwt) {
|
|
2063
|
+
const chat = event.chat;
|
|
2064
|
+
if (!chat) return { ok: false, reason: "no_chat_envelope" };
|
|
2065
|
+
if (!canCompleteOfficialHop({
|
|
2066
|
+
inferencePath: chat.inference_path,
|
|
2067
|
+
hopId: chat.hop_id,
|
|
2068
|
+
hostInferenceUrl: chat.host_inference_url,
|
|
2069
|
+
jwt
|
|
2070
|
+
})) {
|
|
2071
|
+
return { ok: false, reason: "official_complete_skipped" };
|
|
2072
|
+
}
|
|
2073
|
+
const model = chat.requested_model?.trim();
|
|
2074
|
+
const text = chat.user_text?.trim();
|
|
2075
|
+
if (!model) return { ok: false, reason: "official_complete_missing_model" };
|
|
2076
|
+
if (!text) return { ok: false, reason: "official_complete_missing_text" };
|
|
2138
2077
|
const logFn = deps.logFn ?? ((line) => console.error(line));
|
|
2078
|
+
logFn(
|
|
2079
|
+
`[acn listen] official_complete chat_id=${chat.chat_id} model=${model}`
|
|
2080
|
+
);
|
|
2081
|
+
const fetchFn = deps.fetchFn ?? fetch;
|
|
2082
|
+
const timeoutMs = opts.completeTimeoutMs ?? DEFAULT_COMPLETE_TIMEOUT_MS;
|
|
2083
|
+
const controller = new AbortController();
|
|
2084
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
2139
2085
|
try {
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
`[acn listen] official_door chat_id=${event.chat.chat_id} base=${door.baseUrl}`
|
|
2160
|
-
);
|
|
2161
|
-
} else {
|
|
2162
|
-
logFn(
|
|
2163
|
-
`[acn listen] official_door_skipped chat_id=${event.chat.chat_id}`
|
|
2164
|
-
);
|
|
2165
|
-
}
|
|
2086
|
+
const res = await fetchFn(`${chat.host_inference_url}/chat/completions`, {
|
|
2087
|
+
method: "POST",
|
|
2088
|
+
headers: {
|
|
2089
|
+
authorization: `Bearer ${jwt}`,
|
|
2090
|
+
"content-type": "application/json",
|
|
2091
|
+
"X-Hop-Id": chat.hop_id,
|
|
2092
|
+
"X-Agent-Id": opts.agentId
|
|
2093
|
+
},
|
|
2094
|
+
body: JSON.stringify({
|
|
2095
|
+
model,
|
|
2096
|
+
messages: [{ role: "user", content: text }],
|
|
2097
|
+
hop_id: chat.hop_id,
|
|
2098
|
+
...chat.max_output_tokens && chat.max_output_tokens > 0 ? { max_tokens: chat.max_output_tokens } : {}
|
|
2099
|
+
}),
|
|
2100
|
+
signal: controller.signal
|
|
2101
|
+
});
|
|
2102
|
+
const raw = await res.text();
|
|
2103
|
+
if (res.status < 200 || res.status >= 300) {
|
|
2104
|
+
return { ok: false, reason: `official_complete_http_${res.status}` };
|
|
2166
2105
|
}
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2106
|
+
let payload;
|
|
2107
|
+
try {
|
|
2108
|
+
payload = JSON.parse(raw);
|
|
2109
|
+
} catch {
|
|
2110
|
+
return { ok: false, reason: "official_complete_invalid_json" };
|
|
2111
|
+
}
|
|
2112
|
+
const content = extractChatCompletionContent(payload);
|
|
2113
|
+
if (!content) return { ok: false, reason: "official_complete_missing_content" };
|
|
2114
|
+
return { ok: true, result: { content } };
|
|
2115
|
+
} catch (err) {
|
|
2116
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2117
|
+
if (controller.signal.aborted || /abort|timeout/i.test(msg)) {
|
|
2118
|
+
return { ok: false, reason: "official_complete_timeout" };
|
|
2171
2119
|
}
|
|
2120
|
+
return { ok: false, reason: msg.slice(0, 200) };
|
|
2121
|
+
} finally {
|
|
2122
|
+
clearTimeout(timer);
|
|
2172
2123
|
}
|
|
2173
2124
|
}
|
|
2125
|
+
async function completeViaExec(event, opts, deps, jwt) {
|
|
2126
|
+
return spawnCompleteExec(event, opts, deps, jwt);
|
|
2127
|
+
}
|
|
2174
2128
|
async function postWriteback(event, complete, opts, deps) {
|
|
2175
2129
|
const chat = event.chat;
|
|
2176
2130
|
if (!chat) return { ok: false, reason: "no_chat_envelope" };
|
|
@@ -2290,11 +2244,26 @@ async function handleChatWriteback(event, opts, deps = {}) {
|
|
|
2290
2244
|
const logFn = deps.logFn ?? ((line) => console.error(line));
|
|
2291
2245
|
if (!event.chat) return { ok: false, reason: "no_chat_envelope" };
|
|
2292
2246
|
let jwt = null;
|
|
2293
|
-
if (event.chat.inference_path === "official"
|
|
2247
|
+
if (event.chat.inference_path === "official") {
|
|
2294
2248
|
const minted = await mintAgentJwt(opts, deps.fetchFn ?? fetch);
|
|
2295
|
-
if (minted.ok)
|
|
2249
|
+
if (!minted.ok) {
|
|
2250
|
+
logFn(
|
|
2251
|
+
`[acn listen] official_jwt_failed chat_id=${event.chat.chat_id} reason=${minted.reason}`
|
|
2252
|
+
);
|
|
2253
|
+
return { ok: false, reason: minted.reason };
|
|
2254
|
+
}
|
|
2255
|
+
jwt = minted.token;
|
|
2256
|
+
}
|
|
2257
|
+
let completed;
|
|
2258
|
+
if (event.chat.inference_path === "official") {
|
|
2259
|
+
completed = await completeOfficialViaHost(event, opts, deps, jwt);
|
|
2260
|
+
} else if (opts.completeUrl) {
|
|
2261
|
+
completed = await completeViaHttp(event, opts, deps);
|
|
2262
|
+
} else if (opts.completeExec) {
|
|
2263
|
+
completed = await completeViaExec(event, opts, deps, jwt);
|
|
2264
|
+
} else {
|
|
2265
|
+
completed = { ok: false, reason: "byo_complete_missing" };
|
|
2296
2266
|
}
|
|
2297
|
-
const completed = opts.completeUrl ? await completeViaHttp(event, opts, deps) : await completeViaExec(event, opts, deps, jwt);
|
|
2298
2267
|
if (!completed.ok) {
|
|
2299
2268
|
logFn(
|
|
2300
2269
|
`[acn listen] chat_complete_failed chat_id=${event.chat.chat_id} message_id=${event.message_id} reason=${completed.reason}`
|
|
@@ -2519,7 +2488,7 @@ function formatWakeFailed(event, reason) {
|
|
|
2519
2488
|
function formatDeduped(event) {
|
|
2520
2489
|
return `[acn listen] deduped key=${dedupeKey(event)}`;
|
|
2521
2490
|
}
|
|
2522
|
-
function dispatchLocalReceiver(correlationId, bodyText, opts, dedupeStore,
|
|
2491
|
+
function dispatchLocalReceiver(correlationId, bodyText, opts, dedupeStore, send, deps = {}) {
|
|
2523
2492
|
const logFn = deps.logFn ?? ((line) => console.error(line));
|
|
2524
2493
|
const result = processIncomingRequest(
|
|
2525
2494
|
correlationId,
|
|
@@ -2528,7 +2497,7 @@ function dispatchLocalReceiver(correlationId, bodyText, opts, dedupeStore, send2
|
|
|
2528
2497
|
dedupeStore,
|
|
2529
2498
|
deps
|
|
2530
2499
|
);
|
|
2531
|
-
|
|
2500
|
+
send(result.response);
|
|
2532
2501
|
if (result.dedupeHit && result.event) {
|
|
2533
2502
|
logFn(formatDeduped(result.event));
|
|
2534
2503
|
return;
|
|
@@ -2589,7 +2558,7 @@ function errorResponse(id, status, detail) {
|
|
|
2589
2558
|
body: JSON.stringify({ error: detail })
|
|
2590
2559
|
};
|
|
2591
2560
|
}
|
|
2592
|
-
async function dispatchA2aRequest(frame, opts,
|
|
2561
|
+
async function dispatchA2aRequest(frame, opts, send, deps = {}) {
|
|
2593
2562
|
const bodyBuf = decodeBody(frame);
|
|
2594
2563
|
try {
|
|
2595
2564
|
if (opts.runtime) {
|
|
@@ -2599,7 +2568,7 @@ async function dispatchA2aRequest(frame, opts, send2, deps = {}) {
|
|
|
2599
2568
|
bodyBuf.toString("utf-8"),
|
|
2600
2569
|
opts.runtime,
|
|
2601
2570
|
store,
|
|
2602
|
-
|
|
2571
|
+
send,
|
|
2603
2572
|
{
|
|
2604
2573
|
fetchFn: deps.fetchFn,
|
|
2605
2574
|
spawnFn: deps.spawnFn,
|
|
@@ -2609,17 +2578,17 @@ async function dispatchA2aRequest(frame, opts, send2, deps = {}) {
|
|
|
2609
2578
|
return;
|
|
2610
2579
|
}
|
|
2611
2580
|
if (opts.forward) {
|
|
2612
|
-
await forwardToHttp(frame, bodyBuf, opts.forward, deps.fetchFn ?? fetch,
|
|
2581
|
+
await forwardToHttp(frame, bodyBuf, opts.forward, deps.fetchFn ?? fetch, send);
|
|
2613
2582
|
return;
|
|
2614
2583
|
}
|
|
2615
2584
|
if (opts.exec) {
|
|
2616
|
-
|
|
2585
|
+
send(await runExec(frame, bodyBuf, opts.exec, deps.spawnFn ?? import_child_process3.spawn));
|
|
2617
2586
|
return;
|
|
2618
2587
|
}
|
|
2619
|
-
|
|
2588
|
+
send(errorResponse(frame.id, 500, "no handler configured"));
|
|
2620
2589
|
} catch (err) {
|
|
2621
2590
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2622
|
-
|
|
2591
|
+
send(errorResponse(frame.id, 502, `handler failed: ${msg}`));
|
|
2623
2592
|
}
|
|
2624
2593
|
}
|
|
2625
2594
|
function buildForwardHeaders(frame) {
|
|
@@ -2629,7 +2598,7 @@ function buildForwardHeaders(frame) {
|
|
|
2629
2598
|
}
|
|
2630
2599
|
return headers;
|
|
2631
2600
|
}
|
|
2632
|
-
async function forwardToHttp(frame, bodyBuf, base, fetchFn,
|
|
2601
|
+
async function forwardToHttp(frame, bodyBuf, base, fetchFn, send) {
|
|
2633
2602
|
const suffix = frame.path && frame.path !== "/" ? "/" + frame.path.replace(/^\//, "") : "";
|
|
2634
2603
|
const targetUrl = base.replace(/\/$/, "") + suffix;
|
|
2635
2604
|
const method = (frame.method ?? "POST").toUpperCase();
|
|
@@ -2647,7 +2616,7 @@ async function forwardToHttp(frame, bodyBuf, base, fetchFn, send2) {
|
|
|
2647
2616
|
const { done, value } = await reader.read();
|
|
2648
2617
|
if (done) break;
|
|
2649
2618
|
if (value && value.length > 0) {
|
|
2650
|
-
|
|
2619
|
+
send({
|
|
2651
2620
|
type: "a2a_stream_chunk",
|
|
2652
2621
|
id: frame.id,
|
|
2653
2622
|
seq: seq++,
|
|
@@ -2656,17 +2625,17 @@ async function forwardToHttp(frame, bodyBuf, base, fetchFn, send2) {
|
|
|
2656
2625
|
});
|
|
2657
2626
|
}
|
|
2658
2627
|
}
|
|
2659
|
-
|
|
2628
|
+
send({ type: "a2a_stream_end", id: frame.id, status: res.status });
|
|
2660
2629
|
} catch (err) {
|
|
2661
2630
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2662
|
-
|
|
2631
|
+
send({ type: "a2a_stream_end", id: frame.id, error: msg });
|
|
2663
2632
|
} finally {
|
|
2664
2633
|
reader.releaseLock();
|
|
2665
2634
|
}
|
|
2666
2635
|
return;
|
|
2667
2636
|
}
|
|
2668
2637
|
const respText = await res.text();
|
|
2669
|
-
|
|
2638
|
+
send({
|
|
2670
2639
|
type: "a2a_response",
|
|
2671
2640
|
id: frame.id,
|
|
2672
2641
|
status: res.status,
|
|
@@ -2777,12 +2746,12 @@ function runListener(cfg) {
|
|
|
2777
2746
|
if (!frame || typeof frame !== "object") return;
|
|
2778
2747
|
const f = frame;
|
|
2779
2748
|
if (f.type === "a2a_request" && typeof f.id === "string") {
|
|
2780
|
-
const
|
|
2749
|
+
const send = (out) => {
|
|
2781
2750
|
if (ws.readyState === import_ws.default.OPEN) {
|
|
2782
2751
|
ws.send(JSON.stringify(out));
|
|
2783
2752
|
}
|
|
2784
2753
|
};
|
|
2785
|
-
void dispatchA2aRequest(f, cfg,
|
|
2754
|
+
void dispatchA2aRequest(f, cfg, send, { dedupeStore });
|
|
2786
2755
|
}
|
|
2787
2756
|
});
|
|
2788
2757
|
ws.on("close", (code, reason) => {
|
|
@@ -2865,10 +2834,10 @@ function listenCommand() {
|
|
|
2865
2834
|
"Deprecated/ignored: writeback now mints ACN agent JWT from api-key"
|
|
2866
2835
|
).option(
|
|
2867
2836
|
"--chat-complete-url <url>",
|
|
2868
|
-
|
|
2837
|
+
"BYO complete URL (optional if official-only; mutually exclusive with --chat-complete-exec)"
|
|
2869
2838
|
).option(
|
|
2870
2839
|
"--chat-complete-exec <cmd>",
|
|
2871
|
-
'
|
|
2840
|
+
'BYO only (optional if official-only): stdin event \u2192 stdout {"content"}. Official hops complete via Host.'
|
|
2872
2841
|
).option(
|
|
2873
2842
|
"--chat-complete-timeout <ms>",
|
|
2874
2843
|
`Host complete timeout in ms (default ${DEFAULT_COMPLETE_TIMEOUT_MS})`,
|