[plugin sdk] Add generic plugin host-hook contracts#72287
[plugin sdk] Add generic plugin host-hook contracts#72287jalehman merged 30 commits intoopenclaw:mainfrom
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Successor note for reviewers: this PR supersedes #72082, which was closed only to reset the review surface after noisy iterative automated review. This branch is a single-commit refile from current upstream/main and is tree-identical to the final #72082 implementation state; the PR body now includes maintainer-facing diagrams and a per-hook matrix showing non-Plan-Mode plugin use cases. |
Greptile SummaryThis PR adds the generic plugin host-hook contract layer for the OpenClaw plugin SDK: session extensions with Confidence Score: 4/5Safe to merge; findings are non-blocking style/cleanup concerns with no runtime impact on correct paths. All findings are P2. The dead code in runBeforeToolCallHook is unreachable today but represents a latent footgun if future edits accidentally restore the path. Core contract logic, cleanup sequencing, trusted-policy evaluation order, and scope enforcement all look correct. Contract test coverage is thorough. src/agents/pi-tools.before-tool-call.ts (dead code at line 478), src/plugins/host-hook-state.ts (silent discard of inactive-plugin injections on drain)
|
There was a problem hiding this comment.
Pull request overview
This PR introduces a generic “host-hook” contract surface so workflow-style plugins can integrate with host lifecycle, session state, prompt construction, gateway APIs, tool policy/metadata, UI descriptors, events, scheduler ownership, and cleanup—without adding product-specific (Plan Mode) core code.
Changes:
- Add plugin host-hook SDK types/APIs (session extensions, next-turn injections, trusted tool policy, tool metadata, UI descriptors, run context, scheduler jobs, lifecycle cleanup, agent event subscriptions).
- Extend host + gateway integration (new
sessions.pluginPatch,plugins.uiDescriptors, session-row projection, tool catalog/effective inventory metadata projection). - Wire new runtime behavior and contract tests (agent turn prepare + heartbeat prompt contribution hooks, trusted policy stage before
before_tool_call, cleanup paths).
Reviewed changes
Copilot reviewed 71 out of 71 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| test/scripts/lint-suppressions.test.ts | Add lint suppression entries |
| test/helpers/plugins/plugin-api.ts | Stub new plugin API methods |
| src/wizard/setup.plugin-config.test.ts | Mock plugin-registry dependency |
| src/plugins/update.test.ts | Reset modules before dynamic import |
| src/plugins/types.ts | Export host-hook types; extend commands |
| src/plugins/trusted-tool-policy.ts | Add trusted tool policy runner |
| src/plugins/synthetic-auth.runtime.test.ts | Fix typed snapshot fixture |
| src/plugins/status.ts | Include syntheticAuthRefs in status |
| src/plugins/status.test-helpers.ts | Extend test plugin load result |
| src/plugins/runtime.ts | Bridge agent events; trigger cleanup |
| src/plugins/registry.ts | Register host-hook contracts in registry |
| src/plugins/registry-types.ts | Add registry types for host hooks |
| src/plugins/registry-empty.ts | Initialize empty host-hook registries |
| src/plugins/public-surface-loader.test.ts | Reset modules for fresh imports |
| src/plugins/provider-auth-choices.test.ts | Refactor mocks + dynamic imports |
| src/plugins/loader.ts | Plumb syntheticAuthRefs into records |
| src/plugins/installed-plugin-index.ts | Persist syntheticAuthRefs reliably |
| src/plugins/install.npm-spec.test.ts | Use dynamic import after resetModules |
| src/plugins/host-hooks.ts | Define host-hook contract types/helpers |
| src/plugins/host-hook-turn-types.ts | Define turn/injection hook payload types |
| src/plugins/host-hook-state.ts | Implement injection queue + session extensions |
| src/plugins/host-hook-runtime.ts | Implement run context + scheduler ownership |
| src/plugins/host-hook-json.ts | Add JSON-compat type guard |
| src/plugins/host-hook-cleanup.ts | Implement host-hook cleanup orchestrator |
| src/plugins/hooks.ts | Add runner support for new hooks |
| src/plugins/hook-types.ts | Add new hook names and handler types |
| src/plugins/hook-before-agent-start.types.ts | Add appendContext support |
| src/plugins/contracts/host-hooks.contract.test.ts | Add comprehensive contract coverage |
| src/plugins/contracts/host-hook-fixture.ts | Add generic fixture plugin registrations |
| src/plugins/commands.ts | Enforce requiredScopes for gateway clients |
| src/plugins/command-registration.ts | Allow bundled reserved command ownership |
| src/plugins/channel-plugin-ids.test.ts | Expand manifest/registry mocking |
| src/plugins/captured-registration.ts | Capture new host-hook registrations |
| src/plugins/bundled-runtime-deps.test.ts | Hoist spawnSync mock; dynamic import |
| src/plugins/api-builder.ts | Add no-op implementations for new API |
| src/plugin-sdk/plugin-entry.ts | Re-export host-hook SDK types |
| src/plugin-sdk/core.ts | Re-export host-hook SDK types |
| src/gateway/test-helpers.plugin-registry.ts | Extend stub registry with host-hook fields |
| src/gateway/session-utils.types.ts | Add pluginExtensions to session row type |
| src/gateway/session-utils.ts | Project plugin extensions into session rows |
| src/gateway/session-reset-service.ts | Invoke plugin host cleanup on reset/delete |
| src/gateway/server-methods/tools-catalog.ts | Project tool metadata into catalog output |
| src/gateway/server-methods/sessions.ts | Add sessions.pluginPatch handler |
| src/gateway/server-methods/plugin-host-hooks.ts | Add plugins.uiDescriptors handler |
| src/gateway/server-methods.ts | Register plugin host-hook gateway handlers |
| src/gateway/server-methods-list.ts | Expose new gateway methods |
| src/gateway/protocol/schema/types.ts | Add schema types for new methods |
| src/gateway/protocol/schema/sessions.ts | Add schemas for sessions.pluginPatch |
| src/gateway/protocol/schema/protocol-schemas.ts | Register new protocol schemas |
| src/gateway/protocol/schema/plugins.ts | Add schemas for UI descriptors + JSON value |
| src/gateway/protocol/schema/agents-models-skills.ts | Add risk/tags fields to tool schemas |
| src/gateway/protocol/schema.ts | Export new plugin schemas |
| src/gateway/protocol/index.ts | Compile validators for new schemas |
| src/gateway/method-scopes.ts | Scope-map new gateway methods |
| src/config/sessions/types.ts | Add persisted plugin extensions + injections |
| src/auto-reply/reply/commands-plugin.ts | Support continueAgent command result |
| src/auto-reply/reply/commands-plugin.test.ts | Test continuation + reply sanitization |
| src/agents/tools-effective-inventory.types.ts | Add risk/tags to effective inventory types |
| src/agents/tools-effective-inventory.ts | Project tool metadata into effective inventory |
| src/agents/tools-effective-inventory.test.ts | Add coverage for metadata projection |
| src/agents/pi-tools.before-tool-call.ts | Run trusted policies before plugin hooks |
| src/agents/pi-tools.before-tool-call.embedded-mode.test.ts | Add trusted-policy approval test coverage |
| src/agents/pi-tool-definition-adapter.ts | Normalize params record before hook |
| src/agents/pi-embedded-runner/run/attempt.ts | Append appendContext into prompt |
| src/agents/pi-embedded-runner/run/attempt.test.ts | Expand prompt hook result expectations |
| src/agents/pi-embedded-runner/run/attempt.prompt-helpers.ts | Drain injections + run new hooks |
| docs/plugins/sdk-overview.md | Document new host-hook SDK surface |
| docs/plugins/hooks.md | Document new hooks + workflows |
| docs/.generated/plugin-sdk-api-baseline.sha256 | Update SDK API baseline hashes |
| apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift | Regenerate Swift models for new protocol |
| apps/macos/Sources/OpenClawProtocol/GatewayModels.swift | Regenerate Swift models for new protocol |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 71 out of 71 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
src/agents/pi-tools.before-tool-call.ts:475
- The
try/catcharound the full before-tool-call path treats failures inrunTrustedToolPolicies(...)as a generic "before_tool_call hook failed" and returnsBEFORE_TOOL_CALL_HOOK_FAILURE_REASON, which is misleading when the error came from the trusted-policy stage. Consider wrapping trusted policy evaluation in its owntry/catchwith a distinct log message / block reason (or adjusting the shared message) so operators can tell whether the failure was in trusted policy vs normal plugin hooks.
const trustedPolicyResult = await runTrustedToolPolicies(
{
toolName,
params: normalizedParams,
...(args.ctx?.runId && { runId: args.ctx.runId }),
...(args.toolCallId && { toolCallId: args.toolCallId }),
},
toolContext,
);
if (trustedPolicyResult?.block) {
return {
blocked: true,
reason: trustedPolicyResult.blockReason || "Tool call blocked by trusted plugin policy",
};
}
if (trustedPolicyResult?.requireApproval) {
return await requestPluginToolApproval({
approval: trustedPolicyResult.requireApproval,
toolName,
toolCallId: args.toolCallId,
ctx: args.ctx,
signal: args.signal,
baseParams: normalizedParams,
overrideParams: trustedPolicyResult.params,
});
}
const policyAdjustedParams: Record<string, unknown> =
trustedPolicyResult?.params ?? normalizedParams;
if (!hookRunner?.hasHooks("before_tool_call")) {
return { blocked: false, params: policyAdjustedParams };
}
const hookResult = await hookRunner.runBeforeToolCall(
{
toolName,
params: policyAdjustedParams,
...(args.ctx?.runId && { runId: args.ctx.runId }),
...(args.toolCallId && { toolCallId: args.toolCallId }),
},
toolContext,
);
if (hookResult?.block) {
return {
blocked: true,
reason: hookResult.blockReason || "Tool call blocked by plugin hook",
};
}
if (hookResult?.requireApproval) {
return await requestPluginToolApproval({
approval: hookResult.requireApproval,
toolName,
toolCallId: args.toolCallId,
ctx: args.ctx,
signal: args.signal,
baseParams: policyAdjustedParams,
overrideParams: hookResult.params,
});
}
if (hookResult?.params) {
return {
blocked: false,
params: mergeParamsWithApprovalOverrides(policyAdjustedParams, hookResult.params),
};
}
return { blocked: false, params: policyAdjustedParams };
} catch (err) {
const toolCallId = args.toolCallId ? ` toolCallId=${args.toolCallId}` : "";
const cause = unwrapErrorCause(err);
log.error(`before_tool_call hook failed: tool=${toolName}${toolCallId} error=${String(cause)}`);
return {
blocked: true,
reason: BEFORE_TOOL_CALL_HOOK_FAILURE_REASON,
};
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 71 out of 71 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
src/plugins/command-registration.ts:121
validatePluginCommandDefinitionpassesallowReservedCommandNamesthrough to native command alias validation as well. Whenownership: "reserved"is set, this would allow a plugin to register a reserved built-in name (e.g. "help") as a native alias, potentially shadowing core commands unintentionally. If reserved ownership is intended only to permit the primary command name override, keep alias validation strict (or gate reserved aliases behind a separate explicit option).
Summary
This PR supersedes #72082 with a clean review surface for the generic plugin host-hook implementation proposed by RFC #71731.
It adds SDK, host, Gateway, runner, policy, UI-descriptor, scheduler, event, run-context, and cleanup seams that workflow plugins can use without patching core. #71676 remains only a parity oracle for host entry-point classes; this PR intentionally contains no Plan Mode product code.
Current update: the original long-form maintainer description has been restored after a later stack-status edit accidentally compressed it too far. The branch is no longer described as a single-commit/tree-identical refile; review should use the current head and exact-head CI state.
Related:
Expansion note: This PR is actually medium but expanded during an approximately 10-hour final review/hardening pass by GPT 5.5 XHIGH due to it's nature as Plugin SDK. That expansion was necessary to make the generic host-hook surface production-safe rather than just API-complete.
Maintainer Decision
Review this as the focused generic host-hook contract PR. If accepted, bundled workflow plugins should be able to implement approval flows, policy gates, background monitors, setup wizards, review assistants, release workflows, and similar host-integrated behavior through SDK contracts rather than bespoke core patches.
The success bar is not "Plan Mode works here." The success bar is: after this lands, Plan Mode and other host-integrated workflow plugins have the durable state, command, policy, UI, scheduler, event, prompt, metadata, and cleanup seams they need without adding product-specific core code.
Stack Position
Architecture
Lifecycle And Cleanup
Hook To Plugin Archetypes
Generic Hooks And Non-Plan Uses
Every example below is deliberately non-Plan-Mode.
api.registerSessionExtension(...)pluginExtensions.sessions.pluginPatchapi.enqueueNextTurnInjection(...)agent_turn_prepareheartbeat_prompt_contributionapi.registerTrustedToolPolicy(...)before_tool_callhooks and can block, require approval, or rewrite params.api.registerToolMetadata(...)api.registerCommand(...)requiredScopes/update; bundled setup owns/wizard; bundled compliance plugin owns a reserved audit command.continueAgent: true/deploy approvequeues deployment context and resumes;/review retryupdates target files and resumes;/incident acknowledgeupdates state and resumes.api.registerControlUiDescriptor(...)api.registerAgentEventSubscription(...)setRunContext/getRunContext/clearRunContextapi.registerSessionSchedulerJob(...)api.registerRuntimeLifecycle(...)What This Implements
sessions.pluginPatchprotocol support.agent_turn_prepareprompt context.requiredScopes, bundled reserved ownership, andcontinueAgentcontinuation.plugins.uiDescriptors.host-hook-fixturecontract coverage for approval workflow, policy gate, and background lifecycle archetypes.RFC Coverage
This implements the generic SDK / host contract for:
sessions.pluginPatch, projection, and cleanup.agent_turn_prepare.continueAgent.Review Hardening Added After The Original Refile
agent_turn_prepare, including append-context placement.plugin-sdk:api:checkbaseline was regenerated for the intentional public SDK surface.Non-Scope
This PR does not add product-specific workflow behavior:
/plancommand textThose belong in bundled plugin implementation work after the generic host hooks land.
Validation
Validation performed across the restored/rebased branch and later hardening passes:
pnpm docs:listpnpm test src/plugins/contracts/host-hooks.contract.test.ts src/agents/pi-embedded-runner/run/attempt.test.tspnpm test src/plugins/contracts/host-hooks.contract.test.ts src/agents/pi-tools.before-tool-call.embedded-mode.test.ts src/auto-reply/reply/commands-plugin.test.tspnpm tsgo:allpnpm lintpnpm lint:corepnpm check:import-cyclespnpm plugin-sdk:api:checkpnpm buildgit diff --check upstream/main..HEADNotes:
pnpm check:changedshould be treated through exact-head CI when local SwiftLint/full-Xcode availability is not equivalent to GitHub CI.