@victor-software-house/pi-acp 0.13.0 → 0.14.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.
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ ACP ([Agent Client Protocol](https://agentclientprotocol.com/get-started/introdu
|
|
|
7
7
|
## Specs and decisions
|
|
8
8
|
|
|
9
9
|
- [`docs/prd/PRD-001-acp-v013-zed-alignment.md`](docs/prd/PRD-001-acp-v013-zed-alignment.md) — v0.5 release PRD (Shipped).
|
|
10
|
-
- [`docs/prd/PRD-002-portable-runtime.md`](docs/prd/PRD-002-portable-runtime.md) — v0.6 portable runtime + multi-host resource composition (
|
|
10
|
+
- [`docs/prd/PRD-002-portable-runtime.md`](docs/prd/PRD-002-portable-runtime.md) — v0.6 portable runtime + multi-host resource composition (Substrate Shipped; Phases 8b/9 deferred).
|
|
11
11
|
- [`docs/prd/PRD-003-runtime-daemon.md`](docs/prd/PRD-003-runtime-daemon.md) — v0.6 long-running daemon + thin-client binary (Draft).
|
|
12
12
|
- [`docs/architecture/plan-acp-v013-zed-alignment.md`](docs/architecture/plan-acp-v013-zed-alignment.md) — v0.5 phased implementation plan.
|
|
13
13
|
- [`docs/architecture/plan-portable-runtime.md`](docs/architecture/plan-portable-runtime.md) — v0.6 portable-runtime plan.
|
|
@@ -53,6 +53,63 @@ Active development. ACP compliance is improving steadily. Development is centere
|
|
|
53
53
|
- Built-in adapter commands (see below)
|
|
54
54
|
- Authentication via Terminal Auth (ACP Registry support)
|
|
55
55
|
- Startup info block with pi version and context (configurable via `quietStartup` setting)
|
|
56
|
+
- **Resource composition manifest** (`.pi-acp.yaml`) — PRD-002 §FR-3
|
|
57
|
+
- Cascade: ACP session params > project `<cwd>/.pi-acp.yaml` > user-global `~/.pi-acp/config.yaml` > synthesized default
|
|
58
|
+
- Backends: `local`, `ssh` (Bun Shell `$` + ssh self-terminate options), `http` (HTTPS-only fetch + per-URL TTL cache, default 300s)
|
|
59
|
+
- Merge strategies: `append` (default) or `override-by-name` for skills and prompts
|
|
60
|
+
- Opt-in diagnostics surface (`diagnostics: true`) — one-line resource summary on first prompt of each session
|
|
61
|
+
- **Cwd-independence modes** (PRD-002 §FR-5)
|
|
62
|
+
- `local` (default) / `overlay` — ACP `params.cwd` used as session cwd; manifest roots compose
|
|
63
|
+
- `none` — pi-acp mints an ephemeral tmpdir under `os.tmpdir()/pi-acp-session-*`, cleaned up at session dispose. For one-shot Q&A sessions that shouldn't pollute any project directory.
|
|
64
|
+
- **ACP-FS `read` delegation** (PRD-002 §FR-6) — When the client advertises `clientCapabilities.fs.readTextFile`, pi-acp routes pi's built-in `read` tool through `connection.fs.readTextFile` instead of local disk. Lets Zed Remote read the actual remote workspace files (the ones the user is editing) while pi runs locally.
|
|
65
|
+
|
|
66
|
+
## Resource composition (`.pi-acp.yaml`)
|
|
67
|
+
|
|
68
|
+
Drop a `.pi-acp.yaml` at the project root (or `~/.pi-acp/config.yaml` for user-global defaults). Schema version `1`:
|
|
69
|
+
|
|
70
|
+
```yaml
|
|
71
|
+
version: 1
|
|
72
|
+
mode: local # local (default) | overlay | none
|
|
73
|
+
mergeStrategy: append # append | override-by-name
|
|
74
|
+
diagnostics: false # true: emit a one-line resource summary on first prompt
|
|
75
|
+
|
|
76
|
+
roots:
|
|
77
|
+
# Local roots (cwd + optional alt agentDir)
|
|
78
|
+
- id: project
|
|
79
|
+
kind: local
|
|
80
|
+
paths:
|
|
81
|
+
cwd: .
|
|
82
|
+
agentDir: ~/.pi/agent
|
|
83
|
+
|
|
84
|
+
# Remote files over SSH (operator's ~/.ssh/config honored end-to-end)
|
|
85
|
+
- id: cvm
|
|
86
|
+
kind: ssh
|
|
87
|
+
host: cvm
|
|
88
|
+
user: varaujo
|
|
89
|
+
paths:
|
|
90
|
+
agentsFiles:
|
|
91
|
+
- /home/varaujo/.pi/agent/AGENTS.md
|
|
92
|
+
- /workspace/team/SECURITY.md
|
|
93
|
+
# skills/prompts/extensions over SSH not yet implemented;
|
|
94
|
+
# declaring paths.skills here emits a diagnostic at session start.
|
|
95
|
+
|
|
96
|
+
# Public HTTPS fetch (e.g. team's shared AGENTS file on a public repo)
|
|
97
|
+
- id: team
|
|
98
|
+
kind: http
|
|
99
|
+
baseUrl: https://raw.githubusercontent.com/team/dotfiles/main
|
|
100
|
+
cache:
|
|
101
|
+
ttl: 600 # per-URL TTL in seconds; default 300, 0 disables
|
|
102
|
+
paths:
|
|
103
|
+
agentsFiles:
|
|
104
|
+
- AGENTS.md
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Cascade precedence (highest first):
|
|
108
|
+
|
|
109
|
+
1. ACP session params: `params._meta.piAcp.manifest` (inline manifest object OR string path to a YAML file)
|
|
110
|
+
2. Project: `<cwd>/.pi-acp.yaml`
|
|
111
|
+
3. User-global: `~/.pi-acp/config.yaml`
|
|
112
|
+
4. Synthesized default (single implicit local root)
|
|
56
113
|
|
|
57
114
|
## Prerequisites
|
|
58
115
|
|
|
@@ -151,7 +208,7 @@ bun run dev # run from src
|
|
|
151
208
|
bun run build # tsdown -> dist/index.mjs
|
|
152
209
|
bun run typecheck # tsc --noEmit
|
|
153
210
|
bun run lint # biome + oxlint
|
|
154
|
-
bun test #
|
|
211
|
+
bun test # 277 tests
|
|
155
212
|
```
|
|
156
213
|
|
|
157
214
|
Project layout:
|
|
@@ -191,7 +248,7 @@ test/
|
|
|
191
248
|
### Not implemented (MAY / client capabilities)
|
|
192
249
|
|
|
193
250
|
- **`agent_plan`** -- plan updates not emitted before tool execution. pi has no equivalent planning surface.
|
|
194
|
-
- **ACP filesystem delegation** (`fs/
|
|
251
|
+
- **ACP filesystem `write` delegation** (`fs/write_text_file`) -- pi writes locally. Not advertised. `fs/read_text_file` IS routed through ACP when the client advertises the capability (see Features → ACP-FS `read` delegation).
|
|
195
252
|
- **ACP terminal delegation** (`terminal/*`) -- pi executes commands locally. Not advertised.
|
|
196
253
|
|
|
197
254
|
### Design decisions
|
|
@@ -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, createReadToolDefinition, getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
11
|
+
import { DefaultResourceLoader, SessionManager, createAgentSession, createBashToolDefinition, 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,78 @@ function resolveIdleMs() {
|
|
|
184
184
|
return n * 1e3;
|
|
185
185
|
}
|
|
186
186
|
//#endregion
|
|
187
|
+
//#region src/acp/acp-bash-operations.ts
|
|
188
|
+
const POLL_INTERVAL_MS = 100;
|
|
189
|
+
const SHELL_PATH = "/bin/sh";
|
|
190
|
+
function createAcpBashOperations(deps) {
|
|
191
|
+
const { conn, getSessionId } = deps;
|
|
192
|
+
return { async exec(command, cwd, options) {
|
|
193
|
+
const sessionId = getSessionId();
|
|
194
|
+
if (sessionId === "") throw new Error("pi-acp acp-bash: sessionId not yet bound");
|
|
195
|
+
const env = options.env !== void 0 ? Object.entries(options.env).filter(([, v]) => v !== void 0).map(([name, value]) => ({
|
|
196
|
+
name,
|
|
197
|
+
value: String(value)
|
|
198
|
+
})) : [];
|
|
199
|
+
const createParams = {
|
|
200
|
+
sessionId,
|
|
201
|
+
command: SHELL_PATH,
|
|
202
|
+
args: ["-c", command],
|
|
203
|
+
cwd,
|
|
204
|
+
env
|
|
205
|
+
};
|
|
206
|
+
const terminal = await conn.createTerminal(createParams);
|
|
207
|
+
let lastOutputLen = 0;
|
|
208
|
+
let cancelled = false;
|
|
209
|
+
const abortHandler = () => {
|
|
210
|
+
cancelled = true;
|
|
211
|
+
terminal.kill().catch(() => {});
|
|
212
|
+
};
|
|
213
|
+
options.signal?.addEventListener("abort", abortHandler);
|
|
214
|
+
const pollLoop = async () => {
|
|
215
|
+
while (!cancelled) {
|
|
216
|
+
try {
|
|
217
|
+
const snap = await terminal.currentOutput();
|
|
218
|
+
if (snap.output.length > lastOutputLen) {
|
|
219
|
+
const delta = snap.output.slice(lastOutputLen);
|
|
220
|
+
lastOutputLen = snap.output.length;
|
|
221
|
+
options.onData(Buffer.from(delta, "utf8"));
|
|
222
|
+
}
|
|
223
|
+
if (snap.exitStatus !== null && snap.exitStatus !== void 0) return;
|
|
224
|
+
} catch {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
const timeoutPromise = options.timeout !== void 0 && options.timeout > 0 ? new Promise((resolve) => setTimeout(() => resolve({ timedOut: true }), options.timeout)) : null;
|
|
231
|
+
try {
|
|
232
|
+
const pollPromise = pollLoop();
|
|
233
|
+
const exitPromise = terminal.waitForExit();
|
|
234
|
+
const winner = timeoutPromise !== null ? await Promise.race([exitPromise, timeoutPromise]) : await exitPromise;
|
|
235
|
+
let exitCode;
|
|
236
|
+
if ("timedOut" in winner) {
|
|
237
|
+
await terminal.kill().catch(() => {});
|
|
238
|
+
exitCode = (await terminal.waitForExit()).exitCode ?? null;
|
|
239
|
+
} else exitCode = winner.exitCode ?? null;
|
|
240
|
+
cancelled = true;
|
|
241
|
+
await pollPromise;
|
|
242
|
+
try {
|
|
243
|
+
const final = await terminal.currentOutput();
|
|
244
|
+
if (final.output.length > lastOutputLen) {
|
|
245
|
+
const delta = final.output.slice(lastOutputLen);
|
|
246
|
+
options.onData(Buffer.from(delta, "utf8"));
|
|
247
|
+
}
|
|
248
|
+
} catch {}
|
|
249
|
+
return { exitCode };
|
|
250
|
+
} finally {
|
|
251
|
+
options.signal?.removeEventListener("abort", abortHandler);
|
|
252
|
+
try {
|
|
253
|
+
await terminal.release();
|
|
254
|
+
} catch {}
|
|
255
|
+
}
|
|
256
|
+
} };
|
|
257
|
+
}
|
|
258
|
+
//#endregion
|
|
187
259
|
//#region src/acp/acp-read-operations.ts
|
|
188
260
|
function createAcpReadOperations(deps) {
|
|
189
261
|
const { conn, getSessionId } = deps;
|
|
@@ -263,41 +335,66 @@ function detectAuthError(err) {
|
|
|
263
335
|
}
|
|
264
336
|
//#endregion
|
|
265
337
|
//#region src/acp/client-capabilities.ts
|
|
266
|
-
/**
|
|
267
|
-
* Extract well-known capability flags from ACP `ClientCapabilities`.
|
|
268
|
-
*
|
|
269
|
-
* Reads from:
|
|
270
|
-
* - `_meta.terminal_output` (terminal output rendering)
|
|
271
|
-
* - `_meta.terminal-auth` (terminal auth with command metadata)
|
|
272
|
-
* - `auth._meta.gateway` (gateway auth, future use)
|
|
273
|
-
*/
|
|
274
338
|
function parseClientCapabilities(caps) {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
};
|
|
281
|
-
const meta = caps._meta;
|
|
282
|
-
const terminalOutput = typeof meta === "object" && meta !== null && meta["terminal_output"] === true;
|
|
283
|
-
const terminalAuth = typeof meta === "object" && meta !== null && meta["terminal-auth"] === true;
|
|
284
|
-
let gatewayAuth = false;
|
|
285
|
-
if ("auth" in caps) {
|
|
286
|
-
const auth = caps.auth;
|
|
287
|
-
if (typeof auth === "object" && auth !== null && "_meta" in auth) {
|
|
288
|
-
const authMeta = auth._meta;
|
|
289
|
-
if (typeof authMeta === "object" && authMeta !== null && "gateway" in authMeta) gatewayAuth = authMeta["gateway"] === true;
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
const fsReadTextFile = caps.fs?.readTextFile === true;
|
|
339
|
+
const safe = caps ?? {};
|
|
340
|
+
const meta = safe._meta;
|
|
341
|
+
const metaIsObject = typeof meta === "object" && meta !== null;
|
|
342
|
+
const authMeta = "auth" in safe && typeof safe.auth === "object" && safe.auth !== null && "_meta" in safe.auth ? safe.auth._meta : void 0;
|
|
343
|
+
const authMetaIsObject = typeof authMeta === "object" && authMeta !== null;
|
|
293
344
|
return {
|
|
294
|
-
terminalOutput,
|
|
295
|
-
terminalAuth,
|
|
296
|
-
gatewayAuth,
|
|
297
|
-
fsReadTextFile
|
|
345
|
+
terminalOutput: metaIsObject && meta["terminal_output"] === true,
|
|
346
|
+
terminalAuth: metaIsObject && meta["terminal-auth"] === true,
|
|
347
|
+
gatewayAuth: authMetaIsObject && authMeta["gateway"] === true,
|
|
348
|
+
fsReadTextFile: safe.fs?.readTextFile === true,
|
|
349
|
+
terminal: safe.terminal === true
|
|
298
350
|
};
|
|
299
351
|
}
|
|
300
352
|
//#endregion
|
|
353
|
+
//#region src/acp/ext-methods.ts
|
|
354
|
+
/**
|
|
355
|
+
* ACP `extMethod` / `extNotification` dispatcher.
|
|
356
|
+
*
|
|
357
|
+
* ACP spec recommends prefixing extension method names with a unique
|
|
358
|
+
* identifier (e.g., a domain name). pi-acp uses the `pi-acp/` prefix for
|
|
359
|
+
* its built-ins; client-defined methods can also be routed here by
|
|
360
|
+
* registering handlers via `register()`.
|
|
361
|
+
*
|
|
362
|
+
* Unknown request methods throw `RequestError.methodNotFound`. Unknown
|
|
363
|
+
* notification methods are silently ignored per JSON-RPC 2.0 semantics —
|
|
364
|
+
* notifications have no response channel, so erroring is meaningless.
|
|
365
|
+
*/
|
|
366
|
+
var ExtMethodDispatcher = class {
|
|
367
|
+
requestHandlers = /* @__PURE__ */ new Map();
|
|
368
|
+
notificationHandlers = /* @__PURE__ */ new Map();
|
|
369
|
+
constructor(deps) {
|
|
370
|
+
this.requestHandlers.set("pi-acp/ping", () => ({
|
|
371
|
+
ok: true,
|
|
372
|
+
ts: Date.now()
|
|
373
|
+
}));
|
|
374
|
+
this.requestHandlers.set("pi-acp/runtime-info", () => ({
|
|
375
|
+
version: deps.version,
|
|
376
|
+
uptimeMs: Date.now() - deps.startedAt,
|
|
377
|
+
sessionCount: deps.sessionCount()
|
|
378
|
+
}));
|
|
379
|
+
}
|
|
380
|
+
register(method, handler) {
|
|
381
|
+
this.requestHandlers.set(method, handler);
|
|
382
|
+
}
|
|
383
|
+
registerNotification(method, handler) {
|
|
384
|
+
this.notificationHandlers.set(method, handler);
|
|
385
|
+
}
|
|
386
|
+
async handleRequest(method, params) {
|
|
387
|
+
const handler = this.requestHandlers.get(method);
|
|
388
|
+
if (handler === void 0) throw RequestError.methodNotFound(method);
|
|
389
|
+
return await handler(params);
|
|
390
|
+
}
|
|
391
|
+
async handleNotification(method, params) {
|
|
392
|
+
const handler = this.notificationHandlers.get(method);
|
|
393
|
+
if (handler === void 0) return;
|
|
394
|
+
await handler(params);
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
//#endregion
|
|
301
398
|
//#region src/acp/model-alias.ts
|
|
302
399
|
/**
|
|
303
400
|
* Tokenize a string: split on non-alphanumeric, lowercase, strip "claude".
|
|
@@ -856,6 +953,17 @@ var SessionManager$1 = class {
|
|
|
856
953
|
if (!s) throw RequestError.invalidParams(`Unknown sessionId: ${sessionId}`);
|
|
857
954
|
return s;
|
|
858
955
|
}
|
|
956
|
+
size() {
|
|
957
|
+
return this.sessions.size;
|
|
958
|
+
}
|
|
959
|
+
values() {
|
|
960
|
+
return this.sessions.values();
|
|
961
|
+
}
|
|
962
|
+
/** First registered session, or undefined. Order = insertion order. */
|
|
963
|
+
first() {
|
|
964
|
+
const it = this.sessions.values().next();
|
|
965
|
+
return it.done === true ? void 0 : it.value;
|
|
966
|
+
}
|
|
859
967
|
};
|
|
860
968
|
var PiAcpSession = class {
|
|
861
969
|
sessionId;
|
|
@@ -2007,7 +2115,7 @@ var SshBackend = class {
|
|
|
2007
2115
|
//#endregion
|
|
2008
2116
|
//#region package.json
|
|
2009
2117
|
var name = "@victor-software-house/pi-acp";
|
|
2010
|
-
var version = "0.
|
|
2118
|
+
var version = "0.14.0";
|
|
2011
2119
|
//#endregion
|
|
2012
2120
|
//#region src/acp/agent.ts
|
|
2013
2121
|
/** Builtin ACP slash commands handled directly by the adapter. */
|
|
@@ -2097,11 +2205,14 @@ var PiAcpAgent = class {
|
|
|
2097
2205
|
terminalOutput: false,
|
|
2098
2206
|
terminalAuth: false,
|
|
2099
2207
|
gatewayAuth: false,
|
|
2100
|
-
fsReadTextFile: false
|
|
2208
|
+
fsReadTextFile: false,
|
|
2209
|
+
terminal: false
|
|
2101
2210
|
};
|
|
2102
2211
|
daemonContext;
|
|
2103
2212
|
/** Unique ID for this ACP connection. Used as the ownership key in the daemon SessionRegistry. */
|
|
2104
2213
|
connectionId = randomUUID();
|
|
2214
|
+
extMethods;
|
|
2215
|
+
startedAt = Date.now();
|
|
2105
2216
|
dispose() {
|
|
2106
2217
|
if (this.daemonContext !== void 0) {
|
|
2107
2218
|
const registry = this.daemonContext.sessionRegistry;
|
|
@@ -2114,6 +2225,17 @@ var PiAcpAgent = class {
|
|
|
2114
2225
|
constructor(conn, daemonContext) {
|
|
2115
2226
|
this.conn = conn;
|
|
2116
2227
|
this.daemonContext = daemonContext;
|
|
2228
|
+
this.extMethods = new ExtMethodDispatcher({
|
|
2229
|
+
version,
|
|
2230
|
+
startedAt: this.startedAt,
|
|
2231
|
+
sessionCount: () => this.sessions.size()
|
|
2232
|
+
});
|
|
2233
|
+
}
|
|
2234
|
+
async extMethod(method, params) {
|
|
2235
|
+
return this.extMethods.handleRequest(method, params);
|
|
2236
|
+
}
|
|
2237
|
+
async extNotification(method, params) {
|
|
2238
|
+
await this.extMethods.handleNotification(method, params);
|
|
2117
2239
|
}
|
|
2118
2240
|
registerWithDaemon(input) {
|
|
2119
2241
|
if (this.daemonContext === void 0) return;
|
|
@@ -2139,15 +2261,20 @@ var PiAcpAgent = class {
|
|
|
2139
2261
|
* Phase 7 adds `kind: "http"`. `acp-fs` still parses fine but surfaces as
|
|
2140
2262
|
* a diagnostic until its backend lands in a subsequent phase.
|
|
2141
2263
|
*/
|
|
2142
|
-
async buildResourceLoader(cwd, sessionParams) {
|
|
2264
|
+
async buildResourceLoader(cwd, sessionParams, opts) {
|
|
2143
2265
|
const loaded = await loadManifest({
|
|
2144
2266
|
cwd,
|
|
2145
2267
|
sessionParams
|
|
2146
2268
|
});
|
|
2147
|
-
const modeResult = resolveMode({
|
|
2269
|
+
const modeResult = opts?.resolveCwdMode !== false ? resolveMode({
|
|
2148
2270
|
manifest: loaded.manifest,
|
|
2149
2271
|
requestedCwd: cwd
|
|
2150
|
-
})
|
|
2272
|
+
}) : {
|
|
2273
|
+
mode: loaded.manifest.mode,
|
|
2274
|
+
cwd,
|
|
2275
|
+
cleanup: () => {},
|
|
2276
|
+
ephemeral: false
|
|
2277
|
+
};
|
|
2151
2278
|
const effectiveCwd = modeResult.cwd;
|
|
2152
2279
|
const diagnostics = [...loaded.diagnostics];
|
|
2153
2280
|
const sources = [];
|
|
@@ -2208,26 +2335,43 @@ var PiAcpAgent = class {
|
|
|
2208
2335
|
};
|
|
2209
2336
|
}
|
|
2210
2337
|
/**
|
|
2211
|
-
* PRD-002 §FR-6 —
|
|
2338
|
+
* PRD-002 §FR-6 + §FR-6.5 — tool overrides for ACP-FS read + ACP terminal bash.
|
|
2212
2339
|
*
|
|
2213
|
-
*
|
|
2214
|
-
*
|
|
2215
|
-
*
|
|
2216
|
-
*
|
|
2217
|
-
*
|
|
2218
|
-
* the tool-definition `Map.set` path inside AgentSession.
|
|
2340
|
+
* For each tool we override, the allowlist MUST include the original
|
|
2341
|
+
* tool name so pi's customTool registration loop (which filters by
|
|
2342
|
+
* name) can register the override; the override then shadows the
|
|
2343
|
+
* builtin via the tool-definition `Map.set` path inside AgentSession
|
|
2344
|
+
* (verified against pi source — agent-session.js:1811).
|
|
2219
2345
|
*
|
|
2220
|
-
*
|
|
2221
|
-
* `
|
|
2222
|
-
*
|
|
2346
|
+
* SessionId binding is late: pi mints the id inside `createAgentSession`
|
|
2347
|
+
* (after `customTools` is built), so we share a single mutable ref
|
|
2348
|
+
* across all overrides. The caller mutates `sessionIdRef.current`
|
|
2349
|
+
* right after createAgentSession returns, before any model turn — the
|
|
2350
|
+
* tools aren't invoked until prompt-time, so the late binding is safe.
|
|
2223
2351
|
*
|
|
2224
|
-
* Returns `null` when
|
|
2225
|
-
*
|
|
2226
|
-
* everything locally.
|
|
2352
|
+
* Returns `null` when neither capability is advertised; callers skip
|
|
2353
|
+
* the overlay and pi's built-in tools handle everything locally.
|
|
2227
2354
|
*/
|
|
2228
|
-
|
|
2229
|
-
|
|
2355
|
+
buildAcpToolOverlay(cwd) {
|
|
2356
|
+
const wantRead = this.clientCapabilities.fsReadTextFile;
|
|
2357
|
+
const wantBash = this.clientCapabilities.terminal;
|
|
2358
|
+
if (!wantRead && !wantBash) return null;
|
|
2230
2359
|
const sessionIdRef = { current: "" };
|
|
2360
|
+
const customTools = [];
|
|
2361
|
+
if (wantRead) {
|
|
2362
|
+
const readToolDef = createReadToolDefinition(cwd, { operations: createAcpReadOperations({
|
|
2363
|
+
conn: this.conn,
|
|
2364
|
+
getSessionId: () => sessionIdRef.current
|
|
2365
|
+
}) });
|
|
2366
|
+
customTools.push(readToolDef);
|
|
2367
|
+
}
|
|
2368
|
+
if (wantBash) {
|
|
2369
|
+
const bashToolDef = createBashToolDefinition(cwd, { operations: createAcpBashOperations({
|
|
2370
|
+
conn: this.conn,
|
|
2371
|
+
getSessionId: () => sessionIdRef.current
|
|
2372
|
+
}) });
|
|
2373
|
+
customTools.push(bashToolDef);
|
|
2374
|
+
}
|
|
2231
2375
|
return {
|
|
2232
2376
|
sessionIdRef,
|
|
2233
2377
|
tools: [
|
|
@@ -2239,10 +2383,7 @@ var PiAcpAgent = class {
|
|
|
2239
2383
|
"find",
|
|
2240
2384
|
"ls"
|
|
2241
2385
|
],
|
|
2242
|
-
customTools
|
|
2243
|
-
conn: this.conn,
|
|
2244
|
-
getSessionId: () => sessionIdRef.current
|
|
2245
|
-
}) })]
|
|
2386
|
+
customTools
|
|
2246
2387
|
};
|
|
2247
2388
|
}
|
|
2248
2389
|
async initialize(params) {
|
|
@@ -2293,15 +2434,15 @@ var PiAcpAgent = class {
|
|
|
2293
2434
|
sources: resourceLoader.listSources(),
|
|
2294
2435
|
manifestDiagnostics
|
|
2295
2436
|
}).text : "";
|
|
2296
|
-
const
|
|
2437
|
+
const acpToolOverlay = this.buildAcpToolOverlay(effectiveCwd);
|
|
2297
2438
|
let result;
|
|
2298
2439
|
try {
|
|
2299
2440
|
result = await createAgentSession({
|
|
2300
2441
|
cwd: effectiveCwd,
|
|
2301
2442
|
resourceLoader,
|
|
2302
|
-
...
|
|
2303
|
-
tools:
|
|
2304
|
-
customTools:
|
|
2443
|
+
...acpToolOverlay ? {
|
|
2444
|
+
tools: acpToolOverlay.tools,
|
|
2445
|
+
customTools: acpToolOverlay.customTools
|
|
2305
2446
|
} : {}
|
|
2306
2447
|
});
|
|
2307
2448
|
} catch (e) {
|
|
@@ -2312,7 +2453,7 @@ var PiAcpAgent = class {
|
|
|
2312
2453
|
throw RequestError.internalError({}, `Failed to create pi session: ${msg}`);
|
|
2313
2454
|
}
|
|
2314
2455
|
const piSession = result.session;
|
|
2315
|
-
if (
|
|
2456
|
+
if (acpToolOverlay !== null) acpToolOverlay.sessionIdRef.current = piSession.sessionManager.getSessionId();
|
|
2316
2457
|
if (piSession.modelRegistry.getAvailable().length === 0) {
|
|
2317
2458
|
piSession.dispose();
|
|
2318
2459
|
modeResult.cleanup();
|
|
@@ -2575,18 +2716,18 @@ var PiAcpAgent = class {
|
|
|
2575
2716
|
this.sessions.close(params.sessionId);
|
|
2576
2717
|
const sessionFile = await this.resolveSessionFile(params.sessionId);
|
|
2577
2718
|
if (sessionFile === null) throw RequestError.invalidParams(`Unknown sessionId: ${params.sessionId}`);
|
|
2578
|
-
const
|
|
2719
|
+
const acpToolOverlay = this.buildAcpToolOverlay(params.cwd);
|
|
2579
2720
|
let result;
|
|
2580
2721
|
try {
|
|
2581
2722
|
const sm = SessionManager.open(sessionFile);
|
|
2582
|
-
const { loader: resourceLoader } = await this.buildResourceLoader(params.cwd, params);
|
|
2723
|
+
const { loader: resourceLoader } = await this.buildResourceLoader(params.cwd, params, { resolveCwdMode: false });
|
|
2583
2724
|
result = await createAgentSession({
|
|
2584
2725
|
cwd: params.cwd,
|
|
2585
2726
|
sessionManager: sm,
|
|
2586
2727
|
resourceLoader,
|
|
2587
|
-
...
|
|
2588
|
-
tools:
|
|
2589
|
-
customTools:
|
|
2728
|
+
...acpToolOverlay ? {
|
|
2729
|
+
tools: acpToolOverlay.tools,
|
|
2730
|
+
customTools: acpToolOverlay.customTools
|
|
2590
2731
|
} : {}
|
|
2591
2732
|
});
|
|
2592
2733
|
} catch (e) {
|
|
@@ -2596,7 +2737,7 @@ var PiAcpAgent = class {
|
|
|
2596
2737
|
throw RequestError.internalError({}, `Failed to load pi session: ${msg}`);
|
|
2597
2738
|
}
|
|
2598
2739
|
const piSession = result.session;
|
|
2599
|
-
if (
|
|
2740
|
+
if (acpToolOverlay !== null) acpToolOverlay.sessionIdRef.current = piSession.sessionManager.getSessionId();
|
|
2600
2741
|
const session = new PiAcpSession({
|
|
2601
2742
|
sessionId: params.sessionId,
|
|
2602
2743
|
cwd: params.cwd,
|
|
@@ -2681,18 +2822,18 @@ var PiAcpAgent = class {
|
|
|
2681
2822
|
}
|
|
2682
2823
|
const sessionFile = await this.resolveSessionFile(params.sessionId);
|
|
2683
2824
|
if (sessionFile === null) throw RequestError.invalidParams(`Unknown sessionId: ${params.sessionId}`);
|
|
2684
|
-
const
|
|
2825
|
+
const acpToolOverlay = this.buildAcpToolOverlay(params.cwd);
|
|
2685
2826
|
let result;
|
|
2686
2827
|
try {
|
|
2687
2828
|
const sm = SessionManager.open(sessionFile);
|
|
2688
|
-
const { loader: resourceLoader } = await this.buildResourceLoader(params.cwd, params);
|
|
2829
|
+
const { loader: resourceLoader } = await this.buildResourceLoader(params.cwd, params, { resolveCwdMode: false });
|
|
2689
2830
|
result = await createAgentSession({
|
|
2690
2831
|
cwd: params.cwd,
|
|
2691
2832
|
sessionManager: sm,
|
|
2692
2833
|
resourceLoader,
|
|
2693
|
-
...
|
|
2694
|
-
tools:
|
|
2695
|
-
customTools:
|
|
2834
|
+
...acpToolOverlay ? {
|
|
2835
|
+
tools: acpToolOverlay.tools,
|
|
2836
|
+
customTools: acpToolOverlay.customTools
|
|
2696
2837
|
} : {}
|
|
2697
2838
|
});
|
|
2698
2839
|
} catch (e) {
|
|
@@ -2702,7 +2843,7 @@ var PiAcpAgent = class {
|
|
|
2702
2843
|
throw RequestError.internalError({}, `Failed to resume pi session: ${msg}`);
|
|
2703
2844
|
}
|
|
2704
2845
|
const piSession = result.session;
|
|
2705
|
-
if (
|
|
2846
|
+
if (acpToolOverlay !== null) acpToolOverlay.sessionIdRef.current = piSession.sessionManager.getSessionId();
|
|
2706
2847
|
const session = new PiAcpSession({
|
|
2707
2848
|
sessionId: params.sessionId,
|
|
2708
2849
|
cwd: params.cwd,
|
|
@@ -2746,18 +2887,18 @@ var PiAcpAgent = class {
|
|
|
2746
2887
|
if (!isAbsolute(params.cwd)) throw RequestError.invalidParams(`cwd must be an absolute path: ${params.cwd}`);
|
|
2747
2888
|
const sourceFile = await this.resolveSessionFile(params.sessionId);
|
|
2748
2889
|
if (sourceFile === null) throw RequestError.invalidParams(`Unknown sessionId: ${params.sessionId}`);
|
|
2749
|
-
const
|
|
2890
|
+
const acpToolOverlay = this.buildAcpToolOverlay(params.cwd);
|
|
2750
2891
|
let result;
|
|
2751
2892
|
try {
|
|
2752
2893
|
const sm = SessionManager.forkFrom(sourceFile, params.cwd);
|
|
2753
|
-
const { loader: resourceLoader } = await this.buildResourceLoader(params.cwd, params);
|
|
2894
|
+
const { loader: resourceLoader } = await this.buildResourceLoader(params.cwd, params, { resolveCwdMode: false });
|
|
2754
2895
|
result = await createAgentSession({
|
|
2755
2896
|
cwd: params.cwd,
|
|
2756
2897
|
sessionManager: sm,
|
|
2757
2898
|
resourceLoader,
|
|
2758
|
-
...
|
|
2759
|
-
tools:
|
|
2760
|
-
customTools:
|
|
2899
|
+
...acpToolOverlay ? {
|
|
2900
|
+
tools: acpToolOverlay.tools,
|
|
2901
|
+
customTools: acpToolOverlay.customTools
|
|
2761
2902
|
} : {}
|
|
2762
2903
|
});
|
|
2763
2904
|
} catch (e) {
|
|
@@ -2768,7 +2909,7 @@ var PiAcpAgent = class {
|
|
|
2768
2909
|
}
|
|
2769
2910
|
const piSession = result.session;
|
|
2770
2911
|
const newSessionId = piSession.sessionManager.getSessionId();
|
|
2771
|
-
if (
|
|
2912
|
+
if (acpToolOverlay !== null) acpToolOverlay.sessionIdRef.current = newSessionId;
|
|
2772
2913
|
const newSessionFile = piSession.sessionManager.getSessionFile();
|
|
2773
2914
|
if (newSessionFile !== void 0) this.sessionPaths.set(newSessionId, newSessionFile);
|
|
2774
2915
|
const session = new PiAcpSession({
|
|
@@ -3387,4 +3528,4 @@ async function runDaemon() {
|
|
|
3387
3528
|
//#endregion
|
|
3388
3529
|
export { runDaemon };
|
|
3389
3530
|
|
|
3390
|
-
//# sourceMappingURL=daemon-
|
|
3531
|
+
//# sourceMappingURL=daemon-CjPR14E_.mjs.map
|