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

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,17 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [6.8.5] - 2026-01-21
6
+ ### Breaking Changes
7
+
8
+ - Changed timeout parameter from seconds to milliseconds in Python tool
9
+ - Updated PythonExecutorOptions interface to use timeoutMs instead of timeout
10
+
11
+ ### Changed
12
+
13
+ - Updated default timeout to 30000ms (30 seconds) for Python tool
14
+ - Improved streaming output handling and buffer management
15
+
5
16
  ## [6.8.4] - 2026-01-21
6
17
  ### Changed
7
18
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oh-my-pi/pi-coding-agent",
3
- "version": "6.8.4",
3
+ "version": "6.8.5",
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.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",
43
+ "@oh-my-pi/pi-agent-core": "6.8.5",
44
+ "@oh-my-pi/pi-ai": "6.8.5",
45
+ "@oh-my-pi/pi-git-tool": "6.8.5",
46
+ "@oh-my-pi/pi-tui": "6.8.5",
47
+ "@oh-my-pi/pi-utils": "6.8.5",
48
48
  "@openai/agents": "^0.3.7",
49
49
  "@sinclair/typebox": "^0.34.46",
50
50
  "ajv": "^8.17.1",
@@ -14,9 +14,9 @@ export interface PythonExecutorOptions {
14
14
  /** Working directory for command execution */
15
15
  cwd?: string;
16
16
  /** Timeout in milliseconds */
17
- timeout?: number;
17
+ timeoutMs?: number;
18
18
  /** Callback for streaming output chunks (already sanitized) */
19
- onChunk?: (chunk: string) => void;
19
+ onChunk?: (chunk: string) => Promise<void> | void;
20
20
  /** AbortSignal for cancellation */
21
21
  signal?: AbortSignal;
22
22
  /** Session identifier for kernel reuse */
@@ -216,17 +216,13 @@ async function executeWithKernel(
216
216
  try {
217
217
  const result = await kernel.execute(code, {
218
218
  signal: options?.signal,
219
- timeoutMs: options?.timeout,
220
- onChunk: (text) => {
221
- sink.push(text);
222
- },
223
- onDisplay: (output) => {
224
- displayOutputs.push(output);
225
- },
219
+ timeoutMs: options?.timeoutMs,
220
+ onChunk: (text) => sink.push(text),
221
+ onDisplay: (output) => void displayOutputs.push(output),
226
222
  });
227
223
 
228
224
  if (result.cancelled) {
229
- const secs = options?.timeout ? Math.round(options.timeout / 1000) : undefined;
225
+ const secs = options?.timeoutMs ? Math.round(options.timeoutMs / 1000) : undefined;
230
226
  const annotation =
231
227
  result.timedOut && secs !== undefined ? `Command timed out after ${secs} seconds` : undefined;
232
228
  return {
@@ -33,8 +33,6 @@ export class OutputSink {
33
33
  path: string;
34
34
  sink: Bun.FileSink;
35
35
  };
36
- #bytesWritten: number = 0;
37
- #pending: Promise<void> = Promise.resolve();
38
36
 
39
37
  readonly #allocateFilePath: () => string;
40
38
  readonly #spillThreshold: number;
@@ -54,15 +52,16 @@ export class OutputSink {
54
52
 
55
53
  async #pushSanitized(data: string): Promise<void> {
56
54
  this.#onChunk?.(data);
57
- const dataBytes = Buffer.byteLength(data);
58
- const overflow = dataBytes + this.#bytesWritten > this.#spillThreshold || this.#file != null;
55
+
56
+ const bufferOverflow = data.length + this.#buffer.length > this.#spillThreshold;
57
+ const overflow = this.#file || bufferOverflow;
59
58
 
60
59
  const sink = overflow ? await this.#fileSink() : null;
61
60
 
62
61
  this.#buffer += data;
63
62
  await sink?.write(data);
64
63
 
65
- if (this.#buffer.length > this.#spillThreshold) {
64
+ if (bufferOverflow) {
66
65
  this.#buffer = this.#buffer.slice(-this.#spillThreshold);
67
66
  }
68
67
  }
@@ -81,28 +80,21 @@ export class OutputSink {
81
80
 
82
81
  async push(chunk: string): Promise<void> {
83
82
  chunk = sanitizeText(chunk);
84
- const op = this.#pending.then(() => this.#pushSanitized(chunk));
85
- this.#pending = op.catch(() => {});
86
- await op;
83
+ await this.#pushSanitized(chunk);
87
84
  }
88
85
 
89
86
  createInput(): WritableStream<Uint8Array | string> {
90
- let decoder: TextDecoder | undefined;
91
- let finalize = async () => {};
87
+ const dec = new TextDecoder("utf-8", { ignoreBOM: true });
88
+ const finalize = async () => {
89
+ await this.push(dec.decode());
90
+ };
92
91
 
93
92
  return new WritableStream<Uint8Array | string>({
94
93
  write: async (chunk) => {
95
94
  if (typeof chunk === "string") {
96
95
  await this.push(chunk);
97
96
  } else {
98
- if (!decoder) {
99
- const dec = new TextDecoder("utf-8", { ignoreBOM: true });
100
- decoder = dec;
101
- finalize = async () => {
102
- await this.push(dec.decode());
103
- };
104
- }
105
- await this.push(decoder.decode(chunk, { stream: true }));
97
+ await this.push(dec.decode(chunk, { stream: true }));
106
98
  }
107
99
  },
108
100
  close: finalize,
@@ -111,7 +103,6 @@ export class OutputSink {
111
103
  }
112
104
 
113
105
  async dump(notice?: string): Promise<OutputResult> {
114
- await this.#pending;
115
106
  const noticeLine = notice ? `[${notice}]\n` : "";
116
107
 
117
108
  if (this.#file) {
@@ -40,7 +40,7 @@ function groupPreludeHelpers(helpers: PreludeHelper[]): PreludeCategory[] {
40
40
 
41
41
  export const pythonSchema = Type.Object({
42
42
  code: Type.String({ description: "Python code to execute" }),
43
- timeout: Type.Optional(Type.Number({ description: "Timeout in seconds (optional, no default timeout)" })),
43
+ timeoutMs: Type.Optional(Type.Number({ description: "Timeout in milliseconds (default: 30000)" })),
44
44
  workdir: Type.Optional(
45
45
  Type.String({ description: "Working directory for the command (default: current directory)" }),
46
46
  ),
@@ -151,7 +151,7 @@ export class PythonTool implements AgentTool<typeof pythonSchema> {
151
151
  throw new Error("Python tool requires a session when not using proxy executor");
152
152
  }
153
153
 
154
- const { code, timeout, workdir, reset } = params;
154
+ const { code, timeoutMs = 30000, workdir, reset } = params;
155
155
  const controller = new AbortController();
156
156
  const onAbort = () => controller.abort();
157
157
  signal?.addEventListener("abort", onAbort, { once: true });
@@ -182,7 +182,7 @@ export class PythonTool implements AgentTool<typeof pythonSchema> {
182
182
  const sessionId = sessionFile ? `session:${sessionFile}:workdir:${commandCwd}` : `cwd:${commandCwd}`;
183
183
  const executorOptions: PythonExecutorOptions = {
184
184
  cwd: commandCwd,
185
- timeout: timeout ? timeout * 1000 : undefined,
185
+ timeoutMs,
186
186
  signal: controller.signal,
187
187
  sessionId,
188
188
  kernelMode: this.session.settings?.getPythonKernelMode?.() ?? "session",