@victor-software-house/pi-acp 0.10.0 → 0.11.0

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.
@@ -8,7 +8,7 @@ import { isAbsolute, join, resolve } from "node:path";
8
8
  import { Hono } from "hono";
9
9
  import { AgentSideConnection, RequestError, ndJsonStream } from "@agentclientprotocol/sdk";
10
10
  import { randomUUID } from "node:crypto";
11
- import { DefaultResourceLoader, SessionManager, createAgentSession, getAgentDir } from "@earendil-works/pi-coding-agent";
11
+ import { DefaultResourceLoader, SessionManager, createAgentSession, createReadToolDefinition, getAgentDir } from "@earendil-works/pi-coding-agent";
12
12
  import * as z from "zod";
13
13
  import { parse } from "yaml";
14
14
  import { $ } from "bun";
@@ -184,6 +184,30 @@ function resolveIdleMs() {
184
184
  return n * 1e3;
185
185
  }
186
186
  //#endregion
187
+ //#region src/acp/acp-read-operations.ts
188
+ function createAcpReadOperations(deps) {
189
+ const { conn, getSessionId } = deps;
190
+ return {
191
+ async readFile(absolutePath) {
192
+ const sessionId = getSessionId();
193
+ if (sessionId === "") throw new Error("pi-acp acp-fs read: sessionId not yet bound");
194
+ const response = await conn.readTextFile({
195
+ sessionId,
196
+ path: absolutePath
197
+ });
198
+ return Buffer.from(response.content, "utf8");
199
+ },
200
+ async access(absolutePath) {
201
+ const sessionId = getSessionId();
202
+ if (sessionId === "") throw new Error("pi-acp acp-fs access: sessionId not yet bound");
203
+ await conn.readTextFile({
204
+ sessionId,
205
+ path: absolutePath
206
+ });
207
+ }
208
+ };
209
+ }
210
+ //#endregion
187
211
  //#region src/acp/auth.ts
188
212
  const AUTH_METHOD_ID = "pi_terminal_login";
189
213
  function buildAuthMethods(opts) {
@@ -251,7 +275,8 @@ function parseClientCapabilities(caps) {
251
275
  if (caps === void 0 || caps === null) return {
252
276
  terminalOutput: false,
253
277
  terminalAuth: false,
254
- gatewayAuth: false
278
+ gatewayAuth: false,
279
+ fsReadTextFile: false
255
280
  };
256
281
  const meta = caps._meta;
257
282
  const terminalOutput = typeof meta === "object" && meta !== null && meta["terminal_output"] === true;
@@ -264,10 +289,12 @@ function parseClientCapabilities(caps) {
264
289
  if (typeof authMeta === "object" && authMeta !== null && "gateway" in authMeta) gatewayAuth = authMeta["gateway"] === true;
265
290
  }
266
291
  }
292
+ const fsReadTextFile = caps.fs?.readTextFile === true;
267
293
  return {
268
294
  terminalOutput,
269
295
  terminalAuth,
270
- gatewayAuth
296
+ gatewayAuth,
297
+ fsReadTextFile
271
298
  };
272
299
  }
273
300
  //#endregion
@@ -1870,7 +1897,7 @@ var SshBackend = class {
1870
1897
  //#endregion
1871
1898
  //#region package.json
1872
1899
  var name = "@victor-software-house/pi-acp";
1873
- var version = "0.10.0";
1900
+ var version = "0.11.0";
1874
1901
  //#endregion
1875
1902
  //#region src/acp/agent.ts
1876
1903
  /** Builtin ACP slash commands handled directly by the adapter. */
@@ -1959,7 +1986,8 @@ var PiAcpAgent = class {
1959
1986
  clientCapabilities = {
1960
1987
  terminalOutput: false,
1961
1988
  terminalAuth: false,
1962
- gatewayAuth: false
1989
+ gatewayAuth: false,
1990
+ fsReadTextFile: false
1963
1991
  };
1964
1992
  daemonContext;
1965
1993
  /** Unique ID for this ACP connection. Used as the ownership key in the daemon SessionRegistry. */
@@ -2059,6 +2087,44 @@ var PiAcpAgent = class {
2059
2087
  }
2060
2088
  return loader;
2061
2089
  }
2090
+ /**
2091
+ * PRD-002 §FR-6 — `read` tool ACP-FS delegation overlay.
2092
+ *
2093
+ * When the client advertises `fs.readTextFile`, we override pi's
2094
+ * built-in `read` with a custom `read` tool that proxies to
2095
+ * `connection.fs.readTextFile`. The allowlist MUST include "read" so
2096
+ * pi's customTool registration loop (which filters by name) can
2097
+ * register the override; the override then shadows the builtin via
2098
+ * the tool-definition `Map.set` path inside AgentSession.
2099
+ *
2100
+ * The sessionId ref is mutated by the caller right after
2101
+ * `createAgentSession` returns, before any model turn — the tool
2102
+ * isn't invoked until prompt-time, so the late binding is safe.
2103
+ *
2104
+ * Returns `null` when the client doesn't advertise the capability;
2105
+ * callers then skip the overlay and pi's built-in `read` handles
2106
+ * everything locally.
2107
+ */
2108
+ buildAcpReadOverlay(cwd) {
2109
+ if (!this.clientCapabilities.fsReadTextFile) return null;
2110
+ const sessionIdRef = { current: "" };
2111
+ return {
2112
+ sessionIdRef,
2113
+ tools: [
2114
+ "read",
2115
+ "bash",
2116
+ "edit",
2117
+ "write",
2118
+ "grep",
2119
+ "find",
2120
+ "ls"
2121
+ ],
2122
+ customTools: [createReadToolDefinition(cwd, { operations: createAcpReadOperations({
2123
+ conn: this.conn,
2124
+ getSessionId: () => sessionIdRef.current
2125
+ }) })]
2126
+ };
2127
+ }
2062
2128
  async initialize(params) {
2063
2129
  const supportedVersion = 1;
2064
2130
  const requested = params.protocolVersion;
@@ -2093,12 +2159,17 @@ var PiAcpAgent = class {
2093
2159
  }
2094
2160
  async newSession(params) {
2095
2161
  if (!isAbsolute(params.cwd)) throw RequestError.invalidParams(`cwd must be an absolute path: ${params.cwd}`);
2162
+ const acpReadOverlay = this.buildAcpReadOverlay(params.cwd);
2096
2163
  let result;
2097
2164
  try {
2098
2165
  const resourceLoader = await this.buildResourceLoader(params.cwd, params);
2099
2166
  result = await createAgentSession({
2100
2167
  cwd: params.cwd,
2101
- resourceLoader
2168
+ resourceLoader,
2169
+ ...acpReadOverlay ? {
2170
+ tools: acpReadOverlay.tools,
2171
+ customTools: acpReadOverlay.customTools
2172
+ } : {}
2102
2173
  });
2103
2174
  } catch (e) {
2104
2175
  const authErr = detectAuthError(e);
@@ -2107,6 +2178,7 @@ var PiAcpAgent = class {
2107
2178
  throw RequestError.internalError({}, `Failed to create pi session: ${msg}`);
2108
2179
  }
2109
2180
  const piSession = result.session;
2181
+ if (acpReadOverlay !== null) acpReadOverlay.sessionIdRef.current = piSession.sessionManager.getSessionId();
2110
2182
  if (piSession.modelRegistry.getAvailable().length === 0) {
2111
2183
  piSession.dispose();
2112
2184
  throw RequestError.authRequired({ authMethods: buildAuthMethods() }, "Configure an API key or log in with an OAuth provider.");
@@ -2366,6 +2438,7 @@ var PiAcpAgent = class {
2366
2438
  this.sessions.close(params.sessionId);
2367
2439
  const sessionFile = await this.resolveSessionFile(params.sessionId);
2368
2440
  if (sessionFile === null) throw RequestError.invalidParams(`Unknown sessionId: ${params.sessionId}`);
2441
+ const acpReadOverlay = this.buildAcpReadOverlay(params.cwd);
2369
2442
  let result;
2370
2443
  try {
2371
2444
  const sm = SessionManager.open(sessionFile);
@@ -2373,7 +2446,11 @@ var PiAcpAgent = class {
2373
2446
  result = await createAgentSession({
2374
2447
  cwd: params.cwd,
2375
2448
  sessionManager: sm,
2376
- resourceLoader
2449
+ resourceLoader,
2450
+ ...acpReadOverlay ? {
2451
+ tools: acpReadOverlay.tools,
2452
+ customTools: acpReadOverlay.customTools
2453
+ } : {}
2377
2454
  });
2378
2455
  } catch (e) {
2379
2456
  const authErr = detectAuthError(e);
@@ -2382,6 +2459,7 @@ var PiAcpAgent = class {
2382
2459
  throw RequestError.internalError({}, `Failed to load pi session: ${msg}`);
2383
2460
  }
2384
2461
  const piSession = result.session;
2462
+ if (acpReadOverlay !== null) acpReadOverlay.sessionIdRef.current = piSession.sessionManager.getSessionId();
2385
2463
  const session = new PiAcpSession({
2386
2464
  sessionId: params.sessionId,
2387
2465
  cwd: params.cwd,
@@ -2466,6 +2544,7 @@ var PiAcpAgent = class {
2466
2544
  }
2467
2545
  const sessionFile = await this.resolveSessionFile(params.sessionId);
2468
2546
  if (sessionFile === null) throw RequestError.invalidParams(`Unknown sessionId: ${params.sessionId}`);
2547
+ const acpReadOverlay = this.buildAcpReadOverlay(params.cwd);
2469
2548
  let result;
2470
2549
  try {
2471
2550
  const sm = SessionManager.open(sessionFile);
@@ -2473,7 +2552,11 @@ var PiAcpAgent = class {
2473
2552
  result = await createAgentSession({
2474
2553
  cwd: params.cwd,
2475
2554
  sessionManager: sm,
2476
- resourceLoader
2555
+ resourceLoader,
2556
+ ...acpReadOverlay ? {
2557
+ tools: acpReadOverlay.tools,
2558
+ customTools: acpReadOverlay.customTools
2559
+ } : {}
2477
2560
  });
2478
2561
  } catch (e) {
2479
2562
  const authErr = detectAuthError(e);
@@ -2482,6 +2565,7 @@ var PiAcpAgent = class {
2482
2565
  throw RequestError.internalError({}, `Failed to resume pi session: ${msg}`);
2483
2566
  }
2484
2567
  const piSession = result.session;
2568
+ if (acpReadOverlay !== null) acpReadOverlay.sessionIdRef.current = piSession.sessionManager.getSessionId();
2485
2569
  const session = new PiAcpSession({
2486
2570
  sessionId: params.sessionId,
2487
2571
  cwd: params.cwd,
@@ -2525,6 +2609,7 @@ var PiAcpAgent = class {
2525
2609
  if (!isAbsolute(params.cwd)) throw RequestError.invalidParams(`cwd must be an absolute path: ${params.cwd}`);
2526
2610
  const sourceFile = await this.resolveSessionFile(params.sessionId);
2527
2611
  if (sourceFile === null) throw RequestError.invalidParams(`Unknown sessionId: ${params.sessionId}`);
2612
+ const acpReadOverlay = this.buildAcpReadOverlay(params.cwd);
2528
2613
  let result;
2529
2614
  try {
2530
2615
  const sm = SessionManager.forkFrom(sourceFile, params.cwd);
@@ -2532,7 +2617,11 @@ var PiAcpAgent = class {
2532
2617
  result = await createAgentSession({
2533
2618
  cwd: params.cwd,
2534
2619
  sessionManager: sm,
2535
- resourceLoader
2620
+ resourceLoader,
2621
+ ...acpReadOverlay ? {
2622
+ tools: acpReadOverlay.tools,
2623
+ customTools: acpReadOverlay.customTools
2624
+ } : {}
2536
2625
  });
2537
2626
  } catch (e) {
2538
2627
  const authErr = detectAuthError(e);
@@ -2542,6 +2631,7 @@ var PiAcpAgent = class {
2542
2631
  }
2543
2632
  const piSession = result.session;
2544
2633
  const newSessionId = piSession.sessionManager.getSessionId();
2634
+ if (acpReadOverlay !== null) acpReadOverlay.sessionIdRef.current = newSessionId;
2545
2635
  const newSessionFile = piSession.sessionManager.getSessionFile();
2546
2636
  if (newSessionFile !== void 0) this.sessionPaths.set(newSessionId, newSessionFile);
2547
2637
  const session = new PiAcpSession({
@@ -3160,4 +3250,4 @@ async function runDaemon() {
3160
3250
  //#endregion
3161
3251
  export { runDaemon };
3162
3252
 
3163
- //# sourceMappingURL=daemon-xclwSgis.mjs.map
3253
+ //# sourceMappingURL=daemon-COFaIaTB.mjs.map