@oh-my-pi/pi-coding-agent 14.9.7 → 14.9.9
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 +36 -0
- package/package.json +7 -7
- package/scripts/build-binary.ts +11 -0
- package/scripts/generate-template.ts +4 -3
- package/src/cli/stats-cli.ts +2 -0
- package/src/cli.ts +23 -1
- package/src/config/settings-schema.ts +30 -9
- package/src/config/settings.ts +18 -1
- package/src/edit/streaming.ts +1 -1
- package/src/eval/js/context-manager.ts +9 -8
- package/src/export/html/index.ts +5 -2
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.macro.ts +4 -3
- package/src/hashline/grammar.lark +1 -1
- package/src/hashline/input.ts +11 -5
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/modes/components/read-tool-group.ts +9 -0
- package/src/prompts/tools/hashline.md +15 -15
- package/src/prompts/tools/read.md +1 -0
- package/src/task/index.ts +12 -50
- package/src/task/worktree.ts +170 -239
- package/src/tools/browser/tab-supervisor.ts +13 -13
- package/src/tools/conflict-detect.ts +661 -0
- package/src/tools/index.ts +6 -0
- package/src/tools/path-utils.ts +1 -1
- package/src/tools/read.ts +130 -0
- package/src/tools/write.ts +204 -0
- package/src/utils/git.ts +5 -0
- package/src/task/isolation-backend.ts +0 -94
package/CHANGELOG.md
CHANGED
|
@@ -1,22 +1,58 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
## [14.9.9] - 2026-05-12
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added new `task.isolation.mode` values `auto`, `apfs`, `btrfs`, `zfs`, `reflink`, `overlayfs`, `projfs`, `block-clone`, and `rcopy` for native PAL-backed task isolation backends
|
|
10
|
+
- Added automatic PAL-backed isolation backend selection so `task.isolation.mode` uses the host's best-available backend
|
|
11
|
+
- Added input-token and output-token totals to `omp stats --summary`.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- Changed `task.isolation.enabled=true` migration to map to `task.isolation.mode = "auto"` instead of legacy `worktree` isolation
|
|
16
|
+
- Updated isolation configuration UI labels and descriptions to expose new back-end names (`overlayfs`, `projfs`, etc.) and removed references to deprecated values in guidance text
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Fixed worktree delta capture to include previously untracked file state by baselining untracked patches for both snapshots
|
|
21
|
+
- Fixed task isolation startup to try alternate PAL backends when the preferred one is unavailable, allowing successful fallback instead of immediate failure
|
|
22
|
+
- Mapped legacy `task.isolation.mode` values `worktree`, `fuse-overlay`, and `fuse-projfs` to their new equivalents during settings migration to preserve behavior with older configs
|
|
23
|
+
|
|
24
|
+
## [14.9.8] - 2026-05-12
|
|
25
|
+
|
|
4
26
|
### Breaking Changes
|
|
5
27
|
|
|
6
28
|
- Changed the `eval` tool input format to a single-line `*** Cell <lang>:"<title>" [t:<duration>] [rst]` header per cell, replacing the `*** Begin <LANG>` / `*** End <LANG>` envelope and the standalone `*** Title:` / `*** Timeout:` / `*** Reset` directives. The lark grammar enforces a fixed attribute order; the runtime parser remains lenient (alias keys, bare positional tokens, single-quoted titles).
|
|
7
29
|
|
|
8
30
|
### Added
|
|
9
31
|
|
|
32
|
+
- Added `:conflicts` read selector (`read <path>:conflicts`) to return a one-line index of all unresolved merge conflicts with stable `#N` IDs for quick inspection
|
|
33
|
+
- Added bulk conflict resolution with `write({ path: "conflict://*", content })` to resolve all currently registered conflicts across files in one call, expanding `@ours`/`@theirs`/`@base`/`@both` per conflict and returning per-file counts
|
|
34
|
+
- Added `read` support for `conflict://<N>` and `read conflict://<N>/<scope>` to inspect unresolved conflict regions captured by a prior read, including `ours`, `theirs`, and `base` side views with original file line alignment
|
|
35
|
+
- Added shorthand content tokens `@ours`, `@theirs`, `@both`, and `@base` to conflict-resolution writes using `path: "conflict://<N>"` so replacement content can be composed from recorded conflict sections
|
|
36
|
+
- Added conflict count metadata to read results so conflict files now show a warning badge (`⚠ N`) in the read tool UI
|
|
10
37
|
- Added support for explicit boolean `rst` values (`rst:true`, `rst:false`, `rst:1`, `rst:0`, `rst:yes`, `rst:no`, `rst:on`, `rst:off`) in `*** Cell` headers
|
|
38
|
+
- Added detection of unresolved git merge conflicts in `read` output: each marker block is registered with a session-stable id and surfaced in a footer with `ours`/`theirs` previews. Resolve a block by calling `write({ path: "conflict://<id>", content })` — the tool splices the recorded marker region (markers and all sides) with the supplied content and routes through the normal writethrough (LSP format/diagnostics, fs-cache invalidation).
|
|
11
39
|
|
|
12
40
|
### Changed
|
|
13
41
|
|
|
42
|
+
- Changed `read` conflict warning footers to show `X of Y` unresolved conflicts when a range only captures part of a file and provide a `read <path>:conflicts` hint for the full list
|
|
43
|
+
- Changed conflict scanning in conflict read paths to inspect the whole file (with a 10 MB cap) so totals better reflect hidden conflicts and truncated scans are called out
|
|
44
|
+
- Changed conflict marker scanning during `read` to only register fully formed, column-0 merge-marker blocks, so indented or malformed marker-like lines are no longer treated as conflicts
|
|
45
|
+
- Changed `write` conflict resolution to validate `conflict://` IDs and report clear errors for malformed or unknown conflict URIs
|
|
14
46
|
- Changed the HTML transcript renderer to parse the new `*** Cell` headers while keeping the older `*** Begin <LANG>` and `===== ... =====` formats renderable for historical sessions.
|
|
15
47
|
- Changed the `eval` tool parser so a stray non-marker line between cells no longer crashes with `null is not an object (evaluating 'BEGIN_RE.exec(lines[i])[1]')`; stray content is consumed without aborting parsing.
|
|
16
48
|
- Changed `*** End` to be an optional, undocumented per-cell terminator (kept in the lark to satisfy GPT-trained models' natural terminator habit during constrained sampling).
|
|
17
49
|
|
|
18
50
|
### Fixed
|
|
19
51
|
|
|
52
|
+
- Fixed single-conflict `write` retries to re-locate the recorded conflict block by exact marker content so shifted line numbers from out-of-band edits no longer prevent resolution
|
|
53
|
+
- Fixed `read conflict://*` handling by rejecting wildcard reads with a clear write-only guidance error
|
|
54
|
+
- Fixed conflict resolution to verify the live file still contains recorded `<<<<<<<` and `>>>>>>>` markers before splicing, preventing stale conflict IDs from silently corrupting out-of-band-edited files
|
|
55
|
+
- Fixed `@base` token handling so two-way conflicts without a base section now return a clear error
|
|
20
56
|
- Improved `*** Cell` header parsing to reject invalid `rst` values with a clear `invalid rst value` error
|
|
21
57
|
|
|
22
58
|
## [14.9.7] - 2026-05-12
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
4
|
-
"version": "14.9.
|
|
4
|
+
"version": "14.9.9",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/can1357/oh-my-pi",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -47,12 +47,12 @@
|
|
|
47
47
|
"@agentclientprotocol/sdk": "0.21.0",
|
|
48
48
|
"@babel/parser": "^7.29.3",
|
|
49
49
|
"@mozilla/readability": "^0.6.0",
|
|
50
|
-
"@oh-my-pi/omp-stats": "14.9.
|
|
51
|
-
"@oh-my-pi/pi-agent-core": "14.9.
|
|
52
|
-
"@oh-my-pi/pi-ai": "14.9.
|
|
53
|
-
"@oh-my-pi/pi-natives": "14.9.
|
|
54
|
-
"@oh-my-pi/pi-tui": "14.9.
|
|
55
|
-
"@oh-my-pi/pi-utils": "14.9.
|
|
50
|
+
"@oh-my-pi/omp-stats": "14.9.9",
|
|
51
|
+
"@oh-my-pi/pi-agent-core": "14.9.9",
|
|
52
|
+
"@oh-my-pi/pi-ai": "14.9.9",
|
|
53
|
+
"@oh-my-pi/pi-natives": "14.9.9",
|
|
54
|
+
"@oh-my-pi/pi-tui": "14.9.9",
|
|
55
|
+
"@oh-my-pi/pi-utils": "14.9.9",
|
|
56
56
|
"@puppeteer/browsers": "^2.13.0",
|
|
57
57
|
"@sinclair/typebox": "^0.34.49",
|
|
58
58
|
"@types/turndown": "5.0.6",
|
package/scripts/build-binary.ts
CHANGED
|
@@ -40,6 +40,17 @@ async function main(): Promise<void> {
|
|
|
40
40
|
"--root",
|
|
41
41
|
"../..",
|
|
42
42
|
"./src/cli.ts",
|
|
43
|
+
// Worker entrypoints. Bun's `--compile` discovers the literal in
|
|
44
|
+
// `new Worker("…", …)` at each spawn site, but only actually
|
|
45
|
+
// emits the worker into the bunfs root when it is listed here as
|
|
46
|
+
// an explicit additional entry. Paths are relative to this
|
|
47
|
+
// script's cwd (packages/coding-agent) and the `--root` above
|
|
48
|
+
// (../..) makes them appear inside the binary at
|
|
49
|
+
// `/$bunfs/root/packages/<pkg>/src/<worker>.js`, which is
|
|
50
|
+
// exactly what the literals at the spawn sites resolve to.
|
|
51
|
+
"../stats/src/sync-worker.ts",
|
|
52
|
+
"./src/tools/browser/tab-worker-entry.ts",
|
|
53
|
+
"./src/eval/js/worker-entry.ts",
|
|
43
54
|
"--outfile",
|
|
44
55
|
"dist/omp",
|
|
45
56
|
],
|
|
@@ -18,10 +18,11 @@ const minifiedCss = css
|
|
|
18
18
|
.replace(/\s*([{}:;,])\s*/g, "$1")
|
|
19
19
|
.trim();
|
|
20
20
|
|
|
21
|
-
// Inline everything
|
|
21
|
+
// Inline everything; use function replacements so `$'`, `$&`, `$$`, etc. inside
|
|
22
|
+
// the embedded CSS/JS are not interpreted as substitution patterns.
|
|
22
23
|
const template = html
|
|
23
|
-
.replace("<template-css/>", `<style>${minifiedCss}</style>`)
|
|
24
|
-
.replace("<template-js/>", `<script>${js}</script>`);
|
|
24
|
+
.replace("<template-css/>", () => `<style>${minifiedCss}</style>`)
|
|
25
|
+
.replace("<template-js/>", () => `<script>${js}</script>`);
|
|
25
26
|
|
|
26
27
|
// Write generated file
|
|
27
28
|
const output = `// Auto-generated by scripts/generate-template.ts - DO NOT EDIT
|
package/src/cli/stats-cli.ts
CHANGED
|
@@ -176,6 +176,8 @@ async function printStatsSummary(): Promise<void> {
|
|
|
176
176
|
console.log(` Requests: ${formatNumber(overall.totalRequests)} (${formatNumber(overall.failedRequests)} errors)`);
|
|
177
177
|
console.log(` Error Rate: ${formatPercent(overall.errorRate)}`);
|
|
178
178
|
console.log(` Total Tokens: ${formatNumber(overall.totalInputTokens + overall.totalOutputTokens)}`);
|
|
179
|
+
console.log(` Input Tokens: ${formatNumber(overall.totalInputTokens)}`);
|
|
180
|
+
console.log(` Output Tokens: ${formatNumber(overall.totalOutputTokens)}`);
|
|
179
181
|
console.log(` Cache Rate: ${formatPercent(overall.cacheRate)}`);
|
|
180
182
|
console.log(` Total Cost: ${formatCost(overall.totalCost)}`);
|
|
181
183
|
console.log(` Premium Requests: ${formatNumber(normalizePremiumRequests(overall.totalPremiumRequests ?? 0))}`);
|
package/src/cli.ts
CHANGED
|
@@ -84,8 +84,30 @@ function isSubcommand(first: string | undefined): boolean {
|
|
|
84
84
|
return commands.some(e => e.name === first || e.aliases?.includes(first));
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Smoke-test entry. Spawns the stats sync worker, pings it, exits.
|
|
89
|
+
*
|
|
90
|
+
* Purpose: catch the silent worker-load regressions that hit compiled
|
|
91
|
+
* binaries (issues #1011 and #1027). Neither `--version` nor
|
|
92
|
+
* `stats --summary` actually spawns a Worker on a fresh install — the
|
|
93
|
+
* sync path early-returns when no session files exist. This probe is the
|
|
94
|
+
* minimal end-to-end test that proves `new Worker(...)` resolves and the
|
|
95
|
+
* bundled worker module evaluates successfully. Wired into
|
|
96
|
+
* `scripts/install-tests/run-ci.sh` so binary / source-link / tarball
|
|
97
|
+
* installs all exercise it on every CI run.
|
|
98
|
+
*/
|
|
99
|
+
async function runSmokeTest(): Promise<void> {
|
|
100
|
+
const { smokeTestSyncWorker } = await import("@oh-my-pi/omp-stats");
|
|
101
|
+
await smokeTestSyncWorker();
|
|
102
|
+
process.stdout.write("smoke-test: ok\n");
|
|
103
|
+
}
|
|
104
|
+
|
|
87
105
|
/** Run the CLI with the given argv (no `process.argv` prefix). */
|
|
88
|
-
export function runCli(argv: string[]): Promise<void> {
|
|
106
|
+
export async function runCli(argv: string[]): Promise<void> {
|
|
107
|
+
if (argv[0] === "--smoke-test") {
|
|
108
|
+
await runSmokeTest();
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
89
111
|
// --help and --version are handled by run() directly, don't rewrite those.
|
|
90
112
|
// Everything else that isn't a known subcommand routes to "launch".
|
|
91
113
|
const first = argv[0];
|
|
@@ -2067,25 +2067,46 @@ export const SETTINGS_SCHEMA = {
|
|
|
2067
2067
|
// Delegation
|
|
2068
2068
|
"task.isolation.mode": {
|
|
2069
2069
|
type: "enum",
|
|
2070
|
-
values: [
|
|
2070
|
+
values: [
|
|
2071
|
+
"none",
|
|
2072
|
+
"auto",
|
|
2073
|
+
"apfs",
|
|
2074
|
+
"btrfs",
|
|
2075
|
+
"zfs",
|
|
2076
|
+
"reflink",
|
|
2077
|
+
"overlayfs",
|
|
2078
|
+
"projfs",
|
|
2079
|
+
"block-clone",
|
|
2080
|
+
"rcopy",
|
|
2081
|
+
] as const,
|
|
2071
2082
|
default: "none",
|
|
2072
2083
|
ui: {
|
|
2073
2084
|
tab: "tasks",
|
|
2074
2085
|
label: "Isolation Mode",
|
|
2075
2086
|
description:
|
|
2076
|
-
|
|
2087
|
+
'Isolation backend for subagents. "auto" lets the native PAL pick the best available backend (CoW-aware filesystems, then overlayfs/ProjFS, then a git worktree / recursive-copy fallback).',
|
|
2077
2088
|
options: [
|
|
2078
2089
|
{ value: "none", label: "None", description: "No isolation" },
|
|
2079
|
-
{ value: "
|
|
2090
|
+
{ value: "auto", label: "Auto", description: "Let the PAL pick the best available backend" },
|
|
2091
|
+
{ value: "apfs", label: "APFS", description: "macOS clonefile reflink (APFS)" },
|
|
2092
|
+
{ value: "btrfs", label: "btrfs", description: "btrfs subvolume snapshot" },
|
|
2093
|
+
{ value: "zfs", label: "ZFS", description: "ZFS snapshot + clone" },
|
|
2094
|
+
{ value: "reflink", label: "Reflink", description: "Linux FICLONE per-file reflink" },
|
|
2095
|
+
{
|
|
2096
|
+
value: "overlayfs",
|
|
2097
|
+
label: "Overlayfs",
|
|
2098
|
+
description: "Linux kernel overlay (or fuse-overlayfs fallback)",
|
|
2099
|
+
},
|
|
2100
|
+
{ value: "projfs", label: "ProjFS", description: "Windows Projected File System" },
|
|
2080
2101
|
{
|
|
2081
|
-
value: "
|
|
2082
|
-
label: "
|
|
2083
|
-
description: "
|
|
2102
|
+
value: "block-clone",
|
|
2103
|
+
label: "Block clone",
|
|
2104
|
+
description: "Windows FSCTL_DUPLICATE_EXTENTS_TO_FILE (NTFS/ReFS)",
|
|
2084
2105
|
},
|
|
2085
2106
|
{
|
|
2086
|
-
value: "
|
|
2087
|
-
label: "
|
|
2088
|
-
description: "
|
|
2107
|
+
value: "rcopy",
|
|
2108
|
+
label: "Recursive copy",
|
|
2109
|
+
description: "git worktree if available, otherwise recursive copy",
|
|
2089
2110
|
},
|
|
2090
2111
|
],
|
|
2091
2112
|
},
|
package/src/config/settings.ts
CHANGED
|
@@ -598,11 +598,28 @@ export class Settings {
|
|
|
598
598
|
const isolationObj = taskObj?.isolation as Record<string, unknown> | undefined;
|
|
599
599
|
if (isolationObj && "enabled" in isolationObj) {
|
|
600
600
|
if (typeof isolationObj.enabled === "boolean") {
|
|
601
|
-
isolationObj.mode = isolationObj.enabled ? "
|
|
601
|
+
isolationObj.mode = isolationObj.enabled ? "auto" : "none";
|
|
602
602
|
}
|
|
603
603
|
delete isolationObj.enabled;
|
|
604
604
|
}
|
|
605
605
|
|
|
606
|
+
// task.isolation.mode: legacy values from before the pi-iso PAL refactor.
|
|
607
|
+
// `worktree` was git worktree → now lives under `rcopy`. `fuse-overlay`
|
|
608
|
+
// and `fuse-projfs` are now the platform-named `overlayfs` / `projfs`
|
|
609
|
+
// kinds; the PAL falls back internally when the chosen one isn't
|
|
610
|
+
// available, so we don't need the old TS-side platform guards.
|
|
611
|
+
if (isolationObj && typeof isolationObj.mode === "string") {
|
|
612
|
+
const legacy: Record<string, string> = {
|
|
613
|
+
worktree: "rcopy",
|
|
614
|
+
"fuse-overlay": "overlayfs",
|
|
615
|
+
"fuse-projfs": "projfs",
|
|
616
|
+
};
|
|
617
|
+
const mapped = legacy[isolationObj.mode as string];
|
|
618
|
+
if (mapped !== undefined) {
|
|
619
|
+
isolationObj.mode = mapped;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
606
623
|
// edit.mode: removed "atom" variant is now "hashline"
|
|
607
624
|
const editObj = raw.edit as Record<string, unknown> | undefined;
|
|
608
625
|
if (editObj) {
|
package/src/edit/streaming.ts
CHANGED
|
@@ -287,7 +287,7 @@ const hashlineStrategy: EditStreamingStrategy<HashlineArgs> = {
|
|
|
287
287
|
sections = splitHashlineInputs(args.input, { cwd: ctx.cwd, path: args.path });
|
|
288
288
|
} catch {
|
|
289
289
|
// Single-section fallback keeps the original error rendering for the
|
|
290
|
-
// "haven't typed
|
|
290
|
+
// "haven't typed `@@ PATH` yet" case.
|
|
291
291
|
const result = await computeHashlineDiff({ input: args.input, path: args.path }, ctx.cwd, {
|
|
292
292
|
autoDropPureInsertDuplicates: ctx.hashlineAutoDropPureInsertDuplicates,
|
|
293
293
|
});
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import { logger, Snowflake } from "@oh-my-pi/pi-utils";
|
|
1
|
+
import { isCompiledBinary, logger, Snowflake } from "@oh-my-pi/pi-utils";
|
|
2
2
|
import type { ToolSession } from "../../tools";
|
|
3
3
|
import { ToolAbortError, ToolError } from "../../tools/tool-errors";
|
|
4
4
|
import { callSessionTool, type JsStatusEvent } from "./tool-bridge";
|
|
5
5
|
import { WorkerCore } from "./worker-core";
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
import jsWorkerEntryUrl from "./worker-entry.ts" with { type: "file" };
|
|
6
|
+
// Worker entry. See `tab-supervisor.ts` for the rationale behind the
|
|
7
|
+
// literal-string + `new URL(import.meta.url)` hybrid: the literal is what
|
|
8
|
+
// Bun's `--compile` bundler discovers, the `new URL` form is what makes dev
|
|
9
|
+
// runs portable across cwds. The worker is registered as an additional
|
|
10
|
+
// `--compile` entrypoint in `scripts/build-binary.ts`.
|
|
12
11
|
import type {
|
|
13
12
|
JsDisplayOutput,
|
|
14
13
|
RunErrorPayload,
|
|
@@ -344,7 +343,9 @@ async function raceWithTimeout<T>(promise: Promise<T>, timeoutMs: number, reason
|
|
|
344
343
|
|
|
345
344
|
async function spawnJsWorker(): Promise<WorkerHandle> {
|
|
346
345
|
try {
|
|
347
|
-
const worker =
|
|
346
|
+
const worker = isCompiledBinary()
|
|
347
|
+
? new Worker("./packages/coding-agent/src/eval/js/worker-entry.ts", { type: "module" })
|
|
348
|
+
: new Worker(new URL("./worker-entry.ts", import.meta.url).href, { type: "module" });
|
|
348
349
|
return wrapBunWorker(worker);
|
|
349
350
|
} catch (err) {
|
|
350
351
|
logger.warn("Bun Worker spawn failed; using inline JS eval worker (no sync-loop guard)", {
|
package/src/export/html/index.ts
CHANGED
|
@@ -103,9 +103,12 @@ async function generateHtml(sessionData: SessionData, themeName?: string): Promi
|
|
|
103
103
|
const themeVars = await generateThemeVars(themeName);
|
|
104
104
|
const sessionDataBase64 = Buffer.from(JSON.stringify(sessionData)).toBase64();
|
|
105
105
|
|
|
106
|
-
|
|
106
|
+
// Use function replacements so `$'`, `$&`, `$$`, `$n`, etc. in the
|
|
107
|
+
// substituted CSS/base64 are not interpreted as substitution patterns
|
|
108
|
+
// (see https://mdn.io/String.replace).
|
|
109
|
+
return TEMPLATE.replace("<theme-vars/>", () => `<style>:root { ${themeVars} }</style>`).replace(
|
|
107
110
|
"{{SESSION_DATA}}",
|
|
108
|
-
sessionDataBase64,
|
|
111
|
+
() => sessionDataBase64,
|
|
109
112
|
);
|
|
110
113
|
}
|
|
111
114
|
|