mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 17:00:59 +00:00
Hook API: replace send() with sendMessage(), add appendEntry() and registerCommand()
Breaking changes to Hook API: - pi.send(text, attachments?) replaced with pi.sendMessage(message, triggerTurn?) - Creates CustomMessageEntry instead of user messages - Properly handles queuing during streaming via agent loop - Supports optional turn triggering when idle - New pi.appendEntry(customType, data?) for hook state persistence - New pi.registerCommand(name, options) for custom slash commands - Handler types renamed: SendHandler -> SendMessageHandler, new AppendEntryHandler Implementation: - AgentSession.sendHookMessage() handles all three cases: - Streaming: queues message with _hookData marker, agent loop processes it - Not streaming + triggerTurn: appends to state/session, calls agent.continue() - Not streaming + no trigger: appends to state/session only - message_end handler routes based on _hookData presence to correct persistence - HookRunner gains getRegisteredCommands() and getCommand() methods New types: HookMessage<T>, RegisteredCommand, CommandContext
This commit is contained in:
parent
d43a5e47a1
commit
ba185b0571
13 changed files with 412 additions and 77 deletions
|
|
@ -14,7 +14,7 @@
|
|||
*/
|
||||
|
||||
import type { Agent, AgentEvent, AgentState, AppMessage, Attachment, ThinkingLevel } from "@mariozechner/pi-agent-core";
|
||||
import type { AssistantMessage, Message, Model, TextContent } from "@mariozechner/pi-ai";
|
||||
import type { AssistantMessage, ImageContent, Message, Model, TextContent } from "@mariozechner/pi-ai";
|
||||
import { isContextOverflow, modelsAreEqual, supportsXhigh } from "@mariozechner/pi-ai";
|
||||
import { getAuthPath } from "../config.js";
|
||||
import { type BashResult, executeBash as executeBashCommand } from "./bash-executor.js";
|
||||
|
|
@ -27,7 +27,7 @@ import {
|
|||
} from "./compaction.js";
|
||||
import type { LoadedCustomTool, SessionEvent as ToolSessionEvent } from "./custom-tools/index.js";
|
||||
import { exportSessionToHtml } from "./export-html.js";
|
||||
import type { HookRunner, SessionEventResult, TurnEndEvent, TurnStartEvent } from "./hooks/index.js";
|
||||
import type { HookMessage, HookRunner, SessionEventResult, TurnEndEvent, TurnStartEvent } from "./hooks/index.js";
|
||||
import type { BashExecutionMessage } from "./messages.js";
|
||||
import type { ModelRegistry } from "./model-registry.js";
|
||||
import type { CompactionEntry, SessionManager } from "./session-manager.js";
|
||||
|
|
@ -101,6 +101,13 @@ export interface SessionStats {
|
|||
cost: number;
|
||||
}
|
||||
|
||||
/** Internal marker for hook messages queued through the agent loop */
|
||||
interface HookMessageData {
|
||||
customType: string;
|
||||
display: boolean;
|
||||
details?: unknown;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Constants
|
||||
// ============================================================================
|
||||
|
|
@ -211,7 +218,21 @@ export class AgentSession {
|
|||
|
||||
// Handle session persistence
|
||||
if (event.type === "message_end") {
|
||||
this.sessionManager.appendMessage(event.message);
|
||||
// Check if this is a hook message (has _hookData marker)
|
||||
type HookAppMessage = AppMessage & { _hookData?: HookMessageData; content: (TextContent | ImageContent)[] };
|
||||
const hookMessage = event.message as HookAppMessage;
|
||||
if (hookMessage._hookData) {
|
||||
// Persist as CustomMessageEntry
|
||||
this.sessionManager.appendCustomMessageEntry(
|
||||
hookMessage._hookData.customType,
|
||||
hookMessage.content,
|
||||
hookMessage._hookData.display,
|
||||
hookMessage._hookData.details,
|
||||
);
|
||||
} else {
|
||||
// Regular message - persist as SessionMessageEntry
|
||||
this.sessionManager.appendMessage(event.message);
|
||||
}
|
||||
|
||||
// Track assistant message for auto-compaction (checked on agent_end)
|
||||
if (event.message.role === "assistant") {
|
||||
|
|
@ -473,6 +494,60 @@ export class AgentSession {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a hook message to the session. Creates a CustomMessageEntry.
|
||||
*
|
||||
* Handles three cases:
|
||||
* - Streaming: queues message, processed when loop pulls from queue
|
||||
* - Not streaming + triggerTurn: appends to state/session, starts new turn
|
||||
* - Not streaming + no trigger: appends to state/session, no turn
|
||||
*
|
||||
* @param message Hook message with customType, content, display, details
|
||||
* @param triggerTurn If true and not streaming, triggers a new LLM turn
|
||||
*/
|
||||
async sendHookMessage<T = unknown>(message: HookMessage<T>, triggerTurn?: boolean): Promise<void> {
|
||||
// Normalize content to array format for the AppMessage
|
||||
const content: (TextContent | ImageContent)[] =
|
||||
typeof message.content === "string" ? [{ type: "text", text: message.content }] : message.content;
|
||||
|
||||
// Create AppMessage with _hookData marker for routing in message_end handler
|
||||
const appMessage: AppMessage & { _hookData: HookMessageData } = {
|
||||
role: "user",
|
||||
content,
|
||||
timestamp: Date.now(),
|
||||
_hookData: {
|
||||
customType: message.customType,
|
||||
display: message.display,
|
||||
details: message.details,
|
||||
},
|
||||
};
|
||||
|
||||
if (this.isStreaming) {
|
||||
// Queue for processing by agent loop
|
||||
await this.agent.queueMessage(appMessage);
|
||||
} else if (triggerTurn) {
|
||||
// Append to agent state and session, then trigger a turn
|
||||
this.agent.appendMessage(appMessage);
|
||||
this.sessionManager.appendCustomMessageEntry(
|
||||
message.customType,
|
||||
message.content,
|
||||
message.display,
|
||||
message.details,
|
||||
);
|
||||
// Start a new turn - agent.continue() works because last message is user role
|
||||
await this.agent.continue();
|
||||
} else {
|
||||
// Just append to agent state and session, no turn
|
||||
this.agent.appendMessage(appMessage);
|
||||
this.sessionManager.appendCustomMessageEntry(
|
||||
message.customType,
|
||||
message.content,
|
||||
message.display,
|
||||
message.details,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear queued messages and return them.
|
||||
* Useful for restoring to editor when user aborts.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue