mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-16 01:03:49 +00:00
feat(hooks): add systemPromptAppend to before_agent_start, full tool registry
- before_agent_start handlers can return systemPromptAppend to dynamically append text to the system prompt for that turn - Multiple hooks' systemPromptAppend strings are concatenated - Multiple hooks' messages are now all injected (not just first) - Tool registry now contains ALL built-in tools (read, bash, edit, write, grep, find, ls) regardless of --tools flag - --tools only sets initially active tools, hooks can enable any via setActiveTools() - System prompt automatically rebuilds when tools change, updating tool descriptions and guidelines - Add pirate.ts example hook demonstrating systemPromptAppend - Update hooks.md with systemPromptAppend documentation
This commit is contained in:
parent
892acedb6b
commit
e4dd21a3b2
7 changed files with 136 additions and 20 deletions
|
|
@ -84,6 +84,8 @@ export interface AgentSessionConfig {
|
|||
modelRegistry: ModelRegistry;
|
||||
/** Tool registry for hook getTools/setTools - maps name to tool */
|
||||
toolRegistry?: Map<string, AgentTool>;
|
||||
/** Function to rebuild system prompt when tools change */
|
||||
rebuildSystemPrompt?: (toolNames: string[]) => string;
|
||||
}
|
||||
|
||||
/** Options for AgentSession.prompt() */
|
||||
|
|
@ -186,6 +188,12 @@ export class AgentSession {
|
|||
// Tool registry for hook getTools/setTools
|
||||
private _toolRegistry: Map<string, AgentTool>;
|
||||
|
||||
// Function to rebuild system prompt when tools change
|
||||
private _rebuildSystemPrompt?: (toolNames: string[]) => string;
|
||||
|
||||
// Base system prompt (without hook appends) - used to apply fresh appends each turn
|
||||
private _baseSystemPrompt: string;
|
||||
|
||||
constructor(config: AgentSessionConfig) {
|
||||
this.agent = config.agent;
|
||||
this.sessionManager = config.sessionManager;
|
||||
|
|
@ -197,6 +205,8 @@ export class AgentSession {
|
|||
this._skillsSettings = config.skillsSettings;
|
||||
this._modelRegistry = config.modelRegistry;
|
||||
this._toolRegistry = config.toolRegistry ?? new Map();
|
||||
this._rebuildSystemPrompt = config.rebuildSystemPrompt;
|
||||
this._baseSystemPrompt = config.agent.state.systemPrompt;
|
||||
|
||||
// Always subscribe to agent events for internal handling
|
||||
// (session persistence, hooks, auto-compaction, retry logic)
|
||||
|
|
@ -448,17 +458,26 @@ export class AgentSession {
|
|||
/**
|
||||
* Set active tools by name.
|
||||
* Only tools in the registry can be enabled. Unknown tool names are ignored.
|
||||
* Also rebuilds the system prompt to reflect the new tool set.
|
||||
* Changes take effect on the next agent turn.
|
||||
*/
|
||||
setActiveToolsByName(toolNames: string[]): void {
|
||||
const tools: AgentTool[] = [];
|
||||
const validToolNames: string[] = [];
|
||||
for (const name of toolNames) {
|
||||
const tool = this._toolRegistry.get(name);
|
||||
if (tool) {
|
||||
tools.push(tool);
|
||||
validToolNames.push(name);
|
||||
}
|
||||
}
|
||||
this.agent.setTools(tools);
|
||||
|
||||
// Rebuild base system prompt with new tool set
|
||||
if (this._rebuildSystemPrompt) {
|
||||
this._baseSystemPrompt = this._rebuildSystemPrompt(validToolNames);
|
||||
this.agent.setSystemPrompt(this._baseSystemPrompt);
|
||||
}
|
||||
}
|
||||
|
||||
/** Whether auto-compaction is currently running */
|
||||
|
|
@ -589,15 +608,25 @@ export class AgentSession {
|
|||
// Emit before_agent_start hook event
|
||||
if (this._hookRunner) {
|
||||
const result = await this._hookRunner.emitBeforeAgentStart(expandedText, options?.images);
|
||||
if (result?.message) {
|
||||
messages.push({
|
||||
role: "hookMessage",
|
||||
customType: result.message.customType,
|
||||
content: result.message.content,
|
||||
display: result.message.display,
|
||||
details: result.message.details,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
// Add all hook messages
|
||||
if (result?.messages) {
|
||||
for (const msg of result.messages) {
|
||||
messages.push({
|
||||
role: "hookMessage",
|
||||
customType: msg.customType,
|
||||
content: msg.content,
|
||||
display: msg.display,
|
||||
details: msg.details,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
}
|
||||
}
|
||||
// Apply hook systemPromptAppend on top of base prompt
|
||||
if (result?.systemPromptAppend) {
|
||||
this.agent.setSystemPrompt(`${this._baseSystemPrompt}\n\n${result.systemPromptAppend}`);
|
||||
} else {
|
||||
// Ensure we're using the base prompt (in case previous turn had appends)
|
||||
this.agent.setSystemPrompt(this._baseSystemPrompt);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,12 @@ import type {
|
|||
ToolResultEventResult,
|
||||
} from "./types.js";
|
||||
|
||||
/** Combined result from all before_agent_start handlers (internal) */
|
||||
interface BeforeAgentStartCombinedResult {
|
||||
messages?: NonNullable<BeforeAgentStartEventResult["message"]>[];
|
||||
systemPromptAppend?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener for hook errors.
|
||||
*/
|
||||
|
|
@ -485,14 +491,15 @@ export class HookRunner {
|
|||
|
||||
/**
|
||||
* Emit before_agent_start event to all hooks.
|
||||
* Returns the first message to inject (if any handler returns one).
|
||||
* Returns combined result: all messages and all systemPromptAppend strings concatenated.
|
||||
*/
|
||||
async emitBeforeAgentStart(
|
||||
prompt: string,
|
||||
images?: ImageContent[],
|
||||
): Promise<BeforeAgentStartEventResult | undefined> {
|
||||
): Promise<BeforeAgentStartCombinedResult | undefined> {
|
||||
const ctx = this.createContext();
|
||||
let result: BeforeAgentStartEventResult | undefined;
|
||||
const messages: NonNullable<BeforeAgentStartEventResult["message"]>[] = [];
|
||||
const systemPromptAppends: string[] = [];
|
||||
|
||||
for (const hook of this.hooks) {
|
||||
const handlers = hook.handlers.get("before_agent_start");
|
||||
|
|
@ -503,9 +510,16 @@ export class HookRunner {
|
|||
const event: BeforeAgentStartEvent = { type: "before_agent_start", prompt, images };
|
||||
const handlerResult = await handler(event, ctx);
|
||||
|
||||
// Take the first message returned
|
||||
if (handlerResult && (handlerResult as BeforeAgentStartEventResult).message && !result) {
|
||||
result = handlerResult as BeforeAgentStartEventResult;
|
||||
if (handlerResult) {
|
||||
const result = handlerResult as BeforeAgentStartEventResult;
|
||||
// Collect all messages
|
||||
if (result.message) {
|
||||
messages.push(result.message);
|
||||
}
|
||||
// Collect all systemPromptAppend strings
|
||||
if (result.systemPromptAppend) {
|
||||
systemPromptAppends.push(result.systemPromptAppend);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
|
|
@ -518,6 +532,14 @@ export class HookRunner {
|
|||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
// Return combined result
|
||||
if (messages.length > 0 || systemPromptAppends.length > 0) {
|
||||
return {
|
||||
messages: messages.length > 0 ? messages : undefined,
|
||||
systemPromptAppend: systemPromptAppends.length > 0 ? systemPromptAppends.join("\n\n") : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -608,6 +608,8 @@ export interface ToolResultEventResult {
|
|||
export interface BeforeAgentStartEventResult {
|
||||
/** Message to inject into context (persisted to session, visible in TUI) */
|
||||
message?: Pick<HookMessage, "customType" | "content" | "display" | "details">;
|
||||
/** Text to append to the system prompt for this agent run */
|
||||
systemPromptAppend?: string;
|
||||
}
|
||||
|
||||
/** Return type for session_before_switch handlers */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue