Reverted branch-side edits to spawn-policy prompts/tests, settings tab
groups, mermaid cache typing, prewalk todo gating, and packages/ai test
churn back to merge-base content; trimmed their changelog entries. These
repaired stale CI against an older main and are stale or conflicting
against current main.
A completed delivery hands off from the AsyncJobManager to the session's
yield queue before the follow-up is injected (idle flush runs on a delayed
post-prompt task; mid-turn entries wait for the next step boundary). In
that window hasPendingAsyncWork() read false from manager state alone, so
a terminal yield observed there terminated the run and silently dropped
the delivered result - the stale-success class the quiescence barrier
exists to prevent. The wake predicate now also counts queued async-result
entries on the yield queue; added a session-level contract test that
pinned the window (failed before, passes after).
Review follow-ups (Codex on #6362, round 5):
- #computeSnapcompactRescueMaxFrames now subtracts the kept tail AFTER the
archive (plus the existing fixed-context reserves) so the budget mirrors
what #compactionCreatedHeadroom will measure, and returns 0 when not even
one frame fits — the rescue bails instead of appending a rebuild that can
never create headroom (and would wedge prepareCompaction behind its
last-entry guard once elide fixes the real tail).
- Dead-end warnings now stamp the branch's LATEST compaction entry: the
post-pass path no longer badges the entry the rescue just superseded, and
the no-preparation path badges the rebuilt entry when the rescue appended
without creating headroom.
Claude-Session: https://claude.ai/code/session_014rh4JyWFkxgMhgFaEf8VBY
Review follow-up (Codex on #6362, round 4): rebuilding a non-tail archive
appends the replacement compaction at the leaf, so the branch tail becomes a
compaction entry that prepareCompaction's last-entry guard can never
summarize past — even after elide shrinks the oversized kept tool result
that was the real culprit. The rescue now estimates the kept tail AFTER the
latest archive and bails when it alone exceeds the recovery band, leaving
that shape to the elide/image tiers.
Claude-Session: https://claude.ai/code/session_014rh4JyWFkxgMhgFaEf8VBY
Review follow-ups (Codex on #6362):
- #rescueSnapcompactFrameOverflow now returns the CompactionResult and emits
session_compact for the rebuilt entry, so extensions see the entry that is
actually active instead of (only) the one the rescue superseded.
- The no-preparation auto_compaction_end now carries that result instead of
{result: undefined, skipped: true} when the rescue rewrote history — the
TUI rebuilds the transcript on result, so a successful rescue is no longer
presented as a benign no-op.
Claude-Session: https://claude.ai/code/session_014rh4JyWFkxgMhgFaEf8VBY
Review follow-up (Codex on #6362): the rescue's replaceMessages() rebuild
drops the transient plan-reference message, so clear #planReferenceSent
(#1246) and reset advisor runtimes / todo phases exactly like the regular
compaction append path.
Claude-Session: https://claude.ai/code/session_014rh4JyWFkxgMhgFaEf8VBY
Review follow-ups (Codex on #6362):
- The !preparation frame rescue now counts as complete only when the rebuild
actually created headroom; otherwise the elide/image tiers still run and the
no-progress warning stays — a frame-count shrink alone must not suppress it
when the oversized tail is a kept message/tool result the archive rescue
cannot touch.
- #computeSnapcompactRescueMaxFrames now applies the same MAX_FRAMES_DEFAULT /
maxFramesForDataBudget caps as #computeSnapcompactMaxFrames, so a
threshold-derived count can never exceed what the rebuilt prompt can attach.
Claude-Session: https://claude.ai/code/session_014rh4JyWFkxgMhgFaEf8VBY
A branch whose last entry is a snapcompact CompactionEntry billed past the
compaction threshold (FRAME_TOKEN_ESTIMATE x frames) dead-ended on every
resume: prepareCompaction returns undefined (nothing after the entry to
summarize), and the #4786 elide/image rescue tiers only inspect
"message"/"custom_message" entries, so a type:"compaction" tail escaped both
and the "Compaction freed too little context" warning re-fired forever.
Add a dedicated first rescue tier that rebuilds the SAME archive locally (no
LLM, no network) by re-running snapcompact.compact() over the entry's
carried-forward source text at a maxFrames derived from the trigger
threshold's recovery band instead of the window-fit budget: planArchive
truncates the oldest chars to fit, so the rebuilt entry genuinely shrinks.
Persisting through appendCompaction lets the write-time
superseded-compaction elision drop the stale frame payload from the JSONL,
and the pass skips the misleading no-progress warning.
Fixes the loop reported in
https://github.com/can1357/oh-my-pi/issues/4786#issuecomment-5056055342
Claude-Session: https://claude.ai/code/session_014rh4JyWFkxgMhgFaEf8VBY
- Implemented ModelRegistry.hasProvider to return true when a provider has live models, is discoverable, or is registered at runtime.
- Replaced AgentSession's internal provider check with #isKnownProvider that delegates to the new hasProvider method, updating related fallback logic.
- Exposed the active retry fallback selector from live agent sessions.
- Rendered fallback rows with an explicit marker and resolved provider/model.
- Added an end-to-end fallback-to-Agent-Hub regression assertion.
Fixes#6316
Emitted session_shutdown after model rendering and centralized managed timer cleanup across one-shot listings and agent sessions.
Added regression coverage for the extension shutdown lifecycle.
Fixes#6297
clampTimeout resolved the per-tool default (bash 300s) whenever the agent
omitted `timeout` and only enforced the tool's own min/max, so the
tools.maxTimeout global ceiling — applied solely in sdk.ts on explicitly
numeric args — was bypassed on the common default-fallback path.
Thread maxTimeout into clampTimeout so the resolved effective timeout,
including the default path, is capped before the per-tool floor/ceiling
apply. Explicit values below the cap still win; maxTimeout <= 0 stays
no-cap. Applied at every call site (bash, eval, browser, debug, lsp,
fetch, and the session-level bash executor), and the bash clamp notice
now names the global ceiling when it is the binding limit.
Fixes#6294
- Add missing `import { describe, expect, it } from "bun:test"` to
workspace-directories test file (required for TS type checking)
- Move workspace.additionalDirectories setting from tab=model/group=Context
(unregistered) to tab=context/group=General (registered in TAB_GROUPS)
- Wrap refreshBaseSystemPrompt in switchSession in try-catch so a rebuild
failure doesn't roll back an otherwise-successful session switch
Co-authored-by: oh-my-pi <https://omp.sh>
- Carried startup-selected fallback role and primary selector into AgentSession.
- Continued remaining role fallback entries after the startup fallback fails.
- Added regression coverage for chained startup failover.
Fixes#6283
switchSession (/resume, RPC reload) updates the manager's roots from
the target header but never rebuilt the base system prompt. The next
turn would see the previous session's stale <workspace-roots> block
until an unrelated prompt rebuild happened.
Co-authored-by: oh-my-pi <https://omp.sh>
- Add additionalDirectories to NewSessionOptions
- #resetToNewSession now seeds #additionalDirectories from options,
so all new-session transitions (handoff, branch, /new) get the roots
- AgentSession.newSession passes settings dirs via options instead of
calling setAdditionalDirectories after the fact
Co-authored-by: oh-my-pi <https://omp.sh>
- Call refreshBaseSystemPrompt() after newSession so the
<workspace-roots> block reflects the new session's directory set,
not the previous session's stale roots (agent-session.ts)
- Replace inline require('node:os') with top-level import * as os
in test file, following repo convention
Co-authored-by: oh-my-pi <https://omp.sh>
- Seed workspace.additionalDirectories settings on initial launch session,
not just /new (sdk.ts)
- Always call setAdditionalDirectories on /new, even with empty list, to
clear stale roots from the previous session (agent-session.ts)
- Make setAdditionalDirectories async and trigger atomic rewrite when a
session file already exists, so --continue --add-dir persists (session-manager.ts)
- Route addWorkspaceDirectory/removeWorkspaceDirectory through
normalizeWorkspaceDirectory for consistent ~ expansion (session-manager.ts)
- Drop dead exports: workspaceRootForPath (no production callers),
getWorkspace (no production callers), and unused SessionWorkspace type
import from session-manager.ts (session-workspace.ts, session-manager.ts)
- Remove unnecessary as SettingPath / as string[] casts (agent-session.ts)
- Update tests: add ~ expansion coverage, root-clearing on /new,
persistence on resumed sessions, fix header line parsing
Co-authored-by: oh-my-pi <https://omp.sh>
A session now carries an ordered list of workspace directories beyond cwd,
managed live from the terminal. New /add-dir, /remove-dir, and /dirs slash
commands let you add and remove folders mid-session; the repeatable --add-dir
CLI flag seeds them at launch, and the workspace.additionalDirectories
setting persists defaults per project. Additional roots are persisted in the
session header, survive reopen/fork/move, and are surfaced to the agent in the
system prompt so it knows they exist and can read/grep/glob them by absolute
path. Design aligns with the endorsed community implementation on
feature/session-workspace.
Co-authored-by: oh-my-pi <https://omp.sh>
A non-retriable provider error on the continuation turn after a failed
tool result ended the run, but #persistSessionMessageIfMissing dropped
the empty error turn as reload poison, so the session JSONL stopped at
the last tool result and the provider errorMessage was lost with no
durable record of why the run stopped. When retry, model fallback, and
compaction all decline the turn, the non-retry terminal error tail now
persists it via the same helper the retry-lifecycle dead-ends use; the
empty turn stays off the wire on reload via the transform-messages
empty-assistant filter, matching the existing retry-exhaustion path.
Fixes#6249
The agent_end handler only recorded stopReason/provider/model at debug and dropped errorMessage/errorStatus/errorId, so a session dying repeatedly on provider stream failures left no actionable trace in the main log. Extract logProviderTurnError and emit one warn-level entry carrying provider, model, errorMessage, errorStatus, and errorId when a turn ends in stopReason:error.
Fixes#6177
Short-circuited session_stop emission when an abort or disposal is already in progress, avoiding extension work whose result cannot be used.
Added deterministic coverage for an abort racing the final settle pass.
Fixes#6134
A stream that stalls or aborts mid-tool-call ends the assistant turn with
stopReason error/aborted, then appends a synthetic tool_result per un-run
tool call to keep the provider's tool_use/tool_result pairing intact. That
placeholder trailed the failed turn, so AgentSession.retry() — which only
inspected the last message and required role assistant — short-circuited to
false and /retry printed 'Nothing to retry'.
retry() now walks back over trailing synthetic tool results (details
__synthetic true) before the assistant + stopReason check, stripping both
the placeholders and the failed turn. Only synthetic results are skipped, so
a turn whose tools actually ran stays non-retryable. Adds an exported
isSyntheticToolResultMessage guard in agent-loop.ts.
Fixes#6056