import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent"; import { writeToMailbox } from "./mailbox.js"; import { sanitizeName } from "./names.js"; import { getTeamDir } from "./paths.js"; import { TEAM_MAILBOX_NS } from "./protocol.js"; import { ensureTeamConfig } from "./team-config.js"; import type { TeamTask } from "./task-store.js"; import type { TeammateRpc } from "./teammate-rpc.js"; import type { TeamsStyle } from "./teams-style.js"; import { formatMemberDisplayName, getTeamsStrings } from "./teams-style.js"; export async function handleTeamSendCommand(opts: { ctx: ExtensionCommandContext; rest: string[]; teammates: Map; style: TeamsStyle; renderWidget: () => void; }): Promise { const { ctx, rest, teammates, style, renderWidget } = opts; const strings = getTeamsStrings(style); const nameRaw = rest[0]; const msg = rest.slice(1).join(" ").trim(); if (!nameRaw || !msg) { ctx.ui.notify("Usage: /team send ", "error"); return; } const name = sanitizeName(nameRaw); const t = teammates.get(name); if (!t) { ctx.ui.notify(`Unknown ${strings.memberTitle.toLowerCase()}: ${name}`, "error"); return; } if (t.status === "streaming") await t.followUp(msg); else await t.prompt(msg); ctx.ui.notify(`Sent to ${name}`, "info"); renderWidget(); } export async function handleTeamSteerCommand(opts: { ctx: ExtensionCommandContext; rest: string[]; teammates: Map; style: TeamsStyle; renderWidget: () => void; }): Promise { const { ctx, rest, teammates, style, renderWidget } = opts; const strings = getTeamsStrings(style); const nameRaw = rest[0]; const msg = rest.slice(1).join(" ").trim(); if (!nameRaw || !msg) { ctx.ui.notify("Usage: /team steer ", "error"); return; } const name = sanitizeName(nameRaw); const t = teammates.get(name); if (!t) { ctx.ui.notify(`Unknown ${strings.memberTitle.toLowerCase()}: ${name}`, "error"); return; } await t.steer(msg); ctx.ui.notify(`Steering sent to ${name}`, "info"); renderWidget(); } export async function handleTeamDmCommand(opts: { ctx: ExtensionCommandContext; rest: string[]; teamId: string; leadName: string; style: TeamsStyle; }): Promise { const { ctx, rest, teamId, leadName, style } = opts; // Parse --urgent flag from anywhere in the args const isUrgent = rest.includes("--urgent"); const filtered = rest.filter((a) => a !== "--urgent"); const nameRaw = filtered[0]; const msg = filtered.slice(1).join(" ").trim(); if (!nameRaw || !msg) { ctx.ui.notify("Usage: /team dm [--urgent] ", "error"); return; } const name = sanitizeName(nameRaw); await writeToMailbox(getTeamDir(teamId), TEAM_MAILBOX_NS, name, { from: leadName, text: msg, timestamp: new Date().toISOString(), ...(isUrgent ? { urgent: true } : {}), }); const verb = isUrgent ? "Urgent DM" : "DM"; ctx.ui.notify(`${verb} queued for ${formatMemberDisplayName(style, name)}`, "info"); } export async function handleTeamBroadcastCommand(opts: { ctx: ExtensionCommandContext; rest: string[]; teamId: string; teammates: Map; leadName: string; style: TeamsStyle; refreshTasks: () => Promise; getTasks: () => TeamTask[]; getTaskListId: () => string | null; }): Promise { const { ctx, rest, teamId, teammates, leadName, style, refreshTasks, getTasks, getTaskListId } = opts; const strings = getTeamsStrings(style); // Parse --urgent flag from anywhere in the args const isUrgent = rest.includes("--urgent"); const filtered = rest.filter((a) => a !== "--urgent"); const msg = filtered.join(" ").trim(); if (!msg) { ctx.ui.notify("Usage: /team broadcast [--urgent] ", "error"); return; } const teamDir = getTeamDir(teamId); const taskListId = getTaskListId(); const cfg = await ensureTeamConfig(teamDir, { teamId, taskListId: taskListId ?? teamId, leadName, style }); const recipients = new Set(); for (const m of cfg.members) { if (m.role === "worker") recipients.add(m.name); } for (const name of teammates.keys()) recipients.add(name); // Include task owners (helps reach manual tmux workers not tracked as RPC teammates). await refreshTasks(); for (const t of getTasks()) { if (t.owner && t.owner !== leadName) recipients.add(t.owner); } const names = Array.from(recipients).sort(); if (names.length === 0) { ctx.ui.notify(`No ${strings.memberTitle.toLowerCase()}s to broadcast to`, "warning"); return; } const ts = new Date().toISOString(); await Promise.all( names.map((name) => writeToMailbox(teamDir, TEAM_MAILBOX_NS, name, { from: leadName, text: msg, timestamp: ts, ...(isUrgent ? { urgent: true } : {}), }), ), ); const verb = isUrgent ? "Urgent broadcast" : "Broadcast"; ctx.ui.notify( `${verb} queued for ${names.length} ${strings.memberTitle.toLowerCase()}(s): ${names.map((n) => formatMemberDisplayName(style, n)).join(", ")}`, "info", ); }