mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-16 13:04:08 +00:00
Merge hooks and custom-tools into unified extensions system (#454)
Breaking changes: - Settings: 'hooks' and 'customTools' arrays replaced with 'extensions' - CLI: '--hook' and '--tool' flags replaced with '--extension' / '-e' - API: HookMessage renamed to CustomMessage, role 'hookMessage' to 'custom' - API: FileSlashCommand renamed to PromptTemplate - API: discoverSlashCommands() renamed to discoverPromptTemplates() - Directories: commands/ renamed to prompts/ for prompt templates Migration: - Session version bumped to 3 (auto-migrates v2 sessions) - Old 'hookMessage' role entries converted to 'custom' Structural changes: - src/core/hooks/ and src/core/custom-tools/ merged into src/core/extensions/ - src/core/slash-commands.ts renamed to src/core/prompt-templates.ts - examples/hooks/ and examples/custom-tools/ merged into examples/extensions/ - docs/hooks.md and docs/custom-tools.md merged into docs/extensions.md New test coverage: - test/extensions-runner.test.ts (10 tests) - test/extensions-discovery.test.ts (26 tests) - test/prompt-templates.test.ts
This commit is contained in:
parent
9794868b38
commit
c6fc084534
112 changed files with 2842 additions and 6747 deletions
|
|
@ -9,20 +9,11 @@
|
|||
* // Minimal - everything auto-discovered
|
||||
* const session = await createAgentSession();
|
||||
*
|
||||
* // With custom hooks
|
||||
* const session = await createAgentSession({
|
||||
* hooks: [
|
||||
* ...await discoverHooks(),
|
||||
* { factory: myHookFactory },
|
||||
* ],
|
||||
* });
|
||||
*
|
||||
* // Full control
|
||||
* const session = await createAgentSession({
|
||||
* model: myModel,
|
||||
* getApiKey: async () => process.env.MY_KEY,
|
||||
* tools: [readTool, bashTool],
|
||||
* hooks: [],
|
||||
* skills: [],
|
||||
* sessionFile: false,
|
||||
* });
|
||||
|
|
@ -31,27 +22,25 @@
|
|||
|
||||
import { Agent, type AgentTool, type ThinkingLevel } from "@mariozechner/pi-agent-core";
|
||||
import type { Model } from "@mariozechner/pi-ai";
|
||||
import type { KeyId } from "@mariozechner/pi-tui";
|
||||
import { join } from "path";
|
||||
import { getAgentDir } from "../config.js";
|
||||
import { AgentSession } from "./agent-session.js";
|
||||
import { AuthStorage } from "./auth-storage.js";
|
||||
import {
|
||||
type CustomToolsLoadResult,
|
||||
discoverAndLoadCustomTools,
|
||||
type LoadedCustomTool,
|
||||
wrapCustomTools,
|
||||
} from "./custom-tools/index.js";
|
||||
import type { CustomTool } from "./custom-tools/types.js";
|
||||
import { createEventBus, type EventBus } from "./event-bus.js";
|
||||
import { discoverAndLoadHooks, HookRunner, type LoadedHook, wrapToolsWithHooks } from "./hooks/index.js";
|
||||
import type { HookFactory } from "./hooks/types.js";
|
||||
import {
|
||||
discoverAndLoadExtensions,
|
||||
ExtensionRunner,
|
||||
type LoadExtensionsResult,
|
||||
type LoadedExtension,
|
||||
wrapRegisteredTools,
|
||||
wrapToolsWithExtensions,
|
||||
} from "./extensions/index.js";
|
||||
import { convertToLlm } from "./messages.js";
|
||||
import { ModelRegistry } from "./model-registry.js";
|
||||
import { loadPromptTemplates as loadPromptTemplatesInternal, type PromptTemplate } from "./prompt-templates.js";
|
||||
import { SessionManager } from "./session-manager.js";
|
||||
import { type Settings, SettingsManager, type SkillsSettings } from "./settings-manager.js";
|
||||
import { loadSkills as loadSkillsInternal, type Skill } from "./skills.js";
|
||||
import { type FileSlashCommand, loadSlashCommands as loadSlashCommandsInternal } from "./slash-commands.js";
|
||||
import {
|
||||
buildSystemPrompt as buildSystemPromptInternal,
|
||||
loadProjectContextFiles as loadContextFilesInternal,
|
||||
|
|
@ -107,27 +96,20 @@ export interface CreateAgentSessionOptions {
|
|||
|
||||
/** Built-in tools to use. Default: codingTools [read, bash, edit, write] */
|
||||
tools?: Tool[];
|
||||
/** Custom tools (replaces discovery). */
|
||||
customTools?: Array<{ path?: string; tool: CustomTool }>;
|
||||
/** Additional custom tool paths to load (merged with discovery). */
|
||||
additionalCustomToolPaths?: string[];
|
||||
/** Additional extension paths to load (merged with discovery). */
|
||||
additionalExtensionPaths?: string[];
|
||||
/** Pre-loaded extensions (skips loading, used when extensions were loaded early for CLI flags). */
|
||||
preloadedExtensions?: LoadedExtension[];
|
||||
|
||||
/** Hooks (replaces discovery). */
|
||||
hooks?: Array<{ path?: string; factory: HookFactory }>;
|
||||
/** Additional hook paths to load (merged with discovery). */
|
||||
additionalHookPaths?: string[];
|
||||
/** Pre-loaded hooks (skips loading, used when hooks were loaded early for CLI flags). */
|
||||
preloadedHooks?: LoadedHook[];
|
||||
|
||||
/** Shared event bus for tool/hook communication. Default: creates new bus. */
|
||||
/** Shared event bus for tool/extension communication. Default: creates new bus. */
|
||||
eventBus?: EventBus;
|
||||
|
||||
/** Skills. Default: discovered from multiple locations */
|
||||
skills?: Skill[];
|
||||
/** Context files (AGENTS.md content). Default: discovered walking up from cwd */
|
||||
contextFiles?: Array<{ path: string; content: string }>;
|
||||
/** Slash commands. Default: discovered from cwd/.pi/commands/ + agentDir/commands/ */
|
||||
slashCommands?: FileSlashCommand[];
|
||||
/** Prompt templates. Default: discovered from cwd/.pi/prompts/ + agentDir/prompts/ */
|
||||
promptTemplates?: PromptTemplate[];
|
||||
|
||||
/** Session manager. Default: SessionManager.create(cwd) */
|
||||
sessionManager?: SessionManager;
|
||||
|
|
@ -140,19 +122,18 @@ export interface CreateAgentSessionOptions {
|
|||
export interface CreateAgentSessionResult {
|
||||
/** The created session */
|
||||
session: AgentSession;
|
||||
/** Custom tools result (for UI context setup in interactive mode) */
|
||||
customToolsResult: CustomToolsLoadResult;
|
||||
/** Extensions result (for UI context setup in interactive mode) */
|
||||
extensionsResult: LoadExtensionsResult;
|
||||
/** Warning if session was restored with a different model than saved */
|
||||
modelFallbackMessage?: string;
|
||||
}
|
||||
|
||||
// Re-exports
|
||||
|
||||
export type { CustomTool } from "./custom-tools/types.js";
|
||||
export type { HookAPI, HookCommandContext, HookContext, HookFactory } from "./hooks/types.js";
|
||||
export type { ExtensionAPI, ExtensionCommandContext, ExtensionContext, ExtensionFactory } from "./extensions/index.js";
|
||||
export type { PromptTemplate } from "./prompt-templates.js";
|
||||
export type { Settings, SkillsSettings } from "./settings-manager.js";
|
||||
export type { Skill } from "./skills.js";
|
||||
export type { FileSlashCommand } from "./slash-commands.js";
|
||||
export type { Tool } from "./tools/index.js";
|
||||
|
||||
export {
|
||||
|
|
@ -202,63 +183,27 @@ export function discoverModels(authStorage: AuthStorage, agentDir: string = getD
|
|||
}
|
||||
|
||||
/**
|
||||
* Discover hooks from cwd and agentDir.
|
||||
* @param eventBus - Shared event bus for pi.events communication. Pass to createAgentSession too.
|
||||
* Discover extensions from cwd and agentDir.
|
||||
* @param eventBus - Shared event bus for extension communication. Pass to createAgentSession too.
|
||||
* @param cwd - Current working directory
|
||||
* @param agentDir - Agent configuration directory
|
||||
*/
|
||||
export async function discoverHooks(
|
||||
export async function discoverExtensions(
|
||||
eventBus: EventBus,
|
||||
cwd?: string,
|
||||
agentDir?: string,
|
||||
): Promise<Array<{ path: string; factory: HookFactory }>> {
|
||||
): Promise<LoadExtensionsResult> {
|
||||
const resolvedCwd = cwd ?? process.cwd();
|
||||
const resolvedAgentDir = agentDir ?? getDefaultAgentDir();
|
||||
|
||||
const { hooks, errors } = await discoverAndLoadHooks([], resolvedCwd, resolvedAgentDir, eventBus);
|
||||
const result = await discoverAndLoadExtensions([], resolvedCwd, resolvedAgentDir, eventBus);
|
||||
|
||||
// Log errors but don't fail
|
||||
for (const { path, error } of errors) {
|
||||
console.error(`Failed to load hook "${path}": ${error}`);
|
||||
for (const { path, error } of result.errors) {
|
||||
console.error(`Failed to load extension "${path}": ${error}`);
|
||||
}
|
||||
|
||||
return hooks.map((h) => ({
|
||||
path: h.path,
|
||||
factory: createFactoryFromLoadedHook(h),
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Discover custom tools from cwd and agentDir.
|
||||
* @param eventBus - Shared event bus for tool.events communication. Pass to createAgentSession too.
|
||||
* @param cwd - Current working directory
|
||||
* @param agentDir - Agent configuration directory
|
||||
*/
|
||||
export async function discoverCustomTools(
|
||||
eventBus: EventBus,
|
||||
cwd?: string,
|
||||
agentDir?: string,
|
||||
): Promise<Array<{ path: string; tool: CustomTool }>> {
|
||||
const resolvedCwd = cwd ?? process.cwd();
|
||||
const resolvedAgentDir = agentDir ?? getDefaultAgentDir();
|
||||
|
||||
const { tools, errors } = await discoverAndLoadCustomTools(
|
||||
[],
|
||||
resolvedCwd,
|
||||
Object.keys(allTools),
|
||||
resolvedAgentDir,
|
||||
eventBus,
|
||||
);
|
||||
|
||||
// Log errors but don't fail
|
||||
for (const { path, error } of errors) {
|
||||
console.error(`Failed to load custom tool "${path}": ${error}`);
|
||||
}
|
||||
|
||||
return tools.map((t) => ({
|
||||
path: t.path,
|
||||
tool: t.tool,
|
||||
}));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -284,10 +229,10 @@ export function discoverContextFiles(cwd?: string, agentDir?: string): Array<{ p
|
|||
}
|
||||
|
||||
/**
|
||||
* Discover slash commands from cwd and agentDir.
|
||||
* Discover prompt templates from cwd and agentDir.
|
||||
*/
|
||||
export function discoverSlashCommands(cwd?: string, agentDir?: string): FileSlashCommand[] {
|
||||
return loadSlashCommandsInternal({
|
||||
export function discoverPromptTemplates(cwd?: string, agentDir?: string): PromptTemplate[] {
|
||||
return loadPromptTemplatesInternal({
|
||||
cwd: cwd ?? process.cwd(),
|
||||
agentDir: agentDir ?? getDefaultAgentDir(),
|
||||
});
|
||||
|
|
@ -336,139 +281,12 @@ export function loadSettings(cwd?: string, agentDir?: string): Settings {
|
|||
hideThinkingBlock: manager.getHideThinkingBlock(),
|
||||
shellPath: manager.getShellPath(),
|
||||
collapseChangelog: manager.getCollapseChangelog(),
|
||||
hooks: manager.getHookPaths(),
|
||||
customTools: manager.getCustomToolPaths(),
|
||||
extensions: manager.getExtensionPaths(),
|
||||
skills: manager.getSkillsSettings(),
|
||||
terminal: { showImages: manager.getShowImages() },
|
||||
};
|
||||
}
|
||||
|
||||
// Internal Helpers
|
||||
|
||||
/**
|
||||
* Create a HookFactory from a LoadedHook.
|
||||
* This allows mixing discovered hooks with inline hooks.
|
||||
*/
|
||||
function createFactoryFromLoadedHook(loaded: LoadedHook): HookFactory {
|
||||
return (api) => {
|
||||
for (const [eventType, handlers] of loaded.handlers) {
|
||||
for (const handler of handlers) {
|
||||
api.on(eventType as any, handler as any);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert hook definitions to LoadedHooks for the HookRunner.
|
||||
*/
|
||||
function createLoadedHooksFromDefinitions(
|
||||
definitions: Array<{ path?: string; factory: HookFactory }>,
|
||||
eventBus: EventBus,
|
||||
): LoadedHook[] {
|
||||
return definitions.map((def) => {
|
||||
const hookPath = def.path ?? "<inline>";
|
||||
const handlers = new Map<string, Array<(...args: unknown[]) => Promise<unknown>>>();
|
||||
const messageRenderers = new Map<string, any>();
|
||||
const commands = new Map<string, any>();
|
||||
const flags = new Map<string, any>();
|
||||
const flagValues = new Map<string, boolean | string>();
|
||||
const shortcuts = new Map<KeyId, any>();
|
||||
let sendMessageHandler: (
|
||||
message: any,
|
||||
options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" },
|
||||
) => void = () => {};
|
||||
let appendEntryHandler: (customType: string, data?: any) => void = () => {};
|
||||
let getActiveToolsHandler: () => string[] = () => [];
|
||||
let getAllToolsHandler: () => string[] = () => [];
|
||||
let setActiveToolsHandler: (toolNames: string[]) => void = () => {};
|
||||
let newSessionHandler: (options?: any) => Promise<{ cancelled: boolean }> = async () => ({ cancelled: false });
|
||||
let branchHandler: (entryId: string) => Promise<{ cancelled: boolean }> = async () => ({ cancelled: false });
|
||||
let navigateTreeHandler: (targetId: string, options?: any) => Promise<{ cancelled: boolean }> = async () => ({
|
||||
cancelled: false,
|
||||
});
|
||||
|
||||
const api = {
|
||||
on: (event: string, handler: (...args: unknown[]) => Promise<unknown>) => {
|
||||
const list = handlers.get(event) ?? [];
|
||||
list.push(handler);
|
||||
handlers.set(event, list);
|
||||
},
|
||||
sendMessage: (message: any, options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" }) => {
|
||||
sendMessageHandler(message, options);
|
||||
},
|
||||
appendEntry: (customType: string, data?: any) => {
|
||||
appendEntryHandler(customType, data);
|
||||
},
|
||||
registerMessageRenderer: (customType: string, renderer: any) => {
|
||||
messageRenderers.set(customType, renderer);
|
||||
},
|
||||
registerCommand: (name: string, options: any) => {
|
||||
commands.set(name, { name, ...options });
|
||||
},
|
||||
registerFlag: (name: string, options: any) => {
|
||||
flags.set(name, { name, hookPath, ...options });
|
||||
if (options.default !== undefined) {
|
||||
flagValues.set(name, options.default);
|
||||
}
|
||||
},
|
||||
getFlag: (name: string) => flagValues.get(name),
|
||||
registerShortcut: (shortcut: KeyId, options: any) => {
|
||||
shortcuts.set(shortcut, { shortcut, hookPath, ...options });
|
||||
},
|
||||
newSession: (options?: any) => newSessionHandler(options),
|
||||
branch: (entryId: string) => branchHandler(entryId),
|
||||
navigateTree: (targetId: string, options?: any) => navigateTreeHandler(targetId, options),
|
||||
getActiveTools: () => getActiveToolsHandler(),
|
||||
getAllTools: () => getAllToolsHandler(),
|
||||
setActiveTools: (toolNames: string[]) => setActiveToolsHandler(toolNames),
|
||||
events: eventBus,
|
||||
};
|
||||
|
||||
def.factory(api as any);
|
||||
|
||||
return {
|
||||
path: hookPath,
|
||||
resolvedPath: hookPath,
|
||||
handlers,
|
||||
messageRenderers,
|
||||
commands,
|
||||
flags,
|
||||
flagValues,
|
||||
shortcuts,
|
||||
setSendMessageHandler: (
|
||||
handler: (message: any, options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" }) => void,
|
||||
) => {
|
||||
sendMessageHandler = handler;
|
||||
},
|
||||
setAppendEntryHandler: (handler: (customType: string, data?: any) => void) => {
|
||||
appendEntryHandler = handler;
|
||||
},
|
||||
setNewSessionHandler: (handler: (options?: any) => Promise<{ cancelled: boolean }>) => {
|
||||
newSessionHandler = handler;
|
||||
},
|
||||
setBranchHandler: (handler: (entryId: string) => Promise<{ cancelled: boolean }>) => {
|
||||
branchHandler = handler;
|
||||
},
|
||||
setNavigateTreeHandler: (handler: (targetId: string, options?: any) => Promise<{ cancelled: boolean }>) => {
|
||||
navigateTreeHandler = handler;
|
||||
},
|
||||
setGetActiveToolsHandler: (handler: () => string[]) => {
|
||||
getActiveToolsHandler = handler;
|
||||
},
|
||||
setGetAllToolsHandler: (handler: () => string[]) => {
|
||||
getAllToolsHandler = handler;
|
||||
},
|
||||
setSetActiveToolsHandler: (handler: (toolNames: string[]) => void) => {
|
||||
setActiveToolsHandler = handler;
|
||||
},
|
||||
setFlagValue: (name: string, value: boolean | string) => {
|
||||
flagValues.set(name, value);
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// Factory
|
||||
|
||||
/**
|
||||
|
|
@ -497,7 +315,6 @@ function createLoadedHooksFromDefinitions(
|
|||
* getApiKey: async () => process.env.MY_KEY,
|
||||
* systemPrompt: 'You are helpful.',
|
||||
* tools: [readTool, bashTool],
|
||||
* hooks: [],
|
||||
* skills: [],
|
||||
* sessionManager: SessionManager.inMemory(),
|
||||
* });
|
||||
|
|
@ -592,7 +409,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|||
time("discoverContextFiles");
|
||||
|
||||
const autoResizeImages = settingsManager.getImageAutoResize();
|
||||
// Create ALL built-in tools for the registry (hooks can enable any of them)
|
||||
// Create ALL built-in tools for the registry (extensions can enable any of them)
|
||||
const allBuiltInToolsMap = createAllTools(cwd, { read: { autoResizeImages } });
|
||||
// Determine initially active built-in tools (default: read, bash, edit, write)
|
||||
const defaultActiveToolNames: ToolName[] = ["read", "bash", "edit", "write"];
|
||||
|
|
@ -602,62 +419,54 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|||
const initialActiveBuiltInTools = initialActiveToolNames.map((name) => allBuiltInToolsMap[name]);
|
||||
time("createAllTools");
|
||||
|
||||
let customToolsResult: CustomToolsLoadResult;
|
||||
if (options.customTools !== undefined) {
|
||||
// Use provided custom tools
|
||||
const loadedTools: LoadedCustomTool[] = options.customTools.map((ct) => ({
|
||||
path: ct.path ?? "<inline>",
|
||||
resolvedPath: ct.path ?? "<inline>",
|
||||
tool: ct.tool,
|
||||
}));
|
||||
customToolsResult = {
|
||||
tools: loadedTools,
|
||||
// Load extensions (discovers from standard locations + configured paths)
|
||||
let extensionsResult: LoadExtensionsResult;
|
||||
if (options.preloadedExtensions !== undefined && options.preloadedExtensions.length > 0) {
|
||||
// Use pre-loaded extensions (from early CLI flag discovery)
|
||||
extensionsResult = {
|
||||
extensions: options.preloadedExtensions,
|
||||
errors: [],
|
||||
setUIContext: () => {},
|
||||
setSendMessageHandler: () => {},
|
||||
};
|
||||
} else {
|
||||
// Discover custom tools, merging with additional paths
|
||||
const configuredPaths = [...settingsManager.getCustomToolPaths(), ...(options.additionalCustomToolPaths ?? [])];
|
||||
customToolsResult = await discoverAndLoadCustomTools(
|
||||
configuredPaths,
|
||||
cwd,
|
||||
Object.keys(allTools),
|
||||
agentDir,
|
||||
eventBus,
|
||||
);
|
||||
time("discoverAndLoadCustomTools");
|
||||
for (const { path, error } of customToolsResult.errors) {
|
||||
console.error(`Failed to load custom tool "${path}": ${error}`);
|
||||
// Discover extensions, merging with additional paths
|
||||
const configuredPaths = [...settingsManager.getExtensionPaths(), ...(options.additionalExtensionPaths ?? [])];
|
||||
extensionsResult = await discoverAndLoadExtensions(configuredPaths, cwd, agentDir, eventBus);
|
||||
time("discoverAndLoadExtensions");
|
||||
for (const { path, error } of extensionsResult.errors) {
|
||||
console.error(`Failed to load extension "${path}": ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
let hookRunner: HookRunner | undefined;
|
||||
if (options.preloadedHooks !== undefined && options.preloadedHooks.length > 0) {
|
||||
// Use pre-loaded hooks (from early CLI flag discovery)
|
||||
hookRunner = new HookRunner(options.preloadedHooks, cwd, sessionManager, modelRegistry);
|
||||
} else if (options.hooks !== undefined) {
|
||||
if (options.hooks.length > 0) {
|
||||
const loadedHooks = createLoadedHooksFromDefinitions(options.hooks, eventBus);
|
||||
hookRunner = new HookRunner(loadedHooks, cwd, sessionManager, modelRegistry);
|
||||
}
|
||||
} else {
|
||||
// Discover hooks, merging with additional paths
|
||||
const configuredPaths = [...settingsManager.getHookPaths(), ...(options.additionalHookPaths ?? [])];
|
||||
const { hooks, errors } = await discoverAndLoadHooks(configuredPaths, cwd, agentDir, eventBus);
|
||||
time("discoverAndLoadHooks");
|
||||
for (const { path, error } of errors) {
|
||||
console.error(`Failed to load hook "${path}": ${error}`);
|
||||
}
|
||||
if (hooks.length > 0) {
|
||||
hookRunner = new HookRunner(hooks, cwd, sessionManager, modelRegistry);
|
||||
}
|
||||
// Create extension runner if we have extensions
|
||||
let extensionRunner: ExtensionRunner | undefined;
|
||||
if (extensionsResult.extensions.length > 0) {
|
||||
extensionRunner = new ExtensionRunner(extensionsResult.extensions, cwd, sessionManager, modelRegistry);
|
||||
}
|
||||
|
||||
// Wrap custom tools with context getter (agent/session assigned below, accessed at execute time)
|
||||
// Wrap extension-registered tools with context getter (agent/session assigned below, accessed at execute time)
|
||||
let agent: Agent;
|
||||
let session: AgentSession;
|
||||
const wrappedCustomTools = wrapCustomTools(customToolsResult.tools, () => ({
|
||||
const registeredTools = extensionRunner?.getAllRegisteredTools() ?? [];
|
||||
const wrappedExtensionTools = wrapRegisteredTools(registeredTools, () => ({
|
||||
ui: extensionRunner?.getUIContext() ?? {
|
||||
select: async () => undefined,
|
||||
confirm: async () => false,
|
||||
input: async () => undefined,
|
||||
notify: () => {},
|
||||
setStatus: () => {},
|
||||
setWidget: () => {},
|
||||
setTitle: () => {},
|
||||
custom: async () => undefined as never,
|
||||
setEditorText: () => {},
|
||||
getEditorText: () => "",
|
||||
editor: async () => undefined,
|
||||
get theme() {
|
||||
return {} as any;
|
||||
},
|
||||
},
|
||||
hasUI: extensionRunner?.getHasUI() ?? false,
|
||||
cwd,
|
||||
sessionManager,
|
||||
modelRegistry,
|
||||
model: agent.state.model,
|
||||
|
|
@ -668,27 +477,27 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|||
},
|
||||
}));
|
||||
|
||||
// Create tool registry mapping name -> tool (for hook getTools/setTools)
|
||||
// Registry contains ALL built-in tools so hooks can enable any of them
|
||||
// Create tool registry mapping name -> tool (for extension getTools/setTools)
|
||||
// Registry contains ALL built-in tools so extensions can enable any of them
|
||||
const toolRegistry = new Map<string, AgentTool>();
|
||||
for (const [name, tool] of Object.entries(allBuiltInToolsMap)) {
|
||||
toolRegistry.set(name, tool as AgentTool);
|
||||
}
|
||||
for (const tool of wrappedCustomTools as AgentTool[]) {
|
||||
for (const tool of wrappedExtensionTools as AgentTool[]) {
|
||||
toolRegistry.set(tool.name, tool);
|
||||
}
|
||||
|
||||
// Initially active tools = active built-in + custom
|
||||
let activeToolsArray: Tool[] = [...initialActiveBuiltInTools, ...wrappedCustomTools];
|
||||
// Initially active tools = active built-in + extension tools
|
||||
let activeToolsArray: Tool[] = [...initialActiveBuiltInTools, ...wrappedExtensionTools];
|
||||
time("combineTools");
|
||||
|
||||
// Wrap tools with hooks if available
|
||||
// Wrap tools with extensions if available
|
||||
let wrappedToolRegistry: Map<string, AgentTool> | undefined;
|
||||
if (hookRunner) {
|
||||
activeToolsArray = wrapToolsWithHooks(activeToolsArray as AgentTool[], hookRunner);
|
||||
// Wrap ALL registry tools (not just active) so hooks can enable any
|
||||
if (extensionRunner) {
|
||||
activeToolsArray = wrapToolsWithExtensions(activeToolsArray as AgentTool[], extensionRunner);
|
||||
// Wrap ALL registry tools (not just active) so extensions can enable any
|
||||
const allRegistryTools = Array.from(toolRegistry.values());
|
||||
const wrappedAllTools = wrapToolsWithHooks(allRegistryTools, hookRunner);
|
||||
const wrappedAllTools = wrapToolsWithExtensions(allRegistryTools, extensionRunner);
|
||||
wrappedToolRegistry = new Map<string, AgentTool>();
|
||||
for (const tool of wrappedAllTools) {
|
||||
wrappedToolRegistry.set(tool.name, tool);
|
||||
|
|
@ -727,8 +536,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|||
const systemPrompt = rebuildSystemPrompt(initialActiveToolNames);
|
||||
time("buildSystemPrompt");
|
||||
|
||||
const slashCommands = options.slashCommands ?? discoverSlashCommands(cwd, agentDir);
|
||||
time("discoverSlashCommands");
|
||||
const promptTemplates = options.promptTemplates ?? discoverPromptTemplates(cwd, agentDir);
|
||||
time("discoverPromptTemplates");
|
||||
|
||||
agent = new Agent({
|
||||
initialState: {
|
||||
|
|
@ -738,9 +547,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|||
tools: activeToolsArray,
|
||||
},
|
||||
convertToLlm,
|
||||
transformContext: hookRunner
|
||||
transformContext: extensionRunner
|
||||
? async (messages) => {
|
||||
return hookRunner.emitContext(messages);
|
||||
return extensionRunner.emitContext(messages);
|
||||
}
|
||||
: undefined,
|
||||
steeringMode: settingsManager.getSteeringMode(),
|
||||
|
|
@ -775,9 +584,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|||
sessionManager,
|
||||
settingsManager,
|
||||
scopedModels: options.scopedModels,
|
||||
fileCommands: slashCommands,
|
||||
hookRunner,
|
||||
customTools: customToolsResult.tools,
|
||||
promptTemplates: promptTemplates,
|
||||
extensionRunner,
|
||||
skillsSettings: settingsManager.getSkillsSettings(),
|
||||
modelRegistry,
|
||||
toolRegistry: wrappedToolRegistry ?? toolRegistry,
|
||||
|
|
@ -785,14 +593,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|||
});
|
||||
time("createAgentSession");
|
||||
|
||||
// Wire up sendMessage for custom tools
|
||||
customToolsResult.setSendMessageHandler((msg, opts) => {
|
||||
session.sendHookMessage(msg, opts);
|
||||
});
|
||||
|
||||
return {
|
||||
session,
|
||||
customToolsResult,
|
||||
extensionsResult,
|
||||
modelFallbackMessage,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue