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
This commit is contained in:
DexterFromLab 2026-03-11 14:53:39 +01:00
parent 1331d094b3
commit a158ed9544
19 changed files with 1918 additions and 39 deletions

View file

@ -49,6 +49,7 @@ interface QueryMessage {
claudeConfigDir?: string;
additionalDirectories?: string[];
worktreeName?: string;
extraEnv?: Record<string, string>;
}
interface StopMessage {
@ -73,7 +74,7 @@ async function handleMessage(msg: Record<string, unknown>) {
}
async function handleQuery(msg: QueryMessage) {
const { sessionId, prompt, cwd, maxTurns, maxBudgetUsd, resumeSessionId, permissionMode, settingSources, systemPrompt, model, claudeConfigDir, additionalDirectories, worktreeName } = msg;
const { sessionId, prompt, cwd, maxTurns, maxBudgetUsd, resumeSessionId, permissionMode, settingSources, systemPrompt, model, claudeConfigDir, additionalDirectories, worktreeName, extraEnv } = msg;
if (sessions.has(sessionId)) {
send({ type: 'error', sessionId, message: 'Session already running' });
@ -98,6 +99,12 @@ async function handleQuery(msg: QueryMessage) {
if (claudeConfigDir) {
cleanEnv['CLAUDE_CONFIG_DIR'] = claudeConfigDir;
}
// Inject extra environment variables (e.g. BTMSG_AGENT_ID for agent communication)
if (extraEnv) {
for (const [key, value] of Object.entries(extraEnv)) {
cleanEnv[key] = value;
}
}
try {
if (!claudePath) {

View file

@ -43,6 +43,7 @@ interface QueryMessage {
systemPrompt?: string;
model?: string;
providerConfig?: Record<string, unknown>;
extraEnv?: Record<string, string>;
}
interface StopMessage {
@ -67,7 +68,7 @@ async function handleMessage(msg: Record<string, unknown>) {
}
async function handleQuery(msg: QueryMessage) {
const { sessionId, prompt, cwd, maxTurns, resumeSessionId, permissionMode, model, providerConfig } = msg;
const { sessionId, prompt, cwd, maxTurns, resumeSessionId, permissionMode, model, providerConfig, extraEnv } = msg;
if (sessions.has(sessionId)) {
send({ type: 'error', sessionId, message: 'Session already running' });
@ -90,6 +91,12 @@ async function handleQuery(msg: QueryMessage) {
if (apiKey) {
cleanEnv['CODEX_API_KEY'] = apiKey;
}
// Inject extra environment variables (e.g. BTMSG_AGENT_ID for agent communication)
if (extraEnv) {
for (const [key, value] of Object.entries(extraEnv)) {
cleanEnv[key] = value;
}
}
// Dynamically import SDK — fails gracefully if not installed
let Codex: any;