The :tools model suffix engages NanoGPT's server-side tool-call parser,
which 502s with code malformed_tool_call on complex DeepSeek payloads
(observed reliably on todo_write). The default route forwards
delta.content (including DSML envelope leaks) which our StreamMarkupHealing
already heals into a structured tool call.
The DSML allowlist still covers nanogpt and the parallel-index fix
remains; only the :tools suffix is removed.
Fixes#1488
- Updated the agent loop to execute tool calls only when the assistant stop reason was `toolUse`.
- Added skipped placeholder `tool_result` messages for leftover `toolCall` blocks when a turn ended without `toolUse`.
- Promoted OpenAI/Ollama `stop` tool-call turns to `toolUse` and stripped thinking signatures on abandoned tool-use turns during message transforms.
- Added a `historyRebuild` render intent to clear viewport and scrollback and emit a full repaint when geometry change invalidated terminal history.
- Updated render planning so width and height changes now rebuild native history immediately, while non-size content-only shrink updates only repaint the viewport to avoid yanking users in existing scrollback.
Tracked OpenAI-compatible streaming tool calls by provider index so parallel NanoGPT read calls keep their argument deltas attached to the matching tool-call block instead of falling through to the most recently opened call.
Added a NanoGPT DeepSeek regression covering two parallel read calls whose argument chunks arrive by index after the start chunk.
Fixes#1488
NanoGPT-hosted DeepSeek models (e.g. `nanogpt/deepseek/deepseek-v4-pro`
with reasoning enabled) emit `<|DSML|tool_calls>` envelopes inside
`delta.content` rather than the structured `tool_calls` array. The
healing pass that turns those leaks into real tool calls only engaged
when `modelMayLeakDsmlToolCalls` saw a known DeepSeek-hosting provider
in its allowlist; NanoGPT was missing, so the markup fell through as
visible text and the turn failed with malformed tool-call data.
Add `nanogpt` to the allowlist so the existing dsml grammar parses
the envelope into a structured tool call, mirroring `deepseek`,
`ollama`, `openrouter`, etc. Covered by a new repro test that
streams the reporter's verbatim DSML leak through the NanoGPT model.
Fixes#1488
- Coalesced overlapping and abutting same-path reads in InMemorySnapshotStore into one existing tag.
- Folded disjoint but consistent reads into a single SparseSnapshot tag when their shared lines matched.
- Minted a new InMemorySnapshotStore tag when shared lines disagreed, treating the view as a changed file.
PluginManager.link symlinks the package into <plugins>/node_modules
and records it in omp-plugins.lock.json, but never writes to
<plugins>/package.json#dependencies. getEnabledPlugins iterated only
the dependency map, so the documented `omp install ./local-extension`
workflow (delegated to plugin link) succeeded but its sibling skills/,
hooks/, tools/, etc. stayed invisible after install.
Iterate the union of package.json#dependencies and
omp-plugins.lock.json#plugins so symlinked-only packages surface
alongside npm/marketplace installs. Lockfile entries whose
node_modules tree has since been deleted (stale link) are skipped
silently. Linked-only setups with no <plugins>/package.json at all
now work too.
Per-PR review feedback: https://github.com/can1357/oh-my-pi/pull/1498
Marketplace and `omp plugin link` installs write to
`<plugins>/node_modules/` rather than to `extensions:` in settings,
so the original PR still missed their sibling skills/, hooks/,
tools/, commands/, rules/, prompts/, .mcp.json sub-trees. Wire
listOmpExtensionRoots to enumerate getEnabledPlugins(cwd, { home })
in addition to CLI-injected and settings-driven roots.
Adds an optional { home } parameter to getEnabledPlugins so the
discovery loader can pass through LoadContext.home for tempdir-rooted
tests. The getPluginsNodeModules/getPluginsPackageJson/
getPluginsLockfile helpers gain the same optional home overload so
they mirror getPluginsDir.
Per-PR review feedback: https://github.com/can1357/oh-my-pi/pull/1498
Bug 1: capability loaders in src/discovery/builtin.ts only walked
.omp/ and ~/.omp/agent/, so extension packages registered via
extensions: in settings or --extension on the CLI shipped their
skills/, hooks/pre|post/, tools/, commands/, rules/, prompts/, and
.mcp.json silently — the docs at omp.sh/docs/extension-authoring
advertise the opposite. Add a new omp-plugins discovery provider that
scans every configured extension package directory for those
sub-trees, plus a small omp-extension-roots helper that resolves the
union of settings-driven and CLI-injected roots. main.ts injects CLI
extension paths via injectOmpExtensionCliRoots before any capability
load.
Bug 2: install was never registered as a top-level subcommand, so
`omp install ./my-extension` was rewritten to `launch install
./my-extension` and forwarded to the LLM as an initial prompt. Add a
top-level install command that routes local paths to plugin link and
remote specs to plugin install. Extract the command table into
src/cli-commands.ts so tests can introspect registered subcommands
without triggering cli.ts's top-level await.
Fixes#1496
Updated zhipu-coding-plan discovery and credential validation to use the dedicated Coding Plan API base URL instead of the general BigModel endpoint.
Added regression coverage for the default discovery URL.
Fixes#1494
- Added AnthropicCompat.supportsMidConversationSystem and AnthropicMessageParam system-role support for per-model control.
- Added supportsMidConversationSystemMessages(modelId) to return false on unknown models and true only for opus >=4.8.
- Updated convertAnthropicMessages/buildParams to map eligible developer turns to system role and keep unsupported turns as user.
- Added tests and fixtures for mid-conversation handling and documented `claude-opus-4-8` Bedrock metadata in changelog.
- Added a new ultrathink mode module with standalone, case-insensitive detection, rainbow editor highlighting, and a hidden notice payload.
- Updated `CustomEditor` and the shared TUI `Editor` to support optional zero-width text decoration and apply the `ultrathink` styling during input rendering.
- Extended `AgentSession` prompt handling to append the hidden ultrathink notice after user turns in both streaming and non-streaming message flows, excluding synthetic messages.
- Mocked the Vertex stream E2E test to override the home directory and clear GOOGLE_APPLICATION_CREDENTIALS so token resolution uses metadata credentials instead of local ADC files.
- Updated wafer and model-registry test expectations to match current model metadata values (Qwen3.7 Max and claude-opus-4-8).
- Added a `hashRecognized` field to `MismatchDetails` and `MismatchError`, defaulting it to `true` for compatibility.
- Updated stale mismatch rejection messaging to distinguish drifted hashes from session-absent hashes with explicit guidance.
- Propagated `hashRecognized: snapshot !== null` in `Patcher` and added tests for both mismatch branches.
- Added Opus 4.8 metadata and expanded provider entries, including Bedrock and other model variants.
- Updated `models.json` with additional Claude, Grok, Gemini, and Qwen entries carrying reasoning or image support.
- Adjusted Anthropic effort mapping for Opus 4.7+ to a five-tier scale and cached `requireSupportedEffort` results.
- Adjusted xhigh handling so legacy models now map to max and Opus 4.7+ map to low/med/high/xhigh/max.
- Updated thinking and alignment tests to expect revised xhigh/max mappings for opus47 and legacy models.
The memory pipeline hardcoded `Effort.Low` (stage1) and `Effort.Medium` (phase2
consolidation) when calling `completeSimple`. On models whose supported efforts
exclude those levels (e.g. `deepseek/deepseek-v4-pro` → [high, xhigh]),
`completeSimple → mapOptionsForApi → resolveOpenAiReasoningEffort →
requireSupportedEffort` threw "Thinking effort low is not supported by
<provider>/<model>" and every stage1 job was recorded as failed, blocking phase2
and producing no memory artifacts.
Route both call sites through `clampThinkingLevelForModel(model, requested)` —
the same helper already used by compaction (#1182). For `[high, xhigh]` both
`low` and `medium` lift to `high`; non-reasoning models continue to receive
`undefined`, preserving prior behaviour.
Fixes#1480
Limited bunfs package-root overrides to compiled-binary mode so non-compiled installs (monorepo, source-link, node_modules) keep resolving legacy pi roots through Bun's package resolver instead of a hardcoded source-tree path.
Refs #1474
Retried original legacy specifiers after canonical peer fallback fails so direct plugin imports with only legacy-scoped peer dependencies continue to load.
Listing the coding-agent's own ./src/index.ts as a bun --compile extra entrypoint silently breaks the CLI binary startup. Added a dedicated legacy-pi-coding-agent-shim.ts that re-exports the canonical barrel, registered the shim instead of the package index, and updated the compat resolver to point pi-coding-agent at the shim path.
Added bundled root overrides for legacy pi package imports in compiled binaries and corrected fallback resolution to use canonical @oh-my-pi specifiers.
Fixes#1474
- Column truncation is now applied to a cloned array so `collectedLines` retains on-disk content for snapshot recording.
- Snapshots bound to hashline TAGs now hold the original file text, preventing hash-mismatch failures on subsequent edits to files with long lines.
- Added regression tests covering full-file, range, multi-range reads and a live edit-after-read scenario.
- Prepended `¶path#TAG` hashline header to plain file, ACP-bridge, and conflict resolution write results.
- Bulk conflict resolutions emit a trailing `Snapshots:` block with one header per written file.
- Suppressed when hashline display mode is disabled or for archive/SQLite/internal-URL targets.
- Added tests covering header presence, patcher usability, and disabled-mode suppression.
@yofriadi flagged that DeepSeek V4, GLM-5.x, Qwen3.x, MiMo, and MiniMax
on opencode-go front the same Zen gateway as Kimi (https://opencode.ai/go).
The thinking-state invariant — reasoning_content required on assistant
tool-call history when thinking is enabled, rejected when off — is
gateway-wide, not Kimi-specific.
Drop the `isKimiModelId` gate from the buildParams override so every
opencode request in thinking mode flips
requiresReasoningContentForToolCalls=true (with
allowsSyntheticReasoningContentForToolCalls=false and
reasoningContentField="reasoning_content" pinned the same way). The
existing #1071 guard for thinking-off and the
disableReasoningOnForcedToolChoice guard remain intact, so non-thinking
and forced-tool turns never inject the field.
Added parametrized regression tests covering GLM-5.1, Qwen3.7 Max, and
MiMo-V2 Pro through `streamOpenAICompletions` + `onPayload` — three
distinct thinkingFormat paths ("openai", "qwen", "openai") — to
prove the override fires regardless of model family. Expanded the
CHANGELOG entry to list every catalog model now covered.
@yofriadi reports the same OpenCode Zen 'reasoning_content is missing'
400 on `opencode-go/deepseek-v4-flash` and `deepseek-v4-pro`. The
gateway is the same Zen instance and the wire-body invariant matches:
the streamed-signature path in convertMessages previously emitted both
`reasoning` and `reasoning_content` on DeepSeek turns, which the
strict schema flags exactly like the Kimi case.
The line-1488 fix from 4215228b8 already coerces the replay onto the
configured `reasoningContentField` whenever
`allowsSyntheticReasoningContentForToolCalls=false` — DeepSeek family
sets that flag — so deepseek-v4 payloads now carry only
`reasoning_content`. Add a streamOpenAICompletions + onPayload
regression test that pins the shape.
The opencode-kimi thinking-mode override flipped
`requiresReasoningContentForToolCalls` on but left the rest of compat
at its default: `allowsSyntheticReasoningContentForToolCalls=true` and
the streamed-signature path in `convertMessages` echoed whichever
recognized field the upstream emitted. Opencode Kimi streams reasoning
under `reasoning`, so a follow-up replay landed `reasoning` on the
assistant message and left `reasoning_content` empty — the gateway
still 400s with 'reasoning_content is missing in assistant tool call
message at index N'.
Force `reasoning_content` as the wire field for this override:
- `buildParams` now also sets
`allowsSyntheticReasoningContentForToolCalls=false` and
`reasoningContentField="reasoning_content"` on the same gated
branch (kimi + opencode + thinking-on + not forced-tool).
- `convertMessages` thinking-block branch now respects
`allowsSyntheticReasoningContentForToolCalls`: when false, replay
always uses the configured `reasoningContentField` instead of the
streamed signature, so we never simultaneously write to both
`reasoning` and `reasoning_content`. DeepSeek already runs through
the same code with `allowsSynthetic=false` and existing tests
continue to pass under the cleaner output.
Updated the #1484 regression test to use the upstream's actual
`thinkingSignature: "reasoning"` shape and to additionally assert
that `reasoning` is absent from the wire body so a future regression
to dual-key emission would fail.
When OpenCode Kimi receives a forced `tool_choice`, the existing
`disableReasoningOnForcedToolChoice` guard at the bottom of
`buildParams` strips `reasoning_effort` (and sets `thinking: disabled`
for zai-format paths) from the wire body so Kimi does not 400 with
'tool_choice specified is incompatible with thinking enabled'. The
per-request reasoning_content override added for #1484 ran earlier in
`buildParams` and ignored that suppression, so the resulting payload
combined a thinking-disabled request with replayed reasoning_content on
prior assistant tool-call turns — reviving the #1071 'Extra inputs are
not permitted' failure.
Mirror the suppression in the override: skip flipping
`requiresReasoningContentForToolCalls` whenever
`compat.disableReasoningOnForcedToolChoice` would erase thinking on
this turn. New regression test exercises the forced-tool path through
`streamOpenAICompletions` + `onPayload` and asserts both the absence
of `reasoning_content` on the assistant message and the absence of
`reasoning_effort` on the request body.
OpenCode Zen's Kimi gateway gates reasoning_content on the request's
thinking state: it 400s with 'Extra inputs are not permitted' when
thinking is off but the field is supplied (#1071), and 400s with
'thinking is enabled but reasoning_content is missing in assistant tool
call message at index N' (#1484) when thinking is on and the field is
absent. Static compat detection from #1071 kept the field off
unconditionally, so reasoning-mode requests now 400 on the follow-up
turn after any tool call.
Override compat.requiresReasoningContentForToolCalls in buildParams when
the request itself is in thinking mode (options.reasoning set,
!options.disableReasoning, model.reasoning true), so prior tool-call
turns replay reasoning_content while thinking-disabled requests keep
the #1071 guard intact. Regression tests exercise both states through
streamOpenAICompletions + onPayload to assert on the wire body.
Fixes#1484
- Changed `bridge_chunks` to drain all currently queued output chunks into one string before invoking the JS callback.
- Added a 64 KiB batch cap with a small initial buffer so each N-API dispatch stays bounded while reducing callback churn from chatty processes.
- Added `/drop-images` slash command handling for runtime and TUI to drop images and report results.
- Added `stripImagesFromMessage` utilities to remove image blocks from message content and return removal counts.
- Added `AgentSession.dropImages()` to prune image blocks, rewrite history when needed, and rebuild session context.
- Added tests for user, toolResult, fileMention, and assistant image-stripping, placeholders, and zero-removal cases.
- Centralized compaction stop-reason error throws via createSummarizationError().
- Set compaction thrown errors to copy response.errorStatus into Error.status.
- Expanded compaction auth detection to treat HTTP 401/403 as auth failures with regex fallback preserved.
- Added regression tests for 401/403 status propagation and compaction fallback auth behavior.
- Documented both package fixes in Unreleased Fixed changelog entries.
- Added fetch.pruneTags=false to git wrapper to prevent tag deletion during concurrent maintenance.
- Implemented atomic tag creation and push with retry logic to defend against tags being pruned by background git maintenance processes.
- Changed push strategy to use --atomic flag and retry up to 3 times on refspec mismatch errors.
- Added embedded addon tarball output in `embed-native.ts` using `embedded-addons.<platform>.tar.gz` artifacts.
- Added metadata-rich addon types with `size`, `filePath`, and optional `archive` fields.
- Updated extraction to prioritize archive unpacking and skip cached `.node` files when sizes match.
- Added `extractEmbeddedAddonArchive()` with archive parsing and safe validation of archive entry names and kinds.
- Adjusted release profile to disable line-table generation and strip symbols in `Cargo.toml`.
- Added CI-only ELF validation for forbidden sections and regression coverage for issue-823 archive extraction.