paperclip/server/src/services/plugin-tool-dispatcher.ts

452 lines
15 KiB
TypeScript
Raw Normal View History

2026-03-13 16:22:34 -05:00
/**
* PluginToolDispatcher orchestrates plugin tool discovery, lifecycle
* integration, and execution routing for the agent service.
*
* This service sits between the agent service and the lower-level
* `PluginToolRegistry` + `PluginWorkerManager`, providing a clean API that:
*
* - Discovers tools from loaded plugin manifests and registers them
* in the tool registry.
* - Hooks into `PluginLifecycleManager` events to automatically register
* and unregister tools when plugins are enabled or disabled.
* - Exposes the tool list in an agent-friendly format (with namespaced
* names, descriptions, parameter schemas).
* - Routes `executeTool` calls to the correct plugin worker and returns
* structured results.
* - Validates tool parameters against declared schemas before dispatch.
*
* The dispatcher is created once at server startup and shared across
* the application.
*
* @see PLUGIN_SPEC.md §11 Agent Tools
* @see PLUGIN_SPEC.md §13.10 `executeTool`
*/
import type { Db } from "@paperclipai/db";
import type {
PaperclipPluginManifestV1,
PluginRecord,
} from "@paperclipai/shared";
import type { ToolRunContext, ToolResult } from "@paperclipai/plugin-sdk";
import type { PluginWorkerManager } from "./plugin-worker-manager.js";
import type { PluginLifecycleManager } from "./plugin-lifecycle.js";
import {
createPluginToolRegistry,
type PluginToolRegistry,
type RegisteredTool,
type ToolListFilter,
type ToolExecutionResult,
} from "./plugin-tool-registry.js";
import { pluginRegistryService } from "./plugin-registry.js";
import { logger } from "../middleware/logger.js";
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
/**
* An agent-facing tool descriptor the shape returned when agents
* query for available tools.
*
* This is intentionally simpler than `RegisteredTool`, exposing only
* what agents need to decide whether and how to call a tool.
*/
export interface AgentToolDescriptor {
/** Fully namespaced tool name (e.g. `"acme.linear:search-issues"`). */
name: string;
/** Human-readable display name. */
displayName: string;
/** Description for the agent — explains when and how to use this tool. */
description: string;
/** JSON Schema describing the tool's input parameters. */
parametersSchema: Record<string, unknown>;
/** The plugin that provides this tool. */
pluginId: string;
}
/**
* Options for creating the plugin tool dispatcher.
*/
export interface PluginToolDispatcherOptions {
/** The worker manager used to dispatch RPC calls to plugin workers. */
workerManager?: PluginWorkerManager;
/** The lifecycle manager to listen for plugin state changes. */
lifecycleManager?: PluginLifecycleManager;
/** Database connection for looking up plugin records. */
db?: Db;
}
// ---------------------------------------------------------------------------
// PluginToolDispatcher interface
// ---------------------------------------------------------------------------
/**
* The plugin tool dispatcher the primary integration point between the
* agent service and the plugin tool system.
*
* Agents use this service to:
* 1. List all available tools (for prompt construction / tool choice)
* 2. Execute a specific tool by its namespaced name
*
* The dispatcher handles lifecycle management internally when a plugin
* is loaded or unloaded, its tools are automatically registered or removed.
*/
export interface PluginToolDispatcher {
/**
* Initialize the dispatcher load tools from all currently-ready plugins
* and start listening for lifecycle events.
*
* Must be called once at server startup after the lifecycle manager
* and worker manager are ready.
*/
initialize(): Promise<void>;
/**
* Tear down the dispatcher unregister lifecycle event listeners
* and clear all tool registrations.
*
* Called during server shutdown.
*/
teardown(): void;
/**
* List all available tools for agents, optionally filtered.
*
* Returns tool descriptors in an agent-friendly format.
*
* @param filter - Optional filter criteria
* @returns Array of agent tool descriptors
*/
listToolsForAgent(filter?: ToolListFilter): AgentToolDescriptor[];
/**
* Look up a tool by its namespaced name.
*
* @param namespacedName - e.g. `"acme.linear:search-issues"`
* @returns The registered tool, or `null` if not found
*/
getTool(namespacedName: string): RegisteredTool | null;
/**
* Execute a tool by its namespaced name, routing to the correct
* plugin worker.
*
* @param namespacedName - Fully qualified tool name
* @param parameters - Input parameters matching the tool's schema
* @param runContext - Agent run context
* @returns The execution result with routing metadata
* @throws {Error} if the tool is not found, the worker is not running,
* or the tool execution fails
*/
executeTool(
namespacedName: string,
parameters: unknown,
runContext: ToolRunContext,
): Promise<ToolExecutionResult>;
/**
* Register all tools from a plugin manifest.
*
* This is called automatically when a plugin transitions to `ready`.
* Can also be called manually for testing or recovery scenarios.
*
Improve CLI API parity coverage (#6626) ## Thinking Path > - Paperclip is a control plane for AI-agent companies, with the CLI acting as a scriptable operator and agent interface to that control plane. > - The REST API surface has grown across companies, agents, issues, routines, plugins, auth, workspaces, secrets, and operational inspection commands. > - The CLI had drifted from that API surface: some commands were missing, some command shapes differed from docs/reference material, and several edge cases only failed during end-to-end local-source testing. > - The local development runbook requires these tests to be disposable and isolated from a real `~/.paperclip`, `~/.codex`, or `~/.claude` installation. > - This pull request adds broad CLI/API parity coverage, fixes the actionable bugs found during that pass, and records the reproducible test log under `doc/logs`. > - The benefit is a more complete, scriptable CLI surface with regression coverage for the command families exercised by the parity run. ## What Changed - Added or expanded CLI command coverage for access/auth, companies, agents, projects, goals, issues and subresources, routines, plugins, workspaces, activity/run/cost/dashboard inspection, assets, skills, secrets, tokens, prompt/wake flows, and local setup helpers. - Fixed CLI/API parity bugs found during the run, including context profile patching, issue interaction optional payloads, malformed tree-hold errors, environment duplicate handling, configure invalid-section exit codes, worktree pnpm invocation, token agent ID resolution, plugin tool worker lookup, and routine webhook secret cleanup. - Added missing CLI wrappers and route coverage for health/access, invite resolution URL forwarding, join status normalization, secret lifecycle commands, LLM docs routes, available-skill isolation, positive board-claim coverage, and interactive `connect` prompt-flow tests. - Added a schema-backed `/api/openapi.json` route sufficient for CLI parity and `paperclipai openapi --json` smoke coverage. - Added `doc/logs/2026-05-24-cli-api-parity-e2e-log.md` with the detailed living test/bug log and renamed the log directory from `doc/bugs` to `doc/logs`. - Added `doc/plans/2026-05-23-cli-api-parity.md` and the OpenAPI parity reference used during the pass. OpenAPI note: this PR intentionally does not try to subsume `feature/openapi-spec`. The OpenAPI implementation here is schema-backed and better than the earlier route-inventory stub, but `feature/openapi-spec` is the fuller/better OpenAPI branch because it includes exact mounted-route coverage tests and additional current route coverage. That branch should stay as its own PR and can supersede this OpenAPI route implementation. ## Verification Targeted automated checks run: - `pnpm exec vitest run server/src/__tests__/openapi-routes.test.ts` - `pnpm exec vitest run server/src/__tests__/board-claim.test.ts` - `pnpm exec vitest run cli/src/__tests__/connect.test.ts` - `pnpm exec vitest run cli/src/__tests__/agent-lifecycle.test.ts` - `pnpm exec vitest run server/src/__tests__/plugin-database.test.ts` - `pnpm exec vitest run server/src/__tests__/routines-service.test.ts` - `pnpm --dir cli typecheck` - `pnpm --dir server typecheck` Manual/local E2E verification: - Ran the full disposable local-source CLI/API parity pass with isolated `PAPERCLIP_HOME`, `PAPERCLIP_CONFIG`, `PAPERCLIP_CONTEXT`, `PAPERCLIP_AUTH_STORE`, `CODEX_HOME`, and `CLAUDE_HOME` under `tmp/cli-api-parity`. - Verified `DATABASE_URL` and `DATABASE_MIGRATION_URL` stayed unset for the scratch server. - Verified live health and schema-backed OpenAPI responses on non-default port `3197`. - Revoked created board/agent tokens and cleaned up temporary plugins, secrets, non-default environments, and project workspaces. - See `doc/logs/2026-05-24-cli-api-parity-e2e-log.md` for the full command-by-command reproduction log. Not run: - Full `pnpm test`, `pnpm test:run`, or `pnpm build` were not run after the entire branch because the branch is broad and the parity pass used focused test/typecheck verification plus live isolated CLI reruns. ## Risks - This is a broad PR and touches many CLI command modules, so review surface is high. The changes are grouped around one theme, but a split may be easier if maintainers prefer narrower PRs. - The OpenAPI route in this PR is not the final/best OpenAPI implementation. `feature/openapi-spec` has stronger exact-route coverage and should remain the source for the dedicated OpenAPI PR. - The living log is intentionally detailed and large. It is useful for reproducibility but adds documentation weight. - No UI changes are intended; screenshots are not applicable. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - OpenAI Codex, GPT-5-based coding agent in Codex desktop. Exact served model/context-window identifier was not exposed in the local app. Work used shell/Git/GitHub CLI tooling, local source inspection, targeted test execution, and live isolated Paperclip CLI/API smoke testing. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] If this change affects the UI, I have included before/after screenshots - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Devin Foley <devin@devinfoley.com>
2026-06-03 02:13:29 +02:00
* @param pluginId - The plugin's stable manifest/plugin key used for tool namespacing
2026-03-13 16:22:34 -05:00
* @param manifest - The plugin manifest containing tool declarations
Improve CLI API parity coverage (#6626) ## Thinking Path > - Paperclip is a control plane for AI-agent companies, with the CLI acting as a scriptable operator and agent interface to that control plane. > - The REST API surface has grown across companies, agents, issues, routines, plugins, auth, workspaces, secrets, and operational inspection commands. > - The CLI had drifted from that API surface: some commands were missing, some command shapes differed from docs/reference material, and several edge cases only failed during end-to-end local-source testing. > - The local development runbook requires these tests to be disposable and isolated from a real `~/.paperclip`, `~/.codex`, or `~/.claude` installation. > - This pull request adds broad CLI/API parity coverage, fixes the actionable bugs found during that pass, and records the reproducible test log under `doc/logs`. > - The benefit is a more complete, scriptable CLI surface with regression coverage for the command families exercised by the parity run. ## What Changed - Added or expanded CLI command coverage for access/auth, companies, agents, projects, goals, issues and subresources, routines, plugins, workspaces, activity/run/cost/dashboard inspection, assets, skills, secrets, tokens, prompt/wake flows, and local setup helpers. - Fixed CLI/API parity bugs found during the run, including context profile patching, issue interaction optional payloads, malformed tree-hold errors, environment duplicate handling, configure invalid-section exit codes, worktree pnpm invocation, token agent ID resolution, plugin tool worker lookup, and routine webhook secret cleanup. - Added missing CLI wrappers and route coverage for health/access, invite resolution URL forwarding, join status normalization, secret lifecycle commands, LLM docs routes, available-skill isolation, positive board-claim coverage, and interactive `connect` prompt-flow tests. - Added a schema-backed `/api/openapi.json` route sufficient for CLI parity and `paperclipai openapi --json` smoke coverage. - Added `doc/logs/2026-05-24-cli-api-parity-e2e-log.md` with the detailed living test/bug log and renamed the log directory from `doc/bugs` to `doc/logs`. - Added `doc/plans/2026-05-23-cli-api-parity.md` and the OpenAPI parity reference used during the pass. OpenAPI note: this PR intentionally does not try to subsume `feature/openapi-spec`. The OpenAPI implementation here is schema-backed and better than the earlier route-inventory stub, but `feature/openapi-spec` is the fuller/better OpenAPI branch because it includes exact mounted-route coverage tests and additional current route coverage. That branch should stay as its own PR and can supersede this OpenAPI route implementation. ## Verification Targeted automated checks run: - `pnpm exec vitest run server/src/__tests__/openapi-routes.test.ts` - `pnpm exec vitest run server/src/__tests__/board-claim.test.ts` - `pnpm exec vitest run cli/src/__tests__/connect.test.ts` - `pnpm exec vitest run cli/src/__tests__/agent-lifecycle.test.ts` - `pnpm exec vitest run server/src/__tests__/plugin-database.test.ts` - `pnpm exec vitest run server/src/__tests__/routines-service.test.ts` - `pnpm --dir cli typecheck` - `pnpm --dir server typecheck` Manual/local E2E verification: - Ran the full disposable local-source CLI/API parity pass with isolated `PAPERCLIP_HOME`, `PAPERCLIP_CONFIG`, `PAPERCLIP_CONTEXT`, `PAPERCLIP_AUTH_STORE`, `CODEX_HOME`, and `CLAUDE_HOME` under `tmp/cli-api-parity`. - Verified `DATABASE_URL` and `DATABASE_MIGRATION_URL` stayed unset for the scratch server. - Verified live health and schema-backed OpenAPI responses on non-default port `3197`. - Revoked created board/agent tokens and cleaned up temporary plugins, secrets, non-default environments, and project workspaces. - See `doc/logs/2026-05-24-cli-api-parity-e2e-log.md` for the full command-by-command reproduction log. Not run: - Full `pnpm test`, `pnpm test:run`, or `pnpm build` were not run after the entire branch because the branch is broad and the parity pass used focused test/typecheck verification plus live isolated CLI reruns. ## Risks - This is a broad PR and touches many CLI command modules, so review surface is high. The changes are grouped around one theme, but a split may be easier if maintainers prefer narrower PRs. - The OpenAPI route in this PR is not the final/best OpenAPI implementation. `feature/openapi-spec` has stronger exact-route coverage and should remain the source for the dedicated OpenAPI PR. - The living log is intentionally detailed and large. It is useful for reproducibility but adds documentation weight. - No UI changes are intended; screenshots are not applicable. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - OpenAI Codex, GPT-5-based coding agent in Codex desktop. Exact served model/context-window identifier was not exposed in the local app. Work used shell/Git/GitHub CLI tooling, local source inspection, targeted test execution, and live isolated Paperclip CLI/API smoke testing. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] If this change affects the UI, I have included before/after screenshots - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Devin Foley <devin@devinfoley.com>
2026-06-03 02:13:29 +02:00
* @param pluginDbId - The plugin database ID used for worker lookup
2026-03-13 16:22:34 -05:00
*/
registerPluginTools(
pluginId: string,
manifest: PaperclipPluginManifestV1,
Improve CLI API parity coverage (#6626) ## Thinking Path > - Paperclip is a control plane for AI-agent companies, with the CLI acting as a scriptable operator and agent interface to that control plane. > - The REST API surface has grown across companies, agents, issues, routines, plugins, auth, workspaces, secrets, and operational inspection commands. > - The CLI had drifted from that API surface: some commands were missing, some command shapes differed from docs/reference material, and several edge cases only failed during end-to-end local-source testing. > - The local development runbook requires these tests to be disposable and isolated from a real `~/.paperclip`, `~/.codex`, or `~/.claude` installation. > - This pull request adds broad CLI/API parity coverage, fixes the actionable bugs found during that pass, and records the reproducible test log under `doc/logs`. > - The benefit is a more complete, scriptable CLI surface with regression coverage for the command families exercised by the parity run. ## What Changed - Added or expanded CLI command coverage for access/auth, companies, agents, projects, goals, issues and subresources, routines, plugins, workspaces, activity/run/cost/dashboard inspection, assets, skills, secrets, tokens, prompt/wake flows, and local setup helpers. - Fixed CLI/API parity bugs found during the run, including context profile patching, issue interaction optional payloads, malformed tree-hold errors, environment duplicate handling, configure invalid-section exit codes, worktree pnpm invocation, token agent ID resolution, plugin tool worker lookup, and routine webhook secret cleanup. - Added missing CLI wrappers and route coverage for health/access, invite resolution URL forwarding, join status normalization, secret lifecycle commands, LLM docs routes, available-skill isolation, positive board-claim coverage, and interactive `connect` prompt-flow tests. - Added a schema-backed `/api/openapi.json` route sufficient for CLI parity and `paperclipai openapi --json` smoke coverage. - Added `doc/logs/2026-05-24-cli-api-parity-e2e-log.md` with the detailed living test/bug log and renamed the log directory from `doc/bugs` to `doc/logs`. - Added `doc/plans/2026-05-23-cli-api-parity.md` and the OpenAPI parity reference used during the pass. OpenAPI note: this PR intentionally does not try to subsume `feature/openapi-spec`. The OpenAPI implementation here is schema-backed and better than the earlier route-inventory stub, but `feature/openapi-spec` is the fuller/better OpenAPI branch because it includes exact mounted-route coverage tests and additional current route coverage. That branch should stay as its own PR and can supersede this OpenAPI route implementation. ## Verification Targeted automated checks run: - `pnpm exec vitest run server/src/__tests__/openapi-routes.test.ts` - `pnpm exec vitest run server/src/__tests__/board-claim.test.ts` - `pnpm exec vitest run cli/src/__tests__/connect.test.ts` - `pnpm exec vitest run cli/src/__tests__/agent-lifecycle.test.ts` - `pnpm exec vitest run server/src/__tests__/plugin-database.test.ts` - `pnpm exec vitest run server/src/__tests__/routines-service.test.ts` - `pnpm --dir cli typecheck` - `pnpm --dir server typecheck` Manual/local E2E verification: - Ran the full disposable local-source CLI/API parity pass with isolated `PAPERCLIP_HOME`, `PAPERCLIP_CONFIG`, `PAPERCLIP_CONTEXT`, `PAPERCLIP_AUTH_STORE`, `CODEX_HOME`, and `CLAUDE_HOME` under `tmp/cli-api-parity`. - Verified `DATABASE_URL` and `DATABASE_MIGRATION_URL` stayed unset for the scratch server. - Verified live health and schema-backed OpenAPI responses on non-default port `3197`. - Revoked created board/agent tokens and cleaned up temporary plugins, secrets, non-default environments, and project workspaces. - See `doc/logs/2026-05-24-cli-api-parity-e2e-log.md` for the full command-by-command reproduction log. Not run: - Full `pnpm test`, `pnpm test:run`, or `pnpm build` were not run after the entire branch because the branch is broad and the parity pass used focused test/typecheck verification plus live isolated CLI reruns. ## Risks - This is a broad PR and touches many CLI command modules, so review surface is high. The changes are grouped around one theme, but a split may be easier if maintainers prefer narrower PRs. - The OpenAPI route in this PR is not the final/best OpenAPI implementation. `feature/openapi-spec` has stronger exact-route coverage and should remain the source for the dedicated OpenAPI PR. - The living log is intentionally detailed and large. It is useful for reproducibility but adds documentation weight. - No UI changes are intended; screenshots are not applicable. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - OpenAI Codex, GPT-5-based coding agent in Codex desktop. Exact served model/context-window identifier was not exposed in the local app. Work used shell/Git/GitHub CLI tooling, local source inspection, targeted test execution, and live isolated Paperclip CLI/API smoke testing. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] If this change affects the UI, I have included before/after screenshots - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Devin Foley <devin@devinfoley.com>
2026-06-03 02:13:29 +02:00
pluginDbId?: string,
2026-03-13 16:22:34 -05:00
): void;
/**
* Unregister all tools for a plugin.
*
* Called automatically when a plugin is disabled or unloaded.
*
* @param pluginId - The plugin to unregister
*/
unregisterPluginTools(pluginId: string): void;
/**
* Get the total number of registered tools, optionally scoped to a plugin.
*
* @param pluginId - If provided, count only this plugin's tools
*/
toolCount(pluginId?: string): number;
/**
* Access the underlying tool registry for advanced operations.
*
* This escape hatch exists for internal use (e.g. diagnostics).
* Prefer the dispatcher's own methods for normal operations.
*/
getRegistry(): PluginToolRegistry;
}
// ---------------------------------------------------------------------------
// Factory: createPluginToolDispatcher
// ---------------------------------------------------------------------------
/**
* Create a new `PluginToolDispatcher`.
*
* The dispatcher:
* 1. Creates and owns a `PluginToolRegistry` backed by the given worker manager.
* 2. Listens for lifecycle events (plugin.enabled, plugin.disabled, plugin.unloaded)
* to automatically register and unregister tools.
* 3. On `initialize()`, loads tools from all currently-ready plugins via the DB.
*
* @param options - Configuration options
*
* @example
* ```ts
* // At server startup
* const dispatcher = createPluginToolDispatcher({
* workerManager,
* lifecycleManager,
* db,
* });
* await dispatcher.initialize();
*
* // In agent service — list tools for prompt construction
* const tools = dispatcher.listToolsForAgent();
*
* // In agent service — execute a tool
* const result = await dispatcher.executeTool(
* "acme.linear:search-issues",
* { query: "auth bug" },
* { agentId: "a-1", runId: "r-1", companyId: "c-1", projectId: "p-1" },
* );
* ```
*/
export function createPluginToolDispatcher(
options: PluginToolDispatcherOptions = {},
): PluginToolDispatcher {
const { workerManager, lifecycleManager, db } = options;
const log = logger.child({ service: "plugin-tool-dispatcher" });
// Create the underlying tool registry, backed by the worker manager
const registry = createPluginToolRegistry(workerManager);
// Track lifecycle event listeners so we can remove them on teardown
let enabledListener: ((payload: { pluginId: string; pluginKey: string }) => void) | null = null;
let disabledListener: ((payload: { pluginId: string; pluginKey: string; reason?: string }) => void) | null = null;
let unloadedListener: ((payload: { pluginId: string; pluginKey: string; removeData: boolean }) => void) | null = null;
let initialized = false;
// -----------------------------------------------------------------------
// Internal helpers
// -----------------------------------------------------------------------
/**
* Attempt to register tools for a plugin by looking up its manifest
* from the DB. No-ops gracefully if the plugin or manifest is missing.
*/
async function registerFromDb(pluginId: string): Promise<void> {
if (!db) {
log.warn(
{ pluginId },
"cannot register tools from DB — no database connection configured",
);
return;
}
const pluginRegistry = pluginRegistryService(db);
const plugin = await pluginRegistry.getById(pluginId) as PluginRecord | null;
if (!plugin) {
log.warn({ pluginId }, "plugin not found in registry, cannot register tools");
return;
}
const manifest = plugin.manifestJson;
if (!manifest) {
log.warn({ pluginId }, "plugin has no manifest, cannot register tools");
return;
}
registry.registerPlugin(plugin.pluginKey, manifest, plugin.id);
}
/**
* Convert a `RegisteredTool` to an `AgentToolDescriptor`.
*/
function toAgentDescriptor(tool: RegisteredTool): AgentToolDescriptor {
return {
name: tool.namespacedName,
displayName: tool.displayName,
description: tool.description,
parametersSchema: tool.parametersSchema,
pluginId: tool.pluginDbId,
};
}
// -----------------------------------------------------------------------
// Lifecycle event handlers
// -----------------------------------------------------------------------
function handlePluginEnabled(payload: { pluginId: string; pluginKey: string }): void {
log.debug({ pluginId: payload.pluginId, pluginKey: payload.pluginKey }, "plugin enabled — registering tools");
// Async registration from DB — we fire-and-forget since the lifecycle
// event handler must be synchronous. Any errors are logged.
void registerFromDb(payload.pluginId).catch((err) => {
log.error(
{ pluginId: payload.pluginId, err: err instanceof Error ? err.message : String(err) },
"failed to register tools after plugin enabled",
);
});
}
function handlePluginDisabled(payload: { pluginId: string; pluginKey: string; reason?: string }): void {
log.debug({ pluginId: payload.pluginId, pluginKey: payload.pluginKey }, "plugin disabled — unregistering tools");
registry.unregisterPlugin(payload.pluginKey);
}
function handlePluginUnloaded(payload: { pluginId: string; pluginKey: string; removeData: boolean }): void {
log.debug({ pluginId: payload.pluginId, pluginKey: payload.pluginKey }, "plugin unloaded — unregistering tools");
registry.unregisterPlugin(payload.pluginKey);
}
// -----------------------------------------------------------------------
// Public API
// -----------------------------------------------------------------------
return {
async initialize(): Promise<void> {
if (initialized) {
log.warn("dispatcher already initialized, skipping");
return;
}
log.info("initializing plugin tool dispatcher");
// Step 1: Load tools from all currently-ready plugins
if (db) {
const pluginRegistry = pluginRegistryService(db);
const readyPlugins = await pluginRegistry.listByStatus("ready") as PluginRecord[];
let totalTools = 0;
for (const plugin of readyPlugins) {
const manifest = plugin.manifestJson;
if (manifest?.tools && manifest.tools.length > 0) {
registry.registerPlugin(plugin.pluginKey, manifest, plugin.id);
totalTools += manifest.tools.length;
}
}
log.info(
{ readyPlugins: readyPlugins.length, registeredTools: totalTools },
"loaded tools from ready plugins",
);
}
// Step 2: Subscribe to lifecycle events for dynamic updates
if (lifecycleManager) {
enabledListener = handlePluginEnabled;
disabledListener = handlePluginDisabled;
unloadedListener = handlePluginUnloaded;
lifecycleManager.on("plugin.enabled", enabledListener);
lifecycleManager.on("plugin.disabled", disabledListener);
lifecycleManager.on("plugin.unloaded", unloadedListener);
log.debug("subscribed to lifecycle events");
} else {
log.warn("no lifecycle manager provided — tools will not auto-update on plugin state changes");
}
initialized = true;
log.info(
{ totalTools: registry.toolCount() },
"plugin tool dispatcher initialized",
);
},
teardown(): void {
if (!initialized) return;
// Unsubscribe from lifecycle events
if (lifecycleManager) {
if (enabledListener) lifecycleManager.off("plugin.enabled", enabledListener);
if (disabledListener) lifecycleManager.off("plugin.disabled", disabledListener);
if (unloadedListener) lifecycleManager.off("plugin.unloaded", unloadedListener);
enabledListener = null;
disabledListener = null;
unloadedListener = null;
}
// Note: we do NOT clear the registry here because teardown may be
// called during graceful shutdown where in-flight tool calls should
// still be able to resolve their tool entries.
initialized = false;
log.info("plugin tool dispatcher torn down");
},
listToolsForAgent(filter?: ToolListFilter): AgentToolDescriptor[] {
return registry.listTools(filter).map(toAgentDescriptor);
},
getTool(namespacedName: string): RegisteredTool | null {
return registry.getTool(namespacedName);
},
async executeTool(
namespacedName: string,
parameters: unknown,
runContext: ToolRunContext,
): Promise<ToolExecutionResult> {
log.debug(
{
tool: namespacedName,
agentId: runContext.agentId,
runId: runContext.runId,
},
"dispatching tool execution",
);
const result = await registry.executeTool(
namespacedName,
parameters,
runContext,
);
log.debug(
{
tool: namespacedName,
pluginId: result.pluginId,
hasContent: !!result.result.content,
hasError: !!result.result.error,
},
"tool execution completed",
);
return result;
},
registerPluginTools(
pluginId: string,
manifest: PaperclipPluginManifestV1,
Improve CLI API parity coverage (#6626) ## Thinking Path > - Paperclip is a control plane for AI-agent companies, with the CLI acting as a scriptable operator and agent interface to that control plane. > - The REST API surface has grown across companies, agents, issues, routines, plugins, auth, workspaces, secrets, and operational inspection commands. > - The CLI had drifted from that API surface: some commands were missing, some command shapes differed from docs/reference material, and several edge cases only failed during end-to-end local-source testing. > - The local development runbook requires these tests to be disposable and isolated from a real `~/.paperclip`, `~/.codex`, or `~/.claude` installation. > - This pull request adds broad CLI/API parity coverage, fixes the actionable bugs found during that pass, and records the reproducible test log under `doc/logs`. > - The benefit is a more complete, scriptable CLI surface with regression coverage for the command families exercised by the parity run. ## What Changed - Added or expanded CLI command coverage for access/auth, companies, agents, projects, goals, issues and subresources, routines, plugins, workspaces, activity/run/cost/dashboard inspection, assets, skills, secrets, tokens, prompt/wake flows, and local setup helpers. - Fixed CLI/API parity bugs found during the run, including context profile patching, issue interaction optional payloads, malformed tree-hold errors, environment duplicate handling, configure invalid-section exit codes, worktree pnpm invocation, token agent ID resolution, plugin tool worker lookup, and routine webhook secret cleanup. - Added missing CLI wrappers and route coverage for health/access, invite resolution URL forwarding, join status normalization, secret lifecycle commands, LLM docs routes, available-skill isolation, positive board-claim coverage, and interactive `connect` prompt-flow tests. - Added a schema-backed `/api/openapi.json` route sufficient for CLI parity and `paperclipai openapi --json` smoke coverage. - Added `doc/logs/2026-05-24-cli-api-parity-e2e-log.md` with the detailed living test/bug log and renamed the log directory from `doc/bugs` to `doc/logs`. - Added `doc/plans/2026-05-23-cli-api-parity.md` and the OpenAPI parity reference used during the pass. OpenAPI note: this PR intentionally does not try to subsume `feature/openapi-spec`. The OpenAPI implementation here is schema-backed and better than the earlier route-inventory stub, but `feature/openapi-spec` is the fuller/better OpenAPI branch because it includes exact mounted-route coverage tests and additional current route coverage. That branch should stay as its own PR and can supersede this OpenAPI route implementation. ## Verification Targeted automated checks run: - `pnpm exec vitest run server/src/__tests__/openapi-routes.test.ts` - `pnpm exec vitest run server/src/__tests__/board-claim.test.ts` - `pnpm exec vitest run cli/src/__tests__/connect.test.ts` - `pnpm exec vitest run cli/src/__tests__/agent-lifecycle.test.ts` - `pnpm exec vitest run server/src/__tests__/plugin-database.test.ts` - `pnpm exec vitest run server/src/__tests__/routines-service.test.ts` - `pnpm --dir cli typecheck` - `pnpm --dir server typecheck` Manual/local E2E verification: - Ran the full disposable local-source CLI/API parity pass with isolated `PAPERCLIP_HOME`, `PAPERCLIP_CONFIG`, `PAPERCLIP_CONTEXT`, `PAPERCLIP_AUTH_STORE`, `CODEX_HOME`, and `CLAUDE_HOME` under `tmp/cli-api-parity`. - Verified `DATABASE_URL` and `DATABASE_MIGRATION_URL` stayed unset for the scratch server. - Verified live health and schema-backed OpenAPI responses on non-default port `3197`. - Revoked created board/agent tokens and cleaned up temporary plugins, secrets, non-default environments, and project workspaces. - See `doc/logs/2026-05-24-cli-api-parity-e2e-log.md` for the full command-by-command reproduction log. Not run: - Full `pnpm test`, `pnpm test:run`, or `pnpm build` were not run after the entire branch because the branch is broad and the parity pass used focused test/typecheck verification plus live isolated CLI reruns. ## Risks - This is a broad PR and touches many CLI command modules, so review surface is high. The changes are grouped around one theme, but a split may be easier if maintainers prefer narrower PRs. - The OpenAPI route in this PR is not the final/best OpenAPI implementation. `feature/openapi-spec` has stronger exact-route coverage and should remain the source for the dedicated OpenAPI PR. - The living log is intentionally detailed and large. It is useful for reproducibility but adds documentation weight. - No UI changes are intended; screenshots are not applicable. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - OpenAI Codex, GPT-5-based coding agent in Codex desktop. Exact served model/context-window identifier was not exposed in the local app. Work used shell/Git/GitHub CLI tooling, local source inspection, targeted test execution, and live isolated Paperclip CLI/API smoke testing. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] If this change affects the UI, I have included before/after screenshots - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Devin Foley <devin@devinfoley.com>
2026-06-03 02:13:29 +02:00
pluginDbId?: string,
2026-03-13 16:22:34 -05:00
): void {
Improve CLI API parity coverage (#6626) ## Thinking Path > - Paperclip is a control plane for AI-agent companies, with the CLI acting as a scriptable operator and agent interface to that control plane. > - The REST API surface has grown across companies, agents, issues, routines, plugins, auth, workspaces, secrets, and operational inspection commands. > - The CLI had drifted from that API surface: some commands were missing, some command shapes differed from docs/reference material, and several edge cases only failed during end-to-end local-source testing. > - The local development runbook requires these tests to be disposable and isolated from a real `~/.paperclip`, `~/.codex`, or `~/.claude` installation. > - This pull request adds broad CLI/API parity coverage, fixes the actionable bugs found during that pass, and records the reproducible test log under `doc/logs`. > - The benefit is a more complete, scriptable CLI surface with regression coverage for the command families exercised by the parity run. ## What Changed - Added or expanded CLI command coverage for access/auth, companies, agents, projects, goals, issues and subresources, routines, plugins, workspaces, activity/run/cost/dashboard inspection, assets, skills, secrets, tokens, prompt/wake flows, and local setup helpers. - Fixed CLI/API parity bugs found during the run, including context profile patching, issue interaction optional payloads, malformed tree-hold errors, environment duplicate handling, configure invalid-section exit codes, worktree pnpm invocation, token agent ID resolution, plugin tool worker lookup, and routine webhook secret cleanup. - Added missing CLI wrappers and route coverage for health/access, invite resolution URL forwarding, join status normalization, secret lifecycle commands, LLM docs routes, available-skill isolation, positive board-claim coverage, and interactive `connect` prompt-flow tests. - Added a schema-backed `/api/openapi.json` route sufficient for CLI parity and `paperclipai openapi --json` smoke coverage. - Added `doc/logs/2026-05-24-cli-api-parity-e2e-log.md` with the detailed living test/bug log and renamed the log directory from `doc/bugs` to `doc/logs`. - Added `doc/plans/2026-05-23-cli-api-parity.md` and the OpenAPI parity reference used during the pass. OpenAPI note: this PR intentionally does not try to subsume `feature/openapi-spec`. The OpenAPI implementation here is schema-backed and better than the earlier route-inventory stub, but `feature/openapi-spec` is the fuller/better OpenAPI branch because it includes exact mounted-route coverage tests and additional current route coverage. That branch should stay as its own PR and can supersede this OpenAPI route implementation. ## Verification Targeted automated checks run: - `pnpm exec vitest run server/src/__tests__/openapi-routes.test.ts` - `pnpm exec vitest run server/src/__tests__/board-claim.test.ts` - `pnpm exec vitest run cli/src/__tests__/connect.test.ts` - `pnpm exec vitest run cli/src/__tests__/agent-lifecycle.test.ts` - `pnpm exec vitest run server/src/__tests__/plugin-database.test.ts` - `pnpm exec vitest run server/src/__tests__/routines-service.test.ts` - `pnpm --dir cli typecheck` - `pnpm --dir server typecheck` Manual/local E2E verification: - Ran the full disposable local-source CLI/API parity pass with isolated `PAPERCLIP_HOME`, `PAPERCLIP_CONFIG`, `PAPERCLIP_CONTEXT`, `PAPERCLIP_AUTH_STORE`, `CODEX_HOME`, and `CLAUDE_HOME` under `tmp/cli-api-parity`. - Verified `DATABASE_URL` and `DATABASE_MIGRATION_URL` stayed unset for the scratch server. - Verified live health and schema-backed OpenAPI responses on non-default port `3197`. - Revoked created board/agent tokens and cleaned up temporary plugins, secrets, non-default environments, and project workspaces. - See `doc/logs/2026-05-24-cli-api-parity-e2e-log.md` for the full command-by-command reproduction log. Not run: - Full `pnpm test`, `pnpm test:run`, or `pnpm build` were not run after the entire branch because the branch is broad and the parity pass used focused test/typecheck verification plus live isolated CLI reruns. ## Risks - This is a broad PR and touches many CLI command modules, so review surface is high. The changes are grouped around one theme, but a split may be easier if maintainers prefer narrower PRs. - The OpenAPI route in this PR is not the final/best OpenAPI implementation. `feature/openapi-spec` has stronger exact-route coverage and should remain the source for the dedicated OpenAPI PR. - The living log is intentionally detailed and large. It is useful for reproducibility but adds documentation weight. - No UI changes are intended; screenshots are not applicable. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - OpenAI Codex, GPT-5-based coding agent in Codex desktop. Exact served model/context-window identifier was not exposed in the local app. Work used shell/Git/GitHub CLI tooling, local source inspection, targeted test execution, and live isolated Paperclip CLI/API smoke testing. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] If this change affects the UI, I have included before/after screenshots - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Devin Foley <devin@devinfoley.com>
2026-06-03 02:13:29 +02:00
registry.registerPlugin(pluginId, manifest, pluginDbId);
2026-03-13 16:22:34 -05:00
},
unregisterPluginTools(pluginId: string): void {
registry.unregisterPlugin(pluginId);
},
toolCount(pluginId?: string): number {
return registry.toolCount(pluginId);
},
getRegistry(): PluginToolRegistry {
return registry;
},
};
}