Skip to content

fix(agent-core-v2): align externalHooks context injection and UserPromptSubmit payloads - #4256

Open
luyao10282 wants to merge 2 commits into
MoonshotAI:mainfrom
luyao10282:feat/cc-hooks-compat
Open

luyao10282 wants to merge 2 commits into
MoonshotAI:mainfrom
luyao10282:feat/cc-hooks-compat

Conversation

@luyao10282

Copy link
Copy Markdown

Requirement or Bug

Resolve #4255

Bug Reproduction Steps

  1. Configure a UserPromptSubmit or PreToolUse hook in ~/.kimi-code/config.toml that outputs {"hookSpecificOutput":{"hookEventName":"UserPromptSubmit","additionalContext":"context text"}}.
  2. Send a prompt or invoke a slash skill (/my-skill arg1):
    • On normal prompts, additionalContext is silently dropped because HookSpecificOutputSchema omits additionalContext and structuredOutput === true suppresses the stdout fallback.
    • On slash skills (origin.kind === 'skill_activation'), UserPromptSubmit is skipped completely, and on normal prompts parsed.prompt on stdin is a ContentPart[] array rather than a string.
    • On PreToolUse and PostToolUse, additionalContext and updatedInput are ignored.

Root Cause

  1. HookSpecificOutputSchema in packages/agent-core-v2/src/features/externalHooks/internal/runHook.ts only extracted message, permissionDecision, and permissionDecisionReason, omitting additionalContext, systemMessage, and updatedInput.
  2. runPromptSubmitHook in agentExternalHooksService.ts gated on origin.kind === 'user' and passed raw ContentPart[] as prompt.
  3. runPreToolUse and notifyPostToolUse did not collect structured hook messages or updatedInput, and ExternalHooksRunnerService did not subscribe to config.onDidSectionChange.

This is a fundamental fix in packages/agent-core-v2.

Code Changes

  1. Extended HookSpecificOutputSchema and HookJsonOutputSchema in internal/runHook.ts to parse additionalContext, systemMessage, and updatedInput.
  2. Updated runPromptSubmitHook to run on user-slash skill_activation and plugin_command origins and serialize prompt as a string via turnPromptText.
  3. Injected structured additionalContext / message from SessionStart, PreToolUse, and PostToolUse as <system-reminder><hook_result ...> messages (while ignoring noisy plain stdout on tool hooks), and wired updatedInput into BeforeToolExecuteEvent / AgentToolExecutorService with schema re-validation and resolveExecution re-evaluation.
  4. Subscribed ExternalHooksRunnerService to config.onDidSectionChange for HOOKS_SECTION.

Behavior Changes and Affected Users

Behavior Before After Who relies on the old behavior Escape hatch
hookSpecificOutput.additionalContext in hook JSON output Silently dropped (structuredOutput: true with message: undefined) Extracted as hook message on UserPromptSubmit, SessionStart, PreToolUse, and PostToolUse None (previously a no-op bug) Omit additionalContext from hook JSON output
UserPromptSubmit on user-slash skill/plugin commands & prompt stdin field Skipped on slash skills; prompt serialized as ContentPart[] array Runs on user-slash skill/plugin activations; prompt serialized as string None Use matcher on UserPromptSubmit to filter slash commands
PreToolUse updatedInput and PostToolUse execution updatedInput ignored; PostToolUse fire-and-forget without tool_response updatedInput updates and re-validates tool args; PostToolUse awaited with tool_response Hooks writing incidental plain stdout on tool events remain unaffected (allowPlainStdout: false) N/A

Affected modules: packages/agent-core-v2/src/features/externalHooks, packages/agent-core-v2/src/agent/toolExecutor.
Test coverage: packages/agent-core-v2/test/features/externalHooks/integration.test.ts (142/142 tests passing).

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue (external PRs: issue must have a maintainer's /approve).
  • I have added tests that prove my feature works.
  • The behavior-change table above is complete, and every removed behavior or flipped default is named in the changeset and either has an escape hatch or was explicitly approved by a maintainer in this PR.
  • Ran gen-changesets skill, or this PR needs no changeset.
  • Ran gen-docs skill, or this PR needs no doc update.

…mptSubmit payloads

- Parse hookSpecificOutput.additionalContext, systemMessage, and updatedInput in runHook
- Fire UserPromptSubmit on user-slash skill and plugin-command activations and pass prompt as string instead of ContentPart[]
- Inject additionalContext on SessionStart, PreToolUse, and PostToolUse as system-reminder hook_result messages while ignoring noisy plain stdout on tool hooks
- Support updatedInput mutation and re-validation in BeforeToolExecuteEvent / AgentToolExecutorService
- Await PostToolUse and include tool_response in stdin payload
- Reload hooks index when the hooks config section changes
Copilot AI balanced review requested due to automatic review settings October 16, 2026 08:08
@changeset-bot

changeset-bot Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: fca6815

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@moonshot-ai/kimi-code Patch
@moonshot-ai/kimi-code-sdk Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-16T12:45:01.257995Z fca6815 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Rewritten executions retain stale authorization, and argument propagation, prompt handling, and context preservation defects remain.

5 open findings
What changed in this PR

Addresses #4255 by improving external-hook context injection and prompt payloads in the agent engine.

Changes:

  • Parses structured hook context and tool-input updates.
  • Extends prompt hooks to user-invoked slash commands.
  • Adds hook configuration reloads and integration coverage.
File Description
packages/​agent-core-v2/​test/​features/​externalHooks/​integration.test.ts Tests slash prompts and tool-hook outputs.
packages/​agent-core-v2/​src/​features/​externalHooks/​session/​sessionExternalHooksService.ts Queues SessionStart context.
packages/​agent-core-v2/​src/​features/​externalHooks/​session/​sessionExternalHooks.ts Exposes queued context retrieval.
packages/​agent-core-v2/​src/​features/​externalHooks/​internal/​userPrompt.ts Extracts messages and input updates.
packages/​agent-core-v2/​src/​features/​externalHooks/​internal/​types.ts Adds updated-input result data.
packages/​agent-core-v2/​src/​features/​externalHooks/​internal/​runHook.ts Parses additional structured output fields.
packages/​agent-core-v2/​src/​features/​externalHooks/​app/​externalHooksRunnerService.ts Reloads hooks after configuration changes.
packages/​agent-core-v2/​src/​features/​externalHooks/​agent/​agentExternalHooksService.ts Integrates context, input updates, and slash prompts.
packages/​agent-core-v2/​src/​agent/​toolExecutor/​toolHooks.ts Extends argument-update contracts.
packages/​agent-core-v2/​src/​agent/​toolExecutor/​toolExecutorService.ts Revalidates and resolves rewritten executions.
packages/​agent-core-v2/​src/​agent/​toolExecutor/​beforeToolExecuteEvent.ts Carries argument-update decisions.
.changeset/​hooks-context-and-prompt-payload.md Records user-facing hook changes.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +432 to +433
try {
execution = await call.tool.resolveExecution(call.args);
signal.throwIfAborted();
const results = await this.runner.trigger('UserPromptSubmit', {
matcherValue: input,
matcherValue: promptText !== undefined ? promptText : input,
Comment on lines +422 to +423
if (decision?.updatedArgs !== undefined) {
const validationError = validateExecutableToolArgs(call.tool, call.args);
sessionId: this.sessionContext.sessionId,
inputData: this.withSessionFacts({ prompt: input, isSteer: ctx.isSteer }),
inputData: this.withSessionFacts({
prompt: promptText ?? input,
Comment on lines +202 to +206
message ??
systemMessage ??
additionalContext ??
hookSpecificOutput?.message ??
hookSpecificOutput?.additionalContext,

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4498f658ae

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +432 to +433
try {
execution = await call.tool.resolveExecution(call.args);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Re-run permission checks for updated tool executions

When a PreToolUse hook changes a path-bearing input (for example, changing Write.path from a workspace file to an absolute outside-workspace or .git path), this new resolveExecution happens only after fireBeforeExecute has already run every permission listener against the original execution.accesses. The updated execution is then scheduled without re-running those policies, so it can execute under an approval or UI description computed for a different target; re-evaluate permissions against the updated execution before accepting the earlier decision.

AGENTS.md reference: AGENTS.md:L122-L124

Useful? React with 👍 / 👎.

Comment on lines +574 to +575
? `/${origin.commandName} ${args}`
: `/${origin.commandName}`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include the plugin namespace in submitted command text

For a user slash plugin command such as /demo:deploy prod, AgentPluginCommandService stores pluginId: "demo" and commandName: "deploy", and promptMetadataTextFromPluginCommand renders the canonical prompt as /demo:deploy prod; this branch instead sends /deploy prod as the matcher and stdin prompt. Hooks matching the namespaced command therefore do not run, while hooks that do run see a command the user never entered, so construct this text from both origin.pluginId and origin.commandName.

AGENTS.md reference: AGENTS.md:L114-L116

Useful? React with 👍 / 👎.

Comment on lines +436 to +438
inputData: this.withSessionFacts({
prompt: promptText ?? input,
isSteer: ctx.isSteer,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep prompt payloads string-valued for media-only submissions

When kap-server accepts an image/video-only or empty-text prompt, turnPromptText returns undefined, so this fallback puts the original ContentPart[] back into stdin even though textual prompts now use a string. A UserPromptSubmit hook written to the new string payload contract will fail or silently allow those valid submissions; use a string fallback such as the empty string instead.

AGENTS.md reference: AGENTS.md:L114-L116

Useful? React with 👍 / 👎.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(agent-core-v2): externalHooks drops hookSpecificOutput.additionalContext and skips UserPromptSubmit on slash skills

2 participants