refactor(plugins): make lifecycle hook timing explicit

Use one registration method and subject.operation.step names so plugin authors can read whether an operation waits. Preserve old registrations through a single SDK adapter and keep timing and payloads unchanged.
This commit is contained in:
Mohamed Boudra
2026-10-28 04:47:50 +01:00
parent 2c23cd4927
commit f28e0e072c
41 changed files with 1864 additions and 1270 deletions
@@ -1,15 +1,15 @@
# Agent configuration
Sets saved Codex options through `server.before("agent.create", ...)`:
Sets saved Codex options through `server.addHook("agent.create.prepare", async ...)`:
- Sets `config.providerOptions.sandbox_mode` to `workspace-write`.
- Sets `config.providerOptions.approval_policy` to `on-request`.
- Preserves other configuration and provider options.
Adds the `company` MCP server to Codex launches through
`server.before("agent.session_open", ...)`. The server is never saved in the agent's configuration;
`server.addHook("agent.open.prepare", async ...)`. The server is never saved in the agent's configuration;
existing saved MCP servers are preserved. Existing agents receive the server through the
[session reopen behavior](../../public-docs/plugins/reference.md#attach-tools-to-agents).
[session reopen behavior](../../public-docs/plugins/hooks.md#attach-tools-to-agents).
Replace `https://tools.example.com/mcp` in `index.server.ts` with your MCP server URL before using
this example. A `company` name collision with a saved server or another
@@ -26,7 +26,7 @@ entries added by this plugin's earlier callbacks. The create callback receives c
earlier plugins have transformed it; its explicit Codex options replace the named fields in the
saved configuration of new agents and remain after this plugin is disabled.
See [Inject MCP servers](../../public-docs/plugins/reference.md#inject-mcp-servers) for choosing
See [Inject MCP servers](../../public-docs/plugins/hooks.md#inject-mcp-servers) for choosing
between saved configuration and plugin-owned servers. [Lifecycle actions](../lifecycle-actions)
demonstrates provider switching and environment injection. Its provider-switch callback clears
provider options when changing Codex to Claude, so use these examples separately when trying the
@@ -1,7 +1,7 @@
import type { PluginServerContext } from "@getpaseo/plugin/server";
export default function contribute(server: PluginServerContext) {
server.before("agent.create", ({ request }) => {
server.addHook("agent.create.prepare", async ({ request }) => {
if (request.config.provider !== "codex") {
return request;
}
@@ -19,7 +19,7 @@ export default function contribute(server: PluginServerContext) {
};
});
server.before("agent.session_open", ({ request }) => {
server.addHook("agent.open.prepare", async ({ request }) => {
if (request.provider !== "codex") {
return;
}
@@ -1,6 +1,6 @@
{
"id": "agent-configuration",
"requirements": {
"paseo": ">=0.13.2"
"paseo": ">=0.14.0-beta.1"
}
}
+1 -1
View File
@@ -29,5 +29,5 @@ From the repository root, `npm run typecheck` includes this example. Outside thi
use `paseo plugin init` to create the package and TypeScript setup, then copy `index.server.ts`
and the manifest into that project.
See [Attach tools to agents](../../public-docs/plugins/reference.md#attach-tools-to-agents)
See [Attach tools to agents](../../public-docs/plugins/hooks.md#attach-tools-to-agents)
for targeting, execution, provider gates, collisions, and plugin reload behavior.
+1 -1
View File
@@ -18,7 +18,7 @@ const appendWorkspaceNote = defineAgentTool({
});
export default function contribute(server: PluginServerContext) {
server.before("agent.session_open", ({ request }) => {
server.addHook("agent.open.prepare", async ({ request }) => {
if (request.labels["notes/enabled"] !== "true") return;
return { ...request, tools: [...request.tools, appendWorkspaceNote] };
});
@@ -1,4 +1,4 @@
{
"id": "agent-tools",
"requirements": { "paseo": ">=0.13.2" }
"requirements": { "paseo": ">=0.14.0-beta.1" }
}
@@ -2,7 +2,7 @@ import type { PluginServerContext } from "@getpaseo/plugin/server";
import { latestOutputText, shellCommand } from "./server/inspect";
export default function contribute(server: PluginServerContext) {
server.before("workspace.create", ({ request }) => {
server.addHook("workspace.create.prepare", async ({ request }) => {
if (request.title === "Isolated lifecycle example" && request.source.kind === "directory") {
return {
...request,
@@ -17,7 +17,7 @@ export default function contribute(server: PluginServerContext) {
return request;
});
server.on("agent.turn_ended", async (event, context) => {
server.addHook("agent.turn.completed", async (event, context) => {
if (event.outcome.kind === "canceled") {
return;
}
@@ -30,7 +30,7 @@ export default function contribute(server: PluginServerContext) {
}
});
server.on("agent.permission_requested", async (event, context) => {
server.addHook("agent.permission.started", async (event, context) => {
const command = shellCommand(event.request);
if (command === null) {
return;
@@ -56,7 +56,7 @@ export default function contribute(server: PluginServerContext) {
}
});
server.before("agent.create", ({ request }) => {
server.addHook("agent.create.prepare", async ({ request }) => {
return {
...request,
env: {
@@ -66,7 +66,7 @@ export default function contribute(server: PluginServerContext) {
};
});
server.before("agent.create", ({ request }) => {
server.addHook("agent.create.prepare", async ({ request }) => {
if (request.config.provider === "codex") {
return {
...request,
@@ -82,7 +82,7 @@ export default function contribute(server: PluginServerContext) {
return request;
});
server.before("agent.session_open", ({ request }) => {
server.addHook("agent.open.prepare", async ({ request }) => {
return {
...request,
env: {
@@ -1,6 +1,6 @@
{
"id": "lifecycle-actions",
"requirements": {
"paseo": ">=0.8.0"
"paseo": ">=0.14.0-beta.1"
}
}
@@ -2,55 +2,55 @@ import type { PluginServerContext } from "@getpaseo/plugin/server";
import { logHook } from "./server/log";
export default function contribute(server: PluginServerContext) {
server.before("workspace.create", ({ request }) => {
logHook("before workspace.create", { request });
server.addHook("workspace.create.prepare", async ({ request }) => {
logHook("workspace.create.prepare", { request });
return request;
});
server.on("workspace.created", (event) => {
logHook("workspace.created", event);
server.addHook("workspace.create.completed", async (event) => {
logHook("workspace.create.completed", event);
});
server.on("workspace.archived", (event) => {
logHook("workspace.archived", event);
server.addHook("workspace.archive.completed", async (event) => {
logHook("workspace.archive.completed", event);
});
server.before("agent.create", ({ request }) => {
logHook("before agent.create", { request });
server.addHook("agent.create.prepare", async ({ request }) => {
logHook("agent.create.prepare", { request });
return request;
});
server.before("agent.session_open", ({ request }) => {
logHook("before agent.session_open", { request });
server.addHook("agent.open.prepare", async ({ request }) => {
logHook("agent.open.prepare", { request });
return request;
});
server.on("agent.created", (event) => {
logHook("agent.created", event);
server.addHook("agent.create.completed", async (event) => {
logHook("agent.create.completed", event);
});
server.on("agent.closed", (event) => {
logHook("agent.closed", event);
server.addHook("agent.close.completed", async (event) => {
logHook("agent.close.completed", event);
});
server.on("agent.turn_started", (event) => {
logHook("agent.turn_started", event);
server.addHook("agent.turn.started", async (event) => {
logHook("agent.turn.started", event);
});
server.on("agent.turn_ended", (event) => {
logHook("agent.turn_ended", event);
server.addHook("agent.turn.completed", async (event) => {
logHook("agent.turn.completed", event);
});
server.on("agent.permission_requested", (event) => {
logHook("agent.permission_requested", event);
server.addHook("agent.permission.started", async (event) => {
logHook("agent.permission.started", event);
});
server.on("agent.permission_resolved", (event) => {
logHook("agent.permission_resolved", event);
server.addHook("agent.permission.completed", async (event) => {
logHook("agent.permission.completed", event);
});
server.on("agent.archived", (event) => {
logHook("agent.archived", event);
server.addHook("agent.archive.completed", async (event) => {
logHook("agent.archive.completed", event);
});
return () => {};
@@ -1,6 +1,6 @@
{
"id": "lifecycle-logger",
"requirements": {
"paseo": ">=0.8.0"
"paseo": ">=0.14.0-beta.1"
}
}