@oh-my-pi/pi-coding-agent 15.10.11 → 15.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.
Files changed (217) hide show
  1. package/CHANGELOG.md +103 -2
  2. package/dist/cli.js +5790 -5731
  3. package/dist/types/async/index.d.ts +0 -1
  4. package/dist/types/cli/args.d.ts +1 -0
  5. package/dist/types/cli/gallery-fixtures/types.d.ts +5 -0
  6. package/dist/types/cli-commands.d.ts +12 -0
  7. package/dist/types/commands/launch.d.ts +4 -0
  8. package/dist/types/config/api-key-resolver.d.ts +3 -0
  9. package/dist/types/config/keybindings.d.ts +6 -1
  10. package/dist/types/config/model-registry.d.ts +1 -0
  11. package/dist/types/config/model-resolver.d.ts +18 -0
  12. package/dist/types/config/settings-schema.d.ts +85 -34
  13. package/dist/types/config/settings.d.ts +7 -0
  14. package/dist/types/edit/hashline/noop-loop-guard.d.ts +72 -0
  15. package/dist/types/eval/py/executor.d.ts +5 -0
  16. package/dist/types/eval/py/kernel.d.ts +6 -1
  17. package/dist/types/eval/py/runtime.d.ts +9 -0
  18. package/dist/types/exec/bash-executor.d.ts +2 -0
  19. package/dist/types/export/html/template.generated.d.ts +1 -1
  20. package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
  21. package/dist/types/extensibility/extensions/runner.d.ts +3 -2
  22. package/dist/types/extensibility/extensions/types.d.ts +3 -0
  23. package/dist/types/extensibility/shared-events.d.ts +2 -2
  24. package/dist/types/internal-urls/history-protocol.d.ts +14 -0
  25. package/dist/types/internal-urls/index.d.ts +1 -0
  26. package/dist/types/internal-urls/types.d.ts +1 -1
  27. package/dist/types/irc/bus.d.ts +66 -0
  28. package/dist/types/memory-backend/index.d.ts +1 -0
  29. package/dist/types/memory-backend/runtime.d.ts +4 -0
  30. package/dist/types/memory-backend/types.d.ts +66 -1
  31. package/dist/types/modes/components/agent-hub.d.ts +30 -0
  32. package/dist/types/modes/components/compaction-summary-message.d.ts +10 -4
  33. package/dist/types/modes/components/custom-editor.d.ts +2 -0
  34. package/dist/types/modes/components/tool-execution.d.ts +8 -0
  35. package/dist/types/modes/components/ttsr-notification.d.ts +5 -1
  36. package/dist/types/modes/components/welcome.d.ts +3 -9
  37. package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
  38. package/dist/types/modes/index.d.ts +3 -3
  39. package/dist/types/modes/interactive-mode.d.ts +10 -4
  40. package/dist/types/modes/oauth-manual-input.d.ts +7 -0
  41. package/dist/types/modes/rpc/rpc-client.d.ts +39 -2
  42. package/dist/types/modes/rpc/rpc-mode.d.ts +31 -2
  43. package/dist/types/modes/rpc/rpc-subagents.d.ts +24 -0
  44. package/dist/types/modes/rpc/rpc-types.d.ts +75 -1
  45. package/dist/types/modes/setup-wizard/index.d.ts +5 -1
  46. package/dist/types/modes/setup-wizard/lazy.d.ts +2 -0
  47. package/dist/types/modes/theme/theme.d.ts +2 -1
  48. package/dist/types/modes/types.d.ts +5 -2
  49. package/dist/types/modes/utils/ui-helpers.d.ts +1 -1
  50. package/dist/types/registry/agent-lifecycle.d.ts +51 -0
  51. package/dist/types/registry/agent-registry.d.ts +16 -5
  52. package/dist/types/secrets/index.d.ts +1 -1
  53. package/dist/types/secrets/obfuscator.d.ts +8 -2
  54. package/dist/types/session/agent-session.d.ts +49 -32
  55. package/dist/types/session/messages.d.ts +2 -4
  56. package/dist/types/session/session-history-format.d.ts +12 -0
  57. package/dist/types/session/session-manager.d.ts +21 -3
  58. package/dist/types/session/streaming-output.d.ts +46 -0
  59. package/dist/types/slash-commands/acp-builtins.d.ts +16 -0
  60. package/dist/types/slash-commands/builtin-registry.d.ts +1 -0
  61. package/dist/types/slash-commands/types.d.ts +1 -1
  62. package/dist/types/system-prompt.d.ts +2 -0
  63. package/dist/types/task/executor.d.ts +12 -2
  64. package/dist/types/task/index.d.ts +13 -6
  65. package/dist/types/task/output-manager.d.ts +0 -7
  66. package/dist/types/task/repair-args.d.ts +8 -7
  67. package/dist/types/task/types.d.ts +63 -51
  68. package/dist/types/thinking.d.ts +4 -0
  69. package/dist/types/tiny/title-client.d.ts +11 -0
  70. package/dist/types/tiny/title-protocol.d.ts +1 -0
  71. package/dist/types/tools/browser/tab-worker.d.ts +3 -1
  72. package/dist/types/tools/find.d.ts +0 -11
  73. package/dist/types/tools/grouped-file-output.d.ts +0 -49
  74. package/dist/types/tools/index.d.ts +7 -3
  75. package/dist/types/tools/irc.d.ts +76 -38
  76. package/dist/types/tools/job.d.ts +7 -1
  77. package/dist/types/utils/git.d.ts +15 -2
  78. package/dist/types/utils/title-generator.d.ts +3 -2
  79. package/examples/extensions/with-deps/package.json +1 -0
  80. package/package.json +11 -10
  81. package/scripts/bundle-dist.ts +28 -19
  82. package/src/async/index.ts +0 -1
  83. package/src/auto-thinking/classifier.ts +1 -0
  84. package/src/cli/args.ts +3 -0
  85. package/src/cli/gallery-cli.ts +1 -1
  86. package/src/cli/gallery-fixtures/agentic.ts +230 -115
  87. package/src/cli/gallery-fixtures/types.ts +5 -0
  88. package/src/cli-commands.ts +29 -0
  89. package/src/cli.ts +28 -15
  90. package/src/commands/launch.ts +4 -0
  91. package/src/commit/agentic/tools/analyze-file.ts +38 -19
  92. package/src/commit/model-selection.ts +3 -2
  93. package/src/config/api-key-resolver.ts +8 -6
  94. package/src/config/keybindings.ts +6 -1
  95. package/src/config/model-registry.ts +97 -30
  96. package/src/config/model-resolver.ts +60 -0
  97. package/src/config/settings-schema.ts +99 -55
  98. package/src/config/settings.ts +68 -3
  99. package/src/edit/hashline/execute.ts +39 -2
  100. package/src/edit/hashline/noop-loop-guard.ts +99 -0
  101. package/src/eval/__tests__/agent-bridge.test.ts +5 -3
  102. package/src/eval/agent-bridge.ts +3 -16
  103. package/src/eval/completion-bridge.ts +1 -0
  104. package/src/eval/js/shared/prelude.txt +1 -1
  105. package/src/eval/py/executor.ts +29 -7
  106. package/src/eval/py/index.ts +6 -1
  107. package/src/eval/py/kernel.ts +31 -11
  108. package/src/eval/py/prelude.py +5 -6
  109. package/src/eval/py/runtime.ts +37 -0
  110. package/src/exec/bash-executor.ts +82 -3
  111. package/src/export/html/template.generated.ts +1 -1
  112. package/src/export/html/template.js +38 -13
  113. package/src/extensibility/custom-tools/types.ts +2 -2
  114. package/src/extensibility/extensions/get-commands-handler.ts +2 -1
  115. package/src/extensibility/extensions/runner.ts +6 -1
  116. package/src/extensibility/extensions/types.ts +3 -0
  117. package/src/extensibility/shared-events.ts +2 -2
  118. package/src/hindsight/bank.ts +17 -2
  119. package/src/internal-urls/docs-index.generated.ts +11 -11
  120. package/src/internal-urls/history-protocol.ts +113 -0
  121. package/src/internal-urls/index.ts +1 -0
  122. package/src/internal-urls/router.ts +3 -1
  123. package/src/internal-urls/types.ts +1 -1
  124. package/src/irc/bus.ts +292 -0
  125. package/src/main.ts +26 -66
  126. package/src/memories/index.ts +2 -0
  127. package/src/memory-backend/index.ts +1 -0
  128. package/src/memory-backend/local-backend.ts +9 -0
  129. package/src/memory-backend/off-backend.ts +9 -0
  130. package/src/memory-backend/runtime.ts +66 -0
  131. package/src/memory-backend/types.ts +81 -1
  132. package/src/mnemopi/backend.ts +151 -4
  133. package/src/modes/acp/acp-agent.ts +119 -11
  134. package/src/modes/components/{session-observer-overlay.ts → agent-hub.ts} +586 -367
  135. package/src/modes/components/assistant-message.ts +19 -21
  136. package/src/modes/components/compaction-summary-message.ts +68 -32
  137. package/src/modes/components/custom-editor.ts +10 -0
  138. package/src/modes/components/footer.ts +3 -1
  139. package/src/modes/components/status-line/component.ts +118 -34
  140. package/src/modes/components/tool-execution.ts +31 -1
  141. package/src/modes/components/ttsr-notification.ts +72 -30
  142. package/src/modes/components/welcome.ts +9 -33
  143. package/src/modes/controllers/command-controller.ts +1 -1
  144. package/src/modes/controllers/event-controller.ts +65 -0
  145. package/src/modes/controllers/extension-ui-controller.ts +8 -8
  146. package/src/modes/controllers/input-controller.ts +19 -2
  147. package/src/modes/controllers/mcp-command-controller.ts +38 -3
  148. package/src/modes/controllers/selector-controller.ts +21 -17
  149. package/src/modes/index.ts +3 -21
  150. package/src/modes/interactive-mode.ts +47 -22
  151. package/src/modes/oauth-manual-input.ts +30 -3
  152. package/src/modes/rpc/rpc-client.ts +154 -3
  153. package/src/modes/rpc/rpc-mode.ts +97 -12
  154. package/src/modes/rpc/rpc-subagents.ts +265 -0
  155. package/src/modes/rpc/rpc-types.ts +81 -1
  156. package/src/modes/setup-wizard/index.ts +12 -2
  157. package/src/modes/setup-wizard/lazy.ts +16 -0
  158. package/src/modes/theme/theme.ts +18 -5
  159. package/src/modes/types.ts +5 -5
  160. package/src/modes/utils/hotkeys-markdown.ts +1 -0
  161. package/src/modes/utils/ui-helpers.ts +51 -49
  162. package/src/prompts/system/irc-incoming.md +3 -4
  163. package/src/prompts/system/orchestrate-notice.md +2 -2
  164. package/src/prompts/system/subagent-system-prompt.md +0 -5
  165. package/src/prompts/system/system-prompt.md +1 -0
  166. package/src/prompts/system/workflow-notice.md +2 -2
  167. package/src/prompts/tools/eval.md +3 -3
  168. package/src/prompts/tools/irc.md +29 -19
  169. package/src/prompts/tools/read.md +2 -2
  170. package/src/prompts/tools/task-summary.md +5 -16
  171. package/src/prompts/tools/task.md +38 -29
  172. package/src/registry/agent-lifecycle.ts +218 -0
  173. package/src/registry/agent-registry.ts +16 -5
  174. package/src/sdk.ts +37 -10
  175. package/src/secrets/index.ts +8 -1
  176. package/src/secrets/obfuscator.ts +39 -18
  177. package/src/session/agent-session.ts +422 -291
  178. package/src/session/messages.ts +11 -78
  179. package/src/session/session-history-format.ts +246 -0
  180. package/src/session/session-manager.ts +59 -5
  181. package/src/session/streaming-output.ts +226 -10
  182. package/src/slash-commands/acp-builtins.ts +24 -0
  183. package/src/slash-commands/builtin-registry.ts +20 -0
  184. package/src/slash-commands/types.ts +1 -1
  185. package/src/system-prompt.ts +14 -0
  186. package/src/task/executor.ts +851 -461
  187. package/src/task/index.ts +721 -796
  188. package/src/task/output-manager.ts +0 -11
  189. package/src/task/render.ts +148 -63
  190. package/src/task/repair-args.ts +21 -9
  191. package/src/task/types.ts +82 -66
  192. package/src/thinking.ts +7 -0
  193. package/src/tiny/title-client.ts +34 -5
  194. package/src/tiny/title-protocol.ts +1 -1
  195. package/src/tiny/worker.ts +6 -4
  196. package/src/tools/ask.ts +4 -2
  197. package/src/tools/bash.ts +61 -10
  198. package/src/tools/browser/tab-worker.ts +26 -7
  199. package/src/tools/browser.ts +28 -1
  200. package/src/tools/find.ts +2 -27
  201. package/src/tools/grouped-file-output.ts +1 -118
  202. package/src/tools/image-gen.ts +11 -4
  203. package/src/tools/index.ts +17 -13
  204. package/src/tools/inspect-image.ts +1 -0
  205. package/src/tools/irc.ts +596 -171
  206. package/src/tools/job.ts +41 -7
  207. package/src/tools/read.ts +57 -1
  208. package/src/tools/renderers.ts +2 -0
  209. package/src/tools/resolve.ts +4 -1
  210. package/src/utils/commit-message-generator.ts +1 -0
  211. package/src/utils/git.ts +267 -13
  212. package/src/utils/title-generator.ts +24 -5
  213. package/dist/types/async/support.d.ts +0 -2
  214. package/dist/types/modes/components/session-observer-overlay.d.ts +0 -11
  215. package/dist/types/task/simple-mode.d.ts +0 -8
  216. package/src/async/support.ts +0 -5
  217. package/src/task/simple-mode.ts +0 -27
@@ -17,7 +17,13 @@ import { Settings } from "../../config/settings";
17
17
  import { type KernelDisplayOutput, renderKernelDisplay } from "./display";
18
18
  import { PYTHON_PRELUDE } from "./prelude";
19
19
  import RUNNER_SCRIPT from "./runner.py" with { type: "text" };
20
- import { enumeratePythonRuntimes, filterEnv, type PythonRuntime, resolvePythonRuntime } from "./runtime";
20
+ import {
21
+ enumeratePythonRuntimes,
22
+ filterEnv,
23
+ type PythonRuntime,
24
+ resolveExplicitPythonRuntime,
25
+ resolvePythonRuntime,
26
+ } from "./runtime";
21
27
  import { hostHasInheritableConsole, shouldHideKernelWindow } from "./spawn-options";
22
28
 
23
29
  export type { KernelDisplayOutput, PythonStatusEvent } from "./display";
@@ -96,6 +102,11 @@ interface KernelLifecycleOptions {
96
102
  interface KernelStartOptions extends KernelLifecycleOptions {
97
103
  cwd: string;
98
104
  env?: Record<string, string | undefined>;
105
+ /**
106
+ * Explicit interpreter path (`python.interpreter` from the session's
107
+ * settings). When set, runtime discovery is skipped entirely.
108
+ */
109
+ interpreter?: string;
99
110
  }
100
111
 
101
112
  interface KernelShutdownOptions {
@@ -129,20 +140,24 @@ function throwIfAborted(signal: AbortSignal | undefined, fallbackReason: string)
129
140
  throw createAbortError("AbortError", typeof reason === "string" ? reason : fallbackReason);
130
141
  }
131
142
 
132
- // Cache successful probes per resolved cwd: every cell otherwise pays one (or
133
- // two — backend.isAvailable + ensureKernelAvailable) interpreter spawns even
134
- // when the kernel is already hot. Failures are not cached so installing a
135
- // Python mid-session is picked up on the next attempt.
143
+ // Cache successful probes per resolved cwd + explicit interpreter: every cell
144
+ // otherwise pays one (or two — backend.isAvailable + ensureKernelAvailable)
145
+ // interpreter spawns even when the kernel is already hot. Failures are not
146
+ // cached so installing a Python mid-session is picked up on the next attempt.
136
147
  const availabilityCache = new Map<string, Promise<PythonKernelAvailability>>();
137
148
 
138
- export async function checkPythonKernelAvailability(cwd: string): Promise<PythonKernelAvailability> {
149
+ export async function checkPythonKernelAvailability(
150
+ cwd: string,
151
+ interpreter?: string,
152
+ ): Promise<PythonKernelAvailability> {
139
153
  if (isBunTestRuntime() || $flag("PI_PYTHON_SKIP_CHECK")) {
140
154
  return { ok: true };
141
155
  }
142
- const key = path.resolve(cwd);
156
+ const resolvedCwd = path.resolve(cwd);
157
+ const key = `${resolvedCwd}\0${interpreter ?? ""}`;
143
158
  const cached = availabilityCache.get(key);
144
159
  if (cached) return await cached;
145
- const probe = probePythonKernelAvailability(key);
160
+ const probe = probePythonKernelAvailability(resolvedCwd, interpreter);
146
161
  availabilityCache.set(key, probe);
147
162
  const result = await probe;
148
163
  if (!result.ok && availabilityCache.get(key) === probe) {
@@ -151,12 +166,14 @@ export async function checkPythonKernelAvailability(cwd: string): Promise<Python
151
166
  return result;
152
167
  }
153
168
 
154
- async function probePythonKernelAvailability(cwd: string): Promise<PythonKernelAvailability> {
169
+ async function probePythonKernelAvailability(cwd: string, interpreter?: string): Promise<PythonKernelAvailability> {
155
170
  try {
156
171
  const settings = await Settings.init();
157
172
  const { env } = settings.getShellConfig();
158
173
  const baseEnv = filterEnv(env);
159
- const runtimes = enumeratePythonRuntimes(cwd, baseEnv);
174
+ const runtimes = interpreter
175
+ ? [resolveExplicitPythonRuntime(interpreter, cwd, baseEnv)]
176
+ : enumeratePythonRuntimes(cwd, baseEnv);
160
177
  if (runtimes.length === 0) {
161
178
  return { ok: false, reason: "Python executable not found on PATH" };
162
179
  }
@@ -239,6 +256,7 @@ export class PythonKernel {
239
256
  "PythonKernel.start:availabilityCheck",
240
257
  checkPythonKernelAvailability,
241
258
  options.cwd,
259
+ options.interpreter,
242
260
  );
243
261
  if (!availability.ok) {
244
262
  throw new Error(availability.reason ?? "Python kernel unavailable");
@@ -251,7 +269,9 @@ export class PythonKernel {
251
269
  let runtime = availability.runtime;
252
270
  if (!runtime) {
253
271
  const { env: shellEnv } = (await Settings.init()).getShellConfig();
254
- runtime = resolvePythonRuntime(options.cwd, filterEnv(shellEnv));
272
+ runtime = options.interpreter
273
+ ? resolveExplicitPythonRuntime(options.interpreter, options.cwd, filterEnv(shellEnv))
274
+ : resolvePythonRuntime(options.cwd, filterEnv(shellEnv));
255
275
  }
256
276
  const spawnEnv: Record<string, string> = {};
257
277
  for (const [key, value] of Object.entries(runtime.env)) {
@@ -519,21 +519,20 @@ if "__omp_prelude_loaded__" not in globals():
519
519
  text = res.get("text") if isinstance(res, dict) else res
520
520
  return json.loads(text) if schema is not None else text
521
521
 
522
- def agent(prompt, *, agent_type="task", model=None, context=None, label=None, schema=None):
522
+ def agent(prompt, *, agent_type="task", model=None, label=None, schema=None):
523
523
  """Run a subagent and return its final output.
524
524
 
525
525
  `agent_type` selects the subagent definition (default "task"). Pass
526
- `model` to override that agent's model, `context` for shared background,
527
- `label` for the output artifact id, and `schema` to request structured
528
- JSON output; when `schema` is supplied the parsed object is returned.
526
+ `model` to override that agent's model, `label` for the output artifact
527
+ id, and `schema` to request structured JSON output; when `schema` is
528
+ supplied the parsed object is returned. Share background by writing a
529
+ local:// file and referencing it in the prompt.
529
530
  """
530
531
  args = {"prompt": prompt}
531
532
  if agent_type is not None:
532
533
  args["agentType"] = agent_type
533
534
  if model is not None:
534
535
  args["model"] = model
535
- if context is not None:
536
- args["context"] = context
537
536
  if label is not None:
538
537
  args["label"] = label
539
538
  if schema is not None:
@@ -5,6 +5,7 @@
5
5
  * for both the shared gateway and local kernel spawning.
6
6
  */
7
7
  import * as fs from "node:fs";
8
+ import * as os from "node:os";
8
9
  import * as path from "node:path";
9
10
  import { $env, $which, getPythonEnvDir } from "@oh-my-pi/pi-utils";
10
11
 
@@ -182,6 +183,42 @@ function venvBinDir(venvPath: string): string {
182
183
  return process.platform === "win32" ? path.join(venvPath, "Scripts") : path.join(venvPath, "bin");
183
184
  }
184
185
 
186
+ function detectExplicitVenv(pythonPath: string): { venvPath: string; binDir: string } | undefined {
187
+ const binDir = path.dirname(pythonPath);
188
+ const venvPath = path.dirname(binDir);
189
+ if (fs.existsSync(path.join(venvPath, "pyvenv.cfg"))) {
190
+ return { venvPath, binDir };
191
+ }
192
+ return undefined;
193
+ }
194
+
195
+ /**
196
+ * Resolve an explicitly configured interpreter (`python.interpreter`) into a
197
+ * runtime, bypassing discovery. Does not probe or validate the executable —
198
+ * callers must check it actually runs. `~` expands to the home directory and
199
+ * relative paths resolve against `cwd`. When the interpreter sits inside a
200
+ * virtualenv (a `pyvenv.cfg` above its bin dir), the venv activation env is
201
+ * applied so subprocesses and `pip` resolve consistently.
202
+ */
203
+ export function resolveExplicitPythonRuntime(
204
+ interpreter: string,
205
+ cwd: string,
206
+ baseEnv: Record<string, string | undefined>,
207
+ ): PythonRuntime {
208
+ const expanded =
209
+ interpreter === "~"
210
+ ? os.homedir()
211
+ : interpreter.startsWith("~/")
212
+ ? path.join(os.homedir(), interpreter.slice(2))
213
+ : interpreter;
214
+ const pythonPath = path.isAbsolute(expanded) ? expanded : path.resolve(cwd, expanded);
215
+ const venv = detectExplicitVenv(pythonPath);
216
+ if (venv) {
217
+ return { pythonPath, env: applyVenvEnv(baseEnv, venv.venvPath, venv.binDir), venvPath: venv.venvPath };
218
+ }
219
+ return { pythonPath, env: { ...baseEnv } };
220
+ }
221
+
185
222
  /**
186
223
  * Enumerate candidate Python runtimes in priority order: an active/project venv,
187
224
  * the managed `~/.omp/python-env`, then the system interpreter on PATH. Every
@@ -6,6 +6,7 @@
6
6
  import * as fs from "node:fs/promises";
7
7
  import { ExponentialYield } from "@oh-my-pi/pi-agent-core/utils/yield";
8
8
  import { executeShell, type MinimizerOptions, Shell, type ShellRunResult } from "@oh-my-pi/pi-natives";
9
+ import { isExecutable, type ShellConfig } from "@oh-my-pi/pi-utils/procmgr";
9
10
  import { Settings, type ShellMinimizerSettings } from "../config/settings";
10
11
  import { OutputSink } from "../session/streaming-output";
11
12
  import { resolveOutputMaxColumns, resolveOutputSinkHeadBytes } from "../tools/output-meta";
@@ -22,6 +23,8 @@ export interface BashExecutorOptions {
22
23
  sessionKey?: string;
23
24
  /** Additional environment variables to inject */
24
25
  env?: Record<string, string>;
26
+ /** Run through the configured user shell instead of brush parsing directly. */
27
+ useUserShell?: boolean;
25
28
  /** Artifact path/id for full output storage */
26
29
  artifactPath?: string;
27
30
  artifactId?: string;
@@ -95,13 +98,86 @@ export function buildMinimizerOptions(group: ShellMinimizerSettings): MinimizerO
95
98
  only: group.only.length > 0 ? group.only : undefined,
96
99
  except: group.except.length > 0 ? group.except : undefined,
97
100
  maxCaptureBytes: group.maxCaptureBytes,
101
+ sourceOutlineLevel: group.sourceOutlineLevel === "default" ? undefined : group.sourceOutlineLevel,
102
+ legacyFilters: group.legacyFilters,
103
+ };
104
+ }
105
+
106
+ function shellBasename(shell: string): string {
107
+ return shell.replace(/\\/g, "/").split("/").pop()?.toLowerCase() ?? "";
108
+ }
109
+
110
+ function isBashShell(shell: string): boolean {
111
+ const basename = shellBasename(shell);
112
+ return basename.includes("bash");
113
+ }
114
+
115
+ function needsInteractiveShellArg(shell: string): boolean {
116
+ const basename = shellBasename(shell);
117
+ return basename.includes("zsh");
118
+ }
119
+
120
+ function supportsAutoUserShell(shell: string): boolean {
121
+ const basename = shellBasename(shell);
122
+ return basename.includes("bash") || basename.includes("zsh") || basename.includes("fish");
123
+ }
124
+
125
+ function hasInteractiveShellArg(args: string[]): boolean {
126
+ return args.some(arg => arg === "--interactive" || /^-[^-]*i/.test(arg));
127
+ }
128
+
129
+ function ensureInteractiveShellArgs(shell: string, args: string[]): string[] {
130
+ if (!needsInteractiveShellArg(shell) || hasInteractiveShellArg(args)) return args;
131
+
132
+ const commandIndex = args.findIndex(arg => arg === "-c" || arg === "--command");
133
+ if (commandIndex !== -1) {
134
+ return [...args.slice(0, commandIndex), "-i", ...args.slice(commandIndex)];
135
+ }
136
+
137
+ const compactCommandIndex = args.findIndex(arg => /^-[^-]*c[^-]*$/.test(arg));
138
+ if (compactCommandIndex !== -1) {
139
+ return args.map((arg, index) => (index === compactCommandIndex ? arg.replace("c", "ic") : arg));
140
+ }
141
+
142
+ return [...args, "-i"];
143
+ }
144
+
145
+ function quoteShellArg(value: string): string {
146
+ return `'${value.replace(/'/g, "'\\''")}'`;
147
+ }
148
+
149
+ function buildUserShellCommand(shell: string, args: string[], command: string): string {
150
+ return [shell, ...ensureInteractiveShellArgs(shell, args), command].map(quoteShellArg).join(" ");
151
+ }
152
+
153
+ function resolveUserShellConfig(settings: Settings, baseConfig: ShellConfig): ShellConfig {
154
+ const customShellPath = settings.get("shellPath");
155
+ const envShell = Bun.env.SHELL;
156
+ if (customShellPath || process.platform === "win32" || !envShell || envShell === baseConfig.shell) {
157
+ return baseConfig;
158
+ }
159
+ if (!supportsAutoUserShell(envShell) || !isExecutable(envShell)) {
160
+ return baseConfig;
161
+ }
162
+
163
+ return {
164
+ ...baseConfig,
165
+ shell: envShell,
166
+ env: {
167
+ ...baseConfig.env,
168
+ SHELL: envShell,
169
+ },
98
170
  };
99
171
  }
100
172
 
101
173
  export async function executeBash(command: string, options?: BashExecutorOptions): Promise<BashResult> {
102
174
  const settings = await Settings.init();
103
- const { shell, env: shellEnv, prefix } = settings.getShellConfig();
104
- const snapshotPath = shell.includes("bash") ? await getOrCreateSnapshot(shell, shellEnv) : null;
175
+ const baseShellConfig = settings.getShellConfig();
176
+ const shellConfig =
177
+ options?.useUserShell === true ? resolveUserShellConfig(settings, baseShellConfig) : baseShellConfig;
178
+ const { shell, args, env: shellEnv, prefix } = shellConfig;
179
+ const bashShell = isBashShell(shell);
180
+ const snapshotPath = bashShell ? await getOrCreateSnapshot(shell, shellEnv) : null;
105
181
 
106
182
  const minimizer = buildMinimizerOptions(settings.getGroup("shellMinimizer"));
107
183
 
@@ -110,7 +186,10 @@ export async function executeBash(command: string, options?: BashExecutorOptions
110
186
 
111
187
  // Apply command prefix if configured
112
188
  const prefixedCommand = prefix ? `${prefix} ${command}` : command;
113
- const finalCommand = prefixedCommand;
189
+ const finalCommand =
190
+ options?.useUserShell === true && !bashShell
191
+ ? buildUserShellCommand(shell, args, prefixedCommand)
192
+ : prefixedCommand;
114
193
 
115
194
  // Create output sink for truncation and artifact handling
116
195
  const sink = new OutputSink({