@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.
Files changed (3) hide show
  1. package/README.md +2 -1
  2. package/dist/index.js +215 -197
  3. 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.8",
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 shouldOpenOfficialDoor(opts) {
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 === hasExec) {
1888
- return '--chat-writeback requires exactly one of --chat-complete-url or --chat-complete-exec (host returns JSON {"content":"..."} and optional usage).';
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
- try {
1994
- const res = await fetchFn(url, {
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
- parsed = JSON.parse(text);
2011
- } catch {
2012
- return { ok: false, reason: "oauth_invalid_json" };
2013
- }
2014
- const rec = asRecord2(parsed);
2015
- const token = typeof rec?.access_token === "string" ? rec.access_token.trim() : "";
2016
- if (!token) return { ok: false, reason: "oauth_missing_access_token" };
2017
- const expiresIn = typeof rec?.expires_in === "number" && rec.expires_in > 0 ? rec.expires_in : 1800;
2018
- cachedJwt = {
2019
- token,
2020
- agentId: opts.agentId,
2021
- expEpochSec: now + expiresIn
2022
- };
2023
- return { ok: true, token };
2024
- } catch (err) {
2025
- const msg = err instanceof Error ? err.message : String(err);
2026
- return { ok: false, reason: msg.slice(0, 200) };
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
- async function completeViaExec(event, opts, deps, jwt) {
2142
- let door = null;
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
- if (shouldOpenOfficialDoor({
2146
- inferencePath: event.chat?.inference_path,
2147
- hopId: event.chat?.hop_id,
2148
- hostInferenceUrl: event.chat?.host_inference_url,
2149
- jwt
2150
- })) {
2151
- try {
2152
- door = await startOfficialHopDoor({
2153
- hostInferenceUrl: event.chat.host_inference_url,
2154
- hopId: event.chat.hop_id,
2155
- agentId: opts.agentId,
2156
- jwt,
2157
- fetchFn: deps.fetchFn
2158
- });
2159
- } catch {
2160
- door = null;
2161
- }
2162
- if (door) {
2163
- logFn(
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
- return await spawnCompleteExec(event, opts, deps, jwt, door);
2173
- } finally {
2174
- if (door) {
2175
- await door.close().catch(() => void 0);
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" && opts.completeExec) {
2278
+ if (event.chat.inference_path === "official") {
2299
2279
  const minted = await mintAgentJwt(opts, deps.fetchFn ?? fetch);
2300
- if (minted.ok) jwt = minted.token;
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, send2, deps = {}) {
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
- send2(result.response);
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, send2, deps = {}) {
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
- send2,
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, send2);
2638
+ await forwardToHttp(frame, bodyBuf, opts.forward, deps.fetchFn ?? fetch, send);
2618
2639
  return;
2619
2640
  }
2620
2641
  if (opts.exec) {
2621
- send2(await runExec(frame, bodyBuf, opts.exec, deps.spawnFn ?? import_child_process3.spawn));
2642
+ send(await runExec(frame, bodyBuf, opts.exec, deps.spawnFn ?? import_child_process3.spawn));
2622
2643
  return;
2623
2644
  }
2624
- send2(errorResponse(frame.id, 500, "no handler configured"));
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
- send2(errorResponse(frame.id, 502, `handler failed: ${msg}`));
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, send2) {
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
- send2({
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
- send2({ type: "a2a_stream_end", id: frame.id, status: res.status });
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
- send2({ type: "a2a_stream_end", id: frame.id, error: msg });
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
- send2({
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 send2 = (out) => {
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, send2, { dedupeStore });
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
- 'POST NormalizedEvent \u2192 JSON {"content":"..."} (mutually exclusive with --chat-complete-exec)'
2894
+ "BYO complete URL (optional if official-only; mutually exclusive with --chat-complete-exec)"
2874
2895
  ).option(
2875
2896
  "--chat-complete-exec <cmd>",
2876
- 'Shell: event JSON on stdin \u2192 stdout JSON {"content":"..."}'
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
- `Host complete timeout in ms (default ${DEFAULT_COMPLETE_TIMEOUT_MS})`,
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 chatCompleteTimeoutMs = Number.parseInt(
2954
- opts.chatCompleteTimeout ?? "",
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acnlabs/acn-cli",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "Official CLI for ACN (Agent Collaboration Network) \u2014 zero-integration agent access",
5
5
  "main": "dist/index.js",
6
6
  "bin": {