fix(#1151): drive codex sandbox_mode emission from runtime descriptor sandboxTier axis (#1152)

* fix(#1151): drive codex sandbox_mode emission from runtime descriptor sandboxTier axis

The sandboxTier runtime-capability axis was cosmetic: declared and validated
on all 16 descriptors but read by nothing. The codex per-agent sandbox_mode
line was emitted unconditionally from the hardcoded CODEX_AGENT_SANDBOX map, so
the descriptor field drove no behaviour (ADR-857 audit finding F10; the
"rides along in 5e/5g" promise in ADR-1016 §8 never landed).

Make the axis load-bearing:
- resolveInstallPlan projects sandboxTier as a 7th InstallPlan axis and fails
  loud (throws) on a missing/invalid value rather than coercing to 'none'.
- installCodexConfig / generateCodexAgentToml gate sandbox_mode emission on
  sandboxTier !== 'none'.
- The per-agent CODEX_AGENT_SANDBOX map is kept: it is GSD agent policy, not a
  runtime-descriptor property (different layer). Full removal of that map is
  tracked under #1138 (phase-6 descriptor-residue removal).

For codex (sandboxTier === 'codex-agent-sandbox') the emitted TOML is
byte-identical to before; for 'none' runtimes sandbox_mode is omitted.
Adds leaf, projection, and installCodexConfig threading-seam regression tests;
updates the enh-1082 InstallPlan golden master with sandboxTier for all 16
runtimes. Confirmed hypothesis: schema-first vocabulary closure outran consumer
wiring, with no conformance gate to catch the orphaned axis.

Closes #1151
Refs #857, #1138

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(#1151): stamp changeset with PR number 1152

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-06-13 00:06:52 -04:00
committed by GitHub
parent 607813f5d0
commit 0c0a8966ba
7 changed files with 145 additions and 7 deletions

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 1152
---
**`sandbox_mode` emission in Codex TOML is now gated on the runtime descriptor's `sandboxTier` axis** — previously `installCodexConfig` emitted `sandbox_mode` unconditionally from a hardcoded policy map regardless of whether the runtime descriptor declared a sandbox tier, making the descriptor field cosmetic. `resolveInstallPlan` now projects `sandboxTier` from the capability registry, and `generateCodexAgentToml` / `installCodexConfig` gate emission on `sandboxTier !== 'none'`. The per-agent mode table `CODEX_AGENT_SANDBOX` remains GSD agent policy (not a runtime-descriptor property). For codex (`sandboxTier === 'codex-agent-sandbox'`) output is byte-identical to before; for all other runtimes (`sandboxTier === 'none'`) `sandbox_mode` is correctly omitted. `resolveInstallPlan` now fails loud (throws `TypeError`) on a missing or invalid `sandboxTier` descriptor axis rather than silently coercing garbage to `'none'`, preventing a corrupt/stale registry from silently dropping sandbox enforcement. Full removal of the per-agent registration-tax map remains tracked under #1138. (#1151)

View File

@@ -179,7 +179,7 @@ A Capability whose integration shape brings its own external process, service, o
Standalone hook-surface writer module extracted from `bin/install.js` as ADR-857 phase 5f-1 (behavior-preserving relocation, no logic change). Owns: Cline rules-body/agents-md/pre-tool-use hook generation (`buildClineRulesBody`, `buildClineAgentsMdBody`, `buildClinePreToolUseHook`, `mergeGsdAgentsMd`, `writeClineArtifacts`); Cursor `hooks.json` lifecycle (`buildCursorHookEntry`, `isManagedCursorHookEntry`, `reconcileCursorHooksJson`, `writeCursorHooksJson`, `removeCursorHooksJson`); Copilot session-hook config (`buildCopilotHookConfig`, `writeCopilotHookConfig`); Codex hook-block and event management (`buildCodexHookBlock`, `rewriteLegacyCodexHookBlock`, `reconcileCodexHooksJsonEvent`, `reconcileCodexHooksJsonSessionStart`, `ensureCodexHooksJsonSessionStart`, `ensureCodexHooksJsonEvent`, `removeCodexHooksJsonEvent`, `removeCodexHooksJsonSessionStart`, `buildCodexHookWindowsShimIR`); and shared hook command helpers (`buildHookCommand`, `rewriteLegacyManagedNodeHookCommands`, `normalizeNodePath`, `resolveNodeRunner`). `bin/install.js` delegates to this module via thin wrappers and re-exports its functions unchanged so existing tests require no modification. Source: `src/runtime-hooks-surface.cts`. Built output: `gsd-core/bin/lib/runtime-hooks-surface.cjs`.
### Runtime Config Adapter Registry
Module owning the explicit per-runtime config-mutation dispatch table for the installer. `resolveRuntimeConfigIntent(runtime)` projects a typed config intent — `installSurface` (`settings-json` | `codex-toml` | `copilot-instructions` | `cline-rules` | `cursor-hooks-json` | `profile-marker-only`), `writesSharedSettings` (the `finishInstall` shared-settings write gate), and `finishPermissionWriter` (`opencode` | `kilo` | none) — that `bin/install.js` dispatches on instead of inline `runtime === '...'` branching. Owns adapter selection only: it performs no filesystem IO and does not execute config mutations (the install/finishInstall handlers and the per-runtime writers do that). Unknown runtimes fail loudly with a `TypeError`, guarded by an `Object.hasOwn` own-property check so prototype-chain keys (`__proto__`, `constructor`) also throw. Also exports `resolveInstallPlan(runtime)` — the ADR-58 `InstallPlan` capstone — which collects the install-level descriptor axes (`installSurface`, `writesSharedSettings`, `finishPermissionWriter`, `hookEvents`, `extendedHookEvents`, `hooksSurface`) into one typed `InstallPlan` value consumed by `install()` and `finishInstall()` in `bin/install.js`. The spatial axes (`configHome`, `artifactLayout`, `commandStyle`) remain behind their self-resolving adapter modules and are not part of the plan; they are the execution adapters. Realizes both the adapter-selection and plan-collection halves of the Runtime Install Policy Module boundary. Source: `gsd-core/bin/lib/runtime-config-adapter-registry.cjs`. See ADR-58, #60.
Module owning the explicit per-runtime config-mutation dispatch table for the installer. `resolveRuntimeConfigIntent(runtime)` projects a typed config intent — `installSurface` (`settings-json` | `codex-toml` | `copilot-instructions` | `cline-rules` | `cursor-hooks-json` | `profile-marker-only`), `writesSharedSettings` (the `finishInstall` shared-settings write gate), and `finishPermissionWriter` (`opencode` | `kilo` | none) — that `bin/install.js` dispatches on instead of inline `runtime === '...'` branching. Owns adapter selection only: it performs no filesystem IO and does not execute config mutations (the install/finishInstall handlers and the per-runtime writers do that). Unknown runtimes fail loudly with a `TypeError`, guarded by an `Object.hasOwn` own-property check so prototype-chain keys (`__proto__`, `constructor`) also throw. Also exports `resolveInstallPlan(runtime)` — the ADR-58 `InstallPlan` capstone — which collects the install-level descriptor axes (`installSurface`, `writesSharedSettings`, `finishPermissionWriter`, `hookEvents`, `extendedHookEvents`, `hooksSurface`, `sandboxTier`) into one typed `InstallPlan` value consumed by `install()` and `finishInstall()` in `bin/install.js`. `sandboxTier` (`none` | `codex-agent-sandbox`) gates per-agent `sandbox_mode` emission in the codex TOML path and fails loud on a missing/invalid value (#1151). The spatial axes (`configHome`, `artifactLayout`, `commandStyle`) remain behind their self-resolving adapter modules and are not part of the plan; they are the execution adapters. Realizes both the adapter-selection and plan-collection halves of the Runtime Install Policy Module boundary. Source: `gsd-core/bin/lib/runtime-config-adapter-registry.cjs`. See ADR-58, #60.
### Claude Code Plugin Manifest Module
Module owning the projection of gsd-core's artifact surfaces (`commands`, `agents`, hooks) onto the Claude Code plugin contract (`.claude-plugin/plugin.json` + `hooks/hooks.json`) — the plugin-contract sibling of the Runtime Artifact Layout Module (which projects the same surfaces onto filesystem placements). Defined mapping: `name`=`binName` (drives the `/gsd-core:` command namespace), `repository`/`homepage`=`repoUrl` (Package Identity Module), `version`/`description`/`license` from `package.json` (`version` is required for `claude plugin validate --strict`), `commands`=`./commands/gsd/`, agents via Claude Code's default `agents/` discovery (the explicit string form is schema-rejected), `hooks`=`./hooks/hooks.json`. The hook projection carries ONLY the always-on subset of the Installer Module's Claude `settings.json` wiring (check-update, context-monitor, prompt-guard, read-guard, worktree-path-guard, read-injection-scanner) via `${CLAUDE_PLUGIN_ROOT}`; config-gated opt-in hooks are excluded because a static manifest cannot honor per-project config gates, and plugin-shipped agents cannot carry hook frontmatter (so all plugin-path hook wiring lives in hooks.json). `hooks.json` covers all seven Claude Code lifecycle events: SessionStart, PreToolUse, PostToolUse, SubagentStop, Stop, PreCompact (all wired to context-monitor for context-headroom awareness), and FileChanged (matcher: `config.json` → config-reload, injects `additionalContext` when `.planning/config.json` changes mid-session). Additive — the file-copy path (Runtime Artifact Layout / Install Policy / Installer Modules) is unchanged. Conformance is validated by `claude plugin validate --strict` plus the in-repo drift-guard `tests/issue-766-plugin-manifest.test.cjs`. _Avoid_: "the plugin API", "the plugin file" (when you mean the seam). See ADR-766 and Runtime Artifact Layout Module.

View File

@@ -3166,7 +3166,7 @@ purpose: ${toSingleLine(description)}
* @param {object|null} runtimeResolver — runtime-aware tier resolver from readGsdRuntimeProfileResolver
* @param {object|null} effortCfg — #443: merged effort config from readGsdEffectiveEffortConfig
*/
function generateCodexAgentToml(agentName, agentContent, modelOverrides = null, runtimeResolver = null, effortCfg = null) {
function generateCodexAgentToml(agentName, agentContent, modelOverrides = null, runtimeResolver = null, effortCfg = null, sandboxTier = 'codex-agent-sandbox') {
const sandboxMode = CODEX_AGENT_SANDBOX[agentName] || 'read-only';
const { frontmatter, body } = extractFrontmatterAndBody(agentContent);
const frontmatterText = frontmatter || '';
@@ -3179,8 +3179,10 @@ function generateCodexAgentToml(agentName, agentContent, modelOverrides = null,
const lines = [
`name = ${JSON.stringify(resolvedName)}`,
`description = ${JSON.stringify(resolvedDescription)}`,
`sandbox_mode = "${sandboxMode}"`,
];
if (sandboxTier != null && sandboxTier !== 'none') {
lines.push(`sandbox_mode = "${sandboxMode}"`);
}
// Embed model override when configured in ~/.gsd/defaults.json so that
// model_overrides is respected on Codex (which uses static TOML, not inline
@@ -5745,7 +5747,7 @@ function writeCopilotHookConfig(targetDir) {
* Generate config.toml and per-agent .toml files for Codex.
* Reads agent .md files from source, extracts metadata, writes .toml configs.
*/
function installCodexConfig(targetDir, agentsSrc) {
function installCodexConfig(targetDir, agentsSrc, sandboxTier = 'codex-agent-sandbox') {
const configPath = path.join(targetDir, 'config.toml');
const agentsTomlDir = path.join(targetDir, 'agents');
fs.mkdirSync(agentsTomlDir, { recursive: true });
@@ -5791,7 +5793,7 @@ function installCodexConfig(targetDir, agentsSrc) {
// #443 — pass unified effort config so model_reasoning_effort in the .toml
// follows the same config-driven precedence as the Claude .md effort key.
const effortCfg = readGsdEffectiveEffortConfig(targetDir);
const tomlContent = generateCodexAgentToml(name, content, modelOverrides, runtimeResolver, effortCfg);
const tomlContent = generateCodexAgentToml(name, content, modelOverrides, runtimeResolver, effortCfg, sandboxTier);
fs.writeFileSync(path.join(agentsTomlDir, `${name}.toml`), tomlContent);
}
@@ -10717,7 +10719,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
if (!isMinimalMode(_effectiveInstallMode)) {
try {
// Generate Codex config.toml and per-agent .toml files.
agentCount = installCodexConfig(targetDir, agentsSrc);
agentCount = installCodexConfig(targetDir, agentsSrc, plan.sandboxTier);
} catch (e) {
restoreCodexSnapshot();
throw e;

View File

@@ -150,7 +150,7 @@ The migration is staged the way phases 3–4 were (registry-only → consume inc
6. **5f — extract `hooksSurface` into its own module, then drive it** ← descriptor. `hooks-surface` is the one axis still scattered across `install.js`; its module extraction is a prerequisite, exactly as ADR-3660 was for `artifactLayout`. **5f-completion (done):** `installSurface` drives the config-writing adapter in `runtime-config-adapter-registry` (#1055, retiring the hand-kept `REGISTRY` table); `extendedHookEvents` drives per-event hook registration in `applySettingsJsonHooks` (#1076, retiring the three hardcoded per-event guards); `hooksSurface === 'none'` is now load-bearing for the hook-skip path (replacing `isOpencode/isKilo` flags). `writesSharedSettings` and `permissionWriter` complete the adapter-selection and permissions-writer parameterisation (all four axes added to `validateRuntimeBody`).
7. **5g — materialize the `InstallPlan`** (ADR-58) — **DONE**: the install-level descriptor axes (`installSurface`, `writesSharedSettings`, `finishPermissionWriter`, `hookEvents`, `extendedHookEvents`, `hooksSurface`) are collected into one typed `InstallPlan` value by the exported `resolveInstallPlan(runtime)` in `runtime-config-adapter-registry`. `install()` and `finishInstall()` in `bin/install.js` now route through it. The spatial axes (`configHome`, `artifactLayout`, `commandStyle`) remain behind their self-resolving adapter modules (`runtime-homes` / `runtime-artifact-layout` / `runtime-slash`) as the execution adapters — consistent with the "adapters execute" pattern. Phase 5 is fully materialized.
Each rung is its own approved-enhancement + PR + equivalence proof (`sandbox-tier`, codex-only, is tiny and rides along in 5e/5g). **Irreducible first-party code, named not hidden:** the artifact converter *functions* remain first-party code, selected by the descriptor's closed `ConverterName` — the descriptor never embeds them.
Each rung is its own approved-enhancement + PR + equivalence proof. (`sandbox-tier`, codex-only, was scoped to ride along in 5e/5g, but neither rung delivered the drive — the InstallPlan capstone shipped with six axes and `sandboxTier` was not among them. It is now wired directly: `resolveInstallPlan` projects `sandboxTier`, and `installCodexConfig` gates per-agent `sandbox_mode` emission on `sandboxTier !== 'none'`. The per-agent mode table `CODEX_AGENT_SANDBOX` remains GSD agent policy — not a runtime-descriptor property — so it is intentionally NOT retired here; full removal of that registration-tax map is tracked under #1138 (phase-6 descriptor-residue removal). The gate is behaviourally a no-op while codex is the only runtime with a non-`none` tier.) **Irreducible first-party code, named not hidden:** the artifact converter *functions* remain first-party code, selected by the descriptor's closed `ConverterName` — the descriptor never embeds them.
### 9. Two axes: the descriptor dogfoods the *runtime* interface only

View File

@@ -30,6 +30,9 @@
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { runtimes } = require('./capability-registry.cjs') as { runtimes: Record<string, { runtime: Record<string, unknown> | undefined }> };
/** Valid sandboxTier enum values — mirrors the gen-capability-registry validator vocabulary. */
const VALID_SANDBOX_TIERS = new Set(['none', 'codex-agent-sandbox']);
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
@@ -71,6 +74,8 @@ interface InstallPlan extends RuntimeConfigIntent {
extendedHookEvents: string[];
/** Which surface owns the hook registration for this runtime. */
hooksSurface: HooksSurface;
/** Runtime sandbox tier ('none' | 'codex-agent-sandbox'); gates per-agent sandbox_mode emission. */
sandboxTier: string;
}
// ---------------------------------------------------------------------------
@@ -130,6 +135,10 @@ function resolveInstallPlan(runtime: string): InstallPlan {
const desc = runtimes[runtime]?.runtime;
if (!desc) throw new TypeError(`Unknown runtime for install plan: ${runtime}`);
const configIntent = resolveRuntimeConfigIntent(runtime);
const sandboxTier = desc['sandboxTier'];
if (typeof sandboxTier !== 'string' || !VALID_SANDBOX_TIERS.has(sandboxTier)) {
throw new TypeError(`Runtime '${runtime}' has a missing or invalid sandboxTier descriptor axis: ${JSON.stringify(sandboxTier)}`);
}
return {
runtime,
installSurface: configIntent.installSurface,
@@ -140,6 +149,7 @@ function resolveInstallPlan(runtime: string): InstallPlan {
hooksSurface: desc['hooksSurface'] != null
? desc['hooksSurface'] as HooksSurface
: ((runtime === 'opencode' || runtime === 'kilo') ? 'none' : 'settings-json'),
sandboxTier,
};
}

View File

@@ -58,6 +58,8 @@ const {
resolveNodeRunner,
} = require('../bin/install.js');
const { resolveInstallPlan } = require('../gsd-core/bin/lib/runtime-config-adapter-registry.cjs');
function runCodexInstall(codexHome, cwd = path.join(__dirname, '..')) {
const previousCodeHome = process.env.CODEX_HOME;
const previousCwd = process.cwd();
@@ -579,6 +581,109 @@ description: Maps the codebase
});
});
// ─── sandboxTier gate on generateCodexAgentToml ────────────────────────────────
describe('generateCodexAgentToml sandboxTier gate', () => {
const sampleAgent = `---
name: gsd-executor
description: Executes plans
tools: Read, Write, Edit
color: yellow
---
<role>You are an executor.</role>`;
test('sandboxTier=none: does NOT emit sandbox_mode', () => {
const result = generateCodexAgentToml('gsd-executor', sampleAgent, null, null, null, 'none');
assert.ok(!result.includes('sandbox_mode'), 'sandbox_mode must be absent when sandboxTier is none');
});
test('sandboxTier=codex-agent-sandbox: emits sandbox_mode = "workspace-write"', () => {
const result = generateCodexAgentToml('gsd-executor', sampleAgent, null, null, null, 'codex-agent-sandbox');
assert.ok(result.includes('sandbox_mode = "workspace-write"'), 'must emit workspace-write for codex-agent-sandbox tier');
});
test('default (no sandboxTier arg): still emits sandbox_mode = "workspace-write" (no-op for codex)', () => {
const result = generateCodexAgentToml('gsd-executor', sampleAgent);
assert.ok(result.includes('sandbox_mode = "workspace-write"'), 'default preserves codex behavior');
});
test('resolveInstallPlan projection: codex.sandboxTier === "codex-agent-sandbox"', () => {
const plan = resolveInstallPlan('codex');
assert.strictEqual(plan.sandboxTier, 'codex-agent-sandbox', 'codex must project sandboxTier=codex-agent-sandbox');
});
test('resolveInstallPlan projection: claude.sandboxTier === "none"', () => {
const plan = resolveInstallPlan('claude');
assert.strictEqual(plan.sandboxTier, 'none', 'claude must project sandboxTier=none');
});
});
// ─── installCodexConfig threading-seam: sandboxTier → per-agent TOML ─────────
describe('installCodexConfig sandboxTier threading seam', () => {
const { installCodexConfig } = require('../bin/install.js');
let tmpDir;
let agentsSrc;
let targetDir;
beforeEach(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-sandboxtier-seam-'));
agentsSrc = path.join(tmpDir, 'agents');
targetDir = path.join(tmpDir, 'codex');
fs.mkdirSync(agentsSrc, { recursive: true });
fs.mkdirSync(targetDir, { recursive: true });
// Write a minimal gsd-executor agent fixture
fs.writeFileSync(path.join(agentsSrc, 'gsd-executor.md'), [
'---',
'name: gsd-executor',
'description: Executes plans',
'tools: Read, Write, Edit',
'---',
'',
'<role>You are an executor.</role>',
].join('\n'));
});
afterEach(() => {
cleanup(tmpDir);
});
test('sandboxTier=none: written per-agent .toml does NOT contain sandbox_mode', () => {
installCodexConfig(targetDir, agentsSrc, 'none');
const tomlPath = path.join(targetDir, 'agents', 'gsd-executor.toml');
assert.ok(fs.existsSync(tomlPath), 'per-agent TOML must be written');
const toml = fs.readFileSync(tomlPath, 'utf8');
assert.ok(!toml.includes('sandbox_mode'), 'sandbox_mode must be absent when sandboxTier=none');
});
test('sandboxTier=codex-agent-sandbox: written per-agent .toml contains sandbox_mode', () => {
installCodexConfig(targetDir, agentsSrc, 'codex-agent-sandbox');
const tomlPath = path.join(targetDir, 'agents', 'gsd-executor.toml');
assert.ok(fs.existsSync(tomlPath), 'per-agent TOML must be written');
const toml = fs.readFileSync(tomlPath, 'utf8');
assert.ok(toml.includes('sandbox_mode'), 'sandbox_mode must be present when sandboxTier=codex-agent-sandbox');
});
test('default 2-arg form (no sandboxTier): written per-agent .toml contains sandbox_mode (codex default)', () => {
installCodexConfig(targetDir, agentsSrc);
const tomlPath = path.join(targetDir, 'agents', 'gsd-executor.toml');
assert.ok(fs.existsSync(tomlPath), 'per-agent TOML must be written');
const toml = fs.readFileSync(tomlPath, 'utf8');
assert.ok(toml.includes('sandbox_mode'), 'sandbox_mode must be present in default 2-arg form (codex-agent-sandbox default)');
});
});
// NOTE: A test for the new fail-loud throw on missing/invalid sandboxTier in
// resolveInstallPlan is omitted here. Constructing a descriptor without the
// field would require mocking the capability-registry module which is a
// singleton require(); patching it invasively would corrupt other tests in the
// same process. The throw path is verified at the type level (tsc) and by the
// build passing, and the happy-path coverage (claude.sandboxTier === 'none' and
// codex.sandboxTier === 'codex-agent-sandbox') confirms the real registry has
// valid values for all 16 runtimes.
// ─── CODEX_AGENT_SANDBOX mapping ────────────────────────────────────────────────
describe('CODEX_AGENT_SANDBOX', () => {

View File

@@ -26,6 +26,7 @@ const EXPECTED = {
hookEvents: 'claude',
extendedHookEvents: ['SubagentStop', 'Stop', 'PreCompact', 'FileChanged'],
hooksSurface: 'settings-json',
sandboxTier: 'none',
},
codex: {
runtime: 'codex',
@@ -35,6 +36,7 @@ const EXPECTED = {
hookEvents: 'claude',
extendedHookEvents: [],
hooksSurface: 'codex-hooks-json',
sandboxTier: 'codex-agent-sandbox',
},
antigravity: {
runtime: 'antigravity',
@@ -44,6 +46,7 @@ const EXPECTED = {
hookEvents: 'gemini',
extendedHookEvents: [],
hooksSurface: 'settings-json',
sandboxTier: 'none',
},
gemini: {
runtime: 'gemini',
@@ -53,6 +56,7 @@ const EXPECTED = {
hookEvents: 'gemini',
extendedHookEvents: ['BeforeAgent', 'AfterAgent', 'BeforeModel'],
hooksSurface: 'settings-json',
sandboxTier: 'none',
},
cursor: {
runtime: 'cursor',
@@ -62,6 +66,7 @@ const EXPECTED = {
hookEvents: 'claude',
extendedHookEvents: [],
hooksSurface: 'cursor-hooks-json',
sandboxTier: 'none',
},
opencode: {
runtime: 'opencode',
@@ -71,6 +76,7 @@ const EXPECTED = {
hookEvents: undefined,
extendedHookEvents: [],
hooksSurface: 'none',
sandboxTier: 'none',
},
kilo: {
runtime: 'kilo',
@@ -80,6 +86,7 @@ const EXPECTED = {
hookEvents: undefined,
extendedHookEvents: [],
hooksSurface: 'none',
sandboxTier: 'none',
},
copilot: {
runtime: 'copilot',
@@ -89,6 +96,7 @@ const EXPECTED = {
hookEvents: undefined,
extendedHookEvents: [],
hooksSurface: 'copilot-inline',
sandboxTier: 'none',
},
augment: {
runtime: 'augment',
@@ -98,6 +106,7 @@ const EXPECTED = {
hookEvents: 'claude',
extendedHookEvents: [],
hooksSurface: 'settings-json',
sandboxTier: 'none',
},
trae: {
runtime: 'trae',
@@ -107,6 +116,7 @@ const EXPECTED = {
hookEvents: undefined,
extendedHookEvents: [],
hooksSurface: 'none',
sandboxTier: 'none',
},
qwen: {
runtime: 'qwen',
@@ -116,6 +126,7 @@ const EXPECTED = {
hookEvents: 'claude',
extendedHookEvents: ['SubagentStop', 'Stop', 'PreCompact'],
hooksSurface: 'settings-json',
sandboxTier: 'none',
},
hermes: {
runtime: 'hermes',
@@ -125,6 +136,7 @@ const EXPECTED = {
hookEvents: 'claude',
extendedHookEvents: [],
hooksSurface: 'settings-json',
sandboxTier: 'none',
},
codebuddy: {
runtime: 'codebuddy',
@@ -134,6 +146,7 @@ const EXPECTED = {
hookEvents: 'claude',
extendedHookEvents: [],
hooksSurface: 'settings-json',
sandboxTier: 'none',
},
cline: {
runtime: 'cline',
@@ -143,6 +156,7 @@ const EXPECTED = {
hookEvents: undefined,
extendedHookEvents: [],
hooksSurface: 'cline-rules',
sandboxTier: 'none',
},
kimi: {
runtime: 'kimi',
@@ -152,6 +166,7 @@ const EXPECTED = {
hookEvents: undefined,
extendedHookEvents: [],
hooksSurface: 'none',
sandboxTier: 'none',
},
windsurf: {
runtime: 'windsurf',
@@ -161,6 +176,7 @@ const EXPECTED = {
hookEvents: undefined,
extendedHookEvents: [],
hooksSurface: 'none',
sandboxTier: 'none',
},
};