Commit Graph

369 Commits

Author SHA1 Message Date
roboomp 0c4c2f2d91 style: bun run fix 2026-07-02 08:40:30 +00:00
roboomp 5bc796d4cb fix(clipboard): read the system clipboard via Bun.spawn instead of execSync
readTextFromClipboard called execSync for pbpaste, termux-clipboard-get,
wl-paste, and xclip; readMacFileUrlsFromClipboard did the same for
osascript; copyToClipboard for termux-clipboard-set. execSync parks the
event loop until the child exits or the 2000ms timeout fires, so a hung
clipboard daemon froze the TUI render loop for the whole budget on every
paste and copy chord (input-controller handleImagePaste and
handleClipboardTextRawPaste).

A new spawnCapture helper wraps Bun.spawn with the same 2000ms guard,
stdout-to-string decoding, and non-zero-exit/timeout throw semantics the
outer try/catch already assumed. Every synchronous clipboard shell-out
now yields to the event loop while the child runs. Regression test
under readTextFromClipboard runs a slow fake pbpaste and asserts a
concurrent setInterval keeps ticking; the pre-fix code delivered zero
ticks.

Fixes #4235
2026-07-02 08:40:14 +00:00
can1357 51684b4b1d refactor(coding-agent): streamlined codebase by deduplicating helper logic and shims
- Consolidated duplicated inline thinking level comparisons into a unified `concreteThinkingLevel` helper.
- Enhanced legacy tool shims to respect isolated session settings and support legacy options.
- Cleaned up redundant UI render requests and extra status-line updates.
- Refactored `grep` tool shim to configure context dynamically via isolated settings.
- Disabled platform-incompatible shell shim tests on Windows environments.
2026-07-02 02:40:08 +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 9756d5f6c6 fix(coding-agent): separated network timeout for git clone and fetch
- Added GIT_NETWORK_TIMEOUT_MS (30 min) for clone/fetch with an overridable timeoutMs option; local plumbing keeps the 5-minute cap.
- Migrated fetch() from a positional AbortSignal to an options object.
2026-07-02 00:32:58 +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 e4c3cba143 Merge PR #3875 (selective): skip double-format of revealed thinking blocks (@oldschoola)
Ports only the thinking double-format fix: resolveThinkingDisplay reuses block.thinking when rawThinking is set (buildDisplayMessage already formatted it), plus a single-entry memo in formatThinkingForDisplay and a rawThinking regression test. The PR's incremental reveal slicing is superseded by the already-merged #3848 (memoized grapheme slicing).
2026-07-01 22:37:36 +02:00
ben 34a4777497 test(coding-agent): harden local ci isolation 2026-07-01 22:25:04 +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 debe71ae0e Merge PR #4129: fix(coding-agent): preserved explicit :auto suffix in modelRoles (@roboomp) 2026-07-01 21:53:17 +02:00
can1357 9e64acfc93 fix(coding-agent): wait for timed-out git subprocesses 2026-07-01 21:53:16 +02: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 a4ae4c130c fix(coding-agent): preserved explicit :auto suffix in modelRoles
The model selector's persistence path dropped the `:auto` selector when parsing role values, producing a warning ('Invalid thinking level "auto"') and rendering the badge as `inherit` instead of `auto`. Reload of the default role also lost the auto state whenever the role value carried an explicit `:auto` suffix instead of relying on `defaultThinkingLevel`.

Widen the resolver chain (`parseThinkingSuffix`, `splitThinkingSuffix`, `parseModelString`, `parseModelPattern*`, `ResolvedModelRoleValue`, `ResolvedRoleModel`, `ResolveCliModelResult`) to carry the `AUTO_THINKING` sentinel end to end, and coerce it back to `undefined` at concrete-only boundaries (glob scope patterns, retry fallback, advisor, commit pipeline, guided-goal, bench).

Regression tests cover:

- `resolveModelRoleValue("provider/model:auto")` returns explicit auto without a warning.

- `ModelSelector` renders `DEFAULT (auto)` and `SMOL (auto)` when the role value has `:auto`.

- `cycleRoleModels` activates auto thinking on entering a `:auto` role.

- Startup resume activates auto thinking when `modelRoles.default` carries `:auto`.

Fixes #4128
2026-07-01 08:18:42 +00:00
roboomp ae34cc1b1c fix(coding-agent): bounded git subprocesses
Forced non-interactive credential env for git and gh subprocesses, added a default timeout, and capped captured stdout/stderr with a truncation marker. Added regression coverage for prompt env, output capping, and timeout cleanup.

Fixes #4072
2026-07-01 07:13:23 +00:00
can1357 ef7636805b feat(coding-agent): removed canonical model variant selection and tracking
- Removed the canonical model variant indexing, selection, and tracking logic from the model registry and resolver.
- Eliminated the `canonical` sub-command, tab view, search tokens, and equivalence configuration structures from the CLI and model selector components.
- Refined model identification, lookup, and provider fallback resolution to bind exclusively to standard, raw model IDs.
- Relocated the equivalence utility script within the catalog package to support script-only policy generation.
2026-07-01 05:22:42 +02:00
can1357 3d0d20c746 fix(coding-agent/utils): cleared ambient git environment variables in git spawns
- Added a helper to build the Git process environment that explicitly clears common ambient Git environment variables.
- Applied the new environment builder to both async and synchronous Git commands to prevent environment bleeding from parent processes.
2026-06-30 20:43:15 +02: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
can1357 9038488cb0 merge #3845: preserve agent commit history across isolated branch merges 2026-06-30 03:01:02 +02:00
can1357 e8090bb48a feat: introduced binary file detection to prevent encoding corruption
- Introduced `isProbablyBinary` utility to sniff file headers for NUL bytes or invalid UTF-8 sequences.
- Updated `ReadTool` to use the binary sniffer, preventing mojibake corruption in output when reading non-text files.
- Refined `file-mentions` auto-reads to skip binary files and mark them as `binary` in the message transcript.
- Added comprehensive unit tests for binary detection logic, covering NUL bytes, truncated multibyte characters, and path-based file sniffing.
2026-06-30 02:59:41 +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 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 4db3d68bdb feat(coding-agent): implemented git worktree detection and rendering
- Added `git.repo.linkedWorktreeSync` to identify and resolve git worktree metadata without spawning subprocesses.
- Updated `StatusLineComponent` to detect linked worktrees and resolve project/worktree context names.
- Modified path segment rendering to collapse nested git worktree paths and display the worktree name when it diverges from the active branch.
- Introduced `icon.worktree` symbol across themes to visually distinguish git worktree paths.
2026-06-28 22:50:30 +02:00
roboomp 4d0ef260b6 fix(tui): allowed thinking toggle after streamed reasoning
Tracked received thinking content per interactive session so OpenAI-compatible providers that omit reasoning metadata can still reveal streamed reasoning blocks. Added a Ctrl+T regression covering the unlocked visibility path.

Fixes #3669
2026-06-27 17:18:04 +00:00
can1357 f0f7a5ba89 feat(coding-agent): introduced tiny model role for background tasks
- Added `tiny` as a first-class model role to override online models for lightweight background tasks.
- Updated session title generation, auto-thinking difficulty classification, unexpected-stop detection, and mnemopi backend to resolve via the `tiny` role before falling back to `smol`.
- Updated configuration schema and documentation to reflect the new role precedence.
2026-06-27 07:56:27 +02:00
can1357 ec03d3366e refactor(prompt): share prompt path normalization 2026-06-27 01:40:13 +02:00
can1357 979fa4f93e fix(advisor): avoid ancestor walks for child repos 2026-06-27 01:40:12 +02:00
can1357 5abe19eda1 Merge PR #3156: fix(advisor): surface nested repo context (@oldschoola)
# Conflicts:
#	packages/coding-agent/src/modes/components/status-line/component.ts
#	packages/coding-agent/src/sdk.ts
#	packages/coding-agent/src/system-prompt.ts
2026-06-27 01:40:12 +02:00
can1357 9dea09e238 Merge PR #3264: feat(coding-agent): cache successful document conversions (@wolfiesch) 2026-06-27 01:39:33 +02:00
can1357 2e3479a045 Merge PR #3334: fix(coding-agent): detect CMakeLists.txt language by basename (@oldschoola) 2026-06-26 23:27:39 +02:00
can1357 772a1d454f Merge PR #3585: fix(coding-agent): report screenshot fallback dimensions (@roboomp) 2026-06-26 23:27:39 +02:00
can1357 52b8fb1565 feat(coding-agent): improved session title casing logic
- Updated `normalizeGeneratedTitle` to reconcile model-generated titles against the user's input instead of forcing title-case.
- Added logic to restore distinctive proper-noun casing (e.g., `TinyVMM`) and flatten model-generated camelCase artifacts (e.g., `dAemon`) that do not appear in the user's message.
- Ensured model-cased proper nouns that are not in the source message (e.g., `GitHub`) are preserved.
2026-06-26 20:27:40 +02:00
roboomp 9c670d033a style: bun run fix 2026-06-26 16:23:42 +00:00
roboomp 830e99400d fix(coding-agent): reported fallback image dimensions
Read PNG/JPEG headers when Bun.Image rejects a screenshot so browser results keep real dimensions and surface the unresized fallback.

Fixes #3577
2026-06-26 16:23:22 +00:00
can1357 ac904fc70c fix: patched Kimi model edit mode fallback
- Added a fallback from `hashline` to `replace` mode for Kimi-family models to resolve compatibility issues.
- Introduced `PI_STRICT_EDIT_MODE` environment variable to bypass automatic model-specific edit-mode fallbacks.
- Updated `getEditVariantForModel` to perform case-insensitive matching for model variant configurations.
- Added comprehensive unit tests for edit mode resolution and settings configuration.
2026-06-26 13:17:35 +02:00
can1357 68d7593244 chore: reorg 2026-06-26 12:24:42 +02:00
roboomp a1f5b8cbcb style: bun run fix 2026-06-25 23:25:13 +00:00
roboomp b3f99dc634 fix(coding-agent): reach macOS public.file-url pasteboard via osascript
Reviewer caught (correctly) that the #3506 text fallback relied on
`clipboard.readText()`, which on Darwin shells out to `pbpaste(1)` —
pbpaste only surfaces plain text / RTF / EPS, so a Finder Cmd+C
pasteboard (`public.file-url` only, no plain text, no raw image bytes)
made readText() return empty and the new path-detection never ran.

Add a Darwin-only `readMacFileUrlsFromClipboard` helper that pipes a
small AppleScript through `osascript -` to coerce the pasteboard via
`«class furl»` and emit POSIX paths one per line. Wire it into
`InputController.handleImagePaste` between the readImage and readText
calls; the first image-shaped path routes through
`handleImagePathPaste`, non-image file URLs (e.g. a copied .pdf) fall
through to the existing text fallback. The clipboard interface field is
optional so existing test fixtures keep working without changes.

Tests: covers (a) Darwin file-URL pasteboard with empty pbpaste,
(b) non-image file URLs falling through to text, (c) the helper itself
on darwin/non-darwin and when osascript fails.

Refs #3506
2026-06-25 23:25:00 +00:00
roboomp d08dc7946b fix(bash): kept snapshot 0600 when rc resets umask
PR #3474 second review: previous revision ran `umask 077` only BEFORE
sourcing the rc, so a typical `.bashrc`/`.zshrc` that calls
`umask 022` reopened the world-read window between the spawned shell's
first `>|` and the JS post-spawn chmod. Snapshot file (with inlined
env-var values) lived at 0644 for the full body of the script.

Two-layer fix:
 - JS caller now pre-creates the snapshot file at 0600 with
   `fs.writeFileSync(path, "", { mode: 0o600 })` before spawning. The
   shell's `>|` (truncate) and `>>` (append) preserve the existing
   inode mode, so the file is 0600 from byte zero regardless of the
   spawned shell's umask state.
 - Script also re-applies `umask 077` after the rc source so any
   other file the script might create (none today, defensive) stays
   private even when the rc resets umask.

New e2e regression test seeds a `.bashrc` containing `umask 022` and
asserts the resulting snapshot mode `& 0o077 === 0`.
2026-06-25 15:26:26 +00:00
roboomp de1368fd2d fix(bash): tightened snapshot perms and denied secret-shaped env vars
PR #3474 review: the new export pass writes referenced env-var values
into a snapshot file under `os.tmpdir()/omp-shell-snapshots`. On Linux
where `os.tmpdir()` is `/tmp` and the umask is the default 022, the
file ended up world-readable (0644) until postmortem cleanup. A user
rcfile defining `deploy(){ curl -H "Authorization: $GITHUB_TOKEN" ...; }`
would have its token written verbatim to that file.

Three-layer mitigation:
 - `umask 077` at the top of the snapshot script so the file is 0600
   from the first byte (the shell creates it via redirection, not JS).
 - JS caller now passes `mode: 0o700` to `mkdirSync` and chmods the
   dir + file defensively after the script exits, covering pre-existing
   dirs and exotic shells where the umask call might not take.
 - Helper denylist gained the common secret-shaped name patterns
   (`*TOKEN*`, `*SECRET*`, `*API_KEY*`, `*PASSWORD*`, `*PASSWD*`,
   `*PRIVATE_KEY*`, `*ACCESS_KEY*`, `*CREDENTIAL*`, `*SESSION_KEY*`)
   so even when the file is locked down, we don't materialise tokens
   onto disk in the first place.

Tests cover both: a new helper-level test asserts none of the secret
names (or their values) appear in the export stream, and the e2e test
now stats the snapshot file + dir and asserts `mode & 0o077 === 0`.
2026-06-25 15:19:23 +00:00
roboomp 77265de55e fix(bash): re-exported env vars referenced by snapshotted shell functions
generateSnapshotScript captured the user's shell functions via declare -f /
typeset -f and dropped everything except PATH on the export floor. mise
activate installs a mise() function whose body expands $__MISE_EXE; the
replay shell then ran `command "" "$@"` and died with
`command: command not found:` (exit 127). The same shape breaks asdf
shims, direnv-style helpers, and any other activation idiom that pairs a
shell function with a sidecar env var.

The snapshot script now scans captured function bodies for $VAR /
${VAR…} references and re-emits `export NAME='value'` for each name
that is currently set and not on a shell-internal denylist (PATH, HOME,
BASH_*, LC_*, …). getShellConfigFile also honours env.HOME so callers
(and tests) can target a sandboxed home — os.homedir() is cached by Bun
and ignores later process.env.HOME mutations.

Fixes #3470
2026-06-25 15:10:59 +00:00
roboomp 44f3632cff fix(tools): bounded tool asset downloads
Stream fetched tool assets to disk under the existing download abort signal instead of passing the Response object to Bun.write. Remove partial files when a stalled body is aborted and cover completed plus stalled downloads with regression tests.

Fixes #3369
2026-06-24 14:11:39 +00:00
Wolfgang Schoenberger 3a2ab8ad7d test: restore full env state in document-conversion cache tests
Snapshot and restore PI_CODING_AGENT_DIR, OMP_PROFILE, PI_PROFILE, and
XDG_CACHE_HOME instead of relying on setAgentDir(originalAgentDir), which
cannot restore previously-unset or profile-derived env state. Reset the
profile snapshot and rebuild dirs from env in cleanup to prevent
suite-order pollution.

Also reject empty cached content in parseCacheEntry() to harden the
cache contract against corrupted/empty entries.
2026-06-24 03:08:48 -07:00
oldschoola 5e1bff6b2b fix(utils): CMakeLists.txt returns cmake instead of text in getLanguageFromPath
The .txt extension match was winning over the basename check in
getLanguageFromPath, returning 'text' instead of 'cmake' for
CMakeLists.txt. Reordered the function to check basename special-cases
first (mirroring detectLanguageId's structure), so CMakeLists.txt,
Dockerfile, .env., .emacs, and justfile all fire before the extension
lookup.

Rewrote tests to defend observable contracts (special filenames, case
handling, lookup ordering) instead of re-stating the EXTENSION_LANG
table. Added contract test pinning that CMakeLists.txt resolves to
'cmake' not 'text'.

Updated CHANGELOG: moved from ### Added (test-only) to ### Fixed
(bug fix).
2026-06-23 10:25:12 -07:00
Wolfgang Schoenberger ef97b63313 refactor(coding-agent): harden document conversion cache temp handling
- Add random UUID suffix to cache temp filenames to avoid same-pid/same-ms collisions
- Export pruneMarkitConversionCache and cover orphaned .tmp sweeping with a regression test
2026-06-22 17:44:04 -07:00
wolfiesch e58096a1e9 fix(coding-agent): add uncached buffer-conversion path for side-effect tests 2026-06-22 16:54:14 -07:00
Wolfgang Schoenberger e09ff9af88 refactor(coding-agent): address cache review feedback
- fold coding-agent package version into the cache key so releases that
  change markit converter output auto-invalidate stale entries
- sweep orphaned `.tmp` files during prune (crash between write and
  rename previously leaked, invisible to the size cap)
- make prune fire-and-forget after rename so a cache miss returns once
  the entry is on disk instead of waiting on a readdir + N×stat sweep
- document the FIFO-by-mtime eviction policy on-record
- use Bun.file()/Bun.write() for payload I/O per repo conventions
2026-06-22 16:54:13 -07:00
Wolfgang Schoenberger 2cb892ef1f feat(coding-agent): cache successful document conversions
Repeated reads of unchanged PDFs, Office documents, and EPUBs re-ran the
full markit conversion every time. Add a transparent, content-addressed
cache for successful conversions keyed by SHA-256(content) + normalized
extension, so repeat reads reuse converted markdown instead of
reconverting.

- packages/utils: XDG-aware getDocumentConversionCacheDir() helper
- coding-agent: markit-cache module (bounded 256 MiB, oldest-first prune,
  best-effort writes that never fail conversion) layered over the central
  convertFileWithMarkit/convertBufferWithMarkit wrappers
- imageDir conversions stay uncached (cache:"skipped") to preserve PDF
  image extraction side effects; failed/empty/aborted conversions are
  never cached
- abort-safe: file byte reads run under untilAborted; cache I/O rechecks
  the signal
2026-06-22 16:54:13 -07:00