@oh-my-pi/pi-coding-agent 6.8.2 → 6.8.4

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/CHANGELOG.md CHANGED
@@ -2,6 +2,36 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [6.8.4] - 2026-01-21
6
+ ### Changed
7
+
8
+ - Updated output sink to properly handle large outputs
9
+ - Improved error message formatting in SSH executor
10
+ - Updated web fetch timeout bounds and conversion
11
+
12
+ ### Fixed
13
+
14
+ - Fixed output truncation handling in streaming output
15
+ - Fixed timeout handling in web fetch tool
16
+ - Fixed async stream dumping in executors
17
+
18
+ ## [6.8.3] - 2026-01-21
19
+
20
+ ### Changed
21
+
22
+ - Updated keybinding system to normalize key IDs to lowercase
23
+ - Changed label edit shortcut from 'l' to 'Shift+L' in tree selector
24
+ - Changed output file extension from `.out.md` to `.md` for artifacts
25
+
26
+ ### Removed
27
+
28
+ - Removed bundled worktree command from custom commands loader
29
+
30
+ ### Fixed
31
+
32
+ - Fixed keybinding case sensitivity issues by normalizing all key IDs
33
+ - Fixed task artifact path handling and simplified file structure
34
+
5
35
  ## [6.8.2] - 2026-01-21
6
36
 
7
37
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oh-my-pi/pi-coding-agent",
3
- "version": "6.8.2",
3
+ "version": "6.8.4",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "ompConfig": {
@@ -40,11 +40,11 @@
40
40
  "prepublishOnly": "bun run generate-template && bun run clean && bun run build"
41
41
  },
42
42
  "dependencies": {
43
- "@oh-my-pi/pi-agent-core": "6.8.2",
44
- "@oh-my-pi/pi-ai": "6.8.2",
45
- "@oh-my-pi/pi-git-tool": "6.8.2",
46
- "@oh-my-pi/pi-tui": "6.8.2",
47
- "@oh-my-pi/pi-utils": "6.8.2",
43
+ "@oh-my-pi/pi-agent-core": "6.8.4",
44
+ "@oh-my-pi/pi-ai": "6.8.4",
45
+ "@oh-my-pi/pi-git-tool": "6.8.4",
46
+ "@oh-my-pi/pi-tui": "6.8.4",
47
+ "@oh-my-pi/pi-utils": "6.8.4",
48
48
  "@openai/agents": "^0.3.7",
49
49
  "@sinclair/typebox": "^0.34.46",
50
50
  "ajv": "^8.17.1",
@@ -22,7 +22,7 @@ import type { Rule } from "../capability/rule";
22
22
  import { getAgentDbPath } from "../config";
23
23
  import { theme } from "../modes/interactive/theme/theme";
24
24
  import ttsrInterruptTemplate from "../prompts/system/ttsr-interrupt.md" with { type: "text" };
25
- import { type BashResult, executeBash as executeBashCommand, executeBashWithOperations } from "./bash-executor";
25
+ import { type BashResult, executeBash as executeBashCommand } from "./bash-executor";
26
26
  import {
27
27
  type CompactionResult,
28
28
  calculateContextTokens,
@@ -60,10 +60,8 @@ import type { Skill, SkillWarning } from "./skills";
60
60
  import { expandSlashCommand, type FileSlashCommand } from "./slash-commands";
61
61
  import { closeAllConnections } from "./ssh/connection-manager";
62
62
  import { unmountAll } from "./ssh/sshfs-mount";
63
- import type { BashOperations } from "./tools/bash";
64
63
  import { normalizeDiff, normalizeToLF, ParseError, previewPatch, stripBom } from "./tools/patch";
65
64
  import { resolveToCwd } from "./tools/path-utils";
66
- import { getArtifactsDir } from "./tools/task/artifacts";
67
65
  import type { TodoItem } from "./tools/todo-write";
68
66
  import type { TtsrManager } from "./ttsr";
69
67
 
@@ -1991,10 +1989,7 @@ export class AgentSession {
1991
1989
  const sessionFile = this.sessionManager.getSessionFile();
1992
1990
  if (!sessionFile) return;
1993
1991
 
1994
- const artifactsDir = getArtifactsDir(sessionFile);
1995
- if (!artifactsDir) return;
1996
-
1997
- const todoPath = `${artifactsDir}/todos.json`;
1992
+ const todoPath = `${sessionFile.slice(0, -6)}/todos.json`;
1998
1993
  const file = Bun.file(todoPath);
1999
1994
  if (!(await file.exists())) {
2000
1995
  this._todoReminderCount = 0;
@@ -2550,25 +2545,19 @@ export class AgentSession {
2550
2545
  * @param command The bash command to execute
2551
2546
  * @param onChunk Optional streaming callback for output
2552
2547
  * @param options.excludeFromContext If true, command output won't be sent to LLM (!! prefix)
2553
- * @param options.operations Custom BashOperations for remote execution
2554
2548
  */
2555
2549
  async executeBash(
2556
2550
  command: string,
2557
2551
  onChunk?: (chunk: string) => void,
2558
- options?: { excludeFromContext?: boolean; operations?: BashOperations },
2552
+ options?: { excludeFromContext?: boolean },
2559
2553
  ): Promise<BashResult> {
2560
2554
  this._bashAbortController = new AbortController();
2561
2555
 
2562
2556
  try {
2563
- const result = options?.operations
2564
- ? await executeBashWithOperations(command, process.cwd(), options.operations, {
2565
- onChunk,
2566
- signal: this._bashAbortController.signal,
2567
- })
2568
- : await executeBashCommand(command, {
2569
- onChunk,
2570
- signal: this._bashAbortController.signal,
2571
- });
2557
+ const result = await executeBashCommand(command, {
2558
+ onChunk,
2559
+ signal: this._bashAbortController.signal,
2560
+ });
2572
2561
 
2573
2562
  this.recordBashResult(command, result, options);
2574
2563
  return result;
@@ -8,7 +8,6 @@ import { cspawn, Exception, ptree } from "@oh-my-pi/pi-utils";
8
8
  import { getShellConfig } from "../utils/shell";
9
9
  import { getOrCreateSnapshot, getSnapshotSourceCommand } from "../utils/shell-snapshot";
10
10
  import { OutputSink } from "./streaming-output";
11
- import type { BashOperations } from "./tools/bash";
12
11
 
13
12
  export interface BashExecutorOptions {
14
13
  cwd?: string;
@@ -34,7 +33,7 @@ export async function executeBash(command: string, options?: BashExecutorOptions
34
33
  const prefixedCommand = prefix ? `${prefix} ${command}` : command;
35
34
  const finalCommand = `${snapshotPrefix}${prefixedCommand}`;
36
35
 
37
- const stream = new OutputSink({ onChunk: options?.onChunk });
36
+ const sink = new OutputSink({ onChunk: options?.onChunk });
38
37
 
39
38
  const child = cspawn([shell, ...args, finalCommand], {
40
39
  cwd: options?.cwd,
@@ -45,12 +44,9 @@ export async function executeBash(command: string, options?: BashExecutorOptions
45
44
 
46
45
  // Pump streams - errors during abort/timeout are expected
47
46
  // Use preventClose to avoid closing the shared sink when either stream finishes
48
- await Promise.allSettled([
49
- child.stdout.pipeTo(stream.createWritable()),
50
- child.stderr.pipeTo(stream.createWritable()),
51
- ])
52
- .then(() => stream.close())
53
- .catch(() => {});
47
+ await Promise.allSettled([child.stdout.pipeTo(sink.createInput()), child.stderr.pipeTo(sink.createInput())]).catch(
48
+ () => {},
49
+ );
54
50
 
55
51
  // Wait for process exit
56
52
  try {
@@ -58,7 +54,7 @@ export async function executeBash(command: string, options?: BashExecutorOptions
58
54
  return {
59
55
  exitCode: child.exitCode ?? 0,
60
56
  cancelled: false,
61
- ...stream.dump(),
57
+ ...(await sink.dump()),
62
58
  };
63
59
  } catch (err) {
64
60
  // Exception covers NonZeroExitError, AbortError, TimeoutError
@@ -71,7 +67,7 @@ export async function executeBash(command: string, options?: BashExecutorOptions
71
67
  return {
72
68
  exitCode: undefined,
73
69
  cancelled: true,
74
- ...stream.dump(annotation),
70
+ ...(await sink.dump(annotation)),
75
71
  };
76
72
  }
77
73
 
@@ -79,60 +75,7 @@ export async function executeBash(command: string, options?: BashExecutorOptions
79
75
  return {
80
76
  exitCode: err.exitCode,
81
77
  cancelled: false,
82
- ...stream.dump(),
83
- };
84
- }
85
-
86
- throw err;
87
- }
88
- }
89
-
90
- export async function executeBashWithOperations(
91
- command: string,
92
- cwd: string,
93
- operations: BashOperations,
94
- options?: BashExecutorOptions,
95
- ): Promise<BashResult> {
96
- const stream = new OutputSink({ onChunk: options?.onChunk });
97
- const writable = stream.createWritable();
98
- const writer = writable.getWriter();
99
-
100
- const closeStreams = async () => {
101
- try {
102
- await writer.close();
103
- } catch {}
104
- try {
105
- await writable.close();
106
- } catch {}
107
- try {
108
- await stream.close();
109
- } catch {}
110
- };
111
-
112
- try {
113
- const result = await operations.exec(command, cwd, {
114
- onData: (data) => writer.write(data),
115
- signal: options?.signal,
116
- timeout: options?.timeout,
117
- });
118
-
119
- await closeStreams();
120
-
121
- const cancelled = options?.signal?.aborted ?? false;
122
-
123
- return {
124
- exitCode: cancelled ? undefined : (result.exitCode ?? undefined),
125
- cancelled,
126
- ...stream.dump(),
127
- };
128
- } catch (err) {
129
- await closeStreams();
130
-
131
- if (options?.signal?.aborted) {
132
- return {
133
- exitCode: undefined,
134
- cancelled: true,
135
- ...stream.dump(),
78
+ ...(await sink.dump()),
136
79
  };
137
80
  }
138
81
 
@@ -13,7 +13,6 @@ import { getAgentDir, getConfigDirs } from "../../config";
13
13
  import * as piCodingAgent from "../../index";
14
14
  import { execCommand } from "../exec";
15
15
  import { ReviewCommand } from "./bundled/review";
16
- import { WorktreeCommand } from "./bundled/wt";
17
16
  import type {
18
17
  CustomCommand,
19
18
  CustomCommandAPI,
@@ -153,13 +152,6 @@ function loadBundledCommands(sharedApi: CustomCommandAPI): LoadedCustomCommand[]
153
152
  source: "bundled",
154
153
  });
155
154
 
156
- bundled.push({
157
- path: "bundled:wt",
158
- resolvedPath: "bundled:wt",
159
- command: new WorktreeCommand(sharedApi),
160
- source: "bundled",
161
- });
162
-
163
155
  return bundled;
164
156
  }
165
157
 
@@ -29,7 +29,6 @@ import type {
29
29
  SessionManager,
30
30
  } from "../session-manager";
31
31
  import type { BashToolDetails, FindToolDetails, GrepToolDetails, LsToolDetails, ReadToolDetails } from "../tools";
32
- import type { BashOperations } from "../tools/bash";
33
32
  import type { EditToolDetails } from "../tools/patch";
34
33
 
35
34
  export type { ExecOptions, ExecResult } from "../exec";
@@ -551,8 +550,6 @@ export interface InputEventResult {
551
550
 
552
551
  /** Result from user_bash event handler */
553
552
  export interface UserBashEventResult {
554
- /** Custom operations to use for execution */
555
- operations?: BashOperations;
556
553
  /** Full replacement: extension handled execution, use this result */
557
554
  result?: BashResult;
558
555
  }
package/src/core/index.ts CHANGED
@@ -11,7 +11,7 @@ export {
11
11
  type PromptOptions,
12
12
  type SessionStats,
13
13
  } from "./agent-session";
14
- export { type BashExecutorOptions, type BashResult, executeBash, executeBashWithOperations } from "./bash-executor";
14
+ export { type BashExecutorOptions, type BashResult, executeBash } from "./bash-executor";
15
15
  export type { CompactionResult } from "./compaction/index";
16
16
  export {
17
17
  discoverAndLoadExtensions,
@@ -124,6 +124,8 @@ const KEY_LABELS: Record<string, string> = {
124
124
  right: "Right",
125
125
  };
126
126
 
127
+ const normalizeKeyId = (key: KeyId): KeyId => key.toLowerCase() as KeyId;
128
+
127
129
  function formatKeyPart(part: string): string {
128
130
  const lower = part.toLowerCase();
129
131
  const modifier = MODIFIER_LABELS[lower];
@@ -199,14 +201,20 @@ export class KeybindingsManager {
199
201
  // Set defaults for app actions
200
202
  for (const [action, keys] of Object.entries(DEFAULT_APP_KEYBINDINGS)) {
201
203
  const keyArray = Array.isArray(keys) ? keys : [keys];
202
- this.appActionToKeys.set(action as AppAction, [...keyArray]);
204
+ this.appActionToKeys.set(
205
+ action as AppAction,
206
+ keyArray.map((key) => normalizeKeyId(key as KeyId)),
207
+ );
203
208
  }
204
209
 
205
210
  // Override with user config (app actions only)
206
211
  for (const [action, keys] of Object.entries(this.config)) {
207
212
  if (keys === undefined || !isAppAction(action)) continue;
208
213
  const keyArray = Array.isArray(keys) ? keys : [keys];
209
- this.appActionToKeys.set(action, keyArray);
214
+ this.appActionToKeys.set(
215
+ action,
216
+ keyArray.map((key) => normalizeKeyId(key as KeyId)),
217
+ );
210
218
  }
211
219
  }
212
220
 
@@ -1,4 +1,4 @@
1
- import { logger, sanitizeText } from "@oh-my-pi/pi-utils";
1
+ import { logger } from "@oh-my-pi/pi-utils";
2
2
  import {
3
3
  checkPythonKernelAvailability,
4
4
  type KernelDisplayOutput,
@@ -210,30 +210,20 @@ async function executeWithKernel(
210
210
  code: string,
211
211
  options: PythonExecutorOptions | undefined,
212
212
  ): Promise<PythonResult> {
213
- const sink = new OutputSink({ onLine: options?.onChunk });
213
+ const sink = new OutputSink({ onChunk: options?.onChunk });
214
214
  const displayOutputs: KernelDisplayOutput[] = [];
215
215
 
216
216
  try {
217
- const writable = sink.createStringWritable();
218
- const writer = writable.getWriter();
219
- let result: KernelExecuteResult;
220
- try {
221
- result = await kernel.execute(code, {
222
- signal: options?.signal,
223
- timeoutMs: options?.timeout,
224
- onChunk: (text) => {
225
- writer.write(sanitizeText(text));
226
- },
227
- onDisplay: (output) => {
228
- displayOutputs.push(output);
229
- },
230
- });
231
- } catch (err) {
232
- await writer.abort(err);
233
- throw err;
234
- } finally {
235
- await writer.close().catch(() => {});
236
- }
217
+ const result = await kernel.execute(code, {
218
+ signal: options?.signal,
219
+ timeoutMs: options?.timeout,
220
+ onChunk: (text) => {
221
+ sink.push(text);
222
+ },
223
+ onDisplay: (output) => {
224
+ displayOutputs.push(output);
225
+ },
226
+ });
237
227
 
238
228
  if (result.cancelled) {
239
229
  const secs = options?.timeout ? Math.round(options.timeout / 1000) : undefined;
@@ -244,7 +234,7 @@ async function executeWithKernel(
244
234
  cancelled: true,
245
235
  displayOutputs,
246
236
  stdinRequested: result.stdinRequested,
247
- ...sink.dump(annotation),
237
+ ...(await sink.dump(annotation)),
248
238
  };
249
239
  }
250
240
 
@@ -254,7 +244,7 @@ async function executeWithKernel(
254
244
  cancelled: false,
255
245
  displayOutputs,
256
246
  stdinRequested: true,
257
- ...sink.dump("Kernel requested stdin; interactive input is not supported."),
247
+ ...(await sink.dump("Kernel requested stdin; interactive input is not supported.")),
258
248
  };
259
249
  }
260
250
 
@@ -264,7 +254,7 @@ async function executeWithKernel(
264
254
  cancelled: false,
265
255
  displayOutputs,
266
256
  stdinRequested: false,
267
- ...sink.dump(),
257
+ ...(await sink.dump()),
268
258
  };
269
259
  } catch (err) {
270
260
  const error = err instanceof Error ? err : new Error(String(err));
@@ -70,16 +70,11 @@ export async function executeSSH(
70
70
  timeout: options?.timeout,
71
71
  });
72
72
 
73
- const sink = new OutputSink({ onLine: options?.onChunk });
73
+ const sink = new OutputSink({ onChunk: options?.onChunk });
74
74
 
75
- try {
76
- await Promise.allSettled([
77
- child.stdout.pipeTo(sink.createWritable()),
78
- child.stderr.pipeTo(sink.createWritable()),
79
- ]);
80
- } finally {
81
- await sink.close();
82
- }
75
+ await Promise.allSettled([child.stdout.pipeTo(sink.createInput()), child.stderr.pipeTo(sink.createInput())]).catch(
76
+ () => {},
77
+ );
83
78
 
84
79
  try {
85
80
  await child.exited;
@@ -87,7 +82,7 @@ export async function executeSSH(
87
82
  return {
88
83
  exitCode,
89
84
  cancelled: false,
90
- ...sink.dump(),
85
+ ...(await sink.dump()),
91
86
  };
92
87
  } catch (err) {
93
88
  if (err instanceof ptree.Exception) {
@@ -95,20 +90,20 @@ export async function executeSSH(
95
90
  return {
96
91
  exitCode: undefined,
97
92
  cancelled: true,
98
- ...sink.dump(`SSH: ${err.message}`),
93
+ ...(await sink.dump(`SSH: ${err.message}`)),
99
94
  };
100
95
  }
101
96
  if (err.aborted) {
102
97
  return {
103
98
  exitCode: undefined,
104
99
  cancelled: true,
105
- ...sink.dump(`SSH command aborted: ${err.message}`),
100
+ ...(await sink.dump(`Command aborted: ${err.message}`)),
106
101
  };
107
102
  }
108
103
  return {
109
104
  exitCode: err.exitCode,
110
105
  cancelled: false,
111
- ...sink.dump(`Unexpected error: ${err.message}`),
106
+ ...(await sink.dump(`Unexpected error: ${err.message}`)),
112
107
  };
113
108
  }
114
109
  throw err;