@snowyroad/arp 0.4.0 → 0.5.1
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/cli.js +74 -23
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -781,14 +781,14 @@ ${fence("peer roster", joinUntrusted(lines, "\n"))}`;
|
|
|
781
781
|
}
|
|
782
782
|
|
|
783
783
|
// src/channelContext.ts
|
|
784
|
-
var
|
|
784
|
+
var MAX_INJECTED_INSTRUCTIONS_CHARS = 8e3;
|
|
785
785
|
var MAX_INJECTED_SOURCE_CHARS = 8e3;
|
|
786
786
|
var MAX_INJECTED_SOURCES_TOTAL_CHARS = 24e3;
|
|
787
787
|
function buildChannelContext(input) {
|
|
788
788
|
let out = "";
|
|
789
|
-
if (!isBlankText(input.
|
|
790
|
-
const raw = rawUntrusted(input.
|
|
791
|
-
const bounded = raw.length >
|
|
789
|
+
if (!isBlankText(input.instructions)) {
|
|
790
|
+
const raw = rawUntrusted(input.instructions);
|
|
791
|
+
const bounded = raw.length > MAX_INJECTED_INSTRUCTIONS_CHARS ? untrusted(raw.slice(0, MAX_INJECTED_INSTRUCTIONS_CHARS) + "\n[...truncated]") : input.instructions;
|
|
792
792
|
out += `## Channel Instructions (standing guidance for this channel)
|
|
793
793
|
${fence("channel instructions", bounded)}
|
|
794
794
|
---
|
|
@@ -1387,6 +1387,14 @@ var RelayClient = class {
|
|
|
1387
1387
|
activity: catchingUp ? "catching_up" : "thinking"
|
|
1388
1388
|
});
|
|
1389
1389
|
}
|
|
1390
|
+
/** Forward one normalized agent-activity event (Agent Activity View, slice 1) to the
|
|
1391
|
+
* relay over the agent WS. The emitted frame is the normalized ActivityEvent spread
|
|
1392
|
+
* out with `type: "activity_event"` and the channelId added on top — exactly the shape
|
|
1393
|
+
* the relay ingest parses. No-op if the socket is closed (send() guards readyState),
|
|
1394
|
+
* matching sendActivity: a dropped activity event must never break a turn. */
|
|
1395
|
+
sendActivityEvent(channelId, event) {
|
|
1396
|
+
this.send({ type: "activity_event", channelId, ...event });
|
|
1397
|
+
}
|
|
1390
1398
|
/** Publish this agent's partial A2A card; the relay fills url/version/provider. */
|
|
1391
1399
|
async putAgentCard(card) {
|
|
1392
1400
|
const url = `${this.cfg.relayHttpUrl}/agents/me/agent-card`;
|
|
@@ -1440,7 +1448,7 @@ var RelayClient = class {
|
|
|
1440
1448
|
if (u.thoughtTokens !== void 0) b.thoughtTokens = u.thoughtTokens;
|
|
1441
1449
|
return b;
|
|
1442
1450
|
}
|
|
1443
|
-
async postMessage(channelId, content, usage) {
|
|
1451
|
+
async postMessage(channelId, content, usage, turnId) {
|
|
1444
1452
|
const ch = this.pathId(channelId, "channelId");
|
|
1445
1453
|
if (!ch) return;
|
|
1446
1454
|
const url = `${this.cfg.relayHttpUrl}/channels/${ch}/messages`;
|
|
@@ -1451,6 +1459,9 @@ var RelayClient = class {
|
|
|
1451
1459
|
agentId: this.cfg.agentUuid,
|
|
1452
1460
|
agentName: this.cfg.agentName,
|
|
1453
1461
|
messageType: "agent",
|
|
1462
|
+
// Carry the turn's id so the relay/web can correlate this message with the
|
|
1463
|
+
// turn's activity events (Agent Activity View). Omit when absent.
|
|
1464
|
+
...turnId ? { turnId } : {},
|
|
1454
1465
|
...this.usageBody(usage)
|
|
1455
1466
|
});
|
|
1456
1467
|
try {
|
|
@@ -1494,21 +1505,21 @@ var RelayClient = class {
|
|
|
1494
1505
|
console.warn("[arp-bridge] flow post failed:", sanitizeForTty(String(err)));
|
|
1495
1506
|
}
|
|
1496
1507
|
}
|
|
1497
|
-
/** Channel
|
|
1498
|
-
async
|
|
1508
|
+
/** Channel instructions text (empty if none or on error — never throws). Branded (H2-4). */
|
|
1509
|
+
async fetchChannelInstructions(channelId) {
|
|
1499
1510
|
const ch = this.pathId(channelId, "channelId");
|
|
1500
1511
|
if (!ch) return untrusted("");
|
|
1501
|
-
const url = `${this.cfg.relayHttpUrl}/channels/${ch}/
|
|
1512
|
+
const url = `${this.cfg.relayHttpUrl}/channels/${ch}/instructions`;
|
|
1502
1513
|
try {
|
|
1503
1514
|
const res = await this.deps.fetchFn(url, { headers: { Authorization: `Bearer ${this.cfg.token}` } });
|
|
1504
1515
|
if (!res.ok) {
|
|
1505
|
-
console.warn("[arp-bridge]
|
|
1516
|
+
console.warn("[arp-bridge] instructions HTTP", res.status);
|
|
1506
1517
|
return untrusted("");
|
|
1507
1518
|
}
|
|
1508
1519
|
const data = await res.json();
|
|
1509
1520
|
return untrusted(typeof data?.content === "string" ? data.content : "");
|
|
1510
1521
|
} catch (err) {
|
|
1511
|
-
console.warn("[arp-bridge]
|
|
1522
|
+
console.warn("[arp-bridge] instructions fetch failed:", sanitizeForTty(String(err)));
|
|
1512
1523
|
return untrusted("");
|
|
1513
1524
|
}
|
|
1514
1525
|
}
|
|
@@ -1593,18 +1604,18 @@ var RelayClient = class {
|
|
|
1593
1604
|
return null;
|
|
1594
1605
|
}
|
|
1595
1606
|
}
|
|
1596
|
-
/** Assemble the situational channel-context block (
|
|
1607
|
+
/** Assemble the situational channel-context block (instructions + sources + topics) for a
|
|
1597
1608
|
* passive message. Parallel fetch with per-source graceful degradation (each fetcher
|
|
1598
1609
|
* swallows its own errors). Returns "" when there is nothing to inject.
|
|
1599
1610
|
* The fetchers return raw structured data; untrusted-data fencing happens ONCE, in
|
|
1600
1611
|
* buildChannelContext (the single-layer rule, see untrusted.ts). Do not fence here. */
|
|
1601
1612
|
async fetchChannelContext(channelId) {
|
|
1602
|
-
const [
|
|
1603
|
-
this.
|
|
1613
|
+
const [instructions, topics, pins] = await Promise.all([
|
|
1614
|
+
this.fetchChannelInstructions(channelId),
|
|
1604
1615
|
this.fetchChannelTopics(channelId),
|
|
1605
1616
|
this.fetchSourcesContext(channelId)
|
|
1606
1617
|
]);
|
|
1607
|
-
return buildChannelContext({
|
|
1618
|
+
return buildChannelContext({ instructions, topics, pins });
|
|
1608
1619
|
}
|
|
1609
1620
|
/** Fetch a flow's transcript (used to backfill a minimal turn_notification). [] on error. */
|
|
1610
1621
|
async fetchFlowMessages(channelId, flowId) {
|
|
@@ -1738,10 +1749,10 @@ ${toolStatusLine(this.toolMode)}
|
|
|
1738
1749
|
}
|
|
1739
1750
|
async start(opts) {
|
|
1740
1751
|
this.session = await this.adapter.start(opts);
|
|
1741
|
-
this.session.onTurn((full, usage) => {
|
|
1752
|
+
this.session.onTurn((full, usage, turnId) => {
|
|
1742
1753
|
this.beacon?.end();
|
|
1743
1754
|
if (full.replace(/<<silent>>/gi, "").trim() === "") return;
|
|
1744
|
-
this.onReply(full.replace(/^\s*(?:<<silent>>\s*)+/i, "").trim(), usage);
|
|
1755
|
+
this.onReply(full.replace(/^\s*(?:<<silent>>\s*)+/i, "").trim(), usage, turnId);
|
|
1745
1756
|
});
|
|
1746
1757
|
}
|
|
1747
1758
|
/**
|
|
@@ -1962,6 +1973,7 @@ function dropVendorNotifications(input) {
|
|
|
1962
1973
|
}
|
|
1963
1974
|
|
|
1964
1975
|
// src/acp/client.ts
|
|
1976
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
1965
1977
|
var MODEL_AUTH_ENV_KEYS = ["ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN"];
|
|
1966
1978
|
var BRIDGE_ENV_PREFIX = "ARP_";
|
|
1967
1979
|
function buildAcpEnv(base, extra) {
|
|
@@ -2105,6 +2117,9 @@ var AcpClient = class {
|
|
|
2105
2117
|
this.activeTurnBuffer.usage.costCurrency = u.cost.currency;
|
|
2106
2118
|
}
|
|
2107
2119
|
}
|
|
2120
|
+
if ((u.sessionUpdate === "tool_call" || u.sessionUpdate === "tool_call_update") && this.launch.onActivity && this.activeTurnBuffer) {
|
|
2121
|
+
this.emitActivity(u.sessionUpdate, u, this.activeTurnBuffer.turnId);
|
|
2122
|
+
}
|
|
2108
2123
|
},
|
|
2109
2124
|
requestPermission: async (req) => {
|
|
2110
2125
|
const verdict = evaluateAcpPermission(this.policy.mode, this.policy.configDirAbs, req);
|
|
@@ -2181,12 +2196,41 @@ var AcpClient = class {
|
|
|
2181
2196
|
});
|
|
2182
2197
|
return run;
|
|
2183
2198
|
}
|
|
2199
|
+
/**
|
|
2200
|
+
* Normalize one ACP tool-call update (metadata only) and hand it to the activity
|
|
2201
|
+
* sink, stamped with this turn's turnId. Never throws: a sink fault is caught and
|
|
2202
|
+
* logged so it cannot break the turn. SLICE 1: deliberately drops content,
|
|
2203
|
+
* rawInput, and rawOutput — only kind/title/status/locations are carried.
|
|
2204
|
+
*/
|
|
2205
|
+
emitActivity(eventType, u, turnId) {
|
|
2206
|
+
const sink = this.launch.onActivity;
|
|
2207
|
+
if (!sink) return;
|
|
2208
|
+
const locations = u.locations && u.locations.length > 0 ? u.locations.map((l) => l.line == null ? { path: l.path } : { path: l.path, line: l.line }) : null;
|
|
2209
|
+
const event = {
|
|
2210
|
+
turnId,
|
|
2211
|
+
toolCallId: u.toolCallId,
|
|
2212
|
+
eventType,
|
|
2213
|
+
kind: u.kind ?? null,
|
|
2214
|
+
title: u.title ?? null,
|
|
2215
|
+
status: u.status ?? null,
|
|
2216
|
+
locations,
|
|
2217
|
+
ts: Date.now()
|
|
2218
|
+
};
|
|
2219
|
+
try {
|
|
2220
|
+
sink(event);
|
|
2221
|
+
} catch (err) {
|
|
2222
|
+
console.warn(
|
|
2223
|
+
`[arp-bridge] onActivity sink threw (ignored): ${sanitizeForTty(String(err?.message ?? err))}`
|
|
2224
|
+
);
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2184
2227
|
/** Execute exactly one prompt turn with its own isolated reply buffer. */
|
|
2185
2228
|
async runTurn(text) {
|
|
2186
2229
|
if (!this.conn || !this._sessionId) {
|
|
2187
2230
|
throw new Error("AcpClient.submit called before start()");
|
|
2188
2231
|
}
|
|
2189
|
-
const
|
|
2232
|
+
const turnId = randomUUID2();
|
|
2233
|
+
const buffer = { text: "", turnId };
|
|
2190
2234
|
this.activeTurnBuffer = buffer;
|
|
2191
2235
|
try {
|
|
2192
2236
|
const resp = await this.guard(
|
|
@@ -2209,7 +2253,7 @@ var AcpClient = class {
|
|
|
2209
2253
|
}
|
|
2210
2254
|
};
|
|
2211
2255
|
}
|
|
2212
|
-
return { text: buffer.text, usage: buffer.usage };
|
|
2256
|
+
return { text: buffer.text, usage: buffer.usage, turnId };
|
|
2213
2257
|
} finally {
|
|
2214
2258
|
if (this.activeTurnBuffer === buffer) this.activeTurnBuffer = null;
|
|
2215
2259
|
}
|
|
@@ -2480,6 +2524,9 @@ var AcpAdapter = class {
|
|
|
2480
2524
|
cwd,
|
|
2481
2525
|
mcpServers: opts.mcpServers,
|
|
2482
2526
|
env: { ...this.launch.env, ...opts.env },
|
|
2527
|
+
// Agent Activity View: forward normalized tool-call events to the bridge-supplied
|
|
2528
|
+
// sink (wired to RelayClient.sendActivityEvent). Absent => no activity surfaced.
|
|
2529
|
+
onActivity: opts.onActivity,
|
|
2483
2530
|
session: this.session ? {
|
|
2484
2531
|
persistedId: rec?.sessionId ?? null,
|
|
2485
2532
|
save: (id) => this.session.save(mergeSessionPointer(this.session.load(), id, cwd))
|
|
@@ -2547,7 +2594,7 @@ var AcpAdapter = class {
|
|
|
2547
2594
|
const result = await client.submit(text);
|
|
2548
2595
|
this.consecutiveRestarts = 0;
|
|
2549
2596
|
const usage = this.usageSource?.forTurn(result.usage);
|
|
2550
|
-
this.turnCbs.forEach((cb) => cb(result.text, usage));
|
|
2597
|
+
this.turnCbs.forEach((cb) => cb(result.text, usage, result.turnId));
|
|
2551
2598
|
return true;
|
|
2552
2599
|
} catch (err) {
|
|
2553
2600
|
if (this.stopped) {
|
|
@@ -2830,7 +2877,7 @@ import http from "http";
|
|
|
2830
2877
|
import os from "os";
|
|
2831
2878
|
import path from "path";
|
|
2832
2879
|
import fs from "fs";
|
|
2833
|
-
import { randomUUID as
|
|
2880
|
+
import { randomUUID as randomUUID3, randomBytes } from "crypto";
|
|
2834
2881
|
var SourceBroker = class {
|
|
2835
2882
|
constructor(channelId, reader) {
|
|
2836
2883
|
this.channelId = channelId;
|
|
@@ -2843,7 +2890,7 @@ var SourceBroker = class {
|
|
|
2843
2890
|
socketPath = "";
|
|
2844
2891
|
async start() {
|
|
2845
2892
|
if (this.server) throw new Error("SourceBroker already started");
|
|
2846
|
-
this.socketPath = path.join(os.tmpdir(), `arp-broker-${
|
|
2893
|
+
this.socketPath = path.join(os.tmpdir(), `arp-broker-${randomUUID3()}.sock`);
|
|
2847
2894
|
try {
|
|
2848
2895
|
fs.unlinkSync(this.socketPath);
|
|
2849
2896
|
} catch {
|
|
@@ -2939,7 +2986,7 @@ async function startBridge(cfg, relay, deps) {
|
|
|
2939
2986
|
const beacon = new ActivityBeacon((state) => relay.sendActivity(channelId, state));
|
|
2940
2987
|
const session = new ChannelSession(
|
|
2941
2988
|
adapter,
|
|
2942
|
-
(text, usage) => void relay.postMessage(channelId, text, usage),
|
|
2989
|
+
(text, usage, turnId) => void relay.postMessage(channelId, text, usage, turnId),
|
|
2943
2990
|
cfg.agentName,
|
|
2944
2991
|
channelId,
|
|
2945
2992
|
{
|
|
@@ -2962,7 +3009,11 @@ async function startBridge(cfg, relay, deps) {
|
|
|
2962
3009
|
]
|
|
2963
3010
|
}];
|
|
2964
3011
|
try {
|
|
2965
|
-
await session.start({
|
|
3012
|
+
await session.start({
|
|
3013
|
+
model: cfg.model,
|
|
3014
|
+
mcpServers,
|
|
3015
|
+
onActivity: (event) => relay.sendActivityEvent(channelId, event)
|
|
3016
|
+
});
|
|
2966
3017
|
} catch (err) {
|
|
2967
3018
|
void broker.stop();
|
|
2968
3019
|
throw err;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@snowyroad/arp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Connect your own coding agent (Claude Code, Codex, Gemini, Grok) to an Agent Relay Protocol channel and collaborate with other agents and humans.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"author": "SnowyRoad",
|