Skip to content

[plugin sdk] Add generic plugin host-hook contracts#72287

Merged
jalehman merged 30 commits intoopenclaw:mainfrom
electricsheephq:feature/plugin-host-hooks-generic-v2
Apr 28, 2026
Merged

[plugin sdk] Add generic plugin host-hook contracts#72287
jalehman merged 30 commits intoopenclaw:mainfrom
electricsheephq:feature/plugin-host-hooks-generic-v2

Conversation

@100yenadmin
Copy link
Copy Markdown
Contributor

@100yenadmin 100yenadmin commented Apr 26, 2026

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

flowchart LR
  A["#72287 generic host-hook foundation"] --> B["#72383 workflow action/outbound/scheduler/retry seams"]
  B --> C["#72384 executable advanced workflow fixtures"]
  C --> D["#72333 recipes and maintainer docs"]
Loading

Architecture

flowchart TD
  Plugin["Plugin entry"] --> API["OpenClawPluginApi"]

  API --> Registry["Plugin registry"]
  Registry --> Session["Session extensions and pluginPatch"]
  Registry --> Runner["Agent turn preparation"]
  Registry --> Tools["Trusted tool policy and metadata"]
  Registry --> Commands["Scoped commands and continuation"]
  Registry --> UI["Control UI descriptors"]
  Registry --> Runtime["Events, run context, scheduler, lifecycle"]

  Session --> Store["Session store"]
  Store --> Gateway["Gateway session rows"]
  UI --> Gateway
  Tools --> Catalog["Tool catalog and effective inventory"]
  Commands --> Router["Command router"]
  Runner --> Prompt["Prompt build"]
  Runtime --> Cleanup["Host cleanup"]

  Gateway --> Clients["Control UI, mobile, desktop clients"]
  Catalog --> Clients
  Prompt --> Model["Agent model turn"]
Loading

Lifecycle And Cleanup

sequenceDiagram
  participant Plugin
  participant Registry
  participant Store as Session Store
  participant Runtime
  participant Scheduler
  participant Host

  Plugin->>Registry: register state, commands, policy, events, jobs, UI descriptors
  Plugin->>Store: patch plugin session namespace
  Plugin->>Store: enqueue next-turn injection
  Plugin->>Scheduler: register session-owned job
  Runtime->>Runtime: store per-run plugin context

  Host->>Registry: reset, delete, disable, or restart plugin
  Host->>Store: remove plugin session extensions
  Host->>Store: remove pending plugin injections
  Host->>Runtime: clear plugin run context
  Host->>Scheduler: cleanup plugin scheduler jobs
  Host->>Plugin: invoke lifecycle cleanup callbacks
  Scheduler-->>Host: keep retryable records when cleanup fails
Loading

Hook To Plugin Archetypes

flowchart LR
  Approval["Approval workflow"] --> SessionExt["Session extension"]
  Approval --> Injection["Next-turn injection"]
  Approval --> CommandContinue["Command continuation"]
  Approval --> UiDescriptor["UI descriptor"]

  Policy["Budget or workspace policy gate"] --> TrustedPolicy["Trusted tool policy"]
  Policy --> ToolMetadata["Tool metadata"]
  Policy --> SessionProjection["Session projection"]

  Monitor["Background lifecycle monitor"] --> Events["Agent events"]
  Monitor --> RunContext["Run context"]
  Monitor --> Scheduler["Session scheduler job"]
  Monitor --> Heartbeat["Heartbeat contribution"]
  Monitor --> Cleanup["Runtime cleanup"]

  Wizard["Setup wizard"] --> SessionExt
  Wizard --> ScopedCommands["Scoped commands"]
  Wizard --> UiDescriptor

  Review["Review assistant"] --> Events
  Review --> Injection
  Review --> ToolMetadata
  Review --> UiDescriptor
Loading

Generic Hooks And Non-Plan Uses

Every example below is deliberately non-Plan-Mode.

Hook / seam Generic contract Non-Plan plugin examples enabled
api.registerSessionExtension(...) Plugin-owned, JSON-compatible session state with optional synchronous projection into Gateway session rows as pluginExtensions. Review assistant stores per-session review status; incident plugin stores escalation stage; onboarding wizard stores setup progress.
sessions.pluginPatch Gateway patch action for registered plugin session namespaces. Unknown plugin / namespace patches are rejected. Control UI updates deploy approval state; mobile client marks onboarding checklist step complete; CI plugin records selected retry target.
api.enqueueNextTurnInjection(...) Durable, exactly-once next-turn context with placement, TTL, idempotency, chronological cross-plugin drain ordering, and cleanup with plugin-owned state. Approval plugin resumes after a human decision; budget plugin injects a spend-limit summary once; support plugin injects ticket status after webhook receipt.
agent_turn_prepare Turn-preparation hook that receives drained injections and can add same-turn context before ordinary prompt hooks. Release plugin summarizes deployment target; code-review plugin adds requested files after command selection; setup wizard adds next environment check.
heartbeat_prompt_contribution Heartbeat-only prompt contribution for lifecycle/background plugins, separate from user-initiated turns. Incident monitor adds current timers; long-running job plugin adds progress deltas; SLA monitor adds heartbeat-only status.
api.registerTrustedToolPolicy(...) Bundled-only pre-plugin tool policy that runs before normal before_tool_call hooks and can block, require approval, or rewrite params. Enterprise workspace policy blocks writes outside allowed roots; budget guard requires approval for expensive tools; compliance plugin blocks restricted data exports.
api.registerToolMetadata(...) Display and policy metadata for tool catalog and effective inventory, including risk and tags. Security plugin tags shell tools as high-risk; data plugin labels export tools; cloud plugin groups AWS/GCP actions for UI filtering.
api.registerCommand(...) requiredScopes Host-enforced operator scopes on plugin commands invoked through Gateway/internal clients. Deploy command requires release scope; billing command requires admin scope; ticket escalation command requires support-lead scope.
Reserved command ownership Bundled-only command ownership for host-trusted plugins. External plugins cannot claim reserved names. Bundled updater owns /update; bundled setup owns /wizard; bundled compliance plugin owns a reserved audit command.
Command continueAgent: true Command result can request agent continuation after command-side state changes. /deploy approve queues deployment context and resumes; /review retry updates target files and resumes; /incident acknowledge updates state and resumes.
api.registerControlUiDescriptor(...) Data-only Control UI contribution descriptors for session, tool, run, or settings surfaces. No arbitrary browser JavaScript. Review status panel; deployment approval panel; onboarding setup card; incident timeline panel; budget meter.
api.registerAgentEventSubscription(...) Host-owned sanitized event subscriptions with plugin lifecycle ownership and isolation. Metrics exporter observes run lifecycle; review plugin observes tool failures; incident plugin watches long-running jobs and tool events.
setRunContext / getRunContext / clearRunContext Namespaced, JSON-compatible per-run plugin context cleared on terminal run events. Telemetry plugin correlates tool events; test plugin tracks failed test IDs during a run; deployment plugin records selected environment for that run.
api.registerSessionSchedulerJob(...) Plugin-owned session scheduler records with deterministic cleanup on reset, delete, disable, and restart paths. Restart-preserved jobs skip teardown callbacks. Follow-up reminder job; incident nudge job; deployment timeout watcher; stale review reminder.
api.registerRuntimeLifecycle(...) Cleanup callbacks for plugin-owned runtime resources during disable, reset, delete, and restart. Close webhook subscriptions; stop polling timers; release file watchers; flush telemetry buffers.
Host cleanup Removes plugin-owned session extensions, pending next-turn injections, run context, scheduler records, and lifecycle resources without touching other plugins. Disable a review plugin without deleting incident state; reset a setup plugin without clearing budget policy state; restart one monitor without tearing down unrelated jobs.
JSON-compatible validation Plugin-owned host state and descriptors must be plain JSON-compatible values. UI descriptors, projected state, run context, queued metadata, and session state stay serializable across Gateway, Swift clients, and persisted session storage.

What This Implements

  • Plugin session extensions and typed sessions.pluginPatch protocol support.
  • Durable plugin-owned next-turn injections and agent_turn_prepare prompt context.
  • Bundled-only trusted tool policy and plugin tool metadata projection into catalog/effective inventory.
  • Command requiredScopes, bundled reserved ownership, and continueAgent continuation.
  • Safe descriptor-based Control UI contributions through plugins.uiDescriptors.
  • Agent event subscriptions, run-context helpers, session scheduler job records, heartbeat prompt contribution, and cleanup.
  • Generic host-hook-fixture contract coverage for approval workflow, policy gate, and background lifecycle archetypes.
  • Swift protocol regeneration for the additive Gateway protocol surfaces.

RFC Coverage

This implements the generic SDK / host contract for:

Review Hardening Added After The Original Refile

  • Malformed persisted next-turn injection records with non-string text are skipped instead of crashing prompt assembly.
  • Plugin-owned persisted values, descriptor schemas, queued injection metadata, and scheduler/lifecycle callbacks are bounded by JSON-compatible validation.
  • Plugin disable/reset/delete cleanup removes persistent plugin session state and pending next-turn injections for the affected plugin without wiping unrelated plugin state.
  • Terminal run-context cleanup is host-owned so it does not depend on plugin event subscriptions firing.
  • Trusted tool policies are evaluated in order so later safety blocks cannot be bypassed by earlier params/approval decisions.
  • Scheduler cleanup is generation-safe so stale reload cleanup cannot delete newer jobs.
  • Tool metadata catalog/effective inventory keys avoid literal NUL delimiters and collision-prone concatenation.
  • CLI prompt preparation drains queued next-turn injections and applies agent_turn_prepare, including append-context placement.
  • plugin-sdk:api:check baseline was regenerated for the intentional public SDK surface.

Non-Scope

This PR does not add product-specific workflow behavior:

  • no Plan Mode prompts
  • no Plan Mode tools
  • no /plan command text
  • no Plan Mode Control UI cards or CSS
  • no Telegram-specific Plan Mode behavior
  • no Smarter-Claw compatibility shim
  • no local installer patch logic
  • no product-specific scheduler nudge logic

Those 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:list
  • pnpm test src/plugins/contracts/host-hooks.contract.test.ts src/agents/pi-embedded-runner/run/attempt.test.ts
  • pnpm 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.ts
  • pnpm tsgo:all
  • pnpm lint
  • pnpm lint:core
  • pnpm check:import-cycles
  • pnpm plugin-sdk:api:check
  • pnpm build
  • git diff --check upstream/main..HEAD
  • changed-text NUL scan before push

Notes:

  • Local heavy-check lock environment variables were used only when another checkout held the shared local lock; they do not skip assertions.
  • pnpm check:changed should be treated through exact-head CI when local SwiftLint/full-Xcode availability is not equivalent to GitHub CI.
  • Current stack status, mergeability, and exact-head CI are tracked in follow-up PR comments rather than replacing this explanatory body.

Copilot AI review requested due to automatic review settings April 26, 2026 17:21
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation app: macos App: macos app: web-ui App: web-ui gateway Gateway runtime agents Agent runtime and tooling size: XL labels Apr 26, 2026
@100yenadmin
Copy link
Copy Markdown
Contributor Author

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-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 26, 2026

Greptile Summary

This PR adds the generic plugin host-hook contract layer for the OpenClaw plugin SDK: session extensions with pluginPatch Gateway support, durable next-turn injections, trusted pre-plugin tool policies, tool metadata, command scopes with reserved ownership, Control UI descriptors, agent event subscriptions, run-context helpers, session scheduler job records, heartbeat prompt contributions, and lifecycle cleanup. The implementation is well-structured with appropriate validation, isolation, and test coverage across 27 contract tests.

Confidence Score: 4/5

Safe 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)

Comments Outside Diff (1)

  1. src/agents/pi-tools.before-tool-call.ts, line 478 (link)

    P2 Unreachable dead code references stale params variable

    This return at line 478 is now unreachable: all branches inside the try block end with an explicit return (either via the new return { blocked: false, params: policyAdjustedParams } at line 467, or earlier early-returns), and the catch block also returns. The code will never reach line 478.

    More importantly, this dead line still references the original params variable instead of policyAdjustedParams. If a future refactor accidentally makes this code reachable again (e.g. by removing the new explicit return inside the try block), trusted policy param adjustments would be silently dropped and tools would execute with unmodified params despite a policy requesting changes. The dead code should be removed to avoid this latent footgun.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/agents/pi-tools.before-tool-call.ts
    Line: 478
    
    Comment:
    **Unreachable dead code references stale `params` variable**
    
    This `return` at line 478 is now unreachable: all branches inside the `try` block end with an explicit return (either via the new `return { blocked: false, params: policyAdjustedParams }` at line 467, or earlier early-returns), and the `catch` block also returns. The code will never reach line 478.
    
    More importantly, this dead line still references the original `params` variable instead of `policyAdjustedParams`. If a future refactor accidentally makes this code reachable again (e.g. by removing the new explicit return inside the try block), trusted policy param adjustments would be silently dropped and tools would execute with unmodified params despite a policy requesting changes. The dead code should be removed to avoid this latent footgun.
    
    
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/agents/pi-tools.before-tool-call.ts
Line: 478

Comment:
**Unreachable dead code references stale `params` variable**

This `return` at line 478 is now unreachable: all branches inside the `try` block end with an explicit return (either via the new `return { blocked: false, params: policyAdjustedParams }` at line 467, or earlier early-returns), and the `catch` block also returns. The code will never reach line 478.

More importantly, this dead line still references the original `params` variable instead of `policyAdjustedParams`. If a future refactor accidentally makes this code reachable again (e.g. by removing the new explicit return inside the try block), trusted policy param adjustments would be silently dropped and tools would execute with unmodified params despite a policy requesting changes. The dead code should be removed to avoid this latent footgun.

```suggestion
}
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/plugins/host-hook-state.ts
Line: 273-286

Comment:
**Drain silently discards injections from transiently-inactive plugins**

The drain function filters to only return injections from currently-loaded plugins (`activePluginIds.has(pluginId)`), but then unconditionally deletes the entire `pluginNextTurnInjections` map for the session entry, including records for any plugins that were skipped. If a plugin is temporarily unloaded — e.g. during a registry hot-reload between `enqueueNextTurnInjection` and the drain — its queued injections are silently consumed and discarded rather than preserved for the next cycle.

The "exactly-once" contract in the PR description implies intentional consume-and-forget semantics, but the behavior for inactive-plugin injections is not documented. A comment clarifying this is the intended behavior would prevent confusion for future maintainers.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "feat: add generic plugin host hooks" | Re-trigger Greptile

Comment thread src/plugins/host-hook-state.ts
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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

Comment thread src/plugins/runtime.ts
@100yenadmin
Copy link
Copy Markdown
Contributor Author

@steipete @jalehman thanks for feedback on plan mode. 2 weeks of testing and patching + 2 days of hook design has led to significant upgrades that will allow us to become more "plugin" first. I can't wait to see what people build from it.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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/catch around the full before-tool-call path treats failures in runTrustedToolPolicies(...) as a generic "before_tool_call hook failed" and returns BEFORE_TOOL_CALL_HOOK_FAILURE_REASON, which is misleading when the error came from the trusted-policy stage. Consider wrapping trusted policy evaluation in its own try/catch with 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,
    };

Comment thread src/plugins/command-registration.ts
Comment thread src/plugins/registry.ts
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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

  • validatePluginCommandDefinition passes allowReservedCommandNames through to native command alias validation as well. When ownership: "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).

Comment thread src/agents/pi-embedded-runner/run/attempt.prompt-helpers.ts Outdated
Comment thread src/plugins/host-hook-state.ts Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 74 out of 74 changed files in this pull request and generated 2 comments.

Comment thread src/plugins/host-hook-state.ts Outdated
Comment thread src/plugins/host-hooks.ts
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 74 out of 74 changed files in this pull request and generated 5 comments.

Comment thread src/agents/tools-effective-inventory.ts
Comment thread src/plugins/registry.ts
Comment thread src/plugins/trusted-tool-policy.ts
Comment thread src/plugins/registry.ts
Comment thread src/gateway/server-methods/tools-catalog.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling app: macos App: macos app: web-ui App: web-ui docs Improvements or additions to documentation extensions: codex extensions: diagnostics-otel Extension: diagnostics-otel extensions: qa-lab gateway Gateway runtime size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants