@snowyroad/arp 0.3.5 → 0.3.6
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 +94 -14
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1867,7 +1867,7 @@ var ActivityBeacon = class {
|
|
|
1867
1867
|
// src/adapter.ts
|
|
1868
1868
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
1869
1869
|
import { accessSync, constants, existsSync as existsSync2, statSync } from "fs";
|
|
1870
|
-
import { delimiter, dirname as
|
|
1870
|
+
import { delimiter, dirname as dirname3, join as join4, resolve as resolve2 } from "path";
|
|
1871
1871
|
|
|
1872
1872
|
// src/acp/client.ts
|
|
1873
1873
|
import { spawn } from "child_process";
|
|
@@ -1967,6 +1967,7 @@ var AcpClient = class {
|
|
|
1967
1967
|
child = null;
|
|
1968
1968
|
conn = null;
|
|
1969
1969
|
_sessionId = null;
|
|
1970
|
+
loadSupported = false;
|
|
1970
1971
|
/**
|
|
1971
1972
|
* The currently-running turn's reply accumulator. Set for the duration of one
|
|
1972
1973
|
* turn so agent_message_chunk text lands in THIS turn's buffer only. Because
|
|
@@ -2075,7 +2076,7 @@ var AcpClient = class {
|
|
|
2075
2076
|
}
|
|
2076
2077
|
};
|
|
2077
2078
|
this.conn = new ClientSideConnection(() => client, stream);
|
|
2078
|
-
await this.guard(
|
|
2079
|
+
const init = await this.guard(
|
|
2079
2080
|
this.conn.initialize({
|
|
2080
2081
|
protocolVersion: 1,
|
|
2081
2082
|
clientCapabilities: {
|
|
@@ -2085,10 +2086,29 @@ var AcpClient = class {
|
|
|
2085
2086
|
clientInfo: { name: "arp-bridge", version: "0.1.0" }
|
|
2086
2087
|
})
|
|
2087
2088
|
);
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
this.
|
|
2089
|
+
this.loadSupported = init.agentCapabilities?.loadSession === true;
|
|
2090
|
+
const candidateId = this._sessionId ?? this.launch.session?.persistedId ?? null;
|
|
2091
|
+
let liveId = null;
|
|
2092
|
+
if (candidateId && this.loadSupported) {
|
|
2093
|
+
try {
|
|
2094
|
+
await this.guard(
|
|
2095
|
+
this.conn.loadSession({ sessionId: candidateId, cwd: this.launch.cwd, mcpServers: [] })
|
|
2096
|
+
);
|
|
2097
|
+
liveId = candidateId;
|
|
2098
|
+
} catch (err) {
|
|
2099
|
+
console.warn(
|
|
2100
|
+
`[arp-bridge] session/load failed; starting a fresh session: ${sanitizeForTty(String(err?.message ?? err))}`
|
|
2101
|
+
);
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
if (!liveId) {
|
|
2105
|
+
const session = await this.guard(
|
|
2106
|
+
this.conn.newSession({ cwd: this.launch.cwd, mcpServers: [] })
|
|
2107
|
+
);
|
|
2108
|
+
liveId = session.sessionId;
|
|
2109
|
+
}
|
|
2110
|
+
this._sessionId = liveId;
|
|
2111
|
+
this.launch.session?.save(liveId);
|
|
2092
2112
|
}
|
|
2093
2113
|
/**
|
|
2094
2114
|
* Send one user turn. Resolves with the full assembled reply text once the
|
|
@@ -2189,6 +2209,45 @@ var AcpClient = class {
|
|
|
2189
2209
|
}
|
|
2190
2210
|
};
|
|
2191
2211
|
|
|
2212
|
+
// src/sessionStore.ts
|
|
2213
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, renameSync as renameSync2, chmodSync as chmodSync2 } from "fs";
|
|
2214
|
+
import { join as join3, dirname as dirname2 } from "path";
|
|
2215
|
+
function safe(s) {
|
|
2216
|
+
return s.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
2217
|
+
}
|
|
2218
|
+
function sessionFilePath(dir, relayUrl, agentName, channelId) {
|
|
2219
|
+
return join3(dir, "sessions", relayHostSegment(relayUrl), `${safe(agentName)}__${safe(channelId)}.json`);
|
|
2220
|
+
}
|
|
2221
|
+
function loadStoredSession(dir, relayUrl, agentName, channelId) {
|
|
2222
|
+
const file = sessionFilePath(dir, relayUrl, agentName, channelId);
|
|
2223
|
+
let raw;
|
|
2224
|
+
try {
|
|
2225
|
+
raw = readFileSync2(file, "utf8");
|
|
2226
|
+
} catch {
|
|
2227
|
+
return null;
|
|
2228
|
+
}
|
|
2229
|
+
try {
|
|
2230
|
+
const p = JSON.parse(raw);
|
|
2231
|
+
if (typeof p.sessionId === "string" && p.sessionId.trim() !== "" && typeof p.cwd === "string" && p.cwd.trim() !== "") {
|
|
2232
|
+
return { sessionId: p.sessionId, cwd: p.cwd };
|
|
2233
|
+
}
|
|
2234
|
+
return null;
|
|
2235
|
+
} catch {
|
|
2236
|
+
return null;
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
function saveStoredSession(dir, relayUrl, agentName, channelId, rec) {
|
|
2240
|
+
const file = sessionFilePath(dir, relayUrl, agentName, channelId);
|
|
2241
|
+
mkdirSync2(dirname2(file), { recursive: true, mode: 448 });
|
|
2242
|
+
const tmp = `${file}.tmp-${process.pid}`;
|
|
2243
|
+
writeFileSync2(tmp, JSON.stringify(rec, null, 2) + "\n", { mode: 384 });
|
|
2244
|
+
renameSync2(tmp, file);
|
|
2245
|
+
try {
|
|
2246
|
+
chmodSync2(file, 384);
|
|
2247
|
+
} catch {
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2192
2251
|
// src/adapter.ts
|
|
2193
2252
|
function defaultToolPolicy() {
|
|
2194
2253
|
return { mode: "readonly", configDirAbs: resolve2(configDir(process.env)) };
|
|
@@ -2205,9 +2264,9 @@ function pinned(pkg) {
|
|
|
2205
2264
|
var npxBinaryAbs = null;
|
|
2206
2265
|
function resolveNpxBinary() {
|
|
2207
2266
|
if (npxBinaryAbs) return npxBinaryAbs;
|
|
2208
|
-
const nodeDir =
|
|
2267
|
+
const nodeDir = dirname3(process.execPath);
|
|
2209
2268
|
for (const name of ["npx", "npx.cmd"]) {
|
|
2210
|
-
const candidate =
|
|
2269
|
+
const candidate = join4(nodeDir, name);
|
|
2211
2270
|
if (existsSync2(candidate)) {
|
|
2212
2271
|
npxBinaryAbs = candidate;
|
|
2213
2272
|
return candidate;
|
|
@@ -2220,7 +2279,7 @@ function resolveNpxBinary() {
|
|
|
2220
2279
|
function which(cmd, pathEnv = process.env.PATH ?? "") {
|
|
2221
2280
|
for (const dir of pathEnv.split(delimiter)) {
|
|
2222
2281
|
if (!dir) continue;
|
|
2223
|
-
const candidate =
|
|
2282
|
+
const candidate = join4(dir, cmd);
|
|
2224
2283
|
try {
|
|
2225
2284
|
accessSync(candidate, constants.X_OK);
|
|
2226
2285
|
if (statSync(candidate).isFile()) return resolve2(candidate);
|
|
@@ -2274,13 +2333,15 @@ var defaultAcpClientFactory = (launch) => new AcpClient(launch);
|
|
|
2274
2333
|
var MAX_CONSECUTIVE_RESTARTS = 3;
|
|
2275
2334
|
var RESTART_BACKOFF_MS = 250;
|
|
2276
2335
|
var AcpAdapter = class {
|
|
2277
|
-
constructor(agent, makeClient = defaultAcpClientFactory, backoffMs = RESTART_BACKOFF_MS, policy = defaultToolPolicy()) {
|
|
2336
|
+
constructor(agent, makeClient = defaultAcpClientFactory, backoffMs = RESTART_BACKOFF_MS, policy = defaultToolPolicy(), session) {
|
|
2278
2337
|
this.makeClient = makeClient;
|
|
2279
2338
|
this.backoffMs = backoffMs;
|
|
2339
|
+
this.session = session;
|
|
2280
2340
|
this.launch = { ...launchSpecFor(agent), policy };
|
|
2281
2341
|
}
|
|
2282
2342
|
makeClient;
|
|
2283
2343
|
backoffMs;
|
|
2344
|
+
session;
|
|
2284
2345
|
launch;
|
|
2285
2346
|
// --- supervised live state (set in start()) ---
|
|
2286
2347
|
client = null;
|
|
@@ -2294,7 +2355,17 @@ var AcpAdapter = class {
|
|
|
2294
2355
|
/** Latched once the loop guard trips, so we stop trying (and stop retrying turns). */
|
|
2295
2356
|
gaveUp = false;
|
|
2296
2357
|
async start(_opts) {
|
|
2297
|
-
|
|
2358
|
+
const rec = this.session?.load() ?? null;
|
|
2359
|
+
const cwd = rec?.cwd ?? this.launch.cwd;
|
|
2360
|
+
const launch = {
|
|
2361
|
+
...this.launch,
|
|
2362
|
+
cwd,
|
|
2363
|
+
session: this.session ? {
|
|
2364
|
+
persistedId: rec?.sessionId ?? null,
|
|
2365
|
+
save: (id) => this.session.save({ sessionId: id, cwd })
|
|
2366
|
+
} : void 0
|
|
2367
|
+
};
|
|
2368
|
+
this.client = this.makeClient(launch);
|
|
2298
2369
|
await this.client.start();
|
|
2299
2370
|
return {
|
|
2300
2371
|
submit: (text) => {
|
|
@@ -2520,14 +2591,23 @@ var ClaudeAdapter = class {
|
|
|
2520
2591
|
};
|
|
2521
2592
|
}
|
|
2522
2593
|
};
|
|
2523
|
-
function createAdapter(cfg) {
|
|
2594
|
+
function createAdapter(cfg, channelId) {
|
|
2524
2595
|
const policy = {
|
|
2525
2596
|
mode: cfg.toolMode,
|
|
2526
2597
|
configDirAbs: resolve2(configDir(process.env)),
|
|
2527
2598
|
agentName: cfg.agentName
|
|
2528
2599
|
// for the once-per-process "arp tools full <name>" denial hint
|
|
2529
2600
|
};
|
|
2530
|
-
|
|
2601
|
+
if (cfg.agentMode !== "acp") return new ClaudeAdapter(policy);
|
|
2602
|
+
const session = channelId ? makeSessionPersistence(cfg, channelId) : void 0;
|
|
2603
|
+
return new AcpAdapter(cfg.agent, void 0, void 0, policy, session);
|
|
2604
|
+
}
|
|
2605
|
+
function makeSessionPersistence(cfg, channelId) {
|
|
2606
|
+
const dir = configDir(process.env);
|
|
2607
|
+
return {
|
|
2608
|
+
load: () => loadStoredSession(dir, cfg.relayWsUrl, cfg.agentName, channelId),
|
|
2609
|
+
save: (rec) => saveStoredSession(dir, cfg.relayWsUrl, cfg.agentName, channelId, rec)
|
|
2610
|
+
};
|
|
2531
2611
|
}
|
|
2532
2612
|
|
|
2533
2613
|
// src/elicit.ts
|
|
@@ -2612,7 +2692,7 @@ async function startBridge(cfg, relay, deps) {
|
|
|
2612
2692
|
const inFlight = pending.get(channelId);
|
|
2613
2693
|
if (inFlight) return inFlight;
|
|
2614
2694
|
const p = (async () => {
|
|
2615
|
-
const adapter = deps.makeAdapter(cfg);
|
|
2695
|
+
const adapter = deps.makeAdapter(cfg, channelId);
|
|
2616
2696
|
const beacon = new ActivityBeacon((state) => relay.sendActivity(channelId, state));
|
|
2617
2697
|
const session = new ChannelSession(
|
|
2618
2698
|
adapter,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@snowyroad/arp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
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",
|