UNPKG

consortium

Version:

Remote control and session sharing CLI for AI coding agents

261 lines (257 loc) • 10.5 kB
import { l as logger } from './types-vIstUf63.mjs'; import { e as createAttachmentDriveClient, f as ScratchDir, h as attachmentsKillSwitchOff, r as resolvePendingAttachments, A as AttachmentResolver, n as normalizeAttachmentEnvelope } from './index-Ucc63AlK.mjs'; import { r as resolveCapability } from './capabilities-Cq3eleV5.mjs'; import React, { useState, useRef, useEffect, useCallback } from 'react'; import { useStdout, useInput, Box, Text } from 'ink'; function createRunnerAttachments(opts) { const tag = opts.logTag ?? "runner"; const driveClient = createAttachmentDriveClient(opts.credentials); const scratchDir = new ScratchDir(opts.sessionId); const resolver = driveClient ? new AttachmentResolver(driveClient, scratchDir) : null; const pendingMessageLocalIds = []; const pendingDriveNodeRefs = []; const pendingAttachmentIds = []; const pendingRecords = []; return { get enabled() { return resolver !== null; }, bindBackend(backend) { try { backend.setScratchDir?.(scratchDir); } catch (err) { logger.debug(`[${tag}/attachments] bindBackend failed:`, err); } }, observeUserMessage(message) { const env = normalizeAttachmentEnvelope(message); if (driveClient && env.driveId && env.driveDek) { try { driveClient.setKnownDek( env.driveId, new Uint8Array(Buffer.from(env.driveDek, "base64")) ); } catch (err) { logger.debug(`[${tag}/attachments] setKnownDek failed:`, err); } } if (env.messageLocalId) pendingMessageLocalIds.push(env.messageLocalId); if (env.driveId && env.driveNodeIds.length > 0) { pendingDriveNodeRefs.push({ driveId: env.driveId, nodeIds: env.driveNodeIds }); } if (env.bindingOnlyNodeIds.length > 0) { pendingAttachmentIds.push(...env.bindingOnlyNodeIds); } if (env.records.length > 0) { pendingRecords.push(...env.records); if (driveClient) { for (const rec of env.records) { if (!rec.key) continue; try { driveClient.setKnownNodeKey(rec.nodeId, new Uint8Array(Buffer.from(rec.key, "base64"))); } catch (err) { logger.debug(`[${tag}/attachments] setKnownNodeKey failed:`, err); } } } } }, async sendPrompt(backend, acpSessionId, text) { let resolved = []; if (attachmentsKillSwitchOff()) { pendingMessageLocalIds.length = 0; pendingDriveNodeRefs.length = 0; pendingAttachmentIds.length = 0; pendingRecords.length = 0; logger.debug(`[${tag}/attachments] kill switch CONSORTIUM_ATTACHMENTS_V1 is off \u2014 dropping attachments`); await backend.sendPrompt(acpSessionId, text); return; } if (resolver && driveClient) { const localIds = pendingMessageLocalIds.splice(0); const refs = pendingDriveNodeRefs.splice(0); const ids = pendingAttachmentIds.splice(0); const records = pendingRecords.splice(0); try { resolved = await resolvePendingAttachments( resolver, driveClient, opts.sessionId, { localIds, refs, bindingOnlyIds: ids, records }, tag ); } catch (err) { logger.debug(`[${tag}/attachments] resolve failed (continuing without):`, err); resolved = []; } } else { pendingMessageLocalIds.length = 0; pendingDriveNodeRefs.length = 0; pendingAttachmentIds.length = 0; pendingRecords.length = 0; } if (backend.setAttachmentCapability && opts.agentId) { const model = opts.getCurrentModel?.() ?? null; try { backend.setAttachmentCapability(resolveCapability(opts.agentId, model)); } catch (err) { logger.debug(`[${tag}/attachments] resolveCapability failed:`, err); } } if (resolved.length > 0) { await backend.sendPrompt(acpSessionId, { text, attachments: resolved }); } else { await backend.sendPrompt(acpSessionId, text); } }, async dispose() { try { await scratchDir.cleanup(); } catch { } } }; } const GeminiDisplay = ({ messageBuffer, logPath, currentModel, onExit }) => { const [messages, setMessages] = useState([]); const [confirmationMode, setConfirmationMode] = useState(false); const [actionInProgress, setActionInProgress] = useState(false); const [model, setModel] = useState(currentModel); const confirmationTimeoutRef = useRef(null); const { stdout } = useStdout(); const terminalWidth = stdout.columns || 80; const terminalHeight = stdout.rows || 24; useEffect(() => { if (currentModel !== void 0 && currentModel !== model) { setModel(currentModel); } }, [currentModel]); useEffect(() => { setMessages(messageBuffer.getMessages()); const unsubscribe = messageBuffer.onUpdate((newMessages) => { setMessages(newMessages); const modelMessage = [...newMessages].reverse().find( (msg) => msg.type === "system" && msg.content.startsWith("[MODEL:") ); if (modelMessage) { const modelMatch = modelMessage.content.match(/\[MODEL:(.+?)\]/); if (modelMatch && modelMatch[1]) { const extractedModel = modelMatch[1]; setModel((prevModel) => { if (extractedModel !== prevModel) { return extractedModel; } return prevModel; }); } } }); return () => { unsubscribe(); if (confirmationTimeoutRef.current) { clearTimeout(confirmationTimeoutRef.current); } }; }, [messageBuffer]); const resetConfirmation = useCallback(() => { setConfirmationMode(false); if (confirmationTimeoutRef.current) { clearTimeout(confirmationTimeoutRef.current); confirmationTimeoutRef.current = null; } }, []); const setConfirmationWithTimeout = useCallback(() => { setConfirmationMode(true); if (confirmationTimeoutRef.current) { clearTimeout(confirmationTimeoutRef.current); } confirmationTimeoutRef.current = setTimeout(() => { resetConfirmation(); }, 15e3); }, [resetConfirmation]); useInput(useCallback(async (input, key) => { if (actionInProgress) return; if (key.ctrl && input === "c") { if (confirmationMode) { resetConfirmation(); setActionInProgress(true); await new Promise((resolve) => setTimeout(resolve, 100)); onExit?.(); } else { setConfirmationWithTimeout(); } return; } if (confirmationMode) { resetConfirmation(); } }, [confirmationMode, actionInProgress, onExit, setConfirmationWithTimeout, resetConfirmation])); const getMessageColor = (type) => { switch (type) { case "user": return "magenta"; case "assistant": return "cyan"; case "system": return "blue"; case "tool": return "yellow"; case "result": return "green"; case "status": return "gray"; default: return "white"; } }; const formatMessage = (msg) => { const lines = msg.content.split("\n"); const maxLineLength = terminalWidth - 10; return lines.map((line) => { if (line.length <= maxLineLength) return line; const chunks = []; for (let i = 0; i < line.length; i += maxLineLength) { chunks.push(line.slice(i, i + maxLineLength)); } return chunks.join("\n"); }).join("\n"); }; return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", width: terminalWidth, height: terminalHeight }, /* @__PURE__ */ React.createElement( Box, { flexDirection: "column", width: terminalWidth, height: terminalHeight - 4, borderStyle: "round", borderColor: "gray", paddingX: 1, overflow: "hidden" }, /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "cyan", bold: true }, "\u2728 Gemini Agent Messages"), /* @__PURE__ */ React.createElement(Text, { color: "gray", dimColor: true }, "\u2500".repeat(Math.min(terminalWidth - 4, 60)))), /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", height: terminalHeight - 10, overflow: "hidden" }, messages.length === 0 ? /* @__PURE__ */ React.createElement(Text, { color: "gray", dimColor: true }, "Waiting for messages...") : messages.filter((msg) => { if (msg.type === "system" && !msg.content.trim()) { return false; } if (msg.type === "system" && msg.content.startsWith("[MODEL:")) { return false; } if (msg.type === "system" && msg.content.startsWith("Using model:")) { return false; } return true; }).slice(-Math.max(1, terminalHeight - 10)).map((msg, index, array) => /* @__PURE__ */ React.createElement(Box, { key: msg.id, flexDirection: "column", marginBottom: index < array.length - 1 ? 1 : 0 }, /* @__PURE__ */ React.createElement(Text, { color: getMessageColor(msg.type), dimColor: true }, formatMessage(msg))))) ), /* @__PURE__ */ React.createElement( Box, { width: terminalWidth, borderStyle: "round", borderColor: actionInProgress ? "gray" : confirmationMode ? "red" : "cyan", paddingX: 2, justifyContent: "center", alignItems: "center", flexDirection: "column" }, /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", alignItems: "center" }, actionInProgress ? /* @__PURE__ */ React.createElement(Text, { color: "gray", bold: true }, "Exiting agent...") : confirmationMode ? /* @__PURE__ */ React.createElement(Text, { color: "red", bold: true }, "\u26A0\uFE0F Press Ctrl-C again to exit the agent") : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Text, { color: "cyan", bold: true }, "\u2728 Gemini Agent Running \u2022 Ctrl-C to exit"), model && /* @__PURE__ */ React.createElement(Text, { color: "gray", dimColor: true }, "Model: ", model)), process.env.DEBUG && logPath && /* @__PURE__ */ React.createElement(Text, { color: "gray", dimColor: true }, "Debug logs: ", logPath)) )); }; export { GeminiDisplay as G, createRunnerAttachments as c };