Add 5 pi extensions: pi-subagents, pi-crew, rpiv-pi, pi-interactive-shell, pi-intercom

This commit is contained in:
2026-05-08 15:59:25 +10:00
parent d0d1d9b045
commit 31b4110c87
457 changed files with 85157 additions and 0 deletions

View File

@@ -0,0 +1,216 @@
import type { OverlayHandle } from "@mariozechner/pi-tui";
import type { HeadlessDispatchMonitor } from "./headless-monitor.js";
import type { MonitorConfig, MonitorEventPayload, MonitorSessionState, MonitorTerminalReason } from "./types.js";
const MONITOR_HISTORY_LIMIT = 200;
/** Centralizes overlay, monitor, widget, and completion-suppression state for the extension runtime. */
export class InteractiveShellCoordinator {
private overlayOpen = false;
private overlayHandle: OverlayHandle | null = null;
private headlessMonitors = new Map<string, HeadlessDispatchMonitor>();
private monitorEventHistory = new Map<string, MonitorEventPayload[]>();
private monitorEventCounters = new Map<string, number>();
private monitorSessionState = new Map<string, MonitorSessionState>();
private pendingMonitorReason = new Map<string, MonitorTerminalReason>();
private bgWidgetCleanup: (() => void) | null = null;
private agentHandledCompletion = new Set<string>();
isOverlayOpen(): boolean {
return this.overlayOpen;
}
beginOverlay(): boolean {
if (this.overlayOpen) return false;
this.overlayOpen = true;
return true;
}
endOverlay(): void {
this.overlayOpen = false;
this.clearOverlayHandle();
}
focusOverlay(): void {
this.overlayHandle?.focus();
}
unfocusOverlay(): void {
this.overlayHandle?.unfocus();
}
isOverlayFocused(): boolean {
return this.overlayHandle?.isFocused() === true;
}
setOverlayHandle(handle: OverlayHandle): void {
this.overlayHandle = handle;
}
clearOverlayHandle(): void {
this.overlayHandle = null;
}
markAgentHandledCompletion(sessionId: string): void {
this.agentHandledCompletion.add(sessionId);
}
consumeAgentHandledCompletion(sessionId: string): boolean {
const had = this.agentHandledCompletion.has(sessionId);
this.agentHandledCompletion.delete(sessionId);
return had;
}
setMonitor(id: string, monitor: HeadlessDispatchMonitor): void {
this.headlessMonitors.set(id, monitor);
}
getMonitor(id: string): HeadlessDispatchMonitor | undefined {
return this.headlessMonitors.get(id);
}
deleteMonitor(id: string): void {
this.headlessMonitors.delete(id);
}
registerMonitorSession(sessionId: string, monitor: MonitorConfig, startedAt: Date): MonitorSessionState {
const state: MonitorSessionState = {
sessionId,
strategy: monitor.strategy ?? "stream",
triggerIds: monitor.triggers.map((trigger) => trigger.id),
status: "running",
eventCount: 0,
startedAt: startedAt.toISOString(),
};
this.monitorSessionState.set(sessionId, state);
return state;
}
markMonitorStopping(sessionId: string, reason: MonitorTerminalReason = "stopped"): void {
this.pendingMonitorReason.set(sessionId, reason);
}
consumePendingMonitorReason(sessionId: string): MonitorTerminalReason | undefined {
const reason = this.pendingMonitorReason.get(sessionId);
this.pendingMonitorReason.delete(sessionId);
return reason;
}
finalizeMonitorSession(
sessionId: string,
result: { exitCode: number | null; signal?: number },
reason: MonitorTerminalReason,
): MonitorSessionState | undefined {
const current = this.monitorSessionState.get(sessionId);
if (!current) return undefined;
const finalized: MonitorSessionState = {
...current,
status: "stopped",
endedAt: new Date().toISOString(),
terminalReason: reason,
exitCode: result.exitCode,
signal: result.signal,
};
this.monitorSessionState.set(sessionId, finalized);
this.pendingMonitorReason.delete(sessionId);
return finalized;
}
getMonitorSessionState(sessionId: string): MonitorSessionState | undefined {
return this.monitorSessionState.get(sessionId);
}
recordMonitorEvent(event: Omit<MonitorEventPayload, "eventId" | "timestamp">): MonitorEventPayload {
const nextId = (this.monitorEventCounters.get(event.sessionId) ?? 0) + 1;
this.monitorEventCounters.set(event.sessionId, nextId);
const recorded: MonitorEventPayload = {
...event,
eventId: nextId,
timestamp: new Date().toISOString(),
};
const existing = this.monitorEventHistory.get(event.sessionId) ?? [];
existing.push(recorded);
if (existing.length > MONITOR_HISTORY_LIMIT) {
existing.splice(0, existing.length - MONITOR_HISTORY_LIMIT);
}
this.monitorEventHistory.set(event.sessionId, existing);
const currentState = this.monitorSessionState.get(event.sessionId);
if (currentState) {
this.monitorSessionState.set(event.sessionId, {
...currentState,
eventCount: nextId,
lastEventId: recorded.eventId,
lastEventAt: recorded.timestamp,
lastTriggerId: recorded.triggerId,
});
}
return recorded;
}
getMonitorEvents(sessionId: string, options?: { limit?: number; offset?: number; sinceEventId?: number; triggerId?: string }): {
events: MonitorEventPayload[];
total: number;
limit: number;
offset: number;
sinceEventId?: number;
triggerId?: string;
} {
let events = this.monitorEventHistory.get(sessionId) ?? [];
const sinceEventId = options?.sinceEventId !== undefined ? Math.max(0, Math.trunc(options.sinceEventId)) : undefined;
if (sinceEventId !== undefined) {
events = events.filter((event) => event.eventId > sinceEventId);
}
const triggerId = options?.triggerId?.trim();
if (triggerId) {
events = events.filter((event) => event.triggerId === triggerId);
}
const total = events.length;
const limit = Math.max(1, Math.trunc(options?.limit ?? 20));
const offset = Math.max(0, Math.trunc(options?.offset ?? 0));
const end = Math.max(0, total - offset);
const start = Math.max(0, end - limit);
return {
events: events.slice(start, end),
total,
limit,
offset,
sinceEventId,
triggerId,
};
}
clearMonitorEvents(sessionId: string): void {
this.monitorEventHistory.delete(sessionId);
this.monitorEventCounters.delete(sessionId);
this.monitorSessionState.delete(sessionId);
this.pendingMonitorReason.delete(sessionId);
}
disposeMonitor(id: string): void {
const monitor = this.headlessMonitors.get(id);
if (!monitor) return;
monitor.dispose();
this.headlessMonitors.delete(id);
}
disposeAllMonitors(): void {
for (const monitor of this.headlessMonitors.values()) {
monitor.dispose();
}
this.headlessMonitors.clear();
}
replaceBackgroundWidgetCleanup(cleanup: (() => void) | null): void {
this.bgWidgetCleanup?.();
this.bgWidgetCleanup = cleanup;
}
clearBackgroundWidget(): void {
this.bgWidgetCleanup?.();
this.bgWidgetCleanup = null;
}
}