pi-acp 0.0.31 → 0.0.33

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
@@ -38,7 +38,7 @@ npm install -g @earendil-works/pi-coding-agent
38
38
  ```
39
39
 
40
40
  - Node.js 22+
41
- - `pi` installed and available on your `PATH` (the adapter runs the `pi` executable)
41
+ - `pi` v0.80.4+ installed and available on your `PATH` (the adapter runs the `pi` executable)
42
42
  - Configure `pi` separately for your model providers/API keys
43
43
 
44
44
  ## Install
package/dist/index.js CHANGED
@@ -526,6 +526,52 @@ function expandSlashCommand(text, fileCommands) {
526
526
  return substituteArgs(cmd.content, args);
527
527
  }
528
528
 
529
+ // src/acp/translate/bash.ts
530
+ function isBashTool(toolName) {
531
+ return toolName.toLowerCase() === "bash";
532
+ }
533
+ function bashCommand(value) {
534
+ const record = value;
535
+ const command = record?.command ?? record?.cmd ?? record?.args?.command ?? record?.args?.cmd ?? record?.input?.command ?? record?.input?.cmd ?? record?.rawInput?.command ?? record?.rawInput?.cmd ?? record?.toolInput?.command ?? record?.toolInput?.cmd ?? record?.details?.command ?? record?.details?.cmd;
536
+ return typeof command === "string" && command.trim() ? command : void 0;
537
+ }
538
+ function bashResultText(result) {
539
+ const record = result;
540
+ const content = record?.content;
541
+ if (Array.isArray(content)) {
542
+ const texts = content.map((c) => {
543
+ const block = c;
544
+ return block.type === "text" && typeof block.text === "string" ? block.text : "";
545
+ }).filter(Boolean);
546
+ if (texts.length) return texts.join("");
547
+ }
548
+ const details = record?.details;
549
+ const stdout = (typeof details?.stdout === "string" ? details.stdout : void 0) ?? (typeof record?.stdout === "string" ? record.stdout : void 0) ?? (typeof details?.output === "string" ? details.output : void 0) ?? (typeof record?.output === "string" ? record.output : void 0);
550
+ const stderr = (typeof details?.stderr === "string" ? details.stderr : void 0) ?? (typeof record?.stderr === "string" ? record.stderr : void 0);
551
+ return [stdout, stderr].filter((part) => typeof part === "string" && part.length > 0).join("\n");
552
+ }
553
+ function bashExitCode(result, isError) {
554
+ const record = result;
555
+ const details = record?.details;
556
+ const exitCode = details?.exitCode ?? record?.exitCode ?? details?.code ?? record?.code;
557
+ return typeof exitCode === "number" ? exitCode : isError ? 1 : 0;
558
+ }
559
+ function bashOutputDelta(previous, next) {
560
+ return next.startsWith(previous) ? next.slice(previous.length) : next;
561
+ }
562
+ function bashTerminalContent(toolCallId) {
563
+ return [{ type: "terminal", terminalId: toolCallId }];
564
+ }
565
+ function bashTerminalInfoMeta(toolCallId, cwd) {
566
+ return { terminal_info: { terminal_id: toolCallId, cwd } };
567
+ }
568
+ function bashTerminalOutputMeta(toolCallId, data) {
569
+ return { terminal_output: { terminal_id: toolCallId, data } };
570
+ }
571
+ function bashTerminalExitMeta(toolCallId, exitCode) {
572
+ return { terminal_exit: { terminal_id: toolCallId, exit_code: exitCode, signal: null } };
573
+ }
574
+
529
575
  // src/acp/translate/pi-tools.ts
530
576
  function toolResultToText(result) {
531
577
  if (!result) return "";
@@ -741,14 +787,17 @@ var PiAcpSession = class {
741
787
  // Some pi events can arrive out of order (e.g. late toolcall_* deltas after execution starts),
742
788
  // and clients may hide progress if we ever downgrade back to `pending`.
743
789
  currentToolCalls = /* @__PURE__ */ new Map();
744
- // pi can emit multiple `turn_end` events for a single user prompt (e.g. after tool_use).
745
- // The overall agent loop completes when `agent_end` is emitted.
790
+ // pi can emit multiple `turn_end` and `agent_end` events for a single user prompt
791
+ // when retry, compaction, or queued continuations run. The session-level prompt
792
+ // completes only when `agent_settled` is emitted.
746
793
  inAgentLoop = false;
747
794
  // For ACP diff support: capture file contents before edit/write mutations,
748
795
  // then emit ToolCallContent {type:"diff"}. Compatible structured edit/write
749
796
  // events may need to be implemented in pi in the future.
750
797
  fileSnapshots = /* @__PURE__ */ new Map();
751
798
  fileMutationToolCallIds = /* @__PURE__ */ new Set();
799
+ bashToolCallIds = /* @__PURE__ */ new Set();
800
+ bashOutputSnapshots = /* @__PURE__ */ new Map();
752
801
  // Ensure `session/update` notifications are sent in order and can be awaited
753
802
  // before completing a `session/prompt` request.
754
803
  lastEmit = Promise.resolve();
@@ -832,6 +881,41 @@ var PiAcpSession = class {
832
881
  async flushEmits() {
833
882
  await this.lastEmit;
834
883
  }
884
+ emitBashToolCall(params) {
885
+ this.bashToolCallIds.add(params.toolCallId);
886
+ this.emit({
887
+ sessionUpdate: params.sessionUpdate,
888
+ toolCallId: params.toolCallId,
889
+ title: bashCommand(params.args) ?? params.toolName,
890
+ kind: "execute",
891
+ status: params.status,
892
+ locations: params.locations,
893
+ ...params.includeTerminal ? { content: bashTerminalContent(params.toolCallId) } : {},
894
+ ...params.includeTerminal ? { _meta: bashTerminalInfoMeta(params.toolCallId, this.cwd) } : {}
895
+ });
896
+ }
897
+ emitBashOutputUpdate(params) {
898
+ const text = bashResultText(params.result);
899
+ const previous = this.bashOutputSnapshots.get(params.toolCallId) ?? "";
900
+ const delta = bashOutputDelta(previous, text);
901
+ this.bashOutputSnapshots.set(params.toolCallId, text);
902
+ this.emit({
903
+ sessionUpdate: "tool_call_update",
904
+ toolCallId: params.toolCallId,
905
+ status: params.status,
906
+ _meta: {
907
+ ...delta ? bashTerminalOutputMeta(params.toolCallId, delta) : {},
908
+ ...params.status === "completed" || params.status === "failed" ? bashTerminalExitMeta(params.toolCallId, bashExitCode(params.result, Boolean(params.isError))) : {}
909
+ }
910
+ });
911
+ }
912
+ cleanupToolCall(toolCallId) {
913
+ this.currentToolCalls.delete(toolCallId);
914
+ this.fileSnapshots.delete(toolCallId);
915
+ this.fileMutationToolCallIds.delete(toolCallId);
916
+ this.bashToolCallIds.delete(toolCallId);
917
+ this.bashOutputSnapshots.delete(toolCallId);
918
+ }
835
919
  startTurn(t) {
836
920
  this.cancelRequested = false;
837
921
  this.inAgentLoop = false;
@@ -899,7 +983,18 @@ var PiAcpSession = class {
899
983
  const locations = toToolCallLocations(rawInput, this.cwd);
900
984
  const existingStatus = this.currentToolCalls.get(toolCallId);
901
985
  const status = existingStatus ?? "pending";
902
- if (!existingStatus) {
986
+ if (isBashTool(toolName)) {
987
+ if (!existingStatus) this.currentToolCalls.set(toolCallId, "pending");
988
+ this.emitBashToolCall({
989
+ sessionUpdate: existingStatus ? "tool_call_update" : "tool_call",
990
+ toolCallId,
991
+ toolName,
992
+ args: rawInput,
993
+ status,
994
+ locations,
995
+ includeTerminal: !existingStatus
996
+ });
997
+ } else if (!existingStatus) {
903
998
  this.currentToolCalls.set(toolCallId, "pending");
904
999
  this.emit({
905
1000
  sessionUpdate: "tool_call",
@@ -929,6 +1024,21 @@ var PiAcpSession = class {
929
1024
  const toolName = String(ev.toolName ?? "tool");
930
1025
  const args = ev.args;
931
1026
  let line;
1027
+ if (isBashTool(toolName)) {
1028
+ const locations2 = toToolCallLocations(args, this.cwd);
1029
+ const existingStatus = this.currentToolCalls.get(toolCallId);
1030
+ this.currentToolCalls.set(toolCallId, "in_progress");
1031
+ this.emitBashToolCall({
1032
+ sessionUpdate: existingStatus ? "tool_call_update" : "tool_call",
1033
+ toolCallId,
1034
+ toolName,
1035
+ args,
1036
+ status: "in_progress",
1037
+ locations: locations2,
1038
+ includeTerminal: !existingStatus
1039
+ });
1040
+ break;
1041
+ }
932
1042
  const isFileMutation = toolName === "edit" || toolName === "write";
933
1043
  let snapshotOldText;
934
1044
  if (isFileMutation) {
@@ -979,6 +1089,10 @@ var PiAcpSession = class {
979
1089
  const toolCallId = String(ev.toolCallId ?? "");
980
1090
  if (!toolCallId) break;
981
1091
  const partial = ev.partialResult;
1092
+ if (this.bashToolCallIds.has(toolCallId)) {
1093
+ this.emitBashOutputUpdate({ toolCallId, status: "in_progress", result: partial });
1094
+ break;
1095
+ }
982
1096
  const text = this.fileMutationToolCallIds.has(toolCallId) ? "" : toolResultToText(partial);
983
1097
  this.emit({
984
1098
  sessionUpdate: "tool_call_update",
@@ -994,6 +1108,16 @@ var PiAcpSession = class {
994
1108
  if (!toolCallId) break;
995
1109
  const result = ev.result;
996
1110
  const isError = Boolean(ev.isError);
1111
+ if (this.bashToolCallIds.has(toolCallId)) {
1112
+ this.emitBashOutputUpdate({
1113
+ toolCallId,
1114
+ status: isError ? "failed" : "completed",
1115
+ result,
1116
+ isError
1117
+ });
1118
+ this.cleanupToolCall(toolCallId);
1119
+ break;
1120
+ }
997
1121
  const text = toolResultToText(result);
998
1122
  const snapshot = this.fileSnapshots.get(toolCallId);
999
1123
  let content;
@@ -1026,9 +1150,7 @@ var PiAcpSession = class {
1026
1150
  content,
1027
1151
  ...hasStructuredDiff ? {} : { rawOutput: result }
1028
1152
  });
1029
- this.currentToolCalls.delete(toolCallId);
1030
- this.fileSnapshots.delete(toolCallId);
1031
- this.fileMutationToolCallIds.delete(toolCallId);
1153
+ this.cleanupToolCall(toolCallId);
1032
1154
  break;
1033
1155
  }
1034
1156
  case "extension_ui_request": {
@@ -1084,6 +1206,10 @@ var PiAcpSession = class {
1084
1206
  break;
1085
1207
  }
1086
1208
  case "agent_end": {
1209
+ this.inAgentLoop = false;
1210
+ break;
1211
+ }
1212
+ case "agent_settled": {
1087
1213
  void this.flushEmits().finally(() => {
1088
1214
  const reason = this.cancelRequested ? "cancelled" : "end_turn";
1089
1215
  this.pendingTurn?.resolve(reason);
@@ -1137,7 +1263,8 @@ var PiAcpSession = class {
1137
1263
  if (method === "notify") {
1138
1264
  this.emit({
1139
1265
  sessionUpdate: "agent_message_chunk",
1140
- content: { type: "text", text: stringProp(ev, "message") ?? "Pi notification" }
1266
+ content: { type: "text", text: stringProp(ev, "message") ?? "Pi notification" },
1267
+ _meta: { piAcp: { notify: { level: stringProp(ev, "notifyType") ?? "info" } } }
1141
1268
  });
1142
1269
  await this.proc.sendExtensionUiResponse({ id, cancelled: true });
1143
1270
  return;
@@ -1238,7 +1365,7 @@ function toToolKind(toolName) {
1238
1365
  case "edit":
1239
1366
  return "edit";
1240
1367
  case "bash":
1241
- return "other";
1368
+ return "execute";
1242
1369
  default:
1243
1370
  return "other";
1244
1371
  }
@@ -1815,7 +1942,8 @@ var PiAcpAgent = class {
1815
1942
  sessionCapabilities: {
1816
1943
  // **UNSTABLE** ACP capability used by Zed's codex-acp adapter.
1817
1944
  // Enables a native session picker in clients that support it.
1818
- list: {}
1945
+ list: {},
1946
+ delete: {}
1819
1947
  }
1820
1948
  }
1821
1949
  };
@@ -2384,6 +2512,35 @@ ${JSON.stringify(stats, null, 2)}`;
2384
2512
  const toolName = String(m?.toolName ?? "tool");
2385
2513
  const toolCallId = String(m?.toolCallId ?? crypto.randomUUID());
2386
2514
  const isError = Boolean(m?.isError);
2515
+ const isBash = isBashTool(toolName);
2516
+ if (isBash) {
2517
+ const text2 = bashResultText(m);
2518
+ await this.conn.sessionUpdate({
2519
+ sessionId: session.sessionId,
2520
+ update: {
2521
+ sessionUpdate: "tool_call",
2522
+ toolCallId,
2523
+ title: bashCommand(m) ?? toolName,
2524
+ kind: "execute",
2525
+ status: "completed",
2526
+ content: bashTerminalContent(toolCallId),
2527
+ _meta: bashTerminalInfoMeta(toolCallId, params.cwd)
2528
+ }
2529
+ });
2530
+ await this.conn.sessionUpdate({
2531
+ sessionId: session.sessionId,
2532
+ update: {
2533
+ sessionUpdate: "tool_call_update",
2534
+ toolCallId,
2535
+ status: isError ? "failed" : "completed",
2536
+ _meta: {
2537
+ ...text2 ? bashTerminalOutputMeta(toolCallId, text2) : {},
2538
+ ...bashTerminalExitMeta(toolCallId, bashExitCode(m, isError))
2539
+ }
2540
+ }
2541
+ });
2542
+ continue;
2543
+ }
2387
2544
  await this.conn.sessionUpdate({
2388
2545
  sessionId: session.sessionId,
2389
2546
  update: {
@@ -2449,6 +2606,22 @@ ${JSON.stringify(stats, null, 2)}`;
2449
2606
  }, 0);
2450
2607
  return response;
2451
2608
  }
2609
+ async deleteSession(params) {
2610
+ const stored = this.store.get(params.sessionId);
2611
+ const piSession = findPiSession(params.sessionId);
2612
+ if (!stored && !piSession) {
2613
+ return {};
2614
+ }
2615
+ const sessionFile = stored?.sessionFile ?? piSession?.sessionFile;
2616
+ if (sessionFile) {
2617
+ try {
2618
+ if (existsSync4(sessionFile)) unlinkSync(sessionFile);
2619
+ } catch {
2620
+ }
2621
+ }
2622
+ this.store.delete(params.sessionId);
2623
+ return {};
2624
+ }
2452
2625
  async unstable_setSessionModel(params) {
2453
2626
  const session = await this.restoreSession(params.sessionId);
2454
2627
  await setSessionModel(session.proc, params.modelId);
@@ -2764,20 +2937,22 @@ function buildStartupInfo(opts) {
2764
2937
  for (const f of exts) extItems.push(join5(extDir, f));
2765
2938
  } catch {
2766
2939
  }
2767
- try {
2768
- const settingsPath = join5(process.env.HOME ?? "", ".pi", "agent", "settings.json");
2769
- const settings = JSON.parse(readFileSync6(settingsPath, "utf-8"));
2770
- const pkgs = Array.isArray(settings?.packages) ? settings.packages : [];
2771
- for (const pkg2 of pkgs) {
2772
- const s = String(pkg2);
2773
- if (s.startsWith("npm:")) {
2774
- extItems.push(`${s}
2940
+ const settingsPaths = [join5(getAgentDir(), "settings.json"), join5(opts.cwd, ".pi", "settings.json")];
2941
+ for (const settingsPath of settingsPaths) {
2942
+ try {
2943
+ const settings = JSON.parse(readFileSync6(settingsPath, "utf-8"));
2944
+ const pkgs = Array.isArray(settings?.packages) ? settings.packages : [];
2945
+ for (const pkg2 of pkgs) {
2946
+ const s = String(pkg2);
2947
+ if (s.startsWith("npm:")) {
2948
+ extItems.push(`${s}
2775
2949
  - index.ts`);
2776
- } else {
2777
- extItems.push(s);
2950
+ } else {
2951
+ extItems.push(s);
2952
+ }
2778
2953
  }
2954
+ } catch {
2779
2955
  }
2780
- } catch {
2781
2956
  }
2782
2957
  addSection("Extensions", extItems);
2783
2958
  if (opts.updateNotice) {