Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
0fdf460
fix: address review threads wave 4 — worker invocation, codex path re…
ScriptedAlchemy Sep 3, 2026
e39f3f1
fix(doctor): probe runtime endpoints concurrently; docs: scope the ca…
ScriptedAlchemy Sep 3, 2026
6b92178
chore: changeset revision text after rebase
ScriptedAlchemy Sep 3, 2026
2263156
fix(probe): never await a stalled close on the exhausted-budget path
ScriptedAlchemy Sep 3, 2026
5d51483
fix(dev): join detached probe plugin-data cleanup into Workbench shut…
ScriptedAlchemy Sep 3, 2026
5f3e885
fix(dev): fence in-flight probes in McpProbeService.settle()
ScriptedAlchemy Sep 3, 2026
2e125d6
fix(dev,create): guard probe teardown from synchronous close throws; …
ScriptedAlchemy Sep 3, 2026
c4814a1
fix(dev): mask URL userinfo in probe text before the URL path exemption
ScriptedAlchemy Sep 3, 2026
5b56341
fix(dev): retry capped probe plugin-data removal once the teardown se…
ScriptedAlchemy Sep 3, 2026
9c92c5e
fix(dev): keep the capped plugin-data retry outside the settle() fence
ScriptedAlchemy Sep 3, 2026
1af10c3
test(dev): make probe removal-failure and worker-marker tests platfor…
ScriptedAlchemy Sep 3, 2026
8d424a6
fix(dev): reuse the timeout-started transport close in the probe tear…
ScriptedAlchemy Sep 3, 2026
0660df0
fix(dev): mask URL userinfo through the final authority delimiter
ScriptedAlchemy Sep 3, 2026
f6b45d4
fix(dev): treat backslash as URL userinfo when masking probe text
ScriptedAlchemy Sep 3, 2026
9ff70e4
fix(dev): fail closed on local URI paths and retry early-failed plugi…
ScriptedAlchemy Sep 3, 2026
89588c1
fix(dev): let URL userinfo masking run through whitespace
ScriptedAlchemy Sep 3, 2026
5ebf740
test(adapters): keep the cursor adapter revision pin at 1.8.0 after t…
ScriptedAlchemy Sep 3, 2026
461e87c
chore(changeset): rewrite the wave-4 changeset as a user-facing relea…
ScriptedAlchemy Sep 3, 2026
f88e117
fix(workbench): redact URIs glued to a preceding identifier in probe …
ScriptedAlchemy Sep 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .changeset/review-threads-wave4.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
"agent-bundle": patch
"create-agent-bundle": patch
---

Harden the Codex plugin manifest, MCP probe reports, Doctor endpoint scans, CLI
help, and scaffolded README install instructions (#397).

- Reject line terminators, control characters, and backslash-form parent
segments in the pinned Codex `plugin.json` `screenshots` paths, matching the
component and interface-asset patterns; a manifest that relies on them now
fails `AB6012` (pinned-schema rejection) and `AB6032` (Codex host validation)
instead of validating. The Codex adapter is revision `1.9.0` and the composite
`plugin` adapter `1.24.0`.
- Admit any-JSON `tool_input` on `permission/request` event envelopes only for
the `codex` target, whose pinned schema declares it; `claude` envelopes keep
the documented object requirement.
- `agent-bundle build --help` and `agent-bundle prepack --help` now state the
`artifact` default for `--output` that those commands actually use.
- Workbench MCP probe reports keep `http(s)`/`ws(s)` documentation links while
masking URL userinfo (`scheme://user:secret@host`) through the final authority
delimiter, and fail closed on local-resource URIs such as `unix:///…` or
`vscode://file/…` and on every other `scheme://…/…` form. Plugin-data
directories are removed only after the transport teardown settles (bounded by
a 10 s cap, with one fenced retry when a still-exiting child held the
directory), a synchronously throwing `close()` no longer skips cleanup, a
timeout's transport close is reused rather than duplicated, and Workbench
shutdown (`server.close()`) joins in-flight probes and their detached
cleanups.
- `agent-bundle doctor` probes runtime socket and lock endpoints eight at a
time, so a directory of silent runtimes is bounded as a whole instead of
costing one timeout per endpoint.
- `create-agent-bundle` renders README install instructions for the selected
`--targets` (one `npx <bin> install <host>` line per installable host) instead
of a hard-coded `install claude`; portable-only scaffolds explain that no
installer bin is generated and name the `package.json` `bin` entry to restore
alongside the config target to get one.
2 changes: 1 addition & 1 deletion docs/canvases/agent-bundle-walkthrough.canvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -644,7 +644,7 @@ export default function AgentBundleWalkthrough() {
n={5}
title="Thin client prints the host-native response and exits 0"
channel="wrapper → Claude · stdout"
note="Claude blocks the Write and surfaces the reason to the model. On tool/before the wrapper always answers: an explicit hookSpecificOutput.permissionDecision ('allow' unless the route denied, optionally with updatedInput / additionalContext) — even when the route renders no decision. Silence is reserved for observation-only families such as session/end."
note="Claude blocks the Write and surfaces the reason to the model. On tool/before the wrapper always answers: an explicit hookSpecificOutput.permissionDecision ('allow' unless the route denied, optionally with updatedInput / additionalContext) — even when the route renders no decision. That explicit-allow rule is specific to Claude/Codex tool/before: other families, including decision-capable ones such as stop and prompt/submit, project undefined (silence) when the route neither denies nor adds context, and observation-only families such as session/end are always silent."
payload={WIRE_STDOUT}
last
/>
Expand Down
9 changes: 6 additions & 3 deletions docs/framework-mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,12 @@ export default defineConfig({
});
```

`output.distPath` defaults to `dist`. A CLI `--output <path>` overrides the
configured path, so precedence is CLI `--output`, then `output.distPath`, then
`dist`; existing projects are unchanged. The configured directory is excluded
`output.distPath` defaults to `dist` for the programmatic `build()` API and to
`artifact` for the `agent-bundle build` and `agent-bundle prepack` commands,
which also emit the npm package build into `dist/`. A CLI `--output <path>`
overrides the configured path, so precedence is CLI `--output`, then
`output.distPath`, then the operation default; existing projects are
unchanged. The configured directory is excluded
from project source snapshots (as `dist` always was), ignored by the dev
watcher, and used by Workbench host discovery and doctor drift checks.

Expand Down
8 changes: 7 additions & 1 deletion docs/local-ci.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,13 @@ still fails its own scan. Rstest re-hashes that leg directory, worker ID, and
invocation identity to `/tmp/ab-rstest-<hash16>` before exposing its worker
`TMPDIR`; this leaves headroom below Linux's 108-byte `sun_path` cap for nested
socket fixtures without sacrificing per-leg, per-worker, or concurrent-run
isolation. Legs live under
isolation. Because those hashed roots live beside the leg directory rather
than inside it, each one carries an owner marker (`.ab-rstest-owner.json`)
naming the leg `TMPDIR` and process it was derived from; the runner removes
the roots owned by a leg's `TMPDIR` — and only those, once their creating
process has exited — before the leg starts (leftovers of an interrupted run)
and after it finishes (`scripts/rstest-worker-roots.mjs`), so reruns cannot
accumulate worker caches or interrupted-test fixtures under `/tmp`. Legs live under
`.worktrees/local-ci/` (gitignored), are reused across runs for warm caches,
and can be recreated with `--fresh`.

Expand Down
2 changes: 1 addition & 1 deletion packages/agent-bundle/src/adapters/codex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ const hookContract = Object.freeze({
wrapperSource: (entry) => nativeHookWrapperSource(entry, 'Codex'),
} satisfies TargetHookContract);
const metadata = Object.freeze({
adapterRevision: '1.8.0',
adapterRevision: '1.9.0',
observedVersion: capabilityTable.observedCliVersion,
schemas: schemaDescriptorsFrom(schemaProvenance, schemaProvenance.observedCliVersion),
});
Expand Down
2 changes: 1 addition & 1 deletion packages/agent-bundle/src/adapters/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ const artifactValidation = deepFreeze({
});

const metadata = Object.freeze({
adapterRevision: '1.23.0',
adapterRevision: '1.24.0',
observedVersion: `${claudeAdapter.metadata.observedVersion}+${codexAdapter.metadata.observedVersion}+${cursorAdapter.metadata.observedVersion}`,
// Metadata schemas must exactly match the validation contract: each host's
// documents, with one shared Claude-format hook schema (the pinned Codex
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"Inline mcpServers values must be objects; inline hook documents use the same closed eleven-event, command-or-mcp_tool handler shape as hooks.schema.json.",
"The closed interface object admits every documented install-surface field; brandColor requires a six-digit hexadecimal value, external links require http(s), asset paths must stay inside the plugin root, and screenshots must be ./assets/-relative PNG paths.",
"The apps pointer is const-locked to ./.app.json, and app.schema.json requires a nonempty apps map whose entries carry exactly one nonempty registered-connection id.",
"Component and interface-asset path patterns treat backslashes as separators and reject them outright so Windows-form parent traversal cannot escape the plugin root, and they reject control characters and Unicode line terminators (U+0000-U+001F, U+007F, U+2028, U+2029) so a line break cannot hide a parent segment from the containment lookahead; HTTP(S) URL scheme patterns are case-insensitive to match WHATWG URL protocol normalization."
"Component and interface-asset path patterns treat backslashes as separators and reject them outright so Windows-form parent traversal cannot escape the plugin root, and they reject control characters and Unicode line terminators (U+0000-U+001F, U+007F, U+2028, U+2029) so a line break cannot hide a parent segment from the containment lookahead; HTTP(S) URL scheme patterns are case-insensitive to match WHATWG URL protocol normalization.",
"The screenshots item pattern applies the same containment lookahead and character exclusions as the other asset paths (backslashes rejected as separators, control characters and Unicode line terminators rejected) on top of its ./assets/ prefix and .png suffix, so a Windows-form or line-break-hidden parent segment cannot escape the assets directory."
],
"marketplace.schema.json": [
"Transcribed 2026-09-02 from the Marketplace metadata section of https://developers.openai.com/plugins/build/plugins: top-level name, interface.displayName, and plugins[] entries with name, source, policy, and category.",
Expand Down Expand Up @@ -52,8 +53,8 @@
"url": "https://github.com/openai/codex/blob/main/codex-rs/core/config.schema.json"
},
"plugin.schema.json": {
"bytes": 6598,
"sha256": "4ad476545c96c83d899c4524dcccd4eb4fe7d3299c307878a0cd46a237f48d58",
"bytes": 6651,
"sha256": "074c6c71966a3e6560ccbceb8d82ec6a40cb1eccee2f2d863fb4ef1e2276a814",
"url": "https://github.com/openai/codex/blob/main/codex-rs/skills/src/assets/samples/plugin-creator/references/plugin-json-spec.md"
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@
"logoDark": { "pattern": "^\\./(?!(?:.*[/\\\\])?\\.\\.(?:[/\\\\]|$))[^\\\\\\u0000-\\u001F\\u007F\\u2028\\u2029]+$", "type": "string" },
"longDescription": { "minLength": 1, "pattern": "\\S", "type": "string" },
"privacyPolicyURL": { "pattern": "^[Hh][Tt][Tt][Pp][Ss]?://", "type": "string" },
"screenshots": { "items": { "pattern": "^\\./assets/(?!.*(?:^|/)\\.\\.(?:/|$)).+\\.png$", "type": "string" }, "type": "array" },
"screenshots": { "items": { "pattern": "^\\./assets/(?!(?:.*[/\\\\])?\\.\\.(?:[/\\\\]|$))[^\\\\\\u0000-\\u001F\\u007F\\u2028\\u2029]+\\.png$", "type": "string" }, "type": "array" },
"shortDescription": { "minLength": 1, "pattern": "\\S", "type": "string" },
"termsOfServiceURL": { "pattern": "^[Hh][Tt][Tt][Pp][Ss]?://", "type": "string" },
"websiteURL": { "pattern": "^[Hh][Tt][Tt][Pp][Ss]?://", "type": "string" }
Expand Down
4 changes: 2 additions & 2 deletions packages/agent-bundle/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@ export const runCli = async (

const buildCommand = configureSourceOptions(
program.command('build').description('Build a validated Agent Bundle artifact'),
).option('--output <path>', 'Artifact output path relative to --root (overrides config output.distPath; default dist)');
).option('--output <path>', 'Artifact output path relative to --root (overrides config output.distPath; default artifact, since dist is the npm package build output)');
buildCommand.action(async (options: BuildCommandOptions) => {
const { build } = await import('./api.ts');
const result = await build({ ...projectOptions(options), output: options.output, packageOutputs: true });
Expand All @@ -554,7 +554,7 @@ export const runCli = async (

const prepackCommand = configureSourceOptions(
program.command('prepack').description('Build and validate the npm pack inventory'),
).option('--output <path>', 'Artifact output path relative to --root (overrides config output.distPath; default dist)');
).option('--output <path>', 'Artifact output path relative to --root (overrides config output.distPath; default artifact, since dist is the npm package build output)');
prepackCommand.action(async (options: BuildCommandOptions) => {
const { prepack } = await import('./api.ts');
const result = await (dependencies.prepack ?? prepack)({
Expand Down
Loading
Loading