UNPKG

consortium

Version:

Remote control and session sharing CLI for AI coding agents

271 lines (266 loc) 11 kB
'use strict'; var killSwitch = require('./killSwitch-DwcqKgA9.cjs'); var persistence = require('./types-B_i6lpTn.cjs'); var capabilities = require('./capabilities-BsNjrlBG.cjs'); var React = require('react'); var ink = require('ink'); function createRunnerAttachments(opts) { const tag = opts.logTag ?? "runner"; let driveClient = null; if (opts.credentials.encryption?.type === "dataKey") { driveClient = new killSwitch.DriveClient( opts.credentials.token, { x25519SecretKey: opts.credentials.encryption.machineKey } ); } const scratchDir = new killSwitch.ScratchDir(opts.sessionId); const resolver = driveClient ? new killSwitch.AttachmentResolver(driveClient, scratchDir) : null; const pendingMessageLocalIds = []; const pendingDriveNodeRefs = []; const pendingAttachmentIds = []; return { get enabled() { return resolver !== null; }, bindBackend(backend) { try { backend.setScratchDir?.(scratchDir); } catch (err) { persistence.logger.debug(`[${tag}/attachments] bindBackend failed:`, err); } }, observeUserMessage(message) { const env = killSwitch.normalizeAttachmentEnvelope(message); if (driveClient && env.driveId && env.driveDek) { try { driveClient.setKnownDek( env.driveId, new Uint8Array(Buffer.from(env.driveDek, "base64")) ); } catch (err) { persistence.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); } }, async sendPrompt(backend, acpSessionId, text) { let resolved = []; if (killSwitch.attachmentsKillSwitchOff()) { pendingMessageLocalIds.length = 0; pendingDriveNodeRefs.length = 0; pendingAttachmentIds.length = 0; persistence.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); try { for (const lid of localIds) { resolved.push(...await resolver.resolveForMessage(opts.sessionId, lid)); } for (const ref of refs) { resolved.push(...await resolver.resolveByDriveNodeIds(ref.driveId, ref.nodeIds)); } if (ids.length > 0) { try { const bindings = await driveClient.listSessionAttachments(opts.sessionId); const want = new Set(ids); const matched = bindings.filter((b) => want.has(b.driveNodeId)); const byDrive = /* @__PURE__ */ new Map(); for (const b of matched) { if (!b.driveId) continue; const arr = byDrive.get(b.driveId) ?? []; arr.push(b.driveNodeId); byDrive.set(b.driveId, arr); } for (const [driveId, nodeIds] of byDrive) { resolved.push(...await resolver.resolveByDriveNodeIds(driveId, nodeIds)); } } catch (err) { persistence.logger.debug(`[${tag}/attachments] resolve attachmentIds via bindings failed:`, err); } } } catch (err) { persistence.logger.debug(`[${tag}/attachments] resolve failed (continuing without):`, err); resolved = []; } } else { pendingMessageLocalIds.length = 0; pendingDriveNodeRefs.length = 0; pendingAttachmentIds.length = 0; } if (backend.setAttachmentCapability && opts.agentId) { const model = opts.getCurrentModel?.() ?? null; try { backend.setAttachmentCapability(capabilities.resolveCapability(opts.agentId, model)); } catch (err) { persistence.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] = React.useState([]); const [confirmationMode, setConfirmationMode] = React.useState(false); const [actionInProgress, setActionInProgress] = React.useState(false); const [model, setModel] = React.useState(currentModel); const confirmationTimeoutRef = React.useRef(null); const { stdout } = ink.useStdout(); const terminalWidth = stdout.columns || 80; const terminalHeight = stdout.rows || 24; React.useEffect(() => { if (currentModel !== void 0 && currentModel !== model) { setModel(currentModel); } }, [currentModel]); React.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 = React.useCallback(() => { setConfirmationMode(false); if (confirmationTimeoutRef.current) { clearTimeout(confirmationTimeoutRef.current); confirmationTimeoutRef.current = null; } }, []); const setConfirmationWithTimeout = React.useCallback(() => { setConfirmationMode(true); if (confirmationTimeoutRef.current) { clearTimeout(confirmationTimeoutRef.current); } confirmationTimeoutRef.current = setTimeout(() => { resetConfirmation(); }, 15e3); }, [resetConfirmation]); ink.useInput(React.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(ink.Box, { flexDirection: "column", width: terminalWidth, height: terminalHeight }, /* @__PURE__ */ React.createElement( ink.Box, { flexDirection: "column", width: terminalWidth, height: terminalHeight - 4, borderStyle: "round", borderColor: "gray", paddingX: 1, overflow: "hidden" }, /* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React.createElement(ink.Text, { color: "cyan", bold: true }, "\u2728 Gemini Agent Messages"), /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", dimColor: true }, "\u2500".repeat(Math.min(terminalWidth - 4, 60)))), /* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column", height: terminalHeight - 10, overflow: "hidden" }, messages.length === 0 ? /* @__PURE__ */ React.createElement(ink.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(ink.Box, { key: msg.id, flexDirection: "column", marginBottom: index < array.length - 1 ? 1 : 0 }, /* @__PURE__ */ React.createElement(ink.Text, { color: getMessageColor(msg.type), dimColor: true }, formatMessage(msg))))) ), /* @__PURE__ */ React.createElement( ink.Box, { width: terminalWidth, borderStyle: "round", borderColor: actionInProgress ? "gray" : confirmationMode ? "red" : "cyan", paddingX: 2, justifyContent: "center", alignItems: "center", flexDirection: "column" }, /* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column", alignItems: "center" }, actionInProgress ? /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", bold: true }, "Exiting agent...") : confirmationMode ? /* @__PURE__ */ React.createElement(ink.Text, { color: "red", bold: true }, "\u26A0\uFE0F Press Ctrl-C again to exit the agent") : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(ink.Text, { color: "cyan", bold: true }, "\u2728 Gemini Agent Running \u2022 Ctrl-C to exit"), model && /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", dimColor: true }, "Model: ", model)), process.env.DEBUG && logPath && /* @__PURE__ */ React.createElement(ink.Text, { color: "gray", dimColor: true }, "Debug logs: ", logPath)) )); }; exports.GeminiDisplay = GeminiDisplay; exports.createRunnerAttachments = createRunnerAttachments;