Merge pull request #1235 from dougvk/feat/tool-dispatch-skill-commands
Plugin API: tool-dispatched skill commands + tool_result_persist hook
This commit is contained in:
@@ -30,6 +30,9 @@ import type {
|
||||
PluginHookSessionEndEvent,
|
||||
PluginHookSessionStartEvent,
|
||||
PluginHookToolContext,
|
||||
PluginHookToolResultPersistContext,
|
||||
PluginHookToolResultPersistEvent,
|
||||
PluginHookToolResultPersistResult,
|
||||
} from "./types.js";
|
||||
|
||||
// Re-export types for consumers
|
||||
@@ -49,6 +52,9 @@ export type {
|
||||
PluginHookBeforeToolCallEvent,
|
||||
PluginHookBeforeToolCallResult,
|
||||
PluginHookAfterToolCallEvent,
|
||||
PluginHookToolResultPersistContext,
|
||||
PluginHookToolResultPersistEvent,
|
||||
PluginHookToolResultPersistResult,
|
||||
PluginHookSessionContext,
|
||||
PluginHookSessionStartEvent,
|
||||
PluginHookSessionEndEvent,
|
||||
@@ -302,6 +308,59 @@ export function createHookRunner(registry: PluginRegistry, options: HookRunnerOp
|
||||
return runVoidHook("after_tool_call", event, ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run tool_result_persist hook.
|
||||
*
|
||||
* This hook is intentionally synchronous: it runs in hot paths where session
|
||||
* transcripts are appended synchronously.
|
||||
*
|
||||
* Handlers are executed sequentially in priority order (higher first). Each
|
||||
* handler may return `{ message }` to replace the message passed to the next
|
||||
* handler.
|
||||
*/
|
||||
function runToolResultPersist(
|
||||
event: PluginHookToolResultPersistEvent,
|
||||
ctx: PluginHookToolResultPersistContext,
|
||||
): PluginHookToolResultPersistResult | undefined {
|
||||
const hooks = getHooksForName(registry, "tool_result_persist");
|
||||
if (hooks.length === 0) return undefined;
|
||||
|
||||
let current = event.message;
|
||||
|
||||
for (const hook of hooks) {
|
||||
try {
|
||||
const out = (hook.handler as any)({ ...event, message: current }, ctx) as
|
||||
| PluginHookToolResultPersistResult
|
||||
| void
|
||||
| Promise<unknown>;
|
||||
|
||||
// Guard against accidental async handlers (this hook is sync-only).
|
||||
if (out && typeof (out as any).then === "function") {
|
||||
const msg =
|
||||
`[hooks] tool_result_persist handler from ${hook.pluginId} returned a Promise; ` +
|
||||
`this hook is synchronous and the result was ignored.`;
|
||||
if (catchErrors) {
|
||||
logger?.warn?.(msg);
|
||||
continue;
|
||||
}
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
const next = (out as PluginHookToolResultPersistResult | undefined)?.message;
|
||||
if (next) current = next;
|
||||
} catch (err) {
|
||||
const msg = `[hooks] tool_result_persist handler from ${hook.pluginId} failed: ${String(err)}`;
|
||||
if (catchErrors) {
|
||||
logger?.error(msg);
|
||||
} else {
|
||||
throw new Error(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { message: current };
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Session Hooks
|
||||
// =========================================================================
|
||||
@@ -385,6 +444,7 @@ export function createHookRunner(registry: PluginRegistry, options: HookRunnerOp
|
||||
// Tool hooks
|
||||
runBeforeToolCall,
|
||||
runAfterToolCall,
|
||||
runToolResultPersist,
|
||||
// Session hooks
|
||||
runSessionStart,
|
||||
runSessionEnd,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||
import type { Command } from "commander";
|
||||
|
||||
import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
||||
|
||||
import type { AuthProfileCredential, OAuthCredential } from "../agents/auth-profiles/types.js";
|
||||
import type { AnyAgentTool } from "../agents/tools/common.js";
|
||||
import type { ChannelDock } from "../channels/dock.js";
|
||||
@@ -231,6 +233,7 @@ export type PluginHookName =
|
||||
| "message_sent"
|
||||
| "before_tool_call"
|
||||
| "after_tool_call"
|
||||
| "tool_result_persist"
|
||||
| "session_start"
|
||||
| "session_end"
|
||||
| "gateway_start"
|
||||
@@ -338,6 +341,30 @@ export type PluginHookAfterToolCallEvent = {
|
||||
durationMs?: number;
|
||||
};
|
||||
|
||||
// tool_result_persist hook
|
||||
export type PluginHookToolResultPersistContext = {
|
||||
agentId?: string;
|
||||
sessionKey?: string;
|
||||
toolName?: string;
|
||||
toolCallId?: string;
|
||||
};
|
||||
|
||||
export type PluginHookToolResultPersistEvent = {
|
||||
toolName?: string;
|
||||
toolCallId?: string;
|
||||
/**
|
||||
* The toolResult message about to be written to the session transcript.
|
||||
* Handlers may return a modified message (e.g. drop non-essential fields).
|
||||
*/
|
||||
message: AgentMessage;
|
||||
/** True when the tool result was synthesized by a guard/repair step. */
|
||||
isSynthetic?: boolean;
|
||||
};
|
||||
|
||||
export type PluginHookToolResultPersistResult = {
|
||||
message?: AgentMessage;
|
||||
};
|
||||
|
||||
// Session context
|
||||
export type PluginHookSessionContext = {
|
||||
agentId?: string;
|
||||
@@ -407,6 +434,10 @@ export type PluginHookHandlerMap = {
|
||||
event: PluginHookAfterToolCallEvent,
|
||||
ctx: PluginHookToolContext,
|
||||
) => Promise<void> | void;
|
||||
tool_result_persist: (
|
||||
event: PluginHookToolResultPersistEvent,
|
||||
ctx: PluginHookToolResultPersistContext,
|
||||
) => PluginHookToolResultPersistResult | void;
|
||||
session_start: (
|
||||
event: PluginHookSessionStartEvent,
|
||||
ctx: PluginHookSessionContext,
|
||||
|
||||
Reference in New Issue
Block a user