@victor-software-house/pi-acp 0.12.0 → 0.13.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/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 (Draft).
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 # 26 tests
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/read_text_file`, `fs/write_text_file`) -- pi reads/writes locally. Not advertised.
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
@@ -877,6 +877,7 @@ var PiAcpSession = class {
877
877
  lastEmit = Promise.resolve();
878
878
  unsubscribe;
879
879
  cleanups;
880
+ pendingDiagnosticsReport;
880
881
  constructor(opts) {
881
882
  this.sessionId = opts.sessionId;
882
883
  this.cwd = opts.cwd;
@@ -885,6 +886,7 @@ var PiAcpSession = class {
885
886
  this.conn = opts.conn;
886
887
  this.supportsTerminalOutput = opts.supportsTerminalOutput ?? false;
887
888
  this.cleanups = opts.cleanups ?? [];
889
+ this.pendingDiagnosticsReport = opts.diagnosticsReport !== void 0 && opts.diagnosticsReport !== "" ? opts.diagnosticsReport : null;
888
890
  this.unsubscribe = this.piSession.subscribe((ev) => this.handlePiEvent(ev));
889
891
  }
890
892
  dispose() {
@@ -930,6 +932,20 @@ var PiAcpSession = class {
930
932
  };
931
933
  });
932
934
  const imageContents = Array.isArray(images) ? images.filter((img) => typeof img === "object" && img !== null && "type" in img && img.type === "image") : [];
935
+ if (this.pendingDiagnosticsReport !== null) {
936
+ const report = this.pendingDiagnosticsReport;
937
+ this.pendingDiagnosticsReport = null;
938
+ this.conn.sessionUpdate({
939
+ sessionId: this.sessionId,
940
+ update: {
941
+ sessionUpdate: "agent_message_chunk",
942
+ content: {
943
+ type: "text",
944
+ text: `${report}\n`
945
+ }
946
+ }
947
+ }).catch(() => {});
948
+ }
933
949
  this.piSession.prompt(message, { images: imageContents }).catch(() => {
934
950
  this.flushEmits().finally(() => {
935
951
  const reason = this.cancelRequested ? "cancelled" : "error";
@@ -1345,6 +1361,43 @@ function acpPromptToPiMessage(blocks) {
1345
1361
  };
1346
1362
  }
1347
1363
  //#endregion
1364
+ //#region src/resources/diagnostics.ts
1365
+ function buildDiagnosticsReport(input) {
1366
+ const sourceStats = input.sources.map((source) => {
1367
+ const skills = source.getSkills();
1368
+ const prompts = source.getPrompts();
1369
+ const failures = skills.diagnostics.filter((d) => d.type === "warning" || d.type === "error").map((d) => d.message);
1370
+ return {
1371
+ id: source.id,
1372
+ kind: source.kind,
1373
+ agentsFiles: source.getAgentsFiles().length,
1374
+ skills: skills.skills.length,
1375
+ prompts: prompts.prompts.length,
1376
+ failures
1377
+ };
1378
+ });
1379
+ const lines = [];
1380
+ if (sourceStats.length > 0) {
1381
+ lines.push("[pi-acp] resources active:");
1382
+ for (const s of sourceStats) lines.push(` ${s.id.padEnd(20)} kind=${s.kind} (${s.agentsFiles} AGENTS files, ${s.skills} skills, ${s.prompts} prompts)`);
1383
+ }
1384
+ const allFailures = [];
1385
+ for (const s of sourceStats) for (const f of s.failures) allFailures.push(` ${s.id.padEnd(20)} ${f}`);
1386
+ for (const d of input.manifestDiagnostics) {
1387
+ const where = d.path !== void 0 ? ` ${d.path}` : "";
1388
+ allFailures.push(` manifest[${d.source}${where}] ${d.message}`);
1389
+ }
1390
+ if (allFailures.length > 0) {
1391
+ if (lines.length > 0) lines.push("");
1392
+ lines.push("[pi-acp] resource failures:");
1393
+ lines.push(...allFailures);
1394
+ }
1395
+ return {
1396
+ text: lines.join("\n"),
1397
+ sourceStats
1398
+ };
1399
+ }
1400
+ //#endregion
1348
1401
  //#region src/resources/sources/local.ts
1349
1402
  /**
1350
1403
  * LocalBackend: wraps pi's DefaultResourceLoader for one (cwd, agentDir) root.
@@ -1954,7 +2007,7 @@ var SshBackend = class {
1954
2007
  //#endregion
1955
2008
  //#region package.json
1956
2009
  var name = "@victor-software-house/pi-acp";
1957
- var version = "0.12.0";
2010
+ var version = "0.13.1";
1958
2011
  //#endregion
1959
2012
  //#region src/acp/agent.ts
1960
2013
  /** Builtin ACP slash commands handled directly by the adapter. */
@@ -2086,15 +2139,20 @@ var PiAcpAgent = class {
2086
2139
  * Phase 7 adds `kind: "http"`. `acp-fs` still parses fine but surfaces as
2087
2140
  * a diagnostic until its backend lands in a subsequent phase.
2088
2141
  */
2089
- async buildResourceLoader(cwd, sessionParams) {
2142
+ async buildResourceLoader(cwd, sessionParams, opts) {
2090
2143
  const loaded = await loadManifest({
2091
2144
  cwd,
2092
2145
  sessionParams
2093
2146
  });
2094
- const modeResult = resolveMode({
2147
+ const modeResult = opts?.resolveCwdMode !== false ? resolveMode({
2095
2148
  manifest: loaded.manifest,
2096
2149
  requestedCwd: cwd
2097
- });
2150
+ }) : {
2151
+ mode: loaded.manifest.mode,
2152
+ cwd,
2153
+ cleanup: () => {},
2154
+ ephemeral: false
2155
+ };
2098
2156
  const effectiveCwd = modeResult.cwd;
2099
2157
  const diagnostics = [...loaded.diagnostics];
2100
2158
  const sources = [];
@@ -2149,7 +2207,9 @@ var PiAcpAgent = class {
2149
2207
  }
2150
2208
  return {
2151
2209
  loader,
2152
- modeResult
2210
+ modeResult,
2211
+ diagnosticsEnabled: loaded.manifest.diagnostics === true,
2212
+ manifestDiagnostics: diagnostics
2153
2213
  };
2154
2214
  }
2155
2215
  /**
@@ -2223,7 +2283,7 @@ var PiAcpAgent = class {
2223
2283
  };
2224
2284
  }
2225
2285
  async newSession(params) {
2226
- const { loader: resourceLoader, modeResult } = await this.buildResourceLoader(params.cwd, params).catch((e) => {
2286
+ const { loader: resourceLoader, modeResult, diagnosticsEnabled, manifestDiagnostics } = await this.buildResourceLoader(params.cwd, params).catch((e) => {
2227
2287
  const authErr = detectAuthError(e);
2228
2288
  if (authErr !== null) throw authErr;
2229
2289
  const msg = e instanceof Error ? e.message : String(e);
@@ -2234,6 +2294,10 @@ var PiAcpAgent = class {
2234
2294
  modeResult.cleanup();
2235
2295
  throw RequestError.invalidParams(`cwd must be an absolute path: ${params.cwd}`);
2236
2296
  }
2297
+ const diagnosticsReport = diagnosticsEnabled ? buildDiagnosticsReport({
2298
+ sources: resourceLoader.listSources(),
2299
+ manifestDiagnostics
2300
+ }).text : "";
2237
2301
  const acpReadOverlay = this.buildAcpReadOverlay(effectiveCwd);
2238
2302
  let result;
2239
2303
  try {
@@ -2269,7 +2333,8 @@ var PiAcpAgent = class {
2269
2333
  piSession,
2270
2334
  conn: this.conn,
2271
2335
  supportsTerminalOutput: this.clientCapabilities.terminalOutput,
2272
- cleanups: modeResult.ephemeral ? [modeResult.cleanup] : []
2336
+ cleanups: modeResult.ephemeral ? [modeResult.cleanup] : [],
2337
+ ...diagnosticsReport !== "" ? { diagnosticsReport } : {}
2273
2338
  });
2274
2339
  this.sessions.register(session);
2275
2340
  this.registerWithDaemon({
@@ -2519,7 +2584,7 @@ var PiAcpAgent = class {
2519
2584
  let result;
2520
2585
  try {
2521
2586
  const sm = SessionManager.open(sessionFile);
2522
- const { loader: resourceLoader } = await this.buildResourceLoader(params.cwd, params);
2587
+ const { loader: resourceLoader } = await this.buildResourceLoader(params.cwd, params, { resolveCwdMode: false });
2523
2588
  result = await createAgentSession({
2524
2589
  cwd: params.cwd,
2525
2590
  sessionManager: sm,
@@ -2625,7 +2690,7 @@ var PiAcpAgent = class {
2625
2690
  let result;
2626
2691
  try {
2627
2692
  const sm = SessionManager.open(sessionFile);
2628
- const { loader: resourceLoader } = await this.buildResourceLoader(params.cwd, params);
2693
+ const { loader: resourceLoader } = await this.buildResourceLoader(params.cwd, params, { resolveCwdMode: false });
2629
2694
  result = await createAgentSession({
2630
2695
  cwd: params.cwd,
2631
2696
  sessionManager: sm,
@@ -2690,7 +2755,7 @@ var PiAcpAgent = class {
2690
2755
  let result;
2691
2756
  try {
2692
2757
  const sm = SessionManager.forkFrom(sourceFile, params.cwd);
2693
- const { loader: resourceLoader } = await this.buildResourceLoader(params.cwd, params);
2758
+ const { loader: resourceLoader } = await this.buildResourceLoader(params.cwd, params, { resolveCwdMode: false });
2694
2759
  result = await createAgentSession({
2695
2760
  cwd: params.cwd,
2696
2761
  sessionManager: sm,
@@ -3327,4 +3392,4 @@ async function runDaemon() {
3327
3392
  //#endregion
3328
3393
  export { runDaemon };
3329
3394
 
3330
- //# sourceMappingURL=daemon-D6QKWz5C.mjs.map
3395
+ //# sourceMappingURL=daemon-BErbUhcE.mjs.map