pi-codex-tools 0.2.0 → 0.2.2
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 +16 -0
- package/README.md +1 -1
- package/SECURITY.md +1 -1
- package/extensions/index.ts +0 -11
- package/package.json +8 -7
- package/src/patch-preview.ts +13 -4
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,22 @@ This project follows the spirit of [Keep a Changelog](https://keepachangelog.com
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.2.2] - 2026-08-10
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Keep provider-side parallel tool calls enabled while preserving sequential `apply_patch` execution.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Strip untrusted terminal control sequences from `apply_patch` previews before rendering them in the Pi TUI.
|
|
18
|
+
|
|
19
|
+
## [0.2.1] - 2026-08-08
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- Stop npm from running `node-gyp rebuild` on install. The package ships prebuilt native bindings and loads them at runtime, but npm's gypfile detection keyed off the dev-only `binding.gyp` and injected a failing `node-gyp rebuild` into the install lifecycle — breaking install on machines without a build toolchain (e.g. Linux). Set `gypfile: false` to suppress it.
|
|
24
|
+
|
|
9
25
|
## [0.2.0] - 2026-08-08
|
|
10
26
|
|
|
11
27
|
### Added
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Give grammar-capable OpenAI/Codex models the Codex `apply_patch` tool in Pi with
|
|
|
8
8
|
- **Capability-based activation** — requires `openai-codex-responses` or `openai-responses` plus `model.compat.supportsOpenAIGrammarTools === true`; model names alone are never enough.
|
|
9
9
|
- **Safe local mutation** — patches are limited to 1 MiB, target files to 64 MiB, stay under Pi's current working directory, reject symlink escapes, use descriptor-anchored no-follow operations on Linux and macOS, fail closed elsewhere, preflight all hunks, and serialize writes with Pi's mutation queue.
|
|
10
10
|
- **Model switching** — supported models replace Pi's `edit` and `write` tools with `apply_patch`; other active tools are preserved. Switching back restores only the file tools that were active before the switch.
|
|
11
|
-
- **Sequential patch calls** — the extension marks patch execution sequential
|
|
11
|
+
- **Sequential patch calls** — the extension marks patch execution sequential while leaving provider-side parallel tool calls enabled.
|
|
12
12
|
- **Streaming progress** — while a patch is generated, the TUI shows a live, color-coded glimpse of the content being written (new-file content, or `+`/`-` lines for updates) plus a running `+added -removed` tally and a per-file roster for multi-file patches. It reuses Pi's shared diff rendering and mirrors the built-in `write`/`edit` previews; patch execution is unchanged.
|
|
13
13
|
|
|
14
14
|
## Installation
|
package/SECURITY.md
CHANGED
|
@@ -21,6 +21,6 @@ Pi extensions execute with the same permissions as the local user running Pi. Re
|
|
|
21
21
|
|
|
22
22
|
`apply_patch` does not access the network or credential APIs. It can read credential-containing files when a patch targets them. It validates paths beneath the current working directory, rejects symlink paths and symlinked parents, limits patch input to 1 MiB and target-file reads to 64 MiB, preflights file changes before writing, and performs a root-anchored descriptor-based no-follow directory walk so a path component swapped to a symlink between check and use cannot escape. On Linux the walk re-opens each component via `/proc/self/fd`; on macOS it uses a bundled `openat`/`mkdirat`/`unlinkat` N-API binding (committed prebuilds for `darwin-arm64` and `darwin-x64`, loaded via `node-gyp-build`) because Node does not expose `openat` and macOS lacks procfs. The binding is darwin-only, exposes only those three POSIX calls, and is loaded best-effort: on platforms without it `apply_patch` fails closed and Pi keeps its native `edit`/`write` tools. A failure during a multi-file write can still leave earlier files changed; callers should use version control and review the resulting diff.
|
|
23
23
|
|
|
24
|
-
The package reads the current provider/model capability flags only to select tools. It does not log prompts, patches, file contents, credentials, auth headers, or provider responses.
|
|
24
|
+
The package strips untrusted C0/C1 control bytes and terminal escape sequences from `apply_patch` preview text and paths before handing them to the TUI. The package reads the current provider/model capability flags only to select tools. It does not log prompts, patches, file contents, credentials, auth headers, or provider responses.
|
|
25
25
|
|
|
26
26
|
Install/update telemetry is best effort and can be disabled with `PI_OFFLINE=1`, `PI_TELEMETRY=0`, `PI_TELEMETRY=false`, CI detection, or Pi's `enableInstallTelemetry: false` setting. Through `@mocito/install-telemetry`, it sends the package name and version as HTTPS URL query parameters and adds `process.platform`, the runtime name/version, and `process.arch` to the `User-Agent`. These fields are not intended to identify a host, user, repository, or path; no prompts, patches, file contents, credentials, auth headers, or provider responses are sent.
|
package/extensions/index.ts
CHANGED
|
@@ -122,15 +122,4 @@ export default function piCodexTools(pi: ExtensionAPI): void {
|
|
|
122
122
|
pi.on("model_select", (_event, ctx) => {
|
|
123
123
|
synchronizeTools(ctx);
|
|
124
124
|
});
|
|
125
|
-
|
|
126
|
-
pi.on("before_provider_request", (event, ctx) => {
|
|
127
|
-
if (!supportsOpenAIGrammarTools(ctx.model)) return;
|
|
128
|
-
if (typeof pi.getActiveTools !== "function" || !pi.getActiveTools().includes(APPLY_PATCH)) return;
|
|
129
|
-
if (!isRecord(event.payload) || event.payload.parallel_tool_calls !== true) return;
|
|
130
|
-
return { ...event.payload, parallel_tool_calls: false };
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
135
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
136
125
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-codex-tools",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Codex-compatible apply_patch tooling for Pi's grammar-capable OpenAI models.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -60,13 +60,13 @@
|
|
|
60
60
|
"typebox": "*"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
|
-
"@earendil-works/pi-ai": "^0.
|
|
64
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
65
|
-
"@earendil-works/pi-tui": "^0.
|
|
63
|
+
"@earendil-works/pi-ai": "^0.84.1",
|
|
64
|
+
"@earendil-works/pi-coding-agent": "^0.84.1",
|
|
65
|
+
"@earendil-works/pi-tui": "^0.84.1",
|
|
66
66
|
"@types/node": "^26.1.2",
|
|
67
67
|
"prebuildify": "^6.0.1",
|
|
68
|
-
"tsx": "^4.23.
|
|
69
|
-
"typebox": "^1.3.
|
|
68
|
+
"tsx": "^4.23.5",
|
|
69
|
+
"typebox": "^1.3.10",
|
|
70
70
|
"typescript": "^7.0.2"
|
|
71
71
|
},
|
|
72
72
|
"publishConfig": {
|
|
@@ -78,5 +78,6 @@
|
|
|
78
78
|
"dependencies": {
|
|
79
79
|
"@mocito/install-telemetry": "0.1.1",
|
|
80
80
|
"node-gyp-build": "^4.8.4"
|
|
81
|
-
}
|
|
81
|
+
},
|
|
82
|
+
"gypfile": false
|
|
82
83
|
}
|
package/src/patch-preview.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// (it never throws) to drive a live, write-style glimpse of the content plus a running
|
|
6
6
|
// added/removed tally. It reuses Pi's shared `renderDiff` primitive for +/- coloring, so the
|
|
7
7
|
// preview stays consistent with the built-in `edit` tool's diff preview.
|
|
8
|
+
import { stripTerminalSequences } from "@earendil-works/pi-tui";
|
|
8
9
|
import { keyHint, type Theme } from "@earendil-works/pi-coding-agent";
|
|
9
10
|
|
|
10
11
|
const FILE_ADD = "*** Add File: ";
|
|
@@ -26,6 +27,11 @@ const MAX_PREVIEW_BYTES = 256 * 1024;
|
|
|
26
27
|
const MAX_PREVIEW_FILES = 500;
|
|
27
28
|
/** Cap a single rendered glimpse line so minified/generated content cannot flood the TUI. */
|
|
28
29
|
const PREVIEW_LINE_CHARS = 200;
|
|
30
|
+
const TERMINAL_CONTROL_CHARS = /[\u0000-\u0008\u000B-\u001F\u007F-\u009F]/g;
|
|
31
|
+
|
|
32
|
+
function sanitizeTerminalText(text: string): string {
|
|
33
|
+
return stripTerminalSequences(text).replace(TERMINAL_CONTROL_CHARS, "").replace(/\t/g, " ");
|
|
34
|
+
}
|
|
29
35
|
|
|
30
36
|
export type PatchLineType = "add" | "del" | "ctx";
|
|
31
37
|
|
|
@@ -156,8 +162,10 @@ function fileMarkLabel(file: PatchPreviewFile, theme: Theme): string {
|
|
|
156
162
|
}
|
|
157
163
|
|
|
158
164
|
function filePathLabel(file: PatchPreviewFile, theme: Theme): string {
|
|
159
|
-
const path = theme.fg("accent", truncatePath(file.path));
|
|
160
|
-
return file.moveTo
|
|
165
|
+
const path = theme.fg("accent", truncatePath(sanitizeTerminalText(file.path)));
|
|
166
|
+
return file.moveTo
|
|
167
|
+
? `${path} ${theme.fg("muted", "->")} ${theme.fg("accent", truncatePath(sanitizeTerminalText(file.moveTo)))}`
|
|
168
|
+
: path;
|
|
161
169
|
}
|
|
162
170
|
|
|
163
171
|
function formatFileRosterLine(file: PatchPreviewFile, theme: Theme): string {
|
|
@@ -177,7 +185,8 @@ function countLines(file: PatchPreviewFile, type: PatchLineType): number {
|
|
|
177
185
|
*/
|
|
178
186
|
function renderGlimpseLine(line: PatchPreviewLine, theme: Theme): string {
|
|
179
187
|
const sign = line.type === "add" ? "+" : line.type === "del" ? "-" : " ";
|
|
180
|
-
const
|
|
188
|
+
const text = sanitizeTerminalText(line.text);
|
|
189
|
+
const body = text.length > PREVIEW_LINE_CHARS ? `${text.slice(0, PREVIEW_LINE_CHARS)}…` : text;
|
|
181
190
|
const styled = `${sign}${body}`;
|
|
182
191
|
if (line.type === "add") return theme.fg("toolDiffAdded", styled);
|
|
183
192
|
if (line.type === "del") return theme.fg("toolDiffRemoved", styled);
|
|
@@ -263,5 +272,5 @@ export function formatApplyPatchResultText(
|
|
|
263
272
|
.map((part) => part.text ?? "")
|
|
264
273
|
.join("\n")
|
|
265
274
|
.trim();
|
|
266
|
-
return output ? theme.fg("error", output) : undefined;
|
|
275
|
+
return output ? theme.fg("error", sanitizeTerminalText(output)) : undefined;
|
|
267
276
|
}
|