Commit Graph
1824 Commits
Author SHA1 Message Date
enieuwy 837b1ac0e2 fix(session): import ServingModel, anchor fallback routing to its session
Review found the branch does not compile. `bun check` runs biome before
the per-workspace type check and joins them with `&&`, so a pre-existing
format error on `main` short-circuited the run: `tsgo --noEmit` never
executed, and `bun test` type-strips, so the suite stayed green over eight
type errors. `ServingModel` was used in `turn-recovery.ts` without being
imported, and three `subscribe` closures in the retry-fallback suite lost
the outer narrowing of `session`. Verified now against the workspace check
directly rather than the aggregate.

Codex also found `#fallbackRouted` outliving its session. It said how the
CURRENT model was reached, but nothing reset it when a transcript was
switched or resumed, so a freshly loaded session with no served attribution
yet described its own model with the previous session's routing. It is now
anchored on the session id exactly like the attribution beside it — the two
facts are earned together and expire together — and a session switch
reports the current model without claiming to know how it got there.

The replay-unsafe host stub gained `getSessionId`, which the real
`SessionManager` has always had and the anchoring now calls.
2026-08-08 13:16:42 +08:00
enieuwy a1e60c3450 refactor(session): fold the pending-fallback surface into servingModel
Final review found `pendingRetryFallbackModel` unreachable. `servingModel`
returns `undefined` only when the session has no model at all, and the
pending getter required one, so the badge term guarding on it could never
fire. Its case — a fallback armed before anything has served — is already
answered by `servingModel`'s bootstrap, which names the current model and
flags it as fallback-routed. Removed, the same duplicate-surface cleanup
that removed `retryFallbackModel`.

Attribution now anchors on the session id rather than the session file. An
unpersisted session has no file, so two `undefined`s compared equal and
stale attribution survived `/new` and branch switches there; every real
switch mints a new id, persisted or not.

The cooldown-expiry restore keeps `#fallbackRouted` when the stored primary
selector cannot be parsed. Nothing is restored on that path, so the session
is still running on the fallback and its remaining turns are still fallback
work; clearing the flag reported them as the configured primary.

`executor-prewalk`'s fake session predates this work and never set
`servingModel`, so the prewalk hand-off stopped advancing the reported
model once the executor began reading attribution from the session. It now
mirrors the hand-off the way the other executor fixtures do.
2026-08-08 13:16:41 +08:00
enieuwy a06a7f5ef1 refactor(session): anchor attribution to its transcript, drop a dead format branch
Two cleanups from review.

The offline walk matched a served turn to its `model_change` by also
accepting a `:level` thinking suffix, but no writer produces one: every
`appendModelChange` call site records a bare `provider/id` or a
`formatModelStringWithRouting` selector, and only the latter decorates —
with `@upstream`, never a level. Speculative matching in the part of the
change that already reconstructs the most from raw strings. Removed, with
its test narrowed to the format writers actually emit.

Attribution was also retained for the life of the recovery component, so
switching sessions in place could report a model from a different
transcript by name. It is now tagged with the session file it was earned
in and ignored when that no longer matches — the same self-invalidating
anchor `#ensurePersistedMessageKeys` uses, so no mutation call site has
to remember to clear anything. Anchoring on the file rather than the leaf
keeps attribution across compaction and appends, which stay within the
transcript that earned it.
2026-08-08 13:16:11 +08:00
enieuwy a93a53185b fix(session): key attribution off what served, not off the fallback chain
Review of the previous commit found the same defect in four more places,
each a variant of one mistake: the unproven-model gate was expressed as
"a retry fallback is armed and has not served", so it only protected the
one swap path that arms a chain.

- The cooldown-expiry restore to the primary deletes the fallback record
  before swapping, so the gate vanished with it and an unproven, just-
  restored primary was credited immediately.
- A session's first-ever fallback arm constructs the record only after
  the swap, so there was nothing to mark and the guard was a no-op.
- The Fireworks Fast degrade swaps models without arming a chain at all,
  so a live row showed the degraded base model as the configured primary.
- The fresh-versus-resumed heuristic read the message list, which
  compaction collapses, so a resumed-and-compacted session seeded its
  startup candidate as already proven.

Attribution now names the last model that settled a turn here, full stop.
A switch of any kind — into a candidate, back to a restored primary, or a
capability degrade — moves it only once the new model answers. Before
anything has served there is no earlier work to miscredit, so the
configured model is both the only answer and a safe one.

Fallback routing is tracked as its own flag rather than inferred from the
chain record, because the degrade path routes without a chain, and it is
set before each swap so the synchronous `model_changed` fan-out cannot
observe a model mid-relabel. The resumed-session heuristic is gone: it
was only needed to decide whether an unserved candidate could be trusted,
and nothing is trusted before it serves.
2026-08-08 13:16:11 +08:00
enieuwy 0a075662ac fix(session): attribute a run to the model that produced its output
An Agent Hub row reported a subagent as having run on a model that never
spoke. All 97 of its requests, 421K tokens and $6.19 of cost were served
by the primary; a transient stall then armed a fallback, that fallback
errored on its first request with an exhausted quota, and the run died.
Attribution followed the routing switch rather than the output.

Three surfaces lied independently, each re-deriving "the current model"
and calling it the run's model: the executor's progress snapshot, the
session's fallback selector that the hub row reads first, and the
transcript walk behind a settled row.

Sessions now own attribution. `AgentSession.servingModel` names the model
that produced this session's output, holding the last model that actually
served while a candidate is armed but unproven. A switch is a routing
decision, not evidence the target can produce anything, so the answer only
moves once a turn on the target settles.

Consumers read it instead of reconstructing it. The executor's observer
dropped its own event bookkeeping: that bus also carries advisor turns
running on a different model, and it was reading `retry_fallback_applied`
as proof of service. The hub row reads the same getter, so the main
session — which has no executor progress and no persisted history — stops
rendering an unproven candidate as its plain configured model. A fallback
armed before anything has served is still shown, marked as a fallback,
because there is no earlier work to miscredit there.

One predicate decides "this turn produced output", shared by the live
session and the offline replay so they cannot disagree. `error` and
`aborted` are both failures — a stalled stream is finalized as `aborted`
with its partial block still attached, so a stop reason alone proves
nothing — and a turn needs actionable content, which a `length` stop
burning its budget on unsigned thinking does not have. It tolerates
malformed content blocks: transcripts outlive the shapes that wrote them,
and one bad line previously blanked a whole row's history.

Ordering matters at two swap sites. Both the chain advance and the
cooldown-expiry restore move the model and fan `model_changed` out to
subscribers synchronously, so each now updates fallback state before the
swap rather than after; otherwise an observer reading attribution inside
that window sees the incoming candidate carrying the outgoing one's proof.

A startup-selected fallback owns the run from its first request only on a
fresh session. A resumed transcript already holds turns another model
produced, so there the candidate stays unproven until it answers.

`retryFallbackModel` is removed: every consumer reads `servingModel`, and
keeping a parallel derived getter alive for tests is the duplicate surface
this change set exists to remove.
2026-08-08 13:16:11 +08:00
can1357 0697e7f688 refactor(coding-agent): split secret obfuscator into domain modules
- Separated deterministic replacement generation, placeholder derivation,
  placeholder-range scanning and message-tree transforms out of the 2647-line
  module; obfuscator.ts now holds the types and SecretObfuscator.
- ephemeralPlaceholderKey stays a single instance and both global regexes stay
  beside the code that resets their lastIndex, so placeholder stability and
  the security argument in the moved comments are preserved verbatim.
- Repointed every importer at the real modules rather than leaving a re-export
  shim; the public ./secrets barrel exports the same 15 names as before.
2026-08-08 06:32:01 +02:00
can1357 38b61ae342 fix(session): honored explicit retry-after over reason backoff
- A provider-supplied retry-after now bypasses the transient rate/concurrency
  heuristic window instead of being overridden by it (regression from the
  subscription-cap retry change).
- Updated event-controller/ui-helpers test doubles for provenance-gated
  renderer selection (hasBuiltInTool), aggregated retryErrors on
  auto_retry_end, and Bedrock override compat gaining streamIdleTimeoutMs.
2026-08-07 23:38:25 +02:00
roboompandcan1357 7d4b2e7998 fix(agent): re-check context on cooldown-expiry revert in auto-continue path
A cooldown-expiry model revert runs at a turn boundary. The user-prompt
path reverts then re-checks accumulated context against the restored
model via runPrePromptCompactionIfNeeded, but the automatic
agent.continue() path (#scheduleAgentContinue) reverted and issued the
next request with no such check. When a transient failure had fallen
back to a larger-window model and the conversation then grew past the
original model's window, restoring the primary once its cooldown expired
sent a predictably oversized request to the smaller model.

maybeRestoreRetryFallbackPrimary now reports whether it actually
switched, and the auto-continue path runs the same post-revert
context-fit maintenance (compaction/promotion) the prompt path already
runs, but only when a revert occurred.

Fixes #7952
2026-08-07 23:38:24 +02:00
can1357 0e8142ad0e Merge PR #7904: fix(session): surface real handoff errors instead of false cancel (@roboomp) 2026-08-07 14:52:57 +02:00
roboomp 1e6638d8cd fix(session): surfaced real handoff errors instead of false cancel
The handoff catch in session-handoff.ts and the /handoff handler in
command-controller.ts mapped any error named AbortError to "Handoff
cancelled" regardless of whether the handoff signal was actually
aborted. Providers throw name-AbortError errors on non-user conditions
(stalls, idle timeouts, nested resolution failures), so a genuine
generation failure surfaced as a user cancellation and hid the cause.

Only report "Handoff cancelled" when handoffSignal.aborted is set;
re-throw the real error otherwise. The controller now trusts the
normalized "Handoff cancelled" message and drops its own AbortError
check so re-thrown provider failures render as "Handoff failed: ...".

Fixes #7903
2026-08-07 12:00:06 +00:00
can1357 a8a8188e4b Merge PR #7756: fix(coding-agent): preserve before_agent_start prompt override across base rebuilds (@roboomp) 2026-08-07 13:39:53 +02:00
can1357 fe7774a1aa Merge PR #7806: fix(coding-agent): detect non-English questions in todo reminder guard (@roboomp) 2026-08-07 13:39:52 +02:00
can1357 e8e47a3b34 fix(coding-agent): report only matching prewalk arms 2026-08-07 13:39:51 +02:00
can1357 5f758778b8 Merge PR #7785: fix(coding-agent): make prewalk lifecycle one-shot (@eggpeat) 2026-08-07 13:39:51 +02:00
can1357 68dece477f Merge PR #7772: fix(session): handle subscription-cap retry exhaustion (@roboomp) 2026-08-07 13:37:54 +02:00
Brent 2efe8896ea fix(coding-agent): make prewalk lifecycle one-shot 2026-08-06 19:57:10 +00:00
roboomp 181f63ebc0 fix(coding-agent): detect non-English questions in todo reminder guard
The isAwaitingUserAnswer guard that suppresses the todo completion reminder while the agent waits on a user question only recognized English question words and pronouns. A '?'/'?'-terminated Chinese, Japanese, Korean, or Spanish prompt went undetected, so the <system-reminder> interrupted the pause and the model misread it as the user's answer.

isQuestionPromptLine now also treats a question-mark-terminated line containing any non-ASCII character as a pending user question.

Fixes #7803
2026-08-06 06:47:27 +00:00
roboomp f6c5a43a1f fix(session): handled subscription-cap retry exhaustion
- Classified subscription and plan rate caps as credential-rotatable usage limits while preserving transient per-minute throttles.

- Applied reason-specific backoff to transient rate limits and collapsed exhausted retry attempts behind one budget-labeled terminal error.

- Covered classification, delay selection, persisted transcript aggregation, and retry event propagation.

Fixes #7767
2026-08-06 01:31:22 +00:00
roboomp 3df56506c7 fix(coding-agent): preserve before_agent_start prompt override across base rebuilds
The per-turn systemPrompt returned by before_agent_start was applied only to the agent state, so any base-prompt rebuild firing in the prompt window (context-overflow compaction/promotion, memory promotion, MCP/RPC tool refresh, hindsight MM-TTL refresh) re-pushed the rebuilt base via setSystemPrompt and silently dropped the override before the request.

SessionTools now tracks the active per-turn override and re-applies it on every base rebuild during the turn, clearing it when the turn ends.

Fixes #7755
2026-08-05 21:28:37 +00:00
can1357 0474e797da Merge PR #7678: fix(session): migrate hashed session dirs back to legacy names (@roboomp) 2026-08-05 22:16:28 +02:00
can1357 677b6f10f5 Merge PR #7735: fix(coding-agent): return ToolInfo[] from getAllTools extension API (@roboomp) 2026-08-05 22:15:46 +02:00
can1357 3de6dabf98 Merge PR #7740: fix(session): isolate rewind reports by checkpoint cycle (@roboomp) 2026-08-05 21:50:23 +02:00
can1357 a11eacc9d6 fix(advisor): drop unreachable status guard 2026-08-05 21:50:23 +02:00
can1357 c19665ef90 Merge PR #7745: fix(advisor): fail a refusal over to the model fallback chain (@mvid) 2026-08-05 21:50:23 +02:00
Mantas Vidutis a650938d3f Merge upstream/main into fix/advisor-refusal-fallback 2026-08-05 11:43:16 -07:00
roboomp 2cf500dcc0 fix(session): isolated rewind reports by checkpoint cycle
- Bounded rewind report recovery to messages created after the active checkpoint.

- Added resumed-context regression coverage for late stale rewind results.

Fixes #7739
2026-08-05 16:26:56 +00:00
roboomp 83496b8211 fix(coding-agent): returned ToolInfo[] from getAllTools extension API
The ExtensionAPI getAllTools() wired to session.getAllToolNames(),
returning bare tool-name strings. Upstream @earendil-works/pi-coding-agent
promises ToolInfo[] with sourceInfo, so extensions loaded through the
legacy-pi shim (e.g. gentle-pi) crashed on t.sourceInfo.source at every
session start.

Added SourceInfo/ToolInfo types plus SessionTools.getAllToolInfos(), which
returns { name, description, parameters, sourceInfo } and classifies each
tool as builtin/mcp/sdk/extension. Rewired every getAllTools action site
(interactive, acp, print/rpc, subagent executor) and the example extension.

Fixes #7732
2026-08-05 15:42:06 +00:00
can1357 e9888367d1 refactor: migrated packages to internal utility modules and removed external dependencies
- Implemented in-house, zero-dependency utility modules in `pi-utils` covering DOM manipulation, markdown parsing, templating, browser automation helpers, and terminal buffers.
- Migrated packages across the repository to consume the new internal utilities and `omptype` schema validators instead of external dependencies.
- Removed multiple external runtime and development dependencies including Zod, Marked, LRU cache, Turndown, and Puppeteer browser packages.
2026-08-05 13:39:09 +02:00
Can BölükandGitHub 1e492d6ff9 Merge pull request #7354 from brymko/fix/browser-timeout-crash-recovery
Fix/browser timeout crash recovery
2026-08-05 12:39:14 +02:00
brymko 56931915f1 feat(coding-agent): added fatal session recovery hints
Registered live session resume commands with postmortem handling so a
fatal rejection or exception identifies every recoverable agent before
cleanup. Escaped terminal control characters in recovery output.
2026-08-05 14:31:03 +08:00
roboomp 338451ffdb fix(session): migrate hashed session dirs back to legacy names
The 17.2.9 revert (172ce9b) restored the legacy path-based session
directory names but removed all migration, including the reverse path.
Sessions written under the short-lived hashed scheme (17.2.5-17.2.8,
`<scope>-<readable>-<sha256>`) were left orphaned, so `omp -r`
current-folder scope reported no sessions.

computeDefaultSessionDir now reconstructs the hashed dir name for the
cwd and performs a one-way best-effort migration into the legacy name,
alongside the existing legacy-absolute migration.

Fixes #7677
2026-08-05 06:22:22 +00:00
Kyle McCleary 5cc4f5c93a Merge main into refactor/agent-hub-fullscreen 2026-08-04 18:15:42 -07:00
can1357 eb56330c9c chore(changelog): normalized unreleased entries after merges 2026-08-05 02:32:48 +02:00
roboompandcan1357 99748bbe61 fix(ai): omitted codex optional response defaults
Stopped OpenAI Codex requests from sending reasoning.summary, reasoning.context, and text.verbosity unless explicitly configured, matching the safer native Codex request shape for GPT-5.x models.

Preserved explicit overrides and added regression coverage for transformer and settings-aware stream behavior.

Fixes #4949

(cherry picked from commit 15d86667d6572faf453e74b922eed33dd2d3f3c9)
2026-08-05 02:32:36 +02:00
can1357 a6eafcbf6b fix(ai): keep concurrency caps out of auth rotation
(cherry picked from commit bd6285ad9b16ae6f0a75e336a1c4bf961d3e43c7)
2026-08-05 02:32:36 +02:00
metaphoricsandcan1357 5726fac646 fix(ai): tighten concurrency-cap classification and account-cap gating
Reset-window rotation requires account-specific wording; concurrency caps require an actual cap signal; credential removal gated on AuthFailed without UsageLimit so a valid-but-blocked 403 credential is retained.

(cherry picked from commit 2f72752c2586352a4f7e9e814af1cdb0cf192af4)
2026-08-05 02:32:35 +02:00
metaphoricsandcan1357 7262d9762e fix(ai): honor account reset windows and statusless concurrency caps
Account-reset hint evaluated before short retry hints; account-scoped caps rotate on status 403 or undefined (Devin statusless trailer); statusless concurrency caps marked transient; transient same-model retries use the concurrency backoff.

Refuted: quota-worded concurrency caps were already excluded from rotation before the usage-limit text match.
(cherry picked from commit f2b9a18d715ddbcb6ae703670f2212da36bb2826)
2026-08-05 02:32:35 +02:00
Kyle McCleary 5b68e99796 Merge main into refactor/agent-hub-fullscreen 2026-08-04 17:23:50 -07:00
Kyle McCleary 8e5f619502 fix(coding-agent): harden Agent Hub lifecycle and persistence 2026-08-04 16:29:15 -07:00
can1357 89931e109a fix(session): preserve colliding legacy sessions 2026-08-05 01:12:54 +02:00
can1357 94c838faea Merge PR #7539: fix(coding-agent): complete usage-aware fallback integration (@eggpeat) 2026-08-05 01:12:02 +02:00
can1357 975d0a2e9d Merge PR #7501: fix(agent): classify thinking-only unexpected stops (@roboomp) 2026-08-05 01:12:00 +02:00
can1357 0cf5d55ac0 Merge PR #7629: fix(coding-agent): retry buffered print streams (@roboomp) 2026-08-05 01:12:00 +02:00
roboomp 172ce9bf6c revert(session): restored legacy project directory names
Reverted PR #7397 session directory hashing and automatic migration.

Restored existing script compatibility with the legacy cwd-derived naming scheme.

Fixes #7646
2026-08-04 21:10:54 +00:00
Mantas Vidutis 6e32705e71 fix(advisor): fail a refusal over to the model fallback chain
A classifier refusal returned before the `onTurnError` hook that owns
model fallback, so `AdvisorRuntime` treated one provider's policy verdict
as terminal: `Refusal (cyber)` on the advisor model disabled the advisor
outright even with a fallback chain configured. Its only recovery was
stripping echoed primary reasoning and resending once, which does nothing
for a refusal about the content itself.

Route a refusal that outlives the strip through the same hook the generic
failure path uses, mirroring its epoch guard, session-transition requeue,
and requeue-on-recovery. The cascade walks the chain to exhaustion and
only reports the advisor unavailable once the host runs out of candidates,
matching what turn-recovery already allows for the primary.

Each cascade visits a model at most once. A switch re-arms
`#includeThinking` through `#syncModelIdentity`, so chain keys that point
back at each other (A to B, B to A) would otherwise strip-and-resend
against the same pair forever. A successful turn or a reset starts a
fresh walk.

Also stop `/advisor status` throwing when a live advisor has no roster
entry: `formatAdvisorStatus` guarded only the inactive case before
dereferencing `stats.advisors[0]`, and `#ensureAdvisors` clears
`#advisorStatuses` before repopulating it, so a status call landing in
that window hit `undefined.contextWindow`.
2026-08-04 11:47:54 -07:00
roboomp 929ab1a20f fix(coding-agent): allowed retries for buffered print output
Made text replay safety depend on whether the active output sink has committed streamed text.

Kept tool calls, images, and server tools replay-unsafe while covering text and JSON print policies plus a transient socket-close recovery.

Fixes #7625
2026-08-04 13:50:09 +00:00
Kyle McCleary 91467c2f27 fix(coding-agent): restore Agent Hub lineage metrics 2026-08-03 19:42:11 -07:00
Brent 2da96d77a7 fix(coding-agent): close fallback lifecycle races 2026-08-03 17:51:32 +00:00
Brent 353bbc034b fix(coding-agent): harden usage fallback review races 2026-08-03 17:15:57 +00:00
can1357 1a8caad23e chore: update docs + rename reset to clear 2026-08-03 18:37:23 +02:00