@acnlabs/acn-cli 1.0.8 → 1.0.10
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 +2 -1
- package/dist/index.js +215 -197
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -112,7 +112,8 @@ acn listen --runtime http \
|
|
|
112
112
|
|
|
113
113
|
`--chat-token` is deprecated/ignored. Task / Org wakes still use `--wake-url` /
|
|
114
114
|
`--wake-exec`. Chat envelopes skip wake and use the complete → writeback path
|
|
115
|
-
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.
|
|
116
117
|
|
|
117
118
|
**Coverage:** only traffic that arrives over the Mode B relay. Open Task Pool
|
|
118
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.10",
|
|
35
35
|
description: "Official CLI for ACN (Agent Collaboration Network) \u2014 zero-integration agent access",
|
|
36
36
|
main: "dist/index.js",
|
|
37
37
|
bin: {
|
|
@@ -1756,115 +1756,16 @@ var DedupeStore = class {
|
|
|
1756
1756
|
|
|
1757
1757
|
// src/commands/official-hop-door.ts
|
|
1758
1758
|
var import_node_http = __toESM(require("http"));
|
|
1759
|
-
function
|
|
1759
|
+
function canCompleteOfficialHop(opts) {
|
|
1760
1760
|
return Boolean(
|
|
1761
1761
|
opts.inferencePath === "official" && opts.hopId?.trim() && asHostInferenceUrl(opts.hostInferenceUrl) && opts.jwt?.trim()
|
|
1762
1762
|
);
|
|
1763
1763
|
}
|
|
1764
|
-
function readBody(req) {
|
|
1765
|
-
return new Promise((resolve, reject) => {
|
|
1766
|
-
const chunks = [];
|
|
1767
|
-
req.on("data", (c) => chunks.push(Buffer.from(c)));
|
|
1768
|
-
req.on("end", () => resolve(Buffer.concat(chunks)));
|
|
1769
|
-
req.on("error", reject);
|
|
1770
|
-
});
|
|
1771
|
-
}
|
|
1772
|
-
function send(res, status, body, contentType = "application/json") {
|
|
1773
|
-
res.writeHead(status, {
|
|
1774
|
-
"content-type": contentType,
|
|
1775
|
-
"content-length": String(body.length)
|
|
1776
|
-
});
|
|
1777
|
-
res.end(body);
|
|
1778
|
-
}
|
|
1779
|
-
async function handleDoorRequest(req, res, opts) {
|
|
1780
|
-
const path = (req.url ?? "").split("?")[0];
|
|
1781
|
-
if (req.method !== "POST" || path !== "/v1/chat/completions" && path !== "/chat/completions") {
|
|
1782
|
-
send(res, 404, Buffer.from('{"error":"not_found"}'));
|
|
1783
|
-
return;
|
|
1784
|
-
}
|
|
1785
|
-
let payload;
|
|
1786
|
-
try {
|
|
1787
|
-
const raw = await readBody(req);
|
|
1788
|
-
payload = JSON.parse(raw.length ? raw.toString("utf-8") : "{}");
|
|
1789
|
-
} catch {
|
|
1790
|
-
send(res, 400, Buffer.from('{"error":"invalid_json"}'));
|
|
1791
|
-
return;
|
|
1792
|
-
}
|
|
1793
|
-
if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
|
|
1794
|
-
send(res, 400, Buffer.from('{"error":"invalid_json"}'));
|
|
1795
|
-
return;
|
|
1796
|
-
}
|
|
1797
|
-
const body = { ...payload };
|
|
1798
|
-
delete body.agent_id;
|
|
1799
|
-
body.hop_id = opts.hopId;
|
|
1800
|
-
const headers = {
|
|
1801
|
-
authorization: `Bearer ${opts.jwt}`,
|
|
1802
|
-
"content-type": "application/json",
|
|
1803
|
-
"X-Hop-Id": opts.hopId
|
|
1804
|
-
};
|
|
1805
|
-
if (opts.agentId) headers["X-Agent-Id"] = opts.agentId;
|
|
1806
|
-
try {
|
|
1807
|
-
const upstream = await opts.fetchFn(opts.upstream, {
|
|
1808
|
-
method: "POST",
|
|
1809
|
-
headers,
|
|
1810
|
-
body: JSON.stringify(body)
|
|
1811
|
-
});
|
|
1812
|
-
const out = Buffer.from(await upstream.arrayBuffer());
|
|
1813
|
-
const ct = upstream.headers.get("content-type") || "application/json";
|
|
1814
|
-
send(res, upstream.status, out, ct);
|
|
1815
|
-
} catch (err) {
|
|
1816
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1817
|
-
send(
|
|
1818
|
-
res,
|
|
1819
|
-
502,
|
|
1820
|
-
Buffer.from(JSON.stringify({ error: `upstream_unreachable:${msg.slice(0, 120)}` }))
|
|
1821
|
-
);
|
|
1822
|
-
}
|
|
1823
|
-
}
|
|
1824
|
-
function closeServer(server) {
|
|
1825
|
-
return new Promise((resolve) => {
|
|
1826
|
-
server.closeAllConnections?.();
|
|
1827
|
-
server.close(() => resolve());
|
|
1828
|
-
});
|
|
1829
|
-
}
|
|
1830
|
-
async function startOfficialHopDoor(opts) {
|
|
1831
|
-
const dest = asHostInferenceUrl(opts.hostInferenceUrl);
|
|
1832
|
-
const hopId = opts.hopId.trim();
|
|
1833
|
-
const jwt = opts.jwt.trim();
|
|
1834
|
-
if (!dest || !hopId || !jwt) return null;
|
|
1835
|
-
const fetchFn = opts.fetchFn ?? fetch;
|
|
1836
|
-
const upstream = `${dest}/chat/completions`;
|
|
1837
|
-
const server = import_node_http.default.createServer((req, res) => {
|
|
1838
|
-
void handleDoorRequest(req, res, {
|
|
1839
|
-
upstream,
|
|
1840
|
-
hopId,
|
|
1841
|
-
agentId: opts.agentId,
|
|
1842
|
-
jwt,
|
|
1843
|
-
fetchFn
|
|
1844
|
-
});
|
|
1845
|
-
});
|
|
1846
|
-
try {
|
|
1847
|
-
await new Promise((resolve, reject) => {
|
|
1848
|
-
server.once("error", reject);
|
|
1849
|
-
server.listen(0, "127.0.0.1", () => resolve());
|
|
1850
|
-
});
|
|
1851
|
-
} catch {
|
|
1852
|
-
return null;
|
|
1853
|
-
}
|
|
1854
|
-
const addr = server.address();
|
|
1855
|
-
const port = typeof addr === "object" && addr ? addr.port : 0;
|
|
1856
|
-
if (!port) {
|
|
1857
|
-
await closeServer(server);
|
|
1858
|
-
return null;
|
|
1859
|
-
}
|
|
1860
|
-
return {
|
|
1861
|
-
baseUrl: `http://127.0.0.1:${port}/v1`,
|
|
1862
|
-
close: () => closeServer(server)
|
|
1863
|
-
};
|
|
1864
|
-
}
|
|
1865
1764
|
|
|
1866
1765
|
// src/commands/chat-writeback.ts
|
|
1867
1766
|
var DEFAULT_COMPLETE_TIMEOUT_MS = 12e4;
|
|
1767
|
+
var DEFAULT_OFFICIAL_COMPLETE_TIMEOUT_MS = 28e3;
|
|
1768
|
+
var JWT_MINT_ATTEMPTS = 3;
|
|
1868
1769
|
var DEFAULT_WRITEBACK_TIMEOUT_MS = 3e4;
|
|
1869
1770
|
var DEFAULT_CHAT_JWT_AUDIENCE = "https://api.agentplanet.org";
|
|
1870
1771
|
var cachedJwt = null;
|
|
@@ -1884,8 +1785,8 @@ function validateChatWritebackOptions(opts) {
|
|
|
1884
1785
|
}
|
|
1885
1786
|
const hasUrl = Boolean(opts.chatCompleteUrl?.trim());
|
|
1886
1787
|
const hasExec = Boolean(opts.chatCompleteExec?.trim());
|
|
1887
|
-
if (hasUrl
|
|
1888
|
-
return
|
|
1788
|
+
if (hasUrl && hasExec) {
|
|
1789
|
+
return "--chat-writeback: --chat-complete-url and --chat-complete-exec are mutually exclusive (omit both for official-only; BYO hops need exactly one).";
|
|
1889
1790
|
}
|
|
1890
1791
|
return null;
|
|
1891
1792
|
}
|
|
@@ -1919,6 +1820,28 @@ function extractContent(payload) {
|
|
|
1919
1820
|
}
|
|
1920
1821
|
return null;
|
|
1921
1822
|
}
|
|
1823
|
+
function extractChatCompletionContent(payload) {
|
|
1824
|
+
const rec = asRecord2(payload);
|
|
1825
|
+
if (!rec) return null;
|
|
1826
|
+
const choices = rec.choices;
|
|
1827
|
+
if (Array.isArray(choices) && choices.length > 0) {
|
|
1828
|
+
const first = asRecord2(choices[0]);
|
|
1829
|
+
if (first) {
|
|
1830
|
+
const msg = asRecord2(first.message);
|
|
1831
|
+
const fromMsg = msg?.content;
|
|
1832
|
+
if (typeof fromMsg === "string" && fromMsg.trim()) {
|
|
1833
|
+
const t = fromMsg.trim();
|
|
1834
|
+
if (t.toLowerCase() !== "accepted") return t;
|
|
1835
|
+
}
|
|
1836
|
+
const fromText = first.text;
|
|
1837
|
+
if (typeof fromText === "string" && fromText.trim()) {
|
|
1838
|
+
const t = fromText.trim();
|
|
1839
|
+
if (t.toLowerCase() !== "accepted") return t;
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
return extractContent(payload);
|
|
1844
|
+
}
|
|
1922
1845
|
function asNonNegInt(v) {
|
|
1923
1846
|
if (typeof v === "number" && Number.isFinite(v) && v >= 0) {
|
|
1924
1847
|
return Math.floor(v);
|
|
@@ -1990,41 +1913,50 @@ async function mintAgentJwt(opts, fetchFn = fetch) {
|
|
|
1990
1913
|
return { ok: true, token: cachedJwt.token };
|
|
1991
1914
|
}
|
|
1992
1915
|
const url = `${opts.acnBaseUrl.replace(/\/+$/, "")}/oauth/token`;
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
method: "POST",
|
|
1996
|
-
headers: { "content-type": "application/json" },
|
|
1997
|
-
body: JSON.stringify({
|
|
1998
|
-
grant_type: "client_credentials",
|
|
1999
|
-
client_id: opts.agentId,
|
|
2000
|
-
client_secret: opts.apiKey,
|
|
2001
|
-
audience: opts.audience
|
|
2002
|
-
})
|
|
2003
|
-
});
|
|
2004
|
-
const text = await res.text();
|
|
2005
|
-
if (res.status < 200 || res.status >= 300) {
|
|
2006
|
-
return { ok: false, reason: `oauth_http_${res.status}` };
|
|
2007
|
-
}
|
|
2008
|
-
let parsed;
|
|
1916
|
+
let lastReason = "oauth_failed";
|
|
1917
|
+
for (let attempt = 1; attempt <= JWT_MINT_ATTEMPTS; attempt++) {
|
|
2009
1918
|
try {
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
1919
|
+
const res = await fetchFn(url, {
|
|
1920
|
+
method: "POST",
|
|
1921
|
+
headers: { "content-type": "application/json" },
|
|
1922
|
+
body: JSON.stringify({
|
|
1923
|
+
grant_type: "client_credentials",
|
|
1924
|
+
client_id: opts.agentId,
|
|
1925
|
+
client_secret: opts.apiKey,
|
|
1926
|
+
audience: opts.audience
|
|
1927
|
+
})
|
|
1928
|
+
});
|
|
1929
|
+
const text = await res.text();
|
|
1930
|
+
if (res.status < 200 || res.status >= 300) {
|
|
1931
|
+
lastReason = `oauth_http_${res.status}`;
|
|
1932
|
+
} else {
|
|
1933
|
+
let parsed;
|
|
1934
|
+
try {
|
|
1935
|
+
parsed = JSON.parse(text);
|
|
1936
|
+
} catch {
|
|
1937
|
+
lastReason = "oauth_invalid_json";
|
|
1938
|
+
continue;
|
|
1939
|
+
}
|
|
1940
|
+
const rec = asRecord2(parsed);
|
|
1941
|
+
const token = typeof rec?.access_token === "string" ? rec.access_token.trim() : "";
|
|
1942
|
+
if (!token) {
|
|
1943
|
+
lastReason = "oauth_missing_access_token";
|
|
1944
|
+
continue;
|
|
1945
|
+
}
|
|
1946
|
+
const expiresIn = typeof rec?.expires_in === "number" && rec.expires_in > 0 ? rec.expires_in : 1800;
|
|
1947
|
+
cachedJwt = {
|
|
1948
|
+
token,
|
|
1949
|
+
agentId: opts.agentId,
|
|
1950
|
+
expEpochSec: now + expiresIn
|
|
1951
|
+
};
|
|
1952
|
+
return { ok: true, token };
|
|
1953
|
+
}
|
|
1954
|
+
} catch (err) {
|
|
1955
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1956
|
+
lastReason = msg.slice(0, 200);
|
|
1957
|
+
}
|
|
2027
1958
|
}
|
|
1959
|
+
return { ok: false, reason: lastReason };
|
|
2028
1960
|
}
|
|
2029
1961
|
function clearAgentJwtCache() {
|
|
2030
1962
|
cachedJwt = null;
|
|
@@ -2138,44 +2070,92 @@ function spawnCompleteExec(event, opts, deps, jwt, door) {
|
|
|
2138
2070
|
child.stdin?.end(body);
|
|
2139
2071
|
});
|
|
2140
2072
|
}
|
|
2141
|
-
|
|
2142
|
-
|
|
2073
|
+
var OFFICIAL_V0_O_SERIES = /(^|\/)o[134](?:$|[-/:])/;
|
|
2074
|
+
function officialV0SupportsModel(modelId) {
|
|
2075
|
+
const id = (modelId || "").trim().toLowerCase();
|
|
2076
|
+
if (!id) return true;
|
|
2077
|
+
if (id.includes("-think") || id.includes(":thinking") || id.includes("reasoning")) {
|
|
2078
|
+
return false;
|
|
2079
|
+
}
|
|
2080
|
+
if (id.includes("deepseek-r1")) return false;
|
|
2081
|
+
return !OFFICIAL_V0_O_SERIES.test(id);
|
|
2082
|
+
}
|
|
2083
|
+
function officialCompleteFailureContent(reason, model) {
|
|
2084
|
+
const mid = (model || "").trim();
|
|
2085
|
+
if (mid && !officialV0SupportsModel(mid)) {
|
|
2086
|
+
return `Official v0 is a single completion and cannot run thinking/reasoning models (${mid}). Switch to a chat model such as kimi-k2.5.`;
|
|
2087
|
+
}
|
|
2088
|
+
return `Official hop failed (${reason}). Try kimi or another chat model.`;
|
|
2089
|
+
}
|
|
2090
|
+
async function completeOfficialViaHost(event, opts, deps, jwt) {
|
|
2091
|
+
const chat = event.chat;
|
|
2092
|
+
if (!chat) return { ok: false, reason: "no_chat_envelope" };
|
|
2093
|
+
if (!canCompleteOfficialHop({
|
|
2094
|
+
inferencePath: chat.inference_path,
|
|
2095
|
+
hopId: chat.hop_id,
|
|
2096
|
+
hostInferenceUrl: chat.host_inference_url,
|
|
2097
|
+
jwt
|
|
2098
|
+
})) {
|
|
2099
|
+
return { ok: false, reason: "official_complete_skipped" };
|
|
2100
|
+
}
|
|
2101
|
+
const model = chat.requested_model?.trim();
|
|
2102
|
+
const text = chat.user_text?.trim();
|
|
2103
|
+
if (!model) return { ok: false, reason: "official_complete_missing_model" };
|
|
2104
|
+
if (!officialV0SupportsModel(model)) {
|
|
2105
|
+
return { ok: false, reason: "official_complete_unsupported_model" };
|
|
2106
|
+
}
|
|
2107
|
+
if (!text) return { ok: false, reason: "official_complete_missing_text" };
|
|
2143
2108
|
const logFn = deps.logFn ?? ((line) => console.error(line));
|
|
2109
|
+
logFn(
|
|
2110
|
+
`[acn listen] official_complete chat_id=${chat.chat_id} model=${model}`
|
|
2111
|
+
);
|
|
2112
|
+
const fetchFn = deps.fetchFn ?? fetch;
|
|
2113
|
+
const timeoutMs = opts.completeTimeoutMs ?? DEFAULT_OFFICIAL_COMPLETE_TIMEOUT_MS;
|
|
2114
|
+
const controller = new AbortController();
|
|
2115
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
2144
2116
|
try {
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
`[acn listen] official_door chat_id=${event.chat.chat_id} base=${door.baseUrl}`
|
|
2165
|
-
);
|
|
2166
|
-
} else {
|
|
2167
|
-
logFn(
|
|
2168
|
-
`[acn listen] official_door_skipped chat_id=${event.chat.chat_id}`
|
|
2169
|
-
);
|
|
2170
|
-
}
|
|
2117
|
+
const res = await fetchFn(`${chat.host_inference_url}/chat/completions`, {
|
|
2118
|
+
method: "POST",
|
|
2119
|
+
headers: {
|
|
2120
|
+
authorization: `Bearer ${jwt}`,
|
|
2121
|
+
"content-type": "application/json",
|
|
2122
|
+
"X-Hop-Id": chat.hop_id,
|
|
2123
|
+
"X-Agent-Id": opts.agentId
|
|
2124
|
+
},
|
|
2125
|
+
body: JSON.stringify({
|
|
2126
|
+
model,
|
|
2127
|
+
messages: [{ role: "user", content: text }],
|
|
2128
|
+
hop_id: chat.hop_id,
|
|
2129
|
+
...chat.max_output_tokens && chat.max_output_tokens > 0 ? { max_tokens: chat.max_output_tokens } : {}
|
|
2130
|
+
}),
|
|
2131
|
+
signal: controller.signal
|
|
2132
|
+
});
|
|
2133
|
+
const raw = await res.text();
|
|
2134
|
+
if (res.status < 200 || res.status >= 300) {
|
|
2135
|
+
return { ok: false, reason: `official_complete_http_${res.status}` };
|
|
2171
2136
|
}
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2137
|
+
let payload;
|
|
2138
|
+
try {
|
|
2139
|
+
payload = JSON.parse(raw);
|
|
2140
|
+
} catch {
|
|
2141
|
+
return { ok: false, reason: "official_complete_invalid_json" };
|
|
2176
2142
|
}
|
|
2143
|
+
const content = extractChatCompletionContent(payload);
|
|
2144
|
+
if (!content) return { ok: false, reason: "official_complete_missing_content" };
|
|
2145
|
+
return { ok: true, result: { content } };
|
|
2146
|
+
} catch (err) {
|
|
2147
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2148
|
+
if (controller.signal.aborted || /abort|timeout/i.test(msg)) {
|
|
2149
|
+
return { ok: false, reason: "official_complete_timeout" };
|
|
2150
|
+
}
|
|
2151
|
+
return { ok: false, reason: msg.slice(0, 200) };
|
|
2152
|
+
} finally {
|
|
2153
|
+
clearTimeout(timer);
|
|
2177
2154
|
}
|
|
2178
2155
|
}
|
|
2156
|
+
async function completeViaExec(event, opts, deps, jwt) {
|
|
2157
|
+
return spawnCompleteExec(event, opts, deps, jwt);
|
|
2158
|
+
}
|
|
2179
2159
|
async function postWriteback(event, complete, opts, deps) {
|
|
2180
2160
|
const chat = event.chat;
|
|
2181
2161
|
if (!chat) return { ok: false, reason: "no_chat_envelope" };
|
|
@@ -2295,15 +2275,56 @@ async function handleChatWriteback(event, opts, deps = {}) {
|
|
|
2295
2275
|
const logFn = deps.logFn ?? ((line) => console.error(line));
|
|
2296
2276
|
if (!event.chat) return { ok: false, reason: "no_chat_envelope" };
|
|
2297
2277
|
let jwt = null;
|
|
2298
|
-
if (event.chat.inference_path === "official"
|
|
2278
|
+
if (event.chat.inference_path === "official") {
|
|
2299
2279
|
const minted = await mintAgentJwt(opts, deps.fetchFn ?? fetch);
|
|
2300
|
-
if (minted.ok)
|
|
2280
|
+
if (!minted.ok) {
|
|
2281
|
+
logFn(
|
|
2282
|
+
`[acn listen] official_jwt_failed chat_id=${event.chat.chat_id} reason=${minted.reason}`
|
|
2283
|
+
);
|
|
2284
|
+
const written2 = await postWriteback(
|
|
2285
|
+
event,
|
|
2286
|
+
{ content: officialCompleteFailureContent(minted.reason, event.chat.requested_model) },
|
|
2287
|
+
opts,
|
|
2288
|
+
deps
|
|
2289
|
+
);
|
|
2290
|
+
if (written2.ok) {
|
|
2291
|
+
logFn(
|
|
2292
|
+
`[acn listen] official_fail_writeback_ok chat_id=${event.chat.chat_id} message_id=${event.message_id} reason=${minted.reason}`
|
|
2293
|
+
);
|
|
2294
|
+
return written2;
|
|
2295
|
+
}
|
|
2296
|
+
return { ok: false, reason: minted.reason };
|
|
2297
|
+
}
|
|
2298
|
+
jwt = minted.token;
|
|
2299
|
+
}
|
|
2300
|
+
let completed;
|
|
2301
|
+
if (event.chat.inference_path === "official") {
|
|
2302
|
+
completed = await completeOfficialViaHost(event, opts, deps, jwt);
|
|
2303
|
+
} else if (opts.completeUrl) {
|
|
2304
|
+
completed = await completeViaHttp(event, opts, deps);
|
|
2305
|
+
} else if (opts.completeExec) {
|
|
2306
|
+
completed = await completeViaExec(event, opts, deps, jwt);
|
|
2307
|
+
} else {
|
|
2308
|
+
completed = { ok: false, reason: "byo_complete_missing" };
|
|
2301
2309
|
}
|
|
2302
|
-
const completed = opts.completeUrl ? await completeViaHttp(event, opts, deps) : await completeViaExec(event, opts, deps, jwt);
|
|
2303
2310
|
if (!completed.ok) {
|
|
2304
2311
|
logFn(
|
|
2305
2312
|
`[acn listen] chat_complete_failed chat_id=${event.chat.chat_id} message_id=${event.message_id} reason=${completed.reason}`
|
|
2306
2313
|
);
|
|
2314
|
+
if (event.chat.inference_path === "official") {
|
|
2315
|
+
const written2 = await postWriteback(
|
|
2316
|
+
event,
|
|
2317
|
+
{ content: officialCompleteFailureContent(completed.reason, event.chat.requested_model) },
|
|
2318
|
+
opts,
|
|
2319
|
+
deps
|
|
2320
|
+
);
|
|
2321
|
+
if (written2.ok) {
|
|
2322
|
+
logFn(
|
|
2323
|
+
`[acn listen] official_fail_writeback_ok chat_id=${event.chat.chat_id} message_id=${event.message_id} reason=${completed.reason}`
|
|
2324
|
+
);
|
|
2325
|
+
return written2;
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2307
2328
|
return { ok: false, reason: completed.reason };
|
|
2308
2329
|
}
|
|
2309
2330
|
const written = await postWriteback(event, completed.result, opts, deps);
|
|
@@ -2524,7 +2545,7 @@ function formatWakeFailed(event, reason) {
|
|
|
2524
2545
|
function formatDeduped(event) {
|
|
2525
2546
|
return `[acn listen] deduped key=${dedupeKey(event)}`;
|
|
2526
2547
|
}
|
|
2527
|
-
function dispatchLocalReceiver(correlationId, bodyText, opts, dedupeStore,
|
|
2548
|
+
function dispatchLocalReceiver(correlationId, bodyText, opts, dedupeStore, send, deps = {}) {
|
|
2528
2549
|
const logFn = deps.logFn ?? ((line) => console.error(line));
|
|
2529
2550
|
const result = processIncomingRequest(
|
|
2530
2551
|
correlationId,
|
|
@@ -2533,7 +2554,7 @@ function dispatchLocalReceiver(correlationId, bodyText, opts, dedupeStore, send2
|
|
|
2533
2554
|
dedupeStore,
|
|
2534
2555
|
deps
|
|
2535
2556
|
);
|
|
2536
|
-
|
|
2557
|
+
send(result.response);
|
|
2537
2558
|
if (result.dedupeHit && result.event) {
|
|
2538
2559
|
logFn(formatDeduped(result.event));
|
|
2539
2560
|
return;
|
|
@@ -2594,7 +2615,7 @@ function errorResponse(id, status, detail) {
|
|
|
2594
2615
|
body: JSON.stringify({ error: detail })
|
|
2595
2616
|
};
|
|
2596
2617
|
}
|
|
2597
|
-
async function dispatchA2aRequest(frame, opts,
|
|
2618
|
+
async function dispatchA2aRequest(frame, opts, send, deps = {}) {
|
|
2598
2619
|
const bodyBuf = decodeBody(frame);
|
|
2599
2620
|
try {
|
|
2600
2621
|
if (opts.runtime) {
|
|
@@ -2604,7 +2625,7 @@ async function dispatchA2aRequest(frame, opts, send2, deps = {}) {
|
|
|
2604
2625
|
bodyBuf.toString("utf-8"),
|
|
2605
2626
|
opts.runtime,
|
|
2606
2627
|
store,
|
|
2607
|
-
|
|
2628
|
+
send,
|
|
2608
2629
|
{
|
|
2609
2630
|
fetchFn: deps.fetchFn,
|
|
2610
2631
|
spawnFn: deps.spawnFn,
|
|
@@ -2614,17 +2635,17 @@ async function dispatchA2aRequest(frame, opts, send2, deps = {}) {
|
|
|
2614
2635
|
return;
|
|
2615
2636
|
}
|
|
2616
2637
|
if (opts.forward) {
|
|
2617
|
-
await forwardToHttp(frame, bodyBuf, opts.forward, deps.fetchFn ?? fetch,
|
|
2638
|
+
await forwardToHttp(frame, bodyBuf, opts.forward, deps.fetchFn ?? fetch, send);
|
|
2618
2639
|
return;
|
|
2619
2640
|
}
|
|
2620
2641
|
if (opts.exec) {
|
|
2621
|
-
|
|
2642
|
+
send(await runExec(frame, bodyBuf, opts.exec, deps.spawnFn ?? import_child_process3.spawn));
|
|
2622
2643
|
return;
|
|
2623
2644
|
}
|
|
2624
|
-
|
|
2645
|
+
send(errorResponse(frame.id, 500, "no handler configured"));
|
|
2625
2646
|
} catch (err) {
|
|
2626
2647
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2627
|
-
|
|
2648
|
+
send(errorResponse(frame.id, 502, `handler failed: ${msg}`));
|
|
2628
2649
|
}
|
|
2629
2650
|
}
|
|
2630
2651
|
function buildForwardHeaders(frame) {
|
|
@@ -2634,7 +2655,7 @@ function buildForwardHeaders(frame) {
|
|
|
2634
2655
|
}
|
|
2635
2656
|
return headers;
|
|
2636
2657
|
}
|
|
2637
|
-
async function forwardToHttp(frame, bodyBuf, base, fetchFn,
|
|
2658
|
+
async function forwardToHttp(frame, bodyBuf, base, fetchFn, send) {
|
|
2638
2659
|
const suffix = frame.path && frame.path !== "/" ? "/" + frame.path.replace(/^\//, "") : "";
|
|
2639
2660
|
const targetUrl = base.replace(/\/$/, "") + suffix;
|
|
2640
2661
|
const method = (frame.method ?? "POST").toUpperCase();
|
|
@@ -2652,7 +2673,7 @@ async function forwardToHttp(frame, bodyBuf, base, fetchFn, send2) {
|
|
|
2652
2673
|
const { done, value } = await reader.read();
|
|
2653
2674
|
if (done) break;
|
|
2654
2675
|
if (value && value.length > 0) {
|
|
2655
|
-
|
|
2676
|
+
send({
|
|
2656
2677
|
type: "a2a_stream_chunk",
|
|
2657
2678
|
id: frame.id,
|
|
2658
2679
|
seq: seq++,
|
|
@@ -2661,17 +2682,17 @@ async function forwardToHttp(frame, bodyBuf, base, fetchFn, send2) {
|
|
|
2661
2682
|
});
|
|
2662
2683
|
}
|
|
2663
2684
|
}
|
|
2664
|
-
|
|
2685
|
+
send({ type: "a2a_stream_end", id: frame.id, status: res.status });
|
|
2665
2686
|
} catch (err) {
|
|
2666
2687
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2667
|
-
|
|
2688
|
+
send({ type: "a2a_stream_end", id: frame.id, error: msg });
|
|
2668
2689
|
} finally {
|
|
2669
2690
|
reader.releaseLock();
|
|
2670
2691
|
}
|
|
2671
2692
|
return;
|
|
2672
2693
|
}
|
|
2673
2694
|
const respText = await res.text();
|
|
2674
|
-
|
|
2695
|
+
send({
|
|
2675
2696
|
type: "a2a_response",
|
|
2676
2697
|
id: frame.id,
|
|
2677
2698
|
status: res.status,
|
|
@@ -2782,12 +2803,12 @@ function runListener(cfg) {
|
|
|
2782
2803
|
if (!frame || typeof frame !== "object") return;
|
|
2783
2804
|
const f = frame;
|
|
2784
2805
|
if (f.type === "a2a_request" && typeof f.id === "string") {
|
|
2785
|
-
const
|
|
2806
|
+
const send = (out) => {
|
|
2786
2807
|
if (ws.readyState === import_ws.default.OPEN) {
|
|
2787
2808
|
ws.send(JSON.stringify(out));
|
|
2788
2809
|
}
|
|
2789
2810
|
};
|
|
2790
|
-
void dispatchA2aRequest(f, cfg,
|
|
2811
|
+
void dispatchA2aRequest(f, cfg, send, { dedupeStore });
|
|
2791
2812
|
}
|
|
2792
2813
|
});
|
|
2793
2814
|
ws.on("close", (code, reason) => {
|
|
@@ -2870,14 +2891,13 @@ function listenCommand() {
|
|
|
2870
2891
|
"Deprecated/ignored: writeback now mints ACN agent JWT from api-key"
|
|
2871
2892
|
).option(
|
|
2872
2893
|
"--chat-complete-url <url>",
|
|
2873
|
-
|
|
2894
|
+
"BYO complete URL (optional if official-only; mutually exclusive with --chat-complete-exec)"
|
|
2874
2895
|
).option(
|
|
2875
2896
|
"--chat-complete-exec <cmd>",
|
|
2876
|
-
'
|
|
2897
|
+
'BYO only (optional if official-only): stdin event \u2192 stdout {"content"}. Official hops complete via Host.'
|
|
2877
2898
|
).option(
|
|
2878
2899
|
"--chat-complete-timeout <ms>",
|
|
2879
|
-
`
|
|
2880
|
-
String(DEFAULT_COMPLETE_TIMEOUT_MS)
|
|
2900
|
+
`Complete timeout in ms (official default ${DEFAULT_OFFICIAL_COMPLETE_TIMEOUT_MS}; BYO default ${DEFAULT_COMPLETE_TIMEOUT_MS})`
|
|
2881
2901
|
).option("-i, --agent-id <id>", "Agent ID (defaults to config)").option(
|
|
2882
2902
|
"-m, --model <modelId>",
|
|
2883
2903
|
"Declare runtime model (Host Catalog id) on connect + every 15m via REST heartbeat (self-reported; env: ACN_PREFERRED_MODEL)"
|
|
@@ -2950,10 +2970,8 @@ function listenCommand() {
|
|
|
2950
2970
|
}
|
|
2951
2971
|
const wakeTimeoutMs = Number.parseInt(opts.wakeTimeout ?? "", 10);
|
|
2952
2972
|
const dedupeTtlSec = Number.parseInt(opts.dedupeTtl ?? "", 10);
|
|
2953
|
-
const
|
|
2954
|
-
|
|
2955
|
-
10
|
|
2956
|
-
);
|
|
2973
|
+
const rawCompleteTimeout = opts.chatCompleteTimeout?.trim();
|
|
2974
|
+
const chatCompleteTimeoutMs = rawCompleteTimeout ? Number.parseInt(rawCompleteTimeout, 10) : void 0;
|
|
2957
2975
|
if (opts.runtime && (!Number.isFinite(wakeTimeoutMs) || wakeTimeoutMs <= 0)) {
|
|
2958
2976
|
console.error("--wake-timeout must be a positive integer (ms).");
|
|
2959
2977
|
process.exit(1);
|
|
@@ -2962,7 +2980,7 @@ function listenCommand() {
|
|
|
2962
2980
|
console.error("--dedupe-ttl must be a positive integer (seconds).");
|
|
2963
2981
|
process.exit(1);
|
|
2964
2982
|
}
|
|
2965
|
-
if (opts.chatWriteback && (!Number.isFinite(chatCompleteTimeoutMs) || chatCompleteTimeoutMs <= 0)) {
|
|
2983
|
+
if (opts.chatWriteback && rawCompleteTimeout && (!Number.isFinite(chatCompleteTimeoutMs) || (chatCompleteTimeoutMs ?? 0) <= 0)) {
|
|
2966
2984
|
console.error("--chat-complete-timeout must be a positive integer (ms).");
|
|
2967
2985
|
process.exit(1);
|
|
2968
2986
|
}
|