Conversation
The package default-exports the union { id, server, setup }. A v1 host
calls server(); OpenCode 2 calls setup() only, and OpenCode 2 has no v1
plugin compat lane -- none of the v1 hook identifiers exist in the 2.0.10
binary. createToolRegistry() was called only from server(), so under
OpenCode 2 none of ctx_reduce / ctx_search / ctx_memory / ctx_note /
ctx_expand were ever registered and the agent had no Magic Context tools.
registerContext() now builds the registry and adds every tool through
context.tool.transform(...) with options.codemode:false, so they surface
as direct tools exactly as the v1 lane exposed them:
- the zod arg shape is rendered to the JSON Schema the v2 tool domain
expects, with a permissive fallback if a shape is unrenderable so a
schema failure can never abort plugin setup;
- the v1 ToolResult is bridged to the v2 Result shape (content/metadata);
- the v2 execute context carries no `directory`, so it is closed over
from context.location.directory (the only field the ctx_* tools read
besides sessionID and agent).
The registration is wrapped in try/catch: a tools failure degrades to the
previous behaviour (context management still works) instead of breaking
the lane.
Verified on OpenCode 2.0.10: a fresh session lists the five tools and
successfully calls ctx_search.
OpenCode 2 never runs the v1 server() lane, so MagicContextRpcServer was never constructed on this host and the port-file discovery directory stayed empty. The v2 TUI is a pure RPC client (no direct SQLite access), so the sidebar and /ctx-status rendered zeros. Construct and start the RPC server from registerContext, reusing the v2 lane's draft-authoritative live maps (model/variant/agent plus the Channel 1 and refresh sets) so the snapshot/status handlers resolve the session's active model. client/rustModeModuleClient stay undefined: only the recomp/upgrade notify paths need them, and those stay inert on this lane. Start is deferred one task (v1-lane parity) and stopped in dispose(). Verified on an isolated `opencode run --standalone` host: port file written under rpc/<project>/, /health OK, sidebar-snapshot returns snapshot data, and the port file is removed on dispose.
OpenCode 2 runs plugin setup() outside the TUI component tree, so context.keymap.layer() threw "Keymap.Provider is missing" and /ctx-status and /ctx-recomp were never registered. Keep the direct call as the first attempt (hosts that do run setup in-tree), and on that exact error claim the always-mounted "app" slot: its render executes inside the keymap provider, which is where the host's own built-in plugins register their layers. Registration is idempotent across renders, the claim is released on cleanup, and a permanent gap warns once without touching the sidebar. Confirmed live: /ctx-status appears and renders data on OpenCode 2.0.10.
registerContext returned early when compaction was disabled, so such projects got no ctx_* tools, no RPC surface and no memory/docs injection on OpenCode 2 — unlike the v1 lane, where compaction-off is a supported mode. Keep the lane active: the tool registry (already excludes ctx_reduce in this mode), the memory/docs transform (compactionOff: true) and the RPC server all run, while compaction-only machinery stays out of the way — the host checkpoint hook, fold observe/restore, historian runs and the >=95% unsafe interrupt are skipped so the host's native compaction owns the window. Storage failures no longer abort turns in this mode. The v2 TUI sidebar/status now render the compaction-off state (native compaction context label plus memories/notes/archived-compartment rows), mirroring the v1 TUI and using the shared COMPACTION_ENABLED_PATH constant. Verified on an isolated `opencode run --standalone` host scoped by XDG_CONFIG_HOME to a compaction-off config: ctx_search was called and the run exited cleanly, and the RPC sidebar-snapshot reported compaction_enabled:false. A compaction-on run still answers normally.
Three gaps made the sidebar and status dialog show 0.0% · 0/200k: 1. Nothing persisted context usage: last_context_percentage/last_input_tokens are written by the v1 event handler, which the v2 lane does not run. The v2 lane now persists them (plus last_usage_context_limit and last_observed_model_key) alongside the usage map it already maintained. 2. MC's model-limit cache was never warmed on this lane, and its harness-scoped persisted file started empty, so every limit resolved to the generic 200k default. registerContext now seeds the shared cache from context.model.list() (the host's own resolved catalog) one task later, which also persists the last-known-good file for cold starts. 3. findLastAssistantModelFromOpenCodeDb read only the frozen v1 `message` table, so a v2 session without a live entry resolved no model at all. It now prefers session_message when present and falls back to v1 for legacy sessions an OpenCode 2 migration left behind. Verified on an isolated standalone host: session_meta gains real usage (1.36% / 13,569 tokens), model-context-limits-opencode2.json is written with 136 entries, and a sidebar-snapshot for a session without a live entry resolves contextLimit 934464 instead of 0 or 200000.
The v2 lane persists usage in refuseIfUnsafe, which runs before the transform; the transform's first-pass reset then zeroes last_context_percentage and last_input_tokens mid-pass, so the first turn after a process restart still showed 0.0% · 0 in the sidebar even with the persistence in place. Capture the measured usage and re-apply it after the pass (the v1 lane's event handler writes after the pass for the same reason). The early write stays so the unsafe-abort path, which returns before the transform, still records. Verified by continuing an existing session from a new standalone process (the first-transform-pass case): 1.41% / 14,066 tokens persisted instead of 0.
The v1 lane runs /ctx-dream through its host command template; the v2 lane has no command path, so it becomes a third slash command in the TUI keymap layer (next to /ctx-status and /ctx-recomp), backed by a new "dream" RPC. - The RPC runs runManualDream with the same executor wiring as the event-driven dream trigger, using the requesting session as the hidden children's parent. A pass can outlive the request timeout, so it starts in the background and pushes a show-result-dialog notification with the summary when it finishes (toasts cover validation, unavailable, and permanent-failure cases). - summarizeManualDream moved into the dreamer feature directory so the v1 command output and the v2 notification render identically. - "/ctx-dream <task>" validates against CANONICAL_DREAM_TASKS and force-runs that one task, matching v1 semantics. Verified on an isolated standalone host: an unknown task returns the valid-task list; a no-arg run is accepted and seeds task_schedule_state for the project. Confirmed live in the TUI.
Six fixes from the review (no blocking findings): - Pressure math and the persisted percentage now resolve the output-reserved usable window via resolveContextLimit — the same denominator the sidebar, history budgets and geometry use. The raw catalog window previously made the sidebar show 1.36% next to 13,569/934,464. - EmergencyFailClosedError / FailClosedBlockingError from the transform are treated as blocking (interrupt + refusal) instead of being swallowed with a warning and sending the unmodified oversized prompt; compaction-off stays inert, matching the v1 wrapper. - The measured-usage handoff is keyed by session, attributes the reading to the assistant row's own model, and skips the post-pass re-apply when that model differs from the pass's — a model switch must not resurrect stale per-model usage. - findLastAssistantModelFromOpenCodeDb gates its v2 query on isOpenCodeV2Store (1.18 also ships session_message), guards each query individually, and falls back to the v1 table; tests pin the 1.18 store shape. - Recomp/historian can use the lane's hidden-completion executor through ManagedRecompContext (v1 path unchanged), so a host without an SDK client no longer toasts success while the runner throws "Hidden completion client is unavailable". The upgrade's SDK-only memory migration is skipped when no client exists. - The model-limit warm retries after a failed attempt instead of latching forever (retry per pass until the cache holds entries), and tokens.cache reads are guarded for partial rows. Verified: full suite 4950 pass, typecheck clean; an isolated run persists percentage 1.4511 = 13,560/934,464 (was 1.356 = /1,000,000).
Round two (frozen PR worktree) reported no blocking findings; these close the three worth-fixing items and four nits: - The e2e host-contract scan no longer trips on Map/Set `.delete(` (anchored on `session.` lifecycle calls) and allows the deliberate fresh createLiveSessionState() the RPC-server fix needs. The opencode2 contract file passes 10/10; the rest of that suite fails environmentally here (macOS opens /Library/Preferences/Logging paths the hermetic runner forbids). - hidden-completion.ts guards tokens.cache reads, matching the context.ts fix: a partial usage row no longer aborts the whole hidden (historian/dreamer) run. - refuseIfUnsafe uses V2StoreReader.latestAssistant (indexed LIMIT 1) instead of paging the entire session history on every pass. - Usage attribution: a store row without model metadata records no model key and skips the post-pass re-apply, so a switch turn cannot resurrect stale tokens. - /ctx-dream: availability is checked before task validation (v1 messaging), the duplicate server-side "started" toast is gone, requiresTools tasks are reported as unsupported (no tool loop) instead of failed, and a missing context.tool.transform now logs instead of silently skipping registration. Verified: plugin suite 4953/0; e2e adapters-s2-contracts 10/10; typecheck clean.
Round three reported one major and three minor findings; these close them: - Switch-turn admission: the 0.95 guard now measures against the OUTGOING model's window while the persisted reading stays attributed to the model that produced it (new pure helper v2/hooks/usage-reading.ts + tests). Previously a switch from a small to a larger window could refuse on the old model's ratio forever, because the refused turn never let the transform observe the switch. - The scheduled dreamer path applies the same requiresTools capability filter as the manual one, so a v2 host no longer records tool-requiring tasks as failed on every slot. - Session teardown: a session.deleted subscription clears the lane's per-session maps, releases the raw-message provider and drops the sidebar snapshot cache, mirroring the v1 lane (previously everything lived until plugin disposal). - Regression tests: usage-reading (same-model / switch / model-less / partial cache), a cache-less completion row, and latestAssistant ordering. - Nits: the requested-task branch of selectRunnableDreamTasks no longer returns tool-requiring tasks as runnable; the unsupported-only /ctx-dream dialog no longer prints the contradictory "No enabled dream tasks to run."; guarded tokens.input/output in hidden-completion. Verified: plugin suite 4959/0; e2e adapters-s2-contracts + store-reader 15/15; typecheck clean.
Round four reported three worth-fixing items plus nits; these close them: - The dream scheduler pruned task rows against the CALLER's task list, so the round-three capability filter deleted all ten requiresTools canonical rows (last_run_at / next_due_at / watermarks) on the first execution event of a v2 host — project-scoped data v1 would inherit the loss of. planDueTasks now prunes against CANONICAL_DREAM_TASKS; a capability filter may select what to run but must never define what is canonical. - Usage readings coerce non-numeric token fields and completed timestamps to absent: a JSON null `time.completed` previously became lastResponseTime: null -> "" and got the whole session_meta row rejected, cascading to defaults (lost cacheTtl/usage, scheduler always-execute). - The lane's tagger is hoisted out of the transform deps and cleaned on session.deleted (v1 parity), and a deleted-session tombstone stops an in-flight pass from re-registering state for a session deleted mid-pass. Also: the hidden-completion usage falls back to the local meter when a completed row carries a tokens object without numeric input/output; the e2e latestAssistant test shuffles its inserts so seq ordering is actually proven; new usage-reading cases pin the large->small refusal direction and the non-numeric/null guards. Verified: plugin suite 4961/0; e2e adapters-s2-contracts + store-reader 15/15; typecheck clean.
Round five found no blocking or major issues; one worth-fixing test gap and nits remained, closed here: - task-scheduler.test: a canonical task omitted from the caller's execution list (the v2 capability-filter case) keeps its schedule row and cursors — discriminating against the old caller-list pruning semantics. - deletedSessions is capped (oldest-first) and cleared on dispose. - usageMetaPatch re-checks the completed timestamp itself, so a future direct MeasuredUsage producer cannot reintroduce the ""-into-INTEGER cascade. - hidden-completion: a one-sided numeric usage row floors the missing side to 0 deliberately (documented), and a raw non-numeric tokens fixture pins the meter fallback. Verified: plugin suite 4963/0; e2e adapters-s2-contracts + store-reader 15/15; typecheck clean.
…d:false Review of the previous attempt established that materializeM0 never reads M0HardSignals.cacheExpired — only mustMaterialize does, and that decision only runs on the transform path with its own computed signals. This fold path renders fresh bytes unconditionally and uses only the system/model hashes for its markers, so computing the TTL signal here was a runtime no-op with a misleading comment. Keep the literal and document the reason. Verified: plugin suite 4963/0; typecheck clean.
|
Waiting for This PR needs a linked issue with the |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Approved issue: #<needs one — no current open issue covers this lane work; see "No matching issue" below>
Closes #488
Harness coverage (required)
read-session-db.ts) detects 1.18 stores and keeps the existing v1 path, pinned bystore-readertests./ctx-*commands,/ctx-dream, compaction-off mode).dreamer/task-scheduler.ts(canonical-prune fix; imported bypi-plugin/src/dreamer/index.ts) andrecomp-orchestrator.ts(additive executor seam; imported bypi-plugin/src/commands/ctx-session-upgrade.ts). Both are behavior-preserving/beneficial on Pi and pinned by the shared suite. Not applicable for the OpenCode-only surfaces (the v2 plugin lane, the OpenCode store probe, the OpenCode TUI).transformModewiring — andcrates/is untouched).Base:
v0.42.6(698f572) · 13 commits · 27 files, +1,798/−232Why
Magic Context ships an OpenCode 2 (v2) plugin lane, but on a real v2 install large parts of the product never engage: the
ctx_*agent tools are never registered, the sidebar/status have no data source,/ctx-*commands don't exist,/ctx-dreamcan't run, context percentages read0.0%, and projects with MC compaction disabled get nothing at all while the host compacts natively. These commits fix the v2 lane, verified live against OpenCode 2.What this changes
1. Register the
ctx_*tools on the v2 lane (8b4e91a) — the lane never registered the agent tool set. It now registers the same set as v1 through the codemode bridge, self-gating compaction-off.2. Start the RPC server the TUI reads (
753e7d8) — the v2 TUI is a pure RPC client, so without the plugin's server the sidebar/status render zeros. The lane now starts the same surface as v1, handed its draft-authoritative live maps, deferred one task so the server's filesystem writes stay outside the host's plugin-construction deadline.3. Register
/ctx-*from the app slot (d552baa) — the host's keymap provider isn't mounted during plugin setup; the lane now claims the always-mountedappslot and registers the command layer from its first render (the host's own bundledbtwplugin uses the same pattern), with a warn-once fallback.4. Compaction-off parity (
94d70a3) — the lane early-returned when MC compaction is disabled, giving such projects no tools, no RPC, no memory/docs injection. It now skips only the compaction machinery and keeps the tool registry, RPC surface and the shared transform. TUI mirrors v1's compaction-off rows.5. Repair the sidebar/status numbers (
7e2c045,d198f78) — usage was never persisted on v2 (0.0%forever); the harness-keyed model-limit cache was never warmed (everything fell back to 200k, disagreeing with the transform's math); model recovery read only the frozen v1 tables. Now persists the lane's own measurement (pre- and post-transform), warms fromcontext.model.list(), preferssession_messagewith a v1 fallback. Numbers match the host's own counter.6. Wire
/ctx-dream(0060ce7) — manual dream runs, capability-filtered (requiresToolstasks reported as unsupported rather than failed), executor seam threaded.7. Review-round hardening (
2c5c944,58fbe49,d4c903d,6fdab93,88fb463,d4d0e92) — five adversarial review rounds: fail-closed guards, a switch-turn admission fix, per-session usage isolation, numeric guards; the scheduled dream capability filter and a canonical-prune data-loss fix;session.deletedteardown with raced-pass tombstones; partial-token guards; re-anchored e2e static guards; a comment documenting the fold materializer's deliberatecacheExpired: false.Relationship to #477
Deliberately excluded — #477 is its own open PR. Note: on a store migrated from 1.18 the lane needs that guard to engage at all, so this branch alone no-ops there; native v2 stores work standalone. Happy to fold it in or reorder if preferred — and it rebases cleanly if #477 lands first.
Testing
adapters-s2-contracts10/10,store-reader5/5; the rest of that suite fails environmentally on this macOS box (base reproduces it)./ctx-status//ctx-recomppresent,/ctx-dreamrunning supported tasks, compaction-off behaving like v1.Known gaps (unchanged from base)
/ctx-recomp//ctx-session-upgradenotify path inert without a v2 client seam (the runner itself now gets the executor) · v2 tool context has nodirectory(project identity falls back to the launch dir) · orphansession_metarows on session deletion.Notes for reviewers
Commits are self-contained — cherry-pick freely. Happy to split if preferred.
Process note (CONTRIBUTING.md) — drop before submitting
CONTRIBUTING.md(onmaster) requires an approved issue before a PR can leave draft: open a bug/design issue, wait for a maintainer to applydesign-approved, and keep this PR in draft until then — the gate auto-converts unapproved ready-for-review PRs to draft and comments.Closes #<issue>goes in the body above once the issue exists. There is no docs/test bypass (only a maintainertriviallabel).I scanned all 19 open issues: none is an approved issue for this work. Nearest, for cross-linking rather than closing:
opencode— adjacent (same "v2 hosts don't behave" family) but a migration-labeling defect this branch does not touch.max_output_tokens) — a distinct provider-parameter defect; the lane fixes here do not change it.Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.Summary by cubic
Previously, the OpenCode 2 plugin lane was mostly inert on a real OpenCode 2 install. This branch brings it to parity with the v1 lane.
What changed
ctx_*tool set and starts the RPC server the v2 TUI reads; compaction-off projects keep tools, RPC, transform, and sidebar rows./ctx-status,/ctx-recomp, and/ctx-dreamfrom the always-mounted app slot and reports dream results as an RPC dialog.session_message.Side effects
Written for commit d4d0e92. Summary will update on new commits.