feat(daemon+cli): slice 3.5 — runtime.codex.hooks_enabled setting + ensureCodexFeatureFlag adapter method

Per IMPL-PRD §5 + velocity-guard cadence boundary (d) Checkpoint D.1.
Makes the 7 TDD-red tests in commit 6f68042 pass.

Settings (CLI ConfigStore + daemon settings-store, lockstep):
- New SETTINGS_VALID_KEYS entry: runtime.codex.hooks_enabled (default true)
- ENV_MAP: OPENRIG_RUNTIME_CODEX_HOOKS_ENABLED primary; legacy '' empty
  per banked feedback_no_rigged_legacy_for_new_keys.md (net-new key
  post-rename; no RIGGED_X cargo-cult)
- KEY_TO_PATH: ['runtime', 'codex', 'hooksEnabled']
- DEFAULTS (CLI side) + getDefaultValue switch (daemon side): both return
  true

CodexRuntimeAdapter:
- New ensureCodexFeatureFlag(enabled: boolean) method
- enabled=true → idempotent write of codex_hooks = true under [features]
  in ~/.codex/config.toml; preserves any existing config content;
  reuses existing upsertCodexHooksFeature TOML helper
- enabled=false → ZERO mutation (operator owns Codex config)
- Skips silently if no homedir available in fsOps

Existing settings-test fixtures updated:
- user-settings-store.test.ts: SETTINGS_VALID_KEYS expected list +1
- user-settings-routes.test.ts: settings count 27 → 28

Verification:
- codex-hooks-feature-flag.test.ts: 7/7 pass
- Full daemon suite: 3158 pass; 1 fail (pre-existing rsi-v2)

NOT YET in this commit (Checkpoint D.2):
- Activity-hook injection rip from both adapters (provisionActivityHooks,
  upsertCommandHook, hookEntryContainsCommand) — old upsertCodexHooksFeature
  TOML helper stays for now since ensureCodexFeatureFlag reuses it; rip
  removes the activity-hook coupling site
- Asset deletion (openrig-activity-hook-relay.cjs)
- Daemon startup wire-up: replace activityHookRelayAssetPath wiring with
  ensureCodexFeatureFlag(setting) call

Per IMPL-PRD §5 plugin-primitive Phase 3a slice 3.5.
This commit is contained in:
mvschwarz committed 2026-05-10 03:36:16 -07:00
1 parent 6f6804267e
commit 6cb24cce19
5 files changed
+58 -2

No files matched your search

+12
View File
@@ -125,6 +125,12 @@ const DEFAULTS = {
auditLog: false,
},
},
// plugin-primitive Phase 3a slice 3.5 — Codex feature flag default ON.
runtime: {
codex: {
hooksEnabled: true,
},
},
} as const;
export const VALID_KEYS = [
@@ -159,6 +165,8 @@ export const VALID_KEYS = [
"feed.subscriptions.shipped",
"feed.subscriptions.progress",
"feed.subscriptions.audit_log",
// plugin-primitive Phase 3a slice 3.5 — Codex feature flag.
"runtime.codex.hooks_enabled",
] as const;
export type ValidKey = typeof VALID_KEYS[number];
@@ -194,6 +202,9 @@ export const ENV_MAP: Record<ValidKey, { primary: string; legacy: string }> = {
"feed.subscriptions.shipped": { primary: "OPENRIG_FEED_SUBSCRIPTIONS_SHIPPED", legacy: "RIGGED_FEED_SUBSCRIPTIONS_SHIPPED" },
"feed.subscriptions.progress": { primary: "OPENRIG_FEED_SUBSCRIPTIONS_PROGRESS", legacy: "RIGGED_FEED_SUBSCRIPTIONS_PROGRESS" },
"feed.subscriptions.audit_log": { primary: "OPENRIG_FEED_SUBSCRIPTIONS_AUDIT_LOG", legacy: "RIGGED_FEED_SUBSCRIPTIONS_AUDIT_LOG" },
// Net-new key post-rename: OPENRIG_X primary only; legacy field empty
// per banked feedback_no_rigged_legacy_for_new_keys.md.
"runtime.codex.hooks_enabled": { primary: "OPENRIG_RUNTIME_CODEX_HOOKS_ENABLED", legacy: "" },
};
// Maps dotted-string config keys to the camelCase RiggedConfig path.
@@ -228,6 +239,7 @@ const KEY_TO_PATH: Record<ValidKey, string[]> = {
"feed.subscriptions.shipped": ["feed", "subscriptions", "shipped"],
"feed.subscriptions.progress": ["feed", "subscriptions", "progress"],
"feed.subscriptions.audit_log": ["feed", "subscriptions", "auditLog"],
"runtime.codex.hooks_enabled": ["runtime", "codex", "hooksEnabled"],
};
function isValidKey(key: string): key is ValidKey {
@@ -62,6 +62,30 @@ export class CodexRuntimeAdapter implements RuntimeAdapter {
this.activityHookRelayAssetPath = deps.activityHookRelayAssetPath ?? null;
}
/**
* plugin-primitive Phase 3a slice 3.5 — ensure Codex feature flag.
*
* When `enabled` is true, idempotently writes `codex_hooks = true` under
* `[features]` in `~/.codex/config.toml`, creating the file if missing.
* When `enabled` is false, makes ZERO modifications — the operator is
* managing Codex config independently and the daemon does not touch it.
*
* Replaces the activity-hook-injection-coupled call to upsertCodexHooksFeature
* that lived in provisionActivityHooks() pre-rip.
*/
ensureCodexFeatureFlag(enabled: boolean): void {
if (!enabled) return;
const homedir = this.fs.homedir;
if (!homedir) return;
const configPath = nodePath.join(homedir, ".codex", "config.toml");
const existing = this.fs.exists(configPath) ? this.fs.readFile(configPath) : "";
const updated = upsertCodexHooksFeature(existing);
if (updated !== existing) {
this.fs.mkdirp(nodePath.dirname(configPath));
this.fs.writeFile(configPath, updated);
}
}
async listInstalled(binding: NodeBinding): Promise<InstalledResource[]> {
const results: InstalledResource[] = [];
const skillsDir = nodePath.join(binding.cwd, ".agents", "skills");
@@ -70,6 +70,12 @@ export const SETTINGS_VALID_KEYS = [
"feed.subscriptions.shipped",
"feed.subscriptions.progress",
"feed.subscriptions.audit_log",
// plugin-primitive Phase 3a slice 3.5 — Codex feature flag.
// When true (default), daemon ensures `codex_hooks = true` in
// ~/.codex/config.toml on launch so plugin-shipped hooks fire on
// Codex runtime. When false, operator is managing Codex config
// independently — daemon does NOT mutate.
"runtime.codex.hooks_enabled",
] as const;
export type SettingsValidKey = typeof SETTINGS_VALID_KEYS[number];
@@ -102,6 +108,12 @@ const ENV_MAP: Record<SettingsValidKey, { primary: string; legacy: string }> = {
"feed.subscriptions.shipped": { primary: "OPENRIG_FEED_SUBSCRIPTIONS_SHIPPED", legacy: "RIGGED_FEED_SUBSCRIPTIONS_SHIPPED" },
"feed.subscriptions.progress": { primary: "OPENRIG_FEED_SUBSCRIPTIONS_PROGRESS", legacy: "RIGGED_FEED_SUBSCRIPTIONS_PROGRESS" },
"feed.subscriptions.audit_log": { primary: "OPENRIG_FEED_SUBSCRIPTIONS_AUDIT_LOG", legacy: "RIGGED_FEED_SUBSCRIPTIONS_AUDIT_LOG" },
// plugin-primitive Phase 3a slice 3.5 — net-new key post-rename;
// OPENRIG_X primary only (no RIGGED_X legacy per banked
// feedback_no_rigged_legacy_for_new_keys.md). Legacy field is
// empty-string sentinel kept for type-shape uniformity with the
// rest of ENV_MAP.
"runtime.codex.hooks_enabled": { primary: "OPENRIG_RUNTIME_CODEX_HOOKS_ENABLED", legacy: "" },
};
const KEY_TO_PATH: Record<SettingsValidKey, string[]> = {
@@ -132,6 +144,7 @@ const KEY_TO_PATH: Record<SettingsValidKey, string[]> = {
"feed.subscriptions.shipped": ["feed", "subscriptions", "shipped"],
"feed.subscriptions.progress": ["feed", "subscriptions", "progress"],
"feed.subscriptions.audit_log": ["feed", "subscriptions", "auditLog"],
"runtime.codex.hooks_enabled": ["runtime", "codex", "hooksEnabled"],
};
export type SettingSource = "env" | "file" | "default";
@@ -240,6 +253,10 @@ function getDefaultValue(key: SettingsValidKey, workspaceRoot: string): string |
case "feed.subscriptions.shipped": return true;
case "feed.subscriptions.progress": return true;
case "feed.subscriptions.audit_log": return false;
// plugin-primitive Phase 3a slice 3.5 — Codex feature flag default ON.
// Daemon ensures `codex_hooks = true` in ~/.codex/config.toml on
// launch unless operator explicitly sets to false.
case "runtime.codex.hooks_enabled": return true;
default: return "";
}
}
@@ -68,8 +68,9 @@ describe("config routes (User Settings v0)", () => {
expect(res.status).toBe(200);
const body = await res.json() as { settings: Record<string, { value: unknown; source: string }> };
// 18 v0 keys + 2 Phase 4 (advisor/operator) + 5 Phase 5 (feed.subscriptions.*)
// + 2 V1 pre-release Item 1 (transcripts.lines / transcripts.poll_interval_seconds).
expect(Object.keys(body.settings).length).toBe(27);
// + 2 V1 pre-release Item 1 (transcripts.lines / transcripts.poll_interval_seconds)
// + 1 plugin-primitive Phase 3a slice 3.5 (runtime.codex.hooks_enabled).
expect(Object.keys(body.settings).length).toBe(28);
expect(body.settings["daemon.port"]?.source).toBe("default");
expect(body.settings["ui.preview.refresh_interval_seconds"]?.value).toBe(3);
expect(body.settings["ui.preview.max_pins"]?.value).toBe(4);
@@ -79,6 +79,8 @@ describe("SettingsStore (User Settings v0)", () => {
"feed.subscriptions.shipped",
"feed.subscriptions.progress",
"feed.subscriptions.audit_log",
// plugin-primitive Phase 3a slice 3.5 — Codex feature flag.
"runtime.codex.hooks_enabled",
]);
});