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:
@@ -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 [
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user