BTerminal/v2/src/lib/adapters/agent-bridge.ts
DexterFromLab a158ed9544 feat(orchestration): multi-agent communication, unified agents, env passthrough
- btmsg: admin role (tier 0), channel messaging (create/list/send/history),
  admin global feed, mark-read conversations
- Rust btmsg module: admin bypass, channels, feed, 8 new Tauri commands
- CommsTab: sidebar chat interface with activity feed, DMs, channels (Ctrl+M)
- Agent unification: Tier 1 agents rendered as ProjectBoxes via agentToProject()
  converter, getAllWorkItems() combines agents + projects in ProjectGrid
- GroupAgentsPanel: click-to-navigate agents to their ProjectBox
- Agent system prompts: generateAgentPrompt() builds comprehensive introductory
  context (role, environment, team, btmsg/bttask docs, workflow instructions)
- AgentSession passes group context to prompt generator via $derived.by()
- BTMSG_AGENT_ID env var passthrough: extra_env field flows through full chain
  (agent-bridge → Rust AgentQueryOptions → NDJSON → sidecar runners → cleanEnv)
- workspace store: updateAgent() for Tier 1 agent config persistence
2026-03-11 14:53:39 +01:00

77 lines
2.3 KiB
TypeScript

// Agent Bridge — Tauri IPC adapter for sidecar communication
// Mirrors pty-bridge.ts pattern: invoke for commands, listen for events
import { invoke } from '@tauri-apps/api/core';
import { listen, type UnlistenFn } from '@tauri-apps/api/event';
import type { ProviderId } from '../providers/types';
export interface AgentQueryOptions {
provider?: ProviderId;
session_id: string;
prompt: string;
cwd?: string;
max_turns?: number;
max_budget_usd?: number;
resume_session_id?: string;
permission_mode?: string;
setting_sources?: string[];
system_prompt?: string;
model?: string;
claude_config_dir?: string;
additional_directories?: string[];
/** When set, agent runs in a git worktree for isolation */
worktree_name?: string;
provider_config?: Record<string, unknown>;
/** Extra environment variables injected into the agent process (e.g. BTMSG_AGENT_ID) */
extra_env?: Record<string, string>;
remote_machine_id?: string;
}
export async function queryAgent(options: AgentQueryOptions): Promise<void> {
if (options.remote_machine_id) {
const { remote_machine_id: machineId, ...agentOptions } = options;
return invoke('remote_agent_query', { machineId, options: agentOptions });
}
return invoke('agent_query', { options });
}
export async function stopAgent(sessionId: string, remoteMachineId?: string): Promise<void> {
if (remoteMachineId) {
return invoke('remote_agent_stop', { machineId: remoteMachineId, sessionId });
}
return invoke('agent_stop', { sessionId });
}
export async function isAgentReady(): Promise<boolean> {
return invoke<boolean>('agent_ready');
}
export async function restartAgent(): Promise<void> {
return invoke('agent_restart');
}
export interface SidecarMessage {
type: string;
sessionId?: string;
event?: Record<string, unknown>;
message?: string;
exitCode?: number | null;
signal?: string | null;
}
export async function onSidecarMessage(
callback: (msg: SidecarMessage) => void,
): Promise<UnlistenFn> {
return listen<SidecarMessage>('sidecar-message', (event) => {
const payload = event.payload;
if (typeof payload !== 'object' || payload === null) return;
callback(payload as SidecarMessage);
});
}
export async function onSidecarExited(callback: () => void): Promise<UnlistenFn> {
return listen('sidecar-exited', () => {
callback();
});
}