Merge branch 'main' into feat/cosmetic-fixes
This commit is contained in:
@@ -46,8 +46,7 @@ jobs:
|
||||
{"os":"macos-14","platform":"darwin","arch":"arm64"},
|
||||
{"os":"windows-latest","platform":"win32","arch":"x64","variants":"baseline modern"}
|
||||
]') || fromJSON('[
|
||||
{"os":"ubuntu-22.04","platform":"linux","arch":"x64","variants":"baseline modern"},
|
||||
{"os":"ubuntu-22.04","platform":"linux","arch":"arm64","target":"aarch64-unknown-linux-gnu"}
|
||||
{"os":"ubuntu-22.04","platform":"linux","arch":"x64","variants":"baseline modern"}
|
||||
]') }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
@@ -128,9 +127,36 @@ jobs:
|
||||
bun packages/coding-agent/src/cli.ts --help
|
||||
bun packages/coding-agent/src/cli.ts stats --help
|
||||
|
||||
install_methods:
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
RUSTFLAGS: -C target-cpu=x86-64-v3
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
lfs: true
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: "1.3"
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
- name: Cache bun dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.bun/install/cache
|
||||
key: bun-${{ runner.os }}-${{ hashFiles('**/bun.lock') }}
|
||||
- name: Install system deps
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev fd-find ripgrep imagemagick
|
||||
sudo ln -s $(which fdfind) /usr/local/bin/fd
|
||||
sudo ln -sf /usr/bin/convert /usr/local/bin/magick
|
||||
- run: bun install --frozen-lockfile
|
||||
- name: Install method smoke tests
|
||||
run: bash scripts/install-tests/run-ci.sh
|
||||
|
||||
release:
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
needs: [rust, native, test]
|
||||
needs: [rust, native, test, install_methods]
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
@@ -51,3 +51,6 @@ syntax.jsonl
|
||||
out.jsonl
|
||||
out.html
|
||||
pi-*.html
|
||||
|
||||
# Generated files
|
||||
packages/coding-agent/src/internal-urls/docs-index.generated.ts
|
||||
|
||||
+18
-16
@@ -1,35 +1,37 @@
|
||||
# Triage Command
|
||||
|
||||
Classify and label open GitHub issues that are missing labels.
|
||||
Classify and label **newly opened** GitHub issues that are missing labels.
|
||||
|
||||
## Arguments
|
||||
|
||||
- `$ARGUMENTS`: Scope filter — `open` (default), `closed`, or `all`. Append `--relabel` to re-classify issues that already have labels.
|
||||
- `$ARGUMENTS`: Optional window flag `--days <n>` (default: `7`). Only open issues created within this window are triaged.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Fetch Issues
|
||||
|
||||
Parse `$ARGUMENTS` to determine scope and whether `--relabel` is set.
|
||||
Parse `$ARGUMENTS` to determine the new-issue window (`--days`, default `7`).
|
||||
|
||||
```bash
|
||||
# For open (default):
|
||||
gh issue list --state open --json number,title,body,labels,comments,createdAt --limit 50
|
||||
# Build cutoff date (UTC) for "new" issues
|
||||
CUTOFF_DATE="$(python - <<'PY'
|
||||
from datetime import datetime, timedelta, timezone
|
||||
print((datetime.now(timezone.utc) - timedelta(days=7)).strftime('%Y-%m-%d'))
|
||||
PY
|
||||
)"
|
||||
|
||||
# For closed:
|
||||
gh issue list --state closed --json number,title,body,labels,comments,createdAt --limit 30
|
||||
# Fetch only newly created open issues (default 7-day window)
|
||||
gh issue list --state open --search "created:>=${CUTOFF_DATE}" --json number,title,body,labels,comments,createdAt --limit 50
|
||||
|
||||
# For all: run both commands and merge results
|
||||
```
|
||||
### 2. Filter New Candidates
|
||||
|
||||
### 2. Filter Candidates
|
||||
|
||||
- If `--relabel` is NOT set, skip any issue that already has at least one label.
|
||||
- If `--relabel` IS set, process all fetched issues.
|
||||
- Skip any issue older than the cutoff window; this command only triages new issues.
|
||||
- Skip issues with label `triaged` (already handled).
|
||||
- For remaining issues, if type + area + platform are already present, skip unless metadata is clearly missing.
|
||||
|
||||
### 3. Classify Each Issue
|
||||
|
||||
For each candidate issue, read the title, body, and **all comments** (comments often contain critical context). Apply labels from the categories below. An issue can receive multiple labels.
|
||||
For each candidate issue, read the title, body, and **all comments** (comments often contain critical context). Apply labels from the categories below. An issue can receive multiple labels. For each category, skip it only if the issue already has a label in that category — always fill in missing categories.
|
||||
|
||||
**Type labels** (pick exactly one):
|
||||
| Label | Signals |
|
||||
@@ -68,10 +70,10 @@ For each candidate issue, read the title, body, and **all comments** (comments o
|
||||
|
||||
### 4. Apply Labels
|
||||
|
||||
For each issue, apply the chosen labels. **Never remove existing labels.**
|
||||
For each issue, apply the chosen labels and add `triaged`. **Never remove existing labels.**
|
||||
|
||||
```bash
|
||||
gh issue edit <number> --add-label "bug,tui,platform:linux"
|
||||
gh issue edit <number> --add-label "bug,tui,platform:linux,triaged"
|
||||
```
|
||||
|
||||
### 5. Print Summary
|
||||
|
||||
Generated
+5
-5
@@ -98,9 +98,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.101"
|
||||
version = "1.0.102"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea"
|
||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
|
||||
[[package]]
|
||||
name = "arboard"
|
||||
@@ -2058,7 +2058,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pi-natives"
|
||||
version = "12.14.1"
|
||||
version = "12.15.0"
|
||||
dependencies = [
|
||||
"arboard",
|
||||
"brush-builtins",
|
||||
@@ -2610,9 +2610,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.116"
|
||||
version = "2.0.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb"
|
||||
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ exclude = ["crates/brush-core-vendored", "crates/brush-builtins-vendored"]
|
||||
resolver = "3"
|
||||
|
||||
[workspace.package]
|
||||
version = "12.14.1"
|
||||
version = "12.15.0"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
authors = ["Can Boluk"]
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
},
|
||||
"packages/agent": {
|
||||
"name": "@oh-my-pi/pi-agent-core",
|
||||
"version": "12.14.1",
|
||||
"version": "12.15.0",
|
||||
"dependencies": {
|
||||
"@oh-my-pi/pi-ai": "workspace:*",
|
||||
"@oh-my-pi/pi-tui": "workspace:*",
|
||||
@@ -33,7 +33,7 @@
|
||||
},
|
||||
"packages/ai": {
|
||||
"name": "@oh-my-pi/pi-ai",
|
||||
"version": "12.14.1",
|
||||
"version": "12.15.0",
|
||||
"bin": {
|
||||
"pi-ai": "./src/cli.ts",
|
||||
},
|
||||
@@ -62,7 +62,7 @@
|
||||
},
|
||||
"packages/coding-agent": {
|
||||
"name": "@oh-my-pi/pi-coding-agent",
|
||||
"version": "12.14.1",
|
||||
"version": "12.15.0",
|
||||
"bin": {
|
||||
"omp": "src/cli.ts",
|
||||
},
|
||||
@@ -98,7 +98,7 @@
|
||||
},
|
||||
"packages/natives": {
|
||||
"name": "@oh-my-pi/pi-natives",
|
||||
"version": "12.14.1",
|
||||
"version": "12.15.0",
|
||||
"dependencies": {
|
||||
"@oh-my-pi/pi-utils": "workspace:*",
|
||||
},
|
||||
@@ -132,7 +132,7 @@
|
||||
},
|
||||
"packages/stats": {
|
||||
"name": "@oh-my-pi/omp-stats",
|
||||
"version": "12.14.1",
|
||||
"version": "12.15.0",
|
||||
"bin": {
|
||||
"omp-stats": "./src/index.ts",
|
||||
},
|
||||
@@ -157,7 +157,7 @@
|
||||
},
|
||||
"packages/swarm-extension": {
|
||||
"name": "@oh-my-pi/swarm-extension",
|
||||
"version": "12.14.1",
|
||||
"version": "12.15.0",
|
||||
"bin": {
|
||||
"omp-swarm": "src/cli.ts",
|
||||
},
|
||||
@@ -170,7 +170,7 @@
|
||||
},
|
||||
"packages/tui": {
|
||||
"name": "@oh-my-pi/pi-tui",
|
||||
"version": "12.14.1",
|
||||
"version": "12.15.0",
|
||||
"dependencies": {
|
||||
"@oh-my-pi/pi-natives": "workspace:*",
|
||||
"@oh-my-pi/pi-utils": "workspace:*",
|
||||
@@ -186,7 +186,7 @@
|
||||
},
|
||||
"packages/utils": {
|
||||
"name": "@oh-my-pi/pi-utils",
|
||||
"version": "12.14.1",
|
||||
"version": "12.15.0",
|
||||
"dependencies": {
|
||||
"winston": "^3.19.0",
|
||||
"winston-daily-rotate-file": "^5.0.0",
|
||||
@@ -509,21 +509,21 @@
|
||||
|
||||
"@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="],
|
||||
|
||||
"@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260219.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260219.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260219.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260219.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260219.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260219.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260219.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260219.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-Y/mfpmpZwfwyNzBgki/wUm/pWLQM2gaL5cum6lbOv8QNZUtzcIy0wTPaS08sb4yhUSGC1jGaJEPR2FNctfeC2Q=="],
|
||||
"@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260220.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260220.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260220.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260220.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260220.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260220.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260220.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260220.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-trYXlG98/C7Q7pqnPrKo+ksXrWqWVMncCy2x0VftD2llfL99Z//g2mpB9TmzWeKgb4d1659ESvxTowCGnzMccw=="],
|
||||
|
||||
"@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20260219.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-h8gG6gE0YcxJZwxFc+JHjqCoFf/EBZ0k6znspV6+NwkyyJHMIqYiawZ7Lzu2Ka8lJDO3QxXcIdw2jKaktwW2wQ=="],
|
||||
"@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20260220.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-VZQHVaLYTpa3wfCLcFD5cfnegr4iDtzBxV6yh3tys+HYePi4TXuAqct/dmziW0cpCo/UQ2KqAPGxwVO3YMDYJA=="],
|
||||
|
||||
"@typescript/native-preview-darwin-x64": ["@typescript/native-preview-darwin-x64@7.0.0-dev.20260219.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-/tSFdSD76V4X3bX+iXecaa41KRuPMl1LQD3nsE45zZFdmDUIhvCwFRXDP+whkcdaJy8RJTALC0wWhIJ6FUmnwA=="],
|
||||
"@typescript/native-preview-darwin-x64": ["@typescript/native-preview-darwin-x64@7.0.0-dev.20260220.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-ieGtyz904rlme8YWauDpCqGbnOQQ5dzUyRKU25I7MNIaoZFf0vK5gMh3SLjHC7ayWxjrCa2ezH4ka8UmyWQcPg=="],
|
||||
|
||||
"@typescript/native-preview-linux-arm": ["@typescript/native-preview-linux-arm@7.0.0-dev.20260219.1", "", { "os": "linux", "cpu": "arm" }, "sha512-pmTGfe3VGoCmjyy9r68wurGBsoaqwYRZ8/i7cIxAJNnq1huKuXRnLnVoZm6uOzZ2GtjWFjwmXXCvcdfmy9+PvQ=="],
|
||||
"@typescript/native-preview-linux-arm": ["@typescript/native-preview-linux-arm@7.0.0-dev.20260220.1", "", { "os": "linux", "cpu": "arm" }, "sha512-wMA63N6XLAkO0Ibq1Qz2zkQHF6oLynYh5+q1YayzWxp1FOas0oJUdNgVbiWeisAT4IEI9SmYtVwJJNTm/cwrwg=="],
|
||||
|
||||
"@typescript/native-preview-linux-arm64": ["@typescript/native-preview-linux-arm64@7.0.0-dev.20260219.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-9MXFr+T5LQlXuDsKTtwFdN1lbaKSMmZzabT8Gr1C74ueun91IiJVhNNISwCWY48c28Ka6O6fwxeHjU44ee/G9Q=="],
|
||||
"@typescript/native-preview-linux-arm64": ["@typescript/native-preview-linux-arm64@7.0.0-dev.20260220.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-nlOOABSQUe6ix/KOsZS3ALZ/sg9rhtp6SKtQnXO8X4+2XUlwVYS6nIrAQ5jG4+OsCvcESttNeHhBw817uNrsuA=="],
|
||||
|
||||
"@typescript/native-preview-linux-x64": ["@typescript/native-preview-linux-x64@7.0.0-dev.20260219.1", "", { "os": "linux", "cpu": "x64" }, "sha512-EMLKx9koxTbdWFg+jaqy5MwZQ19usFug6Cw+evkCaFlDEfF5sgJ/npSRhNJLbZ451FR0eqNIKmIEA9cXlHSDuA=="],
|
||||
"@typescript/native-preview-linux-x64": ["@typescript/native-preview-linux-x64@7.0.0-dev.20260220.1", "", { "os": "linux", "cpu": "x64" }, "sha512-W7R/5ct/BGuPAmJuKFU0ZuLU2nzLA26XfoaoUHHoTLYRMMuY3LBv+7zKSUTlSGjayeWQ5KtDrfrY72LarWAXkQ=="],
|
||||
|
||||
"@typescript/native-preview-win32-arm64": ["@typescript/native-preview-win32-arm64@7.0.0-dev.20260219.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-vILjYS1EnTZoiD1SfWBtk92qpDrFP9Ln38olonMjtO7mJO6SmN78GHhGR+dyGgNTHQ7PZAxGFiQwZJgrIMWNhw=="],
|
||||
"@typescript/native-preview-win32-arm64": ["@typescript/native-preview-win32-arm64@7.0.0-dev.20260220.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-FNXTr2lS1QLUB05jWjkBVwrDierNuxKduQq9ayloENJrsC8GfG1sXkfy8R021+ozP/D1LI/CFUn3hkB2vPXTsQ=="],
|
||||
|
||||
"@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20260219.1", "", { "os": "win32", "cpu": "x64" }, "sha512-wwjOQCSViLi+mqJycnRek7GeLZXmJClcZaVYFkLRVbmIMWvWSJkafasFjnQHDinXcLiXxZYdJ+oRR/86FOt2Xw=="],
|
||||
"@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20260220.1", "", { "os": "win32", "cpu": "x64" }, "sha512-byHRyf4dOuKOADrs43tWyPhmAgqk+XrA/XOsJnGsxUKxPwots+gf9x5kg/IxTbB3QjPsVe/V9QdMl3//sUTCmQ=="],
|
||||
|
||||
"@typescript/vfs": ["@typescript/vfs@1.6.4", "", { "dependencies": { "debug": "^4.4.3" }, "peerDependencies": { "typescript": "*" } }, "sha512-PJFXFS4ZJKiJ9Qiuix6Dz/OwEIqHD7Dme1UwZhTK11vR+5dqW2ACbdndWQexBzCx+CPuMe5WBYQWCsFyGlQLlQ=="],
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
"version:major": "npm version major -ws --no-git-tag-version && bun scripts/sync-versions.ts && rm -rf node_modules packages/*/node_modules bun.lock && bun install",
|
||||
"version:set": "npm version -ws",
|
||||
"prepublishOnly": "bun run check",
|
||||
"prepare": "bun --cwd=packages/coding-agent run generate-docs-index",
|
||||
"publish": "bun run prepublishOnly && npm publish -ws --access public",
|
||||
"publish:dry": "bun run prepublishOnly && npm publish -ws --access public --dry-run",
|
||||
"release": "bun scripts/release.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@oh-my-pi/pi-agent-core",
|
||||
"version": "12.14.1",
|
||||
"version": "12.15.0",
|
||||
"description": "General-purpose agent with transport abstraction, state management, and attachment support",
|
||||
"type": "module",
|
||||
"main": "./src/index.ts",
|
||||
|
||||
@@ -2,6 +2,17 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [12.15.0] - 2026-02-20
|
||||
### Fixed
|
||||
|
||||
- Improved error messages for OAuth token refresh failures by including detailed error information from the provider
|
||||
- Separated rate limit and usage limit error handling to provide distinct user-friendly messages for ChatGPT rate limits vs subscription usage limits
|
||||
|
||||
### Changed
|
||||
|
||||
- Increased SDK retry attempts to 5 for OpenAI, Azure OpenAI, and Anthropic clients (was SDK default of 2)
|
||||
- Changed 429 retry strategy for OpenAI Codex and Google Gemini CLI to use a 5-minute time budget when the server provides a retry delay, instead of a fixed attempt cap
|
||||
|
||||
## [12.14.0] - 2026-02-19
|
||||
### Added
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@oh-my-pi/pi-ai",
|
||||
"version": "12.14.1",
|
||||
"version": "12.15.0",
|
||||
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
||||
"type": "module",
|
||||
"main": "./src/index.ts",
|
||||
|
||||
@@ -629,6 +629,7 @@ function createClient(
|
||||
baseURL: config.baseURL,
|
||||
defaultHeaders: config.defaultHeaders,
|
||||
dangerouslyAllowBrowser: config.dangerouslyAllowBrowser,
|
||||
maxRetries: 5,
|
||||
});
|
||||
|
||||
return { client, isOAuthToken: config.isOAuthToken };
|
||||
|
||||
@@ -424,6 +424,7 @@ function createClient(model: Model<"azure-openai-responses">, apiKey: string, op
|
||||
apiKey,
|
||||
apiVersion,
|
||||
dangerouslyAllowBrowser: true,
|
||||
maxRetries: 5,
|
||||
defaultHeaders: headers,
|
||||
baseURL: baseUrl,
|
||||
});
|
||||
|
||||
@@ -99,6 +99,7 @@ const MAX_RETRIES = 3;
|
||||
const BASE_DELAY_MS = 1000;
|
||||
const MAX_EMPTY_STREAM_RETRIES = 2;
|
||||
const EMPTY_STREAM_BASE_DELAY_MS = 500;
|
||||
const RATE_LIMIT_BUDGET_MS = 5 * 60 * 1000;
|
||||
const CLAUDE_THINKING_BETA_HEADER = "interleaved-thinking-2025-05-14";
|
||||
|
||||
/**
|
||||
@@ -360,8 +361,9 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
|
||||
let response: Response | undefined;
|
||||
let lastError: Error | undefined;
|
||||
let requestUrl: string | undefined;
|
||||
let rateLimitTimeSpent = 0;
|
||||
|
||||
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
||||
for (let attempt = 0; ; attempt++) {
|
||||
if (options?.signal?.aborted) {
|
||||
throw new Error("Request was aborted");
|
||||
}
|
||||
@@ -382,13 +384,25 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
|
||||
|
||||
const errorText = await response.text();
|
||||
|
||||
// Check if retryable
|
||||
if (attempt < MAX_RETRIES && isRetryableError(response.status, errorText)) {
|
||||
// Use server-provided delay or exponential backoff
|
||||
// Handle 429 rate limits with time budget
|
||||
if (response.status === 429) {
|
||||
const serverDelay = extractRetryDelay(errorText, response);
|
||||
if (serverDelay && rateLimitTimeSpent + serverDelay <= RATE_LIMIT_BUDGET_MS) {
|
||||
rateLimitTimeSpent += serverDelay;
|
||||
await abortableSleep(serverDelay, options?.signal);
|
||||
continue;
|
||||
}
|
||||
// Fallback: use exponential backoff if no server delay, up to MAX_RETRIES
|
||||
if (!serverDelay && attempt < MAX_RETRIES) {
|
||||
await abortableSleep(BASE_DELAY_MS * 2 ** attempt, options?.signal);
|
||||
continue;
|
||||
}
|
||||
} else if (attempt < MAX_RETRIES && isRetryableError(response.status, errorText)) {
|
||||
// Non-429 retryable errors use standard attempt cap
|
||||
const serverDelay = extractRetryDelay(errorText, response);
|
||||
const delayMs = serverDelay ?? BASE_DELAY_MS * 2 ** attempt;
|
||||
|
||||
// Check if server delay exceeds max allowed (default: 60s)
|
||||
// Check if server delay exceeds max allowed (default: 60s) for non-429 errors
|
||||
const maxDelayMs = options?.maxRetryDelayMs ?? 60000;
|
||||
if (maxDelayMs > 0 && serverDelay && serverDelay > maxDelayMs) {
|
||||
const delaySeconds = Math.ceil(serverDelay / 1000);
|
||||
@@ -401,7 +415,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
|
||||
continue;
|
||||
}
|
||||
|
||||
// Not retryable or max retries exceeded
|
||||
// Not retryable or budget exceeded
|
||||
throw new Error(`Cloud Code Assist API error (${response.status}): ${extractErrorMessage(errorText)}`);
|
||||
} catch (error) {
|
||||
// Check for abort - fetch throws AbortError, our code throws "Request was aborted"
|
||||
|
||||
@@ -1377,16 +1377,20 @@ function logCodexDebug(message: string, details?: Record<string, unknown>): void
|
||||
console.error(`[codex] ${message}`);
|
||||
}
|
||||
|
||||
function getRetryDelayMs(response: Response | null, attempt: number, errorBody?: string): number {
|
||||
function getRetryDelayMs(
|
||||
response: Response | null,
|
||||
attempt: number,
|
||||
errorBody?: string,
|
||||
): { delay: number; serverProvided: boolean } {
|
||||
const retryAfter = response?.headers?.get("retry-after") || null;
|
||||
if (retryAfter) {
|
||||
const seconds = Number(retryAfter);
|
||||
if (Number.isFinite(seconds)) {
|
||||
return Math.max(0, seconds * 1000);
|
||||
return { delay: Math.max(0, seconds * 1000), serverProvided: true };
|
||||
}
|
||||
const parsedDate = Date.parse(retryAfter);
|
||||
if (!Number.isNaN(parsedDate)) {
|
||||
return Math.max(0, parsedDate - Date.now());
|
||||
return { delay: Math.max(0, parsedDate - Date.now()), serverProvided: true };
|
||||
}
|
||||
}
|
||||
// Parse retry delay from error body (e.g., "Please try again in 225ms" or "Please try again in 1.5s")
|
||||
@@ -1394,28 +1398,41 @@ function getRetryDelayMs(response: Response | null, attempt: number, errorBody?:
|
||||
const msMatch = /try again in\s+(\d+(?:\.\d+)?)\s*ms/i.exec(errorBody);
|
||||
if (msMatch) {
|
||||
const ms = Number(msMatch[1]);
|
||||
if (Number.isFinite(ms)) return Math.max(ms, 100);
|
||||
if (Number.isFinite(ms)) return { delay: Math.max(ms, 100), serverProvided: true };
|
||||
}
|
||||
const sMatch = /try again in\s+(\d+(?:\.\d+)?)\s*s(?:ec)?/i.exec(errorBody);
|
||||
if (sMatch) {
|
||||
const s = Number(sMatch[1]);
|
||||
if (Number.isFinite(s)) return Math.max(s * 1000, 100);
|
||||
if (Number.isFinite(s)) return { delay: Math.max(s * 1000, 100), serverProvided: true };
|
||||
}
|
||||
}
|
||||
return CODEX_RETRY_DELAY_MS * (attempt + 1);
|
||||
return { delay: CODEX_RETRY_DELAY_MS * (attempt + 1), serverProvided: false };
|
||||
}
|
||||
/** Max total time to spend retrying 429s with server-provided delays (5 minutes). */
|
||||
const CODEX_RATE_LIMIT_BUDGET_MS = 5 * 60 * 1000;
|
||||
|
||||
async function fetchWithRetry(url: string, init: RequestInit, signal?: AbortSignal): Promise<Response> {
|
||||
let attempt = 0;
|
||||
let rateLimitTimeSpent = 0;
|
||||
while (true) {
|
||||
try {
|
||||
const response = await fetch(url, { ...init, signal: signal ?? init.signal });
|
||||
if (!CODEX_RETRYABLE_STATUS.has(response.status) || attempt >= CODEX_MAX_RETRIES) {
|
||||
if (!CODEX_RETRYABLE_STATUS.has(response.status)) {
|
||||
return response;
|
||||
}
|
||||
if (signal?.aborted) return response;
|
||||
// Read error body for retry delay parsing
|
||||
const errorBody = await response.text();
|
||||
const delay = getRetryDelayMs(response, attempt, errorBody);
|
||||
const { delay, serverProvided } = getRetryDelayMs(response, attempt, errorBody);
|
||||
// For 429s with a server-provided delay, use a time budget instead of attempt count
|
||||
if (response.status === 429 && serverProvided) {
|
||||
if (rateLimitTimeSpent + delay > CODEX_RATE_LIMIT_BUDGET_MS) {
|
||||
return response;
|
||||
}
|
||||
rateLimitTimeSpent += delay;
|
||||
} else if (attempt >= CODEX_MAX_RETRIES) {
|
||||
return response;
|
||||
}
|
||||
await abortableSleep(delay, signal);
|
||||
} catch (error) {
|
||||
if (attempt >= CODEX_MAX_RETRIES || signal?.aborted) {
|
||||
|
||||
@@ -47,11 +47,14 @@ export async function parseCodexError(response: Response): Promise<CodexErrorInf
|
||||
const resetsAt = (err as { resets_at?: number }).resets_at ?? primary.resets_at ?? secondary.resets_at;
|
||||
const mins = resetsAt ? Math.max(0, Math.round((resetsAt * 1000 - Date.now()) / 60000)) : undefined;
|
||||
|
||||
if (/usage_limit_reached|usage_not_included|rate_limit_exceeded/i.test(code) || response.status === 429) {
|
||||
if (/usage_limit_reached|usage_not_included/i.test(code)) {
|
||||
const planType = (err as { plan_type?: string }).plan_type;
|
||||
const plan = planType ? ` (${String(planType).toLowerCase()} plan)` : "";
|
||||
const when = mins !== undefined ? ` Try again in ~${mins} min.` : "";
|
||||
friendlyMessage = `You have hit your ChatGPT usage limit${plan}.${when}`.trim();
|
||||
} else if (/rate_limit_exceeded/i.test(code) || response.status === 429) {
|
||||
const when = mins !== undefined ? ` Try again in ~${mins} min.` : "";
|
||||
friendlyMessage = `ChatGPT rate limit exceeded.${when}`.trim();
|
||||
}
|
||||
|
||||
const errMessage = (err as { message?: string }).message;
|
||||
|
||||
@@ -480,6 +480,7 @@ async function createClient(
|
||||
apiKey,
|
||||
baseURL: model.baseUrl,
|
||||
dangerouslyAllowBrowser: true,
|
||||
maxRetries: 5,
|
||||
defaultHeaders: headers,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -397,6 +397,7 @@ function createClient(
|
||||
apiKey,
|
||||
baseURL: model.baseUrl,
|
||||
dangerouslyAllowBrowser: true,
|
||||
maxRetries: 5,
|
||||
defaultHeaders: headers,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -380,7 +380,7 @@ export async function getOAuthApiKey(
|
||||
if (Date.now() >= creds.expires) {
|
||||
try {
|
||||
creds = await refreshOAuthToken(provider, creds);
|
||||
} catch {
|
||||
} catch (refreshError) {
|
||||
if (provider === "perplexity") {
|
||||
const jwtExpiry = getPerplexityJwtExpiryMs(creds.access);
|
||||
if (jwtExpiry && Date.now() < jwtExpiry) {
|
||||
@@ -388,7 +388,8 @@ export async function getOAuthApiKey(
|
||||
return { newCredentials: fallbackCredentials, apiKey: fallbackCredentials.access };
|
||||
}
|
||||
}
|
||||
throw new Error(`Failed to refresh OAuth token for ${provider}`);
|
||||
const reason = refreshError instanceof Error ? refreshError.message : String(refreshError);
|
||||
throw new Error(`Failed to refresh OAuth token for ${provider}: ${reason}`);
|
||||
}
|
||||
}
|
||||
// For providers that need projectId, return JSON
|
||||
|
||||
@@ -147,7 +147,13 @@ export async function refreshOpenAICodexToken(refreshToken: string): Promise<OAu
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`OpenAI Codex token refresh failed: ${response.status}`);
|
||||
let detail = `${response.status}`;
|
||||
try {
|
||||
const body = (await response.json()) as { error?: string; error_description?: string };
|
||||
if (body.error)
|
||||
detail = `${response.status} ${body.error}${body.error_description ? `: ${body.error_description}` : ""}`;
|
||||
} catch {}
|
||||
throw new Error(`OpenAI Codex token refresh failed: ${detail}`);
|
||||
}
|
||||
|
||||
const tokenData = (await response.json()) as {
|
||||
|
||||
@@ -12,6 +12,19 @@
|
||||
- Fixed ReadToolGroupComponent to show status icon before title instead of trailing
|
||||
- Fixed bash-interactive status badge to dim only bracket characters, not the enclosed text
|
||||
|
||||
## [12.15.0] - 2026-02-20
|
||||
|
||||
### Added
|
||||
|
||||
- Added `includeDisabled` parameter to `listAuthCredentials()` to optionally retrieve disabled credentials
|
||||
- Added `disableAuthCredential()` method for soft-deleting auth credentials while preserving database records
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated browser tool prompt to bias towards `observe` over `screenshot` by default
|
||||
- Changed auth credential removal to use soft-delete (disable) instead of hard-delete when OAuth refresh fails, keeping credentials in database for audit purposes
|
||||
- Changed default value of `tools.intentTracing` setting from false to true
|
||||
|
||||
## [12.14.1] - 2026-02-19
|
||||
|
||||
### Fixed
|
||||
@@ -4864,4 +4877,4 @@ Initial public release.
|
||||
- Git branch display in footer
|
||||
- Message queueing during streaming responses
|
||||
- OAuth integration for Gmail and Google Calendar access
|
||||
- HTML export with syntax highlighting and collapsible sections
|
||||
- HTML export with syntax highlighting and collapsible sections
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@oh-my-pi/pi-coding-agent",
|
||||
"version": "12.14.1",
|
||||
"version": "12.15.0",
|
||||
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
@@ -70,7 +70,6 @@
|
||||
"files": [
|
||||
"src",
|
||||
"scripts",
|
||||
"docs",
|
||||
"examples",
|
||||
"README.md",
|
||||
"CHANGELOG.md"
|
||||
@@ -79,6 +78,7 @@
|
||||
"check": "tsgo -p tsconfig.json",
|
||||
"format-prompts": "bun scripts/format-prompts.ts",
|
||||
"generate-docs-index": "bun scripts/generate-docs-index.ts",
|
||||
"prepack": "bun scripts/generate-docs-index.ts",
|
||||
"build:binary": "cd ../.. && bun --cwd=packages/stats scripts/generate-client-bundle.ts && bun --cwd=packages/natives run embed:native && bun build --compile --define PI_COMPILED=true --root . ./packages/coding-agent/src/cli.ts --outfile packages/coding-agent/dist/omp && bun --cwd=packages/natives run embed:native --reset && bun --cwd=packages/stats scripts/generate-client-bundle.ts --reset",
|
||||
"generate-template": "bun scripts/generate-template.ts",
|
||||
"test": "bun test"
|
||||
|
||||
@@ -5,24 +5,6 @@ import * as path from "node:path";
|
||||
|
||||
const docsDir = new URL("../../../docs/", import.meta.url).pathname;
|
||||
const outputPath = new URL("../src/internal-urls/docs-index.generated.ts", import.meta.url).pathname;
|
||||
const importBase = "../../../../docs";
|
||||
|
||||
function toIdentifier(relativePath: string): string {
|
||||
const withoutExt = relativePath.replace(/\.md$/i, "");
|
||||
const parts = withoutExt
|
||||
.split(/[^a-zA-Z0-9]+/)
|
||||
.filter(Boolean)
|
||||
.map((part, index) => {
|
||||
if (index === 0) {
|
||||
return part.toLowerCase();
|
||||
}
|
||||
return part.charAt(0).toUpperCase() + part.slice(1).toLowerCase();
|
||||
});
|
||||
|
||||
const base = parts.length > 0 ? parts.join("") : "doc";
|
||||
const safeBase = /^[0-9]/.test(base) ? `doc${base}` : base;
|
||||
return `${safeBase}Md`;
|
||||
}
|
||||
|
||||
const glob = new Glob("**/*.md");
|
||||
const entries: string[] = [];
|
||||
@@ -31,26 +13,28 @@ for await (const relativePath of glob.scan(docsDir)) {
|
||||
}
|
||||
entries.sort();
|
||||
|
||||
const usedIdentifiers = new Set<string>();
|
||||
const docs = entries.map((relativePath) => {
|
||||
let identifier = toIdentifier(relativePath);
|
||||
let suffix = 2;
|
||||
while (usedIdentifiers.has(identifier)) {
|
||||
identifier = `${toIdentifier(relativePath)}${suffix}`;
|
||||
suffix++;
|
||||
}
|
||||
usedIdentifiers.add(identifier);
|
||||
return { relativePath, identifier };
|
||||
});
|
||||
docs.sort((a, b) => a.identifier.localeCompare(b.identifier) || a.relativePath.localeCompare(b.relativePath));
|
||||
const docsWithContent = await Promise.all(
|
||||
entries.map(async (relativePath) => ({
|
||||
relativePath,
|
||||
content: await Bun.file(path.join(docsDir, relativePath)).text(),
|
||||
}))
|
||||
);
|
||||
|
||||
const imports = docs
|
||||
.map(({ relativePath, identifier }) => `import ${identifier} from "${importBase}/${relativePath}" with { type: "text" };`)
|
||||
const filenamesLiteral = JSON.stringify(entries);
|
||||
|
||||
const mapEntries = docsWithContent
|
||||
.map(({ relativePath, content }) => `\t${JSON.stringify(relativePath)}: ${JSON.stringify(content)},`)
|
||||
.join("\n");
|
||||
|
||||
const mapEntries = docs.map(({ relativePath, identifier }) => `\t"${relativePath}": ${identifier},`).join("\n");
|
||||
|
||||
const output = `// Auto-generated by scripts/generate-docs-index.ts - DO NOT EDIT\n\n${imports}\n\nexport const EMBEDDED_DOCS: Readonly<Record<string, string>> = {\n${mapEntries}\n};\n\nexport const EMBEDDED_DOC_FILENAMES = Object.keys(EMBEDDED_DOCS).sort();\n`;
|
||||
const output = [
|
||||
"// Auto-generated by scripts/generate-docs-index.ts - DO NOT EDIT",
|
||||
"",
|
||||
`export const EMBEDDED_DOC_FILENAMES: readonly string[] = ${filenamesLiteral};`,
|
||||
"",
|
||||
`export const EMBEDDED_DOCS: Readonly<Record<string, string>> = {`,
|
||||
`${mapEntries}`,
|
||||
`};`,
|
||||
"",
|
||||
].join("\n");
|
||||
|
||||
await Bun.write(outputPath, output);
|
||||
console.log(`Generated ${path.relative(process.cwd(), outputPath)} (${docs.length} docs)`);
|
||||
console.log(`Generated ${path.relative(process.cwd(), outputPath)} (${entries.length} docs)`);
|
||||
|
||||
@@ -504,7 +504,7 @@ export const SETTINGS_SCHEMA = {
|
||||
},
|
||||
"tools.intentTracing": {
|
||||
type: "boolean",
|
||||
default: false,
|
||||
default: true,
|
||||
ui: {
|
||||
tab: "tools",
|
||||
label: "Intent tracing",
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
// Auto-generated by scripts/generate-docs-index.ts - DO NOT EDIT
|
||||
|
||||
import bashToolRuntimeMd from "../../../../docs/bash-tool-runtime.md" with { type: "text" };
|
||||
import blobArtifactArchitectureMd from "../../../../docs/blob-artifact-architecture.md" with { type: "text" };
|
||||
import compactionMd from "../../../../docs/compaction.md" with { type: "text" };
|
||||
import configUsageMd from "../../../../docs/config-usage.md" with { type: "text" };
|
||||
import customToolsMd from "../../../../docs/custom-tools.md" with { type: "text" };
|
||||
import environmentVariablesMd from "../../../../docs/environment-variables.md" with { type: "text" };
|
||||
import extensionLoadingMd from "../../../../docs/extension-loading.md" with { type: "text" };
|
||||
import extensionsMd from "../../../../docs/extensions.md" with { type: "text" };
|
||||
import fsScanCacheArchitectureMd from "../../../../docs/fs-scan-cache-architecture.md" with { type: "text" };
|
||||
import geminiManifestExtensionsMd from "../../../../docs/gemini-manifest-extensions.md" with { type: "text" };
|
||||
import handoffGenerationPipelineMd from "../../../../docs/handoff-generation-pipeline.md" with { type: "text" };
|
||||
import hooksMd from "../../../../docs/hooks.md" with { type: "text" };
|
||||
import mcpProtocolTransportsMd from "../../../../docs/mcp-protocol-transports.md" with { type: "text" };
|
||||
import mcpRuntimeLifecycleMd from "../../../../docs/mcp-runtime-lifecycle.md" with { type: "text" };
|
||||
import mcpServerToolAuthoringMd from "../../../../docs/mcp-server-tool-authoring.md" with { type: "text" };
|
||||
import modelsMd from "../../../../docs/models.md" with { type: "text" };
|
||||
import nativesAddonLoaderRuntimeMd from "../../../../docs/natives-addon-loader-runtime.md" with { type: "text" };
|
||||
import nativesArchitectureMd from "../../../../docs/natives-architecture.md" with { type: "text" };
|
||||
import nativesBindingContractMd from "../../../../docs/natives-binding-contract.md" with { type: "text" };
|
||||
import nativesBuildReleaseDebuggingMd from "../../../../docs/natives-build-release-debugging.md" with { type: "text" };
|
||||
import nativesMediaSystemUtilsMd from "../../../../docs/natives-media-system-utils.md" with { type: "text" };
|
||||
import nativesRustTaskCancellationMd from "../../../../docs/natives-rust-task-cancellation.md" with { type: "text" };
|
||||
import nativesShellPtyProcessMd from "../../../../docs/natives-shell-pty-process.md" with { type: "text" };
|
||||
import nativesTextSearchPipelineMd from "../../../../docs/natives-text-search-pipeline.md" with { type: "text" };
|
||||
import nonCompactionRetryPolicyMd from "../../../../docs/non-compaction-retry-policy.md" with { type: "text" };
|
||||
import notebookToolRuntimeMd from "../../../../docs/notebook-tool-runtime.md" with { type: "text" };
|
||||
import pluginManagerInstallerPlumbingMd from "../../../../docs/plugin-manager-installer-plumbing.md" with { type: "text" };
|
||||
import portingFromPiMonoMd from "../../../../docs/porting-from-pi-mono.md" with { type: "text" };
|
||||
import portingToNativesMd from "../../../../docs/porting-to-natives.md" with { type: "text" };
|
||||
import providerStreamingInternalsMd from "../../../../docs/provider-streaming-internals.md" with { type: "text" };
|
||||
import pythonReplMd from "../../../../docs/python-repl.md" with { type: "text" };
|
||||
import rpcMd from "../../../../docs/rpc.md" with { type: "text" };
|
||||
import rulebookMatchingPipelineMd from "../../../../docs/rulebook-matching-pipeline.md" with { type: "text" };
|
||||
import sdkMd from "../../../../docs/sdk.md" with { type: "text" };
|
||||
import secretsMd from "../../../../docs/secrets.md" with { type: "text" };
|
||||
import sessionMd from "../../../../docs/session.md" with { type: "text" };
|
||||
import sessionOperationsExportShareForkResumeMd from "../../../../docs/session-operations-export-share-fork-resume.md" with { type: "text" };
|
||||
import sessionSwitchingAndRecentListingMd from "../../../../docs/session-switching-and-recent-listing.md" with { type: "text" };
|
||||
import sessionTreePlanMd from "../../../../docs/session-tree-plan.md" with { type: "text" };
|
||||
import skillsMd from "../../../../docs/skills.md" with { type: "text" };
|
||||
import slashCommandInternalsMd from "../../../../docs/slash-command-internals.md" with { type: "text" };
|
||||
import taskAgentDiscoveryMd from "../../../../docs/task-agent-discovery.md" with { type: "text" };
|
||||
import themeMd from "../../../../docs/theme.md" with { type: "text" };
|
||||
import treeMd from "../../../../docs/tree.md" with { type: "text" };
|
||||
import ttsrInjectionLifecycleMd from "../../../../docs/ttsr-injection-lifecycle.md" with { type: "text" };
|
||||
import tuiMd from "../../../../docs/tui.md" with { type: "text" };
|
||||
import tuiRuntimeInternalsMd from "../../../../docs/tui-runtime-internals.md" with { type: "text" };
|
||||
|
||||
export const EMBEDDED_DOCS: Readonly<Record<string, string>> = {
|
||||
"bash-tool-runtime.md": bashToolRuntimeMd,
|
||||
"blob-artifact-architecture.md": blobArtifactArchitectureMd,
|
||||
"compaction.md": compactionMd,
|
||||
"config-usage.md": configUsageMd,
|
||||
"custom-tools.md": customToolsMd,
|
||||
"environment-variables.md": environmentVariablesMd,
|
||||
"extension-loading.md": extensionLoadingMd,
|
||||
"extensions.md": extensionsMd,
|
||||
"fs-scan-cache-architecture.md": fsScanCacheArchitectureMd,
|
||||
"gemini-manifest-extensions.md": geminiManifestExtensionsMd,
|
||||
"handoff-generation-pipeline.md": handoffGenerationPipelineMd,
|
||||
"hooks.md": hooksMd,
|
||||
"mcp-protocol-transports.md": mcpProtocolTransportsMd,
|
||||
"mcp-runtime-lifecycle.md": mcpRuntimeLifecycleMd,
|
||||
"mcp-server-tool-authoring.md": mcpServerToolAuthoringMd,
|
||||
"models.md": modelsMd,
|
||||
"natives-addon-loader-runtime.md": nativesAddonLoaderRuntimeMd,
|
||||
"natives-architecture.md": nativesArchitectureMd,
|
||||
"natives-binding-contract.md": nativesBindingContractMd,
|
||||
"natives-build-release-debugging.md": nativesBuildReleaseDebuggingMd,
|
||||
"natives-media-system-utils.md": nativesMediaSystemUtilsMd,
|
||||
"natives-rust-task-cancellation.md": nativesRustTaskCancellationMd,
|
||||
"natives-shell-pty-process.md": nativesShellPtyProcessMd,
|
||||
"natives-text-search-pipeline.md": nativesTextSearchPipelineMd,
|
||||
"non-compaction-retry-policy.md": nonCompactionRetryPolicyMd,
|
||||
"notebook-tool-runtime.md": notebookToolRuntimeMd,
|
||||
"plugin-manager-installer-plumbing.md": pluginManagerInstallerPlumbingMd,
|
||||
"porting-from-pi-mono.md": portingFromPiMonoMd,
|
||||
"porting-to-natives.md": portingToNativesMd,
|
||||
"provider-streaming-internals.md": providerStreamingInternalsMd,
|
||||
"python-repl.md": pythonReplMd,
|
||||
"rpc.md": rpcMd,
|
||||
"rulebook-matching-pipeline.md": rulebookMatchingPipelineMd,
|
||||
"sdk.md": sdkMd,
|
||||
"secrets.md": secretsMd,
|
||||
"session.md": sessionMd,
|
||||
"session-operations-export-share-fork-resume.md": sessionOperationsExportShareForkResumeMd,
|
||||
"session-switching-and-recent-listing.md": sessionSwitchingAndRecentListingMd,
|
||||
"session-tree-plan.md": sessionTreePlanMd,
|
||||
"skills.md": skillsMd,
|
||||
"slash-command-internals.md": slashCommandInternalsMd,
|
||||
"task-agent-discovery.md": taskAgentDiscoveryMd,
|
||||
"theme.md": themeMd,
|
||||
"tree.md": treeMd,
|
||||
"ttsr-injection-lifecycle.md": ttsrInjectionLifecycleMd,
|
||||
"tui.md": tuiMd,
|
||||
"tui-runtime-internals.md": tuiRuntimeInternalsMd,
|
||||
};
|
||||
|
||||
export const EMBEDDED_DOC_FILENAMES = Object.keys(EMBEDDED_DOCS).sort();
|
||||
@@ -123,27 +123,54 @@ export class RpcClient {
|
||||
stdin: "pipe",
|
||||
});
|
||||
|
||||
// Process lines in background
|
||||
// Wait for the "ready" signal or process exit
|
||||
const { promise: readyPromise, resolve: readyResolve, reject: readyReject } = Promise.withResolvers<void>();
|
||||
let readySettled = false;
|
||||
|
||||
// Process lines in background, intercepting the ready signal
|
||||
const lines = readJsonl(this.#process.stdout, this.#abortController.signal);
|
||||
void (async () => {
|
||||
for await (const line of lines) {
|
||||
if (!readySettled && isRecord(line) && line.type === "ready") {
|
||||
readySettled = true;
|
||||
readyResolve();
|
||||
continue;
|
||||
}
|
||||
this.#handleLine(line);
|
||||
}
|
||||
})().catch(() => {});
|
||||
// Stream ended without ready signal — process exited
|
||||
if (!readySettled) {
|
||||
readySettled = true;
|
||||
readyReject(new Error(`Agent process exited before ready. Stderr: ${this.#process?.peekStderr() ?? ""}`));
|
||||
}
|
||||
})().catch((err: Error) => {
|
||||
if (!readySettled) {
|
||||
readySettled = true;
|
||||
readyReject(err);
|
||||
}
|
||||
});
|
||||
|
||||
// Wait a moment for process to initialize
|
||||
await Bun.sleep(100);
|
||||
|
||||
try {
|
||||
const exitCode = await Promise.race([this.#process.exited, Bun.sleep(500).then(() => null)]);
|
||||
if (exitCode !== null) {
|
||||
throw new Error(
|
||||
`Agent process exited immediately with code ${exitCode}. Stderr: ${this.#process.peekStderr()}`,
|
||||
// Also race against process exit (in case stdout closes before we read it)
|
||||
void this.#process.exited.then((exitCode: number) => {
|
||||
if (!readySettled) {
|
||||
readySettled = true;
|
||||
readyReject(
|
||||
new Error(`Agent process exited with code ${exitCode}. Stderr: ${this.#process?.peekStderr() ?? ""}`),
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
// Process still running, which is what we want
|
||||
}
|
||||
});
|
||||
|
||||
// Timeout to prevent hanging forever
|
||||
void Bun.sleep(30000).then(() => {
|
||||
if (!readySettled) {
|
||||
readySettled = true;
|
||||
readyReject(
|
||||
new Error(`Timeout waiting for agent to become ready. Stderr: ${this.#process?.peekStderr() ?? ""}`),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
await readyPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -36,6 +36,8 @@ export type {
|
||||
* Listens for JSON commands on stdin, outputs events and responses on stdout.
|
||||
*/
|
||||
export async function runRpcMode(session: AgentSession): Promise<never> {
|
||||
// Signal to RPC clients that the server is ready to accept commands
|
||||
process.stdout.write(`${JSON.stringify({ type: "ready" })}\n`);
|
||||
const output = (obj: RpcResponse | RpcExtensionUIRequest | object) => {
|
||||
process.stdout.write(`${JSON.stringify(obj)}\n`);
|
||||
};
|
||||
|
||||
@@ -663,10 +663,8 @@ export function applyHashlineEdits(
|
||||
if (edit.content.length === 0) {
|
||||
throw new Error('Insert-between edit (src "A#HH.. B#HH..") requires non-empty dst');
|
||||
}
|
||||
if (edit.before.line !== edit.after.line + 1) {
|
||||
throw new Error(
|
||||
`insert requires adjacent anchors (after ${edit.after.line}, before ${edit.before.line})`,
|
||||
);
|
||||
if (edit.before.line <= edit.after.line) {
|
||||
throw new Error(`insert requires after (${edit.after.line}) < before (${edit.before.line})`);
|
||||
}
|
||||
const afterValid = validateRef(edit.after);
|
||||
const beforeValid = validateRef(edit.before);
|
||||
|
||||
@@ -6,21 +6,28 @@ Use this tool to navigate, click, type, scroll, drag, query DOM content, and cap
|
||||
- Use `action: "open"` to start a new headless browser session (or implicitly launch on first action)
|
||||
- Use `action: "goto"` with `url` to navigate
|
||||
- Use `action: "observe"` to capture a numbered accessibility snapshot with URL/title/viewport/scroll info
|
||||
- Prefer `click_id`, `type_id`, or `fill_id` actions using the returned `element_id` values
|
||||
- Optional flags: `include_all` to include non-interactive nodes, `viewport_only` to limit to visible elements
|
||||
- Prefer `click_id`, `type_id`, or `fill_id` actions using the returned `element_id` values
|
||||
- Optional flags: `include_all` to include non-interactive nodes, `viewport_only` to limit to visible elements
|
||||
- Use `action: "click"`, `"type"`, `"fill"`, `"press"`, `"scroll"`, or `"drag"` for selector-based interactions
|
||||
- Prefer ARIA or text selectors (e.g. `p-aria/[name="Sign in"]`, `p-text/Continue`) over brittle CSS
|
||||
- Prefer ARIA or text selectors (e.g. `p-aria/[name="Sign in"]`, `p-text/Continue`) over brittle CSS
|
||||
- Use `action: "click_id"`, `"type_id"`, or `"fill_id"` to interact with observed elements without selectors
|
||||
- Use `action: "wait_for_selector"` before interacting when the page is dynamic
|
||||
- Use `action: "evaluate"` with `script` to run a JavaScript expression in the page context
|
||||
- Use `action: "get_text"`, `"get_html"`, or `"get_attribute"` for DOM queries
|
||||
- For batch queries, pass `args: [{ selector, attribute? }]` to get an array of results (attribute required for `get_attribute`)
|
||||
- For batch queries, pass `args: [{ selector, attribute? }]` to get an array of results (attribute required for `get_attribute`)
|
||||
- Use `action: "extract_readable"` to return reader-mode content (title/byline/excerpt/text or markdown)
|
||||
- Set `format` to `"markdown"` (default) or `"text"`
|
||||
- Set `format` to `"markdown"` (default) or `"text"`
|
||||
- Use `action: "screenshot"` to capture images (optionally with `selector` to capture a single element)
|
||||
- Use `action: "close"` to release the browser when done
|
||||
</instruction>
|
||||
|
||||
<critical>
|
||||
**Default to `observe`, not `screenshot`.**
|
||||
- `observe` is cheaper, faster, and returns structured data — use it to understand page state, find elements, and plan interactions.
|
||||
- Only use `screenshot` when visual appearance matters (verifying layout, debugging CSS, capturing a visual artifact for the user).
|
||||
- Never screenshot just to "see what's on the page" — `observe` gives you that with element IDs you can act on immediately.
|
||||
</critical>
|
||||
|
||||
<output>
|
||||
Returns text output for navigation and DOM queries, and image output for screenshots. Screenshots can optionally be saved to disk via the `path` parameter.
|
||||
</output>
|
||||
@@ -37,7 +37,7 @@ export interface StoredAuthCredential {
|
||||
}
|
||||
|
||||
/** Bump when schema changes require migration */
|
||||
const SCHEMA_VERSION = 3;
|
||||
const SCHEMA_VERSION = 4;
|
||||
|
||||
/**
|
||||
* Type guard for plain objects.
|
||||
@@ -124,11 +124,14 @@ export class AgentStorage {
|
||||
#deleteExpiredCacheStmt: Statement;
|
||||
#listAuthStmt: Statement;
|
||||
#listAuthByProviderStmt: Statement;
|
||||
#listActiveAuthStmt: Statement;
|
||||
#listActiveAuthByProviderStmt: Statement;
|
||||
#insertAuthStmt: Statement;
|
||||
#updateAuthStmt: Statement;
|
||||
#deleteAuthStmt: Statement;
|
||||
#deleteAuthByProviderStmt: Statement;
|
||||
#countAuthStmt: Statement;
|
||||
#disableAuthStmt: Statement;
|
||||
#upsertModelUsageStmt: Statement;
|
||||
#listModelUsageStmt: Statement;
|
||||
#modelUsageCache: string[] | null = null;
|
||||
@@ -165,6 +168,12 @@ export class AgentStorage {
|
||||
this.#listAuthByProviderStmt = this.#db.prepare(
|
||||
"SELECT id, provider, credential_type, data FROM auth_credentials WHERE provider = ? ORDER BY id ASC",
|
||||
);
|
||||
this.#listActiveAuthStmt = this.#db.prepare(
|
||||
"SELECT id, provider, credential_type, data FROM auth_credentials WHERE disabled = 0 ORDER BY id ASC",
|
||||
);
|
||||
this.#listActiveAuthByProviderStmt = this.#db.prepare(
|
||||
"SELECT id, provider, credential_type, data FROM auth_credentials WHERE provider = ? AND disabled = 0 ORDER BY id ASC",
|
||||
);
|
||||
this.#insertAuthStmt = this.#db.prepare(
|
||||
"INSERT INTO auth_credentials (provider, credential_type, data) VALUES (?, ?, ?) RETURNING id",
|
||||
);
|
||||
@@ -173,6 +182,9 @@ export class AgentStorage {
|
||||
);
|
||||
this.#deleteAuthStmt = this.#db.prepare("DELETE FROM auth_credentials WHERE id = ?");
|
||||
this.#deleteAuthByProviderStmt = this.#db.prepare("DELETE FROM auth_credentials WHERE provider = ?");
|
||||
this.#disableAuthStmt = this.#db.prepare(
|
||||
"UPDATE auth_credentials SET disabled = 1, updated_at = unixepoch() WHERE id = ?",
|
||||
);
|
||||
this.#countAuthStmt = this.#db.prepare("SELECT COUNT(*) as count FROM auth_credentials");
|
||||
|
||||
this.#upsertModelUsageStmt = this.#db.prepare(
|
||||
@@ -198,6 +210,7 @@ CREATE TABLE IF NOT EXISTS auth_credentials (
|
||||
provider TEXT NOT NULL,
|
||||
credential_type TEXT NOT NULL,
|
||||
data TEXT NOT NULL,
|
||||
disabled INTEGER NOT NULL DEFAULT 0,
|
||||
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
||||
updated_at INTEGER NOT NULL DEFAULT (unixepoch())
|
||||
);
|
||||
@@ -281,10 +294,21 @@ CREATE TABLE settings (
|
||||
current: versionRow.version,
|
||||
expected: SCHEMA_VERSION,
|
||||
});
|
||||
this.#migrateSchema(versionRow.version);
|
||||
}
|
||||
this.#db.prepare("INSERT OR REPLACE INTO schema_version(version) VALUES (?)").run(SCHEMA_VERSION);
|
||||
}
|
||||
|
||||
#migrateSchema(fromVersion: number): void {
|
||||
if (fromVersion < 4) {
|
||||
// v3 → v4: Add disabled column to auth_credentials
|
||||
const cols = this.#db.prepare("PRAGMA table_info(auth_credentials)").all() as Array<{ name?: string }>;
|
||||
if (!cols.some(c => c.name === "disabled")) {
|
||||
this.#db.exec("ALTER TABLE auth_credentials ADD COLUMN disabled INTEGER NOT NULL DEFAULT 0");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns singleton instance for the given database path, creating if needed.
|
||||
* Retries on SQLITE_BUSY with exponential backoff.
|
||||
@@ -429,14 +453,19 @@ CREATE TABLE settings (
|
||||
|
||||
/**
|
||||
* Lists auth credentials, optionally filtered by provider.
|
||||
* Only returns active (non-disabled) credentials by default.
|
||||
* @param provider - Optional provider name to filter by
|
||||
* @param includeDisabled - If true, includes disabled credentials
|
||||
* @returns Array of stored credentials with their database IDs
|
||||
*/
|
||||
listAuthCredentials(provider?: string): StoredAuthCredential[] {
|
||||
const rows =
|
||||
(provider
|
||||
? (this.#listAuthByProviderStmt.all(provider) as AuthRow[])
|
||||
: (this.#listAuthStmt.all() as AuthRow[])) ?? [];
|
||||
listAuthCredentials(provider?: string, includeDisabled = false): StoredAuthCredential[] {
|
||||
const rows = includeDisabled
|
||||
? ((provider
|
||||
? (this.#listAuthByProviderStmt.all(provider) as AuthRow[])
|
||||
: (this.#listAuthStmt.all() as AuthRow[])) ?? [])
|
||||
: ((provider
|
||||
? (this.#listActiveAuthByProviderStmt.all(provider) as AuthRow[])
|
||||
: (this.#listActiveAuthStmt.all() as AuthRow[])) ?? []);
|
||||
|
||||
const results: StoredAuthCredential[] = [];
|
||||
for (const row of rows) {
|
||||
@@ -498,6 +527,19 @@ CREATE TABLE settings (
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables an auth credential by ID (soft-delete).
|
||||
* Disabled credentials are excluded from normal listing but remain in the database.
|
||||
* @param id - Database row ID of the credential to disable
|
||||
*/
|
||||
disableAuthCredential(id: number): void {
|
||||
try {
|
||||
this.#disableAuthStmt.run(id);
|
||||
} catch (error) {
|
||||
logger.warn("AgentStorage disableAuthCredential failed", { id, error: String(error) });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all auth credentials for a provider.
|
||||
* @param provider - Provider name whose credentials should be deleted
|
||||
|
||||
@@ -547,13 +547,14 @@ export class AuthStorage {
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes credential at index (used when OAuth refresh fails).
|
||||
* Cleans up provider entry if last credential removed.
|
||||
* Disables credential at index (used when OAuth refresh fails).
|
||||
* The credential remains in the database but is excluded from active queries.
|
||||
* Cleans up provider entry if last credential disabled.
|
||||
*/
|
||||
#removeCredentialAt(provider: string, index: number): void {
|
||||
const entries = this.#getStoredCredentials(provider);
|
||||
if (index < 0 || index >= entries.length) return;
|
||||
this.storage.deleteAuthCredential(entries[index].id);
|
||||
this.storage.disableAuthCredential(entries[index].id);
|
||||
const updated = entries.filter((_value, idx) => idx !== index);
|
||||
this.#setStoredCredentials(provider, updated);
|
||||
this.#resetProviderAssignments(provider);
|
||||
@@ -1339,7 +1340,7 @@ export class AuthStorage {
|
||||
// Keep credentials for transient errors (network, 5xx) and block temporarily
|
||||
const isDefinitiveFailure =
|
||||
/invalid_grant|invalid_token|revoked|unauthorized|expired.*refresh|refresh.*expired/i.test(errorMsg) ||
|
||||
(/401|403/.test(errorMsg) && !/timeout|network|fetch failed|ECONNREFUSED/i.test(errorMsg));
|
||||
(/\b(401|403)\b/.test(errorMsg) && !/timeout|network|fetch failed|ECONNREFUSED/i.test(errorMsg));
|
||||
|
||||
logger.warn("OAuth token refresh failed", {
|
||||
provider,
|
||||
|
||||
@@ -379,6 +379,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
||||
status: "running",
|
||||
task,
|
||||
description: options.description,
|
||||
lastIntent: undefined,
|
||||
recentTools: [],
|
||||
recentOutput: [],
|
||||
toolCount: 0,
|
||||
@@ -645,14 +646,19 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
||||
}
|
||||
break;
|
||||
|
||||
case "tool_execution_start":
|
||||
case "tool_execution_start": {
|
||||
progress.toolCount++;
|
||||
progress.currentTool = event.toolName;
|
||||
progress.currentToolArgs = extractToolArgsPreview(
|
||||
(event as { toolArgs?: Record<string, unknown> }).toolArgs || event.args || {},
|
||||
);
|
||||
progress.currentToolStartMs = now;
|
||||
const intent = event.intent?.trim();
|
||||
if (intent) {
|
||||
progress.lastIntent = intent;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "tool_execution_end": {
|
||||
if (progress.currentTool) {
|
||||
@@ -1174,6 +1180,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
||||
agentSource: agent.source,
|
||||
task,
|
||||
description: options.description,
|
||||
lastIntent: progress.lastIntent,
|
||||
exitCode,
|
||||
output: truncatedOutput,
|
||||
stderr,
|
||||
|
||||
@@ -535,8 +535,9 @@ function renderAgentProgress(
|
||||
if (progress.status === "running") {
|
||||
if (progress.currentTool) {
|
||||
let toolLine = `${continuePrefix}${theme.tree.hook} ${theme.fg("muted", progress.currentTool)}`;
|
||||
if (progress.currentToolArgs) {
|
||||
toolLine += `: ${theme.fg("dim", truncateToWidth(progress.currentToolArgs, 40))}`;
|
||||
const toolDetail = progress.lastIntent ?? progress.currentToolArgs;
|
||||
if (toolDetail) {
|
||||
toolLine += `: ${theme.fg("dim", truncateToWidth(replaceTabs(toolDetail), 40))}`;
|
||||
}
|
||||
if (progress.currentToolStartMs) {
|
||||
const elapsed = Date.now() - progress.currentToolStartMs;
|
||||
@@ -549,8 +550,9 @@ function renderAgentProgress(
|
||||
// Show most recent completed tool when idle between tools
|
||||
const recent = progress.recentTools[0];
|
||||
let toolLine = `${continuePrefix}${theme.tree.hook} ${theme.fg("dim", recent.tool)}`;
|
||||
if (recent.args) {
|
||||
toolLine += `: ${theme.fg("dim", truncateToWidth(recent.args, 40))}`;
|
||||
const toolDetail = progress.lastIntent ?? recent.args;
|
||||
if (toolDetail) {
|
||||
toolLine += `: ${theme.fg("dim", truncateToWidth(replaceTabs(toolDetail), 40))}`;
|
||||
}
|
||||
lines.push(toolLine);
|
||||
}
|
||||
|
||||
@@ -140,6 +140,7 @@ export interface AgentProgress {
|
||||
status: "pending" | "running" | "completed" | "failed" | "aborted";
|
||||
task: string;
|
||||
description?: string;
|
||||
lastIntent?: string;
|
||||
currentTool?: string;
|
||||
currentToolArgs?: string;
|
||||
currentToolStartMs?: number;
|
||||
@@ -161,6 +162,7 @@ export interface SingleResult {
|
||||
agentSource: AgentSource;
|
||||
task: string;
|
||||
description?: string;
|
||||
lastIntent?: string;
|
||||
exitCode: number;
|
||||
output: string;
|
||||
stderr: string;
|
||||
|
||||
@@ -842,11 +842,19 @@ describe("applyHashlineEdits — errors", () => {
|
||||
expect(() => applyHashlineEdits(content, edits)).toThrow();
|
||||
});
|
||||
|
||||
it("rejects insert with non-adjacent anchors", () => {
|
||||
it("inserts with non-adjacent anchors (before the 'before' anchor)", () => {
|
||||
const content = "aaa\nbbb\nccc";
|
||||
const edits: HashlineEdit[] = [
|
||||
{ op: "insert", after: makeTag(1, "aaa"), before: makeTag(3, "ccc"), content: ["NEW"] },
|
||||
];
|
||||
expect(() => applyHashlineEdits(content, edits)).toThrow(/adjacent anchors/);
|
||||
const result = applyHashlineEdits(content, edits);
|
||||
expect(result.content).toBe("aaa\nbbb\nNEW\nccc");
|
||||
});
|
||||
it("rejects insert with reversed anchors (before <= after)", () => {
|
||||
const content = "aaa\nbbb\nccc";
|
||||
const edits: HashlineEdit[] = [
|
||||
{ op: "insert", after: makeTag(3, "ccc"), before: makeTag(1, "aaa"), content: ["NEW"] },
|
||||
];
|
||||
expect(() => applyHashlineEdits(content, edits)).toThrow(/after.*<.*before/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import * as path from "node:path";
|
||||
import { RpcClient } from "@oh-my-pi/pi-coding-agent/modes/rpc/rpc-client";
|
||||
|
||||
describe("RpcClient.start", () => {
|
||||
test("rejects when RPC process exits immediately", async () => {
|
||||
using client = new RpcClient({
|
||||
cliPath: path.join(import.meta.dir, "..", "src", "cli.ts"),
|
||||
cwd: path.join(import.meta.dir, ".."),
|
||||
provider: "__missing_provider__",
|
||||
model: "claude-sonnet-4-5",
|
||||
env: { PI_NO_TITLE: "1" },
|
||||
});
|
||||
|
||||
await expect(client.start()).rejects.toThrow(/Unknown provider.*__missing_provider__/);
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@oh-my-pi/pi-natives",
|
||||
"version": "12.14.1",
|
||||
"version": "12.15.0",
|
||||
"description": "Native Rust functionality via N-API",
|
||||
"keywords": ["napi", "rust", "native", "grep", "text-processing"],
|
||||
"type": "module",
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,699 @@
|
||||
# Edit Benchmark Report
|
||||
|
||||
## Configuration
|
||||
|
||||
| Setting | Value |
|
||||
|---------|-------|
|
||||
| Date | 2026-02-20T10:39:39.638Z |
|
||||
| Model | p-anthropic/p-anthropic/claude-sonnet-4-5 |
|
||||
| Thinking Level | low |
|
||||
| Runs per task | 1 |
|
||||
| Edit Variant | hashline |
|
||||
| Edit Fuzzy | auto |
|
||||
| Edit Fuzzy Threshold | auto |
|
||||
| Guided Mode | no |
|
||||
| Max Attempts | 1 |
|
||||
| Max Turns | unset |
|
||||
| No-op Retry Limit | 2 |
|
||||
| Mutation Scope Window | 20 |
|
||||
| Require Edit Tool | no |
|
||||
| Require Read Tool | no |
|
||||
| No-Edit Baseline | no |
|
||||
|
||||
## Summary
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Total Tasks | 80 |
|
||||
| Total Runs | 80 |
|
||||
| Successful Runs | 64 |
|
||||
| **Task Success Rate** | **80.0% (64/80)** |
|
||||
| Verified Rate | 80.0% (64/80) |
|
||||
| Edit Tool Usage Rate | 100.0% (80/80) |
|
||||
| **Edit Success Rate** | **100.0%** |
|
||||
| Timeout Runs | 0 |
|
||||
| Mutation Intent Match Rate | 82.5% |
|
||||
| Patch Failure Rate | 0.0% (0/80) |
|
||||
| Tasks All Passing | 64 |
|
||||
| Tasks Flaky/Failing | 16 |
|
||||
|
||||
### Tool Calls
|
||||
|
||||
| Tool | Total | Avg/Run |
|
||||
|------|-------|---------|
|
||||
| Read | 166 | 2.1 |
|
||||
| Edit | 80 | 1.0 |
|
||||
| Write | 0 | 0.0 |
|
||||
| **Tool Input Chars** | 20,655 | 258 |
|
||||
|
||||
### Tokens & Time
|
||||
|
||||
| Metric | Total | Avg/Run |
|
||||
|--------|-------|---------|
|
||||
| Input Tokens | 3,762 | 47 |
|
||||
| Output Tokens | 129,239 | 1,615 |
|
||||
| Total Tokens | 3,778,048 | 47,226 |
|
||||
| Duration | 2721.2s | 34.0s |
|
||||
| **Avg Indent Score** | — | **0.03** |
|
||||
|
||||
## Task Results
|
||||
|
||||
| Task | File | Success | Edit Hit | R/E/W | Tokens (In/Out) | Time | Indent |
|
||||
|------|------|---------|----------|-------|-----------------|------|--------|
|
||||
| Access Remove Optional Chain 001 | registerDevToolsEventLogger.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/1,868 | 35.8s | 0.00 |
|
||||
| Access Remove Optional Chain 002 | index.js | 0/1 ❌ | 100.0% | 2/1/0 | 46/1,272 | 26.6s | 0.00 |
|
||||
| Access Remove Optional Chain 003 | canvas.js | 0/1 ❌ | 100.0% | 2/1/0 | 46/1,365 | 31.0s | 0.00 |
|
||||
| Access Remove Optional Chain 004 | index.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/1,165 | 24.3s | 0.00 |
|
||||
| Call Swap Call Args 001 | FallbackCompositionState.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/1,014 | 20.4s | 0.00 |
|
||||
| Call Swap Call Args 002 | ReactFlightDOMClientBrowser.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/1,070 | 22.3s | 0.00 |
|
||||
| Call Swap Call Args 003 | ReactDOMEventListener.js | 1/1 ✅ | 100.0% | 2/1/0 | 47/1,134 | 24.5s | 0.00 |
|
||||
| Call Swap Call Args 004 | ReactFlightDOMServerEdge.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/966 | 24.2s | 0.00 |
|
||||
| Duplicate Duplicate Line Flip 001 | ReactFlightClientDevToolsHook.js | 1/1 ✅ | 100.0% | 2/1/0 | 47/1,520 | 29.2s | 0.00 |
|
||||
| Duplicate Duplicate Line Flip 002 | ReactFizzConfigMarkup.js | 1/1 ✅ | 100.0% | 2/1/0 | 47/1,058 | 31.0s | 0.00 |
|
||||
| Duplicate Duplicate Line Flip 003 | ActivityList.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/1,203 | 42.9s | 0.00 |
|
||||
| Duplicate Duplicate Line Flip 004 | RunReactCompiler.ts | 1/1 ✅ | 100.0% | 2/1/0 | 46/1,338 | 32.9s | 0.00 |
|
||||
| Identifier Identifier Multi Edit 001 | ReactDOMUpdatePriority.js | 1/1 ✅ | 100.0% | 2/1/0 | 47/1,058 | 21.1s | 0.00 |
|
||||
| Identifier Identifier Multi Edit 002 | ReactDOMSelect.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/1,243 | 23.4s | 0.00 |
|
||||
| Identifier Identifier Multi Edit 003 | ReactFlightDOMClientEdge.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/1,007 | 21.8s | 0.00 |
|
||||
| Identifier Identifier Multi Edit 004 | ErrorBoundary.js | 1/1 ✅ | 100.0% | 3/1/0 | 58/1,110 | 23.2s | 0.00 |
|
||||
| Import Swap Named Imports 001 | SettingsModal.js | 0/1 ❌ | 100.0% | 2/1/0 | 46/1,675 | 32.4s | 0.00 |
|
||||
| Import Swap Named Imports 002 | index.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/1,424 | 28.8s | 0.00 |
|
||||
| Import Swap Named Imports 003 | ReactDOMInput.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/2,758 | 54.1s | 0.00 |
|
||||
| Import Swap Named Imports 004 | Element.js | 0/1 ❌ | 100.0% | 2/1/0 | 46/5,232 | 94.3s | 0.00 |
|
||||
| Literal Flip Boolean 001 | DevToolsFeatureFlags.core-fb.js | 1/1 ✅ | 100.0% | 2/1/0 | 47/727 | 16.9s | 0.00 |
|
||||
| Literal Flip Boolean 002 | ReactDOMSelection.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/971 | 32.0s | 0.00 |
|
||||
| Literal Flip Boolean 003 | DOMPropertyOperations.js | 0/1 ❌ | 100.0% | 2/1/0 | 47/856 | 22.5s | 0.00 |
|
||||
| Literal Flip Boolean 004 | SuspenseRects.js | 0/1 ❌ | 100.0% | 2/1/0 | 46/5,745 | 110.2s | 0.00 |
|
||||
| Literal Off By One 001 | AutoSizeInput.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/971 | 21.0s | 0.00 |
|
||||
| Literal Off By One 002 | ReactFlightDOMClientNode.js | 1/1 ✅ | 100.0% | 2/1/0 | 47/1,273 | 25.6s | 0.00 |
|
||||
| Literal Off By One 003 | simulateBrowserEventDispatch.js | 1/1 ✅ | 100.0% | 2/1/0 | 47/1,068 | 27.0s | 0.00 |
|
||||
| Literal Off By One 004 | ReactFlightDOMClientBrowser.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/1,334 | 29.9s | 0.00 |
|
||||
| Operator Remove Negation 001 | InspectedElementStateTree.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/935 | 22.0s | 0.00 |
|
||||
| Operator Remove Negation 002 | messageHandlers.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/852 | 28.6s | 0.00 |
|
||||
| Operator Remove Negation 003 | BeforeInputEventPlugin.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/1,617 | 38.9s | 0.00 |
|
||||
| Operator Remove Negation 004 | ReactFiberComponentStack.js | 1/1 ✅ | 100.0% | 3/1/0 | 59/1,754 | 36.8s | 0.00 |
|
||||
| Operator Swap Arithmetic 001 | id-generator.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/924 | 19.5s | 0.00 |
|
||||
| Operator Swap Arithmetic 002 | ReactCompiler.ts | 1/1 ✅ | 100.0% | 2/1/0 | 46/1,102 | 22.4s | 0.00 |
|
||||
| Operator Swap Arithmetic 003 | ReactFlightDOMServerEdge.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/1,051 | 22.5s | 0.00 |
|
||||
| Operator Swap Arithmetic 004 | ReactFlightPerformanceTrack.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/1,021 | 25.1s | 0.00 |
|
||||
| Operator Swap Comparison 001 | useSmartTooltip.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/897 | 21.8s | 0.00 |
|
||||
| Operator Swap Comparison 002 | ReactFlightHooks.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/1,036 | 22.7s | 0.00 |
|
||||
| Operator Swap Comparison 003 | ReactFlightDOMServerBrowser.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/1,421 | 30.3s | 0.00 |
|
||||
| Operator Swap Comparison 004 | ReactDOMInput.js | 1/1 ✅ | 100.0% | 2/1/0 | 47/1,142 | 25.1s | 0.00 |
|
||||
| Operator Swap Equality 001 | backend.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/935 | 20.2s | 0.00 |
|
||||
| Operator Swap Equality 002 | SnapshotSelector.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/1,148 | 25.4s | 0.00 |
|
||||
| Operator Swap Equality 003 | ReactFiberTransition.js | 1/1 ✅ | 100.0% | 2/1/0 | 47/1,227 | 30.2s | 0.00 |
|
||||
| Operator Swap Equality 004 | ReactFlightDOMClientEdge.js | 1/1 ✅ | 100.0% | 2/1/0 | 47/1,289 | 24.8s | 0.00 |
|
||||
| Operator Swap Increment Decrement 001 | ReactFlightClientConfigTargetTurbopackServer.js | 1/1 ✅ | 100.0% | 2/1/0 | 45/899 | 23.7s | 0.00 |
|
||||
| Operator Swap Increment Decrement 002 | ReactFlightWebpackNodeRegister.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/999 | 20.1s | 0.00 |
|
||||
| Operator Swap Increment Decrement 003 | ReactChildren.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/930 | 23.8s | 0.00 |
|
||||
| Operator Swap Increment Decrement 004 | ReactFlightClientConfigBundlerWebpack.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/1,375 | 28.3s | 0.00 |
|
||||
| Operator Swap Logical 001 | withPermissionsCheck.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/1,086 | 23.6s | 0.00 |
|
||||
| Operator Swap Logical 002 | ReactFlightClientConfigBundlerWebpackBrowser.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/1,257 | 28.8s | 0.00 |
|
||||
| Operator Swap Logical 003 | ReactFlightDOMClientBrowser.js | 1/1 ✅ | 100.0% | 2/1/0 | 47/997 | 21.8s | 0.00 |
|
||||
| Operator Swap Logical 004 | ReactFlightDOMServerEdge.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/1,157 | 24.0s | 0.00 |
|
||||
| Operator Swap Nullish 001 | colors.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/1,014 | 21.9s | 0.00 |
|
||||
| Operator Swap Nullish 002 | ReactFlightServerConfigTurbopackBundler.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/1,279 | 24.2s | 0.00 |
|
||||
| Operator Swap Nullish 003 | hookNamesCache.js | 0/1 ❌ | 100.0% | 2/1/0 | 46/3,602 | 68.8s | 0.00 |
|
||||
| Operator Swap Nullish 004 | RunReactCompiler.ts | 1/1 ✅ | 100.0% | 2/1/0 | 46/1,232 | 25.2s | 0.00 |
|
||||
| Regex Swap Regex Quantifier 001 | SimpleValues.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/1,352 | 28.3s | 0.00 |
|
||||
| Regex Swap Regex Quantifier 002 | utils.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/1,441 | 34.2s | 0.00 |
|
||||
| Regex Swap Regex Quantifier 003 | ReactFlightStackConfigV8.js | 0/1 ❌ | 100.0% | 2/1/0 | 46/1,927 | 42.5s | 0.00 |
|
||||
| Regex Swap Regex Quantifier 004 | ReactChildren.js | 1/1 ✅ | 100.0% | 2/1/0 | 47/1,497 | 31.2s | 0.00 |
|
||||
| Structural Delete Statement 001 | geometry.js | 1/1 ✅ | 100.0% | 2/1/0 | 45/1,535 | 30.3s | 0.00 |
|
||||
| Structural Delete Statement 002 | SidebarEventInfo.js | 1/1 ✅ | 100.0% | 2/1/0 | 47/1,358 | 29.4s | 0.00 |
|
||||
| Structural Delete Statement 003 | ReactFlightDOMServerEdge.js | 0/1 ❌ | 100.0% | 2/1/0 | 46/969 | 28.6s | 0.00 |
|
||||
| Structural Delete Statement 004 | ReactFlightDOMServerNode.js | 1/1 ✅ | 100.0% | 4/1/0 | 70/1,974 | 45.9s | 0.00 |
|
||||
| Structural Remove Early Return 001 | formatWithStyles.js | 0/1 ❌ | 100.0% | 2/1/0 | 47/1,283 | 34.5s | 0.00 |
|
||||
| Structural Remove Early Return 002 | ReactFlightClientConfigBundlerWebpackBrowser.js | 0/1 ❌ | 100.0% | 3/1/0 | 55/1,320 | 31.3s | 0.00 |
|
||||
| Structural Remove Early Return 003 | standalone.js | 0/1 ❌ | 100.0% | 2/1/0 | 46/1,090 | 26.0s | 2.00 |
|
||||
| Structural Remove Early Return 004 | backend.js | 0/1 ❌ | 100.0% | 2/1/0 | 46/1,619 | 33.9s | 0.00 |
|
||||
| Structural Swap Adjacent Lines 001 | ReactSuspenseTestUtils.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/1,025 | 19.7s | 0.00 |
|
||||
| Structural Swap Adjacent Lines 002 | NativeEventsView.js | 1/1 ✅ | 100.0% | 2/1/0 | 47/1,041 | 33.0s | 0.00 |
|
||||
| Structural Swap Adjacent Lines 003 | SyntheticEvent.js | 0/1 ❌ | 100.0% | 2/1/0 | 45/20,183 | 331.8s | 0.00 |
|
||||
| Structural Swap Adjacent Lines 004 | ReactDOMComponentTree.js | 0/1 ❌ | 100.0% | 2/1/0 | 47/2,515 | 47.8s | 0.00 |
|
||||
| Structural Swap If Else 001 | resolveBoxStyle.js | 0/1 ❌ | 100.0% | 2/1/0 | 46/1,761 | 33.0s | 0.00 |
|
||||
| Structural Swap If Else 002 | ReactCacheImpl.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/1,172 | 23.7s | 0.00 |
|
||||
| Structural Swap If Else 003 | CSSPropertyOperations.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/1,690 | 34.4s | 0.00 |
|
||||
| Structural Swap If Else 004 | ResponderTouchHistoryStore.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/1,107 | 23.3s | 0.00 |
|
||||
| Unicode Unicode Hyphen 001 | ReactFlightClientConfig.dom-bun.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/755 | 18.7s | 0.00 |
|
||||
| Unicode Unicode Hyphen 002 | backendManager.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/858 | 21.6s | 0.00 |
|
||||
| Unicode Unicode Hyphen 003 | ReactFlightDOMClientBrowser.js | 1/1 ✅ | 100.0% | 3/1/0 | 56/1,011 | 31.8s | 0.00 |
|
||||
| Unicode Unicode Hyphen 004 | ReactFlightActionServer.js | 1/1 ✅ | 100.0% | 2/1/0 | 46/1,153 | 26.3s | 0.00 |
|
||||
|
||||
## Category Summary
|
||||
|
||||
| Category | Runs | Verified | Edit Used | Success | Min/Avg/Max Difficulty |
|
||||
|----------|------|----------|-----------|---------|------------------------|
|
||||
| access | 4 | 50.0% (2/4) | 100.0% (4/4) | 50.0% (2/4) | 6 / 8.0 / 10 |
|
||||
| call | 4 | 100.0% (4/4) | 100.0% (4/4) | 100.0% (4/4) | 5 / 9.3 / 15 |
|
||||
| duplicate | 4 | 100.0% (4/4) | 100.0% (4/4) | 100.0% (4/4) | 9 / 12.0 / 16 |
|
||||
| identifier | 4 | 100.0% (4/4) | 100.0% (4/4) | 100.0% (4/4) | 8 / 9.0 / 10 |
|
||||
| import | 4 | 50.0% (2/4) | 100.0% (4/4) | 50.0% (2/4) | 2 / 4.3 / 8 |
|
||||
| literal | 8 | 75.0% (6/8) | 100.0% (8/8) | 75.0% (6/8) | 4 / 8.8 / 12 |
|
||||
| operator | 28 | 96.4% (27/28) | 100.0% (28/28) | 96.4% (27/28) | 2 / 7.9 / 14 |
|
||||
| regex | 4 | 75.0% (3/4) | 100.0% (4/4) | 75.0% (3/4) | 2 / 6.3 / 8 |
|
||||
| structural | 16 | 50.0% (8/16) | 100.0% (16/16) | 50.0% (8/16) | 4 / 7.9 / 13 |
|
||||
| unicode | 4 | 100.0% (4/4) | 100.0% (4/4) | 100.0% (4/4) | 4 / 7.0 / 8 |
|
||||
|
||||
## Mutation Summary
|
||||
|
||||
| Mutation | Category | Runs | Verified | Edit Used | Success |
|
||||
|----------|----------|------|----------|-----------|---------|
|
||||
| delete-statement | structural | 4 | 75.0% (3/4) | 100.0% (4/4) | 75.0% (3/4) |
|
||||
| duplicate-line-flip | duplicate | 4 | 100.0% (4/4) | 100.0% (4/4) | 100.0% (4/4) |
|
||||
| flip-boolean | literal | 4 | 50.0% (2/4) | 100.0% (4/4) | 50.0% (2/4) |
|
||||
| identifier-multi-edit | identifier | 4 | 100.0% (4/4) | 100.0% (4/4) | 100.0% (4/4) |
|
||||
| off-by-one | literal | 4 | 100.0% (4/4) | 100.0% (4/4) | 100.0% (4/4) |
|
||||
| remove-early-return | structural | 4 | 0.0% (0/4) | 100.0% (4/4) | 0.0% (0/4) |
|
||||
| remove-negation | operator | 4 | 100.0% (4/4) | 100.0% (4/4) | 100.0% (4/4) |
|
||||
| remove-optional-chain | access | 4 | 50.0% (2/4) | 100.0% (4/4) | 50.0% (2/4) |
|
||||
| swap-adjacent-lines | structural | 4 | 50.0% (2/4) | 100.0% (4/4) | 50.0% (2/4) |
|
||||
| swap-arithmetic | operator | 4 | 100.0% (4/4) | 100.0% (4/4) | 100.0% (4/4) |
|
||||
| swap-call-args | call | 4 | 100.0% (4/4) | 100.0% (4/4) | 100.0% (4/4) |
|
||||
| swap-comparison | operator | 4 | 100.0% (4/4) | 100.0% (4/4) | 100.0% (4/4) |
|
||||
| swap-equality | operator | 4 | 100.0% (4/4) | 100.0% (4/4) | 100.0% (4/4) |
|
||||
| swap-if-else | structural | 4 | 75.0% (3/4) | 100.0% (4/4) | 75.0% (3/4) |
|
||||
| swap-increment-decrement | operator | 4 | 100.0% (4/4) | 100.0% (4/4) | 100.0% (4/4) |
|
||||
| swap-logical | operator | 4 | 100.0% (4/4) | 100.0% (4/4) | 100.0% (4/4) |
|
||||
| swap-named-imports | import | 4 | 50.0% (2/4) | 100.0% (4/4) | 50.0% (2/4) |
|
||||
| swap-nullish | operator | 4 | 75.0% (3/4) | 100.0% (4/4) | 75.0% (3/4) |
|
||||
| swap-regex-quantifier | regex | 4 | 75.0% (3/4) | 100.0% (4/4) | 75.0% (3/4) |
|
||||
| unicode-hyphen | unicode | 4 | 100.0% (4/4) | 100.0% (4/4) | 100.0% (4/4) |
|
||||
|
||||
## Difficulty Summary
|
||||
|
||||
| Difficulty Score | Runs | Verified | Edit Used | Success |
|
||||
|------------------|------|----------|-----------|---------|
|
||||
| 0-2 | 4 | 75.0% (3/4) | 100.0% (4/4) | 75.0% (3/4) |
|
||||
| 3-5 | 17 | 76.5% (13/17) | 100.0% (17/17) | 76.5% (13/17) |
|
||||
| 6-8 | 26 | 84.6% (22/26) | 100.0% (26/26) | 84.6% (22/26) |
|
||||
| 9+ | 33 | 78.8% (26/33) | 100.0% (33/33) | 78.8% (26/33) |
|
||||
|
||||
## Failed Tasks (0% passing)
|
||||
|
||||
### Access Remove Optional Chain 002 (index.js) — 0/1
|
||||
|
||||
**All runs failed with same error:** File mismatch for index.js
|
||||
|
||||
**Sample agent response (run 1):**
|
||||
```
|
||||
Fixed. The optional chaining operator has been restored on line 192, changing:
|
||||
```javascript
|
||||
ports[registeredTabId].extension.postMessage({ event });
|
||||
```
|
||||
to:
|
||||
```javascript
|
||||
ports[registeredTabId].extension?.postMessage({ event });
|
||||
```
|
||||
|
||||
This is the only change made, matching the pattern used elsewhere in the file for safe property access.
|
||||
```
|
||||
|
||||
**Diff (expected vs actual):**
|
||||
```diff
|
||||
@@ -163 @@
|
||||
}
|
||||
|
||||
chrome.runtime.onMessage.addListener((message, sender) => {
|
||||
- switch (message?.source) {
|
||||
+ switch (message.source) {
|
||||
case 'devtools-page': {
|
||||
handleDevToolsPageMessage(message);
|
||||
break;
|
||||
@@ -189 @@
|
||||
const event =
|
||||
activeTabId === numericRegisteredTabId ? 'resumeElementPolling' : 'pauseElementPolling';
|
||||
|
||||
- ports[registeredTabId].extension.postMessage({ event });
|
||||
+ ports[registeredTabId].extension?.postMessage({ event });
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Access Remove Optional Chain 003 (canvas.js) — 0/1
|
||||
|
||||
**All runs failed with same error:** File mismatch for canvas.js
|
||||
|
||||
**Sample agent response (run 1):**
|
||||
```
|
||||
Fixed. Line 205 now correctly uses optional chaining (`canvas?.parentNode`) to handle the case where Flow loses track of canvas nullability, as indicated by the comment on line 204.
|
||||
```
|
||||
|
||||
**Diff (expected vs actual):**
|
||||
```diff
|
||||
@@ -101 @@
|
||||
if (!rect) return;
|
||||
const key = `${rect.left},${rect.top}`;
|
||||
if (!positionGroups.has(key)) positionGroups.set(key, []);
|
||||
- positionGroups.get(key)?.push({ rect, color, displayName, count });
|
||||
+ positionGroups.get(key).push({ rect, color, displayName, count });
|
||||
});
|
||||
|
||||
return Array.from(positionGroups.values()).sort((groupA, groupB) => {
|
||||
@@ -202 @@
|
||||
}
|
||||
|
||||
// $FlowFixMe[incompatible-use]: Flow doesn't recognize Popover API and loses canvas nullability tracking
|
||||
- if (canvas.parentNode != null) {
|
||||
+ if (canvas?.parentNode != null) {
|
||||
// $FlowFixMe[incompatible-call]: Flow doesn't track that canvas is non-null here
|
||||
canvas.parentNode.removeChild(canvas);
|
||||
}
|
||||
```
|
||||
|
||||
### Import Swap Named Imports 001 (SettingsModal.js) — 0/1
|
||||
|
||||
**All runs failed with same error:** File mismatch for SettingsModal.js
|
||||
|
||||
**Sample agent response (run 1):**
|
||||
```
|
||||
Fixed. The two imports `useMemo` and `useEffect` on line 11 have been swapped to their correct order. The line now reads:
|
||||
|
||||
```javascript
|
||||
import { useCallback, useContext, useRef, useEffect, useMemo } from 'react';
|
||||
```
|
||||
|
||||
Only this single line was modified, swapping only those two named imports.
|
||||
```
|
||||
|
||||
**Diff (expected vs actual):**
|
||||
```diff
|
||||
@@ -8 @@
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
-import { useCallback, useContext, useEffect, useMemo, useRef } from 'react';
|
||||
+import { useCallback, useContext, useRef, useEffect, useMemo } from 'react';
|
||||
import { SettingsModalContext } from './SettingsModalContext';
|
||||
import Button from '../Button';
|
||||
import ButtonIcon from '../ButtonIcon';
|
||||
```
|
||||
|
||||
### Import Swap Named Imports 004 (Element.js) — 0/1
|
||||
|
||||
**All runs failed with same error:** File mismatch for Element.js
|
||||
|
||||
**Sample agent response (run 1):**
|
||||
```
|
||||
Fixed. The bug was in the destructuring assignment on lines 129-130 where `isNavigatingWithKeyboard` and `treeFocused` were swapped. They are now in the correct order: `treeFocused` comes before `isNavigatingWithKeyboard` in the destructuring.
|
||||
```
|
||||
|
||||
**Diff (expected vs actual):**
|
||||
```diff
|
||||
@@ -12 @@
|
||||
import Store from 'react-devtools-shared/src/devtools/store';
|
||||
import { ElementTypeActivity } from 'react-devtools-shared/src/frontend/types';
|
||||
import ButtonIcon from '../ButtonIcon';
|
||||
-import { TreeDispatcherContext, TreeStateContext } from './TreeContext';
|
||||
+import { TreeStateContext, TreeDispatcherContext } from './TreeContext';
|
||||
import { StoreContext } from '../context';
|
||||
import { useSubscription } from '../hooks';
|
||||
import { logEvent } from 'react-devtools-shared/src/Logger';
|
||||
@@ -126 @@
|
||||
nameProp,
|
||||
compiledWithForget,
|
||||
} = element;
|
||||
- const { isNavigatingWithKeyboard, onElementMouseEnter, treeFocused, calculateElementOffset } =
|
||||
+ const { treeFocused, onElementMouseEnter, isNavigatingWithKeyboard, calculateElementOffset } =
|
||||
data;
|
||||
|
||||
const isSelected = inspectedElementID === id;
|
||||
```
|
||||
|
||||
### Literal Flip Boolean 003 (DOMPropertyOperations.js) — 0/1
|
||||
|
||||
**All runs failed with same error:** File mismatch for DOMPropertyOperations.js
|
||||
|
||||
**Sample agent response (run 1):**
|
||||
```
|
||||
Fixed. The bug was on line 81 where `return true;` should have been `return false;`. When the value is an empty string and the expected value is `false`, the function should return `false`, not its inverse.
|
||||
```
|
||||
|
||||
**Diff (expected vs actual):**
|
||||
```diff
|
||||
@@ -77 @@
|
||||
}
|
||||
const value = node.getAttribute(name);
|
||||
|
||||
- if (value === '' && expected === true) {
|
||||
- return true;
|
||||
+ if (value === '' && expected === false) {
|
||||
+ return false;
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
```
|
||||
|
||||
### Literal Flip Boolean 004 (SuspenseRects.js) — 0/1
|
||||
|
||||
**All runs failed with same error:** File mismatch for SuspenseRects.js
|
||||
|
||||
**Sample agent response (run 1):**
|
||||
```
|
||||
Perfect! The bug has been fixed. The `ScaledRect` component at line 556 was missing the required `visible` and `suspended` props. These are mandatory according to the component's type definition (lines 36-45), and their absence would cause the `data-visible` and `data-suspended` DOM attributes to be undefined, potentially breaking CSS styling.
|
||||
|
||||
The fix adds:
|
||||
- `visible={true}` - the outline is visible when rendered
|
||||
- `suspended={false}` - it's an outline overlay, not suspended content
|
||||
|
||||
The ch...
|
||||
```
|
||||
|
||||
**Diff (expected vs actual):**
|
||||
```diff
|
||||
@@ -558 @@
|
||||
styles.SuspenseRectOutline + ' ' + getClassNameForEnvironment(selectedEnvironment)
|
||||
}
|
||||
rect={selectedBoundingBox}
|
||||
- adjust={true}
|
||||
+ visible={true}
|
||||
+ suspended={false}
|
||||
+ adjust={false}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
```
|
||||
|
||||
### Operator Swap Nullish 003 (hookNamesCache.js) — 0/1
|
||||
|
||||
**All runs failed with same error:** File mismatch for hookNamesCache.js
|
||||
|
||||
**Sample agent response (run 1):**
|
||||
```
|
||||
Fixed. The nullish coalescing operator on line 132 had its operands swapped. Changed from:
|
||||
```javascript
|
||||
inspected_element_number_of_hooks: resolvedHookNames?.size ?? null,
|
||||
```
|
||||
|
||||
to:
|
||||
```javascript
|
||||
inspected_element_number_of_hooks: null ?? resolvedHookNames?.size,
|
||||
```
|
||||
```
|
||||
|
||||
**Diff (expected vs actual):**
|
||||
```diff
|
||||
@@ -92 @@
|
||||
},
|
||||
|
||||
// Optional property used by Timeline:
|
||||
- displayName: `Loading hook names for ${element.displayName || 'Unknown'}`,
|
||||
+ displayName: `Loading hook names for ${element.displayName ?? 'Unknown'}`,
|
||||
};
|
||||
|
||||
let timeoutID: $FlowFixMe | null;
|
||||
@@ -129 @@
|
||||
event_status: status,
|
||||
duration_ms: durationMs,
|
||||
inspected_element_display_name: element.displayName,
|
||||
- inspected_element_number_of_hooks: resolvedHookNames?.size ?? null,
|
||||
+ inspected_element_number_of_hooks: null ?? resolvedHookNames?.size,
|
||||
});
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Regex Swap Regex Quantifier 003 (ReactFlightStackConfigV8.js) — 0/1
|
||||
|
||||
**All runs failed with same error:** File mismatch for ReactFlightStackConfigV8.js
|
||||
|
||||
**Sample agent response (run 1):**
|
||||
```
|
||||
Fixed. The regex quantifier on line 129 was changed from `{3}` to `{4}` to correctly match V8's stack trace format, which uses 4 spaces before "at" (as shown in the comments above and the stack generation code on line 119).
|
||||
```
|
||||
|
||||
**Diff (expected vs actual):**
|
||||
```diff
|
||||
@@ -126 @@
|
||||
// at filename:0:0
|
||||
// at async filename:0:0
|
||||
const frameRegExp =
|
||||
- /^ {3} at (?:(.+) \((?:(.+):(\d+):(\d+)|\<anonymous\>)\)|(?:async )?(.+):(\d+):(\d+)|\<anonymous\>)$/;
|
||||
+ /^ {4} at (?:(.+) \((?:(.+):(\d*):(\d+)|\<anonymous\>)\)|(?:async )?(.+):(\d+):(\d+)|\<anonymous\>)$/;
|
||||
|
||||
// DEV-only cache of parsed and filtered stack frames.
|
||||
const stackTraceCache: WeakMap<Error, ReactStackTrace> = __DEV__ ? new WeakMap() : (null: any);
|
||||
```
|
||||
|
||||
### Structural Delete Statement 003 (ReactFlightDOMServerEdge.js) — 0/1
|
||||
|
||||
**All runs failed with same error:** File mismatch for ReactFlightDOMServerEdge.js
|
||||
|
||||
**Sample agent response (run 1):**
|
||||
```
|
||||
Fixed. The critical missing statement was `const messages = stringBuffer.split('\n');` which declares the `messages` variable before it's used in the for loop. The fix has been applied at line 82.
|
||||
```
|
||||
|
||||
**Diff (expected vs actual):**
|
||||
```diff
|
||||
@@ -78 @@
|
||||
stringBuffer += done
|
||||
? readFinalStringChunk(stringDecoder, new Uint8Array(0))
|
||||
: readPartialStringChunk(stringDecoder, buffer);
|
||||
+
|
||||
const messages = stringBuffer.split('\n');
|
||||
for (let i = 0; i < messages.length - 1; i++) {
|
||||
resolveDebugMessage(request, messages[i]);
|
||||
```
|
||||
|
||||
### Structural Remove Early Return 001 (formatWithStyles.js) — 0/1
|
||||
|
||||
**All runs failed with same error:** File mismatch for formatWithStyles.js
|
||||
|
||||
**Sample agent response (run 1):**
|
||||
```
|
||||
Fixed. The missing guard clause has been restored at lines 35-37. It checks if the first argument is a string containing `%c` and returns the input args unmodified, matching the algorithm described in the file's comments. The fix adds exactly 3 lines with no other modifications.
|
||||
```
|
||||
|
||||
**Diff (expected vs actual):**
|
||||
```diff
|
||||
@@ -30 @@
|
||||
inputArgs: $ReadOnlyArray<any>,
|
||||
style?: string,
|
||||
): $ReadOnlyArray<any> {
|
||||
- if (
|
||||
- inputArgs === undefined ||
|
||||
- inputArgs === null ||
|
||||
- inputArgs.length === 0 ||
|
||||
- // Matches any of %c but not %%c
|
||||
- (typeof inputArgs[0] === 'string' && inputArgs[0].match(/([^%]|^)(%c)/g)) ||
|
||||
- style === undefined
|
||||
- ) {
|
||||
- return inputArgs;
|
||||
- }
|
||||
-
|
||||
// Matches any of %(o|O|d|i|s|f), but not %%(o|O|d|i|s|f)
|
||||
const REGEXP = /([^%]|^)((%%)*)(%([oOdisf]))/g;
|
||||
// Matches any of %(o|O|d|i|s|f), but not %%(o|O|d|i|s|f)
|
||||
const REGEXP = /([^%]|^)((%%)*)(%([oOdisf]))/g;
|
||||
+ if (typeof inputArgs[0] === 'string' && inputArgs[0].includes('%c')) {
|
||||
+ return inputArgs;
|
||||
+ }
|
||||
if (typeof inputArgs[0] === 'string' && inputArgs[0].match(REGEXP)) {
|
||||
return [`%c${inputArgs[0]}`, style, ...inputArgs.slice(1)];
|
||||
} else {
|
||||
```
|
||||
|
||||
### Structural Remove Early Return 002 (ReactFlightClientConfigBundlerWebpackBrowser.js) — 0/1
|
||||
|
||||
**All runs failed with same error:** File mismatch for ReactFlightClientConfigBundlerWebpackBrowser.js
|
||||
|
||||
**Sample agent response (run 1):**
|
||||
```
|
||||
Perfect. I've restored the missing guard clause in the webpack chunk filename function. The fix adds the exact 3-line pattern between the `flightChunk` declaration and the fallback return:
|
||||
|
||||
```javascript
|
||||
if (flightChunk !== undefined) {
|
||||
return flightChunk;
|
||||
}
|
||||
```
|
||||
|
||||
This ensures that if a chunk from Flight is found in the map, it's returned immediately rather than being ignored and always falling through to the webpack runtime.
|
||||
```
|
||||
|
||||
**Diff (expected vs actual):**
|
||||
```diff
|
||||
@@ -18 @@
|
||||
const webpackGetChunkFilename = __webpack_require__.u;
|
||||
__webpack_require__.u = function (chunkId: string) {
|
||||
const flightChunk = chunkMap.get(chunkId);
|
||||
+
|
||||
if (flightChunk !== undefined) {
|
||||
return flightChunk;
|
||||
}
|
||||
```
|
||||
|
||||
### Structural Remove Early Return 003 (standalone.js) — 0/1
|
||||
|
||||
**All runs failed with same error:** File mismatch for standalone.js
|
||||
|
||||
**Sample agent response (run 1):**
|
||||
```
|
||||
Fixed. The missing guard clause has been restored to `fetchFileWithCaching`. The function now checks if the resource is already cached and returns it immediately, avoiding redundant network requests.
|
||||
```
|
||||
|
||||
**Diff (expected vs actual):**
|
||||
```diff
|
||||
@@ -122 @@
|
||||
// between devtools_page and dedicated content script for it, see `fetchFileWithCaching.js`.
|
||||
async function fetchFileWithCaching(url: string) {
|
||||
if (resourceCache.has(url)) {
|
||||
- return Promise.resolve(resourceCache.get(url));
|
||||
+ return resourceCache.get(url);
|
||||
}
|
||||
}
|
||||
-
|
||||
return fetch(url)
|
||||
.then((data) => data.text())
|
||||
.then((content) => {
|
||||
@@ -178 @@
|
||||
<div class="box-content">
|
||||
Only one copy of DevTools can be used at a time.
|
||||
</div>
|
||||
- </div>
|
||||
+ </div>
|
||||
`;
|
||||
} else {
|
||||
node.innerHTML = `
|
||||
@@ -189 @@
|
||||
<div class="box-content">
|
||||
${message}
|
||||
</div>
|
||||
- </div>
|
||||
+ </div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Structural Remove Early Return 004 (backend.js) — 0/1
|
||||
|
||||
**All runs failed with same error:** File mismatch for backend.js
|
||||
|
||||
**Sample agent response (run 1):**
|
||||
```
|
||||
Fixed. The bug was a missing null check for the `hook` variable in the `connectToDevTools` function. The function retrieved `window.__REACT_DEVTOOLS_GLOBAL_HOOK__` but didn't verify it exists before using it throughout the function (at lines 182, 185, 188, 214, 230, etc.).
|
||||
|
||||
The fix adds the same guard that already exists in the similar `connectWithCustomMessagingProtocol` function at line 325. Minimal change applied - only the required null check was inserted.
|
||||
```
|
||||
|
||||
**Diff (expected vs actual):**
|
||||
```diff
|
||||
@@ -78 @@
|
||||
|
||||
export function connectToDevTools(options: ?ConnectOptions) {
|
||||
const hook: ?DevToolsHook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
||||
+
|
||||
if (hook == null) {
|
||||
// DevTools didn't get injected into this page (maybe b'c of the contentType).
|
||||
return;
|
||||
```
|
||||
|
||||
### Structural Swap Adjacent Lines 003 (SyntheticEvent.js) — 0/1
|
||||
|
||||
**All runs failed with same error:** File mismatch for SyntheticEvent.js
|
||||
|
||||
**Sample agent response (run 1):**
|
||||
```
|
||||
Perfect! The fix has been applied successfully. The two adjacent statements on lines 56-57 have been swapped to the correct order:
|
||||
|
||||
- Line 56 now: `this.target = nativeEventTarget;`
|
||||
- Line 57 now: `this.nativeEvent = nativeEvent;`
|
||||
|
||||
The bug was that `this.nativeEvent` was being assigned before `this.target`, when the correct initialization order should have `this.target` assigned first.
|
||||
```
|
||||
|
||||
**Diff (expected vs actual):**
|
||||
```diff
|
||||
@@ -11 @@
|
||||
|
||||
import type { Fiber } from 'react-reconciler/src/ReactInternalTypes';
|
||||
|
||||
-import assign from 'shared/assign';
|
||||
import getEventCharCode from './getEventCharCode';
|
||||
import getEventCharCode from './getEventCharCode';
|
||||
+import assign from 'shared/assign';
|
||||
|
||||
type EventInterfaceType = {
|
||||
[propName: string]: 0 | ((event: { [propName: string]: mixed, ... }) => mixed),
|
||||
@@ -53 @@
|
||||
this._reactName = reactName;
|
||||
this._targetInst = targetInst;
|
||||
this.type = reactEventType;
|
||||
- this.nativeEvent = nativeEvent;
|
||||
this.target = nativeEventTarget;
|
||||
this.target = nativeEventTarget;
|
||||
+ this.nativeEvent = nativeEvent;
|
||||
this.currentTarget = null;
|
||||
|
||||
for (const propName in Interface) {
|
||||
```
|
||||
|
||||
### Structural Swap Adjacent Lines 004 (ReactDOMComponentTree.js) — 0/1
|
||||
|
||||
**All runs failed with same error:** File mismatch for ReactDOMComponentTree.js
|
||||
|
||||
**Sample agent response (run 1):**
|
||||
```
|
||||
Perfect! The fix has been applied.
|
||||
|
||||
**The bug:** Lines 149-150 had the checks in the wrong order. When `enableInternalInstanceMap` is true, it was checking `internalContainerInstanceKey` before `internalInstanceMap.get()`, which is inconsistent with:
|
||||
1. The else branch on line 153 (which checks container then instance key)
|
||||
2. The same pattern in `getInstanceFromNode` at lines 217-218 (which checks instance map then container key)
|
||||
|
||||
**The fix:** Swapped the order so `internalInstanceMap.get(...
|
||||
```
|
||||
|
||||
**Diff (expected vs actual):**
|
||||
```diff
|
||||
@@ -67 @@
|
||||
internalInstanceMap.delete(node);
|
||||
internalPropsMap.delete(node);
|
||||
delete (node: any)[internalEventHandlersKey];
|
||||
- delete (node: any)[internalEventHandlerListenersKey];
|
||||
delete (node: any)[internalEventHandlesSetKey];
|
||||
delete (node: any)[internalEventHandlesSetKey];
|
||||
+ delete (node: any)[internalEventHandlerListenersKey];
|
||||
delete (node: any)[internalRootNodeResourcesKey];
|
||||
if (__DEV__) {
|
||||
delete (node: any)[internalInstanceKey];
|
||||
@@ -146 @@
|
||||
// If it's not a container, we check if it's an instance.
|
||||
if (enableInternalInstanceMap) {
|
||||
targetInst =
|
||||
- (parentNode: any)[internalContainerInstanceKey] ||
|
||||
- internalInstanceMap.get(((parentNode: any): InstanceUnion));
|
||||
+ internalInstanceMap.get(((parentNode: any): InstanceUnion)) ||
|
||||
+ (parentNode: any)[internalContainerInstanceKey];
|
||||
} else {
|
||||
targetInst =
|
||||
(parentNode: any)[internalContainerInstanceKey] || (parentNode: any)[internalInstanceKey];
|
||||
```
|
||||
|
||||
### Structural Swap If Else 001 (resolveBoxStyle.js) — 0/1
|
||||
|
||||
**All runs failed with same error:** File mismatch for resolveBoxStyle.js
|
||||
|
||||
**Sample agent response (run 1):**
|
||||
```
|
||||
Perfect. The fix is complete. The if and else branches have been swapped back to their correct positions. Now the logic correctly:
|
||||
|
||||
- **If** `styleForVertical` exists: applies it to both `top` and `bottom` (lines 72-74)
|
||||
- **Else**: checks for individual `Bottom` and `Top` style properties (lines 76-85)
|
||||
|
||||
This matches the same pattern used for `styleForHorizontal` earlier in the file. Only the buggy branches around line 72 were modified; all other code remains unchanged.
|
||||
```
|
||||
|
||||
**Diff (expected vs actual):**
|
||||
```diff
|
||||
@@ -78 @@
|
||||
result.bottom = styleForBottom;
|
||||
hasParts = true;
|
||||
}
|
||||
-
|
||||
const styleForTop = style[prefix + 'Top'];
|
||||
if (styleForTop != null) {
|
||||
result.top = styleForTop;
|
||||
```
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+1513
File diff suppressed because it is too large
Load Diff
+1370
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,845 @@
|
||||
# Edit Benchmark Report
|
||||
|
||||
## Configuration
|
||||
|
||||
| Setting | Value |
|
||||
|---------|-------|
|
||||
| Date | 2026-02-20T09:58:58.663Z |
|
||||
| Model | xai/xai/grok-4-1-fast |
|
||||
| Thinking Level | low |
|
||||
| Runs per task | 1 |
|
||||
| Edit Variant | hashline |
|
||||
| Edit Fuzzy | auto |
|
||||
| Edit Fuzzy Threshold | auto |
|
||||
| Guided Mode | no |
|
||||
| Max Attempts | 1 |
|
||||
| No-op Retry Limit | 2 |
|
||||
| Mutation Scope Window | 20 |
|
||||
| Require Edit Tool | no |
|
||||
| Require Read Tool | no |
|
||||
| No-Edit Baseline | no |
|
||||
|
||||
## Summary
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Total Tasks | 80 |
|
||||
| Total Runs | 80 |
|
||||
| Successful Runs | 62 |
|
||||
| **Task Success Rate** | **77.5% (62/80)** |
|
||||
| Verified Rate | 77.5% (62/80) |
|
||||
| Edit Tool Usage Rate | 100.0% (80/80) |
|
||||
| **Edit Success Rate** | **97.7%** |
|
||||
| Timeout Runs | 0 |
|
||||
| Mutation Intent Match Rate | 78.8% |
|
||||
| Patch Failure Rate | 2.3% (2/86) |
|
||||
| Tasks All Passing | 62 |
|
||||
| Tasks Flaky/Failing | 18 |
|
||||
|
||||
### Tool Calls
|
||||
|
||||
| Tool | Total | Avg/Run |
|
||||
|------|-------|---------|
|
||||
| Read | 167 | 2.1 |
|
||||
| Edit | 86 | 1.1 |
|
||||
| Write | 0 | 0.0 |
|
||||
| **Tool Input Chars** | 21,681 | 271 |
|
||||
|
||||
### Tokens & Time
|
||||
|
||||
| Metric | Total | Avg/Run |
|
||||
|--------|-------|---------|
|
||||
| Input Tokens | 718,186 | 8,977 |
|
||||
| Output Tokens | 281,317 | 3,516 |
|
||||
| Total Tokens | 3,481,684 | 43,521 |
|
||||
| Duration | 2824.0s | 35.3s |
|
||||
| **Avg Indent Score** | — | **0.03** |
|
||||
|
||||
## Task Results
|
||||
|
||||
| Task | File | Success | Edit Hit | R/E/W | Tokens (In/Out) | Time | Indent |
|
||||
|------|------|---------|----------|-------|-----------------|------|--------|
|
||||
| Access Remove Optional Chain 001 | fallbackEvalContext.js | 1/1 ✅ | 100.0% | 2/1/0 | 14,359/1,323 | 17.1s | 0.00 |
|
||||
| Access Remove Optional Chain 003 | hookNamesCache.js | 1/1 ✅ | 100.0% | 2/1/0 | 10,033/1,831 | 22.9s | 0.00 |
|
||||
| Access Remove Optional Chain 005 | registerDevToolsEventLogger.js | 1/1 ✅ | 100.0% | 2/1/0 | 9,946/3,824 | 38.1s | 0.00 |
|
||||
| Access Remove Optional Chain 007 | index.js | 0/1 ❌ | 100.0% | 3/1/0 | 27,816/13,559 | 118.1s | 0.00 |
|
||||
| Call Swap Call Args 001 | formatConsoleArguments.js | 1/1 ✅ | 100.0% | 2/1/0 | 0/862 | 9.8s | 0.00 |
|
||||
| Call Swap Call Args 003 | ReactFlightDOMServerBrowser.js | 1/1 ✅ | 100.0% | 2/1/0 | 0/1,182 | 19.7s | 0.00 |
|
||||
| Call Swap Call Args 005 | ReactNoopPersistent.js | 1/1 ✅ | 100.0% | 2/1/0 | 8,094/2,357 | 22.1s | 0.00 |
|
||||
| Call Swap Call Args 007 | parseSourceAndMetadata.js | 0/1 ❌ | 100.0% | 2/1/0 | 23,043/1,657 | 17.8s | 0.00 |
|
||||
| Duplicate Duplicate Line Flip 001 | isCustomElement.js | 1/1 ✅ | 100.0% | 1/1/0 | 6,409/1,117 | 13.8s | 0.00 |
|
||||
| Duplicate Duplicate Line Flip 003 | ReactFiberDevToolsHook.js | 0/1 ❌ | 100.0% | 2/1/0 | 14,063/3,412 | 34.1s | 0.00 |
|
||||
| Duplicate Duplicate Line Flip 005 | shallowEqual.js | 1/1 ✅ | 100.0% | 2/1/0 | 7,858/1,098 | 13.4s | 0.00 |
|
||||
| Duplicate Duplicate Line Flip 007 | ReactDOMEventReplaying.js | 0/1 ❌ | 100.0% | 1/1/0 | 32,469/18,708 | 160.2s | 0.00 |
|
||||
| Identifier Identifier Multi Edit 001 | Button.js | 1/1 ✅ | 100.0% | 2/1/0 | 0/1,766 | 19.6s | 0.00 |
|
||||
| Identifier Identifier Multi Edit 003 | ReactFlightDOMClientBrowser.js | 1/1 ✅ | 100.0% | 2/1/0 | 10,722/2,093 | 22.8s | 0.00 |
|
||||
| Identifier Identifier Multi Edit 005 | githubAPI.js | 1/1 ✅ | 80.0% | 5/5/0 | 18,010/10,304 | 109.8s | 0.00 |
|
||||
| Identifier Identifier Multi Edit 007 | ReactFiberComponentStack.js | 1/1 ✅ | 100.0% | 3/1/0 | 0/1,676 | 18.9s | 0.00 |
|
||||
| Import Swap Named Imports 001 | ListApp.js | 1/1 ✅ | 100.0% | 2/1/0 | 8,334/2,022 | 19.9s | 0.00 |
|
||||
| Import Swap Named Imports 003 | index.js | 1/1 ✅ | 100.0% | 2/1/0 | 14,035/6,305 | 57.7s | 0.00 |
|
||||
| Import Swap Named Imports 005 | SuspenseScrubber.js | 1/1 ✅ | 100.0% | 2/1/0 | 0/1,860 | 14.5s | 0.00 |
|
||||
| Import Swap Named Imports 007 | InspectedElement.js | 0/1 ❌ | 100.0% | 2/1/0 | 13,678/4,339 | 44.1s | 0.00 |
|
||||
| Literal Flip Boolean 001 | ReactDOMLegacyServerImpl.js | 1/1 ✅ | 100.0% | 3/1/0 | 0/4,990 | 31.4s | 0.00 |
|
||||
| Literal Flip Boolean 003 | ReactProfilerTimer.js | 1/1 ✅ | 100.0% | 2/1/0 | 21,563/2,476 | 22.5s | 0.00 |
|
||||
| Literal Flip Boolean 005 | OpenInEditorButton.js | 1/1 ✅ | 100.0% | 2/1/0 | 0/1,623 | 18.2s | 0.00 |
|
||||
| Literal Flip Boolean 007 | Element.js | 1/1 ✅ | 100.0% | 2/1/0 | 15,219/1,304 | 15.8s | 0.00 |
|
||||
| Literal Off By One 001 | ReactOwnerStackFrames.js | 1/1 ✅ | 100.0% | 2/1/0 | 0/1,806 | 18.2s | 0.00 |
|
||||
| Literal Off By One 003 | ReactFlightClientConfigBundlerTurbopack.js | 1/1 ✅ | 100.0% | 1/1/0 | 9,582/1,154 | 14.2s | 0.00 |
|
||||
| Literal Off By One 005 | ContextMenu.js | 1/1 ✅ | 100.0% | 2/1/0 | 0/1,413 | 17.4s | 0.00 |
|
||||
| Literal Off By One 007 | index.js | 1/1 ✅ | 100.0% | 2/1/0 | 14,674/1,117 | 15.2s | 0.00 |
|
||||
| Operator Remove Negation 001 | prepareInjection.js | 1/1 ✅ | 100.0% | 2/1/0 | 13,804/1,551 | 20.4s | 0.00 |
|
||||
| Operator Remove Negation 003 | ReactDOMSelection.js | 1/1 ✅ | 100.0% | 2/1/0 | 13,150/4,963 | 48.6s | 0.00 |
|
||||
| Operator Remove Negation 005 | ReactDOMContainer.js | 0/1 ❌ | 100.0% | 2/1/0 | 0/11,181 | 109.9s | 0.00 |
|
||||
| Operator Remove Negation 007 | SelectEventPlugin.js | 1/1 ✅ | 100.0% | 2/1/0 | 7,482/2,086 | 28.0s | 0.00 |
|
||||
| Operator Swap Arithmetic 001 | formatConsoleArguments.js | 1/1 ✅ | 100.0% | 2/1/0 | 15,749/1,776 | 19.2s | 0.00 |
|
||||
| Operator Swap Arithmetic 003 | ReactFlightDOMServerBrowser.js | 1/1 ✅ | 100.0% | 2/1/0 | 0/2,084 | 31.7s | 0.00 |
|
||||
| Operator Swap Arithmetic 005 | ReactFiberConfigWithNoResources.js | 1/1 ✅ | 100.0% | 2/1/0 | 6,680/1,357 | 16.2s | 0.00 |
|
||||
| Operator Swap Arithmetic 007 | useCanvasInteraction.js | 1/1 ✅ | 100.0% | 2/1/0 | 9,339/1,721 | 19.8s | 0.00 |
|
||||
| Operator Swap Comparison 001 | useSmartTooltip.js | 1/1 ✅ | 100.0% | 2/1/0 | 0/1,784 | 22.2s | 0.00 |
|
||||
| Operator Swap Comparison 003 | ReactFlightDOMServerBrowser.js | 1/1 ✅ | 100.0% | 2/1/0 | 12,724/4,780 | 48.9s | 2.00 |
|
||||
| Operator Swap Comparison 005 | Rectangle.js | 1/1 ✅ | 100.0% | 2/1/0 | 0/1,156 | 10.8s | 0.00 |
|
||||
| Operator Swap Comparison 007 | ReactFiberTreeReflection.js | 1/1 ✅ | 100.0% | 2/1/0 | 14,927/2,194 | 24.6s | 0.00 |
|
||||
| Operator Swap Equality 001 | ReactNoopFlightClient.js | 1/1 ✅ | 100.0% | 2/1/0 | 7,807/1,617 | 20.0s | 0.00 |
|
||||
| Operator Swap Equality 003 | astUtils.js | 1/1 ✅ | 100.0% | 2/1/0 | 0/2,892 | 33.7s | 0.00 |
|
||||
| Operator Swap Equality 005 | ReactDOMContainer.js | 1/1 ✅ | 100.0% | 2/1/0 | 8,442/2,431 | 26.1s | 0.00 |
|
||||
| Operator Swap Equality 007 | ReactFlightDOMServerBrowser.js | 1/1 ✅ | 100.0% | 2/1/0 | 0/6,136 | 67.0s | 0.00 |
|
||||
| Operator Swap Increment Decrement 001 | index.js | 1/1 ✅ | 100.0% | 2/1/0 | 7,311/1,070 | 15.3s | 0.00 |
|
||||
| Operator Swap Increment Decrement 003 | ReactFlightClientConfigBundlerESM.js | 1/1 ✅ | 100.0% | 2/1/0 | 0/1,478 | 16.4s | 0.00 |
|
||||
| Operator Swap Increment Decrement 005 | ReactFiberViewTransitionComponent.js | 1/1 ✅ | 100.0% | 2/1/0 | 14,268/1,693 | 18.0s | 0.00 |
|
||||
| Operator Swap Increment Decrement 007 | ReactFiberConcurrentUpdates.js | 1/1 ✅ | 100.0% | 2/1/0 | 10,713/1,865 | 19.3s | 0.00 |
|
||||
| Operator Swap Logical 001 | ErrorView.js | 0/1 ❌ | 100.0% | 3/2/0 | 17,543/5,545 | 51.9s | 0.00 |
|
||||
| Operator Swap Logical 003 | DevTools.js | 1/1 ✅ | 100.0% | 2/1/0 | 16,218/1,401 | 19.4s | 0.00 |
|
||||
| Operator Swap Logical 005 | UseEffectEvent.js | 1/1 ✅ | 100.0% | 2/1/0 | 0/2,226 | 31.2s | 0.00 |
|
||||
| Operator Swap Logical 007 | getHookNameForLocation.js | 1/1 ✅ | 100.0% | 2/1/0 | 0/1,120 | 16.7s | 0.00 |
|
||||
| Operator Swap Nullish 001 | ElementBadges.js | 1/1 ✅ | 100.0% | 2/1/0 | 10,110/3,578 | 32.6s | 0.00 |
|
||||
| Operator Swap Nullish 003 | ReactComponentStackFrame.js | 1/1 ✅ | 100.0% | 2/1/0 | 11,599/1,981 | 18.9s | 0.00 |
|
||||
| Operator Swap Nullish 005 | ReactLogo.js | 0/1 ❌ | 100.0% | 2/1/0 | 9,480/3,350 | 33.5s | 0.00 |
|
||||
| Operator Swap Nullish 007 | SuspenseBreadcrumbs.js | 1/1 ✅ | 100.0% | 2/1/0 | 4,057/2,630 | 30.6s | 0.00 |
|
||||
| Regex Swap Regex Quantifier 001 | SimpleValues.js | 1/1 ✅ | 100.0% | 2/1/0 | 5,168/1,066 | 15.1s | 0.00 |
|
||||
| Regex Swap Regex Quantifier 003 | utils.js | 1/1 ✅ | 100.0% | 2/1/0 | 0/8,102 | 98.5s | 0.00 |
|
||||
| Regex Swap Regex Quantifier 005 | formatWithStyles.js | 1/1 ✅ | 100.0% | 2/1/0 | 0/3,266 | 40.7s | 0.00 |
|
||||
| Regex Swap Regex Quantifier 007 | RunReactCompiler.ts | 0/1 ❌ | 100.0% | 2/1/0 | 15,418/7,888 | 63.5s | 0.00 |
|
||||
| Structural Delete Statement 001 | useExtensionComponentsPanelVisibility.js | 0/1 ❌ | 100.0% | 2/1/0 | 14,916/2,102 | 21.2s | 0.00 |
|
||||
| Structural Delete Statement 003 | useCanvasInteraction.js | 1/1 ✅ | 100.0% | 2/1/0 | 0/3,051 | 27.0s | 0.00 |
|
||||
| Structural Delete Statement 005 | StackTraceView.js | 0/1 ❌ | 100.0% | 3/1/0 | 0/5,747 | 75.3s | 0.00 |
|
||||
| Structural Delete Statement 007 | ReactDOMFizzStaticBrowser.js | 1/1 ✅ | 100.0% | 2/1/0 | 13,080/5,607 | 33.8s | 0.00 |
|
||||
| Structural Remove Early Return 001 | ReactFlightAsyncDispatcher.js | 0/1 ❌ | 100.0% | 2/1/0 | 0/5,019 | 58.3s | 0.00 |
|
||||
| Structural Remove Early Return 003 | ReactDOMComponentTree.js | 0/1 ❌ | 50.0% | 3/2/0 | 23,863/13,281 | 106.8s | 0.00 |
|
||||
| Structural Remove Early Return 005 | TabBar.js | 0/1 ❌ | 100.0% | 3/1/0 | 9,573/2,688 | 30.6s | 0.00 |
|
||||
| Structural Remove Early Return 007 | CommitTreeBuilder.js | 0/1 ❌ | 100.0% | 2/1/0 | 0/8,330 | 44.5s | 0.00 |
|
||||
| Structural Swap Adjacent Lines 001 | reactPolling.js | 1/1 ✅ | 100.0% | 2/1/0 | 0/1,368 | 11.7s | 0.00 |
|
||||
| Structural Swap Adjacent Lines 003 | OwnersStack.js | 1/1 ✅ | 100.0% | 2/1/0 | 10,369/1,086 | 12.8s | 0.00 |
|
||||
| Structural Swap Adjacent Lines 005 | ReactOwnerStackFrames.js | 1/1 ✅ | 100.0% | 2/1/0 | 7,734/1,415 | 15.4s | 0.00 |
|
||||
| Structural Swap Adjacent Lines 007 | ReactFlightClientConfigBundlerWebpack.js | 0/1 ❌ | 100.0% | 2/1/0 | 23,047/9,057 | 77.9s | 0.00 |
|
||||
| Structural Swap If Else 001 | utils.js | 0/1 ❌ | 100.0% | 2/1/0 | 17,350/4,243 | 45.1s | 0.00 |
|
||||
| Structural Swap If Else 003 | ReactDOMFloat.js | 0/1 ❌ | 100.0% | 2/1/0 | 31,736/10,920 | 103.5s | 0.00 |
|
||||
| Structural Swap If Else 005 | ReactClientConsoleConfigPlain.js | 1/1 ✅ | 100.0% | 2/1/0 | 8,701/2,998 | 31.7s | 0.00 |
|
||||
| Structural Swap If Else 007 | index.js | 1/1 ✅ | 100.0% | 2/1/0 | 20,895/3,179 | 31.9s | 0.00 |
|
||||
| Unicode Unicode Hyphen 001 | formatProdErrorMessage.js | 1/1 ✅ | 100.0% | 2/1/0 | 6,833/1,463 | 19.2s | 0.00 |
|
||||
| Unicode Unicode Hyphen 003 | SourceMapConsumer.js | 1/1 ✅ | 100.0% | 2/1/0 | 0/1,454 | 22.0s | 0.00 |
|
||||
| Unicode Unicode Hyphen 005 | babel.config.js | 1/1 ✅ | 100.0% | 2/1/0 | 7,214/1,586 | 18.6s | 0.00 |
|
||||
| Unicode Unicode Hyphen 007 | ReactInternalTestUtils.js | 1/1 ✅ | 100.0% | 2/1/0 | 10,979/1,577 | 21.1s | 0.00 |
|
||||
|
||||
## Category Summary
|
||||
|
||||
| Category | Runs | Verified | Edit Used | Success | Min/Avg/Max Difficulty |
|
||||
|----------|------|----------|-----------|---------|------------------------|
|
||||
| access | 4 | 75.0% (3/4) | 100.0% (4/4) | 75.0% (3/4) | 3 / 7.5 / 10 |
|
||||
| call | 4 | 75.0% (3/4) | 100.0% (4/4) | 75.0% (3/4) | 2 / 7.8 / 14 |
|
||||
| duplicate | 4 | 50.0% (2/4) | 100.0% (4/4) | 50.0% (2/4) | 8 / 11.3 / 17 |
|
||||
| identifier | 4 | 100.0% (4/4) | 100.0% (4/4) | 100.0% (4/4) | 4 / 6.8 / 9 |
|
||||
| import | 4 | 75.0% (3/4) | 100.0% (4/4) | 75.0% (3/4) | 2 / 3.8 / 5 |
|
||||
| literal | 8 | 100.0% (8/8) | 100.0% (8/8) | 100.0% (8/8) | 3 / 6.9 / 10 |
|
||||
| operator | 28 | 89.3% (25/28) | 100.0% (28/28) | 89.3% (25/28) | 3 / 6.7 / 10 |
|
||||
| regex | 4 | 75.0% (3/4) | 100.0% (4/4) | 75.0% (3/4) | 2 / 5.8 / 10 |
|
||||
| structural | 16 | 43.8% (7/16) | 100.0% (16/16) | 43.8% (7/16) | 4 / 8.3 / 13 |
|
||||
| unicode | 4 | 100.0% (4/4) | 100.0% (4/4) | 100.0% (4/4) | 2 / 4.8 / 8 |
|
||||
|
||||
## Mutation Summary
|
||||
|
||||
| Mutation | Category | Runs | Verified | Edit Used | Success |
|
||||
|----------|----------|------|----------|-----------|---------|
|
||||
| delete-statement | structural | 4 | 50.0% (2/4) | 100.0% (4/4) | 50.0% (2/4) |
|
||||
| duplicate-line-flip | duplicate | 4 | 50.0% (2/4) | 100.0% (4/4) | 50.0% (2/4) |
|
||||
| flip-boolean | literal | 4 | 100.0% (4/4) | 100.0% (4/4) | 100.0% (4/4) |
|
||||
| identifier-multi-edit | identifier | 4 | 100.0% (4/4) | 100.0% (4/4) | 100.0% (4/4) |
|
||||
| off-by-one | literal | 4 | 100.0% (4/4) | 100.0% (4/4) | 100.0% (4/4) |
|
||||
| remove-early-return | structural | 4 | 0.0% (0/4) | 100.0% (4/4) | 0.0% (0/4) |
|
||||
| remove-negation | operator | 4 | 75.0% (3/4) | 100.0% (4/4) | 75.0% (3/4) |
|
||||
| remove-optional-chain | access | 4 | 75.0% (3/4) | 100.0% (4/4) | 75.0% (3/4) |
|
||||
| swap-adjacent-lines | structural | 4 | 75.0% (3/4) | 100.0% (4/4) | 75.0% (3/4) |
|
||||
| swap-arithmetic | operator | 4 | 100.0% (4/4) | 100.0% (4/4) | 100.0% (4/4) |
|
||||
| swap-call-args | call | 4 | 75.0% (3/4) | 100.0% (4/4) | 75.0% (3/4) |
|
||||
| swap-comparison | operator | 4 | 100.0% (4/4) | 100.0% (4/4) | 100.0% (4/4) |
|
||||
| swap-equality | operator | 4 | 100.0% (4/4) | 100.0% (4/4) | 100.0% (4/4) |
|
||||
| swap-if-else | structural | 4 | 50.0% (2/4) | 100.0% (4/4) | 50.0% (2/4) |
|
||||
| swap-increment-decrement | operator | 4 | 100.0% (4/4) | 100.0% (4/4) | 100.0% (4/4) |
|
||||
| swap-logical | operator | 4 | 75.0% (3/4) | 100.0% (4/4) | 75.0% (3/4) |
|
||||
| swap-named-imports | import | 4 | 75.0% (3/4) | 100.0% (4/4) | 75.0% (3/4) |
|
||||
| swap-nullish | operator | 4 | 75.0% (3/4) | 100.0% (4/4) | 75.0% (3/4) |
|
||||
| swap-regex-quantifier | regex | 4 | 75.0% (3/4) | 100.0% (4/4) | 75.0% (3/4) |
|
||||
| unicode-hyphen | unicode | 4 | 100.0% (4/4) | 100.0% (4/4) | 100.0% (4/4) |
|
||||
|
||||
## Difficulty Summary
|
||||
|
||||
| Difficulty Score | Runs | Verified | Edit Used | Success |
|
||||
|------------------|------|----------|-----------|---------|
|
||||
| 0-2 | 4 | 100.0% (4/4) | 100.0% (4/4) | 100.0% (4/4) |
|
||||
| 3-5 | 24 | 75.0% (18/24) | 100.0% (24/24) | 75.0% (18/24) |
|
||||
| 6-8 | 27 | 85.2% (23/27) | 100.0% (27/27) | 85.2% (23/27) |
|
||||
| 9+ | 25 | 68.0% (17/25) | 100.0% (25/25) | 68.0% (17/25) |
|
||||
|
||||
## Edit Tool Errors
|
||||
|
||||
Failures where the edit tool returned an error or failed to apply the patch.
|
||||
|
||||
### Identifier Identifier Multi Edit 005 (githubAPI.js)
|
||||
|
||||
#### Run 1
|
||||
|
||||
##### Attempt 1
|
||||
|
||||
- Path: githubAPI.js
|
||||
- Operation: —
|
||||
|
||||
**Tool error**
|
||||
|
||||
```
|
||||
1 line has changed since last read. Use the updated LINE#ID references shown below (>>> marks changed lines).
|
||||
|
||||
19#HJ: message = message.replace(/
|
||||
20#ZR:
|
||||
>>> 21#NB: 'is:issue',
|
||||
22#MB: 'is:open',
|
||||
23#PQ: 'is:public',
|
||||
```
|
||||
|
||||
**Patch args**
|
||||
|
||||
```diff
|
||||
{
|
||||
"path": "githubAPI.js",
|
||||
"edits": [
|
||||
{
|
||||
"op": "replace",
|
||||
"first": "19#HJ",
|
||||
"last": "21#ZR",
|
||||
"content": [
|
||||
" message = message.replace(/",
|
||||
""
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Structural Remove Early Return 003 (ReactDOMComponentTree.js)
|
||||
|
||||
#### Run 1
|
||||
|
||||
##### Attempt 1
|
||||
|
||||
- Path: ReactDOMComponentTree.js
|
||||
- Operation: —
|
||||
|
||||
**Tool error**
|
||||
|
||||
```
|
||||
1 line has changed since last read. Use the updated LINE#ID references shown below (>>> marks changed lines).
|
||||
|
||||
184#YS: const targetFiber = enableInternalInstanceMap
|
||||
185#BV: ? internalInstanceMap.get(hydrationInstance)
|
||||
>>> 186#HJ: : // $FlowFixMe[prop-missing]
|
||||
187#XW: hydrationInstance[internalInstanceKey];
|
||||
188#ZR:
|
||||
```
|
||||
|
||||
**Patch args**
|
||||
|
||||
```diff
|
||||
{
|
||||
"path": "ReactDOMComponentTree.js",
|
||||
"edits": [
|
||||
{
|
||||
"op": "insert",
|
||||
"after": "186#XW",
|
||||
"content": [
|
||||
" if (targetFiber) {",
|
||||
" return targetFiber;",
|
||||
" }"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Failed Tasks (0% passing)
|
||||
|
||||
### Access Remove Optional Chain 007 (index.js) — 0/1
|
||||
|
||||
**All runs failed with same error:** File mismatch for index.js
|
||||
|
||||
**Sample agent response (run 1):**
|
||||
```
|
||||
**Fix complete.** Verified lines 251, 254 (`bridge?.emit`), and 295 (`bridge?.send`) now use optional chaining. No other changes.
|
||||
```
|
||||
|
||||
**Diff (expected vs actual):**
|
||||
```diff
|
||||
@@ -64 @@
|
||||
},
|
||||
|
||||
send(event: string, payload: any, transferable?: Array<any>) {
|
||||
- port?.postMessage({ event, payload }, transferable);
|
||||
+ port.postMessage({ event, payload }, transferable);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -248 @@
|
||||
});
|
||||
|
||||
createdPanel.onShown.addListener(() => {
|
||||
- bridge.emit('extensionComponentsPanelShown');
|
||||
+ bridge?.emit('extensionComponentsPanelShown');
|
||||
});
|
||||
createdPanel.onHidden.addListener(() => {
|
||||
});
|
||||
createdPanel.onHidden.addListener(() => {
|
||||
- bridge.emit('extensionComponentsPanelHidden');
|
||||
+ bridge?.emit('extensionComponentsPanelHidden');
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -292 @@
|
||||
inspectedElementPortalContainer = portal.container;
|
||||
if (inspectedElementPortalContainer != null && render) {
|
||||
ensureInitialHTMLIsCleared(inspectedElementPortalContainer);
|
||||
- bridge.send('syncSelectionFromBuiltinElementsPanel');
|
||||
+ bridge?.send('syncSelectionFromBuiltinElementsPanel');
|
||||
|
||||
render();
|
||||
portal.injectStyles(cloneStyleTags);
|
||||
```
|
||||
|
||||
### Call Swap Call Args 007 (parseSourceAndMetadata.js) — 0/1
|
||||
|
||||
**All runs failed with same error:** File mismatch for parseSourceAndMetadata.js
|
||||
|
||||
**Sample agent response (run 1):**
|
||||
```
|
||||
**Fixed.** Swapped `columnNumber` and `lineNumber` arguments in `sourceMapConsumer.originalPositionFor()` call (lines 249-250).
|
||||
|
||||
**Verification** (lines 248-251 post-edit):
|
||||
```
|
||||
248#SW: const { column, line, sourceContent, sourceURL } = sourceMapConsumer.originalPositionFor({
|
||||
249#ST: lineNumber,
|
||||
250#BJ: columnNumber,
|
||||
251#NW: });
|
||||
```
|
||||
|
||||
No other changes.
|
||||
```
|
||||
|
||||
**Diff (expected vs actual):**
|
||||
```diff
|
||||
@@ -190 @@
|
||||
sourceMapConsumer: null,
|
||||
};
|
||||
|
||||
- locationKeyToHookParsedMetadata.set(locationKey, hookParsedMetadata);
|
||||
+ locationKeyToHookParsedMetadata.set(hookParsedMetadata, locationKey);
|
||||
});
|
||||
|
||||
return locationKeyToHookParsedMetadata;
|
||||
@@ -246 @@
|
||||
originalSourceURL = hookSourceAndMetadata.runtimeSourceURL;
|
||||
} else {
|
||||
const { column, line, sourceContent, sourceURL } = sourceMapConsumer.originalPositionFor({
|
||||
- columnNumber,
|
||||
lineNumber,
|
||||
lineNumber,
|
||||
+ columnNumber,
|
||||
});
|
||||
if (sourceContent === null || sourceURL === null) {
|
||||
throw Error(
|
||||
```
|
||||
|
||||
### Duplicate Duplicate Line Flip 003 (ReactFiberDevToolsHook.js) — 0/1
|
||||
|
||||
**All runs failed with same error:** File mismatch for ReactFiberDevToolsHook.js
|
||||
|
||||
**Sample agent response (run 1):**
|
||||
```
|
||||
Fix applied: removed extraneous period from format string in line 77 of `ReactFiberDevToolsHook.js`.
|
||||
|
||||
Verified by re-reading lines 70-84: console.error now matches other instances exactly. No other changes.
|
||||
```
|
||||
|
||||
**Diff (expected vs actual):**
|
||||
```diff
|
||||
@@ -46 @@
|
||||
export function injectInternals(internals: Object): boolean {
|
||||
if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ === 'undefined') {
|
||||
// No DevTools
|
||||
- return false;
|
||||
+ return true;
|
||||
}
|
||||
const hook = __REACT_DEVTOOLS_GLOBAL_HOOK__;
|
||||
if (hook.isDisabled) {
|
||||
@@ -74 @@
|
||||
} catch (err) {
|
||||
// Catch all errors because it is unsafe to throw during initialization.
|
||||
if (__DEV__) {
|
||||
- console.error('React instrumentation encountered an error: %o.', err);
|
||||
+ console.error('React instrumentation encountered an error: %o', err);
|
||||
}
|
||||
}
|
||||
if (hook.checkDCE) {
|
||||
```
|
||||
|
||||
### Duplicate Duplicate Line Flip 007 (ReactDOMEventReplaying.js) — 0/1
|
||||
|
||||
**All runs failed with same error:** File mismatch for ReactDOMEventReplaying.js
|
||||
|
||||
**Sample agent response (run 1):**
|
||||
```
|
||||
Fixed: changed `!= null` to `!== null` at line 573. Verified via re-read: only intended token mutated, structure preserved.
|
||||
```
|
||||
|
||||
**Diff (expected vs actual):**
|
||||
```diff
|
||||
@@ -250 @@
|
||||
targetContainer,
|
||||
mouseEvent,
|
||||
);
|
||||
- return true;
|
||||
+ return false;
|
||||
}
|
||||
case 'pointerover': {
|
||||
const pointerEvent = ((nativeEvent: any): PointerEventType);
|
||||
@@ -570 @@
|
||||
// If there's no ownerDocument, then this is the document.
|
||||
const root = unblocked.ownerDocument || unblocked;
|
||||
const formReplayingQueue: void | FormReplayingQueue = (root: any).$$reactFormReplay;
|
||||
- if (formReplayingQueue != null) {
|
||||
+ if (formReplayingQueue !== null) {
|
||||
for (let i = 0; i < formReplayingQueue.length; i += 3) {
|
||||
const form: HTMLFormElement = formReplayingQueue[i];
|
||||
const submitterOrAction: null | HTMLInputElement | HTMLButtonElement | FormAction =
|
||||
```
|
||||
|
||||
### Import Swap Named Imports 007 (InspectedElement.js) — 0/1
|
||||
|
||||
**All runs failed with same error:** File mismatch for InspectedElement.js
|
||||
|
||||
**Diff (expected vs actual):**
|
||||
```diff
|
||||
@@ -10 @@
|
||||
import type { SourceMappedLocation } from 'react-devtools-shared/src/symbolicateSource';
|
||||
|
||||
import * as React from 'react';
|
||||
-import { useCallback, useContext, useSyncExternalStore } from 'react';
|
||||
+import { useSyncExternalStore, useContext, useCallback } from 'react';
|
||||
import { TreeStateContext } from './TreeContext';
|
||||
import { BridgeContext, StoreContext, OptionsContext } from '../context';
|
||||
import Button from '../Button';
|
||||
@@ -17 @@
|
||||
import ButtonIcon from '../ButtonIcon';
|
||||
import Icon from '../Icon';
|
||||
import Toggle from '../Toggle';
|
||||
-import { ElementTypeSuspense, ElementTypeRoot } from 'react-devtools-shared/src/frontend/types';
|
||||
+import { ElementTypeRoot, ElementTypeSuspense } from 'react-devtools-shared/src/frontend/types';
|
||||
import InspectedElementView from './InspectedElementView';
|
||||
import { InspectedElementContext } from './InspectedElementContext';
|
||||
import { getAlwaysOpenInEditor } from '../../../utils';
|
||||
```
|
||||
|
||||
### Operator Remove Negation 005 (ReactDOMContainer.js) — 0/1
|
||||
|
||||
**All runs failed with same error:** File mismatch for ReactDOMContainer.js
|
||||
|
||||
**Sample agent response (run 1):**
|
||||
```
|
||||
**Fix complete.** Line 16 updated to `!!node &&`, restoring the accidentally removed negation. Verified: function now returns `true` for valid DOM element/document/documentFragment nodes, and qualifying comment nodes when comments are enabled; `false` otherwise. Only intended line touched; formatting preserved.
|
||||
```
|
||||
|
||||
**Diff (expected vs actual):**
|
||||
```diff
|
||||
@@ -12 @@
|
||||
import { ELEMENT_NODE, COMMENT_NODE, DOCUMENT_NODE, DOCUMENT_FRAGMENT_NODE } from './HTMLNodeType';
|
||||
|
||||
export function isValidContainer(node: any): boolean {
|
||||
- return !!(
|
||||
- node &&
|
||||
+ return (
|
||||
+ !!node &&
|
||||
(node.nodeType === ELEMENT_NODE ||
|
||||
node.nodeType === DOCUMENT_NODE ||
|
||||
node.nodeType === DOCUMENT_FRAGMENT_NODE ||
|
||||
```
|
||||
|
||||
### Operator Swap Logical 001 (ErrorView.js) — 0/1
|
||||
|
||||
**All runs failed with same error:** File mismatch for ErrorView.js
|
||||
|
||||
**Sample agent response (run 1):**
|
||||
```
|
||||
**Fix verified.**
|
||||
|
||||
Line 36 updated from `dismissError !== null` to `!!dismissError`, preserving all formatting and structure. Confirmed via re-read: only intended line changed.
|
||||
```
|
||||
|
||||
**Diff (expected vs actual):**
|
||||
```diff
|
||||
@@ -32 @@
|
||||
{children}
|
||||
<div className={styles.ErrorInfo}>
|
||||
<div className={styles.HeaderRow}>
|
||||
- <div className={styles.ErrorHeader}>Uncaught Error: {errorMessage || ''}</div>
|
||||
- {dismissError !== null && (
|
||||
+ <div className={styles.ErrorHeader}>Uncaught Error: {errorMessage && ''}</div>
|
||||
+ {!!dismissError && (
|
||||
<Button className={styles.CloseButton} onClick={dismissError}>
|
||||
Dismiss
|
||||
<ButtonIcon className={styles.CloseButtonIcon} type="close" />
|
||||
```
|
||||
|
||||
### Operator Swap Nullish 005 (ReactLogo.js) — 0/1
|
||||
|
||||
**All runs failed with same error:** File mismatch for ReactLogo.js
|
||||
|
||||
**Sample agent response (run 1):**
|
||||
```
|
||||
ReactLogo.js line 22: changed ?? to ||. Verified by re-read. Fix complete.
|
||||
```
|
||||
|
||||
**Diff (expected vs actual):**
|
||||
```diff
|
||||
@@ -19 @@
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
- className={`${styles.ReactLogo} ${className || ''}`}
|
||||
+ className=`${styles.ReactLogo} ${className || ''}`
|
||||
viewBox="-11.5 -10.23174 23 20.46348"
|
||||
>
|
||||
<circle cx="0" cy="0" r="2.05" fill="currentColor" />
|
||||
```
|
||||
|
||||
### Regex Swap Regex Quantifier 007 (RunReactCompiler.ts) — 0/1
|
||||
|
||||
**All runs failed with same error:** File mismatch for RunReactCompiler.ts
|
||||
|
||||
**Sample agent response (run 1):**
|
||||
```
|
||||
**Fix applied.** Changed line 154 in `RunReactCompiler.ts` from `/\$FlowFixMe\[([^\]]+)\]/g` to `/\s*\$FlowFixMe\[([^\]]+)\]/g`. This corrects the failure to match `$FlowFixMe` suppressions preceded by whitespace (common after `// `), restoring proper whitespace matching via optional `\s*`. Verified via re-read: only target line modified, structure preserved.
|
||||
```
|
||||
|
||||
**Diff (expected vs actual):**
|
||||
```diff
|
||||
@@ -151 @@
|
||||
filename: string;
|
||||
userOpts: PluginOptions;
|
||||
};
|
||||
-const FLOW_SUPPRESSION_REGEX = /\$FlowFixMe\[([^\]]*)\]/g;
|
||||
+const FLOW_SUPPRESSION_REGEX = /\s*\$FlowFixMe\[([^\]]+)\]/g;
|
||||
|
||||
function getFlowSuppressions(sourceCode: SourceCode): Array<{ line: number; code: string }> {
|
||||
const comments = sourceCode.getAllComments();
|
||||
```
|
||||
|
||||
### Structural Delete Statement 001 (useExtensionComponentsPanelVisibility.js) — 0/1
|
||||
|
||||
**All runs failed with same error:** File mismatch for useExtensionComponentsPanelVisibility.js
|
||||
|
||||
**Sample agent response (run 1):**
|
||||
```
|
||||
Fixed. Restored `const [isVisible, setIsVisible] = useState(false);` before `useEffect`. Verified via re-read; no other changes.
|
||||
```
|
||||
|
||||
**Diff (expected vs actual):**
|
||||
```diff
|
||||
@@ -13 @@
|
||||
// Events that are prefixed with `extension` will only be emitted for the browser extension implementation.
|
||||
// For other implementations, this hook will just return constant `true` value.
|
||||
export function useExtensionComponentsPanelVisibility(bridge: FrontendBridge): boolean {
|
||||
- const [isVisible, setIsVisible] = useState(true);
|
||||
-
|
||||
+ const [isVisible, setIsVisible] = useState(false);
|
||||
useEffect(() => {
|
||||
function onPanelShown() {
|
||||
setIsVisible(true);
|
||||
```
|
||||
|
||||
### Structural Delete Statement 005 (StackTraceView.js) — 0/1
|
||||
|
||||
**All runs failed with same error:** File mismatch for StackTraceView.js
|
||||
|
||||
**Sample agent response (run 1):**
|
||||
```
|
||||
Bug fixed. Inserted `const ignored = false;` after `const isBuiltIn = ...`. Region re-read (52-66); `ignored` defined before ternary usage. No other changes.
|
||||
```
|
||||
|
||||
**Diff (expected vs actual):**
|
||||
```diff
|
||||
@@ -49 @@
|
||||
);
|
||||
const [functionName, url, line, column] =
|
||||
symbolicatedCallSite !== null ? symbolicatedCallSite.location : callSite;
|
||||
- const ignored = symbolicatedCallSite !== null ? symbolicatedCallSite.ignored : false;
|
||||
+
|
||||
// TODO: Make an option to be able to toggle the display of ignore listed rows.
|
||||
// Ideally this UI should be higher than a single Stack Trace so that there's not
|
||||
// multiple buttons in a single inspection taking up space.
|
||||
@@ -55 @@
|
||||
// multiple buttons in a single inspection taking up space.
|
||||
|
||||
const isBuiltIn = url === '' || url.startsWith('<anonymous>'); // This looks like a fake anonymous through eval.
|
||||
+ const ignored = false;
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
```
|
||||
|
||||
### Structural Remove Early Return 001 (ReactFlightAsyncDispatcher.js) — 0/1
|
||||
|
||||
**All runs failed with same error:** File mismatch for ReactFlightAsyncDispatcher.js
|
||||
|
||||
**Sample agent response (run 1):**
|
||||
```
|
||||
**Fix applied.** Restored guard clause in `resolveCache` at lines 17-19:
|
||||
|
||||
```
|
||||
if (!request) {
|
||||
return new Map();
|
||||
}
|
||||
```
|
||||
|
||||
Updated following return to `getCache(request)` (line 20). Verified: exact 3-line pattern, no extra changes, structure/formatting preserved.
|
||||
```
|
||||
|
||||
**Diff (expected vs actual):**
|
||||
```diff
|
||||
@@ -14 @@
|
||||
|
||||
function resolveCache(): Map<Function, mixed> {
|
||||
const request = resolveRequest();
|
||||
- if (request) {
|
||||
- return getCache(request);
|
||||
+ if (!request) {
|
||||
+ return new Map();
|
||||
}
|
||||
}
|
||||
- return new Map();
|
||||
+ return getCache(request);
|
||||
}
|
||||
|
||||
export const DefaultAsyncDispatcher: AsyncDispatcher = ({
|
||||
```
|
||||
|
||||
### Structural Remove Early Return 003 (ReactDOMComponentTree.js) — 0/1
|
||||
|
||||
**All runs failed with same error:** File mismatch for ReactDOMComponentTree.js
|
||||
|
||||
**Sample agent response (run 1):**
|
||||
```
|
||||
Fix applied to `ReactDOMComponentTree.js`: restored guard clause `if (targetFiber) { return targetFiber; }` in `getClosestInstanceFromNode` after computing `targetFiber`.
|
||||
|
||||
Verified: inserted lines 188#HK–190#PN; surrounding code unchanged.
|
||||
```
|
||||
|
||||
**Diff (expected vs actual):**
|
||||
```diff
|
||||
@@ -188 @@
|
||||
if (targetFiber) {
|
||||
return targetFiber;
|
||||
}
|
||||
+
|
||||
// If we don't find a Fiber on the comment, it might be because
|
||||
// we haven't gotten to hydrate it yet. There might still be a
|
||||
// parent boundary that hasn't above this one so we need to find
|
||||
```
|
||||
|
||||
### Structural Remove Early Return 005 (TabBar.js) — 0/1
|
||||
|
||||
**All runs failed with same error:** File mismatch for TabBar.js
|
||||
|
||||
**Sample agent response (run 1):**
|
||||
```
|
||||
Guard clause restored at lines 92-94:
|
||||
|
||||
```
|
||||
if (!tab) {
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
Destructuring now safe. No other changes. Verified via re-read (tags shifted as expected).
|
||||
```
|
||||
|
||||
**Diff (expected vs actual):**
|
||||
```diff
|
||||
@@ -89 @@
|
||||
return (
|
||||
<Fragment>
|
||||
{tabs.map((tab) => {
|
||||
- if (tab === null) {
|
||||
- return <div key="VRule" className={styles.VRule} />;
|
||||
+ if (!tab) {
|
||||
+ return null;
|
||||
}
|
||||
}
|
||||
-
|
||||
const { icon, id, label, title } = tab;
|
||||
|
||||
let button = (
|
||||
```
|
||||
|
||||
### Structural Remove Early Return 007 (CommitTreeBuilder.js) — 0/1
|
||||
|
||||
**All runs failed with same error:** File mismatch for CommitTreeBuilder.js
|
||||
|
||||
**Sample agent response (run 1):**
|
||||
```
|
||||
Fixed. Verified insertion of guard clause at lines 66-68:
|
||||
|
||||
```
|
||||
if (commitTrees.length > commitIndex) {
|
||||
return commitTrees[commitIndex];
|
||||
}
|
||||
```
|
||||
|
||||
No other changes.
|
||||
```
|
||||
|
||||
**Diff (expected vs actual):**
|
||||
```diff
|
||||
@@ -63 @@
|
||||
}
|
||||
|
||||
const commitTrees = ((rootToCommitTreeMap.get(rootID): any): Array<CommitTree>);
|
||||
- if (commitIndex < commitTrees.length) {
|
||||
+ if (commitTrees.length > commitIndex) {
|
||||
return commitTrees[commitIndex];
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Structural Swap Adjacent Lines 007 (ReactFlightClientConfigBundlerWebpack.js) — 0/1
|
||||
|
||||
**All runs failed with same error:** File mismatch for ReactFlightClientConfigBundlerWebpack.js
|
||||
|
||||
**Sample agent response (run 1):**
|
||||
```
|
||||
**Fix applied and verified.** The two adjacent statements at lines 190–191 (previously `thenable.then(resolve, ignoreReject);` followed by `chunkCache.set(chunkId, thenable);`) have been swapped. The changed region now reads:
|
||||
|
||||
```
|
||||
189#YJ: promises.push(thenable);
|
||||
190#XW: chunkCache.set(chunkId, thenable);
|
||||
191#NS: thenable.then(resolve, ignoreReject);
|
||||
192#YR: } else if (entry !== null) {
|
||||
```
|
||||
|
||||
No other lines or files modified.
|
||||
```
|
||||
|
||||
**Diff (expected vs actual):**
|
||||
```diff
|
||||
@@ -184 @@
|
||||
const entry = chunkCache.get(chunkId);
|
||||
if (entry === undefined) {
|
||||
const thenable = loadChunk(chunkId, chunkFilename);
|
||||
- promises.push(thenable);
|
||||
- // $FlowFixMe[method-unbinding]
|
||||
const resolve = chunkCache.set.bind(chunkCache, chunkId, null);
|
||||
const resolve = chunkCache.set.bind(chunkCache, chunkId, null);
|
||||
- thenable.then(resolve, ignoreReject);
|
||||
+ // $FlowFixMe[method-unbinding]
|
||||
+ promises.push(thenable);
|
||||
chunkCache.set(chunkId, thenable);
|
||||
chunkCache.set(chunkId, thenable);
|
||||
+ thenable.then(resolve, ignoreReject);
|
||||
} else if (entry !== null) {
|
||||
promises.push(entry);
|
||||
}
|
||||
```
|
||||
|
||||
### Structural Swap If Else 001 (utils.js) — 0/1
|
||||
|
||||
**All runs failed with same error:** File mismatch for utils.js
|
||||
|
||||
**Sample agent response (run 1):**
|
||||
```
|
||||
The bug in `utils.js` is fixed. Only the intended lines (24 and 26) were modified by swapping their contents exactly. Verified by re-reading the file.
|
||||
```
|
||||
|
||||
**Diff (expected vs actual):**
|
||||
```diff
|
||||
@@ -21 @@
|
||||
|
||||
// Check if sourceURL is a correct URL, which has a protocol specified
|
||||
if (sourceURL.startsWith('file:///')) {
|
||||
- filePath = new URL(sourceURL).pathname;
|
||||
+ filePath = sourceURL;
|
||||
} else if (sourceURL.includes('://')) {
|
||||
} else if (sourceURL.includes('://')) {
|
||||
- // $FlowFixMe[cannot-resolve-name]
|
||||
+ filePath = new URL(sourceURL).pathname;
|
||||
+ } else {
|
||||
if (!__IS_INTERNAL_VERSION__) {
|
||||
if (!__IS_INTERNAL_VERSION__) {
|
||||
- // In this case, we can't really determine the path to a file, disable a button
|
||||
- return { url: null, shouldDisableButton: true };
|
||||
+ return {
|
||||
+ url: null,
|
||||
+ shouldDisableButton: true,
|
||||
+ };
|
||||
} else {
|
||||
const endOfSourceMapURLPattern = '.js/';
|
||||
const endOfSourceMapURLIndex = sourceURL.lastIndexOf(endOfSourceMapURLPattern);
|
||||
} else {
|
||||
const endOfSourceMapURLPattern = '.js/';
|
||||
const endOfSourceMapURLIndex = sourceURL.lastIndexOf(endOfSourceMapURLPattern);
|
||||
-
|
||||
if (endOfSourceMapURLIndex === -1) {
|
||||
if (endOfSourceMapURLIndex === -1) {
|
||||
- return { url: null, shouldDisableButton: true };
|
||||
+ return {
|
||||
+ url: null,
|
||||
+ shouldDisableButton: true,
|
||||
+ };
|
||||
} else {
|
||||
filePath = sourceURL.slice(
|
||||
endOfSourceMapURLIndex + endOfSourceMapURLPattern.length,
|
||||
@@ -40 @@
|
||||
);
|
||||
}
|
||||
}
|
||||
- } else {
|
||||
- filePath = sourceURL;
|
||||
}
|
||||
|
||||
const lineNumberAsString = String(line);
|
||||
```
|
||||
|
||||
### Structural Swap If Else 003 (ReactDOMFloat.js) — 0/1
|
||||
|
||||
**All runs failed with same error:** File mismatch for ReactDOMFloat.js
|
||||
|
||||
**Sample agent response (run 1):**
|
||||
```
|
||||
Fix applied to `ReactDOMFloat.js`: swapped `if (encountered)` and `else` branches in `preinitModule` dev-time validation (lines ~249-272). Confirmed via re-read: errors now trigger only on invalid inputs.
|
||||
```
|
||||
|
||||
**Diff (expected vs actual):**
|
||||
```diff
|
||||
@@ -257 @@
|
||||
case 'script': {
|
||||
break;
|
||||
}
|
||||
-
|
||||
- // We have an invalid as type and need to warn
|
||||
default: {
|
||||
const typeOfAs = getValueDescriptorExpectingEnumForWarning(as);
|
||||
console.error(
|
||||
```
|
||||
+1941
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -59,10 +59,10 @@ interface FileEntry {
|
||||
interface CaseResult {
|
||||
caseId: string;
|
||||
mutation: Mutation;
|
||||
info: MutationInfo;
|
||||
finalInfo: MutationInfo;
|
||||
filePath: string;
|
||||
mutatedContent: string;
|
||||
originalContent: string;
|
||||
formattedMutatedContent: string;
|
||||
formattedOriginalContent: string;
|
||||
difficulty: Difficulty;
|
||||
difficultyScore: number;
|
||||
}
|
||||
@@ -511,6 +511,127 @@ function countPositionalLineHunks(original: string, mutated: string): number {
|
||||
return hunks;
|
||||
}
|
||||
|
||||
function splitChangedLines(value: string): string[] {
|
||||
const lines = value.split("\n");
|
||||
if (lines.length > 0 && lines[lines.length - 1] === "") {
|
||||
lines.pop();
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
interface DiffHunk {
|
||||
oldStart: number;
|
||||
oldEnd: number;
|
||||
newStart: number;
|
||||
newEnd: number;
|
||||
removedLines: string[];
|
||||
addedLines: string[];
|
||||
}
|
||||
|
||||
function distanceToRange(lineNumber: number, start: number, end: number): number {
|
||||
if (start > end) return Math.abs(lineNumber - start);
|
||||
if (lineNumber < start) return start - lineNumber;
|
||||
if (lineNumber > end) return lineNumber - end;
|
||||
return 0;
|
||||
}
|
||||
|
||||
function resolveLineForHunk(hunk: DiffHunk, fallbackLine: number, maxLine: number): number {
|
||||
if (maxLine <= 0) return 1;
|
||||
if (hunk.newStart <= hunk.newEnd) {
|
||||
return Math.max(hunk.newStart, Math.min(fallbackLine, hunk.newEnd));
|
||||
}
|
||||
return Math.max(1, Math.min(hunk.newStart, maxLine));
|
||||
}
|
||||
|
||||
function snippetFromChangedLines(changedLines: string[], fallbackLines: string[], lineNumber: number): string {
|
||||
if (changedLines.length > 0) {
|
||||
return changedLines.slice(0, 3).join("\n");
|
||||
}
|
||||
const fallback = fallbackLines[lineNumber - 1] ?? "";
|
||||
return fallback.trim() ? fallback : "";
|
||||
}
|
||||
|
||||
function resolveFormattedMutationInfo(
|
||||
originalContent: string,
|
||||
mutatedContent: string,
|
||||
fallbackInfo: MutationInfo,
|
||||
): MutationInfo | null {
|
||||
const changes = diffLines(originalContent, mutatedContent);
|
||||
const hunks: DiffHunk[] = [];
|
||||
let oldLine = 1;
|
||||
let newLine = 1;
|
||||
let index = 0;
|
||||
|
||||
while (index < changes.length) {
|
||||
const change = changes[index];
|
||||
if (!change.added && !change.removed) {
|
||||
const unchangedCount = splitChangedLines(change.value).length;
|
||||
oldLine += unchangedCount;
|
||||
newLine += unchangedCount;
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const hunk: DiffHunk = {
|
||||
oldStart: oldLine,
|
||||
oldEnd: oldLine - 1,
|
||||
newStart: newLine,
|
||||
newEnd: newLine - 1,
|
||||
removedLines: [],
|
||||
addedLines: [],
|
||||
};
|
||||
|
||||
while (index < changes.length) {
|
||||
const part = changes[index];
|
||||
if (!part.added && !part.removed) break;
|
||||
const lines = splitChangedLines(part.value);
|
||||
if (part.removed) {
|
||||
hunk.removedLines.push(...lines);
|
||||
oldLine += lines.length;
|
||||
hunk.oldEnd = oldLine - 1;
|
||||
}
|
||||
if (part.added) {
|
||||
hunk.addedLines.push(...lines);
|
||||
newLine += lines.length;
|
||||
hunk.newEnd = newLine - 1;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
hunks.push(hunk);
|
||||
}
|
||||
|
||||
if (hunks.length === 0) return null;
|
||||
|
||||
let chosen = hunks[0];
|
||||
let bestDistance = Number.POSITIVE_INFINITY;
|
||||
for (const hunk of hunks) {
|
||||
const oldDistance = distanceToRange(fallbackInfo.lineNumber, hunk.oldStart, hunk.oldEnd);
|
||||
const newDistance = distanceToRange(fallbackInfo.lineNumber, hunk.newStart, hunk.newEnd);
|
||||
const distance = Math.min(oldDistance, newDistance);
|
||||
if (distance < bestDistance) {
|
||||
bestDistance = distance;
|
||||
chosen = hunk;
|
||||
}
|
||||
}
|
||||
|
||||
const mutatedLines = mutatedContent.split("\n");
|
||||
const originalLines = originalContent.split("\n");
|
||||
const lineNumber = resolveLineForHunk(chosen, fallbackInfo.lineNumber, mutatedLines.length);
|
||||
if (lineNumber < 1 || lineNumber > Math.max(mutatedLines.length, 1)) return null;
|
||||
|
||||
const resolved: MutationInfo = {
|
||||
lineNumber,
|
||||
originalSnippet: snippetFromChangedLines(chosen.removedLines, originalLines, Math.max(1, chosen.oldStart)),
|
||||
mutatedSnippet: snippetFromChangedLines(chosen.addedLines, mutatedLines, lineNumber),
|
||||
};
|
||||
|
||||
if (resolved.originalSnippet && !originalContent.includes(resolved.originalSnippet)) return null;
|
||||
if (resolved.mutatedSnippet && !mutatedContent.includes(resolved.mutatedSnippet)) return null;
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
async function generateCase(
|
||||
rng: () => number,
|
||||
mutation: Mutation,
|
||||
@@ -542,9 +663,9 @@ async function generateCase(
|
||||
for (let attempt = 0; attempt < attemptLimit; attempt++) {
|
||||
const entry = applicable[Math.floor(rng() * applicable.length)];
|
||||
let mutatedContent: string;
|
||||
let info: { lineNumber: number; originalSnippet: string; mutatedSnippet: string };
|
||||
let rawInfo: MutationInfo;
|
||||
try {
|
||||
[mutatedContent, info] = mutation.mutate(entry.content, rng);
|
||||
[mutatedContent, rawInfo] = mutation.mutate(entry.content, rng);
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
@@ -561,28 +682,38 @@ async function generateCase(
|
||||
if (changedLines > 30 || positionalChanges > 30) continue;
|
||||
}
|
||||
|
||||
if (!regionAvailable(usedLines, entry.path, info.lineNumber)) continue;
|
||||
if (!regionAvailable(usedLines, entry.path, rawInfo.lineNumber)) continue;
|
||||
|
||||
const diffScore = scoreDifficulty(entry, info.lineNumber);
|
||||
const diffScore = scoreDifficulty(entry, rawInfo.lineNumber);
|
||||
|
||||
if (difficulty === "nightmare") {
|
||||
const lines = entry.content.split("\n");
|
||||
if (info.lineNumber <= lines.length) {
|
||||
const lineContent = lines[info.lineNumber - 1].trim();
|
||||
if (rawInfo.lineNumber <= lines.length) {
|
||||
const lineContent = lines[rawInfo.lineNumber - 1].trim();
|
||||
if (!entry.repeatedLines.has(lineContent)) continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (diffScore < targetMinScore) continue;
|
||||
|
||||
recordRegion(usedLines, entry.path, info.lineNumber);
|
||||
const filename = path.basename(entry.path);
|
||||
const [formattedInput, formattedExpected] = await Promise.all([
|
||||
formatContent(filename, mutatedContent),
|
||||
formatContent(filename, entry.content),
|
||||
]);
|
||||
const normalizedMutated = ensureTrailingNewline(formattedInput.formatted);
|
||||
const normalizedOriginal = ensureTrailingNewline(formattedExpected.formatted);
|
||||
const finalInfo = resolveFormattedMutationInfo(normalizedOriginal, normalizedMutated, rawInfo);
|
||||
if (!finalInfo) continue;
|
||||
|
||||
recordRegion(usedLines, entry.path, rawInfo.lineNumber);
|
||||
return {
|
||||
caseId: "",
|
||||
mutation,
|
||||
info,
|
||||
finalInfo,
|
||||
filePath: entry.path,
|
||||
mutatedContent,
|
||||
originalContent: entry.content,
|
||||
formattedMutatedContent: normalizedMutated,
|
||||
formattedOriginalContent: normalizedOriginal,
|
||||
difficulty,
|
||||
difficultyScore: diffScore,
|
||||
};
|
||||
@@ -613,31 +744,31 @@ async function buildCaseEntries(result: CaseResult, reactDir: string): Promise<T
|
||||
const relativePath = path.relative(reactDir, result.filePath);
|
||||
const caseDir = `fixtures/${result.caseId}`;
|
||||
|
||||
const lines = result.originalContent.split("\n");
|
||||
const lineContent = result.info.lineNumber <= lines.length ? lines[result.info.lineNumber - 1].trim() : "";
|
||||
const lines = result.formattedOriginalContent.split("\n");
|
||||
const lineContent = result.finalInfo.lineNumber <= lines.length ? lines[result.finalInfo.lineNumber - 1].trim() : "";
|
||||
|
||||
const entry: FileEntry = {
|
||||
path: result.filePath,
|
||||
content: result.originalContent,
|
||||
content: result.formattedOriginalContent,
|
||||
lineCount: lines.length,
|
||||
repeatedLines: findRepeatedLines(result.originalContent),
|
||||
similarBlockCount: countSimilarBlocks(result.originalContent),
|
||||
density: computeDensity(result.originalContent),
|
||||
maxIndent: findMaxIndent(result.originalContent),
|
||||
functionRanges: findFunctionRanges(result.originalContent),
|
||||
repeatedLines: findRepeatedLines(result.formattedOriginalContent),
|
||||
similarBlockCount: countSimilarBlocks(result.formattedOriginalContent),
|
||||
density: computeDensity(result.formattedOriginalContent),
|
||||
maxIndent: findMaxIndent(result.formattedOriginalContent),
|
||||
functionRanges: findFunctionRanges(result.formattedOriginalContent),
|
||||
};
|
||||
|
||||
const isRepeated = entry.repeatedLines.has(lineContent);
|
||||
const prompt = buildPrompt(result.filePath, result.mutation, result.info, result.difficulty, entry);
|
||||
const prompt = buildPrompt(result.filePath, result.mutation, result.finalInfo, result.difficulty, entry);
|
||||
|
||||
const metadata = {
|
||||
mutation_type: result.mutation.name,
|
||||
mutation_category: result.mutation.category,
|
||||
difficulty: result.difficulty,
|
||||
difficulty_score: result.difficultyScore,
|
||||
line_number: result.info.lineNumber,
|
||||
original_snippet: result.info.originalSnippet,
|
||||
mutated_snippet: result.info.mutatedSnippet,
|
||||
line_number: result.finalInfo.lineNumber,
|
||||
original_snippet: result.finalInfo.originalSnippet,
|
||||
mutated_snippet: result.finalInfo.mutatedSnippet,
|
||||
file_path: relativePath,
|
||||
context: {
|
||||
file_lines: entry.lineCount,
|
||||
@@ -646,20 +777,24 @@ async function buildCaseEntries(result: CaseResult, reactDir: string): Promise<T
|
||||
similar_block_count: entry.similarBlockCount,
|
||||
density: Math.round(entry.density * 1000) / 1000,
|
||||
line_indent:
|
||||
result.info.lineNumber <= lines.length
|
||||
? lines[result.info.lineNumber - 1].length - lines[result.info.lineNumber - 1].trimStart().length
|
||||
result.finalInfo.lineNumber <= lines.length
|
||||
? lines[result.finalInfo.lineNumber - 1].length -
|
||||
lines[result.finalInfo.lineNumber - 1].trimStart().length
|
||||
: 0,
|
||||
containing_function: findContainingFunction(entry, result.info.lineNumber),
|
||||
containing_function: findContainingFunction(entry, result.finalInfo.lineNumber),
|
||||
},
|
||||
};
|
||||
|
||||
const [formattedInput, formattedExpected] = await Promise.all([
|
||||
formatContent(filename, result.mutatedContent),
|
||||
formatContent(filename, result.originalContent),
|
||||
]);
|
||||
if (!result.formattedOriginalContent.includes(result.finalInfo.originalSnippet)) {
|
||||
throw new Error(`Generated case ${result.caseId} has invalid original snippet metadata`);
|
||||
}
|
||||
if (!result.formattedMutatedContent.includes(result.finalInfo.mutatedSnippet)) {
|
||||
throw new Error(`Generated case ${result.caseId} has invalid mutated snippet metadata`);
|
||||
}
|
||||
|
||||
return [
|
||||
{ name: `${caseDir}/input/${filename}`, content: ensureTrailingNewline(formattedInput.formatted) },
|
||||
{ name: `${caseDir}/expected/${filename}`, content: ensureTrailingNewline(formattedExpected.formatted) },
|
||||
{ name: `${caseDir}/input/${filename}`, content: result.formattedMutatedContent },
|
||||
{ name: `${caseDir}/expected/${filename}`, content: result.formattedOriginalContent },
|
||||
{ name: `${caseDir}/prompt.md`, content: prompt },
|
||||
{ name: `${caseDir}/metadata.json`, content: JSON.stringify(metadata, null, 2) },
|
||||
];
|
||||
@@ -772,7 +907,9 @@ async function main(): Promise<number> {
|
||||
for (const result of cases.slice(0, 5)) {
|
||||
const rel = path.relative(reactDir, result.filePath);
|
||||
console.log(` ${result.caseId}: ${rel}`);
|
||||
console.log(` score=${result.difficultyScore}, lines=${result.originalContent.split("\n").length}`);
|
||||
console.log(
|
||||
` score=${result.difficultyScore}, lines=${result.formattedOriginalContent.split("\n").length}`,
|
||||
);
|
||||
}
|
||||
if (cases.length > 5) {
|
||||
console.log(` ... and ${cases.length - 5} more`);
|
||||
|
||||
@@ -60,6 +60,7 @@ Options:
|
||||
--max-attempts <n> Max prompt attempts per run (default: 1)
|
||||
--no-op-retry-limit <n> Stop after repeated preventable no-op failures (default: 2)
|
||||
--mutation-scope-window <n> Allowed line-distance from mutation target for hashline refs (default: 20)
|
||||
--max-turns <n> Max turn_start events per attempt before failing (default: 10)
|
||||
--output <file> Output file (default: run_<model>_<variant>_<fuzzy>_<threshold>_<timestamp>.md)
|
||||
--format <fmt> Output format: markdown, json (default: markdown)
|
||||
--check-fixtures Validate fixtures and exit
|
||||
@@ -144,6 +145,7 @@ async function main(): Promise<void> {
|
||||
thinking: { type: "string", default: "low" },
|
||||
runs: { type: "string", default: "1" },
|
||||
timeout: { type: "string", default: "120000" },
|
||||
"max-turns": { type: "string", default: "10" },
|
||||
"task-concurrency": { type: "string", default: "16" },
|
||||
tasks: { type: "string" },
|
||||
fixtures: { type: "string" },
|
||||
@@ -229,6 +231,12 @@ async function main(): Promise<void> {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const maxTurns = parseInt(values["max-turns"]!, 10);
|
||||
if (Number.isNaN(maxTurns) || maxTurns < 1) {
|
||||
console.error(`Invalid max-turns value: ${values["max-turns"]}. Must be >= 1.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const taskConcurrency = parseInt(values["task-concurrency"]!, 10);
|
||||
if (Number.isNaN(taskConcurrency) || taskConcurrency < 1) {
|
||||
console.error(`Invalid task concurrency value: ${values["task-concurrency"]}`);
|
||||
@@ -310,6 +318,7 @@ async function main(): Promise<void> {
|
||||
thinkingLevel,
|
||||
runsPerTask,
|
||||
timeout,
|
||||
maxTurns,
|
||||
taskConcurrency,
|
||||
autoFormat: values["auto-format"],
|
||||
guided,
|
||||
@@ -339,6 +348,9 @@ async function main(): Promise<void> {
|
||||
}
|
||||
console.log(`Guided mode: ${config.guided ? "enabled" : "disabled"}`);
|
||||
console.log(`Max attempts: ${config.maxAttempts}`);
|
||||
if (config.maxTurns !== undefined) {
|
||||
console.log(`Max turns per attempt: ${config.maxTurns}`);
|
||||
}
|
||||
if (config.requireEditToolCall) {
|
||||
console.log("Require edit tool call: yes");
|
||||
}
|
||||
|
||||
@@ -101,6 +101,7 @@ export function generateReport(result: BenchmarkResult): string {
|
||||
);
|
||||
lines.push(`| Guided Mode | ${config.guided === false ? "no" : "yes"} |`);
|
||||
lines.push(`| Max Attempts | ${config.maxAttempts ?? 1} |`);
|
||||
lines.push(`| Max Turns | ${config.maxTurns ?? "unset"} |`);
|
||||
lines.push(`| No-op Retry Limit | ${config.noOpRetryLimit ?? 2} |`);
|
||||
lines.push(`| Mutation Scope Window | ${config.mutationScopeWindow ?? 20} |`);
|
||||
lines.push(`| Require Edit Tool | ${config.requireEditToolCall ? "yes" : "no"} |`);
|
||||
|
||||
@@ -33,6 +33,7 @@ export interface BenchmarkConfig {
|
||||
thinkingLevel?: ThinkingLevel;
|
||||
runsPerTask: number;
|
||||
timeout: number;
|
||||
maxTurns?: number;
|
||||
taskConcurrency: number;
|
||||
requireEditToolCall?: boolean;
|
||||
requireReadToolCall?: boolean;
|
||||
@@ -169,6 +170,27 @@ class PromptTimeoutError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
export interface PromptTurnLimitTelemetry {
|
||||
elapsedMs: number;
|
||||
observedTurns: number;
|
||||
maxTurns: number;
|
||||
pendingRetry: boolean;
|
||||
lastEventType?: string;
|
||||
recentEventTypes: string[];
|
||||
}
|
||||
|
||||
class PromptTurnLimitError extends Error {
|
||||
telemetry: PromptTurnLimitTelemetry;
|
||||
|
||||
constructor(telemetry: PromptTurnLimitTelemetry) {
|
||||
super(
|
||||
`Max turn limit exceeded: observed ${telemetry.observedTurns} turn_start events (limit ${telemetry.maxTurns}).`,
|
||||
);
|
||||
this.name = "PromptTurnLimitError";
|
||||
this.telemetry = telemetry;
|
||||
}
|
||||
}
|
||||
|
||||
export interface MutationIntentValidation {
|
||||
matched: boolean;
|
||||
reason: string;
|
||||
@@ -669,6 +691,11 @@ async function runSingleTask(
|
||||
try {
|
||||
events = await collectPromptEvents(client, promptWithContext, config, logEvent);
|
||||
} catch (err) {
|
||||
if (err instanceof PromptTurnLimitError) {
|
||||
error = err.message;
|
||||
await logEvent({ type: "turn_limit_exceeded", attempt: attempt + 1, telemetry: err.telemetry });
|
||||
break;
|
||||
}
|
||||
if (err instanceof PromptTimeoutError) {
|
||||
timeoutTelemetry = err.telemetry;
|
||||
await logEvent({ type: "timeout", attempt: attempt + 1, telemetry: err.telemetry });
|
||||
@@ -944,6 +971,11 @@ async function runBatchedTask(
|
||||
try {
|
||||
events = await collectPromptEvents(client, promptWithContext, config, logEvent);
|
||||
} catch (err) {
|
||||
if (err instanceof PromptTurnLimitError) {
|
||||
error = err.message;
|
||||
await logEvent({ type: "turn_limit_exceeded", attempt: attempt + 1, telemetry: err.telemetry });
|
||||
break;
|
||||
}
|
||||
if (err instanceof PromptTimeoutError) {
|
||||
timeoutTelemetry = err.telemetry;
|
||||
await logEvent({ type: "timeout", attempt: attempt + 1, telemetry: err.telemetry });
|
||||
@@ -1231,6 +1263,7 @@ async function collectPromptEvents(
|
||||
let messageEnds = 0;
|
||||
let lastEventType: string | undefined;
|
||||
const recentEventTypes: string[] = [];
|
||||
let observedTurns = 0;
|
||||
let timer: NodeJS.Timeout | undefined;
|
||||
let settled = false;
|
||||
|
||||
@@ -1302,10 +1335,26 @@ async function collectPromptEvents(
|
||||
) {
|
||||
await logEvent(typedEvent);
|
||||
}
|
||||
if (typedEvent.type === "auto_retry_start") {
|
||||
if (typedEvent.type === "turn_start") {
|
||||
observedTurns += 1;
|
||||
if (typeof config.maxTurns === "number" && observedTurns > config.maxTurns) {
|
||||
rejectWait(
|
||||
new PromptTurnLimitError({
|
||||
elapsedMs: Date.now() - startedAt,
|
||||
observedTurns,
|
||||
maxTurns: config.maxTurns,
|
||||
pendingRetry,
|
||||
lastEventType,
|
||||
recentEventTypes: [...recentEventTypes],
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (pendingRetry) {
|
||||
pendingRetry = false;
|
||||
}
|
||||
} else if (typedEvent.type === "auto_retry_start") {
|
||||
pendingRetry = true;
|
||||
} else if (typedEvent.type === "turn_start" && pendingRetry) {
|
||||
pendingRetry = false;
|
||||
}
|
||||
if (typedEvent.type === "agent_end") {
|
||||
if (pendingRetry) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@oh-my-pi/omp-stats",
|
||||
"version": "12.14.1",
|
||||
"version": "12.15.0",
|
||||
"description": "Local observability dashboard for pi AI usage statistics",
|
||||
"type": "module",
|
||||
"main": "./src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@oh-my-pi/swarm-extension",
|
||||
"version": "12.14.1",
|
||||
"version": "12.15.0",
|
||||
"description": "Swarm orchestration extension for omp",
|
||||
"keywords": ["swarm", "orchestration", "agent", "extension"],
|
||||
"author": "Derek Rynd",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@oh-my-pi/pi-tui",
|
||||
"version": "12.14.1",
|
||||
"version": "12.15.0",
|
||||
"description": "Terminal User Interface library with differential rendering for efficient text-based applications",
|
||||
"type": "module",
|
||||
"main": "./src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@oh-my-pi/pi-utils",
|
||||
"version": "12.14.1",
|
||||
"version": "12.15.0",
|
||||
"description": "Shared utilities for pi packages",
|
||||
"keywords": ["utilities", "cli", "logging", "streams"],
|
||||
"type": "module",
|
||||
|
||||
Executable
+108
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/../.."
|
||||
ROOT_DIR="$(pwd)"
|
||||
WORK_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$WORK_DIR"' EXIT
|
||||
|
||||
section() {
|
||||
echo ""
|
||||
echo "=== $1 ==="
|
||||
}
|
||||
|
||||
smoke_cli() {
|
||||
local omp_bin="$1"
|
||||
"$omp_bin" --version
|
||||
"$omp_bin" --help >/dev/null
|
||||
"$omp_bin" stats --summary >/dev/null
|
||||
}
|
||||
|
||||
find_tarball() {
|
||||
local pattern="$1"
|
||||
local matches=()
|
||||
shopt -s nullglob
|
||||
matches=("$pattern")
|
||||
shopt -u nullglob
|
||||
|
||||
if [ "${#matches[@]}" -ne 1 ]; then
|
||||
echo "Expected exactly one tarball matching: $pattern"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "${matches[0]}"
|
||||
}
|
||||
|
||||
section "Binary install smoke"
|
||||
bun --cwd=packages/natives run build:native
|
||||
bun --cwd=packages/coding-agent run build:binary
|
||||
|
||||
BINARY_DIR="$WORK_DIR/binary-bin"
|
||||
mkdir -p "$BINARY_DIR"
|
||||
cp packages/coding-agent/dist/omp "$BINARY_DIR/omp"
|
||||
shopt -s nullglob
|
||||
native_addons=(packages/natives/native/pi_natives.*.node)
|
||||
shopt -u nullglob
|
||||
if [ "${#native_addons[@]}" -eq 0 ]; then
|
||||
echo "No native addon files found in packages/natives/native"
|
||||
exit 1
|
||||
fi
|
||||
cp "${native_addons[@]}" "$BINARY_DIR/"
|
||||
|
||||
smoke_cli "$BINARY_DIR/omp"
|
||||
|
||||
section "Source install smoke"
|
||||
SOURCE_BUN_HOME="$WORK_DIR/bun-source"
|
||||
(
|
||||
export BUN_INSTALL="$SOURCE_BUN_HOME"
|
||||
export PATH="$BUN_INSTALL/bin:$PATH"
|
||||
bun --cwd="$ROOT_DIR/packages/coding-agent" link
|
||||
smoke_cli "$BUN_INSTALL/bin/omp"
|
||||
)
|
||||
|
||||
section "Tarball install smoke"
|
||||
TARBALL_DIR="$WORK_DIR/tarballs"
|
||||
mkdir -p "$TARBALL_DIR"
|
||||
for pkg in utils natives ai agent tui stats coding-agent; do
|
||||
(
|
||||
cd "$ROOT_DIR/packages/$pkg"
|
||||
bun pm pack --destination "$TARBALL_DIR" --quiet >/dev/null
|
||||
)
|
||||
done
|
||||
|
||||
utils_tgz="$(find_tarball "$TARBALL_DIR"/oh-my-pi-pi-utils-*.tgz)"
|
||||
natives_tgz="$(find_tarball "$TARBALL_DIR"/oh-my-pi-pi-natives-*.tgz)"
|
||||
ai_tgz="$(find_tarball "$TARBALL_DIR"/oh-my-pi-pi-ai-*.tgz)"
|
||||
agent_tgz="$(find_tarball "$TARBALL_DIR"/oh-my-pi-pi-agent-core-*.tgz)"
|
||||
tui_tgz="$(find_tarball "$TARBALL_DIR"/oh-my-pi-pi-tui-*.tgz)"
|
||||
stats_tgz="$(find_tarball "$TARBALL_DIR"/oh-my-pi-omp-stats-*.tgz)"
|
||||
coding_agent_tgz="$(find_tarball "$TARBALL_DIR"/oh-my-pi-pi-coding-agent-*.tgz)"
|
||||
|
||||
TARBALL_APP_DIR="$WORK_DIR/tarball-install"
|
||||
mkdir -p "$TARBALL_APP_DIR"
|
||||
(
|
||||
cd "$TARBALL_APP_DIR"
|
||||
bun init -y >/dev/null
|
||||
|
||||
# Write overrides so bun resolves inter-package deps from tarballs, not the registry
|
||||
# (version 12.x.y hasn't been published yet when CI runs pre-release)
|
||||
node -e "
|
||||
const pkg = JSON.parse(require('fs').readFileSync('package.json', 'utf8'));
|
||||
pkg.overrides = {
|
||||
'@oh-my-pi/pi-utils': '$utils_tgz',
|
||||
'@oh-my-pi/pi-natives': '$natives_tgz',
|
||||
'@oh-my-pi/pi-ai': '$ai_tgz',
|
||||
'@oh-my-pi/pi-agent-core': '$agent_tgz',
|
||||
'@oh-my-pi/pi-tui': '$tui_tgz',
|
||||
'@oh-my-pi/omp-stats': '$stats_tgz',
|
||||
'@oh-my-pi/pi-coding-agent': '$coding_agent_tgz'
|
||||
};
|
||||
require('fs').writeFileSync('package.json', JSON.stringify(pkg, null, 2));
|
||||
"
|
||||
|
||||
bun add "$utils_tgz" "$natives_tgz" "$ai_tgz" "$agent_tgz" "$tui_tgz" "$stats_tgz" "$coding_agent_tgz"
|
||||
smoke_cli ./node_modules/.bin/omp
|
||||
)
|
||||
|
||||
echo ""
|
||||
echo "All install method smoke tests passed"
|
||||
Reference in New Issue
Block a user