Commit Graph

3597 Commits

Author SHA1 Message Date
can1357 ecd6f608e5 fix(agent): resize provider concurrency limiter in place instead of replacing it
The per-provider subagent limiter (providers.ollama-cloud.maxConcurrency)
created a fresh Semaphore whenever the configured limit changed, orphaning
in-flight slots on the old instance so a runtime or mixed limit value could
exceed the cap. getProviderSemaphore now always hands out one shared limiter
(Infinity when unlimited, so every run is still counted) and resizes it in
place. Semaphore.release() decrements before admitting, and the new
Semaphore.resize() raises the ceiling by admitting queued waiters while
lowering it drains in-flight holders without admitting past the new cap.

Refs #3464
2026-06-25 20:48:38 +02:00
can1357 556ced266c Merge PR #3466 into sweep 2026-06-25 20:42:31 +02:00
can1357 3721e47c51 chore: normalize changelog after PR sweep 2026-06-25 20:42:30 +02:00
can1357 88783b6e5c fix(agent): cancel in-flight auto-compaction on manual compact startup
The preserveCompaction abort path skipped abortCompaction() entirely,
so a manual /compact starting while auto-compaction was in flight no
longer cancelled it. Both passes could then appendCompaction/
replaceMessages, double-rewriting history (reachable via the RPC/
extension compact paths, whose only guard checks #compactionAbortController).
Preserve the just-installed manual controller but still abort the
auto-compaction controller. Adds regression coverage.
2026-06-25 20:42:30 +02:00
can1357 5cfea2884d Merge PR #3489 into sweep 2026-06-25 20:42:30 +02:00
pr-eval 0fb6af9354 fix(coding-agent): keep ast_grep/ast_edit patterns in transcript summaries
Adding `paths` to the global PRIMARY_ARG_KEYS hid the pattern for the
structural tools: ast_grep ({pat,paths}) and ast_edit ({ops,paths})
rendered scope-only (e.g. `ast_grep(src/**/*.ts)`), dropping `pat`/`ops`
— the most decision-relevant argument. Drop the global `paths` key and
special-case `find` (mirroring `search`) so find/search still surface
scope while ast_grep/ast_edit keep showing their pattern via the
existing fallback. Adds regression tests for both structural tools.
2026-06-25 20:42:30 +02:00
can1357 3ba39c1ec4 Merge PR #3486 into sweep 2026-06-25 20:42:30 +02:00
can1357 0aef0b522c test(bash): resolved bash/echo paths so snapshot e2e tests pass on macOS
The two getOrCreateSnapshot e2e tests hard-coded /usr/bin/bash and
/usr/bin/echo, both absent on macOS (bash is /bin/bash, echo is
/bin/echo). The symlink then pointed at a nonexistent target so
getOrCreateSnapshot returned null, and the replay invoked a missing
echo. Since #3470 is a macOS bug, the regression tests could not run on
the affected platform. Resolve bash via Bun.env.SHELL (mirroring
bash-executor.test.ts) with a /bin/bash fallback and an existsSync skip
guard, and resolve echo via Bun.which with a /bin/echo fallback.
2026-06-25 20:42:30 +02:00
can1357 15816b03af Merge PR #3474 into sweep 2026-06-25 20:42:29 +02:00
can1357 f61fc85382 Merge PR #3477 into sweep 2026-06-25 18:53:34 +02:00
can1357 61da80d2ae Merge PR #3468 into sweep 2026-06-25 18:53:34 +02:00
roboomp d380a9e723 fix(agent): queued manual compact startup steers
Install the manual compaction abort controller before abort teardown so input routing observes session.isCompacting during the starting window.

Fixes #3485
2026-06-25 15:49:23 +00:00
roboomp 08854f9f16 style: bun run fix 2026-06-25 15:39:12 +00:00
roboomp 8f63bd60be fix(coding-agent): surfaced transcript search paths
Added scoped path summaries for find/search tool calls in concise session history rendering, with regression coverage for JSON fallback and hidden search scope.

Fixes #3482
2026-06-25 15:38:59 +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 8f05a29d4d fix(cli): aligned gallery state labels
Accepted displayed gallery lifecycle labels as --state aliases, rejected unknown values before rendering, and updated failed fixtures to render visibly failed states.

Fixes #3473
2026-06-25 15:22:35 +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 039c93be60 fix(tui): restore streaming steer image draft on prompt error
Wrap the streaming Enter steer dispatch in try/catch so prompt failures restore text plus pendingImages / pendingImageLinks / imageLinks and surface showError instead of losing an image-only draft.\n\nAlso remove a forbidden ReturnType<> from the follow-up image regression helper.\n\nFixes #3467
2026-06-25 13:53:00 +00:00
roboomp b0bbd872c4 fix(tui): restore followup image draft on prompt error
Snapshot pendingImageLinks alongside pendingImages at the top of handleFollowUp and wrap both the streaming and idle session.prompt dispatches in try/catch that restores text + pendingImages + pendingImageLinks + imageLinks and surfaces showError, mirroring the main submit and focused submit error paths so an image-only or text+image Ctrl+Enter draft survives dispatch rejection.\n\nFixes #3467
2026-06-25 13:47:06 +00:00
roboomp a2a217277c fix(tui): restore focused submit image draft on prompt error
Snapshot pendingImageLinks and re-seed editor.pendingImages / pendingImageLinks / imageLinks in the focused-session submit catch block so an image-only or text+image draft survives a viewSession.prompt rejection, mirroring the main controller error path.\n\nFixes #3467
2026-06-25 13:40:14 +00:00
roboomp e24b70c09a fix(tui): queued image-only streaming submits
Treat pending pasted images as submit content in the main and focused input controller paths so image-only Enter/Ctrl+Enter submissions queue instead of dropping or aborting.\n\nFixes #3467
2026-06-25 13:32:18 +00:00
roboomp 7e90e4d081 fix(agent): released ollama-cloud semaphore slot when waiter aborts
Semaphore.acquire now accepts an AbortSignal so a queued waiter that is cancelled (parent task abort, wall-clock budget elapsing) removes itself from the wait queue instead of being resolved by the next release. The provider semaphore in runSubprocess passes the run's abortSignal through, preventing aborted ollama-cloud subagents from permanently draining the provider concurrency budget.

Fixes #3464
2026-06-25 12:14:45 +00:00
roboomp 80862b79da fix(agent): handled ollama-cloud task backoff
Added ollama-cloud subagent concurrency limiting, role fallback-chain inheritance, and visible empty length errors for native Ollama responses.

Fixes #3464
2026-06-25 11:57:51 +00:00
roboomp 184f6dd809 style: bun run fix 2026-06-25 11:41:56 +00:00
roboomp 8506fbdf52 fix(coding-agent): switched ctrl-z handler to SIGSTOP-self to defeat brush tokio SIGTSTP hijack
brush-core's Process::wait calls tokio::signal::unix::signal(SIGTSTP) to
detect when its children get stopped. Per tokio's documented contract,
the first call for a SignalKind permanently replaces the kernel-default
handler for the lifetime of the process. So once omp has executed any
bash tool call — even /usr/bin/true — SIGTSTP's default "stop" action
is gone, and InputController.handleCtrlZ's process.kill(0, "SIGTSTP")
gets swallowed by tokio. The TUI tore down via ui.stop() but the process
kept running in Sl+ state, leaving the user with a dead terminal that
only kill -9 could recover.

Send SIGSTOP to our own PID instead. SIGSTOP can't be caught, blocked,
or ignored — it stops the process at the kernel regardless of installed
handlers. Targeting self (not pgid=0) also leaves long-lived children
(MCP stdio servers, the persistent brush native shell) running across
the suspend, so they no longer freeze mid-IPC during a quick fg/bg
detour.

Fixes #3461
2026-06-25 11:33:35 +00:00
can1357 c46216742b feat(hashline): implemented tag-based patch path recovery
- Automatically rebind edits to the correct file when an authored path does not exist but the filename and snapshot tag uniquely match a file read earlier in the session.
- Prevent path recovery for paths that would escalate write privileges, ensuring compatibility with read-only internal URL targets.
- Surface warning messages to the model and user upon successful path recovery to encourage correct future path usage.
2026-06-25 13:14:05 +02:00
can1357 57e9848c8c refactor(coding-agent): removed automatic file attachment for non-image paste paths
- Removed `onPasteFilePath` handler to prevent automatic background file attachment when pasting paths.
- Updated `CustomEditor` to treat non-image paths as literal text input.
- Cleaned up unused file system utilities and paste path resolution logic.
2026-06-25 12:57:54 +02:00
can1357 c03a78917a test(coding-agent): disabled fsmonitor during test repo initialization
- Added core.fsmonitor configuration to the git initialization process in test setup to prevent potential environment-specific conflicts.
2026-06-25 12:55:01 +02:00
can1357 5b439dfe55 test(coding-agent): updated model selection test settings
- Enabled anthropic models in the isolated settings for the session creation test to ensure correct configuration.
2026-06-25 12:44:49 +02:00
can1357 3c0a908733 Merge remote-tracking branch 'origin/farm/30e0ba62/local-binary-attachments' 2026-06-25 12:19:34 +02:00
can1357 e47a1a8449 Merge remote-tracking branch 'origin/farm/c26b81e0/fix-bundled-pi-ai-subpath-resolution' 2026-06-25 12:19:25 +02:00
roboomp 93f1019ead fix(coding-agent): extended binary refusal to cover newline-less blobs
The streaming reader's NUL check only walked completed lines collected
from streamLinesFromFile, so a binary blob whose first newline lay past
the byte budget (videos, archives, packed JSON) left collectedLines
empty and slipped through to the firstLineExceedsLimit branch — which
emitted the decoded preview as text instead of the intended refusal.

Sniff firstLinePreview alongside collectedLines so the existing refusal
fires uniformly. Also added a regression test that uses a 256 KiB blob
with no 0x0A bytes to actually exercise the firstLineExceedsLimit
path — the previous 6-byte test fit in one collected line and never
covered the bug.

Fixes #3448
2026-06-25 07:58:20 +00:00
roboomp 4331217af8 fix(coding-agent): refused local binary attachment text reads
Routed file-backed '/data/workspaces/can1357__oh-my-pi__3448/.omp-session/2026-06-25T07-25-13-303Z_019efdab-28d7-7000-a7a4-e20282508056/local' reads through the normal filesystem reader so binary detection, document/image handling, and streaming safeguards apply before content is materialized.

Hardened the local protocol handler to return metadata-only refusals for binary/container resources instead of decoding them with Bun.file().text().

Fixes #3448
2026-06-25 07:37:30 +00:00
roboomp c7d9abbcfd fix(plugins): bundle wildcard-exported pi-* subpaths
The first pass only enumerated non-wildcard `exports` entries, so
patterns like pi-ai's `./oauth/*` left every concrete target
(`@oh-my-pi/pi-ai/oauth/anthropic` and friends) outside the
bundled registry. Compiled-mode resolution then fell back through
`Bun.resolveSync` → original peer specifier → missing peer dep,
reproducing the original `Cannot find module` failure for any
plugin that imports a wildcard-only subpath (e.g.
`@mariozechner/pi-ai/utils/oauth/anthropic`, remapped via
PI_SUBPATH_REMAPS).

The generator now runs a second pass over wildcard exports,
parses each single-asterisk pattern into prefix/suffix halves,
globs the matching source directory, and emits a registry entry
per concrete `.ts` file. Root catch-all wildcards (`./*` /
`./*.js`) are skipped on purpose — they'd static-import top-level
files like the coding-agent's own `cli.ts` and explode the bundle
through the binary entry's transitive graph. Test, `.d`,
`.generated`, `.bench` files and `index` basenames are filtered
out so the registry stays focused on importable surfaces.

A new test case in
test/extensibility/legacy-pi-bundled-subpath-overrides.test.ts
asserts the reviewer's cited `@oh-my-pi/pi-ai/oauth/anthropic`
key now routes through the virtual namespace and that root
catch-all wildcards remain unbundled.

Fixes #3442
2026-06-25 06:13:38 +00:00
roboomp 897cce792f fix(coding-agent): split mixed file mentions so text stays on developer
Reviewer caught that demoting the whole mixed payload to `user` (`@notes.md
@screenshot.png`) regressed the developer-priority treatment text-only
mentions still get for image-free turns. `generateFileMentionMessages` packs
every `@…` into one `fileMention`, so the previous `hasImage` toggle
collapsed the source-file context into the user slot whenever an image was
attached.

`convertToLlm` now returns up to two messages per `fileMention` via
`flatMap`: text-only files keep their existing `developer` envelope, and
image-bearing files emit a separate `user` envelope that carries their
`<file>` wrappers plus the `input_image` block. Pure-text and pure-image
turns still collapse to a single message.

Tests cover the mixed case (split into developer + user), the image-only case
(single user message), and the existing text-only case (single developer
message).

Fixes #3443
2026-06-25 05:54:15 +00:00
roboomp aa29b79642 fix(plugins): route bundled pi-* subpath imports through the virtual registry
Compiled-binary extension validation rewrote @(scope)/pi-ai/oauth →
@oh-my-pi/pi-ai/oauth, but LEGACY_PI_PACKAGE_ROOT_OVERRIDES only
covered bare package roots. resolveCanonicalPiSpecifier therefore
fell through to Bun.resolveSync, which fails inside bunfs on Bun
1.3.14+, then the rewriteLegacyPiImports catch left the original
specifier alone. Bun's native resolver then failed because most
plugins (e.g. @charmland/pi-hyper-provider) declare @(scope)/pi-ai
as a peerDependency only and never materialize a real install.

A new scripts/generate-legacy-pi-bundled-registry.ts reads every
bundled pi-* package's non-wildcard exports field and emits both
the heavy legacy-pi-bundled-registry.ts (static imports + map) and
a light legacy-pi-bundled-keys.ts. legacy-pi-compat.ts statically
imports the keys file to seed the override map without paying the
legacy-pi-coding-agent-shim → ../index → export/html/... cascade,
so subpath imports now route to the same omp-legacy-pi-bundled:
virtual namespace that already serves the roots.
scripts/build-binary.ts runs the generator before bun build
--compile so new pi-* subpaths added under packages/*/package.json
ship without manual regeneration; --check verifies the committed
output stays in sync.

Fixes #3442
2026-06-25 05:52:38 +00:00
roboomp c2174a87b2 fix(coding-agent): routed image-bearing @ mentions as user-role messages
Codex GPT models on chatgpt.com /codex/responses rejected `@image` turns with
`Codex error event: [OneOfParam] [input[N].content[M]] [invalid_enum_value]
Invalid value: 'input_image'. Supported values are: 'input_text'.` —
`convertToLlm`'s `fileMention` arm always emitted a `developer`-role
Responses message, but a developer-role content slot only accepts
`input_text`. #3421's prior fix only suppressed the Codex Responses Lite
header on image-bearing turns; the full transport kept rejecting the same body.

`fileMention` now uses `user` role when any attached file carries an image;
text-only mentions keep `developer` so the auto-read context still rides at
instruction priority for the agent.

Fixes #3443
2026-06-25 05:47:07 +00:00
roboomp 3ce34621c0 fix(agent): preserved skill url session context
Threaded the caller's loaded skills through internal URL resolution so skill:// handlers do not depend on process-global skill state during tool execution.

Fixes #3436
2026-06-25 03:54:15 +00:00
can1357 366313df33 test(coding-agent): improved stdout geometry stubs in tests
- Added no-op setters to process.stdout properties in test geometry stubs.
- Prevented potential errors when code under test attempts to reassign stdout dimensions.
2026-06-25 05:15:40 +02:00
can1357 0cd852053e Merge branch 'farm/6a62fa2b/fix-bunfs-override-validation-fallback'
Serve bundled pi-* and TypeBox through an in-process virtual namespace
on Bun 1.3.14+ where `--compile` extras are unreachable via any
filesystem API (issue #3423).

Merge resolution:
- Reconciled `TYPEBOX_SHIM_PATH` with main's #3414 fall-through:
  `__resolveTypeBoxShimPath(isCompiled, sourcePath, exists)` returns the
  `omp-legacy-pi-bundled:` virtual specifier in compiled mode (no FS
  probe) and the on-disk source path otherwise, dropping to null when
  the shim file is missing so bare typebox imports fall through to native
  resolution.
- Removed the now-dead `--compile` extras path: dropped
  `LEGACY_COMPAT_BUILD_ENTRYPOINTS` usage from both build-binary.ts and
  ci-release-build-binaries.ts and deleted scripts/binary-entrypoints.ts;
  the bundler reaches every surface via legacy-pi-bundled-registry.ts.
- Deleted obsolete tests for the removed bunfs machinery
  (legacy-pi-compat-entrypoints, legacy-pi-typebox-shim-validation) and
  added regression coverage for __resolveTypeBoxShimPath.
- Dropped the binary-compiling smoke driver per maintainer request.

Verified: bun check clean; 77 extensibility tests pass; instrumented
compiled-binary run confirmed onLoad fires for all bundled specifiers.
2026-06-25 05:08:36 +02:00
roboomp 294d41bd3b fix(coding-agent): served bundled pi-* through virtual ns on bun 1.3.14
Bun 1.3.14 stopped exposing `--compile` extras through every filesystem-style API: `fs.existsSync`, `Bun.file().exists()`, `Bun.resolveSync`, and `await import()` on `/$bunfs/...` or `file:///$bunfs/...` all fail; only `/$bunfs/root/<binary-name>` itself answers. The pre-existing legacy-pi rewrite emitted `file:///$bunfs/...` URLs that Bun then could not load, so every legacy extension that imported `@oh-my-pi/pi-*` or `@sinclair/typebox` failed on the `omp-darwin-arm64` release binary.

`legacy-pi-compat.ts` now keeps a JS-heap reference to every bundled pi-* surface in a lazy-loaded sibling `legacy-pi-bundled-registry.ts` and serves them through an `omp-legacy-pi-bundled:` virtual namespace whose `Bun.plugin().onLoad` synthesizes a re-export module — no bunfs path ever leaves the module in compiled mode. Dev / source-link / installed-package modes keep the historical `file://` rewrite (source files exist on disk). The matching `--compile` extras in `scripts/build-binary.ts` are gone; `BUNFS_PACKAGE_ROOT`, `bunfsPath`, `__computeBunfsPackageRoot`, and `__joinBunfsPath` are deleted as dead code. `scripts/smoke-3423.ts` compiles a tiny binary that loads a fixture extension end-to-end through the new path.

Fixes #3423
2026-06-25 02:35:50 +00:00
can1357 b54cb1f58e Merge remote-tracking branch 'origin/farm/73774d66/typebox-shim-missing-on-bunfs' 2026-06-25 04:02:55 +02:00
can1357 e2c85d9d21 Merge remote-tracking branch 'origin/farm/c748dce5/fix-append-only-context-log-rewrite' 2026-06-25 04:02:51 +02:00
can1357 27ed9f7af7 feat(coding-agent): enabled mouse navigation and fullscreen mode for extension dashboard
- Implemented SGR mouse event routing for dashboard interaction, including tab selection and pane scrolling.
- Added mouse-driven list manipulation in the extension viewer with selection highlighting, click toggling, and wheel navigation.
- Enabled fullscreen alternate-screen behavior and host terminal mouse tracking for the dashboard overlay.
- Integrated hit-testing and row selection logic into the extension list to support unified mouse and keyboard inputs.
2026-06-25 04:02:34 +02:00
roboomp 08977b226d fix(release): restored legacy pi-compat --compile entrypoints in release builds
- Extracted the legacy `--compile` entrypoint list to `scripts/binary-entrypoints.ts` and consumed it from both the release CI script and the local dev `build-binary.ts`, so the two cannot drift apart.
- `scripts/ci-release-build-binaries.ts` no longer ships release binaries without the typebox shim, legacy pi shims, and `@oh-my-pi/{agent,natives,tui,utils}` package barrels in bunfs. Commit dc5c93462f removed worker entrypoints and false-comment-claimed the legacy entrypoints were "still" listed, so every published `omp-<platform>-<arch>` since shipped without them and the resolver emitted bunfs URLs to missing files.
- Validated TYPEBOX_SHIM_PATH at module init via __resolveTypeBoxShimPath, mirroring __validateLegacyPiPackageRootOverrides (#2168). When the shim is absent the rewriter leaves bare typebox / @sinclair/typebox imports alone so Bun falls through to native node_modules resolution.
- Pinned both halves of the contract with tests: release+dev scripts must source from the shared constant, every shim path computed in legacy-pi-compat.ts must appear in it, and __resolveTypeBoxShimPath drops missing candidates.

Fixes #3414
2026-06-25 00:14:41 +00:00
roboomp a2471339a7 test(coding-agent): cover subagent append-only context rewrites
Add a createAgentSession-based subagent regression for issue #3406. The test
runs a taskDepth=1 session against a loopback llama.cpp-style model so
provider.appendOnlyContext auto-enables through the SDK path used by task
subagents.

A real context extension rewrites the prior assistant turn on the second
subagent request. The captured provider contexts assert the first request's user
message object is reused in the second request, proving the append-only log kept
the stable prefix instead of clearing and re-rendering it for subagents.

Fixes #3406
2026-06-24 23:00:19 +00:00
can1357 0eb21efa1a Merge remote-tracking branch 'origin/farm/7ef98714/snapcompact-copilot-vision-gate' 2026-06-24 21:00:37 +02:00
can1357 0f2737f16e Merge remote-tracking branch 'origin/farm/e322f828/eval-agent-yield-terminal' 2026-06-24 20:59:54 +02:00
roboomp 997b2b24ff fix(session): suppressed empty-stop retry after successful yield
Trailing empty assistant 'stop' arriving after a successful 'yield'
revived the already-yielded subagent. AgentSession.agent_end maintenance
compared #assistantEndedWithSuccessfulYield(msg) against the trailing
empty-stop message — not the yield-bearing one — so the empty-stop
recovery path appended a retry reminder and scheduled agent.continue().

Track a sticky #yieldTerminationPending flag set when the yield tool
finishes without error and cleared on the next #promptWithMessage. The
agent_end routing extends the existing successful-yield branch: when the
flag is set, or the current message ended with yield, short-circuit
empty-stop / unexpected-stop / compaction continuations for the rest of
the run, so a successful yield is terminal regardless of trailing stops.

Fixes #3389
2026-06-24 17:08:49 +00:00