Commit Graph

157 Commits

Author SHA1 Message Date
can1357 e8a8c2402c fix(coding-agent): prevented asynchronous write races in plan reviews
- Ensures in-memory overlay edits are durably written to the plan file before proceeding with approval.
- Avoids asynchronous write races by awaiting the final plan file serialization.
- Aligns synthetic approved-plan prompts with reference-only expectations.
2026-07-02 02:19:26 +02:00
can1357 620304c070 Merge remote-tracking branch 'origin/farm/86d90585/fix-stash-pop-index-corruption' 2026-07-02 02:02:17 +02:00
can1357 9a49612c27 fix(coding-agent): prevented concurrency permit leakage on cancelled queued jobs
- Implemented safe `releasePermit` helper to track whether a concurrency permit has been acquired before releasing.
- Guarded against double-releasing or releasing unacquired semaphore permits during queued job cancellation or abort events.
- Added comprehensive unit tests validating concurrency cap enforcement when queued jobs are cancelled.
2026-07-02 01:51:09 +02:00
roboomp e109883ee2 fix(coding-agent/task): treated stash cleanup paths literally
Failed stash-pop cleanup now invokes git clean with literal pathspecs for
stash-derived untracked paths. Filenames such as `:(glob)*` are valid POSIX
filenames and valid Git pathspec magic; passing them as ordinary pathspecs with
`-x` could delete unrelated ignored artifacts that were never stashed and are
not recoverable from the preserved stash.

Extend the fallback regression with a literal `:(glob)*` stash file and an
ignored `build.log` that must survive cleanup.

Fixes #4175
2026-07-01 22:03:23 +00:00
roboomp 4d471b1aa4 fix(coding-agent/task): removed ignored restored stash files after pop failure
When a task branch adds ignore rules for a path that was untracked in the
user's stashed WIP, a failed stash pop can restore the file and then leave it
hidden from normal status after reset. Default `git clean -fd -- <path>` does
not remove ignored files, so the partial restore could still leak into later
isolated task baselines.

Add an includeIgnored clean mode and use `git clean -fdx -- <stash path>` for
failed stash-pop cleanup. Extend the fallback regression so the task branch adds
.gitignore for the restored untracked path and verify both normal and ignored
status return clean.

Fixes #4175
2026-07-01 21:52:29 +00:00
roboomp abd0e2bdcc fix(coding-agent/task): cleaned untracked files after failed stash pop
A failed `git stash pop --index` can restore unrelated untracked files before
exiting on a tracked conflict while still preserving the stash entry. The
previous fallback only reset tracked/index state, leaving those untracked files
in the working tree for subsequent task baselines.

Record the top stash entry's untracked paths before popping and clean exactly
those paths if the pop fails after preflight. Add a regression that forces the
fallback branch and verifies the worktree returns clean with the stash preserved.

Fixes #4175
2026-07-01 21:44:31 +00:00
roboomp ffd6a57d2f fix(coding-agent/task): kept .git/index clean when stash pop conflicts after task merge
mergeTaskBranches and applyNestedPatches both stashed dirty WIP, cherry-picked
task branches, then called `git stash pop` in a finally block. On conflict git
left stage 1/2/3 unmerged entries in .git/index with no MERGE_HEAD to abort;
neither call cleaned up. The corrupted index persisted indefinitely, and every
subsequent overlay-isolated task inherited it through the lower layer —
captureRepoDeltaPatch then emitted `diff --cc` (combined merge format) that
git apply rejects with "No valid patches in input", failing every downstream
task merge with 'Branch merge failed before a task branch could be created'.

Fix at the git API level: git.stash.tryPop now runs `git apply --3way --check`
on `git stash show -p --binary stash@{0}` before popping (`--3way` matches
what git stash pop does internally, so context that drifted after cherry-pick
is still accepted). Preflight failure short-circuits — stash entry preserved,
index untouched. Preflight pass falls through to pop; if pop still leaves
unmerged entries (mode-only or delete/modify conflicts the preflight can miss),
a `reset --hard HEAD" fallback restores the merged HEAD without losing the
cherry-picked commits (stash is preserved by git on failed pop, so the user's
WIP stays recoverable).

Both call sites now share this contract via git.stash.tryPop.

Fixes #4175
2026-07-01 21:36:17 +00:00
can1357 5f1ed0fcde chore: reformat 2026-07-01 23:14:36 +02:00
can1357 88ae6c5314 fix(task): refresh spawn semaphore on release 2026-07-01 22:09:29 +02:00
can1357 1cb8608a58 Merge PR #3896: fix(task): respect task.maxConcurrency + task.maxRecursionDepth across spawn paths (@roboomp)
# Conflicts:
#	packages/coding-agent/src/eval/__tests__/agent-bridge.test.ts
#	packages/coding-agent/src/eval/agent-bridge.ts
2026-07-01 22:07:28 +02:00
can1357 87a53cbe0d Merge PR #4137: fix(agent): handle already-applied patch-mode merges (@roboomp) 2026-07-01 21:53:18 +02:00
can1357 88e3e77f3e Merge PR #3927: fix(agent): reject stale yield labels for override schemas (@roboomp) 2026-07-01 21:50:54 +02:00
roboomp f2c436eb00 style: bun run fix 2026-07-01 12:17:49 +00:00
roboomp cd692bcb02 fix(agent): required forward-check failure for patch-mode no-op
Reverse-check alone can theoretically succeed via git-apply fuzz when the file carries the postimage at another location, so treating it as sufficient risked silently dropping a task patch whose intended hunk still needed to run.

Required both `--reverse --check` to succeed AND forward `--check` to fail before declaring a no-op. When both check directions succeed (ambiguous), the merge falls through to a forward apply instead of skipping — mirroring the pre-idempotence behavior. Both checks are read-only, so no worktree writes on ambiguity.

Added a spy-based regression that forces the ambiguous case and asserts the merge applies forward instead of silently dropping the change.
2026-07-01 12:17:42 +00:00
roboomp f474fa0e11 fix(agent): detected patch-mode idempotence via reverse-check
git apply --3way --check exits 0 even when the real apply would write conflict markers and unmerged index stages, so the previous fix left the worktree dirty on conflicting patches while only flipping changesApplied to false.

Dropped --3way for patch-mode merge and used a --reverse --check probe instead: it succeeds only when the target state is already present (true no-op) and reads without touching the worktree. Conflicts fall through to the normal --check + apply path, which rejects them before writing anything.

Added regression coverage for the conflict scenario asserting the worktree stays clean, and for the fresh apply path.
2026-07-01 12:06:06 +00:00
roboomp 4c9f18972c style: bun run fix 2026-07-01 11:59:11 +00:00
roboomp 66052863bb fix(agent): handled already-applied patch merges
Use git apply --3way for patch-mode isolated merge checks and applies so diff-tree patches that are already present are accepted as no-ops.

Added regression coverage for a clean already-applied binary/full-index patch.

Fixes #4135
2026-07-01 11:58:52 +00:00
roboomp 8e2dfcc43e fix(agent): routed acquire-time abort through queued-spawn settled path
When a batched task spawn is cancelled while still queued behind task.maxConcurrency the semaphore now rejects acquire(), but the previous patch let the abort throw past the aborted handler so progress.status and onSettled never fired and buildAsyncDetails kept reporting the batch as running. The wrapper now records whether the slot was held, funnels both acquire-time and post-acquire aborts through the same aborted branch (releasing only when held), and a batch regression test pins the contract.

Fixes #3930
2026-06-30 23:46:19 +00:00
roboomp 3bc8f995f6 fix(agent): bounded async job disposal
Made AsyncJobManager.dispose honor its timeout while waiting for cancelled jobs, and passed task abort signals into spawn semaphore waits.

Fixes #3930
2026-06-30 23:37:11 +00:00
roboomp 00ef58f843 fix(agent): steered override-schema subagents
- Marked eval agent schema calls as caller overrides so subagent prompts can revoke native output/yield instructions.\n- Added override-schema prompt guidance telling agents to ignore conflicting native output labels and terminal-yield the caller schema object.\n- Added prompt coverage for the override notice.\n\nRefs #3926
2026-06-30 22:30:15 +00:00
can1357 e6140f1b55 Merge remote-tracking branch 'origin/farm/f8778fdf/ruff-lsp-windows-venv-scripts' 2026-06-30 23:57:27 +02:00
roboomp e4561d64fa fix(coding-agent): restored subagent thinking precedence
Agent frontmatter thinkingLevel now wins over model role suffix thinking when both are configured.

Fixes #3915
2026-06-30 20:15:04 +00:00
can1357 bdfc21df43 feat(coding-agent/tiny): added llama3.2:3b local tiny model option
- Added the `llama3.2:3b` model configuration pointing to the quantized `onnx-community/Llama-3.2-3B-Instruct-ONNX` repository.
- Registered the model in both the available local models registry and list of valid memory model values.
- Documented the new option as a shipped local model choice in the documentation and changelog.
2026-06-30 17:59:10 +02:00
can1357 6c1152647c refactor(coding-agent): renamed the quick_task subagent to sonic
- Renamed references to the `quick_task` subagent to `sonic` across docs, agent definitions, prompts, and test files.
- Updated the parallel file analysis tool to spawn `sonic` subagents instead of `quick_task`.
- Documented the breaking change in the changelog along with additions and removals of other built-in subagents.
2026-06-30 16:16:39 +02:00
roboomp 1b9c6be129 fix(task): respect task.maxConcurrency + task.maxRecursionDepth across spawn paths
Three independent paths bypassed the user's subagent caps:

1. TaskTool.#getSpawnSemaphore sized the spawn semaphore from
   task.maxConcurrency only on first use and never re-read the setting,
   so lowering the cap mid-session left every later spawn running
   against the old ceiling. Resize the live semaphore against the
   current setting on each acquire.

2. The task tool prompt threaded MAX_CONCURRENCY through to the
   template but never rendered it. A model with task.maxConcurrency=1
   could still emit oversized tasks[] batches that registered
   immediately and piled up behind the semaphore. Render a 'Concurrency
   cap' directive in task.md whenever the setting is bounded.

3. The eval agent() bridge's assertDepthAllowed gated only against
   the hardcoded EVAL_AGENT_MAX_DEPTH=3 and ignored
   task.maxRecursionDepth, so a user-tightened recursion limit
   (0='None', 1='Single') still let cell-spawned subagents recurse
   to depth 3. Mirror the task tool's canSpawnAtDepth gate, clamped
   by the hard ceiling.

Fixes #3895
2026-06-30 11:37:58 +00:00
can1357 8cd23ebd15 merge #3844: 3-way dirty-context fallback for isolated branch merges
# Conflicts:
#	packages/coding-agent/src/task/worktree.ts
#	packages/coding-agent/test/task/worktree.test.ts
2026-06-30 03:03:43 +02:00
roboomp d120ba6b7d fix(coding-agent): filter baseline wip from preserved agent commits
Dirty isolated baselines can be accidentally committed by subagents that run git add -A. Fetching the raw isolation HEAD then cherry-picking the range would replay that baseline WIP into parent history.

Add a dirty-baseline replay path that rewrites each agent commit against the captured baseline tree, preserving the agent commit message and author while excluding staged, unstaged, and untracked changes that existed before isolation started. Clean baselines still use the raw git fetch path, and nested-only changes keep returning patches without creating an empty root branch.

Add a regression for baseline staged + untracked WIP committed by the agent, asserting the task branch contains only the agent file and parent WIP remains staged/untracked after merge.

Fixes #3842
2026-06-30 00:28:27 +00:00
roboomp da715aae7c fix(coding-agent): preserve agent commits across isolated branch merges
When an isolated task agent commits its own changes before yielding, the
harness used to collapse the captured delta into one AI-summarized commit
and discard the agent's commit messages and authorship entirely. This
violated commit discipline for agentic swarms — multiple logical commits
("fix bug" + "add test") became a single opaque commit, and the
agent's commit object (which lived in isolation/.git/objects under
overlayfs/rcopy) was lost when cleanupIsolation tore down the overlay.

commitToBranch now detects when isolation HEAD moved past baseline.root
.headCommit. When it has, the function git-fetches the agent's HEAD into
the parent repo as omp/task/${taskId} so the commit objects survive
cleanupIsolation, and stamps the captured baselineSha onto the returned
CommitToBranchResult. mergeTaskBranches cherry-picks the inclusive range
baseSha..branchName when baseSha is provided, replaying each agent
commit verbatim with its original message and author. Any uncommitted
leftover (staged, unstaged, untracked) on top of the agent's last commit
becomes one trailing AI-summarized commit on the same branch.

Falls back to the legacy single-commit path when the agent never moved
HEAD (purely dirty working tree); existing patch-mode flow is untouched.

Fixes #3842
2026-06-30 00:13:07 +00:00
roboomp 4b98211c64 fix(coding-agent): fixed dirty isolated branch merges
Applied isolated branch patches with three-way fallback when unrelated parent dirt appears in patch context.

Surfaced branch preparation failures instead of reporting no changes.

Fixes #3841
2026-06-30 00:09:34 +00:00
can1357 d03ea52891 refactor(ai): improved provider in-flight lease signal routing
- Update `releaseProviderInFlightLease` to signal into the specific directory path associated with the lease rather than recomputing it from the root.
- Introduce `signalProviderInFlightWaitersInDir` to decoupling waking waiters from global provider path resolution.
- Remove redundant tests from `coding-agent`.
2026-06-29 20:04:24 +02:00
roboomp 58c0a305d6 fix(agent): shortened isolated task paths
Used compact hashed isolation directory segments and the short m mount dir so long task ids are not copied into subagent working paths.

Kept worktree cleanup compatible with legacy merged task-isolation directories.

Fixes #3756
2026-06-28 21:25:20 +00:00
can1357 ff6fd3df08 test(coding-agent): strengthened coding agent reliability and pathing
- Collapsed git worktree path in status line to project name with icon.
- Fixed out-of-workspace file edits by including the full path in headers.
- Fixed structured output schema violations by correctly handling payload nesting in terminal yields.
- Added test suites for git worktree logic, out-of-cwd reading, and subagent output serialization.
2026-06-28 22:50:31 +02:00
can1357 2cf28f972e feat(coding-agent): added logic to assembleYieldResult to
- Added logic to `assembleYieldResult` to automatically accumulate incremental yields into arrays for schema-identified array properties.
- Updated `YieldTool` to bypass schema validation for incremental stream yields, allowing partial data emissions that don't satisfy the full output schema yet.
- Enhanced `YieldTool` parameter declaration to remove blocking top-level JSON schema combinators, ensuring compatibility with strict-mode providers (OpenAI/Codex).
- Updated `parseYieldType` to gracefully handle `null` type values emitted by strict providers for untyped final yields.
- Added regression tests for array-valued findings alignment and strict-mode tool schema compatibility.
2026-06-28 09:05:37 +02:00
can1357 d8bf177af7 refactor(coding-agent/task): moved taskToolRenderer to separate module
- Extracted taskToolRenderer to a dedicated renderer file to resolve circular dependencies.
- Updated all references to the renderer to point to the new location.
2026-06-28 07:27:02 +02:00
can1357 289dd770c8 feat(coding-agent): reworked subagent yields for incremental results
- Extended `tools/yield.ts` with typed incremental sections, raw last-turn terminal results, and updated yield guidance in the subagent system prompts.
- Reworked `task/executor.ts`, `task/render.ts`, and `task/types.ts` to assemble typed yield sections, render reviewer results from incremental yield data, and preserve the typed result shape.
- Switched `prompts/agents/reviewer.md`, `review-request.md`, and `review-custom-request.md` from `report_finding` calls to incremental `yield` sections.
- Added incremental-yield coverage in `test/task/executor-warnings.test.ts`, `test/task/render-yield-shape.test.ts`, `test/tools/yield-extraction.test.ts`, and `test/tools/yield.test.ts`.
2026-06-28 07:27:01 +02:00
can1357 a555c4c6ef style: biome format #3354 test cleanup 2026-06-27 02:06:39 +02:00
can1357 74d7dfd2fa Merge PR #3354: fix: migrate coding-agent tests to removeWithRetries (@oldschoola) 2026-06-27 02:06:38 +02:00
can1357 2c20a2d368 Fix subagent yield abort cleanup
(cherry picked from commit e22673537f7cc95920cd4ec75d2a4b36d81d7626)
2026-06-26 23:43:03 +02:00
oldschoola a2854ba768 fix: migrate coding-agent tests from fs.rm to removeWithRetries
Migrate 203 test files (356 call sites) from fs.rm/fs.rmSync to
removeWithRetries/removeSyncWithRetries to reduce EBUSY test failures
on Windows. removeWithRetries is now exported from @oh-my-pi/pi-utils.

The migration uses a regex-based approach that:
- Replaces fs.rm(path, { recursive, force }) → removeWithRetries(path)
- Replaces fs.rmSync(path, { recursive, force }) → removeSyncWithRetries(path)
- Replaces fs.rm(path) → removeWithRetries(path) (no options)
- Skips fs.rm/fs.rmSync inside template literals (bun --eval scripts)
- Adds imports to existing @oh-my-pi/pi-utils import or creates new one
- Removes unused fs imports where fs.rm was the only fs usage (4 files)
2026-06-23 15:28:05 -07:00
roboomp d5677cbc1b style: bun run fix 2026-06-23 10:41:59 +00:00
roboomp e4c52de24d fix(task): normalize fractional spawn concurrency
Normalize the configured semaphore max before deciding whether the spawn

limit is bounded. Fractional values between 0 and 1 now truncate to 0 and

fall through to the unbounded path instead of storing 0 and deadlocking

the first acquire.

Fixes #3305
2026-06-23 10:41:51 +00:00
roboomp 296125cce9 fix(task): treat maxConcurrency 0 as unbounded in spawn semaphore
The session-scoped spawn Semaphore clamped its max via Math.max(1, max), so

task.maxConcurrency: 0 — labeled 'Unlimited' in the settings UI — serialized

subagent spawns one at a time instead of releasing every eligible seat.

The constructor now treats max <= 0 (and any non-finite input) as unbounded

via Number.POSITIVE_INFINITY, so the existing 'current < max' check naturally

permits every acquire. Mirrors the eval parallel()/pipeline() worker-pool

semantics (runEvalConcurrency in eval/concurrency-bridge.ts), which already

treats 0 as 'run every item at once'.

Fixes #3305
2026-06-23 10:37:34 +00:00
roboomp 5b6e9f904d fix(eval): paused timeout over baseline capture and surfaced nested stash-restore failures
Two Codex P2 findings landed against 978d2a76d0 that were not in the previously delivered review event:

1) prepareIsolationContext() (which runs captureBaseline → walks nested repos and untracked diffs) was running OUTSIDE withBridgeTimeoutPause; on dirty/large repos the baseline walk can exceed the eval idle timeout while the runtime is blocked. Moved the prep call into the pause closure so the watchdog is suspended for the whole bridge call from prep through cleanup.

2) applyNestedPatches() swallowed git stash pop failures with only a logger.warn, so a stash-pop conflict after a successful agent commit was invisible to the workflow. Changed the helper to return Promise<string[]> of warnings; applyEligibleNestedPatches now wraps them in a <system-notification> appended to the merge summary so the caller actually sees the partial-success case.

Added regression tests:
- bridge: prepare fires after timeout-pause and before timeout-resume.
- runner: applyEligibleNestedPatches surfaces stash-restore warnings as a system-notification.
- worktree (real git): a pre-existing dirty edit on the same file the agent patches causes stash pop to conflict; the helper returns a warning naming the nested repo and the stash entry is preserved for manual recovery.

Fixes #3196
2026-06-22 21:57:35 +02:00
can1357 2f2acaf928 Merge pull request #3205: feat(eval): isolated/apply/merge options for agent() helper
Resolves conflict in test/task/worktree.test.ts by keeping both the
getRepoRoot (main) and applyNestedPatches (PR) describe blocks.

Extends the PR's Python/JS work to the remaining workflow runtimes:
- eval/rb/prelude.rb, eval/jl/prelude.jl: agent() now accepts and
  forwards isolated/apply/merge (as booleans) plus returnHandle, and the
  return_handle node carries isolated/patch_path/branch_name/
  nested_patches/changes_applied/isolation_summary.

Post-merge fixups:
- task/index.ts: drop dead commitStyle var (the dedup refactor reads
  task.isolation.commits inside makeIsolationCommitMessage).
- CHANGELOG: move the misplaced Added entry under [Unreleased], correct
  the stale "defaults track task.isolation.mode" wording to the final
  strict opt-in behavior, and note all four runtimes.

Fixes #3196
2026-06-22 21:56:45 +02:00
roboomp abc9a8f292 fix(task): restored nested stash with index state
git stash pop without --index restores stashed staged changes as unstaged. When a nested repo had staged WIP before the isolated agent ran, the pop in applyNestedPatches() brought the content back but lost the user's index state.

Pass { index: true } so pop uses --index, matching the root merge path that already does the same thing.

Added a regression test that stages a pre-existing edit in the nested repo, runs applyNestedPatches, and asserts the file is still in the index (porcelain "M  " with the trailing space) and the cached diff still shows the staged WIP.

Fixes #3196
2026-06-22 19:41:59 +00:00
roboomp 978d2a76d0 fix(task): preserved nested-repo dirty state across nested patch apply
applyNestedPatches() applied the captured patch then ran git.stage.files(nestedDir), which stages every working-tree change in the nested repo. A nested repo that was already dirty before the agent ran ended up with the user's unrelated work-in-progress committed alongside the agent delta.

Stash any pre-existing dirty state (tracked + untracked) before applying the patch and pop it back in the finally block after the commit, so the agent commit contains only the captured patch and the user's in-flight work is restored on top of it. A failing stash pop logs a warning and leaves the stash entry intact for manual recovery; the broader nested-apply failure path is already non-fatal.

Added a worktree integration test that confirms a pre-existing untracked file in the nested repo is not staged into the agent commit and is still present in the working tree afterwards.

Fixes #3196
2026-06-22 19:34:42 +00:00
roboomp 28137f46d9 refactor(task): deduped nested patch apply + commit-message factory
TaskTool and the eval agent() bridge each held a private copy of the nested-repo patch eligibility gate and the AI commit-message factory; isolation policy could drift between the two callers.

Moved both into task/isolation-runner.ts:
- applyEligibleNestedPatches(opts) — single nested-patch gate (skip on patch-mode parent failure, skip on branch-mode unmerged root, fail non-fatally with a system-notification suffix).
- makeIsolationCommitMessage(session) — single factory that yields the AI commit-message callback when task.isolation.commits === "ai" and a model registry is wired, undefined otherwise.

Both call sites now invoke the helpers; behavior is unchanged. Removed the now-dead generateCommitMessage/applyNestedPatches imports from each caller.

Added unit tests for the new helper covering the skip-on-patch-failure, skip-on-unmerged-branch, success, and failure-suffix paths.

Fixes #3196
2026-06-22 19:22:24 +00:00
roboomp a6ee95df08 fix(eval): preserved returnHandle artifacts and nested branch patches
Eval preludes now forward returnHandle to the bridge so no-session eval runs can preserve the temp artifacts backing returned agent:// handles. The bridge keeps those temporary artifact directories whenever returnHandle is requested, including non-isolated runs and successful isolated applies.

Branch-mode isolation now treats nested-only changes as merge-eligible even when no root branch was produced, letting callers apply nested patches instead of dropping them when the root repo had no diff.

Added regression coverage for returnHandle artifact preservation and nested-only branch isolation.

Fixes #3196
2026-06-22 03:59:17 +00:00
can1357 f353ae0067 chore: biome format after PR integration 2026-06-21 17:20:22 +02:00
can1357 e8b60408b2 Merge PR #1939: fix(coding-agent): guard Git-mutating automation in pure jj workspaces (@roboomp)
# Conflicts:
#	packages/coding-agent/src/task/worktree.ts
#	packages/coding-agent/test/task/worktree.test.ts
2026-06-21 17:05:15 +02:00