Skip to content

nodejs: Expose onAgentStop session hook#2054

Draft
belaltaher8 wants to merge 1 commit into
github:mainfrom
belaltaher8:bt/sdk-on-agent-stop
Draft

nodejs: Expose onAgentStop session hook#2054
belaltaher8 wants to merge 1 commit into
github:mainfrom
belaltaher8:bt/sdk-on-agent-stop

Conversation

@belaltaher8

Copy link
Copy Markdown
Contributor

Summary

Exposes the top-level agent's agentStop lifecycle hook on the Node SDK.

The Copilot CLI runtime already fires the main agent's agentStop hook when the agent reaches a natural terminal stop, and already registers agentStop for SDK callback sessions (REGISTERED_CALLBACK_EVENT_NAMES) — including a working block/continue re-prompt loop (a {decision:"block", reason} result enqueues reason as a follow-up message; consecutive blocks are capped to prevent runaway loops).

The only missing piece was on the SDK side: SessionHooks had no onAgentStop, and _handleHooksInvoke had no dispatch entry — so agentStop invocations from the runtime were silently dropped for SDK clients.

Changes (nodejs)

  • src/types.ts: add AgentStopHookInput, AgentStopHookOutput ({ decision?: "block"; reason?: string }), AgentStopHandler, and the onAgentStop field on SessionHooks.
  • src/session.ts: add the agentStop entry to the _handleHooksInvoke handler map.
  • test/client.test.ts: dispatcher unit tests — direct dispatch returning a block decision, and the full hooks.invoke JSON-RPC wire path.

Motivation

This unblocks the Copilot cloud coding agent (CCA v3) restoring its post-completion security-tool hooks (Dependabot advisory checker, secret scanning) on the proper platform hook — instead of the ad-hoc per-commit report_progress hooks that accumulated repeated "missed commit boundary" review feedback. onAgentStop fires once at the natural end of the run and supports remediation via block/continue.

Notes / follow-ups

  • Wire field mapping: the runtime currently emits the Claude-compatible stop_hook_active (snake_case) in the agentStop input. This PR's AgentStopHookInput.stopHookActive documents that field; normalizing snake→camel for this one hook (or emitting camelCase runtime-side) is a small follow-up. The stopReason/transcriptPath fields and the {decision, reason} output — the load-bearing parts — are correct today.
  • Other languages: parallel onAgentStop additions for the Python / Go / .NET SDKs are follow-ups (this draft covers Node.js, which CCA v3 uses).

Draft (fork PR) for review/discussion.

Related: github/copilot-agent-runtime#13230 (issue is being corrected — the runtime-side agentStop gap it describes does not exist).

The runtime already fires the top-level agent's `agentStop` hook and
registers it for SDK callback sessions (REGISTERED_CALLBACK_EVENT_NAMES),
with a working block/continue re-prompt loop, but the Node SDK never
exposed it: SessionHooks had no `onAgentStop` and `_handleHooksInvoke`
had no dispatch entry, so `agentStop` callbacks were silently dropped.

Add the AgentStopHookInput/Output/Handler types, the `onAgentStop` field
on SessionHooks, and the `agentStop` entry in the hook dispatcher.
Returning `{ decision: "block", reason }` keeps the agent running with
`reason` enqueued as a follow-up message (e.g. to remediate findings a
handler surfaced); returning nothing lets the agent stop.

This unblocks the Copilot cloud agent restoring its post-completion
security-tool hooks (dependabot / secret scanning) on the proper platform
hook rather than ad-hoc per-commit hooks.

Note: parallel changes for the Python/Go/.NET SDKs are follow-ups.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2031e02b-7fe5-4075-9d75-eb20eb29f407
Copilot AI review requested due to automatic review settings July 22, 2026 16:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Exposes the Node.js SDK’s top-level agentStop lifecycle hook.

Changes:

  • Adds typed agent-stop input, output, and handler APIs.
  • Routes agentStop callbacks through the session dispatcher.
  • Adds dispatcher and JSON-RPC unit tests.
Show a summary per file
File Description
nodejs/src/types.ts Defines the agent-stop hook API.
nodejs/src/session.ts Dispatches agent-stop callbacks.
nodejs/test/client.test.ts Tests direct and JSON-RPC dispatch.

Review details

  • Files reviewed: 3/3 changed files
  • Comments generated: 3
  • Review effort level: Medium

Comment thread nodejs/src/session.ts
sessionStart: this.hooks.onSessionStart as GenericHandler | undefined,
sessionEnd: this.hooks.onSessionEnd as GenericHandler | undefined,
errorOccurred: this.hooks.onErrorOccurred as GenericHandler | undefined,
agentStop: this.hooks.onAgentStop as GenericHandler | undefined,
Comment thread nodejs/src/types.ts
Comment on lines +1516 to +1519
export type AgentStopHandler = (
input: AgentStopHookInput,
invocation: { sessionId: string }
) => Promise<AgentStopHookOutput | void> | AgentStopHookOutput | void;
Comment thread nodejs/src/types.ts
Comment on lines +1575 to +1580
* `{ decision: "block", reason }` to keep the agent running with `reason`
* enqueued as a follow-up message — for example, to have the agent
* remediate findings the handler surfaced. Returning nothing lets the
* agent stop.
*/
onAgentStop?: AgentStopHandler;
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.

2 participants