@acnlabs/acn-cli 0.14.1 → 0.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -5
- package/dist/index.js +124 -40
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -91,11 +91,13 @@ the A2A reply. Dedupe is on by default (`--no-dedupe` to disable).
|
|
|
91
91
|
|
|
92
92
|
**Chat writeback (Interfaze / Chat Gateway):** when the relayed message carries
|
|
93
93
|
`metadata.agentplanet.chat_id` + `reply_path`, enable the CLI to complete a
|
|
94
|
-
host reply and POST `agent-messages` (hosts only return `{"content":"..."}`)
|
|
94
|
+
host reply and POST `agent-messages` (hosts only return `{"content":"..."}`).
|
|
95
|
+
Auth uses an **ACN agent JWT** minted from your config `api_key` (no
|
|
96
|
+
AgentPlanet Internal Token):
|
|
95
97
|
|
|
96
98
|
```bash
|
|
97
|
-
export AGENTPLANET_API_BASE=https://api.
|
|
98
|
-
|
|
99
|
+
export AGENTPLANET_API_BASE=https://api.agentplanet.org
|
|
100
|
+
# optional: ACN_CHAT_JWT_AUDIENCE=https://api.agentplanet.org
|
|
99
101
|
|
|
100
102
|
acn listen --runtime http \
|
|
101
103
|
--wake-url http://127.0.0.1:10122/hooks/agent \
|
|
@@ -104,8 +106,9 @@ acn listen --runtime http \
|
|
|
104
106
|
# or: --chat-complete-exec 'python3 /path/to/complete.py'
|
|
105
107
|
```
|
|
106
108
|
|
|
107
|
-
Task / Org wakes still use `--wake-url` /
|
|
108
|
-
wake and use the complete → writeback path
|
|
109
|
+
`--chat-token` is deprecated/ignored. Task / Org wakes still use `--wake-url` /
|
|
110
|
+
`--wake-exec`. Chat envelopes skip wake and use the complete → writeback path
|
|
111
|
+
instead.
|
|
109
112
|
|
|
110
113
|
**Coverage:** only traffic that arrives over the Mode B relay. Open Task Pool
|
|
111
114
|
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: "0.14.
|
|
34
|
+
version: "0.14.2",
|
|
35
35
|
description: "Official CLI for ACN (Agent Collaboration Network) \u2014 zero-integration agent access",
|
|
36
36
|
main: "dist/index.js",
|
|
37
37
|
bin: {
|
|
@@ -469,7 +469,9 @@ function rotateKeyCommand() {
|
|
|
469
469
|
}
|
|
470
470
|
if (!config.api_key) {
|
|
471
471
|
console.error(
|
|
472
|
-
|
|
472
|
+
`No API key found in ~/.acn/config.json. The CLI rotates with the current agent key.
|
|
473
|
+
If you have lost the key, sign in to Labs as the agent owner, open /agents/${agentId}, click "Reset API Key", then run:
|
|
474
|
+
acn config set api_key <new>`
|
|
473
475
|
);
|
|
474
476
|
process.exit(1);
|
|
475
477
|
}
|
|
@@ -1539,6 +1541,8 @@ var DedupeStore = class {
|
|
|
1539
1541
|
// src/commands/chat-writeback.ts
|
|
1540
1542
|
var DEFAULT_COMPLETE_TIMEOUT_MS = 12e4;
|
|
1541
1543
|
var DEFAULT_WRITEBACK_TIMEOUT_MS = 3e4;
|
|
1544
|
+
var DEFAULT_CHAT_JWT_AUDIENCE = "https://api.agentplanet.org";
|
|
1545
|
+
var cachedJwt = null;
|
|
1542
1546
|
function asRecord2(v) {
|
|
1543
1547
|
return v !== null && typeof v === "object" && !Array.isArray(v) ? v : null;
|
|
1544
1548
|
}
|
|
@@ -1547,12 +1551,12 @@ function validateChatWritebackOptions(opts) {
|
|
|
1547
1551
|
if (!opts.agentId) {
|
|
1548
1552
|
return "--chat-writeback requires a known agent id (join / --agent-id).";
|
|
1549
1553
|
}
|
|
1554
|
+
if (!opts.apiKey?.trim()) {
|
|
1555
|
+
return "--chat-writeback requires an ACN API key (acn join / config set api-key).";
|
|
1556
|
+
}
|
|
1550
1557
|
if (!opts.chatApiBase?.trim()) {
|
|
1551
1558
|
return "--chat-writeback requires --chat-api-base (or ACN_CHAT_API_BASE / AGENTPLANET_API_BASE).";
|
|
1552
1559
|
}
|
|
1553
|
-
if (!opts.chatToken?.trim()) {
|
|
1554
|
-
return "--chat-writeback requires --chat-token (or ACN_CHAT_WRITEBACK_TOKEN / AGENTPLANET_INTERNAL_TOKEN / AGENTPLANET_INTERNAL_API_TOKEN).";
|
|
1555
|
-
}
|
|
1556
1560
|
const hasUrl = Boolean(opts.chatCompleteUrl?.trim());
|
|
1557
1561
|
const hasExec = Boolean(opts.chatCompleteExec?.trim());
|
|
1558
1562
|
if (hasUrl === hasExec) {
|
|
@@ -1562,11 +1566,15 @@ function validateChatWritebackOptions(opts) {
|
|
|
1562
1566
|
}
|
|
1563
1567
|
function buildChatWritebackOptions(opts) {
|
|
1564
1568
|
if (!opts.chatWriteback) return void 0;
|
|
1569
|
+
const apiBase = opts.chatApiBase.replace(/\/+$/, "");
|
|
1570
|
+
const audience = opts.audience?.trim() || DEFAULT_CHAT_JWT_AUDIENCE;
|
|
1565
1571
|
return {
|
|
1566
1572
|
enabled: true,
|
|
1567
|
-
apiBase
|
|
1568
|
-
|
|
1573
|
+
apiBase,
|
|
1574
|
+
acnBaseUrl: opts.acnBaseUrl.replace(/\/+$/, ""),
|
|
1575
|
+
apiKey: opts.apiKey,
|
|
1569
1576
|
agentId: opts.agentId,
|
|
1577
|
+
audience,
|
|
1570
1578
|
completeUrl: opts.chatCompleteUrl?.trim() || void 0,
|
|
1571
1579
|
completeExec: opts.chatCompleteExec?.trim() || void 0,
|
|
1572
1580
|
completeTimeoutMs: opts.chatCompleteTimeoutMs,
|
|
@@ -1586,6 +1594,51 @@ function extractContent(payload) {
|
|
|
1586
1594
|
}
|
|
1587
1595
|
return null;
|
|
1588
1596
|
}
|
|
1597
|
+
async function mintAgentJwt(opts, fetchFn = fetch) {
|
|
1598
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1599
|
+
if (cachedJwt && cachedJwt.agentId === opts.agentId && cachedJwt.expEpochSec > now + 60) {
|
|
1600
|
+
return { ok: true, token: cachedJwt.token };
|
|
1601
|
+
}
|
|
1602
|
+
const url = `${opts.acnBaseUrl.replace(/\/+$/, "")}/oauth/token`;
|
|
1603
|
+
try {
|
|
1604
|
+
const res = await fetchFn(url, {
|
|
1605
|
+
method: "POST",
|
|
1606
|
+
headers: { "content-type": "application/json" },
|
|
1607
|
+
body: JSON.stringify({
|
|
1608
|
+
grant_type: "client_credentials",
|
|
1609
|
+
client_id: opts.agentId,
|
|
1610
|
+
client_secret: opts.apiKey,
|
|
1611
|
+
audience: opts.audience
|
|
1612
|
+
})
|
|
1613
|
+
});
|
|
1614
|
+
const text = await res.text();
|
|
1615
|
+
if (res.status < 200 || res.status >= 300) {
|
|
1616
|
+
return { ok: false, reason: `oauth_http_${res.status}` };
|
|
1617
|
+
}
|
|
1618
|
+
let parsed;
|
|
1619
|
+
try {
|
|
1620
|
+
parsed = JSON.parse(text);
|
|
1621
|
+
} catch {
|
|
1622
|
+
return { ok: false, reason: "oauth_invalid_json" };
|
|
1623
|
+
}
|
|
1624
|
+
const rec = asRecord2(parsed);
|
|
1625
|
+
const token = typeof rec?.access_token === "string" ? rec.access_token.trim() : "";
|
|
1626
|
+
if (!token) return { ok: false, reason: "oauth_missing_access_token" };
|
|
1627
|
+
const expiresIn = typeof rec?.expires_in === "number" && rec.expires_in > 0 ? rec.expires_in : 1800;
|
|
1628
|
+
cachedJwt = {
|
|
1629
|
+
token,
|
|
1630
|
+
agentId: opts.agentId,
|
|
1631
|
+
expEpochSec: now + expiresIn
|
|
1632
|
+
};
|
|
1633
|
+
return { ok: true, token };
|
|
1634
|
+
} catch (err) {
|
|
1635
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1636
|
+
return { ok: false, reason: msg.slice(0, 200) };
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
function clearAgentJwtCache() {
|
|
1640
|
+
cachedJwt = null;
|
|
1641
|
+
}
|
|
1589
1642
|
async function completeViaHttp(event, opts, deps) {
|
|
1590
1643
|
const fetchFn = deps.fetchFn ?? fetch;
|
|
1591
1644
|
const timeoutMs = opts.completeTimeoutMs ?? DEFAULT_COMPLETE_TIMEOUT_MS;
|
|
@@ -1693,37 +1746,62 @@ async function postWriteback(event, content, opts, deps) {
|
|
|
1693
1746
|
if (url.origin !== baseOrigin) {
|
|
1694
1747
|
return { ok: false, reason: "reply_url_origin_mismatch" };
|
|
1695
1748
|
}
|
|
1696
|
-
url.searchParams.set("agent_id", opts.agentId);
|
|
1697
1749
|
const timeoutMs = opts.writebackTimeoutMs ?? DEFAULT_WRITEBACK_TIMEOUT_MS;
|
|
1698
|
-
const
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
const res = await fetchFn(url.toString(), {
|
|
1702
|
-
method: "POST",
|
|
1703
|
-
headers: {
|
|
1704
|
-
"content-type": "application/json",
|
|
1705
|
-
"X-Internal-Token": opts.token
|
|
1706
|
-
},
|
|
1707
|
-
body: JSON.stringify({ content }),
|
|
1708
|
-
signal: controller.signal
|
|
1709
|
-
});
|
|
1710
|
-
if (res.status === 200 || res.status === 201) {
|
|
1711
|
-
return { ok: true, httpStatus: res.status };
|
|
1712
|
-
}
|
|
1750
|
+
const postOnce = async (token) => {
|
|
1751
|
+
const controller = new AbortController();
|
|
1752
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
1713
1753
|
try {
|
|
1714
|
-
await
|
|
1715
|
-
|
|
1754
|
+
const res = await fetchFn(url.toString(), {
|
|
1755
|
+
method: "POST",
|
|
1756
|
+
headers: {
|
|
1757
|
+
"content-type": "application/json",
|
|
1758
|
+
Authorization: `Bearer ${token}`
|
|
1759
|
+
},
|
|
1760
|
+
body: JSON.stringify({ content }),
|
|
1761
|
+
signal: controller.signal
|
|
1762
|
+
});
|
|
1763
|
+
if (res.status === 200 || res.status === 201) {
|
|
1764
|
+
return { ok: true, status: res.status };
|
|
1765
|
+
}
|
|
1766
|
+
try {
|
|
1767
|
+
await res.arrayBuffer();
|
|
1768
|
+
} catch {
|
|
1769
|
+
}
|
|
1770
|
+
return {
|
|
1771
|
+
ok: false,
|
|
1772
|
+
status: res.status,
|
|
1773
|
+
reason: `writeback_http_${res.status}`
|
|
1774
|
+
};
|
|
1775
|
+
} catch (err) {
|
|
1776
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1777
|
+
if (controller.signal.aborted || /abort|timeout/i.test(msg)) {
|
|
1778
|
+
return { ok: false, status: 0, reason: "writeback_timeout" };
|
|
1779
|
+
}
|
|
1780
|
+
return { ok: false, status: 0, reason: msg.slice(0, 200) };
|
|
1781
|
+
} finally {
|
|
1782
|
+
clearTimeout(timer);
|
|
1716
1783
|
}
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1784
|
+
};
|
|
1785
|
+
const minted = await mintAgentJwt(opts, fetchFn);
|
|
1786
|
+
if (!minted.ok) {
|
|
1787
|
+
return { ok: false, reason: minted.reason };
|
|
1788
|
+
}
|
|
1789
|
+
let result = await postOnce(minted.token);
|
|
1790
|
+
if (result.ok) {
|
|
1791
|
+
return { ok: true, httpStatus: result.status };
|
|
1792
|
+
}
|
|
1793
|
+
if (result.status === 401) {
|
|
1794
|
+
clearAgentJwtCache();
|
|
1795
|
+
const reminted = await mintAgentJwt(opts, fetchFn);
|
|
1796
|
+
if (!reminted.ok) {
|
|
1797
|
+
return { ok: false, reason: reminted.reason };
|
|
1798
|
+
}
|
|
1799
|
+
result = await postOnce(reminted.token);
|
|
1800
|
+
if (result.ok) {
|
|
1801
|
+
return { ok: true, httpStatus: result.status };
|
|
1722
1802
|
}
|
|
1723
|
-
return { ok: false, reason: msg.slice(0, 200) };
|
|
1724
|
-
} finally {
|
|
1725
|
-
clearTimeout(timer);
|
|
1726
1803
|
}
|
|
1804
|
+
return { ok: false, reason: result.reason };
|
|
1727
1805
|
}
|
|
1728
1806
|
async function handleChatWriteback(event, opts, deps = {}) {
|
|
1729
1807
|
const logFn = deps.logFn ?? ((line) => console.error(line));
|
|
@@ -2255,13 +2333,13 @@ function listenCommand() {
|
|
|
2255
2333
|
"Compat: shell per request; stdout must be a full A2A JSON-RPC response"
|
|
2256
2334
|
).option(
|
|
2257
2335
|
"--chat-writeback",
|
|
2258
|
-
"On Chat Gateway envelopes: complete host reply then POST agent-messages (requires --runtime)"
|
|
2336
|
+
"On Chat Gateway envelopes: complete host reply then POST agent-messages with ACN agent JWT (requires --runtime; uses config api-key)"
|
|
2259
2337
|
).option(
|
|
2260
2338
|
"--chat-api-base <url>",
|
|
2261
2339
|
"Chat Gateway origin for writeback (env: ACN_CHAT_API_BASE or AGENTPLANET_API_BASE)"
|
|
2262
2340
|
).option(
|
|
2263
2341
|
"--chat-token <token>",
|
|
2264
|
-
"
|
|
2342
|
+
"Deprecated/ignored: writeback now mints ACN agent JWT from api-key"
|
|
2265
2343
|
).option(
|
|
2266
2344
|
"--chat-complete-url <url>",
|
|
2267
2345
|
'POST NormalizedEvent \u2192 JSON {"content":"..."} (mutually exclusive with --chat-complete-exec)'
|
|
@@ -2301,20 +2379,24 @@ function listenCommand() {
|
|
|
2301
2379
|
process.exit(1);
|
|
2302
2380
|
}
|
|
2303
2381
|
const chatApiBase = opts.chatApiBase?.trim() || process.env.ACN_CHAT_API_BASE?.trim() || process.env.AGENTPLANET_API_BASE?.trim();
|
|
2304
|
-
const chatToken = opts.chatToken?.trim() || process.env.ACN_CHAT_WRITEBACK_TOKEN?.trim() || process.env.AGENTPLANET_INTERNAL_TOKEN?.trim() || process.env.AGENTPLANET_INTERNAL_API_TOKEN?.trim();
|
|
2305
2382
|
const chatCompleteUrl = opts.chatCompleteUrl?.trim() || process.env.ACN_CHAT_COMPLETE_URL?.trim();
|
|
2306
2383
|
const chatCompleteExec = opts.chatCompleteExec?.trim();
|
|
2307
2384
|
if (opts.chatWriteback && !opts.runtime) {
|
|
2308
2385
|
console.error("--chat-writeback requires --runtime http|command|log.");
|
|
2309
2386
|
process.exit(1);
|
|
2310
2387
|
}
|
|
2388
|
+
if (opts.chatWriteback && (opts.chatToken?.trim() || process.env.ACN_CHAT_WRITEBACK_TOKEN?.trim() || process.env.AGENTPLANET_INTERNAL_TOKEN?.trim() || process.env.AGENTPLANET_INTERNAL_API_TOKEN?.trim())) {
|
|
2389
|
+
console.error(
|
|
2390
|
+
"[acn listen] warning: --chat-token / AGENTPLANET_INTERNAL_TOKEN is ignored; writeback authenticates with ACN agent JWT minted from your api-key."
|
|
2391
|
+
);
|
|
2392
|
+
}
|
|
2311
2393
|
const chatErr = validateChatWritebackOptions({
|
|
2312
2394
|
chatWriteback: opts.chatWriteback,
|
|
2313
2395
|
chatApiBase,
|
|
2314
|
-
chatToken,
|
|
2315
2396
|
chatCompleteUrl,
|
|
2316
2397
|
chatCompleteExec,
|
|
2317
|
-
agentId
|
|
2398
|
+
agentId,
|
|
2399
|
+
apiKey
|
|
2318
2400
|
});
|
|
2319
2401
|
if (chatErr) {
|
|
2320
2402
|
console.error(chatErr);
|
|
@@ -2350,11 +2432,13 @@ function listenCommand() {
|
|
|
2350
2432
|
const chatWriteback = buildChatWritebackOptions({
|
|
2351
2433
|
chatWriteback: opts.chatWriteback,
|
|
2352
2434
|
chatApiBase,
|
|
2353
|
-
|
|
2435
|
+
acnBaseUrl: config.base_url,
|
|
2436
|
+
apiKey,
|
|
2354
2437
|
chatCompleteUrl,
|
|
2355
2438
|
chatCompleteExec,
|
|
2356
2439
|
chatCompleteTimeoutMs: opts.chatWriteback ? chatCompleteTimeoutMs : void 0,
|
|
2357
|
-
agentId
|
|
2440
|
+
agentId,
|
|
2441
|
+
audience: process.env.ACN_CHAT_JWT_AUDIENCE?.trim() || process.env.AGENTPLANET_JWT_AUDIENCE?.trim()
|
|
2358
2442
|
});
|
|
2359
2443
|
runListener({
|
|
2360
2444
|
agentId,
|