diff --git a/configs/antigravity-cli/hooks.json b/configs/antigravity-cli/hooks.json index 5e106deb1..491b37090 100644 --- a/configs/antigravity-cli/hooks.json +++ b/configs/antigravity-cli/hooks.json @@ -24,13 +24,8 @@ ], "Stop": [ { - "matcher": "", - "hooks": [ - { - "type": "command", - "command": "context-mode hook antigravity-cli stop" - } - ] + "type": "command", + "command": "context-mode hook antigravity-cli stop" } ] } diff --git a/configs/antigravity-cli/hooks/hooks.json b/configs/antigravity-cli/hooks/hooks.json index 5e106deb1..491b37090 100644 --- a/configs/antigravity-cli/hooks/hooks.json +++ b/configs/antigravity-cli/hooks/hooks.json @@ -24,13 +24,8 @@ ], "Stop": [ { - "matcher": "", - "hooks": [ - { - "type": "command", - "command": "context-mode hook antigravity-cli stop" - } - ] + "type": "command", + "command": "context-mode hook antigravity-cli stop" } ] } diff --git a/src/adapters/antigravity-cli/index.ts b/src/adapters/antigravity-cli/index.ts index 267245aae..dbad482cd 100644 --- a/src/adapters/antigravity-cli/index.ts +++ b/src/adapters/antigravity-cli/index.ts @@ -108,6 +108,10 @@ function agySessionId(raw: Record): string { function hookEntryHasCommand(entry: unknown, command: string): boolean { const e = asRecord(entry); + // agy stores Stop (and its invocation hooks) as a flat list of handler + // objects, so accept `command` directly as well as the grouped + // matcher/hooks form PreToolUse/PostToolUse use (#1206). + if (e.command === command) return true; const nested = Array.isArray(e.hooks) ? e.hooks : []; return nested.some((hook) => asRecord(hook).command === command); } @@ -348,9 +352,12 @@ export class AntigravityCliAdapter extends AntigravityAdapter { matcher: "", hooks: [{ type: "command", command: POST_HOOK_COMMAND }], }; + // agy 1.2.x rejects the whole hooks.json when Stop uses the grouped + // matcher/hooks form; it expects Stop as a flat list of handler objects + // (its bundled docs table groups only PreToolUse/PostToolUse) (#1206). const desiredStop = { - matcher: "", - hooks: [{ type: "command", command: STOP_HOOK_COMMAND }], + type: "command", + command: STOP_HOOK_COMMAND, }; const changedPre = configureHookEntry(hooks, "PreToolUse", desiredPre, PRE_HOOK_COMMAND); diff --git a/tests/adapters/antigravity.test.ts b/tests/adapters/antigravity.test.ts index 76fa38488..23239ed27 100644 --- a/tests/adapters/antigravity.test.ts +++ b/tests/adapters/antigravity.test.ts @@ -267,7 +267,7 @@ describe("AntigravityCliAdapter", () => { expect(changes.length).toBeGreaterThan(0); const cfg = JSON.parse(readFileSync(antigravityCliHooksPath(), "utf-8")) as { - hooks: Record }>>; + hooks: Record>>; }; expect(cfg.hooks.PreToolUse[0].matcher).toBe("run_command|view_file|grep_search|web_fetch|read_url_content"); expect(cfg.hooks.PreToolUse[0].hooks[0].command).toBe( @@ -276,9 +276,12 @@ describe("AntigravityCliAdapter", () => { expect(cfg.hooks.PostToolUse[0].hooks[0].command).toBe( "context-mode hook antigravity-cli posttooluse", ); - expect(cfg.hooks.Stop[0].hooks[0].command).toBe( - "context-mode hook antigravity-cli stop", - ); + // agy requires Stop as a flat list of handler objects (no matcher/hooks + // wrapper); the grouped form makes agy reject the whole hooks.json (#1206). + expect(cfg.hooks.Stop[0]).toEqual({ + type: "command", + command: "context-mode hook antigravity-cli stop", + }); // Second run sees no drift. expect(adapter.configureAllHooks("/plugin/root")).toEqual([]); @@ -299,7 +302,35 @@ describe("AntigravityCliAdapter", () => { expect(JSON.stringify(after.hooks.PreToolUse)).toContain("echo pre"); expect(JSON.stringify(after.hooks.PreToolUse)).toContain("antigravity-cli pretooluse"); expect(after.hooks.PostToolUse[0].hooks[0].command).toContain("antigravity-cli posttooluse"); - expect(after.hooks.Stop[0].hooks[0].command).toContain("antigravity-cli stop"); + expect(after.hooks.Stop[0].command).toContain("antigravity-cli stop"); + }); + + it("configureAllHooks rewrites a legacy grouped Stop into the flat form agy accepts", () => { + // #1206: agy 1.2.x rejects the whole hooks.json when Stop is grouped. + rmSync(antigravityCliHooksPath(), { force: true }); + const path = antigravityCliHooksPath(); + writeFileSync( + path, + JSON.stringify({ + hooks: { + Stop: [ + { + matcher: "", + hooks: [{ type: "command", command: "context-mode hook antigravity-cli stop" }], + }, + ], + }, + }), + ); + + adapter.configureAllHooks("/plugin/root"); + + const after = JSON.parse(readFileSync(path, "utf-8")); + expect(after.hooks.Stop).toHaveLength(1); + expect(after.hooks.Stop[0]).toEqual({ + type: "command", + command: "context-mode hook antigravity-cli stop", + }); }); it("validateHooks warns until all agy hooks are configured, then passes", () => { @@ -330,11 +361,16 @@ describe("AntigravityCliAdapter", () => { PostToolUse: [ { matcher: "", hooks: [{ type: "command", command: "context-mode hook antigravity-cli posttooluse" }] }, ], + Stop: [ + { type: "command", command: "context-mode hook antigravity-cli stop" }, + ], }, }), ); - expect(adapter.validateHooks("/plugin/root")[0].status).toBe("pass"); + const result = adapter.validateHooks("/plugin/root")[0]; + expect(result.status).toBe("pass"); + expect(result.message).toContain("best-effort Stop hook also configured"); rmSync(pluginHooks, { force: true }); }); }); @@ -381,13 +417,14 @@ describe("configs/antigravity-cli — agy plugin bundle", () => { expect(JSON.parse(readFileSync(resolve(AGY_PLUGIN, "hooks", "hooks.json"), "utf-8"))).toEqual(hooks); const pre = hooks.hooks?.PreToolUse?.[0]; const post = hooks.hooks?.PostToolUse?.[0]?.hooks?.[0]; - const stop = hooks.hooks?.Stop?.[0]?.hooks?.[0]; + const stop = hooks.hooks?.Stop?.[0]; expect(pre?.matcher).toBe("run_command|view_file|grep_search|web_fetch|read_url_content"); expect(pre?.hooks?.[0]?.command).toBe("context-mode hook antigravity-cli pretooluse"); expect(post?.type).toBe("command"); expect(post?.command).toBe("context-mode hook antigravity-cli posttooluse"); - expect(stop?.type).toBe("command"); - expect(stop?.command).toBe("context-mode hook antigravity-cli stop"); + // Stop must stay flat: agy 1.2.x rejects the whole hooks.json when Stop + // carries the matcher/hooks wrapper (#1206). + expect(stop).toEqual({ type: "command", command: "context-mode hook antigravity-cli stop" }); // Do not over-map invocation hooks until agy payload/response semantics are verified. expect(hooks.hooks?.PreInvocation).toBeUndefined(); expect(hooks.hooks?.PostInvocation).toBeUndefined();