Plan-mode entry only added `resolve` to the active toolset, so when
`tools.discoveryMode: "all"` left `write` hidden behind
`search_tool_bm25` the agent was stuck with `edit` to create the plan
file — which fails on a non-existent path and stalls the planning
turn. `#enterPlanMode` now augments the active set with both `resolve`
and `write` whenever the registry built them, matching what
`plan-mode-active.md` instructs the model to use, and `#exitPlanMode`
still restores the pre-plan toolset verbatim.
Fixes#3165
- Migrated all wire protocol, schema definitions, and tools validation from Zod to ArkType across multiple packages.
- Updated extension runtimes, custom tools loader, and TypeBox compatibility shim to expose and use ArkType instances.
- Added a comprehensive ArkType migration guide, validation parity tests, and helper utilities.
- Removed redundant PDF asset routing and parsing implementations from the read tool.
A blocklist of `message`/`mode_change` entries missed context-bearing entries
that carry no literal message — a compacted branch, a branch summary, or a
custom-role message — which buildSessionContext still feeds the model. Such a
resumed session was misclassified as fresh and auto-entered plan mode, overriding
its restored mode.
Gate on buildSessionContext().messages (the resolved branch context — messages,
custom messages, branch summaries, and compaction summaries) plus a `mode_change`
entry check. SDK startup metadata and extension `custom` state entries stay
excluded. Added a regression test for a compacted session with no trailing
message.
The startup-metadata allowlist was incomplete: an extension that persists a
custom entry during session_start (initHooksAndCustomTools runs just before this
guard) made a brand-new session look non-fresh, so plan.defaultOnStartup never
engaged for those users.
Invert to a blocklist: a session is brand-new unless it has a conversation
`message` or a user `mode_change` (which #reconcileModeFromSession just restored).
SDK startup metadata and extension custom entries are ignored. This satisfies the
fresh, continue-created, resumed-conversation, and toggled-off-mode cases at once.
Added a regression test for the extension custom-entry case.
The previous gate keyed off the absence of message entries, but a session can
carry a user mode_change (e.g. plan enabled then toggled off → mode_change
"none") with no message yet. #reconcileModeFromSession restores that off state,
then the startup default wrongly re-entered plan mode because there was no
message entry.
Gate on an allowlist of the SDK's fresh-session startup metadata (model_change,
thinking_level_change, service_tier_change, mcp_tool_selection): a session is
brand-new only when every entry is one of those. Any message or user mode_change
means it is not fresh and its reconciled mode stands. Added a regression test
for the plan→off→continue case.
The plan.defaultOnStartup gate keyed off getEntries().length === 0, but
createAgentSession appends startup metadata (model_change / thinking_level_change
/ service_tier_change) for a brand-new session before InteractiveMode.init()
runs. So a genuinely new session reached the guard with entries already present
and never entered plan mode (the unit harness bypassed the SDK path, hiding it).
Gate on the absence of conversation messages (entry.type === "message") instead.
A fresh session — including a --continue/auto-resume that created one — carries
only metadata and still honors the default; a session with restored conversation
keeps its reconciled mode. Added a regression test seeding model/thinking-level
metadata.
The startup plan-mode auto-entry gated on the CLI-derived `resuming` flag
(continue || resume || fork). But `omp --continue` with no recent session
calls SessionManager.continueRecent(), which creates a fresh, empty session
while `resuming` stays true — so the new setting was skipped on what is really
a brand-new session.
Gate on the actual signal instead: this.sessionManager.getEntries().length === 0,
the same "has prior history" check the auto-resume path already uses. A session
that opens empty (new, or a continue/auto-resume that found nothing) honors the
default; one with restored history keeps its reconciled mode. Removed the now-dead
`resuming` InteractiveModeInitOptions field and its init() argument (the runtime
flag is still used for setup-scene selection). Updated the regression test to
seed real history and corrected the CHANGELOG wording.
New boolean setting (Tasks tab, default off) that auto-enters plan mode at the start of every new interactive session. Scoped to launch via the existing resuming flag, so resumed/continued/forked sessions keep their reconciled mode and the plan-approval -> execution handoff clear is never dragged back into plan mode. Silent no-op when plan.enabled is off or another mode is active; hidden in the settings UI behind a planModeEnabled condition.