@oh-my-pi/pi-coding-agent 6.7.670 → 6.8.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.
Files changed (114) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/package.json +6 -7
  3. package/src/cli/session-picker.ts +27 -28
  4. package/src/cli/setup-cli.ts +7 -16
  5. package/src/cli/update-cli.ts +1 -1
  6. package/src/config.ts +1 -1
  7. package/src/core/agent-session.ts +202 -37
  8. package/src/core/agent-storage.ts +1 -1
  9. package/src/core/auth-storage.ts +15 -25
  10. package/src/core/bash-executor.ts +63 -105
  11. package/src/core/custom-commands/loader.ts +1 -1
  12. package/src/core/custom-tools/loader.ts +1 -1
  13. package/src/core/custom-tools/types.ts +1 -2
  14. package/src/core/exec.ts +16 -100
  15. package/src/core/extensions/index.ts +1 -7
  16. package/src/core/extensions/loader.ts +1 -1
  17. package/src/core/extensions/runner.ts +1 -1
  18. package/src/core/extensions/types.ts +2 -2
  19. package/src/core/extensions/wrapper.ts +15 -20
  20. package/src/core/frontmatter.ts +1 -1
  21. package/src/core/history-storage.ts +3 -6
  22. package/src/core/hooks/index.ts +2 -2
  23. package/src/core/hooks/loader.ts +1 -1
  24. package/src/core/hooks/tool-wrapper.ts +14 -26
  25. package/src/core/hooks/types.ts +1 -2
  26. package/src/core/keybindings.ts +1 -1
  27. package/src/core/mcp/client.ts +13 -13
  28. package/src/core/mcp/json-rpc.ts +1 -1
  29. package/src/core/mcp/loader.ts +1 -1
  30. package/src/core/mcp/manager.ts +2 -2
  31. package/src/core/mcp/tool-cache.ts +1 -1
  32. package/src/core/mcp/transports/http.ts +32 -70
  33. package/src/core/model-registry.ts +1 -1
  34. package/src/core/plugins/installer.ts +13 -11
  35. package/src/core/prompt-templates.ts +4 -9
  36. package/src/core/python-executor.ts +23 -18
  37. package/src/core/python-gateway-coordinator.ts +29 -28
  38. package/src/core/python-kernel.ts +230 -211
  39. package/src/core/sdk.ts +10 -13
  40. package/src/core/session-manager.ts +1 -1
  41. package/src/core/settings-manager.ts +22 -9
  42. package/src/core/skills.ts +1 -1
  43. package/src/core/ssh/connection-manager.ts +19 -33
  44. package/src/core/ssh/ssh-executor.ts +39 -35
  45. package/src/core/ssh/sshfs-mount.ts +14 -33
  46. package/src/core/storage-migration.ts +1 -1
  47. package/src/core/streaming-output.ts +183 -127
  48. package/src/core/system-prompt.ts +119 -79
  49. package/src/core/title-generator.ts +1 -1
  50. package/src/core/tools/ask.ts +2 -2
  51. package/src/core/tools/bash.ts +3 -3
  52. package/src/core/tools/calculator.ts +1 -1
  53. package/src/core/tools/exa/mcp-client.ts +1 -1
  54. package/src/core/tools/exa/render.ts +1 -1
  55. package/src/core/tools/find.ts +39 -71
  56. package/src/core/tools/gemini-image.ts +1 -1
  57. package/src/core/tools/grep.ts +88 -100
  58. package/src/core/tools/index.ts +1 -1
  59. package/src/core/tools/ls.ts +1 -1
  60. package/src/core/tools/lsp/client.ts +50 -50
  61. package/src/core/tools/lsp/clients/lsp-linter-client.ts +1 -1
  62. package/src/core/tools/lsp/config.ts +1 -1
  63. package/src/core/tools/lsp/index.ts +2 -4
  64. package/src/core/tools/lsp/lspmux.ts +1 -1
  65. package/src/core/tools/lsp/rust-analyzer.ts +2 -2
  66. package/src/core/tools/lsp/utils.ts +0 -14
  67. package/src/core/tools/notebook.ts +1 -1
  68. package/src/core/tools/patch/shared.ts +3 -4
  69. package/src/core/tools/python.ts +3 -3
  70. package/src/core/tools/read.ts +29 -68
  71. package/src/core/tools/render-utils.ts +0 -5
  72. package/src/core/tools/ssh.ts +3 -3
  73. package/src/core/tools/task/model-resolver.ts +7 -9
  74. package/src/core/tools/task/worker.ts +144 -139
  75. package/src/core/tools/todo-write.ts +1 -1
  76. package/src/core/tools/truncate.ts +2 -2
  77. package/src/core/tools/web-fetch.ts +13 -15
  78. package/src/core/tools/web-scrapers/types.ts +1 -3
  79. package/src/core/tools/web-scrapers/utils.ts +14 -13
  80. package/src/core/tools/web-scrapers/youtube.ts +39 -12
  81. package/src/core/tools/web-search/auth.ts +1 -1
  82. package/src/core/tools/write.ts +1 -1
  83. package/src/core/ttsr.ts +1 -1
  84. package/src/core/utils.ts +1 -187
  85. package/src/core/voice-controller.ts +1 -1
  86. package/src/core/voice-supervisor.ts +11 -38
  87. package/src/core/voice.ts +1 -8
  88. package/src/discovery/codex.ts +1 -1
  89. package/src/index.ts +4 -4
  90. package/src/main.ts +5 -10
  91. package/src/migrations.ts +1 -1
  92. package/src/modes/index.ts +7 -40
  93. package/src/modes/interactive/components/extensions/state-manager.ts +1 -1
  94. package/src/modes/interactive/components/hook-editor.ts +12 -9
  95. package/src/modes/interactive/components/login-dialog.ts +24 -11
  96. package/src/modes/interactive/components/settings-defs.ts +9 -0
  97. package/src/modes/interactive/components/status-line.ts +36 -35
  98. package/src/modes/interactive/components/todo-display.ts +1 -1
  99. package/src/modes/interactive/components/tool-execution.ts +1 -1
  100. package/src/modes/interactive/controllers/command-controller.ts +50 -84
  101. package/src/modes/interactive/controllers/extension-ui-controller.ts +76 -76
  102. package/src/modes/interactive/controllers/input-controller.ts +12 -11
  103. package/src/modes/interactive/interactive-mode.ts +10 -11
  104. package/src/modes/interactive/theme/theme.ts +1 -1
  105. package/src/modes/interactive/types.ts +1 -1
  106. package/src/modes/rpc/rpc-client.ts +91 -121
  107. package/src/modes/rpc/rpc-mode.ts +71 -79
  108. package/src/prompts/system/ttsr-interrupt.md +7 -0
  109. package/src/utils/clipboard.ts +57 -141
  110. package/src/utils/shell-snapshot.ts +12 -60
  111. package/src/utils/shell.ts +35 -56
  112. package/src/utils/tools-manager.ts +42 -71
  113. package/src/core/logger.ts +0 -111
  114. package/src/modes/cleanup.ts +0 -23
@@ -1,15 +1,14 @@
1
1
  import { createServer } from "node:net";
2
2
  import { delimiter, join } from "node:path";
3
- import type { Subprocess } from "bun";
3
+ import { logger } from "@oh-my-pi/pi-utils";
4
+ import { $, type Subprocess } from "bun";
4
5
  import { nanoid } from "nanoid";
5
6
  import { getShellConfig, killProcessTree } from "../utils/shell";
6
7
  import { getOrCreateSnapshot } from "../utils/shell-snapshot";
7
- import { logger } from "./logger";
8
8
  import { acquireSharedGateway, releaseSharedGateway } from "./python-gateway-coordinator";
9
9
  import { loadPythonModules } from "./python-modules";
10
10
  import { PYTHON_PRELUDE } from "./python-prelude";
11
11
  import { htmlToBasicMarkdown } from "./tools/web-scrapers/types";
12
- import { ScopeSignal } from "./utils";
13
12
 
14
13
  const TEXT_ENCODER = new TextEncoder();
15
14
  const TEXT_DECODER = new TextDecoder();
@@ -285,14 +284,9 @@ export async function checkPythonKernelAvailability(cwd: string): Promise<Python
285
284
  const { env } = await getShellConfig();
286
285
  const baseEnv = filterEnv(env);
287
286
  const runtime = await resolvePythonRuntime(cwd, baseEnv);
288
- const result = Bun.spawnSync(
289
- [
290
- runtime.pythonPath,
291
- "-c",
292
- "import importlib.util,sys;sys.exit(0 if importlib.util.find_spec('kernel_gateway') and importlib.util.find_spec('ipykernel') else 1)",
293
- ],
294
- { cwd, env: runtime.env, stdin: "ignore", stdout: "pipe", stderr: "pipe" },
295
- );
287
+ const checkScript =
288
+ "import importlib.util,sys;sys.exit(0 if importlib.util.find_spec('kernel_gateway') and importlib.util.find_spec('ipykernel') else 1)";
289
+ const result = await $`${runtime.pythonPath} -c ${checkScript}`.quiet().nothrow().cwd(cwd).env(runtime.env);
296
290
  if (result.exitCode === 0) {
297
291
  return { ok: true, pythonPath: runtime.pythonPath };
298
292
  }
@@ -354,27 +348,28 @@ async function checkExternalGatewayAvailability(config: ExternalGatewayConfig):
354
348
  }
355
349
 
356
350
  async function allocatePort(): Promise<number> {
357
- return await new Promise((resolve, reject) => {
358
- const server = createServer();
359
- server.unref();
360
- server.on("error", reject);
361
- server.listen(0, "127.0.0.1", () => {
362
- const address = server.address();
363
- if (address && typeof address === "object") {
364
- const port = address.port;
365
- server.close((err: Error | null | undefined) => {
366
- if (err) {
367
- reject(err);
368
- } else {
369
- resolve(port);
370
- }
371
- });
372
- } else {
373
- server.close();
374
- reject(new Error("Failed to allocate port"));
375
- }
376
- });
351
+ const { promise, resolve, reject } = Promise.withResolvers<number>();
352
+ const server = createServer();
353
+ server.unref();
354
+ server.on("error", reject);
355
+ server.listen(0, "127.0.0.1", () => {
356
+ const address = server.address();
357
+ if (address && typeof address === "object") {
358
+ const port = address.port;
359
+ server.close((err: Error | null | undefined) => {
360
+ if (err) {
361
+ reject(err);
362
+ } else {
363
+ resolve(port);
364
+ }
365
+ });
366
+ } else {
367
+ server.close();
368
+ reject(new Error("Failed to allocate port"));
369
+ }
377
370
  });
371
+
372
+ return promise;
378
373
  }
379
374
 
380
375
  function normalizeDisplayText(text: string): string {
@@ -681,7 +676,7 @@ export class PythonKernel {
681
676
 
682
677
  if (gatewayProcess && gatewayUrl) break;
683
678
 
684
- killProcessTree(candidateProcess.pid);
679
+ await killProcessTree(candidateProcess.pid);
685
680
  lastError = exited ? "Kernel gateway process exited during startup" : "Kernel gateway failed to start";
686
681
  }
687
682
 
@@ -696,7 +691,7 @@ export class PythonKernel {
696
691
  });
697
692
 
698
693
  if (!createResponse.ok) {
699
- killProcessTree(gatewayProcess.pid);
694
+ await killProcessTree(gatewayProcess.pid);
700
695
  throw new Error(`Failed to create kernel: ${await createResponse.text()}`);
701
696
  }
702
697
 
@@ -727,83 +722,84 @@ export class PythonKernel {
727
722
  wsUrl += `?token=${encodeURIComponent(this.#authToken)}`;
728
723
  }
729
724
 
730
- return new Promise((resolve, reject) => {
731
- const ws = new WebSocket(wsUrl);
732
- ws.binaryType = "arraybuffer";
733
- let settled = false;
725
+ const { promise, resolve, reject } = Promise.withResolvers<void>();
726
+ const ws = new WebSocket(wsUrl);
727
+ ws.binaryType = "arraybuffer";
728
+ let settled = false;
734
729
 
735
- const timeout = setTimeout(() => {
736
- ws.close();
737
- if (!settled) {
738
- settled = true;
739
- reject(new Error("WebSocket connection timeout"));
740
- }
741
- }, 10000);
730
+ const timeout = setTimeout(() => {
731
+ ws.close();
732
+ if (!settled) {
733
+ settled = true;
734
+ reject(new Error("WebSocket connection timeout"));
735
+ }
736
+ }, 10000);
742
737
 
743
- ws.onopen = () => {
744
- if (settled) return;
738
+ ws.onopen = () => {
739
+ if (settled) return;
740
+ settled = true;
741
+ clearTimeout(timeout);
742
+ this.#ws = ws;
743
+ resolve();
744
+ };
745
+
746
+ ws.onerror = (event) => {
747
+ const error = new Error(`WebSocket error: ${event}`);
748
+ if (!settled) {
745
749
  settled = true;
746
750
  clearTimeout(timeout);
747
- this.#ws = ws;
748
- resolve();
749
- };
751
+ reject(error);
752
+ return;
753
+ }
754
+ this.#alive = false;
755
+ this.#ws = null;
756
+ this.abortPendingExecutions(error.message);
757
+ };
750
758
 
751
- ws.onerror = (event) => {
752
- const error = new Error(`WebSocket error: ${event}`);
753
- if (!settled) {
754
- settled = true;
755
- clearTimeout(timeout);
756
- reject(error);
757
- return;
758
- }
759
- this.#alive = false;
760
- this.#ws = null;
761
- this.abortPendingExecutions(error.message);
762
- };
759
+ ws.onclose = () => {
760
+ this.#alive = false;
761
+ this.#ws = null;
762
+ if (!settled) {
763
+ settled = true;
764
+ clearTimeout(timeout);
765
+ reject(new Error("WebSocket closed before connection"));
766
+ return;
767
+ }
768
+ this.abortPendingExecutions("WebSocket closed");
769
+ };
763
770
 
764
- ws.onclose = () => {
765
- this.#alive = false;
766
- this.#ws = null;
767
- if (!settled) {
768
- settled = true;
769
- clearTimeout(timeout);
770
- reject(new Error("WebSocket closed before connection"));
771
+ ws.onmessage = (event) => {
772
+ let msg: JupyterMessage | null = null;
773
+ if (event.data instanceof ArrayBuffer) {
774
+ msg = deserializeWebSocketMessage(event.data);
775
+ } else if (typeof event.data === "string") {
776
+ try {
777
+ msg = JSON.parse(event.data) as JupyterMessage;
778
+ } catch {
771
779
  return;
772
780
  }
773
- this.abortPendingExecutions("WebSocket closed");
774
- };
781
+ }
782
+ if (!msg) return;
775
783
 
776
- ws.onmessage = (event) => {
777
- let msg: JupyterMessage | null = null;
778
- if (event.data instanceof ArrayBuffer) {
779
- msg = deserializeWebSocketMessage(event.data);
780
- } else if (typeof event.data === "string") {
781
- try {
782
- msg = JSON.parse(event.data) as JupyterMessage;
783
- } catch {
784
- return;
785
- }
786
- }
787
- if (!msg) return;
784
+ if (TRACE_IPC) {
785
+ logger.debug("Kernel IPC recv", { channel: msg.channel, msgType: msg.header.msg_type });
786
+ }
788
787
 
789
- if (TRACE_IPC) {
790
- logger.debug("Kernel IPC recv", { channel: msg.channel, msgType: msg.header.msg_type });
791
- }
788
+ const parentId = (msg.parent_header as { msg_id?: string }).msg_id;
789
+ if (parentId) {
790
+ const handler = this.#messageHandlers.get(parentId);
791
+ if (handler) handler(msg);
792
+ }
792
793
 
793
- const parentId = (msg.parent_header as { msg_id?: string }).msg_id;
794
- if (parentId) {
795
- const handler = this.#messageHandlers.get(parentId);
796
- if (handler) handler(msg);
794
+ const channelHandlers = this.#channelHandlers.get(msg.channel);
795
+ if (channelHandlers) {
796
+ for (const handler of channelHandlers) {
797
+ handler(msg);
797
798
  }
799
+ }
800
+ };
798
801
 
799
- const channelHandlers = this.#channelHandlers.get(msg.channel);
800
- if (channelHandlers) {
801
- for (const handler of channelHandlers) {
802
- handler(msg);
803
- }
804
- }
805
- };
806
- });
802
+ return promise;
807
803
  }
808
804
 
809
805
  private abortPendingExecutions(reason: string): void {
@@ -857,140 +853,163 @@ export class PythonKernel {
857
853
  let cancelled = false;
858
854
  let timedOut = false;
859
855
 
860
- const executionSignal = new ScopeSignal({ signal: options?.signal, timeout: options?.timeoutMs });
861
-
862
- return new Promise((resolve) => {
863
- let resolved = false;
864
- const finalize = () => {
865
- if (resolved) return;
866
- resolved = true;
867
- this.#messageHandlers.delete(msgId);
868
- this.#pendingExecutions.delete(msgId);
869
- executionSignal[Symbol.dispose]();
870
- resolve({ status, executionCount, error, cancelled, timedOut, stdinRequested });
871
- };
872
-
873
- const checkDone = () => {
874
- if (replyReceived && idleReceived) {
875
- finalize();
876
- }
877
- };
856
+ const controller = new AbortController();
857
+ const onAbort = () => {
858
+ controller.abort(options?.signal?.reason ?? new Error("Aborted"));
859
+ };
860
+ if (options?.signal) {
861
+ if (options.signal.aborted) {
862
+ onAbort();
863
+ } else {
864
+ options.signal.addEventListener("abort", onAbort, { once: true });
865
+ }
866
+ }
867
+ const timeoutId =
868
+ typeof options?.timeoutMs === "number" && options.timeoutMs > 0
869
+ ? setTimeout(() => {
870
+ timedOut = true;
871
+ controller.abort(new Error("Timeout"));
872
+ }, options.timeoutMs)
873
+ : undefined;
874
+
875
+ const { promise, resolve } = Promise.withResolvers<KernelExecuteResult>();
876
+
877
+ let resolved = false;
878
+ const finalize = () => {
879
+ if (resolved) return;
880
+ resolved = true;
881
+ this.#messageHandlers.delete(msgId);
882
+ this.#pendingExecutions.delete(msgId);
883
+ if (timeoutId) clearTimeout(timeoutId);
884
+ if (options?.signal) {
885
+ options.signal.removeEventListener("abort", onAbort);
886
+ }
887
+ resolve({ status, executionCount, error, cancelled, timedOut, stdinRequested });
888
+ };
878
889
 
879
- const cancelFromClose = (reason: string) => {
880
- if (resolved) return;
881
- cancelled = true;
882
- timedOut = false;
883
- if (options?.onChunk) {
884
- void options.onChunk(`[kernel] ${reason}\n`);
885
- }
890
+ const checkDone = () => {
891
+ if (replyReceived && idleReceived) {
886
892
  finalize();
887
- };
893
+ }
894
+ };
888
895
 
889
- this.#pendingExecutions.set(msgId, cancelFromClose);
896
+ const cancelFromClose = (reason: string) => {
897
+ if (resolved) return;
898
+ cancelled = true;
899
+ timedOut = false;
900
+ if (options?.onChunk) {
901
+ void options.onChunk(`[kernel] ${reason}\n`);
902
+ }
903
+ finalize();
904
+ };
905
+
906
+ this.#pendingExecutions.set(msgId, cancelFromClose);
890
907
 
891
- executionSignal.catch(async () => {
892
- cancelled = true;
893
- timedOut = executionSignal.timedOut();
908
+ const onExecutionAbort = () => {
909
+ cancelled = true;
910
+ void (async () => {
894
911
  try {
895
912
  await this.interrupt();
896
913
  } finally {
897
914
  finalize();
898
915
  }
899
- });
916
+ })();
917
+ };
918
+ controller.signal.addEventListener("abort", onExecutionAbort, { once: true });
900
919
 
901
- if (executionSignal.aborted) {
902
- cancelFromClose("Execution aborted");
903
- return;
904
- }
920
+ if (controller.signal.aborted) {
921
+ cancelFromClose("Execution aborted");
922
+ return promise;
923
+ }
905
924
 
906
- this.#messageHandlers.set(msgId, async (response) => {
907
- switch (response.header.msg_type) {
908
- case "execute_reply": {
909
- replyReceived = true;
910
- const replyStatus = response.content.status;
911
- status = replyStatus === "error" ? "error" : "ok";
912
- if (typeof response.content.execution_count === "number") {
913
- executionCount = response.content.execution_count;
914
- }
915
- checkDone();
916
- break;
925
+ this.#messageHandlers.set(msgId, async (response) => {
926
+ switch (response.header.msg_type) {
927
+ case "execute_reply": {
928
+ replyReceived = true;
929
+ const replyStatus = response.content.status;
930
+ status = replyStatus === "error" ? "error" : "ok";
931
+ if (typeof response.content.execution_count === "number") {
932
+ executionCount = response.content.execution_count;
917
933
  }
918
- case "stream": {
919
- const text = String(response.content.text ?? "");
920
- if (text && options?.onChunk) {
921
- await options.onChunk(text);
922
- }
923
- break;
934
+ checkDone();
935
+ break;
936
+ }
937
+ case "stream": {
938
+ const text = String(response.content.text ?? "");
939
+ if (text && options?.onChunk) {
940
+ await options.onChunk(text);
924
941
  }
925
- case "execute_result":
926
- case "display_data": {
927
- const { text, outputs } = this.renderDisplay(response.content);
928
- if (text && options?.onChunk) {
929
- await options.onChunk(text);
930
- }
931
- if (outputs.length > 0 && options?.onDisplay) {
932
- for (const output of outputs) {
933
- await options.onDisplay(output);
934
- }
935
- }
936
- break;
942
+ break;
943
+ }
944
+ case "execute_result":
945
+ case "display_data": {
946
+ const { text, outputs } = this.renderDisplay(response.content);
947
+ if (text && options?.onChunk) {
948
+ await options.onChunk(text);
937
949
  }
938
- case "error": {
939
- const traceback = Array.isArray(response.content.traceback)
940
- ? response.content.traceback.map((line: unknown) => String(line))
941
- : [];
942
- error = {
943
- name: String(response.content.ename ?? "Error"),
944
- value: String(response.content.evalue ?? ""),
945
- traceback,
946
- };
947
- const text = traceback.length > 0 ? `${traceback.join("\n")}\n` : `${error.name}: ${error.value}\n`;
948
- if (options?.onChunk) {
949
- await options.onChunk(text);
950
+ if (outputs.length > 0 && options?.onDisplay) {
951
+ for (const output of outputs) {
952
+ await options.onDisplay(output);
950
953
  }
951
- break;
952
954
  }
953
- case "status": {
954
- const state = response.content.execution_state;
955
- if (state === "idle") {
956
- idleReceived = true;
957
- checkDone();
958
- }
959
- break;
955
+ break;
956
+ }
957
+ case "error": {
958
+ const traceback = Array.isArray(response.content.traceback)
959
+ ? response.content.traceback.map((line: unknown) => String(line))
960
+ : [];
961
+ error = {
962
+ name: String(response.content.ename ?? "Error"),
963
+ value: String(response.content.evalue ?? ""),
964
+ traceback,
965
+ };
966
+ const text = traceback.length > 0 ? `${traceback.join("\n")}\n` : `${error.name}: ${error.value}\n`;
967
+ if (options?.onChunk) {
968
+ await options.onChunk(text);
960
969
  }
961
- case "input_request": {
962
- stdinRequested = true;
963
- if (options?.onChunk) {
964
- await options.onChunk(
965
- "[stdin] Kernel requested input. Interactive stdin is not supported; provide input programmatically.\n",
966
- );
967
- }
968
- this.sendMessage({
969
- channel: "stdin",
970
- header: {
971
- msg_id: nanoid(),
972
- session: this.sessionId,
973
- username: this.username,
974
- date: new Date().toISOString(),
975
- msg_type: "input_reply",
976
- version: "5.5",
977
- },
978
- parent_header: response.header as unknown as Record<string, unknown>,
979
- metadata: {},
980
- content: { value: "" },
981
- });
982
- break;
970
+ break;
971
+ }
972
+ case "status": {
973
+ const state = response.content.execution_state;
974
+ if (state === "idle") {
975
+ idleReceived = true;
976
+ checkDone();
983
977
  }
978
+ break;
979
+ }
980
+ case "input_request": {
981
+ stdinRequested = true;
982
+ if (options?.onChunk) {
983
+ await options.onChunk(
984
+ "[stdin] Kernel requested input. Interactive stdin is not supported; provide input programmatically.\n",
985
+ );
986
+ }
987
+ this.sendMessage({
988
+ channel: "stdin",
989
+ header: {
990
+ msg_id: nanoid(),
991
+ session: this.sessionId,
992
+ username: this.username,
993
+ date: new Date().toISOString(),
994
+ msg_type: "input_reply",
995
+ version: "5.5",
996
+ },
997
+ parent_header: response.header as unknown as Record<string, unknown>,
998
+ metadata: {},
999
+ content: { value: "" },
1000
+ });
1001
+ break;
984
1002
  }
985
- });
986
-
987
- try {
988
- this.sendMessage(msg);
989
- } catch {
990
- cancelled = true;
991
- finalize();
992
1003
  }
993
1004
  });
1005
+
1006
+ try {
1007
+ this.sendMessage(msg);
1008
+ } catch {
1009
+ cancelled = true;
1010
+ finalize();
1011
+ }
1012
+ return promise;
994
1013
  }
995
1014
 
996
1015
  async introspectPrelude(): Promise<PreludeHelper[]> {
@@ -1079,7 +1098,7 @@ export class PythonKernel {
1079
1098
  await releaseSharedGateway();
1080
1099
  } else if (this.gatewayProcess) {
1081
1100
  try {
1082
- killProcessTree(this.gatewayProcess.pid);
1101
+ await killProcessTree(this.gatewayProcess.pid);
1083
1102
  } catch (err: unknown) {
1084
1103
  logger.warn("Failed to terminate gateway process", {
1085
1104
  error: err instanceof Error ? err.message : String(err),
package/src/core/sdk.ts CHANGED
@@ -30,14 +30,14 @@ import { join } from "node:path";
30
30
  import { Agent, type AgentEvent, type AgentMessage, type AgentTool, type ThinkingLevel } from "@oh-my-pi/pi-agent-core";
31
31
  import { type Message, type Model, supportsXhigh } from "@oh-my-pi/pi-ai";
32
32
  import type { Component } from "@oh-my-pi/pi-tui";
33
- import chalk from "chalk";
34
33
  // Import discovery to register all providers on startup
35
- import "../discovery";
34
+ import { logger, postmortem } from "@oh-my-pi/pi-utils";
35
+ import chalk from "chalk";
36
36
  import { loadCapability } from "../capability/index";
37
37
  import { type Rule, ruleCapability } from "../capability/rule";
38
38
  import { getAgentDir, getConfigDirPaths } from "../config";
39
+ import "../discovery";
39
40
  import { initializeWithSettings } from "../discovery";
40
- import { registerAsyncCleanup } from "../modes/cleanup";
41
41
  import { AgentSession } from "./agent-session";
42
42
  import { AuthStorage } from "./auth-storage";
43
43
  import { CursorExecHandlers } from "./cursor/exec-bridge";
@@ -52,15 +52,14 @@ import {
52
52
  type ExtensionContext,
53
53
  type ExtensionFactory,
54
54
  ExtensionRunner,
55
+ ExtensionToolWrapper,
55
56
  type ExtensionUIContext,
56
57
  type LoadExtensionsResult,
57
58
  loadExtensionFromFactory,
58
59
  loadExtensions,
59
60
  type ToolDefinition,
60
61
  wrapRegisteredTools,
61
- wrapToolWithExtensions,
62
62
  } from "./extensions/index";
63
- import { logger } from "./logger";
64
63
  import { discoverAndLoadMCPTools, type MCPManager, type MCPToolsLoadResult } from "./mcp/index";
65
64
  import { convertToLlm } from "./messages";
66
65
  import { ModelRegistry } from "./model-registry";
@@ -212,12 +211,11 @@ export type { FileSlashCommand } from "./slash-commands";
212
211
  export type { Tool } from "./tools/index";
213
212
 
214
213
  export {
214
+ // Individual tool classes (for custom usage)
215
+ BashTool,
215
216
  // Tool classes and factories
216
217
  BUILTIN_TOOLS,
217
218
  createTools,
218
- type ToolSession,
219
- // Individual tool classes (for custom usage)
220
- BashTool,
221
219
  EditTool,
222
220
  FindTool,
223
221
  GitTool,
@@ -227,6 +225,7 @@ export {
227
225
  PythonTool,
228
226
  ReadTool,
229
227
  WriteTool,
228
+ type ToolSession,
230
229
  };
231
230
 
232
231
  // Helper Functions
@@ -441,7 +440,7 @@ async function cleanupSshResources(): Promise<void> {
441
440
  function registerSshCleanup(): void {
442
441
  if (sshCleanupRegistered) return;
443
442
  sshCleanupRegistered = true;
444
- registerAsyncCleanup(() => cleanupSshResources());
443
+ postmortem.register("ssh-cleanup", cleanupSshResources);
445
444
  }
446
445
 
447
446
  let pythonCleanupRegistered = false;
@@ -449,9 +448,7 @@ let pythonCleanupRegistered = false;
449
448
  function registerPythonCleanup(): void {
450
449
  if (pythonCleanupRegistered) return;
451
450
  pythonCleanupRegistered = true;
452
- registerAsyncCleanup(async () => {
453
- await disposeAllKernelSessions();
454
- });
451
+ postmortem.register("python-cleanup", disposeAllKernelSessions);
455
452
  }
456
453
 
457
454
  function customToolToDefinition(tool: CustomTool): ToolDefinition {
@@ -872,7 +869,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
872
869
  }
873
870
  if (extensionRunner) {
874
871
  for (const tool of toolRegistry.values()) {
875
- toolRegistry.set(tool.name, wrapToolWithExtensions(tool, extensionRunner));
872
+ toolRegistry.set(tool.name, new ExtensionToolWrapper(tool, extensionRunner));
876
873
  }
877
874
  }
878
875
  if (model?.provider === "cursor") {
@@ -1,10 +1,10 @@
1
1
  import { basename, join, resolve } from "node:path";
2
2
  import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
3
3
  import type { ImageContent, Message, TextContent, Usage } from "@oh-my-pi/pi-ai";
4
+ import { logger } from "@oh-my-pi/pi-utils";
4
5
  import { nanoid } from "nanoid";
5
6
  import { getAgentDir as getDefaultAgentDir } from "../config";
6
7
  import { resizeImage } from "../utils/image-resize";
7
- import { logger } from "./logger";
8
8
  import {
9
9
  type BashExecutionMessage,
10
10
  type CustomMessage,