🐛 Prepare the plan profile's adapter through the host, not the document - #674
Merged
Conversation
Materializing an embedded adapter runs `npm install`, and the authorship profile refuses a command to everything inside it — so the first fix turned #672's two backend failures into `xmd plan asked for a command, which the authorship profile grants to nothing`, before any turn. Preparation now runs in the scope `xmd plan` was called in, captured before the profile installs its refusals. The distinction is the point: the document decides what to write and may run nothing, while the host installs the adapter it was always going to launch. The wait is registered with a halt, so an ended command takes an unfinished install with it rather than leaving one running under a conversation that is over. AE6 drives the real command through adapters that install by running one, and reproduces that exact refusal without this change. Claude-Session: https://claude.ai/code/session_01TNJwcFmnt3kYSn9gGsx9u7
A patch bump: v0.10.1 shipped the adapter overlay without the preparation scope, so `xmd plan` ends with `asked for a command, which the authorship profile grants to nothing` before any turn. This release carries that correction. Stamps every publishable manifest and restamps the matching workspace entries in bun.lock, which the bump task deliberately leaves alone. Only the ten @executablemd members move. Claude-Session: https://claude.ai/code/session_01TNJwcFmnt3kYSn9gGsx9u7
PR #674: 🐛 Prepare the plan profile's adapter through the host, not the document25 files, +165 / -37 Scope🟡 25 files changed. Are all changes related? 🟡 Changes span 13 directories. 🟡 PR mixes config and source changes. 🟡 package.json changed without dependency justification. Structural✅ No structural bloat detected. Slop✅ Slop indicators look low. Static AnalysisOxlint: 2 diagnostics across 1 file (2 rules) consistent-function-scoping (1): packages/cli/src/authorship-profile.ts CorrectnessNo extraneous code patterns detected. |
taras
enabled auto-merge (squash)
August 31, 2026 02:58
3 tasks
taras
added a commit
that referenced
this pull request
Aug 31, 2026
`xmd plan` printed its review URL and then said it could not open it:
could not open a browser automatically (xmd plan asked for a command,
which the authorship profile grants to nothing). Open the URL above to
continue.
Opening a form runs `open`, `xdg-open` or `start`, and the profile refuses a
command to everything inside it — the same shape as the adapter install fixed in
#674. Showing a person the review is the host's act: the host's provider asking
the host's question about a URL the host is serving, decided by no document,
agent or authored element.
`FormOpener` is where that act already had a seam, so the profile composes around
it and runs the open in the scope the command was called in. Nothing else moves:
a file, a command, the network and a service stay refused, and a failed open is
still a warning printed beside a URL that stands on its own.
Claude-Session: https://claude.ai/code/session_01TNJwcFmnt3kYSn9gGsx9u7
taras
added a commit
that referenced
this pull request
Aug 31, 2026
* 🐛 Type the review schema's conditional branches so the form can compile
`xmd plan` completed its turn and then ended at the review with
<WebForm> schema could not be compiled for the browser: strict mode:
missing type "object" for keyword "required" (strictTypes)
Compiling a schema for the browser takes it apart: RJSF's parser extracts `if`
and `then` and compiles each as a schema of its own. The review schema's two
branches reached `required` through the parent's `type`, so by the time they were
compiled they were typeless schemas, which strict mode refuses. The server,
compiling the whole schema in one piece, accepts them — the one shape the two
sides read differently.
Both branches now declare `type: "object"`. The web suite pins the rule in both
directions, the packaged-document suite pins that this document follows it, and
the spec says so where the preflight boundary is described.
Claude-Session: https://claude.ai/code/session_01TNJwcFmnt3kYSn9gGsx9u7
* 🐛 Open the plan review form as the host, not as the document
`xmd plan` printed its review URL and then said it could not open it:
could not open a browser automatically (xmd plan asked for a command,
which the authorship profile grants to nothing). Open the URL above to
continue.
Opening a form runs `open`, `xdg-open` or `start`, and the profile refuses a
command to everything inside it — the same shape as the adapter install fixed in
#674. Showing a person the review is the host's act: the host's provider asking
the host's question about a URL the host is serving, decided by no document,
agent or authored element.
`FormOpener` is where that act already had a seam, so the profile composes around
it and runs the open in the scope the command was called in. Nothing else moves:
a file, a command, the network and a service stay refused, and a failed open is
still a warning printed beside a URL that stands on its own.
Claude-Session: https://claude.ai/code/session_01TNJwcFmnt3kYSn9gGsx9u7
* ♻️ State the host's own acts once, and put the ceiling on the document alone
The two fixes for `xmd plan`'s capability refusals were written at their call
sites, one per act. This says the rule once instead.
`src/host-acts.ts` owns it: a profile refuses its document ambiently, a call
carries no mark saying who made it, and a provider's work happens inside the
document execution that reached it — core constructs the root provider from
`Execution.around({ document })` — so the scope has to come from the command,
which is the one party that exists before the ceiling does. A host takes its
scope and states which acts are its own; everything else stays refused.
The adapter install is now stated where a host states its dependencies,
`hostAcpDependencies(stack, host)`, so `xmd run` and `xmd plan` say the same
thing and the plan profile stops special-casing it. `xmd run` has no ceiling, so
there it changes nothing.
`refuseDocumentCapabilities()` moves onto a scope holding the document execution
and nothing else. No behavior depends on it — the acts that needed hoisting are
inside that execution either way — but the ceiling now covers what it claims to.
AE6 and PH1 are unchanged and still pass: they assert the outcome, so they hold
the refactor to the behavior the two fixes established.
Claude-Session: https://claude.ai/code/session_01TNJwcFmnt3kYSn9gGsx9u7
* ♻️ Keep the host-act helpers beside the profile that uses them
The previous commit mostly moved code. `inScope` and the form-opener middleware
changed file; the adapter binding moved into `hostAcpDependencies`, which then
needed a `Scope` every caller but one passes for nothing; and `hostScope()` was a
function whose body was `useScope()`. A module for two helpers with a single
caller is premature, and a shared signature is the wrong place for a concern only
the ceiling has.
Both helpers go back beside their caller, carrying the prose that split was
written to hold: why an ambient refusal cannot tell the parties apart, and why
the wait is bound to a halt rather than decoupled the way `scope-eval` decouples.
What that commit actually changed stays: the ceiling is installed on a scope
holding the document execution and nothing else, and the spec says what the
refusal can and cannot distinguish.
Claude-Session: https://claude.ai/code/session_01TNJwcFmnt3kYSn9gGsx9u7
---------
Co-authored-by: Taras Mankovski <74687+taras@users.noreply.github.com>
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.
Why
v0.10.1 shipped #673's adapter overlay, and
xmd plannow fails earlier than itdid before:
Materializing an embedded adapter runs
npm install. The authorship profilerefuses a command to everything inside it, and #673 put the preparation inside
that scope, so the refusal fires at the provider's availability probe — before
any turn, for both providers.
What changes
Before: the first
xmd planof any invocation that resolves Codex or Claude endswith the profile's command refusal.
After: preparation runs in the scope
xmd planwas called in — captured beforethe profile installs its refusals — so the host installs the adapter it is about
to launch, and the document still cannot run anything.
How it works
inScoperegisters the halt before the wait, so an ended or cancelled commandtakes an unfinished install with it rather than leaving one running in a scope
that outlives the conversation.
The distinction the profile is making is the point: the document decides what to
write and may run nothing; the host installs the adapter it was always going to
launch.
What must stay true
network capability — unchanged, and only host preparation escapes it.
ensure-registeredhalt in
inScope.How to verify it
AE6drives the realrunPlanwith adapters that install by running a command,and asserts the command was reached and the invocation completed. Reverting the
inScopewiring makes it fail with the exact refusal above, which is how it waschecked.
at: "min"deliberately: at the defaultstrength it would answer for the refused call too and pass against the defect.
Focused evidence at
1b6ab7cb:ok | 4 passed (41 steps) | 0 failed, plusdeno task checkanddeno task lint.Separately, both embedded snapshots were materialized for real on this machine
from the merged code —
npm installof each vendored tarball, entry file presentand verified — which is the step the refusal was blocking.
Scope
Included
acp-client-spec.mdandplan-command-spec.mdthat state which act the refusal covers.broken half.
Intentionally unchanged
xmd runpath —xmd rungrantsthe document a command, so nothing there was refused.
Scope confirmation
Follow-up to #673, which fixed #672.
https://claude.ai/code/session_01TNJwcFmnt3kYSn9gGsx9u7