mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-16 20:01:24 +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
|
|
@ -16,11 +16,9 @@ import { selectSession } from "./cli/session-picker.js";
|
|||
import { CONFIG_DIR_NAME, getAgentDir, getModelsPath, VERSION } from "./config.js";
|
||||
import type { AgentSession } from "./core/agent-session.js";
|
||||
|
||||
import type { LoadedCustomTool } from "./core/custom-tools/index.js";
|
||||
import { createEventBus } from "./core/event-bus.js";
|
||||
import { exportFromFile } from "./core/export-html/index.js";
|
||||
import { discoverAndLoadHooks } from "./core/hooks/index.js";
|
||||
import type { HookUIContext } from "./core/index.js";
|
||||
import { discoverAndLoadExtensions, type ExtensionUIContext, type LoadedExtension } from "./core/extensions/index.js";
|
||||
import type { ModelRegistry } from "./core/model-registry.js";
|
||||
import { resolveModelScope, type ScopedModel } from "./core/model-resolver.js";
|
||||
import { type CreateAgentSessionOptions, createAgentSession, discoverAuthStorage, discoverModels } from "./core/sdk.js";
|
||||
|
|
@ -62,13 +60,13 @@ async function runInteractiveMode(
|
|||
migratedProviders: string[],
|
||||
versionCheckPromise: Promise<string | undefined>,
|
||||
initialMessages: string[],
|
||||
customTools: LoadedCustomTool[],
|
||||
setToolUIContext: (uiContext: HookUIContext, hasUI: boolean) => void,
|
||||
extensions: LoadedExtension[],
|
||||
setExtensionUIContext: (uiContext: ExtensionUIContext, hasUI: boolean) => void,
|
||||
initialMessage?: string,
|
||||
initialImages?: ImageContent[],
|
||||
fdPath: string | undefined = undefined,
|
||||
): Promise<void> {
|
||||
const mode = new InteractiveMode(session, version, changelogMarkdown, customTools, setToolUIContext, fdPath);
|
||||
const mode = new InteractiveMode(session, version, changelogMarkdown, extensions, setExtensionUIContext, fdPath);
|
||||
|
||||
await mode.init();
|
||||
|
||||
|
|
@ -214,7 +212,7 @@ function buildSessionOptions(
|
|||
scopedModels: ScopedModel[],
|
||||
sessionManager: SessionManager | undefined,
|
||||
modelRegistry: ModelRegistry,
|
||||
preloadedHooks?: import("./core/hooks/index.js").LoadedHook[],
|
||||
preloadedExtensions?: LoadedExtension[],
|
||||
): CreateAgentSessionOptions {
|
||||
const options: CreateAgentSessionOptions = {};
|
||||
|
||||
|
|
@ -273,14 +271,9 @@ function buildSessionOptions(
|
|||
options.skills = [];
|
||||
}
|
||||
|
||||
// Pre-loaded hooks (from early CLI flag discovery)
|
||||
if (preloadedHooks && preloadedHooks.length > 0) {
|
||||
options.preloadedHooks = preloadedHooks;
|
||||
}
|
||||
|
||||
// Additional custom tool paths from CLI
|
||||
if (parsed.customTools && parsed.customTools.length > 0) {
|
||||
options.additionalCustomToolPaths = parsed.customTools;
|
||||
// Pre-loaded extensions (from early CLI flag discovery)
|
||||
if (preloadedExtensions && preloadedExtensions.length > 0) {
|
||||
options.preloadedExtensions = preloadedExtensions;
|
||||
}
|
||||
|
||||
return options;
|
||||
|
|
@ -297,35 +290,35 @@ export async function main(args: string[]) {
|
|||
const modelRegistry = discoverModels(authStorage);
|
||||
time("discoverModels");
|
||||
|
||||
// First pass: parse args to get --hook paths
|
||||
// First pass: parse args to get --extension paths
|
||||
const firstPass = parseArgs(args);
|
||||
time("parseArgs-firstPass");
|
||||
|
||||
// Early load hooks to discover their CLI flags
|
||||
// Early load extensions to discover their CLI flags
|
||||
const cwd = process.cwd();
|
||||
const agentDir = getAgentDir();
|
||||
const eventBus = createEventBus();
|
||||
const hookPaths = firstPass.hooks ?? [];
|
||||
const { hooks: loadedHooks } = await discoverAndLoadHooks(hookPaths, cwd, agentDir, eventBus);
|
||||
time("discoverHookFlags");
|
||||
const extensionPaths = firstPass.extensions ?? [];
|
||||
const { extensions: loadedExtensions } = await discoverAndLoadExtensions(extensionPaths, cwd, agentDir, eventBus);
|
||||
time("discoverExtensionFlags");
|
||||
|
||||
// Collect all hook flags
|
||||
const hookFlags = new Map<string, { type: "boolean" | "string" }>();
|
||||
for (const hook of loadedHooks) {
|
||||
for (const [name, flag] of hook.flags) {
|
||||
hookFlags.set(name, { type: flag.type });
|
||||
// Collect all extension flags
|
||||
const extensionFlags = new Map<string, { type: "boolean" | "string" }>();
|
||||
for (const ext of loadedExtensions) {
|
||||
for (const [name, flag] of ext.flags) {
|
||||
extensionFlags.set(name, { type: flag.type });
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass: parse args with hook flags
|
||||
const parsed = parseArgs(args, hookFlags);
|
||||
// Second pass: parse args with extension flags
|
||||
const parsed = parseArgs(args, extensionFlags);
|
||||
time("parseArgs");
|
||||
|
||||
// Pass flag values to hooks
|
||||
// Pass flag values to extensions
|
||||
for (const [name, value] of parsed.unknownFlags) {
|
||||
for (const hook of loadedHooks) {
|
||||
if (hook.flags.has(name)) {
|
||||
hook.setFlagValue(name, value);
|
||||
for (const ext of loadedExtensions) {
|
||||
if (ext.flags.has(name)) {
|
||||
ext.setFlagValue(name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -401,7 +394,7 @@ export async function main(args: string[]) {
|
|||
sessionManager = SessionManager.open(selectedPath);
|
||||
}
|
||||
|
||||
const sessionOptions = buildSessionOptions(parsed, scopedModels, sessionManager, modelRegistry, loadedHooks);
|
||||
const sessionOptions = buildSessionOptions(parsed, scopedModels, sessionManager, modelRegistry, loadedExtensions);
|
||||
sessionOptions.authStorage = authStorage;
|
||||
sessionOptions.modelRegistry = modelRegistry;
|
||||
sessionOptions.eventBus = eventBus;
|
||||
|
|
@ -416,7 +409,7 @@ export async function main(args: string[]) {
|
|||
}
|
||||
|
||||
time("buildSessionOptions");
|
||||
const { session, customToolsResult, modelFallbackMessage } = await createAgentSession(sessionOptions);
|
||||
const { session, extensionsResult, modelFallbackMessage } = await createAgentSession(sessionOptions);
|
||||
time("createAgentSession");
|
||||
|
||||
if (!isInteractive && !session.model) {
|
||||
|
|
@ -469,8 +462,8 @@ export async function main(args: string[]) {
|
|||
migratedProviders,
|
||||
versionCheckPromise,
|
||||
parsed.messages,
|
||||
customToolsResult.tools,
|
||||
customToolsResult.setUIContext,
|
||||
extensionsResult.extensions,
|
||||
extensionsResult.setUIContext,
|
||||
initialMessage,
|
||||
initialImages,
|
||||
fdPath,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue