feat: implement Hook Chains runtime integration for self-healing agent mesh MVP (#711)

* feat: implement Hook Chains runtime integration for self-healing agent mesh MVP

- Add Hook Chains config loader, evaluator, and dispatcher in src/utils/hookChains.ts
- Wire PostToolUseFailure hook dispatch in executePostToolUseFailureHooks()
- Wire TaskCompleted hook dispatch in executeTaskCompletedHooks()
- Integrate fallback-agent launcher with permission preservation (canUseTool threading)
- Add safety hardening for config-read errors (try-catch protection)
- Update docs with MVP runtime trigger explanation
- Add 10 unit tests and 4 integration tests covering config, rules, guards, and actions

This completes the self-healing agent mesh MVP by enabling declarative rule-based
responses to tool failures and task completions, with fallback agent spawning,
team notification, and capacity warming actions.

* Update docs/hook-chains.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/utils/hookChains.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix: address PR #711 review blockers for Hook Chains

- Gate hook-chain dispatch behind feature('HOOK_CHAINS') and default env gate to off
- Remove committed local artifact (agent.log) and ignore it in .gitignore
- Revert hook dispatcher signature threading changes for canUseTool
- Use ToolUseContext metadata hookChainsCanUseTool for fallback launch permissions
- Make spawn_fallback_agent fail explicitly when launcher context is unavailable
- Add config cache max age and guard map size limits to bound runtime memory
- Update docs and tests for default-off gating and explicit fallback failure

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Urvish L.
2026-04-22 13:40:23 +02:00
committed by GitHub
parent 5b9cd21e37
commit 44a2c30d5f
9 changed files with 2905 additions and 22 deletions

View File

@@ -1241,6 +1241,7 @@ async function checkPermissionsAndCallTool(
{
...toolUseContext,
toolUseId: toolUseID,
hookChainsCanUseTool: canUseTool,
userModified: permissionDecision.userModified ?? false,
},
canUseTool,
@@ -1729,19 +1730,29 @@ async function checkPermissionsAndCallTool(
const hookMessages: MessageUpdateLazy<
AttachmentMessage | ProgressMessage<HookProgress>
>[] = []
for await (const hookResult of runPostToolUseFailureHooks(
toolUseContext,
tool,
toolUseID,
messageId,
processedInput,
content,
isInterrupt,
requestId,
mcpServerType,
mcpServerBaseUrl,
)) {
hookMessages.push(hookResult)
const hookChainsContext = toolUseContext as ToolUseContext & {
hookChainsCanUseTool?: CanUseToolFn
}
hookChainsContext.hookChainsCanUseTool = canUseTool
try {
for await (const hookResult of runPostToolUseFailureHooks(
toolUseContext,
tool,
toolUseID,
messageId,
processedInput,
content,
isInterrupt,
requestId,
mcpServerType,
mcpServerBaseUrl,
)) {
hookMessages.push(hookResult)
}
} finally {
if (hookChainsContext.hookChainsCanUseTool === canUseTool) {
delete hookChainsContext.hookChainsCanUseTool
}
}
return [

View File

@@ -284,6 +284,7 @@ export async function* runPostToolUseFailureHooks<Input extends AnyObject>(
isInterrupt,
permissionMode,
toolUseContext.abortController.signal,
undefined,
)) {
try {
// Check if we were aborted during hook execution