mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-17 00:04:50 +00:00
feat(coding-agent): add event bus for tool/hook communication (#431)
* feat(coding-agent): add event bus for tool/hook communication Adds pi.events API enabling custom tools and hooks to communicate via pub/sub. Tools can emit events, hooks can listen. Shared EventBus instance created per session in createAgentSession(). - EventBus interface with emit() and on() methods - on() returns unsubscribe function - Threaded through hook and tool loaders - Documented in hooks.md and custom-tools.md * fix(coding-agent): wrap event handlers to catch errors * docs: note async handler error handling for event bus * feat(coding-agent): add sendMessage to tools, nextTurn delivery mode - Custom tools now have pi.sendMessage() for direct agent notifications - New deliverAs: 'nextTurn' queues messages for next user prompt - Fix: hooks and tools now share the same eventBus (was isolated before) * fix(coding-agent): nextTurn delivery should always queue, even when streaming
This commit is contained in:
parent
12805f61bd
commit
9c9e6822e3
13 changed files with 293 additions and 33 deletions
|
|
@ -155,6 +155,8 @@ export class AgentSession {
|
|||
private _steeringMessages: string[] = [];
|
||||
/** Tracks pending follow-up messages for UI display. Removed when delivered. */
|
||||
private _followUpMessages: string[] = [];
|
||||
/** Messages queued to be included with the next user prompt as context ("asides"). */
|
||||
private _pendingNextTurnMessages: HookMessage[] = [];
|
||||
|
||||
// Compaction state
|
||||
private _compactionAbortController: AbortController | undefined = undefined;
|
||||
|
|
@ -605,6 +607,12 @@ export class AgentSession {
|
|||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
// Inject any pending "nextTurn" messages as context alongside the user message
|
||||
for (const msg of this._pendingNextTurnMessages) {
|
||||
messages.push(msg);
|
||||
}
|
||||
this._pendingNextTurnMessages = [];
|
||||
|
||||
// Emit before_agent_start hook event
|
||||
if (this._hookRunner) {
|
||||
const result = await this._hookRunner.emitBeforeAgentStart(expandedText, options?.images);
|
||||
|
|
@ -752,11 +760,11 @@ export class AgentSession {
|
|||
*
|
||||
* @param message Hook message with customType, content, display, details
|
||||
* @param options.triggerTurn If true and not streaming, triggers a new LLM turn
|
||||
* @param options.deliverAs When streaming, use "steer" (default) for immediate or "followUp" to wait
|
||||
* @param options.deliverAs Delivery mode: "steer", "followUp", or "nextTurn"
|
||||
*/
|
||||
async sendHookMessage<T = unknown>(
|
||||
message: Pick<HookMessage<T>, "customType" | "content" | "display" | "details">,
|
||||
options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" },
|
||||
options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn" },
|
||||
): Promise<void> {
|
||||
const appMessage = {
|
||||
role: "hookMessage" as const,
|
||||
|
|
@ -766,18 +774,17 @@ export class AgentSession {
|
|||
details: message.details,
|
||||
timestamp: Date.now(),
|
||||
} satisfies HookMessage<T>;
|
||||
if (this.isStreaming) {
|
||||
// Queue for processing by agent loop
|
||||
if (options?.deliverAs === "nextTurn") {
|
||||
this._pendingNextTurnMessages.push(appMessage);
|
||||
} else if (this.isStreaming) {
|
||||
if (options?.deliverAs === "followUp") {
|
||||
this.agent.followUp(appMessage);
|
||||
} else {
|
||||
this.agent.steer(appMessage);
|
||||
}
|
||||
} else if (options?.triggerTurn) {
|
||||
// Send as prompt - agent loop will emit message events
|
||||
await this.agent.prompt(appMessage);
|
||||
} else {
|
||||
// Just append to agent state and session, no turn
|
||||
this.agent.appendMessage(appMessage);
|
||||
this.sessionManager.appendCustomMessageEntry(
|
||||
message.customType,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue