diff --git a/src/app-server/services/catalog.ts b/src/app-server/services/catalog.ts index b624fd17..2ebe44be 100644 --- a/src/app-server/services/catalog.ts +++ b/src/app-server/services/catalog.ts @@ -95,15 +95,15 @@ export async function listHookCatalog(client: AppServerRequestClient, cwd: strin export async function trustHookItem(client: AppServerRequestClient, hook: HookItem): Promise { const operation = appServerHookOperationFromHookItem(hook); await writeHookState(client, operation.key, { - enabled: true, trusted_hash: operation.currentHash, }); } export async function setHookItemEnabled(client: AppServerRequestClient, hook: HookItem, enabled: boolean): Promise { + if (hook.isManaged) throw new Error("Managed hooks cannot be enabled or disabled here."); const operation = appServerHookOperationFromHookItem(hook); - const state: HookConfigState = operation.trustStatus === "trusted" ? { enabled, trusted_hash: operation.currentHash } : { enabled }; - await writeHookState(client, operation.key, state); + if (operation.trustStatus !== "trusted") throw new Error("Trust the current hook definition before enabling it."); + await writeHookState(client, operation.key, { enabled }); } type HookConfigState = Record; diff --git a/src/settings/hook-section.tsx b/src/settings/hook-section.tsx index acd12d8f..d74bb8c6 100644 --- a/src/settings/hook-section.tsx +++ b/src/settings/hook-section.tsx @@ -44,27 +44,34 @@ function Hooks({ state }: { state: HookSectionState }): UiNode { function HookRow({ hook, state }: { hook: HookItem; state: HookSectionState }): UiNode { const canTrust = !hook.isManaged && (hook.trustStatus === "untrusted" || hook.trustStatus === "modified"); + const canToggle = !hook.isManaged && hook.trustStatus === "trusted"; const hookName = firstNonEmptyString(hook.statusMessage, hook.command, hook.matcher, hook.eventName); + const executionStatus = + hook.trustStatus === "trusted" || hook.trustStatus === "managed" ? (hook.enabled ? "enabled" : "disabled") : "inactive"; return ( - { - state.onTrust(hook); - }} - /> - { - state.onToggleEnabled(hook, !hook.enabled); - }} - /> + {canTrust ? ( + { + state.onTrust(hook); + }} + /> + ) : null} + {canToggle ? ( + { + state.onToggleEnabled(hook, !hook.enabled); + }} + /> + ) : null} ); } diff --git a/tests/app-server/catalog.test.ts b/tests/app-server/catalog.test.ts index 88d8d1f4..6821c8f5 100644 --- a/tests/app-server/catalog.test.ts +++ b/tests/app-server/catalog.test.ts @@ -180,7 +180,6 @@ describe("app-server catalog adapters", () => { keyPath: "hooks.state", value: { "hook-trust": { - enabled: true, trusted_hash: "newhash", }, }, @@ -191,7 +190,7 @@ describe("app-server catalog adapters", () => { }); }); - it("preserves trusted hashes when toggling already trusted hooks", async () => { + it("updates hook enablement without rewriting independently managed trust state", async () => { const client = { request: vi.fn().mockResolvedValue({}), } as unknown as AppServerRequestClient; @@ -207,7 +206,6 @@ describe("app-server catalog adapters", () => { value: { "hook-enabled": { enabled: false, - trusted_hash: "trustedhash", }, }, mergeStrategy: "upsert", @@ -217,7 +215,7 @@ describe("app-server catalog adapters", () => { }); }); - it("does not write trusted hashes when toggling untrusted hook revisions", async () => { + it("rejects enablement changes for untrusted hook revisions", async () => { const client = { request: vi.fn().mockResolvedValue({}), } as unknown as AppServerRequestClient; @@ -226,22 +224,8 @@ describe("app-server catalog adapters", () => { ])[0]; if (!hook) throw new Error("Expected mapped hook"); - await setHookItemEnabled(client, hook, true); - - expect(client.request).toHaveBeenCalledWith("config/batchWrite", { - edits: [ - { - keyPath: "hooks.state", - value: { - "hook-modified": { - enabled: true, - }, - }, - mergeStrategy: "upsert", - }, - ], - reloadUserConfig: true, - }); + await expect(setHookItemEnabled(client, hook, true)).rejects.toThrow("Trust the current hook definition before enabling it."); + expect(client.request).not.toHaveBeenCalled(); }); }); diff --git a/tests/settings/settings-tab.test.ts b/tests/settings/settings-tab.test.ts index 111c7c72..3656210f 100644 --- a/tests/settings/settings-tab.test.ts +++ b/tests/settings/settings-tab.test.ts @@ -854,7 +854,11 @@ describe("settings tab", () => { expect(tab.containerEl.querySelectorAll(".codex-panel-settings__hook-list .codex-panel-settings__hook-row")).toHaveLength(1); expect(tab.containerEl.querySelectorAll(".codex-panel-settings__archived-list .codex-panel-settings__archived-row")).toHaveLength(1); expect(tab.containerEl.querySelector(".codex-panel-settings__hook-list")?.textContent).not.toContain("abc123"); + expect(tab.containerEl.querySelector(".codex-panel-settings__hook-list")?.textContent).toContain("untrusted · inactive"); expect(tab.containerEl.querySelector(".codex-panel-settings__archived-list")?.textContent).toContain("Archived thread"); + expect(buttonTexts(tab)).toContain("Trust"); + expect(buttonTexts(tab)).not.toContain("Enable"); + expect(buttonTexts(tab)).not.toContain("Disable"); expect(buttonLabels(tab)).toContain("Restore thread"); expect(buttonLabels(tab)).toContain("Delete thread"); });