fix(node): mirror Claude async rewake metadata
This commit is contained in:
1 parent
2a4e62f0a3
commit
45f6ee076b
4 files changed
+46
-3
No files matched your search
@@ -26,7 +26,7 @@ This repository publishes the open-source server, console, integration catalog,
|
|||||||
- Resolve stable Kimi hooks through `$KIMI_CODE_HOME/config.toml` with `~/.kimi-code/config.toml` as the default, and treat an empty override as the default. Activate `~/.kimi/config.toml` only when the legacy home exists, ignore executable lookup as activation evidence, and collapse equal stable and legacy paths to the stable contract. Register exact `PreToolUse`, `PostToolUse`, and `PostToolUseFailure` rules with ten-second commands; stable Kimi supports its current sixteen-event schema and legacy kimi-cli supports its thirteen-event schema. Preserve the native snake_case payload, propagate exit code `2`, and keep audit delivery fail-open with owner-only error logs. Use encoded PowerShell commands on Windows and exact two-argument shell commands on Unix. Commit every detected config, generated runtime, runtime config, and private key through one rollback-capable transaction. Status requires strict runtime metadata, a canonical private key, exact identity, and non-empty physical scripts.
|
- Resolve stable Kimi hooks through `$KIMI_CODE_HOME/config.toml` with `~/.kimi-code/config.toml` as the default, and treat an empty override as the default. Activate `~/.kimi/config.toml` only when the legacy home exists, ignore executable lookup as activation evidence, and collapse equal stable and legacy paths to the stable contract. Register exact `PreToolUse`, `PostToolUse`, and `PostToolUseFailure` rules with ten-second commands; stable Kimi supports its current sixteen-event schema and legacy kimi-cli supports its thirteen-event schema. Preserve the native snake_case payload, propagate exit code `2`, and keep audit delivery fail-open with owner-only error logs. Use encoded PowerShell commands on Windows and exact two-argument shell commands on Unix. Commit every detected config, generated runtime, runtime config, and private key through one rollback-capable transaction. Status requires strict runtime metadata, a canonical private key, exact identity, and non-empty physical scripts.
|
||||||
- Preserve Kimi TOML comments, array style, and unrelated formatting through syntax-preserving edits, then parse every selected document before writing any file.
|
- Preserve Kimi TOML comments, array style, and unrelated formatting through syntax-preserving edits, then parse every selected document before writing any file.
|
||||||
- Resolve Grok Build user hooks through `$GROK_HOME/hooks/*.json` with `~/.grok/hooks/*.json` as the default, and treat an empty override as the default. Keep Claude Code, Cursor, project `.grok/hooks`, plugin, and `hooks-paths` sources read-only. Register exact `PreToolUse`, `PostToolUse`, and `PostToolUseFailure` groups with ten-second command handlers, preserve the complete native camelCase payload, and emit Grok's documented deny JSON plus exit code `2` for frozen or revoked agents. Use encoded PowerShell commands on Windows and exact two-argument shell commands on Unix. Commit each SDK's Grok hook file, generated runtimes, runtime config, and private key through one rollback-capable transaction. Status requires the exact event triple, strict runtime metadata, a canonical private key, exact identity, and non-empty physical scripts.
|
- Resolve Grok Build user hooks through `$GROK_HOME/hooks/*.json` with `~/.grok/hooks/*.json` as the default, and treat an empty override as the default. Keep Claude Code, Cursor, project `.grok/hooks`, plugin, and `hooks-paths` sources read-only. Register exact `PreToolUse`, `PostToolUse`, and `PostToolUseFailure` groups with ten-second command handlers, preserve the complete native camelCase payload, and emit Grok's documented deny JSON plus exit code `2` for frozen or revoked agents. Use encoded PowerShell commands on Windows and exact two-argument shell commands on Unix. Commit each SDK's Grok hook file, generated runtimes, runtime config, and private key through one rollback-capable transaction. Status requires the exact event triple, strict runtime metadata, a canonical private key, exact identity, and non-empty physical scripts.
|
||||||
- Resolve Claude Code user hooks through `$CLAUDE_CONFIG_DIR/settings.json` with `~/.claude/settings.json` when the variable is absent. Match Claude Code's path resolution exactly: relative and empty values resolve from the current working directory, and literal tildes remain path segments. Keep project, local, managed, plugin, skill, and agent hook sources read-only. Register exact matchless `PreToolUse`, `PostToolUse`, and `PostToolUseFailure` groups with ten-second exec-form command handlers. Preserve the complete native snake_case payload and propagate freeze and revocation through exit code `2`. Commit each SDK's Claude Code user settings, generated runtimes, runtime config, and private key through one rollback-capable transaction. Reject installation while the user source sets `disableAllHooks`, then require `/hooks` and `claude doctor` verification for higher-scope policy effects.
|
- Resolve Claude Code user hooks through `$CLAUDE_CONFIG_DIR/settings.json` with `~/.claude/settings.json` when the variable is absent. Match Claude Code's path resolution exactly: relative and empty values resolve from the current working directory, and literal tildes remain path segments. Keep project, local, managed, plugin, skill, and agent hook sources read-only. Validate the complete shipped handler schema, including command exec form, async rewake metadata, HTTP, MCP tool, prompt, and agent handlers. Register exact matchless `PreToolUse`, `PostToolUse`, and `PostToolUseFailure` groups with ten-second exec-form command handlers. Preserve the complete native snake_case payload and propagate freeze and revocation through exit code `2`. Commit each SDK's Claude Code user settings, generated runtimes, runtime config, and private key through one rollback-capable transaction. Reject installation while the user source sets `disableAllHooks`, then require `/hooks` and `claude doctor` verification for higher-scope policy effects.
|
||||||
- Resolve Gemini CLI user hooks through `$GEMINI_CLI_HOME/.gemini/settings.json` with `~/.gemini/settings.json` when the variable is absent or empty. Preserve relative and literal-tilde home values as Gemini CLI does. Keep workspace, system defaults, system overrides, and extension hooks read-only. Preserve JSON comments while rejecting trailing commas and duplicate keys. Register exact matchless `BeforeTool` and `AfterTool` groups named `elydora-guard` and `elydora-audit` with ten-second command handlers. Preserve the complete native snake_case payload, emit valid JSON on exit code `0`, and propagate freeze and revocation through exit code `2`. Use encoded PowerShell commands on Windows. Commit each SDK's Gemini CLI user settings, generated runtimes, runtime config, and private key through one rollback-capable transaction. Respect `hooksConfig.enabled` and `hooksConfig.disabled`, then require `/hooks list` verification for higher-scope policy effects.
|
- Resolve Gemini CLI user hooks through `$GEMINI_CLI_HOME/.gemini/settings.json` with `~/.gemini/settings.json` when the variable is absent or empty. Preserve relative and literal-tilde home values as Gemini CLI does. Keep workspace, system defaults, system overrides, and extension hooks read-only. Preserve JSON comments while rejecting trailing commas and duplicate keys. Register exact matchless `BeforeTool` and `AfterTool` groups named `elydora-guard` and `elydora-audit` with ten-second command handlers. Preserve the complete native snake_case payload, emit valid JSON on exit code `0`, and propagate freeze and revocation through exit code `2`. Use encoded PowerShell commands on Windows. Commit each SDK's Gemini CLI user settings, generated runtimes, runtime config, and private key through one rollback-capable transaction. Respect `hooksConfig.enabled` and `hooksConfig.disabled`, then require `/hooks list` verification for higher-scope policy effects.
|
||||||
- Write Auggie hooks only to `~/.augment/settings.json`; keep system and workspace settings read-only. Generate `.cmd` wrappers on Windows and `.sh` wrappers on Unix because Auggie dispatches supported script paths, and express hook timeouts in milliseconds.
|
- Write Auggie hooks only to `~/.augment/settings.json`; keep system and workspace settings read-only. Generate `.cmd` wrappers on Windows and `.sh` wrappers on Unix because Auggie dispatches supported script paths, and express hook timeouts in milliseconds.
|
||||||
- Validate Auggie matcher syntax during installation with Node.js `new RegExp`; keep status and uninstall independent from the JavaScript validator so recovery remains available offline.
|
- Validate Auggie matcher syntax during installation with Node.js `new RegExp`; keep status and uninstall independent from the JavaScript validator so recovery remains available offline.
|
||||||
|
|||||||
@@ -22,7 +22,17 @@ const HOOK_EVENTS = new Set([
|
|||||||
|
|
||||||
const COMMON_HANDLER_KEYS = ['if', 'once', 'statusMessage', 'timeout'];
|
const COMMON_HANDLER_KEYS = ['if', 'once', 'statusMessage', 'timeout'];
|
||||||
const HANDLER_KEYS: Readonly<Record<string, readonly string[]>> = {
|
const HANDLER_KEYS: Readonly<Record<string, readonly string[]>> = {
|
||||||
command: [...COMMON_HANDLER_KEYS, 'args', 'async', 'asyncRewake', 'command', 'shell', 'type'],
|
command: [
|
||||||
|
...COMMON_HANDLER_KEYS,
|
||||||
|
'args',
|
||||||
|
'async',
|
||||||
|
'asyncRewake',
|
||||||
|
'command',
|
||||||
|
'rewakeMessage',
|
||||||
|
'rewakeSummary',
|
||||||
|
'shell',
|
||||||
|
'type',
|
||||||
|
],
|
||||||
prompt: [...COMMON_HANDLER_KEYS, 'continueOnBlock', 'model', 'prompt', 'type'],
|
prompt: [...COMMON_HANDLER_KEYS, 'continueOnBlock', 'model', 'prompt', 'type'],
|
||||||
agent: [...COMMON_HANDLER_KEYS, 'model', 'prompt', 'type'],
|
agent: [...COMMON_HANDLER_KEYS, 'model', 'prompt', 'type'],
|
||||||
http: [...COMMON_HANDLER_KEYS, 'allowedEnvVars', 'headers', 'type', 'url'],
|
http: [...COMMON_HANDLER_KEYS, 'allowedEnvVars', 'headers', 'type', 'url'],
|
||||||
@@ -99,6 +109,10 @@ function optionalString(value: JsonObject, field: string, label: string): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function optionalNonEmptyString(value: JsonObject, field: string, label: string): void {
|
||||||
|
if (value[field] !== undefined) requireNonEmptyString(value[field], field, label);
|
||||||
|
}
|
||||||
|
|
||||||
function optionalBoolean(value: JsonObject, field: string, label: string): void {
|
function optionalBoolean(value: JsonObject, field: string, label: string): void {
|
||||||
if (value[field] !== undefined && typeof value[field] !== 'boolean') {
|
if (value[field] !== undefined && typeof value[field] !== 'boolean') {
|
||||||
throw new Error(`${label} field "${field}" must be a boolean`);
|
throw new Error(`${label} field "${field}" must be a boolean`);
|
||||||
@@ -138,6 +152,8 @@ function validateHandler(value: unknown, event: string, group: number, index: nu
|
|||||||
if (value.args !== undefined) validateStringArray(value.args, 'args', label);
|
if (value.args !== undefined) validateStringArray(value.args, 'args', label);
|
||||||
optionalBoolean(value, 'async', label);
|
optionalBoolean(value, 'async', label);
|
||||||
optionalBoolean(value, 'asyncRewake', label);
|
optionalBoolean(value, 'asyncRewake', label);
|
||||||
|
optionalNonEmptyString(value, 'rewakeMessage', label);
|
||||||
|
optionalNonEmptyString(value, 'rewakeSummary', label);
|
||||||
if (value.shell !== undefined && value.shell !== 'bash' && value.shell !== 'powershell') {
|
if (value.shell !== undefined && value.shell !== 'bash' && value.shell !== 'powershell') {
|
||||||
throw new Error(`${label} field "shell" must be "bash" or "powershell"`);
|
throw new Error(`${label} field "shell" must be "bash" or "powershell"`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,22 @@ const claudeBinary = process.env.ELYDORA_CLAUDE_BINARY;
|
|||||||
test('official Claude Code accepts the installed user hook contract', {
|
test('official Claude Code accepts the installed user hook contract', {
|
||||||
skip: claudeBinary ? false : 'set ELYDORA_CLAUDE_BINARY to the official Claude Code executable',
|
skip: claudeBinary ? false : 'set ELYDORA_CLAUDE_BINARY to the official Claude Code executable',
|
||||||
}, async () => {
|
}, async () => {
|
||||||
const fixture = await createFixture();
|
const fixture = await createFixture({
|
||||||
|
settings: {
|
||||||
|
hooks: {
|
||||||
|
Stop: [{
|
||||||
|
hooks: [{
|
||||||
|
type: 'command',
|
||||||
|
command: process.execPath,
|
||||||
|
args: ['--version'],
|
||||||
|
asyncRewake: true,
|
||||||
|
rewakeMessage: 'Background validation failed',
|
||||||
|
rewakeSummary: 'Validation feedback',
|
||||||
|
}],
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
try {
|
try {
|
||||||
assert.equal((await fixture.install()).code, 0);
|
assert.equal((await fixture.install()).code, 0);
|
||||||
const environment = {
|
const environment = {
|
||||||
|
|||||||
@@ -76,6 +76,15 @@ test('Claude installs one exact managed triple and preserves user settings', asy
|
|||||||
matcher: 'Bash',
|
matcher: 'Bash',
|
||||||
hooks: [{ type: 'command', command: 'existing-command', timeout: 5 }],
|
hooks: [{ type: 'command', command: 'existing-command', timeout: 5 }],
|
||||||
}],
|
}],
|
||||||
|
Stop: [{
|
||||||
|
hooks: [{
|
||||||
|
type: 'command',
|
||||||
|
command: 'background-check',
|
||||||
|
asyncRewake: true,
|
||||||
|
rewakeMessage: 'Background validation failed',
|
||||||
|
rewakeSummary: 'Validation feedback',
|
||||||
|
}],
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const fixture = await createFixture({ settings: existing });
|
const fixture = await createFixture({ settings: existing });
|
||||||
@@ -87,6 +96,7 @@ test('Claude installs one exact managed triple and preserves user settings', asy
|
|||||||
assert.equal(installed.settings.model, 'sonnet');
|
assert.equal(installed.settings.model, 'sonnet');
|
||||||
assert.deepEqual(installed.settings.hooks.Notification, existing.hooks.Notification);
|
assert.deepEqual(installed.settings.hooks.Notification, existing.hooks.Notification);
|
||||||
assert.deepEqual(installed.settings.hooks.PreToolUse[0], existing.hooks.PreToolUse[0]);
|
assert.deepEqual(installed.settings.hooks.PreToolUse[0], existing.hooks.PreToolUse[0]);
|
||||||
|
assert.deepEqual(installed.settings.hooks.Stop, existing.hooks.Stop);
|
||||||
assertManagedTriple(installed.settings, fixture);
|
assertManagedTriple(installed.settings, fixture);
|
||||||
|
|
||||||
const second = await fixture.install();
|
const second = await fixture.install();
|
||||||
@@ -166,6 +176,8 @@ test('Claude rejects invalid official hook shapes before writes', async (t) => {
|
|||||||
['command value', JSON.stringify({ hooks: { PreToolUse: [{ hooks: [{ type: 'command', command: '' }] }] } }), /non-empty string/i],
|
['command value', JSON.stringify({ hooks: { PreToolUse: [{ hooks: [{ type: 'command', command: '' }] }] } }), /non-empty string/i],
|
||||||
['argument type', JSON.stringify({ hooks: { PreToolUse: [{ hooks: [{ type: 'command', command: 'x', args: [1] }] }] } }), /array of strings/i],
|
['argument type', JSON.stringify({ hooks: { PreToolUse: [{ hooks: [{ type: 'command', command: 'x', args: [1] }] }] } }), /array of strings/i],
|
||||||
['timeout zero', JSON.stringify({ hooks: { PreToolUse: [{ hooks: [{ type: 'command', command: 'x', timeout: 0 }] }] } }), /positive finite number/i],
|
['timeout zero', JSON.stringify({ hooks: { PreToolUse: [{ hooks: [{ type: 'command', command: 'x', timeout: 0 }] }] } }), /positive finite number/i],
|
||||||
|
['empty rewake message', JSON.stringify({ hooks: { Stop: [{ hooks: [{ type: 'command', command: 'x', rewakeMessage: '' }] }] } }), /rewakeMessage.*non-empty string/i],
|
||||||
|
['empty rewake summary', JSON.stringify({ hooks: { Stop: [{ hooks: [{ type: 'command', command: 'x', rewakeSummary: '' }] }] } }), /rewakeSummary.*non-empty string/i],
|
||||||
['headers type', JSON.stringify({ hooks: { PreToolUse: [{ hooks: [{ type: 'http', url: 'https://example.test', headers: { A: 1 } }] }] } }), /map names to strings/i],
|
['headers type', JSON.stringify({ hooks: { PreToolUse: [{ hooks: [{ type: 'http', url: 'https://example.test', headers: { A: 1 } }] }] } }), /map names to strings/i],
|
||||||
['input type', JSON.stringify({ hooks: { PreToolUse: [{ hooks: [{ type: 'mcp_tool', server: 's', tool: 't', input: [] }] }] } }), /input.*must be an object/i],
|
['input type', JSON.stringify({ hooks: { PreToolUse: [{ hooks: [{ type: 'mcp_tool', server: 's', tool: 't', input: [] }] }] } }), /input.*must be an object/i],
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in new issue
Block a user