commit 80f104e90154e98cc73e99a20ea9e2717cfd65ce Author: applefather.eth <161012565+applefather-eth@users.noreply.github.com> Date: Thu May 14 11:35:14 2026 +0700 Prepare Obsidian community plugin package diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..23ab61b --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +dist/ +.DS_Store +node_modules/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fec8889 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 OpenAgentMarket + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..da68211 --- /dev/null +++ b/README.md @@ -0,0 +1,64 @@ +# OpenAgent + +OpenAgent is an Obsidian plugin for running local AI agent workflows from your vault and Canvas. + +It connects Obsidian to a local OpenAgent daemon so you can start Codex tasks from selected Canvas nodes or notes, track active threads, write results back to Canvas, and keep agent work anchored in your vault. + +## Requirements + +- Obsidian desktop +- Community plugins enabled +- A local OpenAgent daemon running on your machine +- Codex Desktop or a compatible local Codex/OpenAgent setup + +OpenAgent is desktop-only because it uses local filesystem access and a local daemon connection. + +## Install + +After this plugin is published in the Obsidian Community directory: + +1. Open Obsidian. +2. Go to **Settings -> Community plugins**. +3. Search for **OpenAgent**. +4. Install and enable the plugin. + +For manual testing, download `main.js`, `manifest.json`, and `styles.css` from a GitHub release and place them in: + +```text +.obsidian/plugins/openagent/ +``` + +Then enable **OpenAgent** in **Settings -> Community plugins**. + +## Local Daemon + +OpenAgent expects a local daemon to be available. The plugin can start the daemon when configured, or you can run it manually from the OpenAgent project: + +```bash +pnpm dev:daemon +``` + +The plugin stores local daemon configuration under your home directory at `.openagent/daemon-config.json`. + +## Privacy And Security + +OpenAgent is local-first. The plugin communicates with a daemon running on your machine and sends the selected note or Canvas context you choose to use for an agent task. + +The plugin may: + +- Read selected notes and Canvas nodes in your vault. +- Write task results, follow-up nodes, and metadata back to your vault. +- Read and write local OpenAgent configuration under `.openagent`. +- Communicate with a local HTTP daemon using an authentication token. + +The plugin does not include its own payment system. It may connect to local tools or services that require separate accounts, API keys, subscriptions, or usage-based billing. + +## Development + +This repository is the publishable Obsidian Community plugin package. The broader OpenAgent project lives at: + +https://github.com/openagentmarket/openagent + +## License + +MIT diff --git a/logo.png b/logo.png new file mode 100644 index 0000000..aec405e Binary files /dev/null and b/logo.png differ diff --git a/main.js b/main.js new file mode 100644 index 0000000..03e08a5 --- /dev/null +++ b/main.js @@ -0,0 +1,12338 @@ +"use strict"; + +const crypto = require("crypto"); +const fs = require("fs"); +const http = require("http"); +const os = require("os"); +const path = require("path"); +const { spawn } = require("child_process"); +const { fileURLToPath, pathToFileURL } = require("url"); +const { + ButtonComponent, + editorInfoField, + ItemView, + Modal, + Notice, + Plugin, + PluginSettingTab, + Setting, + setIcon, + setTooltip, + TextComponent, + TFile, +} = require("obsidian"); +let EditorView = null; +try { + ({ EditorView } = require("@codemirror/view")); +} catch { + EditorView = null; +} + +const VIEW_TYPE = "openagent-view"; +const VOICE_GRAPH_VIEW_TYPE = "openagent-voice-graph-view"; +const DAEMON_CONFIG_PATH = path.join(os.homedir(), ".openagent", "daemon-config.json"); +const DAEMON_LOG_PATH = path.join(os.homedir(), ".openagent", "daemon.log"); +const PLUGIN_LOGO_FILE_NAME = "logo.png"; +const PLUGIN_LOGO_PATH = path.join(__dirname, PLUGIN_LOGO_FILE_NAME); +const VAULT_PET_MASCOT_FILE_NAME = "vault-pet-mascot.png"; +const VAULT_PET_MASCOT_PATH = path.join(__dirname, VAULT_PET_MASCOT_FILE_NAME); +const DEFAULT_PLUGIN_LOGO_DATA_URL = "data:image/png;base64,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"; +const DEFAULT_VAULT_PET_MASCOT_DATA_URL = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAYAAAD0eNT6AAEAAElEQVR4nOz9B7xt910fiH5XX7vvfco9t+jqXl0VW10usiVjW9iWC8U2tkcyZQJJSGIySd6bPEJIQhwhSiBA5r2XNyQwmc+kkDBgOc4L1SGJHdkYy8hgXHCTLOn20/fZffU1n+/v91/7HAPJEGLAktZPPr7nnrL3Wmvvu37tW4A66qijjjrqqKOOOuqoo4466qijjjrqqKOOOuqoo4466qijjjrqqKOOOuqoo4466qijjjrqqKOOOuqoo4466qijjjrqqKOOOuqoo4466qijjjrqqKOOOuqoo4466qijjjrqqKOOOuqoo4466qijjjrqqKOOOuqoo4466qijjjrqqKOOOuqoo4466qijjjrqqKOOOuqoo4466qijjjrqqKOOOuqoo4466qijjjrqqKOOOuqoo4466qijjjrqqKOOOuqoo4466qijjjrqqKOOOuqoo4466qijjjrqqKOOOuqoo4466qijjjrqqKOOOuqoo4466qijjjrqqKOOOuqoo4466qijjjrqqKOOOuqoo4466qijjjrqqKOOOuqoo4466qijjjrqqKOOOuqoo4466qijjjrqqKOOOuqoo4466qijjjrqqKOOOuqoo4466qijjjrqqKOOOuqoo4466qijjjrqqKOOOuqoo4466qijjjrqqKOOOuqoo4466qijjjrqqKOOOuqoo4466qijjjrqqKOOOuqoo446vqrD+tM+gDrqeJ7+myv/ED/bANAG4ANwze+uArgdwJ2WZW0EjSDwfR+NRgjfC+B4DlzXhe/48Dwfrm/DcW3LtfyO7djHLdc66zhO2ypsPRAbKMoSZZbvZEVxPsvz3TxLF1lSlGVZIE1jJFmKLM2QxAniJEYUR1YSJXmapnMAFwB8AsDnACwA5OaDn88AJF+ha1FHHXV8haMuAOqo40/239gflOyY4PsAmgAC8/dzlmW9uN1u39jutLutVstttpthq9FeaXfb1/R7/UG/30ev30Ov20Gn3ULYbCIMQ/0IAgR+ANdx4EpR4KO0LDiOA9u2YVs8MD20vKyOKkee58iyHGmSo8hTRPECcZIgimLMZgvMZ3OMx2NMJhPs7u5jb7iD6WRyZTqZXokW8/FkMksmk0kynoz38iz/XVMcbAJgsTACMAGQ/RGvUx11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CKPhEMNev+50Ooz6dcIwQOiHohj0vEAmf+kHWkMeIQkq4Y91Xm7tqhdEwCU8Mwj4ESotUCKgRO4q2bDl44sCwAQdqRpAJQmc6ilBlKwD8ebRGGQ5BNApsEGTV5UU/myxwHyRYXpx4R6fnODoeB/HR0dnJydnt05Ojt4s8+xLAL48GAzujsfjM/N6WAMfC4vvMWwDYGHx3jcC+A4ZIZuBa67r/lgYdf5iFEd/PummPzToj/pLwyUMh8vo9bpNN02bMIkQhjHCMHAY0sMTgeO5DhUE9ATwGCpEYx8RAegJgRJAdfpj0A/9COj6b+KMDZNfij/leS4Z+hpQpG/QBIhxwCQckjognrwoGQ9clAzkaYo8Q5bluJhO3bPTU4xPT3FycoKz05OTyeTi7YvpxRv5fP4VAF8yjn1t0ScME9Hq+C0svtewDYCFxfsra6D5Ls3Adbj4S54f/4U4jH8kSdKPJ51uv9freb3h0On3Buh1UqRp3IRxiDiKmjCKnJDZAF4gun8nUEtiXxIHTf6A0f4Zkr9xEiA5T537eRpQowBO/Ercq7nXrxm/WyEv66bKC6cocsyzBaaLhTO5uMDR8RHOz85wfnZ6cTGdvJNli7em55P7TVORyPcNs94/+y72yiYUwMLC4vsB2wBYWLw//06636Ug6pkA+GG47qUgiJb9INwJo+TPJVG4EyVpnHY7Xr8/8Dppik6SSnSwHzEkiIFAAQLfF9Mfkgs9GvC6YsEjX9R3vPZ+z2uAxPFyl89pPy8r5EWOPFuoD3+eYzqdYja5qGaTaTWZTJr5YpLPF/O7i2zxWj7PbldVcRvAa2bKPweQf8fzfPa+bwu/hcX3GbYBsLB4/+KZNKD/z0rcNed8Egmv6ZbAX/M8bz3w/Bcc3/+RIAhXkzjygyhs2ACEbTpgECCMfNfj0cBzQyfwHFr5iud+Wed1U5VlWVdlwYm/BCd8Fv8sW4grX1EUjAA+rMv8taqs3iiq4qguy6mZ6t81zP3xdxT8Zx83YYu+hcV7DNsAWFh88P6u/lGFUwZ5ACOjLqDcsPPUCID/C+GF4iQYeDVWajg/CAfPo4bvwLnreHijQr2H2llUUr4Z5JOTCfjs154D2DeF/ghcFvzRN/tnCY9/3GO3sLD4PsM2ABYWH96G4E/yOQYA1gz57hDAyZ8BAc9K9ywsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCzwp8T/C6pj/dcwsvlUAAAAAElFTkSuQmCC"; +const DEFAULT_SETTINGS = Object.freeze({ + automationFolder: "", + daemonLaunchCommand: "", + daemonLaunchCwd: "", + daemonSandboxMode: "workspace-write", + enableDebugLogging: false, + enableDevSmokeRequests: false, + enableVaultPet: true, + stopActiveTaskHotkey: "", + workspaceRoot: "Workspaces", +}); +const REPO_WORKSPACE_DIR_NAME = ".openagent"; +const WORKSPACE_CONFIG_FILE_NAME = "workspace.json"; +const DEFAULT_WORKSPACE_CANVAS_FILE = "Main.canvas"; +const WORKSPACE_AUTOMATION_DIR_NAME = "Automations"; +const DEFAULT_DAILY_VAULT_REVIEW_AUTOMATION_FILE = "Daily Vault Review.md"; +const DEFAULT_DAILY_VAULT_REVIEW_CWD = "/Users/applefather/Documents/Applefather"; +const WORKSPACE_INDEX_WAIT_MS = 4_000; +const WORKSPACE_INDEX_POLL_MS = 100; +const CANVAS_EDITOR_TEXT_CACHE_TTL_MS = 10_000; +const DAEMON_SANDBOX_MODE_OPTIONS = Object.freeze({ + WORKSPACE_WRITE: "workspace-write", + DANGER_FULL_ACCESS: "danger-full-access", +}); +const RECENT_SELECTION_TTL_MS = 15_000; +const DEV_SMOKE_REQUEST_RELATIVE_PATH = path.join(".openagent", "smoke-request.json"); +const DEV_SMOKE_RESULT_RELATIVE_PATH = path.join(".openagent", "smoke-result.json"); +const DEBUG_LOG_RELATIVE_PATH = path.join(".openagent", "new-thread-debug.jsonl"); +const DEV_SMOKE_POLL_MS = 1_000; +const MAX_DEBUG_EVENTS = 30; +const DAEMON_STATUS_POLL_MS = 10_000; +const ACTIVE_TASK_STREAM_RECONNECT_DELAY_MS = 1_500; +const RESULT_NODE_Y_GAP = 40; +const RESULT_NODE_MIN_HEIGHT = 160; +const RESULT_NODE_MAX_HEIGHT = 520; +const RESULT_NODE_DEFAULT_WIDTH = 420; +const RESULT_NODE_MIN_WIDTH = 320; +const RESULT_NODE_MAX_WIDTH = 720; +const RESULT_NODE_CHARS_PER_LINE = 52; +const FOLLOW_UP_NODE_DEFAULT_HEIGHT = 120; +const CANVAS_LAYOUT_BASE_X = 80; +const CANVAS_LAYOUT_BASE_Y = 80; +const CANVAS_LAYOUT_X_GAP = 180; +const CANVAS_LAYOUT_Y_GAP = 80; +const CANVAS_LAYOUT_COMPONENT_GAP = 220; +const CANVAS_LAYOUT_MIN_WIDTH = 260; +const CANVAS_LAYOUT_MIN_HEIGHT = 120; +const RUNNING_CANVAS_NODE_COLOR = "3"; +const COMPLETED_CANVAS_NODE_COLOR = "#086ddd"; +const CANVAS_NEW_THREAD_BUTTON_ID = "oa-canvas-new-thread-button"; +const CANVAS_FOLLOW_UP_NODE_BUTTON_ID = "oa-canvas-follow-up-node-button"; +const CANVAS_FORK_NODE_BUTTON_ID = "oa-canvas-fork-node-button"; +const AUTO_RUN_TRIGGER_CANVAS_NODE_COLORS = Object.freeze([ + "4", + "#22c55e", + "#16a34a", + "#008000", +]); +const OPENAGENT_CANVAS_SCHEMA_VERSION = 1; +const CHAT_MESSAGE_PAGE_SIZE = 20; +const SETTINGS_RECENT_TASKS_PAGE_SIZE = 10; +const PANEL_TAB_OPTIONS = Object.freeze({ + ACTIVE_TASK: "active-task", + WORKSPACE_SETTINGS: "workspace-settings", + HEARTBEAT: "heartbeat", + WORKSPACE: "workspace", + TASK_LIST: "task-list", + ARCHIVED_TASK_LIST: "archived-task-list", + DEBUG: "debug", +}); +const DAEMON_CONNECTION_STATES = Object.freeze({ + UNKNOWN: "unknown", + CHECKING: "checking", + ONLINE: "online", + OFFLINE: "offline", +}); +const HOTKEY_CAPTURE_INPUT_CLASS = "oa-hotkey-capture-input"; +const CANVAS_SELECTION_TRACKING_PATCH_SYMBOL = Symbol("openagentCanvasSelectionTrackingPatch"); +const VAULT_PET_WIDTH = 118; +const VAULT_PET_HEIGHT = 142; +const VAULT_PET_MARGIN = 14; +const VAULT_PET_ACTIVITY_COOLDOWN_MS = 1_200; +const VOICE_GRAPH_MAX_NODES = 120; +const VOICE_GRAPH_MAX_EDGES = 260; +const VOICE_GRAPH_MIN_NODE_RADIUS = 4; +const VOICE_GRAPH_MAX_NODE_RADIUS = 24; +const VAULT_PET_IDLE_PHRASES = Object.freeze([ + "Bloop. I live here now.", + "Your vault has snacks?", + "One badge, many thoughts.", + "I am guarding this thought.", + "Tiny splash break.", + "Today we make the graph wiggle.", +]); +const VAULT_PET_ACTIVITY_PHRASES = Object.freeze({ + create: [ + "New note! Fresh plankton.", + "I saw that file hatch.", + "A baby idea just appeared.", + ], + modify: [ + "Nice. The vault is breathing.", + "Ink deployed.", + "That note got juicier.", + ], + delete: [ + "Poof. I will not ask questions.", + "The deep takes one back.", + "Clean water, clean vault.", + ], + rename: [ + "New name, same soul.", + "Identity upgrade detected.", + "That file got a little hat.", + ], + workspace: [ + "New room smell.", + "I am recalibrating my halo.", + "Different pane, same tiny ocean.", + ], + pet: [ + "Hehe. Again.", + "Critical morale upgrade.", + "Tentacle high five.", + "I will remember this.", + ], +}); + +function shortHash(value) { + return crypto.createHash("sha256").update(String(value || ""), "utf8").digest("hex").slice(0, 12); +} + +function sleep(ms) { + return new Promise((resolve) => window.setTimeout(resolve, ms)); +} + +function pickRandomItem(items) { + if (!Array.isArray(items) || items.length === 0) { + return ""; + } + return items[Math.floor(Math.random() * items.length)]; +} + +function isUsableClientRect(rect) { + return Boolean( + rect + && Number.isFinite(rect.left) + && Number.isFinite(rect.top) + && rect.width > 0 + && rect.height > 0 + && rect.right >= 0 + && rect.bottom >= 0 + && rect.left <= window.innerWidth + && rect.top <= window.innerHeight + ); +} + +function combineClientRects(rects) { + const usableRects = Array.from(rects || []).filter(isUsableClientRect); + if (usableRects.length === 0) { + return null; + } + const left = Math.min(...usableRects.map((rect) => rect.left)); + const top = Math.min(...usableRects.map((rect) => rect.top)); + const right = Math.max(...usableRects.map((rect) => rect.right)); + const bottom = Math.max(...usableRects.map((rect) => rect.bottom)); + return { + left, + top, + right, + bottom, + width: right - left, + height: bottom - top, + }; +} + +function patchObjectMethod(target, methodName, wrap) { + if (!target || typeof target !== "object") { + return null; + } + + const original = target[methodName]; + if (typeof original !== "function" || typeof wrap !== "function") { + return null; + } + + const wrapped = wrap(original); + if (typeof wrapped !== "function" || wrapped === original) { + return null; + } + + target[methodName] = wrapped; + return () => { + if (target[methodName] === wrapped) { + target[methodName] = original; + } + }; +} + +function shellEscape(value) { + return `'${String(value || "").replace(/'/g, `'\\''`)}'`; +} + +function slugifyWorkspaceName(value) { + return String(value || "") + .trim() + .toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-+|-+$/g, "") + || "workspace"; +} + +function joinVaultPath(...parts) { + return parts + .map((part) => String(part || "").replace(/^\/+|\/+$/g, "")) + .filter(Boolean) + .join("/"); +} + +function parseAutomationScalar(value) { + const raw = String(value || "").trim(); + if (!raw) { + return ""; + } + if ((raw.startsWith('"') && raw.endsWith('"')) || (raw.startsWith("'") && raw.endsWith("'"))) { + return raw.slice(1, -1); + } + if (raw === "true") { + return true; + } + if (raw === "false") { + return false; + } + return raw; +} + +function parseAutomationFrontmatter(raw) { + const text = String(raw || ""); + if (!text.startsWith("---\n")) { + return { data: {}, body: text }; + } + + const endIndex = text.indexOf("\n---", 4); + if (endIndex < 0) { + return { data: {}, body: text }; + } + + const frontmatter = text.slice(4, endIndex).trim(); + const body = text.slice(endIndex + 4).replace(/^\n+/, ""); + const data = {}; + frontmatter.split(/\r?\n/).forEach((line) => { + const match = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(line); + if (!match) { + return; + } + data[match[1]] = parseAutomationScalar(match[2]); + }); + return { data, body }; +} + +function dirnameVaultPath(value) { + const normalized = String(value || "").replace(/^\/+|\/+$/g, ""); + if (!normalized || !normalized.includes("/")) { + return ""; + } + return normalized.slice(0, normalized.lastIndexOf("/")); +} + +function isVaultPathWithinRoot(vaultPath, rootPath) { + const normalizedVaultPath = String(vaultPath || "").replace(/^\/+|\/+$/g, ""); + const normalizedRootPath = String(rootPath || "").replace(/^\/+|\/+$/g, ""); + if (!normalizedVaultPath || !normalizedRootPath) { + return false; + } + return normalizedVaultPath === normalizedRootPath || normalizedVaultPath.startsWith(`${normalizedRootPath}/`); +} + +function safeRealpathSync(value) { + try { + return fs.realpathSync.native ? fs.realpathSync.native(value) : fs.realpathSync(value); + } catch { + return ""; + } +} + +function pathExistsOrIsSymlink(value) { + try { + fs.lstatSync(value); + return true; + } catch { + return false; + } +} + +function getDirectorySymlinkType() { + return process.platform === "win32" ? "junction" : "dir"; +} + +function normalizePanelTab(value) { + if (value === PANEL_TAB_OPTIONS.WORKSPACE_SETTINGS) { + return PANEL_TAB_OPTIONS.TASK_LIST; + } + if (value === PANEL_TAB_OPTIONS.TASK_LIST) { + return PANEL_TAB_OPTIONS.TASK_LIST; + } + if (value === PANEL_TAB_OPTIONS.HEARTBEAT) { + return PANEL_TAB_OPTIONS.HEARTBEAT; + } + if (value === PANEL_TAB_OPTIONS.WORKSPACE) { + return PANEL_TAB_OPTIONS.WORKSPACE; + } + return PANEL_TAB_OPTIONS.TASK_LIST; +} + +function normalizePromptPath(value) { + return String(value || "").trim().replace(/\\/g, "/"); +} + +function normalizeFilesystemPathForCompare(value) { + return normalizePromptPath(path.resolve(String(value || "").trim())); +} + +function encodeMarkdownLinkPath(value) { + return String(value || "") + .split("/") + .map((segment) => encodeURIComponent(segment).replace(/[()]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`)) + .join("/"); +} + +function decodeMarkdownLinkTarget(value) { + const normalized = String(value || "").trim(); + try { + return decodeURI(normalized); + } catch { + return normalized; + } +} + +function parseLocalMarkdownLinkTarget(rawTarget) { + const target = String(rawTarget || "").trim(); + if (!target) { + return null; + } + + const hashIndex = target.indexOf("#"); + const targetWithoutFragment = hashIndex >= 0 ? target.slice(0, hashIndex) : target; + const fragment = hashIndex >= 0 ? target.slice(hashIndex) : ""; + const decodedTarget = decodeMarkdownLinkTarget(targetWithoutFragment); + let localPath = decodedTarget; + + if (/^file:\/\//i.test(decodedTarget)) { + try { + localPath = fileURLToPath(decodedTarget); + } catch { + return null; + } + } + + if (!path.isAbsolute(localPath)) { + return null; + } + + let normalizedPath = localPath; + if (!fs.existsSync(normalizedPath)) { + const lineSuffixMatch = normalizedPath.match(/^(.*):\d+(?::\d+)?$/); + if (lineSuffixMatch && path.isAbsolute(lineSuffixMatch[1])) { + normalizedPath = lineSuffixMatch[1]; + } + } + + return { + localPath: normalizedPath, + fragment, + }; +} + +function rewriteMarkdownLinks(text, rewriteTarget) { + const source = String(text || ""); + if (!source || typeof rewriteTarget !== "function") { + return source; + } + + return source.replace(/(\[[^\]\n]*\]\()(<[^>\n]*>|[^)\s\n]*)(\))/g, (match, prefix, rawTarget, suffix) => { + const target = String(rawTarget || ""); + const unwrappedTarget = target.startsWith("<") && target.endsWith(">") + ? target.slice(1, -1) + : target; + const rewrittenTarget = rewriteTarget(unwrappedTarget); + return rewrittenTarget ? `${prefix}${rewrittenTarget}${suffix}` : match; + }); +} + +function createDaemonStatusSnapshot(overrides = {}) { + return { + state: DAEMON_CONNECTION_STATES.UNKNOWN, + checkedAt: 0, + host: "", + port: "", + error: "", + runtimeAvailable: null, + runtimeMessage: "", + lastRuntimeError: "", + ...overrides, + }; +} + +function formatDaemonStatusError(message) { + const normalizedMessage = String(message || "").trim(); + if (!normalizedMessage) { + return "Can't reach the local Codex server."; + } + + const lowercaseMessage = normalizedMessage.toLowerCase(); + if (lowercaseMessage.includes("config was not found")) { + return "Codex server is not configured yet."; + } + if (lowercaseMessage.includes("not responding") || lowercaseMessage.includes("unable to reach")) { + return "Can't reach the local Codex server."; + } + return normalizedMessage; +} + +function getDaemonStatusTagClassName(state) { + switch (state) { + case DAEMON_CONNECTION_STATES.ONLINE: + return "is-online"; + case DAEMON_CONNECTION_STATES.OFFLINE: + return "is-offline"; + case DAEMON_CONNECTION_STATES.CHECKING: + return "is-checking"; + default: + return ""; + } +} + +function normalizeHotkeyModifierToken(value) { + const normalized = String(value || "").trim().toLowerCase(); + switch (normalized) { + case "mod": + return "Mod"; + case "cmd": + case "command": + case "meta": + return "Meta"; + case "ctrl": + case "control": + return "Ctrl"; + case "alt": + case "option": + return "Alt"; + case "shift": + return "Shift"; + default: + return ""; + } +} + +function normalizeHotkeyPrimaryKey(value) { + const normalized = String(value || "").trim(); + if (!normalized) { + return ""; + } + + const lowercase = normalized.toLowerCase(); + if (lowercase === "space" || lowercase === "spacebar" || normalized === " ") { + return "Space"; + } + if (lowercase === "esc" || lowercase === "escape") { + return "Escape"; + } + if (lowercase === "enter" || lowercase === "return") { + return "Enter"; + } + if (lowercase === "tab") { + return "Tab"; + } + if (lowercase === "backspace") { + return "Backspace"; + } + if (lowercase === "delete" || lowercase === "del") { + return "Delete"; + } + if (lowercase === "up" || lowercase === "arrowup") { + return "ArrowUp"; + } + if (lowercase === "down" || lowercase === "arrowdown") { + return "ArrowDown"; + } + if (lowercase === "left" || lowercase === "arrowleft") { + return "ArrowLeft"; + } + if (lowercase === "right" || lowercase === "arrowright") { + return "ArrowRight"; + } + if (/^f\d{1,2}$/i.test(normalized)) { + return normalized.toUpperCase(); + } + if (normalized.length === 1) { + return /[a-z]/i.test(normalized) ? normalized.toUpperCase() : normalized; + } + return normalized.charAt(0).toUpperCase() + normalized.slice(1); +} + +function composeHotkeyString({ + useMod = false, + useCtrl = false, + useMeta = false, + useAlt = false, + useShift = false, + key = "", +} = {}) { + const normalizedKey = normalizeHotkeyPrimaryKey(key); + if (!normalizedKey) { + return ""; + } + + const parts = []; + if (useMod) { + parts.push("Mod"); + } else { + if (useCtrl) { + parts.push("Ctrl"); + } + if (useMeta) { + parts.push("Meta"); + } + } + if (useAlt) { + parts.push("Alt"); + } + if (useShift) { + parts.push("Shift"); + } + parts.push(normalizedKey); + return parts.join("+"); +} + +function normalizeStopHotkeySetting(value) { + const rawValue = String(value || "").trim(); + if (!rawValue) { + return ""; + } + + const tokens = rawValue + .split("+") + .map((token) => token.trim()) + .filter(Boolean); + if (tokens.length === 0) { + return ""; + } + + const key = normalizeHotkeyPrimaryKey(tokens[tokens.length - 1]); + if (!key || normalizeHotkeyModifierToken(key)) { + return ""; + } + + let useMod = false; + let useCtrl = false; + let useMeta = false; + let useAlt = false; + let useShift = false; + + for (const token of tokens.slice(0, -1)) { + const modifier = normalizeHotkeyModifierToken(token); + if (!modifier) { + return ""; + } + if (modifier === "Mod") { + useMod = true; + useCtrl = false; + useMeta = false; + continue; + } + if (useMod && (modifier === "Ctrl" || modifier === "Meta")) { + continue; + } + if (modifier === "Ctrl") { + useCtrl = true; + } else if (modifier === "Meta") { + useMeta = true; + } else if (modifier === "Alt") { + useAlt = true; + } else if (modifier === "Shift") { + useShift = true; + } + } + + return composeHotkeyString({ + useMod, + useCtrl, + useMeta, + useAlt, + useShift, + key, + }); +} + +function formatHotkeyForDisplay(value) { + const normalized = normalizeStopHotkeySetting(value); + if (!normalized) { + return ""; + } + + const isMac = process.platform === "darwin"; + return normalized + .split("+") + .map((part) => { + if (part === "Mod") { + return isMac ? "Cmd" : "Ctrl"; + } + if (part === "Meta") { + return "Cmd"; + } + if (part === "Alt") { + return isMac ? "Option" : "Alt"; + } + return part; + }) + .join("+"); +} + +function normalizeHotkeyFromEvent(event) { + const key = normalizeHotkeyPrimaryKey(event?.key); + if (!key || normalizeHotkeyModifierToken(key)) { + return ""; + } + + const isMac = process.platform === "darwin"; + return composeHotkeyString({ + useMod: isMac ? event.metaKey : event.ctrlKey, + useCtrl: isMac ? event.ctrlKey : false, + useMeta: !isMac && event.metaKey, + useAlt: event.altKey, + useShift: event.shiftKey, + key, + }); +} + +function hotkeyMatchesKeyboardEvent(configuredHotkey, event) { + const normalizedConfiguredHotkey = normalizeStopHotkeySetting(configuredHotkey); + if (!normalizedConfiguredHotkey) { + return false; + } + return normalizedConfiguredHotkey === normalizeHotkeyFromEvent(event); +} + +function isHotkeyCaptureTarget(target) { + return Boolean( + target + && target.classList + && target.classList.contains(HOTKEY_CAPTURE_INPUT_CLASS) + ); +} + +function isPathInsideDirectory(candidatePath, directoryPath) { + const normalizedCandidatePath = String(candidatePath || "").trim(); + const normalizedDirectoryPath = String(directoryPath || "").trim(); + if (!normalizedCandidatePath || !normalizedDirectoryPath) { + return false; + } + + const relativePath = path.relative(normalizedDirectoryPath, normalizedCandidatePath); + return relativePath === "" + || (!relativePath.startsWith("..") && !path.isAbsolute(relativePath)); +} + +function resolveMarkdownFilePromptPath(file, cwd = "") { + const absolutePath = String(file?.absolutePath || "").trim(); + const normalizedCwd = String(cwd || "").trim(); + const relativeFilePath = normalizePromptPath(file?.path || ""); + if (absolutePath) { + return { + promptPath: normalizePromptPath(absolutePath), + isDirectPath: true, + }; + } + + if (normalizedCwd && relativeFilePath) { + const candidateAbsolutePath = path.resolve(normalizedCwd, relativeFilePath); + if (fs.existsSync(candidateAbsolutePath)) { + return { + promptPath: normalizePromptPath(candidateAbsolutePath), + isDirectPath: true, + }; + } + } + + return { + promptPath: relativeFilePath, + isDirectPath: false, + }; +} + +function getMarkdownFilePromptPath(file, options = {}) { + const { promptPath, isDirectPath } = resolveMarkdownFilePromptPath(file, options.cwd); + if (promptPath && isDirectPath) { + return promptPath; + } + + return promptPath || String(file?.path || file?.absolutePath || "").trim(); +} + +function isSkillMarkdownFile(file, options = {}) { + const promptPath = getMarkdownFilePromptPath(file, options); + const normalizedPath = promptPath.replace(/\\/g, "/"); + const name = String(file?.name || "").trim(); + return /(^|\/)SKILL\.md$/i.test(normalizedPath) + || /(^|\/)Codex Skills\//i.test(normalizedPath) + || /(^|\/)\.codex\/skills\//i.test(normalizedPath) + || /(^|\/)\.agents\/skills\//i.test(normalizedPath) + || /^SKILL\.md$/i.test(name); +} + +function buildMarkdownFilePromptBlock(file, index, options = {}) { + const { promptPath, isDirectPath } = resolveMarkdownFilePromptPath(file, options.cwd); + if (promptPath && isDirectPath) { + return `- ${promptPath}`; + } + + const filePath = promptPath || String(file?.path || file?.absolutePath || "").trim(); + const fileContent = String(file?.content || ""); + if (filePath && fileContent) { + return `- ${filePath}\n\n\`\`\`md\n${fileContent}\n\`\`\``; + } + + return filePath ? `- ${filePath}` : `- Markdown file ${index + 1}`; +} + +function shouldAppendUserRequest(textBlocks, markdownFiles, imageFiles, trimmedMessage, includeContext = true) { + if (!trimmedMessage) { + return false; + } + + if (!includeContext) { + return true; + } + + if (textBlocks.length !== 1) { + return true; + } + + return String(textBlocks[0]?.text || "").trim() !== trimmedMessage; +} + +function buildMarkdownFilePromptSections(markdownFiles, options = {}) { + const contextFiles = []; + const skillFiles = []; + + markdownFiles.forEach((file) => { + if (isSkillMarkdownFile(file, options)) { + skillFiles.push(file); + } else { + contextFiles.push(file); + } + }); + + const sections = []; + if (contextFiles.length > 0) { + sections.push(`Context:\n${contextFiles.map((file, index) => buildMarkdownFilePromptBlock(file, index, options)).join("\n")}`); + } + if (skillFiles.length > 0) { + sections.push(`Skills:\n${skillFiles.map((file, index) => buildMarkdownFilePromptBlock(file, index, options)).join("\n")}`); + } + + return sections; +} + +function buildCanvasSelectionPrompt(selection, userMessage, options = {}) { + const textBlocks = Array.isArray(selection?.textBlocks) + ? selection.textBlocks.filter((block) => String(block?.text || "").trim()) + : []; + const markdownFiles = Array.isArray(selection?.markdownFiles) + ? selection.markdownFiles.filter((file) => String(file?.path || "").trim()) + : []; + const imageFiles = Array.isArray(selection?.imageFiles) + ? selection.imageFiles.filter((file) => String(file?.path || "").trim()) + : []; + const warnings = Array.isArray(selection?.warnings) + ? selection.warnings.map((warning) => String(warning || "").trim()).filter(Boolean) + : []; + const trimmedMessage = String(userMessage || "").trim(); + const parts = []; + + if (textBlocks.length > 0) { + parts.push( + textBlocks + .map((block) => String(block.text || "")) + .join("\n\n") + ); + } + + if (markdownFiles.length > 0) { + parts.push(...buildMarkdownFilePromptSections(markdownFiles, options)); + } + + if (imageFiles.length > 0) { + parts.push("Selected image files are attached to this turn as image inputs. Use them as part of the Canvas context."); + parts.push( + imageFiles + .map((file, index) => `Image file ${index + 1}: ${String(file.path || file.name || "Untitled")}`) + .join("\n\n") + ); + } + + if (warnings.length > 0) { + parts.push(`Resolver warnings:\n- ${warnings.join("\n- ")}`); + } + + if (shouldAppendUserRequest(textBlocks, markdownFiles, imageFiles, trimmedMessage, true)) { + parts.push(trimmedMessage); + } + + return parts.join("\n\n").trim(); +} + +function basenameVaultPath(value) { + const normalized = String(value || "").replace(/^\/+|\/+$/g, ""); + if (!normalized) { + return ""; + } + const index = normalized.lastIndexOf("/"); + return index >= 0 ? normalized.slice(index + 1) : normalized; +} + +function normalizeDaemonSandboxMode(value) { + return value === DAEMON_SANDBOX_MODE_OPTIONS.DANGER_FULL_ACCESS + ? DAEMON_SANDBOX_MODE_OPTIONS.DANGER_FULL_ACCESS + : DAEMON_SANDBOX_MODE_OPTIONS.WORKSPACE_WRITE; +} + +function getDaemonSandboxModeLabel(value) { + return normalizeDaemonSandboxMode(value) === DAEMON_SANDBOX_MODE_OPTIONS.DANGER_FULL_ACCESS + ? "Full access" + : "Workspace only"; +} + +function getDaemonSandboxModeHelpText(value) { + return normalizeDaemonSandboxMode(value) === DAEMON_SANDBOX_MODE_OPTIONS.DANGER_FULL_ACCESS + ? "Full access removes the filesystem sandbox and can reach files outside the selected project folder. Use it only for repos and prompts you trust." + : "Workspace-only keeps Codex limited to the selected project folder and is the safer default for normal work."; +} + +function normalizeRepoPath(value) { + const trimmed = String(value || "").trim(); + if (!trimmed || !path.isAbsolute(trimmed)) { + return ""; + } + + return path.normalize(trimmed); +} + +function compactPathLabel(value) { + const normalized = String(value || "").trim(); + if (!normalized) { + return ""; + } + + return path.basename(normalized) || normalized; +} + +function inferWorkspaceNameFromRepoPath(repoPath) { + const normalized = normalizeRepoPath(repoPath); + return normalized ? path.basename(normalized) : ""; +} + +function stableShortHash(value) { + return crypto.createHash("sha256").update(String(value || ""), "utf8").digest("hex").slice(0, 16); +} + +function createCanvasObjectId(prefix = "oa-canvas") { + const normalizedPrefix = String(prefix || "oa-canvas").trim() || "oa-canvas"; + if (typeof crypto.randomUUID === "function") { + return `${normalizedPrefix}-${crypto.randomUUID()}`; + } + + return `${normalizedPrefix}-${stableShortHash(`${Date.now()}\0${Math.random().toString(16)}`)}`; +} + +function createVaultId() { + return createCanvasObjectId("oa-vault"); +} + +function buildTaskResultNodeId(taskId, sourceNodeId) { + return `oa-result-${stableShortHash(`${String(taskId || "").trim()}\0${String(sourceNodeId || "").trim()}`)}`; +} + +function getOpenAgentCanvasMetadata(value) { + const metadata = value?.openagent; + return metadata && typeof metadata === "object" && !Array.isArray(metadata) ? metadata : null; +} + +function isOpenAgentAssistantResultNode(node) { + const metadata = getOpenAgentCanvasMetadata(node); + return metadata?.kind === "assistant-result" || String(node?.id || "").startsWith("oa-result-"); +} + +function getOpenAgentResultSourceNodeId(node) { + const metadata = getOpenAgentCanvasMetadata(node); + return metadata?.kind === "assistant-result" ? String(metadata.sourceNodeId || "").trim() : ""; +} + +function getOpenAgentResultTaskId(node) { + const metadata = getOpenAgentCanvasMetadata(node); + return metadata?.kind === "assistant-result" ? String(metadata.taskId || "").trim() : ""; +} + +function normalizeResultNodeSyncState(value) { + if (!value || typeof value !== "object" || Array.isArray(value)) { + return {}; + } + + return Object.fromEntries( + Object.entries(value) + .map(([taskId, signature]) => [String(taskId || "").trim(), String(signature || "").trim()]) + .filter(([taskId, signature]) => taskId && signature) + ); +} + +function normalizeCanvasRunSourceRefState(value) { + if (!value || typeof value !== "object" || Array.isArray(value)) { + return {}; + } + + return Object.fromEntries( + Object.entries(value) + .map(([taskId, ref]) => { + const normalizedTaskId = String(taskId || "").trim(); + const canvasPath = String(ref?.canvasPath || "").trim(); + const nodeId = String(ref?.nodeId || "").trim(); + return [normalizedTaskId, { canvasPath, nodeId }]; + }) + .filter(([taskId, ref]) => taskId && ref.canvasPath && ref.nodeId) + ); +} + +function normalizeUnreadTaskState(value) { + if (!value || typeof value !== "object" || Array.isArray(value)) { + return {}; + } + + return Object.fromEntries( + Object.entries(value) + .map(([taskId, unread]) => [String(taskId || "").trim(), unread === true]) + .filter(([taskId, unread]) => taskId && unread) + ); +} + +function clampNumber(value, min, max) { + return Math.max(min, Math.min(max, value)); +} + +function toFiniteNumber(value, fallback = 0) { + const numeric = Number(value); + return Number.isFinite(numeric) ? numeric : fallback; +} + +function estimateCanvasTextNodeHeight(text, width = RESULT_NODE_DEFAULT_WIDTH) { + const normalizedText = String(text || "").replace(/\r\n/g, "\n"); + if (!normalizedText.trim()) { + return RESULT_NODE_MIN_HEIGHT; + } + + const charsPerLine = clampNumber( + Math.round(toFiniteNumber(width, RESULT_NODE_DEFAULT_WIDTH) / 8), + 24, + RESULT_NODE_CHARS_PER_LINE + ); + const visualLines = normalizedText + .split("\n") + .reduce((count, line) => count + Math.max(1, Math.ceil(Math.max(1, line.length) / charsPerLine)), 0); + + return clampNumber(88 + (visualLines * 22), RESULT_NODE_MIN_HEIGHT, RESULT_NODE_MAX_HEIGHT); +} + +function getCanvasTextNodeAutoFitHeight(node) { + if (String(node?.type || "").trim() !== "text") { + return null; + } + + const width = clampNumber( + toFiniteNumber(node?.width, RESULT_NODE_DEFAULT_WIDTH), + RESULT_NODE_MIN_WIDTH, + RESULT_NODE_MAX_WIDTH + ); + const estimatedHeight = estimateCanvasTextNodeHeight(node?.text, width); + const existingHeight = toFiniteNumber(node?.height, 0); + return Math.max(estimatedHeight, existingHeight); +} + +function compareCanvasNodeOrder(a, b) { + const yDifference = toFiniteNumber(a?.y, 0) - toFiniteNumber(b?.y, 0); + if (yDifference !== 0) { + return yDifference; + } + + const xDifference = toFiniteNumber(a?.x, 0) - toFiniteNumber(b?.x, 0); + if (xDifference !== 0) { + return xDifference; + } + + return String(a?.id || "").localeCompare(String(b?.id || "")); +} + +function averageNumbers(values) { + return values.length > 0 + ? values.reduce((sum, value) => sum + value, 0) / values.length + : null; +} + +function getCanvasLayoutNodeWidth(node) { + const fallbackWidth = String(node?.type || "").trim() === "text" ? RESULT_NODE_DEFAULT_WIDTH : 360; + return Math.max(CANVAS_LAYOUT_MIN_WIDTH, toFiniteNumber(node?.width, fallbackWidth)); +} + +function getCanvasLayoutNodeHeight(node, width) { + const existingHeight = toFiniteNumber(node?.height, 0); + if (existingHeight > 0) { + return Math.max(CANVAS_LAYOUT_MIN_HEIGHT, existingHeight); + } + + if (String(node?.type || "").trim() === "text") { + return Math.max(CANVAS_LAYOUT_MIN_HEIGHT, estimateCanvasTextNodeHeight(node?.text, width)); + } + + return 220; +} + +function getCanvasNodeCenter(node) { + if (!node || typeof node !== "object") { + return null; + } + + const width = Math.max(0, toFiniteNumber(node.width, 0)); + const height = Math.max(0, toFiniteNumber(node.height, 0)); + return { + x: toFiniteNumber(node.x, 0) + (width / 2), + y: toFiniteNumber(node.y, 0) + (height / 2), + }; +} + +function doesCanvasGroupContainNode(groupNode, candidateNode) { + if (String(groupNode?.type || "").trim() !== "group") { + return false; + } + + const center = getCanvasNodeCenter(candidateNode); + if (!center) { + return false; + } + + const groupX = toFiniteNumber(groupNode.x, 0); + const groupY = toFiniteNumber(groupNode.y, 0); + const groupWidth = Math.max(0, toFiniteNumber(groupNode.width, 0)); + const groupHeight = Math.max(0, toFiniteNumber(groupNode.height, 0)); + return ( + center.x >= groupX + && center.x <= groupX + groupWidth + && center.y >= groupY + && center.y <= groupY + groupHeight + ); +} + +function escapeAttributeSelectorValue(value) { + return String(value || "") + .replace(/\\/g, "\\\\") + .replace(/"/g, '\\"'); +} + +function parseIsoTimestamp(value) { + const normalizedValue = String(value || "").trim(); + if (!normalizedValue) { + return null; + } + + const timestamp = Date.parse(normalizedValue); + return Number.isFinite(timestamp) ? timestamp : null; +} + +function getStartOfLocalDayTimestamp(value) { + const timestamp = Number(value); + if (!Number.isFinite(timestamp)) { + return null; + } + + const date = new Date(timestamp); + date.setHours(0, 0, 0, 0); + return date.getTime(); +} + +function formatTaskListDayLabel(dayTimestamp) { + const normalizedDayTimestamp = Number(dayTimestamp); + if (!Number.isFinite(normalizedDayTimestamp)) { + return "Unknown"; + } + + const dayDate = new Date(normalizedDayTimestamp); + const today = new Date(); + today.setHours(0, 0, 0, 0); + const dayDifference = Math.round((today.getTime() - normalizedDayTimestamp) / 86_400_000); + if (dayDifference === 0) { + return "Today"; + } + if (dayDifference === 1) { + return "Yesterday"; + } + + return dayDate.toLocaleDateString([], { + weekday: "short", + month: "short", + day: "numeric", + year: dayDate.getFullYear() === today.getFullYear() ? undefined : "numeric", + }); +} + +function formatTaskListTimeLabel(timestamp) { + const normalizedTimestamp = Number(timestamp); + if (!Number.isFinite(normalizedTimestamp) || normalizedTimestamp <= 0) { + return ""; + } + + try { + return new Date(normalizedTimestamp).toLocaleTimeString([], { + hour: "numeric", + minute: "2-digit", + }); + } catch { + return ""; + } +} + +function autoArrangeCanvasNodes(nodes, edges) { + const originalNodes = Array.isArray(nodes) + ? nodes + : []; + const normalizedNodes = originalNodes.filter((node) => String(node?.id || "").trim()); + const groupNodes = normalizedNodes.filter((node) => String(node?.type || "").trim() === "group"); + const layoutNodes = normalizedNodes.filter((node) => { + if (String(node?.type || "").trim() === "group") { + return false; + } + + // Leave group contents untouched so auto-arrange only reflows top-level nodes. + return !groupNodes.some((groupNode) => doesCanvasGroupContainNode(groupNode, node)); + }); + if (layoutNodes.length <= 1) { + return originalNodes; + } + + const normalizedEdges = Array.isArray(edges) ? edges : []; + const nodeById = new Map( + layoutNodes.map((node) => [String(node.id || "").trim(), node]) + ); + const outgoingNodeIdsByNodeId = new Map(); + const incomingNodeIdsByNodeId = new Map(); + const neighborNodeIdsByNodeId = new Map(); + + nodeById.forEach((_node, nodeId) => { + outgoingNodeIdsByNodeId.set(nodeId, []); + incomingNodeIdsByNodeId.set(nodeId, []); + neighborNodeIdsByNodeId.set(nodeId, []); + }); + + normalizedEdges.forEach((edge) => { + const fromNodeId = String(edge?.fromNode || "").trim(); + const toNodeId = String(edge?.toNode || "").trim(); + if (!fromNodeId || !toNodeId || fromNodeId === toNodeId) { + return; + } + if (!nodeById.has(fromNodeId) || !nodeById.has(toNodeId)) { + return; + } + + outgoingNodeIdsByNodeId.get(fromNodeId).push(toNodeId); + incomingNodeIdsByNodeId.get(toNodeId).push(fromNodeId); + neighborNodeIdsByNodeId.get(fromNodeId).push(toNodeId); + neighborNodeIdsByNodeId.get(toNodeId).push(fromNodeId); + }); + + const sortNodeIds = (nodeIds) => { + return [...new Set(nodeIds)].sort((leftNodeId, rightNodeId) => { + return compareCanvasNodeOrder(nodeById.get(leftNodeId), nodeById.get(rightNodeId)); + }); + }; + + const orderedNodeIds = sortNodeIds([...nodeById.keys()]); + const unvisitedNodeIds = new Set(orderedNodeIds); + const connectedComponents = []; + + while (unvisitedNodeIds.size > 0) { + const startNodeId = orderedNodeIds.find((nodeId) => unvisitedNodeIds.has(nodeId)); + if (!startNodeId) { + break; + } + + const componentNodeIds = []; + const queue = [startNodeId]; + unvisitedNodeIds.delete(startNodeId); + + while (queue.length > 0) { + const currentNodeId = queue.shift(); + componentNodeIds.push(currentNodeId); + + sortNodeIds(neighborNodeIdsByNodeId.get(currentNodeId) || []).forEach((neighborNodeId) => { + if (!unvisitedNodeIds.has(neighborNodeId)) { + return; + } + + unvisitedNodeIds.delete(neighborNodeId); + queue.push(neighborNodeId); + }); + } + + connectedComponents.push(sortNodeIds(componentNodeIds)); + } + + const isolatedNodeIds = []; + const layoutComponents = connectedComponents.filter((componentNodeIds) => { + if (componentNodeIds.length !== 1) { + return true; + } + + const nodeId = componentNodeIds[0]; + if ((neighborNodeIdsByNodeId.get(nodeId) || []).length > 0) { + return true; + } + + isolatedNodeIds.push(nodeId); + return false; + }); + if (isolatedNodeIds.length > 0) { + layoutComponents.push(sortNodeIds(isolatedNodeIds)); + } + + const nextPositionByNodeId = new Map(); + let componentOffsetX = CANVAS_LAYOUT_BASE_X; + + [...layoutComponents].reverse().forEach((componentNodeIds) => { + const componentNodeIdSet = new Set(componentNodeIds); + const localIncomingCountByNodeId = new Map(); + const depthByNodeId = new Map(); + const pendingNodeIds = []; + const consumedNodeIds = new Set(); + + componentNodeIds.forEach((nodeId) => { + const incomingNodeIds = (incomingNodeIdsByNodeId.get(nodeId) || []).filter((incomingNodeId) => { + return componentNodeIdSet.has(incomingNodeId); + }); + localIncomingCountByNodeId.set(nodeId, incomingNodeIds.length); + if (incomingNodeIds.length === 0) { + depthByNodeId.set(nodeId, 0); + pendingNodeIds.push(nodeId); + } + }); + + while (pendingNodeIds.length > 0) { + pendingNodeIds.sort((leftNodeId, rightNodeId) => { + return compareCanvasNodeOrder(nodeById.get(leftNodeId), nodeById.get(rightNodeId)); + }); + const currentNodeId = pendingNodeIds.shift(); + if (consumedNodeIds.has(currentNodeId)) { + continue; + } + + consumedNodeIds.add(currentNodeId); + const nextDepth = (depthByNodeId.get(currentNodeId) || 0) + 1; + + sortNodeIds((outgoingNodeIdsByNodeId.get(currentNodeId) || []).filter((nextNodeId) => { + return componentNodeIdSet.has(nextNodeId); + })).forEach((nextNodeId) => { + depthByNodeId.set(nextNodeId, Math.max(depthByNodeId.get(nextNodeId) || 0, nextDepth)); + localIncomingCountByNodeId.set(nextNodeId, Math.max(0, (localIncomingCountByNodeId.get(nextNodeId) || 0) - 1)); + if ((localIncomingCountByNodeId.get(nextNodeId) || 0) === 0) { + pendingNodeIds.push(nextNodeId); + } + }); + } + + let maxDepth = depthByNodeId.size > 0 + ? Math.max(...depthByNodeId.values()) + : 0; + + sortNodeIds(componentNodeIds.filter((nodeId) => !depthByNodeId.has(nodeId))).forEach((nodeId) => { + const incomingDepths = (incomingNodeIdsByNodeId.get(nodeId) || []) + .filter((incomingNodeId) => componentNodeIdSet.has(incomingNodeId) && depthByNodeId.has(incomingNodeId)) + .map((incomingNodeId) => depthByNodeId.get(incomingNodeId)); + const suggestedDepth = incomingDepths.length > 0 ? Math.max(...incomingDepths) + 1 : maxDepth + 1; + depthByNodeId.set(nodeId, suggestedDepth); + maxDepth = Math.max(maxDepth, suggestedDepth); + }); + + const rowDepths = [...new Set(componentNodeIds.map((nodeId) => depthByNodeId.get(nodeId) || 0))].sort((leftDepth, rightDepth) => { + return leftDepth - rightDepth; + }); + const rowHeightByDepth = new Map(); + rowDepths.forEach((depth) => { + const rowHeight = componentNodeIds + .filter((nodeId) => (depthByNodeId.get(nodeId) || 0) === depth) + .reduce((maxHeight, nodeId) => { + const width = getCanvasLayoutNodeWidth(nodeById.get(nodeId)); + return Math.max(maxHeight, getCanvasLayoutNodeHeight(nodeById.get(nodeId), width)); + }, CANVAS_LAYOUT_MIN_HEIGHT); + rowHeightByDepth.set(depth, rowHeight); + }); + + const placedCenterXByNodeId = new Map(); + let componentWidth = 0; + let rowOffsetY = CANVAS_LAYOUT_BASE_Y; + + rowDepths.forEach((depth, rowIndex) => { + if (rowIndex > 0) { + const previousDepth = rowDepths[rowIndex - 1]; + rowOffsetY += (rowHeightByDepth.get(previousDepth) || CANVAS_LAYOUT_MIN_HEIGHT) + CANVAS_LAYOUT_Y_GAP; + } + + const rowNodeIds = componentNodeIds + .filter((nodeId) => (depthByNodeId.get(nodeId) || 0) === depth) + .sort((leftNodeId, rightNodeId) => { + const leftAverageIncomingCenter = averageNumbers( + (incomingNodeIdsByNodeId.get(leftNodeId) || []) + .map((incomingNodeId) => placedCenterXByNodeId.get(incomingNodeId)) + .filter((value) => Number.isFinite(value)) + ); + const rightAverageIncomingCenter = averageNumbers( + (incomingNodeIdsByNodeId.get(rightNodeId) || []) + .map((incomingNodeId) => placedCenterXByNodeId.get(incomingNodeId)) + .filter((value) => Number.isFinite(value)) + ); + if (leftAverageIncomingCenter !== null && rightAverageIncomingCenter !== null && leftAverageIncomingCenter !== rightAverageIncomingCenter) { + return leftAverageIncomingCenter - rightAverageIncomingCenter; + } + if (leftAverageIncomingCenter !== null || rightAverageIncomingCenter !== null) { + return leftAverageIncomingCenter === null ? 1 : -1; + } + + return compareCanvasNodeOrder(nodeById.get(leftNodeId), nodeById.get(rightNodeId)); + }); + + let rowOffsetX = 0; + rowNodeIds.forEach((nodeId) => { + const node = nodeById.get(nodeId); + const width = getCanvasLayoutNodeWidth(node); + const height = getCanvasLayoutNodeHeight(node, width); + nextPositionByNodeId.set(nodeId, { + x: componentOffsetX + rowOffsetX, + y: rowOffsetY, + }); + placedCenterXByNodeId.set(nodeId, rowOffsetX + (width / 2)); + rowOffsetX += width + CANVAS_LAYOUT_X_GAP; + }); + + componentWidth = Math.max(componentWidth, Math.max(0, rowOffsetX - CANVAS_LAYOUT_X_GAP)); + }); + + componentOffsetX += componentWidth + CANVAS_LAYOUT_COMPONENT_GAP; + }); + + return originalNodes.map((node) => { + const nodeId = String(node?.id || "").trim(); + const nextPosition = nextPositionByNodeId.get(nodeId); + if (!nextPosition) { + return node; + } + + return { + ...node, + x: nextPosition.x, + y: nextPosition.y, + }; + }); +} + +class WorkspacePickerModal extends Modal { + constructor(app, plugin, options = {}) { + super(app); + this.plugin = plugin; + this.options = options; + this.repoPathInput = null; + this.workspaceNameInput = null; + } + + onOpen() { + const loadPromise = this.plugin.ensureWorkspaceSummariesLoaded(); + this.render(); + void loadPromise.finally(() => { + if (this.contentEl?.isConnected) { + this.render(); + } + }); + } + + render() { + const { contentEl } = this; + contentEl.empty(); + contentEl.addClass("oa-workspace-modal"); + contentEl.createEl("h2", { text: "Choose workspace" }); + contentEl.createDiv({ + cls: "oa-workspace-modal-subtitle", + text: "Open an existing repo workspace or create a new Canvas home for a project.", + }); + + const existing = this.plugin.listWorkspaceSummaries(); + const isLoadingWorkspaces = this.plugin.isWorkspaceSummariesLoading(); + const existingSection = contentEl.createDiv({ cls: "oa-workspace-section" }); + existingSection.createEl("h3", { text: "Existing workspaces" }); + + if (existing.length === 0 && isLoadingWorkspaces) { + existingSection.createDiv({ + cls: "oa-muted", + text: "Loading workspaces...", + }); + } else if (existing.length === 0) { + existingSection.createDiv({ + cls: "oa-muted", + text: "No workspace yet. Create one from a repo path below.", + }); + } else { + const list = existingSection.createDiv({ cls: "oa-workspace-list" }); + existing.forEach((workspace) => { + const item = list.createDiv({ cls: "oa-workspace-item" }); + const itemMain = item.createDiv({ cls: "oa-workspace-item-main" }); + itemMain.createDiv({ cls: "oa-task-title", text: workspace.name }); + itemMain.createDiv({ cls: "oa-task-meta", text: workspace.repoPath }); + const buttonRow = item.createDiv({ cls: "oa-action-row" }); + const openButton = new ButtonComponent(buttonRow); + openButton.setButtonText("Open"); + openButton.buttonEl?.addClass("oa-workspace-open-button"); + openButton.onClick(async () => { + await this.plugin.openWorkspace(workspace); + this.close(); + }); + }); + } + + const createSection = contentEl.createDiv({ cls: "oa-workspace-section oa-workspace-create-panel" }); + createSection.createEl("h3", { text: "Create workspace" }); + + new Setting(createSection) + .setName("Repo path") + .setDesc("Absolute filesystem path to the repo folder that Codex should use for this workspace.") + .addText((text) => { + this.repoPathInput = text; + text + .setPlaceholder("/absolute/path/to/repo") + .setValue(""); + }) + .addButton((button) => { + button.setButtonText("Browse..."); + button.onClick(async () => { + try { + const defaultPath = this.repoPathInput?.getValue?.() || ""; + const selectedPath = await this.plugin.pickDirectoryPath({ + defaultPath, + title: "Choose repo folder", + }); + if (!selectedPath) { + return; + } + + this.repoPathInput?.setValue(selectedPath); + if (this.workspaceNameInput && !String(this.workspaceNameInput.getValue?.() || "").trim()) { + this.workspaceNameInput.setValue(inferWorkspaceNameFromRepoPath(selectedPath)); + } + } catch (error) { + new Notice(String(error?.message || error)); + } + }); + }); + + new Setting(createSection) + .setName("Workspace name") + .setDesc("Optional. Leave blank to use the repo folder name.") + .addText((text) => { + this.workspaceNameInput = text; + text + .setPlaceholder("openagent") + .setValue(""); + }); + + const actions = createSection.createDiv({ cls: "oa-action-row" }); + const createButton = new ButtonComponent(actions); + createButton.setButtonText("Create"); + createButton.setCta(); + createButton.onClick(async () => { + try { + const repoPath = this.repoPathInput?.getValue?.() || ""; + const workspaceName = this.workspaceNameInput?.getValue?.() || ""; + await this.plugin.createWorkspaceFromRepoPath(repoPath, workspaceName); + this.close(); + } catch (error) { + new Notice(String(error?.message || error)); + } + }); + } + + onClose() { + this.contentEl.empty(); + } +} + +class CanvasForkConfirmModal extends Modal { + constructor(app, options = {}) { + super(app); + this.options = options; + this.resolve = typeof options.resolve === "function" ? options.resolve : () => {}; + this.didChoose = false; + } + + onOpen() { + const { contentEl } = this; + contentEl.empty(); + contentEl.addClass("oa-fork-modal"); + contentEl.createEl("h2", { text: "Fork from here?" }); + contentEl.createDiv({ + cls: "oa-fork-modal-subtitle", + text: "This branch starts a separate OpenAgent thread from the selected Canvas point.", + }); + + const details = contentEl.createDiv({ cls: "oa-fork-modal-details" }); + const originLabel = String(this.options.originLabel || "").trim(); + const rollbackTurns = Math.max(0, Math.floor(Number(this.options.rollbackTurns || 0))); + if (originLabel) { + details.createDiv({ text: `Origin: ${originLabel}` }); + } + if (rollbackTurns > 0) { + details.createDiv({ text: `Codex will fork the thread and drop ${rollbackTurns} later turn${rollbackTurns === 1 ? "" : "s"} before running this node.` }); + } + + const actions = contentEl.createDiv({ cls: "oa-action-row oa-fork-modal-actions" }); + const forkButton = new ButtonComponent(actions); + forkButton.setButtonText("Fork and run"); + forkButton.setCta(); + forkButton.onClick(() => this.choose("fork")); + + const continueButton = new ButtonComponent(actions); + continueButton.setButtonText("Continue thread"); + continueButton.onClick(() => this.choose("continue")); + + const cancelButton = new ButtonComponent(actions); + cancelButton.setButtonText("Cancel"); + cancelButton.onClick(() => this.choose("cancel")); + } + + choose(value) { + this.didChoose = true; + this.resolve(value); + this.close(); + } + + onClose() { + this.contentEl.empty(); + if (!this.didChoose) { + this.resolve("cancel"); + } + } +} + +class OpenAgentDaemonLauncher { + constructor(plugin) { + this.plugin = plugin; + this.startPromise = null; + } + + async ensureStarted() { + if (this.startPromise) { + return this.startPromise; + } + + this.startPromise = this.startInternal(); + try { + return await this.startPromise; + } finally { + this.startPromise = null; + } + } + + async startInternal() { + const launchSpec = this.resolveLaunchSpec(); + if (!launchSpec?.command) { + throw new Error( + "Codex server is not running, and auto-launch could not find this repo. Set a server launch command in OpenAgent settings." + ); + } + + fs.mkdirSync(path.dirname(DAEMON_LOG_PATH), { recursive: true }); + + const shellCommand = `${launchSpec.command} >> ${shellEscape(DAEMON_LOG_PATH)} 2>&1`; + const child = spawn("/bin/zsh", ["-lc", shellCommand], { + cwd: launchSpec.cwd || os.homedir(), + detached: true, + stdio: "ignore", + }); + child.unref(); + + await this.waitForReady(); + } + + resolveLaunchSpec() { + const configuredCommand = String(this.plugin.settings?.daemonLaunchCommand || "").trim(); + const configuredCwd = this.normalizeDirectory(this.plugin.settings?.daemonLaunchCwd); + if (configuredCommand) { + return { + cwd: configuredCwd || os.homedir(), + command: configuredCommand, + }; + } + + const repoRoot = this.findRepoRoot(); + if (!repoRoot) { + return null; + } + + return { + cwd: repoRoot, + command: `cd ${shellEscape(repoRoot)} && exec pnpm dev:daemon`, + }; + } + + findRepoRoot() { + const candidates = [ + this.findRepoNearPlugin(), + path.join(os.homedir(), "Documents", "GitHub", "openagent"), + path.join(os.homedir(), "GitHub", "openagent"), + path.join(os.homedir(), "Documents", "openagent"), + path.join(os.homedir(), "openagent"), + ].filter(Boolean); + + for (const candidate of candidates) { + const daemonPath = path.join(candidate, "apps", "openagent-daemon", "src", "server.js"); + const packagePath = path.join(candidate, "package.json"); + if (fs.existsSync(daemonPath) && fs.existsSync(packagePath)) { + return candidate; + } + } + + return ""; + } + + findRepoNearPlugin() { + let currentPath = __dirname; + while (currentPath) { + const daemonPath = path.join(currentPath, "apps", "openagent-daemon", "src", "server.js"); + if (fs.existsSync(daemonPath)) { + return currentPath; + } + + const parentPath = path.dirname(currentPath); + if (parentPath === currentPath) { + return ""; + } + currentPath = parentPath; + } + + return ""; + } + + normalizeDirectory(value) { + const trimmed = String(value || "").trim(); + if (!trimmed || !path.isAbsolute(trimmed)) { + return ""; + } + + const normalized = path.normalize(trimmed); + if (!fs.existsSync(normalized) || !fs.statSync(normalized).isDirectory()) { + return ""; + } + + return normalized; + } + + async waitForReady() { + let lastError = null; + + for (let attempt = 0; attempt < 20; attempt += 1) { + await sleep(attempt === 0 ? 250 : 500); + + try { + await this.plugin.api.requestOnce("GET", "/health"); + return; + } catch (error) { + lastError = error; + } + } + + throw lastError || new Error("Codex server did not become ready."); + } +} + +class OpenAgentApiClient { + constructor(options = {}) { + this.config = null; + this.daemonLauncher = options.daemonLauncher || null; + } + + loadConfig() { + if (this.config) { + return this.config; + } + + if (!fs.existsSync(DAEMON_CONFIG_PATH)) { + throw new Error("Codex server config was not found. Start the server first."); + } + + const parsed = JSON.parse(fs.readFileSync(DAEMON_CONFIG_PATH, "utf8")); + if (!parsed?.host || !parsed?.port || !parsed?.token) { + throw new Error("Codex server config is invalid."); + } + + this.config = parsed; + return this.config; + } + + async request(method, route, body = null) { + try { + return await this.requestOnce(method, route, body); + } catch (error) { + if (!this.shouldTryAutoLaunch(error)) { + throw error; + } + + if (!this.daemonLauncher) { + throw error; + } + + this.config = null; + await this.daemonLauncher.ensureStarted(); + this.config = null; + return this.requestOnce(method, route, body); + } + } + + async requestOnce(method, route, body = null) { + const config = this.loadConfig(); + + return new Promise((resolve, reject) => { + const payload = body == null ? null : JSON.stringify(body); + const request = http.request({ + host: config.host, + port: config.port, + path: route, + method, + headers: { + "content-type": "application/json", + "x-openagent-token": config.token, + ...(payload ? { "content-length": Buffer.byteLength(payload) } : {}), + }, + }, (response) => { + let buffer = ""; + response.setEncoding("utf8"); + response.on("data", (chunk) => { + buffer += chunk; + }); + response.on("end", () => { + let parsed = {}; + try { + parsed = buffer.trim() ? JSON.parse(buffer) : {}; + } catch (error) { + reject(new Error(`Unable to parse Codex server response: ${error.message}`)); + return; + } + + if (response.statusCode >= 400) { + reject(new Error(parsed?.error?.message || `Codex server request failed (${response.statusCode}).`)); + return; + } + + resolve(parsed); + }); + }); + + request.on("error", (error) => { + reject(new Error(`Unable to reach the Codex server: ${error.message}`)); + }); + + if (payload) { + request.write(payload); + } + + request.end(); + }); + } + + shouldTryAutoLaunch(error) { + const message = String(error?.message || error || "").toLowerCase(); + return ( + message.includes("codex server config was not found") + || message.includes("unable to reach the codex server") + || message.includes("econnrefused") + || message.includes("connect enoent") + ); + } + + getHealth() { + return this.request("GET", "/health"); + } + + getHealthOnce(options = {}) { + if (options.reloadConfig === true) { + this.config = null; + } + return this.requestOnce("GET", "/health"); + } + + getTasks(options = {}) { + const vaultId = String(options.vaultId || "").trim(); + const query = vaultId ? `?vaultId=${encodeURIComponent(vaultId)}` : ""; + return this.request("GET", `/tasks${query}`); + } + + getAutomations() { + return this.request("GET", "/automations"); + } + + syncAutomations(body = {}) { + return this.request("PUT", "/automations/sync", body); + } + + runAutomation(automationId, body = {}) { + return this.request("POST", `/automations/${encodeURIComponent(automationId)}/run`, body); + } + + getTask(taskId) { + return this.request("GET", `/tasks/${encodeURIComponent(taskId)}`); + } + + createTaskFromCanvasSelection(selection) { + return this.request("POST", "/tasks/from-canvas-selection", selection); + } + + runTask(taskId, body = {}) { + return this.request("POST", `/tasks/${encodeURIComponent(taskId)}/run`, body); + } + + forkTask(taskId, body = {}) { + return this.request("POST", `/tasks/${encodeURIComponent(taskId)}/fork`, body); + } + + sendMessage(taskId, text, runtimeConfig = null) { + return this.request("POST", `/tasks/${encodeURIComponent(taskId)}/messages`, { + text, + ...(runtimeConfig ? { runtimeConfig } : {}), + }); + } + + updateTaskCanvasBinding(taskId, body = {}) { + return this.request("PATCH", `/tasks/${encodeURIComponent(taskId)}/canvas-binding`, body); + } + + interruptTask(taskId) { + return this.request("POST", `/tasks/${encodeURIComponent(taskId)}/interrupt`, {}); + } + + startVoiceRealtime(body = {}) { + return this.request("POST", "/voice/realtime/start", body); + } + + stopVoiceRealtime(sessionId) { + return this.request("POST", `/voice/realtime/${encodeURIComponent(sessionId)}/stop`, {}); + } + + openVoiceRealtimeStream(sessionId, handlers = {}) { + return this.openEventStream(`/voice/realtime/${encodeURIComponent(sessionId)}/stream`, handlers); + } + + openEventStream(route, handlers = {}) { + return this.openTaskLikeStream(route, handlers); + } + + openTaskStream(taskId, handlers = {}) { + return this.openTaskLikeStream(`/tasks/${encodeURIComponent(taskId)}/stream`, handlers); + } + + openTaskLikeStream(route, handlers = {}) { + const config = this.loadConfig(); + const normalizedHandlers = typeof handlers === "function" + ? { onEvent: handlers } + : (handlers && typeof handlers === "object" ? handlers : {}); + const onEvent = typeof normalizedHandlers.onEvent === "function" + ? normalizedHandlers.onEvent + : () => {}; + const onOpen = typeof normalizedHandlers.onOpen === "function" + ? normalizedHandlers.onOpen + : () => {}; + const onClose = typeof normalizedHandlers.onClose === "function" + ? normalizedHandlers.onClose + : () => {}; + const onError = typeof normalizedHandlers.onError === "function" + ? normalizedHandlers.onError + : () => {}; + const request = http.request({ + host: config.host, + port: config.port, + path: route, + method: "GET", + headers: { + Accept: "text/event-stream", + "x-openagent-token": config.token, + }, + }); + + let buffer = ""; + let responseRef = null; + let isClosed = false; + const notifyClose = (reason, detail = null) => { + if (isClosed) { + return; + } + + isClosed = true; + try { + onClose(reason, detail); + } catch { + // Ignore stream close callback failures. + } + }; + + request.on("response", (response) => { + responseRef = response; + if ((response.statusCode || 0) >= 400) { + let errorBuffer = ""; + response.setEncoding("utf8"); + response.on("data", (chunk) => { + errorBuffer += chunk; + }); + response.on("end", () => { + let message = `Task stream failed (${response.statusCode}).`; + try { + const parsed = errorBuffer.trim() ? JSON.parse(errorBuffer) : {}; + message = parsed?.error?.message || message; + } catch { + if (errorBuffer.trim()) { + message = errorBuffer.trim(); + } + } + + try { + onError(new Error(message)); + } catch { + // Ignore stream error callback failures. + } + notifyClose("http-error", { + statusCode: response.statusCode, + message, + }); + }); + response.on("close", () => { + notifyClose("close"); + }); + return; + } + + try { + onOpen(); + } catch { + // Ignore stream open callback failures. + } + + response.setEncoding("utf8"); + response.on("data", (chunk) => { + buffer += chunk; + + while (buffer.includes("\n\n")) { + const boundary = buffer.indexOf("\n\n"); + const block = buffer.slice(0, boundary); + buffer = buffer.slice(boundary + 2); + + const lines = block.split("\n"); + let event = "message"; + let data = ""; + for (const line of lines) { + if (line.startsWith("event:")) { + event = line.slice(6).trim(); + } else if (line.startsWith("data:")) { + data += line.slice(5).trim(); + } + } + + if (!data) { + continue; + } + + try { + onEvent(event, JSON.parse(data)); + } catch { + // Ignore malformed stream frames. + } + } + }); + response.on("end", () => { + notifyClose("end"); + }); + response.on("close", () => { + notifyClose("close"); + }); + response.on("error", (error) => { + try { + onError(error); + } catch { + // Ignore stream error callback failures. + } + notifyClose("response-error", { + error: String(error?.message || error), + }); + }); + }); + + request.on("error", (error) => { + try { + onError(error); + } catch { + // Ignore stream error callback failures. + } + notifyClose("request-error", { + error: String(error?.message || error), + }); + }); + request.end(); + + return () => { + isClosed = true; + if (responseRef && !responseRef.destroyed) { + responseRef.destroy(); + } + request.destroy(); + }; + } +} + +class CanvasSelectionResolver { + constructor(app, plugin = null) { + this.app = app; + this.plugin = plugin; + } + + async resolveActiveSelection() { + const view = this.getActiveCanvasView(); + if (!view) { + throw new Error("Open a Canvas view first."); + } + + const file = view.file || this.app.workspace.getActiveFile(); + if (!(file instanceof TFile) || file.extension !== "canvas") { + throw new Error("The active view is not backed by a .canvas file."); + } + + const selectedNodeIds = this.extractSelectedNodeIds(view); + if (selectedNodeIds.length === 0) { + throw new Error("Select one or more Canvas nodes first."); + } + + const liveNodeDataById = this.extractLiveSelectedNodeDataById(view, selectedNodeIds); + await this.flushPendingCanvasEdits(view); + + return this.resolveCanvasSelection(file, selectedNodeIds, { view, liveNodeDataById }); + } + + getActiveCanvasView() { + const activeLeaf = this.app.workspace.activeLeaf; + if (activeLeaf?.view?.getViewType?.() === "canvas") { + return activeLeaf.view; + } + + const canvasLeaves = this.app.workspace.getLeavesOfType("canvas"); + return canvasLeaves[0]?.view || null; + } + + isEditableCanvasElement(element) { + if (!element || typeof element !== "object") { + return false; + } + + if (typeof element.matches === "function" && element.matches("textarea, input, [contenteditable='true'], .cm-content")) { + return true; + } + + return Boolean(element.closest?.("[contenteditable='true'], .cm-content")); + } + + async requestCanvasSave(view) { + const calls = [ + [view, "requestSave"], + [view, "save"], + [view?.canvas, "requestSave"], + [view?.canvas, "save"], + ]; + + for (const [owner, method] of calls) { + if (typeof owner?.[method] !== "function") { + continue; + } + + try { + const result = owner[method](); + if (result && typeof result.then === "function") { + await result; + } + } catch { + // Canvas internals differ between Obsidian versions; best effort is enough here. + } + } + } + + async flushPendingCanvasEdits(view) { + const containerEl = view?.containerEl; + const activeElement = containerEl?.ownerDocument?.activeElement; + const isEditingCanvas = Boolean( + activeElement + && typeof containerEl?.contains === "function" + && containerEl.contains(activeElement) + && this.isEditableCanvasElement(activeElement) + ); + + if (isEditingCanvas && typeof activeElement.blur === "function") { + activeElement.blur(); + await sleep(0); + } + + await this.requestCanvasSave(view); + + if (isEditingCanvas) { + await sleep(50); + await this.requestCanvasSave(view); + } + } + + getCanvasViewForFile(fileOrPath) { + const normalizedPath = typeof fileOrPath === "string" + ? String(fileOrPath || "").trim() + : String(fileOrPath?.path || "").trim(); + if (!normalizedPath) { + return null; + } + + const activeView = this.getActiveCanvasView(); + if (String(activeView?.file?.path || "").trim() === normalizedPath) { + return activeView; + } + + const canvasLeaves = this.app.workspace.getLeavesOfType("canvas"); + for (const leaf of canvasLeaves) { + const view = leaf?.view; + if (String(view?.file?.path || "").trim() === normalizedPath) { + return view; + } + } + + return null; + } + + getCanvasRuntimeData(view) { + const canvas = view?.canvas; + if (!canvas) { + return null; + } + + try { + const runtimeData = typeof canvas.getData === "function" ? canvas.getData() : canvas.data; + return runtimeData && typeof runtimeData === "object" ? runtimeData : null; + } catch { + return null; + } + } + + async readCanvasFileData(file) { + const raw = await this.app.vault.cachedRead(file); + try { + return JSON.parse(raw); + } catch { + throw new Error(`Unable to parse canvas JSON for ${file.path}`); + } + } + + getCachedCanvasNodeText(canvasPath, nodeId) { + if (!this.plugin || typeof this.plugin.getCanvasNodeEditorText !== "function") { + return undefined; + } + return this.plugin.getCanvasNodeEditorText(canvasPath, nodeId); + } + + async resolveCanvasSelection(file, selectedNodeIds, options = {}) { + const resolvedView = String(options.view?.file?.path || "").trim() === String(file?.path || "").trim() + ? options.view + : this.getCanvasViewForFile(file); + const liveSelectedNodeDataById = options.liveNodeDataById instanceof Map + ? new Map(options.liveNodeDataById) + : new Map(); + this.extractLiveSelectedNodeDataById(resolvedView, selectedNodeIds).forEach((nodeData, nodeId) => { + const previous = liveSelectedNodeDataById.get(nodeId) || {}; + liveSelectedNodeDataById.set(nodeId, { ...previous, ...nodeData }); + }); + + const selectedIdSet = new Set(selectedNodeIds.map((nodeId) => String(nodeId))); + const selectedNodesById = new Map(); + const rememberSelectedNodes = (nodes) => { + (Array.isArray(nodes) ? nodes : []).forEach((node) => { + const nodeId = String(node?.id || "").trim(); + if (!nodeId || !selectedIdSet.has(nodeId) || selectedNodesById.has(nodeId)) { + return; + } + selectedNodesById.set(nodeId, node); + }); + }; + + const runtimeData = this.getCanvasRuntimeData(resolvedView); + rememberSelectedNodes(runtimeData?.nodes); + + if (selectedNodesById.size < selectedIdSet.size) { + const parsed = await this.readCanvasFileData(file); + rememberSelectedNodes(parsed?.nodes); + } + + const selectedNodes = Array.from(selectedNodesById.values()) + .map((node) => { + const liveNodeData = liveSelectedNodeDataById.get(String(node?.id || "")); + const mergedNode = liveNodeData ? { ...node, ...liveNodeData } : { ...node }; + const cachedText = this.getCachedCanvasNodeText(file?.path, mergedNode?.id); + if (cachedText !== undefined && String(mergedNode?.type || "") === "text") { + mergedNode.text = cachedText; + } + return mergedNode; + }) + .sort(compareCanvasNodeOrder); + + if (selectedNodes.length === 0) { + throw new Error("The Canvas does not contain the current selection."); + } + + const textBlocks = []; + const markdownFiles = []; + const imageFiles = []; + const warnings = []; + + for (const node of selectedNodes) { + const nodeId = String(node.id || ""); + if (!nodeId) { + continue; + } + + if (node.type === "text") { + const text = typeof node.text === "string" ? node.text.trim() : ""; + if (text) { + textBlocks.push({ id: nodeId, text }); + } else { + warnings.push(`Text node ${nodeId} is empty.`); + } + continue; + } + + if (node.type === "file") { + const selectionEntry = await this.buildCanvasFileSelectionEntry(node, warnings); + if (selectionEntry?.kind === "markdown") { + markdownFiles.push(selectionEntry.value); + } else if (selectionEntry?.kind === "image") { + imageFiles.push(selectionEntry.value); + } + continue; + } + + warnings.push(`Unsupported canvas node type skipped: ${node.type || "unknown"}`); + } + + const normalizedNodeIds = selectedNodes + .map((node) => String(node.id)) + .filter(Boolean) + .sort(); + + return { + canvasPath: file.path, + canvasName: file.basename, + nodeIds: normalizedNodeIds, + textBlocks, + markdownFiles, + imageFiles, + warnings, + title: this.deriveTitle(file.basename, textBlocks, markdownFiles, imageFiles), + }; + } + + getCanvasNodeCenter(node) { + return getCanvasNodeCenter(node); + } + + doesCanvasGroupContainNode(groupNode, candidateNode) { + return doesCanvasGroupContainNode(groupNode, candidateNode); + } + + findSmallestCanvasGroupForNode(nodes, candidateNode) { + if (!Array.isArray(nodes) || !candidateNode) { + return null; + } + + const containingGroups = nodes + .filter((node) => this.doesCanvasGroupContainNode(node, candidateNode)) + .sort((a, b) => ( + (Math.max(0, toFiniteNumber(a?.width, 0)) * Math.max(0, toFiniteNumber(a?.height, 0))) + - (Math.max(0, toFiniteNumber(b?.width, 0)) * Math.max(0, toFiniteNumber(b?.height, 0))) + )); + + return containingGroups[0] || null; + } + + async buildCanvasFileSelectionEntry(node, warnings) { + const filePath = typeof node?.file === "string" ? node.file.trim() : ""; + const abstractFile = await this.waitForCanvasFile(filePath); + if (!(abstractFile instanceof TFile)) { + warnings.push(`Missing file node target: ${filePath}`); + return null; + } + + const vaultBasePath = this.getVaultBasePath(); + const baseEntry = { + id: String(node?.id || ""), + path: abstractFile.path, + absolutePath: vaultBasePath + ? this.resolveVaultPath(vaultBasePath, abstractFile.path) + : "", + name: abstractFile.basename, + }; + + if (abstractFile.extension === "md") { + return { + kind: "markdown", + value: { + ...baseEntry, + content: await this.app.vault.cachedRead(abstractFile), + }, + }; + } + + if (this.isSupportedCanvasImageFile(abstractFile)) { + return { + kind: "image", + value: { + ...baseEntry, + mimeType: this.getCanvasImageMimeType(abstractFile), + }, + }; + } + + warnings.push(`Unsupported file node type skipped: ${filePath}`); + return null; + } + + isSupportedCanvasImageFile(file) { + const extension = String(file?.extension || "").trim().toLowerCase(); + return extension === "png" + || extension === "jpg" + || extension === "jpeg" + || extension === "gif" + || extension === "webp"; + } + + getCanvasImageMimeType(file) { + const extension = String(file?.extension || "").trim().toLowerCase(); + if (extension === "jpg" || extension === "jpeg") { + return "image/jpeg"; + } + if (extension === "gif") { + return "image/gif"; + } + if (extension === "webp") { + return "image/webp"; + } + return "image/png"; + } + + async waitForCanvasFile(filePath, options = {}) { + const normalizedPath = String(filePath || "").trim(); + const timeoutMs = Math.max(0, toFiniteNumber(options.timeoutMs, 4_000)); + const pollMs = Math.max(25, toFiniteNumber(options.pollMs, 100)); + const deadline = Date.now() + timeoutMs; + + while (true) { + const abstractFile = this.app.vault.getAbstractFileByPath(normalizedPath); + if (!normalizedPath || abstractFile instanceof TFile) { + return abstractFile || null; + } + + if (Date.now() >= deadline) { + return abstractFile || null; + } + + await sleep(pollMs); + } + } + + collectConnectedCanvasNodeIds(edges, sourceNodeId) { + const normalizedSourceNodeId = String(sourceNodeId || "").trim(); + const connectedNodeIds = new Set(); + if (!normalizedSourceNodeId || !Array.isArray(edges)) { + return connectedNodeIds; + } + + edges.forEach((edge) => { + const fromNodeId = String(edge?.fromNode || "").trim(); + const toNodeId = String(edge?.toNode || "").trim(); + if (fromNodeId === normalizedSourceNodeId && toNodeId) { + connectedNodeIds.add(toNodeId); + } + if (toNodeId === normalizedSourceNodeId && fromNodeId) { + connectedNodeIds.add(fromNodeId); + } + }); + + return connectedNodeIds; + } + + async appendImplicitCanvasFiles(nodes, warnings, collectedMarkdownFiles, collectedImageFiles, seenNodeIds, predicate) { + const candidateNodes = (Array.isArray(nodes) ? nodes : []) + .filter((node) => { + const nodeId = String(node?.id || "").trim(); + return nodeId + && !seenNodeIds.has(nodeId) + && String(node?.type || "").trim() === "file" + && predicate(node); + }) + .sort(compareCanvasNodeOrder); + + for (const node of candidateNodes) { + const selectionEntry = await this.buildCanvasFileSelectionEntry(node, warnings); + const fileEntry = selectionEntry?.value || null; + if (!fileEntry) { + continue; + } + + const nodeId = String(fileEntry.id || "").trim(); + if (!nodeId || seenNodeIds.has(nodeId)) { + continue; + } + + seenNodeIds.add(nodeId); + if (selectionEntry.kind === "markdown") { + collectedMarkdownFiles.push(fileEntry); + } else if (selectionEntry.kind === "image") { + collectedImageFiles.push(fileEntry); + } + } + } + + async addImplicitCanvasFileContext(selection) { + const canvasPath = String(selection?.canvasPath || "").trim(); + const nodeIds = Array.isArray(selection?.nodeIds) ? selection.nodeIds.map((nodeId) => String(nodeId)).filter(Boolean) : []; + const textBlocks = Array.isArray(selection?.textBlocks) ? selection.textBlocks : []; + const markdownFiles = Array.isArray(selection?.markdownFiles) ? selection.markdownFiles : []; + const imageFiles = Array.isArray(selection?.imageFiles) ? selection.imageFiles : []; + if (!canvasPath || nodeIds.length !== 1 || textBlocks.length !== 1 || (markdownFiles.length > 0 || imageFiles.length > 0)) { + return selection; + } + + const abstractFile = this.app.vault.getAbstractFileByPath(canvasPath); + if (!(abstractFile instanceof TFile) || abstractFile.extension !== "canvas") { + return selection; + } + + const resolvedView = this.getCanvasViewForFile(canvasPath); + let parsed = this.getCanvasRuntimeData(resolvedView); + if (!parsed || !Array.isArray(parsed?.nodes) || !Array.isArray(parsed?.edges)) { + try { + parsed = await this.readCanvasFileData(abstractFile); + } catch { + return selection; + } + } + + const nodes = Array.isArray(parsed?.nodes) ? parsed.nodes : []; + const sourceNodeId = nodeIds[0]; + const sourceNode = nodes.find((node) => String(node?.id || "") === sourceNodeId) || null; + if ((!sourceNode || String(sourceNode?.type || "") !== "text") && parsed !== null) { + try { + const diskParsed = await this.readCanvasFileData(abstractFile); + parsed = diskParsed; + } catch { + return selection; + } + } + + const effectiveNodes = Array.isArray(parsed?.nodes) ? parsed.nodes : []; + const effectiveSourceNode = effectiveNodes.find((node) => String(node?.id || "") === sourceNodeId) || null; + if (!effectiveSourceNode || String(effectiveSourceNode?.type || "") !== "text") { + return selection; + } + + const edges = Array.isArray(parsed?.edges) ? parsed.edges : []; + const implicitMarkdownFiles = []; + const implicitImageFiles = []; + const nextWarnings = Array.isArray(selection?.warnings) ? [...selection.warnings] : []; + const seenNodeIds = new Set([ + ...markdownFiles.map((file) => String(file?.id || "").trim()), + ...imageFiles.map((file) => String(file?.id || "").trim()), + ].filter(Boolean)); + const connectedNodeIds = this.collectConnectedCanvasNodeIds(edges, sourceNodeId); + await this.appendImplicitCanvasFiles( + effectiveNodes, + nextWarnings, + implicitMarkdownFiles, + implicitImageFiles, + seenNodeIds, + (node) => connectedNodeIds.has(String(node?.id || "").trim()) + ); + + const containingGroup = this.findSmallestCanvasGroupForNode(effectiveNodes, effectiveSourceNode); + if (containingGroup) { + await this.appendImplicitCanvasFiles( + effectiveNodes, + nextWarnings, + implicitMarkdownFiles, + implicitImageFiles, + seenNodeIds, + (node) => String(node?.id || "").trim() !== sourceNodeId + && this.doesCanvasGroupContainNode(containingGroup, node) + ); + } + + if (implicitMarkdownFiles.length === 0 && implicitImageFiles.length === 0) { + return selection; + } + + return { + ...selection, + markdownFiles: implicitMarkdownFiles, + imageFiles: implicitImageFiles, + warnings: nextWarnings, + }; + } + + async addImplicitCanvasMarkdownContext(selection) { + return this.addImplicitCanvasFileContext(selection); + } + + async addImplicitGroupMarkdownContext(selection) { + return this.addImplicitCanvasFileContext(selection); + } + + deriveTitle(canvasName, textBlocks, markdownFiles, imageFiles = []) { + if (textBlocks.length > 0) { + const firstLine = textBlocks[0].text.split("\n")[0].trim(); + if (firstLine) { + return firstLine.slice(0, 80); + } + } + + if (markdownFiles.length > 0) { + return markdownFiles[0].name || markdownFiles[0].path; + } + + if (imageFiles.length > 0) { + return imageFiles[0].name || imageFiles[0].path; + } + + return `${canvasName} selection`; + } + + normalizeCanvasNodeData(value) { + if (!value || typeof value !== "object") { + return null; + } + + const nodeId = String(value.id || "").trim(); + if (!nodeId) { + return null; + } + + const normalized = { id: nodeId }; + const nodeType = String(value.type || "").trim(); + if (nodeType) { + normalized.type = nodeType; + } + + if (typeof value.text === "string") { + normalized.text = value.text; + } + + if (typeof value.file === "string") { + normalized.file = value.file; + } + + if (typeof value.subpath === "string") { + normalized.subpath = value.subpath; + } + + return normalized; + } + + extractEditableText(element) { + if (!element) { + return ""; + } + + if (typeof element.value === "string") { + return element.value; + } + + const cmContent = element.matches?.(".cm-content") + ? element + : element.querySelector?.(".cm-content"); + if (cmContent) { + const lines = Array.from(cmContent.querySelectorAll?.(".cm-line") || []); + if (lines.length > 0) { + return lines.map((line) => line.textContent || "").join("\n"); + } + return typeof cmContent.textContent === "string" ? cmContent.textContent : ""; + } + + return typeof element.textContent === "string" ? element.textContent : ""; + } + + rememberDomTextForNodeElement(element, selectedIdSet, textById) { + const nodeId = String(element?.getAttribute?.("data-node-id") || "").trim(); + if (!nodeId || (selectedIdSet.size > 0 && !selectedIdSet.has(nodeId))) { + return; + } + + const editor = element.querySelector?.("textarea, input, [contenteditable='true'], .cm-content"); + if (!editor) { + return; + } + + const liveText = this.extractEditableText(editor); + if (typeof liveText === "string") { + textById.set(nodeId, liveText); + } + } + + extractSelectedDomTextById(view, selectedIdSet) { + const selectedElements = view?.containerEl?.querySelectorAll?.( + "[data-node-id].is-selected, .canvas-node.is-selected[data-node-id], .canvas-node[data-node-id][aria-selected='true']" + ); + const textById = new Map(); + selectedElements?.forEach((element) => this.rememberDomTextForNodeElement(element, selectedIdSet, textById)); + + const activeElement = view?.containerEl?.ownerDocument?.activeElement; + const activeNodeElement = activeElement?.closest?.("[data-node-id]"); + if (activeNodeElement && view?.containerEl?.contains?.(activeNodeElement)) { + this.rememberDomTextForNodeElement(activeNodeElement, selectedIdSet, textById); + } + + return textById; + } + + extractLiveSelectedNodeDataById(view, selectedNodeIds) { + const selectedIdSet = new Set( + (Array.isArray(selectedNodeIds) ? selectedNodeIds : []) + .map((nodeId) => String(nodeId || "").trim()) + .filter(Boolean) + ); + const collected = new Map(); + const visited = new WeakSet(); + const canvas = view?.canvas; + + const rememberNodeData = (candidate) => { + const normalized = this.normalizeCanvasNodeData(candidate); + if (!normalized || !selectedIdSet.has(normalized.id)) { + return; + } + + const previous = collected.get(normalized.id) || {}; + collected.set(normalized.id, { ...previous, ...normalized }); + }; + + const ingest = (value) => { + if (!value) { + return; + } + + if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") { + return; + } + + if (typeof value.getData === "function") { + try { + rememberNodeData(value.getData()); + } catch { + // Ignore runtime node wrappers that throw while being inspected. + } + } + + if (Array.isArray(value)) { + value.forEach(ingest); + return; + } + + if (value instanceof Set) { + value.forEach(ingest); + return; + } + + if (value instanceof Map) { + value.forEach((mapValue) => { + rememberNodeData(mapValue); + ingest(mapValue); + }); + return; + } + + if (typeof value !== "object") { + return; + } + + if (visited.has(value)) { + return; + } + visited.add(value); + + rememberNodeData(value); + rememberNodeData(value.data); + rememberNodeData(value.node); + rememberNodeData(value.node?.data); + rememberNodeData(value.item); + rememberNodeData(value.item?.data); + + [ + value.node, + value.data, + value.item, + value.items, + value.nodes, + value.values, + value.selection, + value.selectedNodes, + ].forEach(ingest); + }; + + ingest(canvas?.selection); + ingest(canvas?.selectionManager?.selection); + ingest(canvas?.selectionManager?.selectedNodes); + ingest(canvas?.selectedNodes); + + if (collected.size < selectedIdSet.size) { + ingest(canvas?.nodes); + } + + const domTextById = this.extractSelectedDomTextById(view, selectedIdSet); + domTextById.forEach((text, nodeId) => { + const previous = collected.get(nodeId) || { id: nodeId, type: "text" }; + collected.set(nodeId, { + ...previous, + id: nodeId, + type: previous.type || "text", + text, + }); + }); + + return collected; + } + + extractSelectedNodeIds(view) { + const canvas = view?.canvas; + const selectedElements = view?.containerEl?.querySelectorAll?.( + "[data-node-id].is-selected, .canvas-node.is-selected[data-node-id], .canvas-node[data-node-id][aria-selected='true']" + ); + const domSelectedNodeIds = new Set(); + if (selectedElements) { + selectedElements.forEach((element) => { + const nodeId = element.getAttribute("data-node-id"); + if (nodeId) { + domSelectedNodeIds.add(String(nodeId)); + } + }); + } + + if (domSelectedNodeIds.size > 0) { + return Array.from(domSelectedNodeIds); + } + + const collected = new Set(); + const visited = new WeakSet(); + const addNodeId = (value) => { + if (typeof value === "string" || typeof value === "number") { + collected.add(String(value)); + } + }; + + const ingest = (value) => { + if (!value) { + return; + } + + if (typeof value === "string" || typeof value === "number") { + addNodeId(value); + return; + } + + if (Array.isArray(value)) { + value.forEach(ingest); + return; + } + + if (value instanceof Set) { + value.forEach(ingest); + return; + } + + if (value instanceof Map) { + value.forEach((mapValue, key) => { + ingest(mapValue); + addNodeId(key); + }); + return; + } + + if (typeof value !== "object") { + return; + } + + if (visited.has(value)) { + return; + } + visited.add(value); + + addNodeId(value.id); + addNodeId(value.node?.id); + addNodeId(value.data?.id); + addNodeId(value.item?.id); + + [ + value.node, + value.data, + value.item, + value.items, + value.nodes, + value.values, + value.selection, + value.selectedNodes, + ].forEach(ingest); + }; + + ingest(canvas?.selection); + ingest(canvas?.selectionManager?.selection); + ingest(canvas?.selectionManager?.selectedNodes); + ingest(canvas?.selectedNodes); + + return Array.from(collected); + } + + inferWorkingDirectory(selection) { + const vaultBasePath = this.getVaultBasePath(); + if (!vaultBasePath) { + return ""; + } + + const candidatePaths = [ + this.resolveVaultPath(vaultBasePath, selection.canvasPath), + ...selection.markdownFiles.map((file) => this.resolveVaultPath(vaultBasePath, file.path)), + ].filter(Boolean); + + for (const candidatePath of candidatePaths) { + const repoRoot = this.findNearestGitRoot(candidatePath); + if (repoRoot) { + return repoRoot; + } + } + + for (const candidatePath of candidatePaths) { + const existingDirectory = this.findNearestExistingDirectory(candidatePath); + if (existingDirectory) { + return existingDirectory; + } + } + + return ""; + } + + getVaultBasePath() { + const adapter = this.app.vault?.adapter; + if (typeof adapter?.getBasePath === "function") { + return adapter.getBasePath(); + } + if (typeof adapter?.basePath === "string" && adapter.basePath.trim()) { + return adapter.basePath.trim(); + } + if (typeof adapter?.path === "string" && adapter.path.trim()) { + return adapter.path.trim(); + } + return ""; + } + + resolveVaultPath(vaultBasePath, relativePath) { + const trimmedPath = String(relativePath || "").trim(); + if (!trimmedPath) { + return ""; + } + + return path.resolve(vaultBasePath, trimmedPath); + } + + findNearestGitRoot(startPath) { + let currentPath = this.findNearestExistingDirectory(startPath); + while (currentPath) { + const gitPath = path.join(currentPath, ".git"); + if (fs.existsSync(gitPath)) { + return currentPath; + } + + const parentPath = path.dirname(currentPath); + if (parentPath === currentPath) { + return ""; + } + currentPath = parentPath; + } + + return ""; + } + + findNearestExistingDirectory(startPath) { + let currentPath = String(startPath || "").trim(); + if (!currentPath) { + return ""; + } + + if (fs.existsSync(currentPath)) { + const stats = fs.statSync(currentPath); + return stats.isDirectory() ? currentPath : path.dirname(currentPath); + } + + currentPath = path.dirname(currentPath); + while (currentPath && currentPath !== path.dirname(currentPath)) { + if (fs.existsSync(currentPath) && fs.statSync(currentPath).isDirectory()) { + return currentPath; + } + currentPath = path.dirname(currentPath); + } + + if (currentPath && fs.existsSync(currentPath) && fs.statSync(currentPath).isDirectory()) { + return currentPath; + } + + return ""; + } +} + +class OpenAgentVoiceGraphView extends ItemView { + constructor(leaf, plugin) { + super(leaf); + this.plugin = plugin; + this.canvas = null; + this.ctx = null; + this.statusEl = null; + this.volumeEl = null; + this.nodeCountEl = null; + this.dictationInputEl = null; + this.dictationSendButtonEl = null; + this.sensitivityInput = null; + this.nodes = []; + this.edges = []; + this.nodeByPath = new Map(); + this.animationFrameId = 0; + this.resizeObserver = null; + this.audioContext = null; + this.audioAnalyser = null; + this.audioData = null; + this.mediaStream = null; + this.audioLevel = 0; + this.frequencyTilt = 0; + this.isMicActive = false; + this.realtimePeerConnection = null; + this.realtimeDataChannel = null; + this.realtimeRemoteAudioEl = null; + this.realtimeSessionId = ""; + this.realtimeStreamDisposer = null; + this.isDictating = false; + this.dictationTranscript = ""; + this.dictationIsSending = false; + this.sensitivity = 1.35; + this.hoveredNode = null; + this.draggingNode = null; + this.isPanning = false; + this.panX = 0; + this.panY = 0; + this.zoom = 1; + this.pointerStart = null; + this.lastFrameAt = 0; + } + + getViewType() { + return VOICE_GRAPH_VIEW_TYPE; + } + + getDisplayText() { + return "Voice Graph"; + } + + getIcon() { + return "audio-lines"; + } + + async onOpen() { + this.render(); + this.rebuildGraph(); + this.startAnimation(); + this.registerEvent(this.app.metadataCache.on("changed", () => this.scheduleGraphRefresh())); + this.registerEvent(this.app.metadataCache.on("resolved", () => this.scheduleGraphRefresh())); + } + + async onClose() { + window.clearTimeout(this.graphRefreshTimer); + this.graphRefreshTimer = null; + this.stopAnimation(); + this.stopDictation(); + this.stopMic(); + this.resizeObserver?.disconnect(); + this.resizeObserver = null; + this.contentEl.empty(); + } + + render() { + this.contentEl.empty(); + this.contentEl.addClass("oa-voice-graph-view"); + + const toolbar = this.contentEl.createDiv({ cls: "oa-voice-graph-toolbar" }); + const title = toolbar.createDiv({ cls: "oa-voice-graph-title" }); + title.createEl("span", { text: "Voice Graph" }); + this.statusEl = title.createEl("small", { text: "Ambient mode" }); + + const controls = toolbar.createDiv({ cls: "oa-voice-graph-controls" }); + const micButton = controls.createEl("button", { + cls: "clickable-icon oa-voice-graph-icon-button", + attr: { "aria-label": "Start microphone" }, + }); + setIcon(micButton, "mic"); + setTooltip(micButton, "Animate graph from microphone"); + micButton.addEventListener("click", () => { + void this.startMic(); + }); + + const stopButton = controls.createEl("button", { + cls: "clickable-icon oa-voice-graph-icon-button", + attr: { "aria-label": "Stop microphone" }, + }); + setIcon(stopButton, "mic-off"); + setTooltip(stopButton, "Stop microphone animation"); + stopButton.addEventListener("click", () => this.stopMic()); + + const refreshButton = controls.createEl("button", { + cls: "clickable-icon oa-voice-graph-icon-button", + attr: { "aria-label": "Refresh graph" }, + }); + setIcon(refreshButton, "refresh-cw"); + setTooltip(refreshButton, "Refresh graph"); + refreshButton.addEventListener("click", () => this.rebuildGraph()); + + const centerButton = controls.createEl("button", { + cls: "clickable-icon oa-voice-graph-icon-button", + attr: { "aria-label": "Center graph" }, + }); + setIcon(centerButton, "scan"); + setTooltip(centerButton, "Center graph"); + centerButton.addEventListener("click", () => this.centerGraph()); + + const dictateButton = controls.createEl("button", { + cls: "clickable-icon oa-voice-graph-icon-button", + attr: { "aria-label": "Start dictation" }, + }); + setIcon(dictateButton, "message-circle"); + setTooltip(dictateButton, "Dictate a message"); + dictateButton.addEventListener("click", () => this.toggleDictation()); + + const sensitivityWrap = controls.createDiv({ cls: "oa-voice-graph-slider" }); + sensitivityWrap.createEl("span", { text: "Sensitivity" }); + this.sensitivityInput = sensitivityWrap.createEl("input", { + attr: { + type: "range", + min: "0.5", + max: "3", + step: "0.05", + value: String(this.sensitivity), + }, + }); + this.sensitivityInput.addEventListener("input", () => { + this.sensitivity = Number(this.sensitivityInput?.value || 1.35); + }); + + const stage = this.contentEl.createDiv({ cls: "oa-voice-graph-stage" }); + this.canvas = stage.createEl("canvas", { cls: "oa-voice-graph-canvas" }); + this.ctx = this.canvas.getContext("2d"); + const hud = stage.createDiv({ cls: "oa-voice-graph-hud" }); + this.nodeCountEl = hud.createEl("span", { text: "0 notes" }); + this.volumeEl = hud.createEl("span", { text: "signal 0%" }); + const dictationDock = stage.createDiv({ cls: "oa-voice-graph-dictation" }); + this.dictationInputEl = dictationDock.createEl("textarea", { + attr: { + rows: "2", + placeholder: "Click the dictation button, or type here...", + }, + }); + this.dictationInputEl.addEventListener("input", () => { + this.dictationTranscript = String(this.dictationInputEl?.value || ""); + this.updateDictationSendState(); + }); + this.dictationSendButtonEl = dictationDock.createEl("button", { text: "Send" }); + this.dictationSendButtonEl.addEventListener("click", () => { + void this.sendDictation(); + }); + this.updateDictationSendState(); + + this.registerCanvasEvents(); + this.resizeObserver = new ResizeObserver(() => this.resizeCanvas()); + this.resizeObserver.observe(stage); + this.resizeCanvas(); + } + + registerCanvasEvents() { + this.canvas.addEventListener("pointerdown", (event) => { + const point = this.getGraphPoint(event); + const node = this.findNodeAt(point.x, point.y); + this.pointerStart = { clientX: event.clientX, clientY: event.clientY, panX: this.panX, panY: this.panY }; + if (node) { + this.draggingNode = node; + node.fixed = true; + } else { + this.isPanning = true; + } + this.canvas.setPointerCapture(event.pointerId); + }); + this.canvas.addEventListener("pointermove", (event) => { + const point = this.getGraphPoint(event); + this.hoveredNode = this.findNodeAt(point.x, point.y); + this.canvas.style.cursor = this.draggingNode ? "grabbing" : (this.hoveredNode ? "pointer" : (this.isPanning ? "grabbing" : "grab")); + if (this.draggingNode) { + this.draggingNode.x = point.x; + this.draggingNode.y = point.y; + this.draggingNode.vx = 0; + this.draggingNode.vy = 0; + } else if (this.isPanning && this.pointerStart) { + this.panX = this.pointerStart.panX + event.clientX - this.pointerStart.clientX; + this.panY = this.pointerStart.panY + event.clientY - this.pointerStart.clientY; + } + }); + this.canvas.addEventListener("pointerup", (event) => this.finishPointer(event)); + this.canvas.addEventListener("pointercancel", (event) => this.finishPointer(event)); + this.canvas.addEventListener("dblclick", (event) => { + const point = this.getGraphPoint(event); + const node = this.findNodeAt(point.x, point.y); + if (node) { + void this.app.workspace.openLinkText(node.path, "", false); + } + }); + this.canvas.addEventListener("wheel", (event) => { + event.preventDefault(); + const before = this.getGraphPoint(event); + const factor = event.deltaY > 0 ? 0.92 : 1.08; + this.zoom = Math.max(0.35, Math.min(2.8, this.zoom * factor)); + const after = this.getGraphPoint(event); + this.panX += (after.x - before.x) * this.zoom; + this.panY += (after.y - before.y) * this.zoom; + }, { passive: false }); + } + + finishPointer(event) { + if (this.draggingNode) { + this.draggingNode.fixed = false; + } + this.draggingNode = null; + this.isPanning = false; + this.pointerStart = null; + try { + this.canvas.releasePointerCapture(event.pointerId); + } catch { + // Pointer capture can already be gone if Obsidian moved focus. + } + } + + resizeCanvas() { + if (!this.canvas) { + return; + } + const rect = this.canvas.getBoundingClientRect(); + const ratio = window.devicePixelRatio || 1; + const width = Math.max(1, Math.floor(rect.width * ratio)); + const height = Math.max(1, Math.floor(rect.height * ratio)); + if (this.canvas.width !== width || this.canvas.height !== height) { + this.canvas.width = width; + this.canvas.height = height; + this.centerGraph({ keepZoom: true }); + } + } + + scheduleGraphRefresh() { + window.clearTimeout(this.graphRefreshTimer); + this.graphRefreshTimer = window.setTimeout(() => this.rebuildGraph(), 300); + } + + rebuildGraph() { + const files = this.app.vault.getMarkdownFiles(); + const fileByPath = new Map(files.map((file) => [file.path, file])); + const degreeByPath = new Map(files.map((file) => [file.path, 0])); + const rawEdges = []; + const resolvedLinks = this.app.metadataCache.resolvedLinks || {}; + Object.entries(resolvedLinks).forEach(([sourcePath, links]) => { + if (!fileByPath.has(sourcePath)) { + return; + } + Object.entries(links || {}).forEach(([targetPath, count]) => { + if (!fileByPath.has(targetPath) || sourcePath === targetPath) { + return; + } + const weight = Math.max(1, Number(count || 1)); + rawEdges.push({ sourcePath, targetPath, weight }); + degreeByPath.set(sourcePath, (degreeByPath.get(sourcePath) || 0) + weight); + degreeByPath.set(targetPath, (degreeByPath.get(targetPath) || 0) + weight); + }); + }); + + const activePath = this.app.workspace.getActiveFile()?.path || ""; + const selectedPaths = files + .map((file) => ({ + file, + score: (degreeByPath.get(file.path) || 0) + (file.path === activePath ? 1_000 : 0), + })) + .sort((a, b) => b.score - a.score || a.file.path.localeCompare(b.file.path)) + .slice(0, VOICE_GRAPH_MAX_NODES) + .map((entry) => entry.file.path); + const selectedSet = new Set(selectedPaths); + const previousByPath = this.nodeByPath; + this.nodeByPath = new Map(); + this.nodes = selectedPaths.map((filePath, index) => { + const file = fileByPath.get(filePath); + const previous = previousByPath.get(filePath); + const angle = (Math.PI * 2 * index) / Math.max(1, selectedPaths.length); + const radius = 170 + 7 * Math.sqrt(index); + const degree = degreeByPath.get(filePath) || 0; + const node = { + path: filePath, + label: file?.basename || path.basename(filePath, ".md"), + x: previous?.x ?? Math.cos(angle) * radius, + y: previous?.y ?? Math.sin(angle) * radius, + vx: previous?.vx ?? 0, + vy: previous?.vy ?? 0, + radius: Math.min(VOICE_GRAPH_MAX_NODE_RADIUS, VOICE_GRAPH_MIN_NODE_RADIUS + Math.sqrt(degree) * 1.7), + pulse: Math.random() * Math.PI * 2, + degree, + fixed: false, + }; + this.nodeByPath.set(filePath, node); + return node; + }); + this.edges = rawEdges + .filter((edge) => selectedSet.has(edge.sourcePath) && selectedSet.has(edge.targetPath)) + .slice(0, VOICE_GRAPH_MAX_EDGES) + .map((edge) => ({ + source: this.nodeByPath.get(edge.sourcePath), + target: this.nodeByPath.get(edge.targetPath), + weight: edge.weight, + })) + .filter((edge) => edge.source && edge.target); + this.nodeCountEl?.setText(`${this.nodes.length} notes, ${this.edges.length} links`); + this.centerGraph({ keepZoom: true }); + } + + centerGraph(options = {}) { + const rect = this.canvas?.getBoundingClientRect(); + if (!rect) { + return; + } + this.panX = rect.width / 2; + this.panY = rect.height / 2; + if (!options.keepZoom) { + this.zoom = 1; + } + } + + async startMic() { + if (this.isMicActive) { + return this.mediaStream; + } + try { + const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false }); + const AudioContextCtor = window.AudioContext || window.webkitAudioContext; + this.audioContext = new AudioContextCtor(); + const source = this.audioContext.createMediaStreamSource(stream); + this.audioAnalyser = this.audioContext.createAnalyser(); + this.audioAnalyser.fftSize = 512; + this.audioAnalyser.smoothingTimeConstant = 0.72; + this.audioData = new Uint8Array(this.audioAnalyser.frequencyBinCount); + source.connect(this.audioAnalyser); + this.mediaStream = stream; + this.isMicActive = true; + this.statusEl?.setText("Listening"); + return stream; + } catch (error) { + this.statusEl?.setText("Mic unavailable"); + new Notice(`Voice Graph could not start the microphone: ${error.message || error}`); + throw error; + } + } + + stopMic() { + if (this.isDictating) { + this.stopDictation(); + } + this.mediaStream?.getTracks?.().forEach((track) => track.stop()); + this.mediaStream = null; + this.audioContext?.close?.().catch(() => {}); + this.audioContext = null; + this.audioAnalyser = null; + this.audioData = null; + this.isMicActive = false; + this.audioLevel = 0; + this.frequencyTilt = 0; + this.statusEl?.setText("Ambient mode"); + } + + toggleDictation() { + if (this.isDictating) { + this.stopDictation(); + return; + } + this.startDictation(); + } + + async startDictation() { + if (this.isDictating) { + return; + } + this.stopDictation({ stopServer: false }); + this.isDictating = true; + this.statusEl?.setText("Starting voice mode"); + this.updateDictationSendState(); + + try { + await this.startMic(); + const offerSdp = await this.createRealtimeWebRtcOffer(); + const context = this.plugin.getVaultPetQuickChatContext(); + const response = await this.plugin.api.startVoiceRealtime({ + cwd: context.cwd || this.plugin.getVaultBasePath(), + outputModality: "text", + offerSdp, + prompt: [ + "You are OpenAgent Voice Graph inside Obsidian.", + "Transcribe the user's speech accurately, including Vietnamese.", + "Keep assistant responses concise. The UI primarily uses the user transcript as a prompt.", + ].join(" "), + runtimeConfig: this.plugin.buildRuntimeConfigPayload(), + }); + this.realtimeSessionId = String(response.sessionId || ""); + if (!this.realtimeSessionId) { + throw new Error("OpenAgent daemon did not return a realtime session id."); + } + const answerSdp = String(response.answerSdp || "").trim(); + if (!answerSdp) { + throw new Error("OpenAgent daemon did not return a realtime SDP answer."); + } + await this.applyRealtimeWebRtcAnswer(answerSdp); + this.openRealtimeTranscriptStream(this.realtimeSessionId); + this.statusEl?.setText("Voice mode"); + } catch (error) { + this.stopDictation({ stopServer: true }); + new Notice(`Voice Graph could not start Codex voice mode: ${error.message || error}`); + } + } + + async createRealtimeWebRtcOffer() { + const PeerConnectionCtor = window.RTCPeerConnection || window.webkitRTCPeerConnection; + if (!PeerConnectionCtor) { + throw new Error("WebRTC is not available in this Obsidian runtime."); + } + if (!this.mediaStream) { + throw new Error("Microphone stream is not active."); + } + const audioTracks = this.mediaStream.getAudioTracks?.() || []; + if (!audioTracks.length) { + throw new Error("Microphone stream does not contain an audio track."); + } + + this.stopRealtimePeerConnection(); + const peerConnection = new PeerConnectionCtor({ iceServers: [] }); + this.realtimePeerConnection = peerConnection; + this.realtimeDataChannel = peerConnection.createDataChannel("oai-events", { ordered: true }); + + peerConnection.onconnectionstatechange = () => { + const state = String(peerConnection.connectionState || ""); + if (state === "connected") { + this.statusEl?.setText("Voice mode"); + } else if (state === "failed" || state === "disconnected") { + this.statusEl?.setText("Voice connection interrupted"); + } + }; + peerConnection.ontrack = (event) => { + const stream = event.streams?.[0]; + if (!stream) { + return; + } + if (!this.realtimeRemoteAudioEl) { + this.realtimeRemoteAudioEl = document.createElement("audio"); + this.realtimeRemoteAudioEl.autoplay = true; + this.realtimeRemoteAudioEl.hidden = true; + this.contentEl.appendChild(this.realtimeRemoteAudioEl); + } + this.realtimeRemoteAudioEl.srcObject = stream; + void this.realtimeRemoteAudioEl.play?.().catch(() => {}); + }; + + for (const track of audioTracks) { + peerConnection.addTransceiver(track, { direction: "sendrecv" }); + } + + const offer = await peerConnection.createOffer({ + offerToReceiveAudio: true, + offerToReceiveVideo: false, + }); + await peerConnection.setLocalDescription(offer); + await this.waitForRealtimeIceGathering(peerConnection, 5_000); + const sdp = String(peerConnection.localDescription?.sdp || "").trim(); + if (!sdp) { + throw new Error("WebRTC did not produce a local SDP offer."); + } + return sdp; + } + + waitForRealtimeIceGathering(peerConnection, timeoutMs) { + if (peerConnection.iceGatheringState === "complete") { + return Promise.resolve(); + } + return new Promise((resolve) => { + let settled = false; + const finish = () => { + if (settled) { + return; + } + settled = true; + window.clearTimeout(timer); + peerConnection.removeEventListener("icegatheringstatechange", onIceGatheringStateChange); + resolve(); + }; + const onIceGatheringStateChange = () => { + if (peerConnection.iceGatheringState === "complete") { + finish(); + } + }; + const timer = window.setTimeout(finish, timeoutMs); + peerConnection.addEventListener("icegatheringstatechange", onIceGatheringStateChange); + }); + } + + async applyRealtimeWebRtcAnswer(answerSdp) { + if (!this.realtimePeerConnection) { + throw new Error("WebRTC peer connection is not active."); + } + await this.realtimePeerConnection.setRemoteDescription({ + type: "answer", + sdp: answerSdp, + }); + } + + openRealtimeTranscriptStream(sessionId) { + this.realtimeStreamDisposer?.(); + this.realtimeStreamDisposer = this.plugin.api.openVoiceRealtimeStream(sessionId, { + onEvent: (event, payload) => { + if (event === "transcript.delta" || event === "transcript.done" || event === "ready") { + const transcript = String(payload?.transcript || payload?.text || "").trim(); + if (transcript) { + this.dictationTranscript = transcript; + if (this.dictationInputEl) { + this.dictationInputEl.value = transcript; + } + this.updateDictationSendState(); + } + } + if (event === "error") { + new Notice(String(payload?.message || "Codex voice mode failed.")); + } + if (event === "closed") { + this.isDictating = false; + if (!this.isMicActive) { + this.statusEl?.setText("Ambient mode"); + } + } + }, + onError: (error) => { + new Notice(`Voice transcript stream failed: ${error.message || error}`); + }, + }); + } + + stopDictation(options = {}) { + const stopServer = options.stopServer !== false; + const sessionId = this.realtimeSessionId; + this.realtimeStreamDisposer?.(); + this.realtimeStreamDisposer = null; + this.realtimeSessionId = ""; + this.stopRealtimePeerConnection(); + if (stopServer && sessionId) { + void this.plugin.api.stopVoiceRealtime(sessionId).catch(() => {}); + } + this.isDictating = false; + if (!this.isMicActive) { + this.statusEl?.setText("Ambient mode"); + } + } + + stopRealtimePeerConnection() { + if (this.realtimeDataChannel) { + try { + this.realtimeDataChannel.close(); + } catch { + // Ignore peer cleanup failures. + } + } + if (this.realtimePeerConnection) { + try { + this.realtimePeerConnection.getSenders?.().forEach((sender) => { + try { + this.realtimePeerConnection.removeTrack(sender); + } catch { + // Ignore senders already removed by the runtime. + } + }); + this.realtimePeerConnection.close(); + } catch { + // Ignore peer cleanup failures. + } + } + if (this.realtimeRemoteAudioEl) { + try { + this.realtimeRemoteAudioEl.pause(); + this.realtimeRemoteAudioEl.srcObject = null; + this.realtimeRemoteAudioEl.remove(); + } catch { + // Ignore audio element cleanup failures. + } + } + this.realtimeDataChannel = null; + this.realtimePeerConnection = null; + this.realtimeRemoteAudioEl = null; + } + + updateDictationSendState() { + if (!this.dictationSendButtonEl) { + return; + } + this.dictationSendButtonEl.disabled = this.dictationIsSending || !String(this.dictationTranscript || "").trim(); + this.dictationSendButtonEl.textContent = this.dictationIsSending ? "Sending..." : "Send"; + } + + async sendDictation() { + const message = String(this.dictationTranscript || this.dictationInputEl?.value || "").trim(); + if (!message) { + new Notice("Dictate or type a message first."); + return; + } + this.stopDictation(); + this.dictationIsSending = true; + this.updateDictationSendState(); + try { + const context = this.plugin.getVaultPetQuickChatContext({ preferVaultCanvas: true }); + await this.plugin.startVaultPetChat(context, message); + this.dictationTranscript = ""; + if (this.dictationInputEl) { + this.dictationInputEl.value = ""; + } + this.updateDictationSendState(); + } catch (error) { + new Notice(String(error?.message || error)); + } finally { + this.dictationIsSending = false; + this.updateDictationSendState(); + } + } + + startAnimation() { + if (this.animationFrameId) { + return; + } + const frame = (timestamp) => { + const dt = Math.min(0.05, Math.max(0.001, (timestamp - (this.lastFrameAt || timestamp)) / 1000)); + this.lastFrameAt = timestamp; + this.updateAudio(); + this.stepPhysics(dt, timestamp / 1000); + this.draw(timestamp / 1000); + this.animationFrameId = window.requestAnimationFrame(frame); + }; + this.animationFrameId = window.requestAnimationFrame(frame); + } + + stopAnimation() { + if (this.animationFrameId) { + window.cancelAnimationFrame(this.animationFrameId); + this.animationFrameId = 0; + } + } + + updateAudio() { + if (!this.audioAnalyser || !this.audioData) { + this.audioLevel *= 0.92; + this.frequencyTilt *= 0.9; + return; + } + this.audioAnalyser.getByteFrequencyData(this.audioData); + let sum = 0; + let high = 0; + for (let index = 0; index < this.audioData.length; index += 1) { + const value = this.audioData[index] / 255; + sum += value * value; + if (index > this.audioData.length * 0.45) { + high += value; + } + } + const rms = Math.sqrt(sum / Math.max(1, this.audioData.length)); + const highAverage = high / Math.max(1, this.audioData.length * 0.55); + this.audioLevel = this.audioLevel * 0.72 + rms * 0.28; + this.frequencyTilt = this.frequencyTilt * 0.78 + highAverage * 0.22; + this.volumeEl?.setText(`signal ${Math.round(Math.min(1, this.audioLevel * 3) * 100)}%`); + } + + stepPhysics(dt, time) { + const signal = Math.min(2.2, (this.audioLevel * 5.5 + 0.08) * this.sensitivity); + const repel = 4800 + signal * 8800; + const linkDistance = 70 + signal * 44; + for (let i = 0; i < this.nodes.length; i += 1) { + const a = this.nodes[i]; + for (let j = i + 1; j < this.nodes.length; j += 1) { + const b = this.nodes[j]; + const dx = b.x - a.x; + const dy = b.y - a.y; + const distanceSq = Math.max(80, dx * dx + dy * dy); + const force = repel / distanceSq; + const distance = Math.sqrt(distanceSq); + const fx = (dx / distance) * force; + const fy = (dy / distance) * force; + if (!a.fixed) { + a.vx -= fx * dt; + a.vy -= fy * dt; + } + if (!b.fixed) { + b.vx += fx * dt; + b.vy += fy * dt; + } + } + } + this.edges.forEach((edge) => { + const dx = edge.target.x - edge.source.x; + const dy = edge.target.y - edge.source.y; + const distance = Math.max(1, Math.sqrt(dx * dx + dy * dy)); + const desired = linkDistance + Math.min(45, edge.weight * 5); + const force = (distance - desired) * 0.018; + const fx = (dx / distance) * force; + const fy = (dy / distance) * force; + if (!edge.source.fixed) { + edge.source.vx += fx; + edge.source.vy += fy; + } + if (!edge.target.fixed) { + edge.target.vx -= fx; + edge.target.vy -= fy; + } + }); + this.nodes.forEach((node, index) => { + const centerPull = 0.018; + if (!node.fixed) { + node.vx += -node.x * centerPull * dt; + node.vy += -node.y * centerPull * dt; + const wave = Math.sin(time * (2.2 + this.frequencyTilt * 4) + node.pulse + index * 0.11); + node.vx += Math.cos(node.pulse) * wave * signal * 4.5 * dt; + node.vy += Math.sin(node.pulse) * wave * signal * 4.5 * dt; + node.x += node.vx; + node.y += node.vy; + } + node.vx *= 0.86; + node.vy *= 0.86; + }); + } + + draw(time) { + if (!this.ctx || !this.canvas) { + return; + } + const ratio = window.devicePixelRatio || 1; + const width = this.canvas.width; + const height = this.canvas.height; + const cssWidth = width / ratio; + const cssHeight = height / ratio; + const signal = Math.min(1.2, (this.audioLevel * 3.2 + 0.035) * this.sensitivity); + this.ctx.setTransform(1, 0, 0, 1, 0, 0); + this.ctx.clearRect(0, 0, width, height); + this.ctx.fillStyle = getComputedStyle(this.contentEl).getPropertyValue("--background-primary") || "#111"; + this.ctx.fillRect(0, 0, width, height); + this.ctx.setTransform(ratio * this.zoom, 0, 0, ratio * this.zoom, ratio * this.panX, ratio * this.panY); + const styles = getComputedStyle(this.contentEl); + const normalColor = styles.getPropertyValue("--text-normal").trim() || "#d7d7d7"; + const mutedColor = styles.getPropertyValue("--text-muted").trim() || "#9a9a9a"; + const faintColor = styles.getPropertyValue("--background-modifier-border").trim() || "#555"; + + this.edges.forEach((edge) => { + this.ctx.beginPath(); + this.ctx.moveTo(edge.source.x, edge.source.y); + this.ctx.lineTo(edge.target.x, edge.target.y); + this.ctx.globalAlpha = 0.2 + Math.min(0.22, signal * 0.12); + this.ctx.strokeStyle = mutedColor; + this.ctx.lineWidth = Math.min(2, 0.65 + edge.weight * 0.12 + signal * 0.16) / this.zoom; + this.ctx.stroke(); + }); + this.ctx.globalAlpha = 1; + + this.nodes.forEach((node, index) => { + const isHot = node === this.hoveredNode; + const breath = Math.sin(time * 4.5 + node.pulse + index * 0.05) * 0.5 + 0.5; + const reactiveRadius = node.radius + signal * (1.2 + breath * 2.6) + (isHot ? 2.5 : 0); + + this.ctx.beginPath(); + this.ctx.arc(node.x, node.y, reactiveRadius, 0, Math.PI * 2); + this.ctx.fillStyle = isHot ? normalColor : mutedColor; + this.ctx.globalAlpha = isHot ? 0.92 : 0.54 + Math.min(0.12, signal * 0.06); + this.ctx.fill(); + this.ctx.lineWidth = (isHot ? 1.7 : 0.9) / this.zoom; + this.ctx.globalAlpha = isHot ? 0.82 : 0.36; + this.ctx.strokeStyle = normalColor; + this.ctx.stroke(); + this.ctx.globalAlpha = 1; + + if ((isHot || this.zoom > 1.02) && reactiveRadius > 7) { + this.ctx.font = `${Math.max(10, 11 / this.zoom)}px -apple-system, BlinkMacSystemFont, sans-serif`; + this.ctx.globalAlpha = isHot ? 0.88 : 0.48; + this.ctx.fillStyle = isHot ? normalColor : faintColor; + this.ctx.textAlign = "center"; + this.ctx.textBaseline = "top"; + this.ctx.fillText(node.label.slice(0, 34), node.x, node.y + reactiveRadius + 7 / this.zoom); + this.ctx.globalAlpha = 1; + } + }); + + if (this.nodes.length === 0) { + this.ctx.setTransform(ratio, 0, 0, ratio, 0, 0); + this.ctx.fillStyle = "rgba(180, 188, 202, 0.85)"; + this.ctx.font = "14px -apple-system, BlinkMacSystemFont, sans-serif"; + this.ctx.textAlign = "center"; + this.ctx.fillText("No markdown links found yet", cssWidth / 2, cssHeight / 2); + } + } + + getGraphPoint(event) { + const rect = this.canvas.getBoundingClientRect(); + return { + x: (event.clientX - rect.left - this.panX) / this.zoom, + y: (event.clientY - rect.top - this.panY) / this.zoom, + }; + } + + findNodeAt(x, y) { + for (let index = this.nodes.length - 1; index >= 0; index -= 1) { + const node = this.nodes[index]; + const radius = node.radius + Math.max(8, this.audioLevel * 24); + const dx = x - node.x; + const dy = y - node.y; + if (dx * dx + dy * dy <= radius * radius) { + return node; + } + } + return null; + } +} + +class OpenAgentView extends ItemView { + constructor(leaf, plugin) { + super(leaf); + this.plugin = plugin; + this.cwdInput = null; + this.composerInput = null; + this.panelScrollEl = null; + this.panelScrollKey = ""; + this.expandedToolMessages = new Set(); + this.visibleMessageCountByTask = new Map(); + this.visibleSettingsTaskCountByKey = new Map(); + this.settingsTaskTab = "all"; + this.lastRenderedActiveTaskId = null; + this.pendingInitialPanelScrollKey = ""; + this.lastRenderedTaskMessageStateByTask = new Map(); + this.activeSelectableMessageEl = null; + this.isClampingMessageSelection = false; + this.taskListHovered = false; + this.renderedActiveTaskId = ""; + this.activeTaskStatusEl = null; + this.messagesSectionEl = null; + } + + getViewType() { + return VIEW_TYPE; + } + + getDisplayText() { + return "OpenAgent"; + } + + getIcon() { + return "bot"; + } + + async onOpen() { + const document = this.containerEl?.ownerDocument; + if (document) { + this.registerDomEvent(document, "selectionchange", () => this.handleMessageSelectionChange()); + this.registerDomEvent(document, "mouseup", () => this.clearActiveMessageSelection()); + this.registerDomEvent(document, "pointerup", () => this.clearActiveMessageSelection()); + } + await this.plugin.refreshTasks(); + this.render(); + } + + clearActiveMessageSelection() { + this.activeSelectableMessageEl = null; + } + + bindMessageSelectionGuard(messageTextEl) { + if (!messageTextEl) { + return; + } + + const activateMessageSelection = (event) => { + if (event && typeof event.button === "number" && event.button !== 0) { + return; + } + this.activeSelectableMessageEl = messageTextEl; + }; + + messageTextEl.addEventListener("mousedown", activateMessageSelection); + messageTextEl.addEventListener("pointerdown", activateMessageSelection); + } + + bindTaskListHoverGuard(taskListEl) { + if (!taskListEl) { + return; + } + + taskListEl.addEventListener("mouseenter", () => { + this.taskListHovered = true; + this.plugin.setTaskListHovered(true); + }); + taskListEl.addEventListener("mouseleave", () => { + this.taskListHovered = false; + this.plugin.setTaskListHovered(false); + }); + } + + hasHoveredTaskList() { + return this.taskListHovered === true; + } + + handleMessageSelectionChange() { + if (this.isClampingMessageSelection) { + return; + } + + const messageTextEl = this.activeSelectableMessageEl; + if (!messageTextEl || !messageTextEl.isConnected) { + this.activeSelectableMessageEl = null; + return; + } + + const document = messageTextEl.ownerDocument; + const selection = document?.getSelection?.(); + if (!selection || selection.rangeCount === 0 || selection.isCollapsed) { + return; + } + + const range = selection.getRangeAt(0); + const startInside = messageTextEl.contains(range.startContainer); + const endInside = messageTextEl.contains(range.endContainer); + if (startInside && endInside) { + return; + } + + if (!range.intersectsNode(messageTextEl)) { + return; + } + + const clampedRange = range.cloneRange(); + if (!startInside) { + clampedRange.setStart(messageTextEl, 0); + } + if (!endInside) { + clampedRange.setEnd(messageTextEl, messageTextEl.childNodes.length); + } + + this.isClampingMessageSelection = true; + try { + selection.removeAllRanges(); + selection.addRange(clampedRange); + } finally { + this.isClampingMessageSelection = false; + } + } + + captureComposerFocusState() { + const input = this.composerInput; + if (!input) { + return null; + } + + const activeElement = input.ownerDocument?.activeElement; + if (activeElement !== input) { + return null; + } + + return { + taskId: String(input.dataset.taskId || ""), + selectionStart: typeof input.selectionStart === "number" ? input.selectionStart : null, + selectionEnd: typeof input.selectionEnd === "number" ? input.selectionEnd : null, + selectionDirection: input.selectionDirection || "none", + scrollTop: input.scrollTop || 0, + }; + } + + restoreComposerFocusState(focusState) { + if (!focusState || !this.composerInput) { + return; + } + + if (String(this.composerInput.dataset.taskId || "") !== String(focusState.taskId || "")) { + return; + } + + window.requestAnimationFrame(() => { + if (!this.composerInput) { + return; + } + + this.composerInput.focus(); + if (typeof focusState.selectionStart === "number" && typeof focusState.selectionEnd === "number") { + try { + this.composerInput.setSelectionRange( + focusState.selectionStart, + focusState.selectionEnd, + focusState.selectionDirection + ); + } catch { + // Ignore selection restore failures on detached or unsupported inputs. + } + } + this.composerInput.scrollTop = Number(focusState.scrollTop || 0); + }); + } + + capturePanelScrollState() { + const scrollEl = this.panelScrollEl; + if (!scrollEl) { + return null; + } + + const maxScrollTop = Math.max(0, scrollEl.scrollHeight - scrollEl.clientHeight); + const scrollTop = Number(scrollEl.scrollTop || 0); + const distanceFromBottom = Math.max(0, maxScrollTop - scrollTop); + return { + key: this.panelScrollKey, + scrollTop, + atBottom: distanceFromBottom <= 32, + }; + } + + restorePanelScrollState(scrollState, nextKey) { + if (!scrollState || scrollState.key !== nextKey) { + return; + } + + window.requestAnimationFrame(() => { + const scrollEl = this.panelScrollEl; + if (!scrollEl || this.panelScrollKey !== nextKey) { + return; + } + + const maxScrollTop = Math.max(0, scrollEl.scrollHeight - scrollEl.clientHeight); + scrollEl.scrollTop = scrollState.atBottom + ? maxScrollTop + : Math.min(Number(scrollState.scrollTop || 0), maxScrollTop); + }); + } + + getLatestMessageState(task) { + const taskId = String(task?.taskId || ""); + const messages = Array.isArray(task?.messages) ? task.messages : []; + if (!taskId || messages.length === 0) { + return { + taskId, + count: messages.length, + lastIdentity: "", + lastRole: "", + }; + } + + const lastIndex = messages.length - 1; + const lastMessage = messages[lastIndex]; + return { + taskId, + count: messages.length, + lastIdentity: this.getMessageIdentity(lastMessage, lastIndex), + lastRole: String(lastMessage?.role || ""), + }; + } + + shouldAutoScrollToLatestUserMessage(task, options = {}) { + if (options.didSwitchActiveTask === true) { + return false; + } + + const nextState = this.getLatestMessageState(task); + if (!nextState.taskId || nextState.count === 0 || nextState.lastRole !== "user") { + return false; + } + + const previousState = this.lastRenderedTaskMessageStateByTask.get(nextState.taskId); + if (!previousState) { + return false; + } + + return ( + nextState.count > previousState.count + || nextState.lastIdentity !== previousState.lastIdentity + ); + } + + rememberRenderedTaskMessageState(task) { + const state = this.getLatestMessageState(task); + if (!state.taskId) { + return; + } + + this.lastRenderedTaskMessageStateByTask.set(state.taskId, state); + } + + scrollMessageIntoView(messageEl, nextKey) { + if (!messageEl) { + return; + } + + window.requestAnimationFrame(() => { + const scrollEl = this.panelScrollEl; + if (!scrollEl || this.panelScrollKey !== nextKey || !messageEl.isConnected) { + return; + } + + messageEl.scrollIntoView({ + block: "end", + inline: "nearest", + }); + }); + } + + queueInitialPanelScroll(nextKey) { + this.pendingInitialPanelScrollKey = String(nextKey || "").trim(); + } + + shouldInitialScrollToConversationEnd(nextKey) { + return String(nextKey || "").trim() !== "" + && this.pendingInitialPanelScrollKey === String(nextKey || "").trim(); + } + + consumeInitialPanelScroll(nextKey) { + if (this.pendingInitialPanelScrollKey === String(nextKey || "").trim()) { + this.pendingInitialPanelScrollKey = ""; + } + } + + hasFocusedComposer() { + const input = this.composerInput; + return Boolean(input && input.ownerDocument?.activeElement === input); + } + + getMessageIdentity(message, index) { + return String( + message?.id + || message?.streamKey + || `${message?.role || "system"}:${message?.kind || ""}:${message?.turnId || ""}:${message?.itemId || ""}:${index}` + ); + } + + isToolMessage(message) { + return String(message?.kind || "").trim() === "tool"; + } + + getToolMessagePreview(text) { + const compact = String(text || "").replace(/\s+/g, " ").trim(); + return compact || "Tool output"; + } + + resetActiveTaskDomRefs() { + this.renderedActiveTaskId = ""; + this.activeTaskStatusEl = null; + this.messagesSectionEl = null; + } + + updateActiveTaskStatus(task) { + if (!this.activeTaskStatusEl?.isConnected) { + return; + } + + const status = String(task?.status || "idle"); + this.activeTaskStatusEl.className = `oa-status-tag${["running", "starting"].includes(status) ? " is-running" : ""}`; + this.activeTaskStatusEl.setText(status); + } + + renderTaskMessages(messagesSection, activeTask, options = {}) { + if (!messagesSection) { + return; + } + + const panelScrollState = options.panelScrollState || null; + const panelScrollKey = String(options.panelScrollKey || this.panelScrollKey || ""); + const didSwitchActiveTask = options.didSwitchActiveTask === true; + const shouldAutoScrollToLatestUserMessage = this.shouldAutoScrollToLatestUserMessage(activeTask, { + didSwitchActiveTask, + }); + + messagesSection.empty(); + const messages = Array.isArray(activeTask?.messages) ? activeTask.messages : []; + const messagesLoaded = this.plugin.taskHasLoadedMessages(activeTask); + const messageCount = Math.max(Number(activeTask?.messageCount) || 0, messages.length); + const renderSelectionImageBubble = () => { + const messageList = messagesSection.createDiv({ cls: "oa-message-list" }); + this.plugin.renderSelectionImageMessage(messageList, activeTask?.selectionContext); + return messageList; + }; + + if (!messagesLoaded && messageCount > 0) { + renderSelectionImageBubble(); + messagesSection.createDiv({ + cls: "oa-muted", + text: `Loading conversation (${messageCount} messages)...`, + }); + this.restorePanelScrollState(panelScrollState, panelScrollKey); + this.rememberRenderedTaskMessageState(activeTask); + return; + } + + if (messages.length === 0) { + renderSelectionImageBubble(); + messagesSection.createDiv({ cls: "oa-muted", text: "No conversation yet." }); + this.restorePanelScrollState(panelScrollState, panelScrollKey); + this.rememberRenderedTaskMessageState(activeTask); + return; + } + + const visibleMessageCount = this.syncVisibleMessageCount(activeTask.taskId, messages.length, { + reset: didSwitchActiveTask, + }); + const firstVisibleIndex = Math.max(0, messages.length - visibleMessageCount); + const hiddenMessageCount = firstVisibleIndex; + const shouldInitialScrollToConversationEnd = this.shouldInitialScrollToConversationEnd(panelScrollKey); + let latestMessageEl = null; + let latestUserMessageEl = null; + + if (hiddenMessageCount > 0) { + const pagination = messagesSection.createDiv({ cls: "oa-message-pagination" }); + pagination.createDiv({ + cls: "oa-message-pagination-summary", + text: `Showing ${visibleMessageCount} latest of ${messages.length} messages.`, + }); + const paginationActions = pagination.createDiv({ cls: "oa-action-row oa-message-pagination-actions" }); + const nextVisibleCount = Math.min(messages.length, visibleMessageCount + CHAT_MESSAGE_PAGE_SIZE); + const loadCount = nextVisibleCount - visibleMessageCount; + this.makeButton( + paginationActions, + `Show ${loadCount} earlier`, + () => this.showOlderMessages(activeTask.taskId, messages.length), + false, + { runOnMouseDown: true } + ); + } + + const messageList = renderSelectionImageBubble(); + messages.slice(firstVisibleIndex).forEach((message, index) => { + const messageIndex = firstVisibleIndex + index; + const isToolMessage = this.isToolMessage(message); + const item = messageList.createDiv({ + cls: `oa-message oa-chat-message oa-role-${message.role || "system"}${isToolMessage ? " oa-message-tool" : ""}`, + }); + latestMessageEl = item; + if (isToolMessage) { + const messageKey = this.getMessageIdentity(message, messageIndex); + const expanded = this.expandedToolMessages.has(messageKey); + item.toggleClass("is-expanded", expanded); + item.toggleClass("is-collapsed", !expanded); + + const toggle = item.createEl("button", { + cls: "oa-message-toggle", + attr: { + type: "button", + "aria-expanded": expanded ? "true" : "false", + }, + }); + const toggleText = toggle.createDiv({ cls: "oa-message-toggle-text" }); + toggleText.createDiv({ + cls: "oa-message-preview", + text: this.getToolMessagePreview(message.text), + }); + toggle.createDiv({ + cls: "oa-message-toggle-icon", + text: expanded ? "-" : "+", + }); + + const body = item.createDiv({ + cls: "oa-message-text", + text: message.text || "", + }); + this.bindMessageSelectionGuard(body); + body.toggleClass("is-hidden", !expanded); + + toggle.addEventListener("click", () => { + if (this.expandedToolMessages.has(messageKey)) { + this.expandedToolMessages.delete(messageKey); + item.removeClass("is-expanded"); + item.addClass("is-collapsed"); + toggle.setAttribute("aria-expanded", "false"); + body.addClass("is-hidden"); + const icon = toggle.querySelector(".oa-message-toggle-icon"); + if (icon) { + icon.textContent = "+"; + } + return; + } + + this.expandedToolMessages.add(messageKey); + item.removeClass("is-collapsed"); + item.addClass("is-expanded"); + toggle.setAttribute("aria-expanded", "true"); + body.removeClass("is-hidden"); + const icon = toggle.querySelector(".oa-message-toggle-icon"); + if (icon) { + icon.textContent = "-"; + } + }); + return; + } + + const body = item.createDiv({ + cls: "oa-message-text", + text: message.text || "", + }); + this.bindMessageSelectionGuard(body); + if (messageIndex === messages.length - 1 && String(message.role || "") === "user") { + latestUserMessageEl = item; + } + }); + + if (shouldInitialScrollToConversationEnd && latestMessageEl) { + this.consumeInitialPanelScroll(panelScrollKey); + this.scrollMessageIntoView(latestMessageEl, panelScrollKey); + } else if (shouldAutoScrollToLatestUserMessage && latestUserMessageEl) { + this.scrollMessageIntoView(latestUserMessageEl, panelScrollKey); + } else { + this.restorePanelScrollState(panelScrollState, panelScrollKey); + } + + this.rememberRenderedTaskMessageState(activeTask); + } + + tryApplyStreamingTaskUpdate(task) { + const normalizedTaskId = String(task?.taskId || "").trim(); + if (!normalizedTaskId) { + return false; + } + + if (this.plugin.getPanelTab() !== PANEL_TAB_OPTIONS.TASK_LIST) { + return false; + } + + const activeTask = this.plugin.getActiveTask(); + if (normalizedTaskId !== String(activeTask?.taskId || "").trim()) { + return false; + } + + if (normalizedTaskId !== String(this.renderedActiveTaskId || "").trim()) { + return false; + } + + if (!this.messagesSectionEl?.isConnected || !this.activeTaskStatusEl?.isConnected) { + return false; + } + + if (!this.plugin.isTaskRunning(task) || String(task?.lastError || "").trim()) { + return false; + } + + const panelScrollState = this.capturePanelScrollState(); + this.updateActiveTaskStatus(task); + this.renderTaskMessages(this.messagesSectionEl, task, { + panelScrollState, + panelScrollKey: this.panelScrollKey, + }); + return true; + } + + getDefaultVisibleMessageCount(totalMessages) { + return Math.min(Math.max(Number(totalMessages) || 0, 0), CHAT_MESSAGE_PAGE_SIZE); + } + + syncVisibleMessageCount(taskId, totalMessages, options = {}) { + const normalizedTaskId = String(taskId || ""); + const normalizedTotal = Math.max(Number(totalMessages) || 0, 0); + if (!normalizedTaskId) { + return this.getDefaultVisibleMessageCount(normalizedTotal); + } + + const defaultCount = this.getDefaultVisibleMessageCount(normalizedTotal); + const currentCount = this.visibleMessageCountByTask.get(normalizedTaskId); + if (options.reset === true || typeof currentCount !== "number" || currentCount <= 0) { + this.visibleMessageCountByTask.set(normalizedTaskId, defaultCount); + return defaultCount; + } + + const nextCount = Math.max(defaultCount, Math.min(currentCount, normalizedTotal)); + this.visibleMessageCountByTask.set(normalizedTaskId, nextCount); + return nextCount; + } + + showOlderMessages(taskId, totalMessages) { + const normalizedTaskId = String(taskId || ""); + if (!normalizedTaskId) { + return; + } + + const currentCount = this.syncVisibleMessageCount(normalizedTaskId, totalMessages); + const nextCount = Math.min(Math.max(Number(totalMessages) || 0, 0), currentCount + CHAT_MESSAGE_PAGE_SIZE); + if (nextCount === currentCount) { + return; + } + + this.visibleMessageCountByTask.set(normalizedTaskId, nextCount); + this.render(); + } + + getDefaultVisibleSettingsTaskCount(totalTasks) { + return Math.min(Math.max(Number(totalTasks) || 0, 0), SETTINGS_RECENT_TASKS_PAGE_SIZE); + } + + syncVisibleSettingsTaskCount(listKey, totalTasks, options = {}) { + const normalizedKey = String(listKey || ""); + const normalizedTotal = Math.max(Number(totalTasks) || 0, 0); + if (!normalizedKey) { + return this.getDefaultVisibleSettingsTaskCount(normalizedTotal); + } + + const defaultCount = this.getDefaultVisibleSettingsTaskCount(normalizedTotal); + const currentCount = this.visibleSettingsTaskCountByKey.get(normalizedKey); + if (options.reset === true || typeof currentCount !== "number" || currentCount <= 0) { + this.visibleSettingsTaskCountByKey.set(normalizedKey, defaultCount); + return defaultCount; + } + + const nextCount = Math.max(defaultCount, Math.min(currentCount, normalizedTotal)); + this.visibleSettingsTaskCountByKey.set(normalizedKey, nextCount); + return nextCount; + } + + showMoreSettingsTasks(listKey, totalTasks) { + const normalizedKey = String(listKey || ""); + if (!normalizedKey) { + return; + } + + const currentCount = this.syncVisibleSettingsTaskCount(normalizedKey, totalTasks); + const nextCount = Math.min(Math.max(Number(totalTasks) || 0, 0), currentCount + SETTINGS_RECENT_TASKS_PAGE_SIZE); + if (nextCount === currentCount) { + return; + } + + this.visibleSettingsTaskCountByKey.set(normalizedKey, nextCount); + this.render(); + } + + getSettingsTaskTab() { + if (this.settingsTaskTab === "archived") { + return "archived"; + } + + if (this.settingsTaskTab === "running") { + return "running"; + } + + return this.settingsTaskTab === "recent" ? "recent" : "all"; + } + + setSettingsTaskTab(tab) { + const nextTab = tab === "archived" + ? "archived" + : (tab === "running" + ? "running" + : (tab === "recent" ? "recent" : "all")); + if (this.settingsTaskTab === nextTab) { + return; + } + + this.settingsTaskTab = nextTab; + this.render(); + } + + getSettingsTaskTabLabel(tab = this.getSettingsTaskTab()) { + if (tab === "archived") { + return "Archived threads"; + } + if (tab === "running") { + return "Running threads"; + } + if (tab === "recent") { + return "Recent threads"; + } + return "All threads"; + } + + bindImmediateMouseAction(element, callback) { + if (!element || typeof callback !== "function") { + return; + } + + let ignoreNextClick = false; + element.addEventListener("mousedown", (event) => { + if (event.button !== 0 || element.disabled) { + return; + } + + event.preventDefault(); + event.stopPropagation(); + ignoreNextClick = true; + callback(event); + }); + element.addEventListener("click", (event) => { + if (ignoreNextClick) { + event.preventDefault(); + event.stopPropagation(); + ignoreNextClick = false; + return; + } + + callback(event); + }); + } + + renderPanelTabs(parentEl, activePanelTab) { + const tabs = parentEl.createDiv({ cls: "oa-panel-tabs" }); + [ + { id: PANEL_TAB_OPTIONS.TASK_LIST, label: "Threads" }, + { id: PANEL_TAB_OPTIONS.HEARTBEAT, label: "Heart beat" }, + { id: PANEL_TAB_OPTIONS.WORKSPACE, label: "Workspace" }, + ].forEach((tab) => { + const isActive = activePanelTab === tab.id; + const button = tabs.createEl("button", { + cls: `oa-panel-tab${isActive ? " is-active" : ""}`, + text: tab.label, + attr: { + type: "button", + "aria-pressed": isActive ? "true" : "false", + }, + }); + this.bindImmediateMouseAction(button, () => { + if (tab.id === PANEL_TAB_OPTIONS.TASK_LIST) { + void this.plugin.setTaskDetailOpen(false); + return; + } + void this.plugin.setPanelTab(tab.id); + }); + }); + } + + renderThreadsPanel(parentEl, context = {}) { + const { + activeCanvasPath = "", + activeWorkspace = null, + activeTask = null, + archivedTasks = [], + runningTasks = [], + tasks = [], + } = context; + const threadsBlock = parentEl.createDiv({ cls: "oa-settings-section-block" }); + const threadsHeader = threadsBlock.createDiv({ cls: "oa-settings-section-header oa-thread-filter-header" }); + const settingsTaskTab = this.getSettingsTaskTab(); + threadsHeader.createEl("h3", { + cls: "oa-settings-section-title", + text: activeCanvasPath ? this.getSettingsTaskTabLabel(settingsTaskTab) : "Tasks", + }); + const filterShell = threadsHeader.createDiv({ cls: "oa-thread-filter-shell" }); + const filterSelect = filterShell.createEl("select", { + cls: "oa-thread-filter-select", + attr: { + "aria-label": "Thread filter", + }, + }); + [ + { id: "all", label: "All threads" }, + { id: "recent", label: "Recent threads" }, + { id: "running", label: "Running threads" }, + { id: "archived", label: "Archived threads" }, + ].forEach((tab) => { + filterSelect.createEl("option", { + text: tab.label, + value: tab.id, + }); + }); + filterSelect.value = settingsTaskTab; + filterSelect.addEventListener("mousedown", (event) => { + event.stopPropagation(); + filterSelect.focus(); + }); + filterSelect.addEventListener("change", () => this.setSettingsTaskTab(filterSelect.value)); + + const threadSection = threadsBlock.createDiv({ cls: "oa-task-section oa-settings-task-section" }); + const allTasks = this.plugin.getTasks(); + const displayedTasks = settingsTaskTab === "all" + ? allTasks + : (settingsTaskTab === "archived" + ? archivedTasks + : (settingsTaskTab === "running" ? runningTasks : tasks)); + const taskListKey = [ + "panel-threads", + settingsTaskTab, + settingsTaskTab === "all" ? "all" : (activeCanvasPath || ""), + settingsTaskTab === "all" ? "" : (activeWorkspace?.repoPath || ""), + ].join(":"); + this.renderTaskList(threadSection, displayedTasks, { + emptyText: settingsTaskTab === "archived" + ? (activeCanvasPath + ? "No archived thread yet for this canvas." + : "No archived task yet.") + : (settingsTaskTab === "all" + ? "No OpenAgent threads yet." + : (settingsTaskTab === "running" + ? (activeCanvasPath + ? "No running thread for this canvas." + : "No running task right now.") + : (activeCanvasPath + ? "No thread yet for this canvas. Select a node and start one." + : "Open a Canvas and select a text node to work with its thread."))), + listKey: taskListKey, + activeTask, + }); + } + + renderHeartbeatPanel(parentEl) { + const block = parentEl.createDiv({ cls: "oa-settings-section-block" }); + block.createEl("h3", { + cls: "oa-settings-section-title", + text: "Automations", + }); + const section = block.createDiv({ cls: "oa-settings-section" }); + const automationFiles = this.plugin.getAutomationFiles(); + const list = section.createDiv({ cls: "oa-settings-list" }); + if (automationFiles.length === 0) { + list.createDiv({ + cls: "oa-muted", + text: "No automation files found.", + }); + } else { + automationFiles.forEach((file) => { + const row = list.createDiv({ cls: "oa-settings-row" }); + row.createDiv({ cls: "oa-settings-label", text: basenameVaultPath(file.path).replace(/\.md$/i, "") }); + const value = row.createDiv({ cls: "oa-settings-value oa-settings-value-stack" }); + value.createDiv({ cls: "oa-settings-value-path", text: file.path, attr: { title: file.path } }); + }); + } + + const actions = section.createDiv({ cls: "oa-action-row oa-settings-actions" }); + this.makeButton(actions, "Sync automations", () => { + void this.plugin.syncAutomationsWithFeedback(); + }, false, { runOnMouseDown: true }); + this.makeButton(actions, "Run daily review", () => { + void this.plugin.runAutomationWithFeedback("daily-vault-review"); + }, false, { runOnMouseDown: true }); + } + + renderWorkspacePanel(parentEl, context = {}) { + const { activeWorkspace = null, workspaceSummariesLoading = false } = context; + const workspaceBlock = parentEl.createDiv({ cls: "oa-settings-section-block" }); + workspaceBlock.createEl("h3", { + cls: "oa-settings-section-title", + text: "Workspace", + }); + const workspaceSection = workspaceBlock.createDiv({ + cls: "oa-settings-section oa-workspace-settings-section", + }); + const workspaceSummary = workspaceSection.createDiv({ cls: "oa-settings-list" }); + const repoPath = activeWorkspace?.repoPath || ""; + const projectConfigPath = repoPath ? path.join(repoPath, REPO_WORKSPACE_DIR_NAME, WORKSPACE_CONFIG_FILE_NAME) : ""; + const agentsPath = repoPath ? path.join(repoPath, "AGENTS.md") : ""; + [ + { + label: "Current workspace", + value: repoPath || (workspaceSummariesLoading ? "Loading workspace..." : "No workspace selected"), + title: repoPath, + }, + { + label: "Vault config", + value: activeWorkspace?.configPath || "Missing", + title: activeWorkspace?.configPath || "", + }, + { + label: "Project config", + value: projectConfigPath + ? (fs.existsSync(projectConfigPath) ? `Found: ${projectConfigPath}` : `Missing: ${projectConfigPath}`) + : "No workspace selected", + title: projectConfigPath, + }, + { + label: "AGENTS.md", + value: agentsPath + ? (fs.existsSync(agentsPath) ? agentsPath : `Missing: ${agentsPath}`) + : "No workspace selected", + title: agentsPath, + }, + ].forEach((item) => { + const row = workspaceSummary.createDiv({ cls: "oa-settings-row" }); + row.createDiv({ cls: "oa-settings-label", text: item.label }); + row.createDiv({ + cls: "oa-settings-value oa-settings-value-path", + text: item.value, + attr: item.title ? { title: item.title } : {}, + }); + }); + + const workspaceActions = workspaceSection.createDiv({ cls: "oa-action-row oa-settings-actions" }); + const chooseWorkspaceButton = this.makeButton(workspaceActions, "New workspace", () => this.plugin.showWorkspacePicker()); + chooseWorkspaceButton.setCta(); + if (activeWorkspace?.configPath) { + this.makeButton(workspaceActions, "Open workspace.json", () => { + void this.plugin.app.workspace.openLinkText(activeWorkspace.configPath, "", false); + }); + } + if (agentsPath && fs.existsSync(agentsPath)) { + this.makeButton(workspaceActions, "Open AGENTS.md", () => { + window.open(pathToFileURL(agentsPath).href); + }); + } + + const daemonBlock = parentEl.createDiv({ cls: "oa-settings-section-block" }); + daemonBlock.createEl("h3", { + cls: "oa-settings-section-title", + text: "Codex server", + }); + const daemonSection = daemonBlock.createDiv({ cls: "oa-settings-section oa-settings-summary" }); + const daemonSummary = daemonSection.createDiv({ cls: "oa-settings-list" }); + const daemonRow = daemonSummary.createDiv({ cls: "oa-settings-row" }); + daemonRow.createDiv({ cls: "oa-settings-label", text: "Connection" }); + const daemonValue = daemonRow.createDiv({ cls: "oa-settings-value oa-settings-value-stack" }); + daemonValue.createDiv({ + cls: `oa-status-tag ${this.plugin.getDaemonStatusTagClassName()}`.trim(), + text: this.plugin.getDaemonStatusLabel(), + }); + daemonValue.createDiv({ + cls: "oa-task-meta", + text: this.plugin.getDaemonStatusDetail() || "Checks whether the local Codex server is reachable.", + }); + + const daemonActions = daemonSection.createDiv({ cls: "oa-action-row oa-settings-actions" }); + this.makeButton(daemonActions, "Refresh", () => { + void this.plugin.refreshDaemonStatus({ + silent: false, + forceRefresh: true, + }); + }, false, { + runOnMouseDown: true, + }); + if (this.plugin.getDaemonStatus().state !== DAEMON_CONNECTION_STATES.ONLINE) { + this.makeButton(daemonActions, "Start server", () => { + void this.plugin.startDaemonWithFeedback(); + }, false, { + runOnMouseDown: true, + }); + } + } + + renderTaskList(parentEl, tasks, options = {}) { + if (!parentEl) { + return; + } + + const title = String(options.title || "").trim(); + if (title) { + parentEl.createEl("h3", { text: title }); + } + + const displayedTasks = Array.isArray(tasks) ? tasks : []; + const listEl = parentEl.createDiv({ cls: "oa-task-list" }); + this.bindTaskListHoverGuard(listEl); + if (displayedTasks.length === 0) { + listEl.createDiv({ + cls: "oa-muted", + text: String(options.emptyText || "No threads yet."), + }); + return; + } + + const listKey = String(options.listKey || "").trim(); + const visibleTaskCount = options.enablePagination === false + ? displayedTasks.length + : this.syncVisibleSettingsTaskCount(listKey, displayedTasks.length, { + reset: options.resetPagination === true, + }); + const onTaskClick = typeof options.onTaskClick === "function" + ? options.onTaskClick + : ((task) => this.plugin.activateTaskFromList(task.taskId)); + const visibleTasks = displayedTasks.slice(0, visibleTaskCount); + let previousDayKey = ""; + + visibleTasks.forEach((task) => { + const dayTimestamp = this.plugin.getTaskConversationLastMessageDayTimestamp(task); + const dayKey = Number.isFinite(dayTimestamp) ? String(dayTimestamp) : "unknown"; + if (dayKey !== previousDayKey) { + previousDayKey = dayKey; + listEl.createEl("h4", { + cls: "oa-task-list-date-heading", + text: formatTaskListDayLabel(dayTimestamp), + }); + } + + const isRunningTask = this.plugin.isTaskRunning(task); + const item = listEl.createDiv({ + cls: `oa-task-item oa-thread-list-item${options.activeTask?.taskId === task.taskId ? " is-active" : ""}${isRunningTask ? " is-running" : ""}`, + }); + const itemHeader = item.createDiv({ cls: "oa-task-item-header" }); + const itemMain = itemHeader.createDiv({ cls: "oa-thread-list-main" }); + itemMain.createDiv({ cls: "oa-task-title oa-thread-list-title", text: task.title || "Untitled task" }); + const metaParts = []; + const projectLabel = this.plugin.getTaskProjectLabel(task); + if (projectLabel) { + metaParts.push(projectLabel); + } + const conversationTimestamp = this.plugin.getTaskConversationLastMessageTimestamp(task); + const conversationTimeLabel = formatTaskListTimeLabel(conversationTimestamp); + if (conversationTimeLabel) { + metaParts.push(conversationTimeLabel); + } + if (metaParts.length > 0) { + itemMain.createDiv({ + cls: "oa-thread-list-meta", + text: metaParts.join(" • "), + }); + } + if (this.plugin.hasUnreadTask(task.taskId)) { + itemHeader.createDiv({ + cls: "oa-thread-list-unread-dot", + attr: { + "aria-label": "Unread result", + title: "Unread result", + }, + }); + } else { + const detailButton = itemHeader.createEl("button", { + cls: "oa-thread-list-arrow", + text: "→", + attr: { + type: "button", + "aria-label": "Show detail", + title: "Show detail", + }, + }); + this.bindImmediateMouseAction(detailButton, () => { + void this.plugin.activateTaskFromList(task.taskId, { + showDetail: true, + }); + }); + } + + this.bindImmediateMouseAction(item, () => { + void onTaskClick(task); + }); + }); + + if (options.enablePagination === false || displayedTasks.length <= visibleTaskCount) { + return; + } + + const moreActions = parentEl.createDiv({ cls: "oa-action-row" }); + this.makeButton( + moreActions, + `Show more (${displayedTasks.length - visibleTaskCount} left)`, + () => this.showMoreSettingsTasks(listKey, displayedTasks.length), + false, + { runOnMouseDown: true } + ); + } + + renderGroupContextPreview(parentEl, preview) { + if (!parentEl || !preview) { + return; + } + + const isLoading = preview.status === "loading"; + const textCount = Array.isArray(preview.textBlocks) ? preview.textBlocks.length : 0; + const fileCount = Array.isArray(preview.markdownFiles) ? preview.markdownFiles.length : 0; + const summaryText = isLoading + ? `Loading group context from ${preview.canvasPath}...` + : fileCount > 0 + ? `${fileCount} markdown file${fileCount === 1 ? "" : "s"} in ${preview.canvasPath}` + : `No markdown file context found in ${preview.canvasPath}`; + + const card = parentEl.createDiv({ cls: "oa-task-item oa-group-context-card" }); + const cardHeader = card.createDiv({ cls: "oa-task-item-header" }); + cardHeader.createDiv({ + cls: "oa-task-title", + text: `Group context: ${preview.groupLabel || "Untitled group"}`, + }); + cardHeader.createDiv({ + cls: "oa-status-tag", + text: isLoading ? "Loading" : "Preview", + }); + + card.createDiv({ + cls: "oa-task-meta", + text: summaryText, + attr: { title: preview.canvasPath }, + }); + + const list = card.createDiv({ cls: "oa-group-context-list" }); + (preview.markdownFiles || []).slice(0, 4).forEach((file, index) => { + const item = list.createDiv({ cls: "oa-group-context-item" }); + item.createDiv({ cls: "oa-group-context-label", text: `Markdown ${index + 1}` }); + item.createDiv({ + cls: "oa-group-context-value", + text: String(file?.path || file?.name || "Untitled"), + attr: file?.path ? { title: file.path } : {}, + }); + }); + if (isLoading && fileCount === 0) { + list.createDiv({ + cls: "oa-muted", + text: "Resolving markdown files in this group...", + }); + } + if ((preview.markdownFiles || []).length > 4) { + list.createDiv({ + cls: "oa-muted", + text: `And ${(preview.markdownFiles || []).length - 4} more markdown file${(preview.markdownFiles || []).length - 4 === 1 ? "" : "s"}.`, + }); + } + + if (fileCount === 0 && textCount > 0) { + list.createDiv({ + cls: "oa-muted", + text: "This group has text nodes, but only markdown file nodes become default context.", + }); + } + + if (Array.isArray(preview.warnings) && preview.warnings.length > 0) { + preview.warnings.forEach((warning) => { + card.createDiv({ cls: "oa-banner oa-banner-warning", text: warning }); + }); + } + + card.createDiv({ + cls: "oa-muted", + text: "Select a text node inside this group to start a new thread with this group context.", + }); + } + + render() { + const composerFocusState = this.captureComposerFocusState(); + const panelScrollState = this.capturePanelScrollState(); + const { contentEl: rootEl } = this; + rootEl.empty(); + this.resetActiveTaskDomRefs(); + rootEl.addClass("openagent-view"); + const activeCanvasPath = this.plugin.getActiveCanvasPath(); + const activeWorkspace = this.plugin.getActiveWorkspace(); + const tasks = this.plugin.getVisibleTasks(); + const archivedTasks = this.plugin.getArchivedTasks(); + const runningTasks = this.plugin.getRunningTasks(); + const activeTask = this.plugin.getActiveTask(); + const selectedGroupContextPreview = this.plugin.getSelectedGroupContextPreview(); + const workspaceSummariesLoading = this.plugin.isWorkspaceSummariesLoading(); + const activeTaskId = activeTask?.taskId ? String(activeTask.taskId) : ""; + const didSwitchActiveTask = Boolean(activeTaskId && activeTaskId !== this.lastRenderedActiveTaskId); + const shouldAutoScrollToLatestUserMessage = this.shouldAutoScrollToLatestUserMessage(activeTask, { + didSwitchActiveTask, + }); + this.lastRenderedActiveTaskId = activeTaskId || null; + const panelTab = this.plugin.getPanelTab(); + const taskDetailOpen = panelTab === PANEL_TAB_OPTIONS.TASK_LIST && this.plugin.isTaskDetailOpen(); + const isUtilityTab = panelTab !== PANEL_TAB_OPTIONS.TASK_LIST; + const contentEl = rootEl.createDiv({ cls: `oa-panel-scroll${isUtilityTab ? " is-settings-screen" : ""}` }); + this.panelScrollEl = contentEl; + this.cwdInput = null; + this.composerInput = null; + const activeTaskArchived = activeTask ? this.plugin.isTaskArchived(activeTask) : false; + const panelScrollKey = [ + panelTab, + activeCanvasPath || "", + panelTab === PANEL_TAB_OPTIONS.TASK_LIST ? (activeTask?.taskId || "") : "", + ].join(":"); + this.panelScrollKey = panelScrollKey; + if (panelTab === PANEL_TAB_OPTIONS.TASK_LIST && activeTaskId && didSwitchActiveTask) { + this.queueInitialPanelScroll(panelScrollKey); + } + + const header = contentEl.createDiv({ cls: "oa-header" }); + const headerTop = header.createDiv({ cls: "oa-header-top" }); + const brand = headerTop.createDiv({ cls: "oa-brand" }); + const logo = brand.createDiv({ cls: "oa-logo", attr: { "aria-hidden": "true" } }); + const logoDataUrl = this.plugin.getPluginLogoDataUrl(); + if (logoDataUrl) { + logo.createEl("img", { + cls: "oa-logo-image", + attr: { + src: logoDataUrl, + alt: "", + }, + }); + } else { + logo.createSpan({ cls: "oa-logo-mark", text: "OA" }); + } + const brandText = brand.createDiv({ cls: "oa-brand-text" }); + const brandTitleRow = brandText.createDiv({ cls: "oa-brand-title-row" }); + brandTitleRow.createEl("h2", { text: "OpenAgent" }); + if (this.plugin.getDaemonStatus().state === DAEMON_CONNECTION_STATES.ONLINE) { + brandTitleRow.createSpan({ + cls: "oa-daemon-online-dot", + attr: { + "aria-label": "Codex server online", + title: this.plugin.getDaemonStatusDetail() || "Codex server is online.", + }, + }); + } + brandText.createDiv({ + cls: "oa-brand-subtitle", + text: activeTask?.cwd + ? compactPathLabel(activeTask.cwd) + : (activeWorkspace?.name || (workspaceSummariesLoading ? "Loading workspace..." : "Canvas-linked conversations")), + }); + const actions = headerTop.createDiv({ cls: "oa-action-row oa-header-actions" }); + this.makeButton(actions, "Refresh", () => { + void this.plugin.refreshTasks(); + }, false, { icon: "refresh-cw", iconOnly: true, tooltip: "Refresh threads" }); + if (activeCanvasPath) { + this.makeButton(actions, "Arrange", () => this.plugin.autoArrangeActiveCanvas(), false, { + icon: "layout-dashboard", + iconOnly: true, + tooltip: "Arrange canvas", + }); + } + if (taskDetailOpen && activeTask) { + const headerConversation = header.createDiv({ cls: "oa-conversation-heading" }); + headerConversation.createDiv({ cls: "oa-detail-title", text: activeTask.title || "Untitled task" }); + this.activeTaskStatusEl = headerConversation.createDiv({ + cls: `oa-status-tag${["running", "starting"].includes(String(activeTask.status || "")) ? " is-running" : ""}`, + text: activeTask.status || "idle", + }); + } + if (this.plugin.runtimeIssue) { + contentEl.createDiv({ + cls: "oa-banner oa-banner-error", + text: this.plugin.runtimeIssue, + }); + } + + const panelBody = contentEl.createDiv({ cls: "oa-settings-body" }); + const panelContent = panelBody.createDiv({ cls: "oa-settings-page-content" }); + this.renderPanelTabs(panelContent, panelTab); + if (panelTab === PANEL_TAB_OPTIONS.HEARTBEAT) { + this.renderHeartbeatPanel(panelContent); + this.restoreComposerFocusState(composerFocusState); + this.restorePanelScrollState(panelScrollState, panelScrollKey); + return; + } + if (panelTab === PANEL_TAB_OPTIONS.WORKSPACE) { + this.renderWorkspacePanel(panelContent, { + activeWorkspace, + workspaceSummariesLoading, + }); + this.restoreComposerFocusState(composerFocusState); + this.restorePanelScrollState(panelScrollState, panelScrollKey); + return; + } + if (!taskDetailOpen || !activeTask) { + this.renderThreadsPanel(panelContent, { + activeCanvasPath, + activeWorkspace, + activeTask, + archivedTasks, + runningTasks, + tasks, + }); + this.restoreComposerFocusState(composerFocusState); + this.restorePanelScrollState(panelScrollState, panelScrollKey); + return; + } + + const detail = panelContent.createDiv({ cls: "oa-detail-section" }); + + if (selectedGroupContextPreview) { + this.renderGroupContextPreview(detail, selectedGroupContextPreview); + } + + if (activeTaskArchived) { + detail.createDiv({ + cls: "oa-banner oa-banner-warning", + text: "Archived because its root Canvas node was deleted.", + }); + } + if (Array.isArray(activeTask.selectionContext?.warnings) && activeTask.selectionContext.warnings.length > 0) { + activeTask.selectionContext.warnings.forEach((warning) => { + detail.createDiv({ cls: "oa-banner oa-banner-warning", text: warning }); + }); + } + + if (activeTask.lastError) { + detail.createDiv({ cls: "oa-banner oa-banner-error", text: activeTask.lastError }); + } + + const messagesSection = detail.createDiv({ cls: "oa-messages-section" }); + this.messagesSectionEl = messagesSection; + this.renderedActiveTaskId = String(activeTask.taskId || ""); + this.renderTaskMessages(messagesSection, activeTask, { + didSwitchActiveTask, + panelScrollState, + panelScrollKey, + }); + + const composer = rootEl.createDiv({ cls: "oa-composer-section" }); + const composerInputShell = composer.createDiv({ cls: "oa-composer-input-shell" }); + const taskIsRunning = this.plugin.isTaskRunning(activeTask); + const canStopActiveTurn = Boolean(activeTask?.currentTurnId); + this.composerInput = composerInputShell.createEl("textarea", { cls: "oa-composer-input" }); + this.composerInput.dataset.taskId = activeTask.taskId; + this.composerInput.placeholder = "Add a follow-up message..."; + this.composerInput.rows = 2; + this.composerInput.value = this.plugin.getDraft(activeTask.taskId); + this.composerInput.addEventListener("focus", () => { + this.plugin.setComposerFocused(true); + }); + this.composerInput.addEventListener("input", (event) => { + this.plugin.setDraft(activeTask.taskId, event.target.value); + }); + this.composerInput.addEventListener("blur", () => { + this.plugin.setComposerFocused(false); + void this.plugin.persistPluginState(); + }); + this.composerInput.addEventListener("keydown", (event) => { + if (event.isComposing || event.keyCode === 229) { + return; + } + + if ((event.metaKey || event.ctrlKey) && event.key === "Enter") { + event.preventDefault(); + this.plugin.sendDraftForTask(activeTask.taskId, { draft: this.composerInput.value }); + } + }); + + const stopActionTitle = this.plugin.getStopActiveTaskButtonTitle(); + const composerActionButton = composerInputShell.createEl("button", { + cls: `oa-composer-action-button${canStopActiveTurn ? " is-stop" : " is-send"}`, + text: canStopActiveTurn ? "Stop" : "↑", + attr: { + type: "button", + "aria-label": canStopActiveTurn ? "Stop active task" : "Send message", + "aria-keyshortcuts": canStopActiveTurn ? this.plugin.getStopActiveTaskHotkey() : "", + title: canStopActiveTurn ? stopActionTitle : "Send", + }, + }); + composerActionButton.disabled = !canStopActiveTurn && taskIsRunning; + composerActionButton.addEventListener("click", () => { + if (canStopActiveTurn) { + void this.plugin.stopActiveTask(); + return; + } + this.plugin.sendDraftForTask(activeTask.taskId, { draft: this.composerInput?.value || "" }); + }); + + this.restoreComposerFocusState(composerFocusState); + } + + makeButton(container, label, onClick, disabled = false, options = {}) { + const button = new ButtonComponent(container); + button.setButtonText(options.iconOnly ? "" : label); + button.setDisabled(Boolean(disabled)); + if (options.icon && button.buttonEl) { + setIcon(button.buttonEl, options.icon); + button.buttonEl.addClass("oa-icon-button"); + if (options.iconOnly) { + button.buttonEl.addClass("is-icon-only"); + } + button.buttonEl.setAttribute("aria-label", String(options.tooltip || label || "Action")); + setTooltip(button.buttonEl, String(options.tooltip || label || "Action")); + } + if (options.runOnMouseDown && button.buttonEl) { + let ignoreNextClick = false; + button.buttonEl.addEventListener("mousedown", (event) => { + if (event.button !== 0 || button.buttonEl.disabled) { + return; + } + + event.preventDefault(); + ignoreNextClick = true; + onClick(); + }); + button.buttonEl.addEventListener("click", (event) => { + if (ignoreNextClick) { + event.preventDefault(); + event.stopPropagation(); + ignoreNextClick = false; + return; + } + + onClick(); + }); + return button; + } + + button.onClick(onClick); + return button; + } +} + +class OpenAgentVaultPet { + constructor(plugin) { + this.plugin = plugin; + this.rootEl = null; + this.bubbleEl = null; + this.chatEl = null; + this.chatPathEl = null; + this.chatCwdEl = null; + this.chatTextareaEl = null; + this.chatSendButtonEl = null; + this.dragState = null; + this.chatContext = null; + this.chatIsSending = false; + this.cleanupCallbacks = []; + this.idleTimer = null; + this.messageTimer = null; + this.saveTimer = null; + this.lastActivityAt = 0; + this.suppressNextClick = false; + } + + mount() { + if (this.rootEl) { + return; + } + + const root = document.createElement("div"); + root.className = "oa-vault-pet"; + root.dataset.mood = "idle"; + root.setAttribute("role", "button"); + root.setAttribute("tabindex", "0"); + root.setAttribute("aria-label", "OpenAgent vault pet"); + root.setAttribute("title", "OpenAgent vault pet"); + + const bubble = document.createElement("div"); + bubble.className = "oa-vault-pet-bubble"; + root.appendChild(bubble); + + const chat = document.createElement("div"); + chat.className = "oa-vault-pet-chat"; + chat.setAttribute("aria-label", "Vault pet selection chat"); + root.appendChild(chat); + + const chatHeader = document.createElement("div"); + chatHeader.className = "oa-vault-pet-chat-header"; + chat.appendChild(chatHeader); + + const chatTitle = document.createElement("div"); + chatTitle.className = "oa-vault-pet-chat-title"; + chatTitle.textContent = "Vault pet chat"; + chatHeader.appendChild(chatTitle); + + const chatCloseButton = document.createElement("button"); + chatCloseButton.className = "oa-vault-pet-chat-close"; + chatCloseButton.type = "button"; + chatCloseButton.setAttribute("aria-label", "Close vault pet chat"); + chatCloseButton.textContent = "x"; + chatHeader.appendChild(chatCloseButton); + + const chatPath = document.createElement("div"); + chatPath.className = "oa-vault-pet-chat-path"; + chat.appendChild(chatPath); + + const chatCwd = document.createElement("div"); + chatCwd.className = "oa-vault-pet-chat-cwd"; + chat.appendChild(chatCwd); + + const chatTextarea = document.createElement("textarea"); + chatTextarea.className = "oa-vault-pet-chat-input"; + chatTextarea.rows = 6; + chatTextarea.placeholder = "Chat or search from here..."; + chat.appendChild(chatTextarea); + + const chatActions = document.createElement("div"); + chatActions.className = "oa-vault-pet-chat-actions"; + chat.appendChild(chatActions); + + const chatHint = document.createElement("div"); + chatHint.className = "oa-vault-pet-chat-hint"; + chatHint.textContent = "Ctrl/Cmd Enter sends a new thread"; + chatActions.appendChild(chatHint); + + const chatSendButton = document.createElement("button"); + chatSendButton.className = "oa-vault-pet-chat-send"; + chatSendButton.type = "button"; + chatSendButton.textContent = "Send"; + chatActions.appendChild(chatSendButton); + + const creature = document.createElement("div"); + creature.className = "oa-vault-pet-creature"; + root.appendChild(creature); + + const mascot = document.createElement("img"); + mascot.className = "oa-vault-pet-mascot"; + mascot.alt = ""; + mascot.draggable = false; + mascot.src = this.plugin.getVaultPetMascotDataUrl(); + creature.appendChild(mascot); + + const shadow = document.createElement("div"); + shadow.className = "oa-vault-pet-shadow"; + root.appendChild(shadow); + + document.body.appendChild(root); + this.rootEl = root; + this.bubbleEl = bubble; + this.chatEl = chat; + this.chatPathEl = chatPath; + this.chatCwdEl = chatCwd; + this.chatTextareaEl = chatTextarea; + this.chatSendButtonEl = chatSendButton; + chatCloseButton.addEventListener("click", (event) => { + event.preventDefault(); + event.stopPropagation(); + this.hideChat(); + }); + chatSendButton.addEventListener("click", (event) => { + event.preventDefault(); + event.stopPropagation(); + void this.submitChat(); + }); + chatTextarea.addEventListener("keydown", (event) => { + event.stopPropagation(); + if ((event.metaKey || event.ctrlKey) && event.key === "Enter") { + event.preventDefault(); + void this.submitChat(); + } else if (event.key === "Escape") { + event.preventDefault(); + this.hideChat(); + } + }); + ["pointerdown", "pointermove", "pointerup", "click"].forEach((eventName) => { + chat.addEventListener(eventName, (event) => event.stopPropagation()); + }); + this.applyPosition(this.getStoredPosition()); + this.bindEvents(); + this.say(pickRandomItem(VAULT_PET_IDLE_PHRASES), { + mood: "idle", + timeoutMs: 4_800, + }); + this.idleTimer = window.setInterval(() => this.tick(), 13_000); + } + + unmount() { + window.clearInterval(this.idleTimer); + window.clearTimeout(this.messageTimer); + window.clearTimeout(this.saveTimer); + this.idleTimer = null; + this.messageTimer = null; + this.saveTimer = null; + this.cleanupCallbacks.forEach((cleanup) => cleanup()); + this.cleanupCallbacks = []; + this.rootEl?.remove(); + this.rootEl = null; + this.bubbleEl = null; + this.chatEl = null; + this.chatPathEl = null; + this.chatCwdEl = null; + this.chatTextareaEl = null; + this.chatSendButtonEl = null; + this.chatContext = null; + this.chatIsSending = false; + this.dragState = null; + } + + bindEvents() { + this.addListener(this.rootEl, "pointerdown", (event) => this.handlePointerDown(event)); + this.addListener(this.rootEl, "click", (event) => this.handleClick(event)); + this.addListener(this.rootEl, "keydown", (event) => this.handleKeydown(event)); + this.addListener(window, "resize", () => this.applyPosition(this.getStoredPosition())); + this.addListener(document, "pointermove", (event) => this.handlePointerMove(event)); + this.addListener(document, "pointerup", (event) => this.handlePointerUp(event)); + } + + addListener(target, eventName, handler) { + if (!target) { + return; + } + target.addEventListener(eventName, handler); + this.cleanupCallbacks.push(() => target.removeEventListener(eventName, handler)); + } + + handlePointerDown(event) { + if (event.button !== 0 || !this.rootEl) { + return; + } + const rect = this.rootEl.getBoundingClientRect(); + this.dragState = { + offsetX: event.clientX - rect.left, + offsetY: event.clientY - rect.top, + startX: event.clientX, + startY: event.clientY, + moved: false, + }; + this.rootEl.classList.add("is-dragging"); + this.rootEl.setPointerCapture?.(event.pointerId); + } + + handlePointerMove(event) { + if (!this.dragState || !this.rootEl) { + return; + } + const nextPosition = this.clampPosition({ + x: event.clientX - this.dragState.offsetX, + y: event.clientY - this.dragState.offsetY, + }); + if ( + Math.abs(event.clientX - this.dragState.startX) > 3 + || Math.abs(event.clientY - this.dragState.startY) > 3 + ) { + this.dragState.moved = true; + } + this.applyPosition(nextPosition, { save: false }); + } + + handlePointerUp(event) { + if (!this.dragState || !this.rootEl) { + return; + } + const wasMoved = this.dragState.moved; + this.dragState = null; + this.rootEl.classList.remove("is-dragging"); + this.rootEl.releasePointerCapture?.(event.pointerId); + this.schedulePositionSave(); + if (wasMoved) { + this.suppressNextClick = true; + this.say("I like this spot.", { + mood: "curious", + timeoutMs: 3_200, + }); + } + } + + handleClick(event) { + if (this.suppressNextClick) { + this.suppressNextClick = false; + event.preventDefault(); + return; + } + this.showQuickChat(); + } + + handleKeydown(event) { + if (event.key !== "Enter" && event.key !== " ") { + return; + } + event.preventDefault(); + this.showQuickChat(); + } + + tick() { + if (!this.rootEl || this.dragState) { + return; + } + if (Math.random() > 0.42) { + this.wander(); + } + if (Math.random() > 0.35) { + this.say(pickRandomItem(VAULT_PET_IDLE_PHRASES), { + mood: "idle", + timeoutMs: 4_600, + }); + } + } + + wander() { + const current = this.getCurrentPosition(); + const next = this.clampPosition({ + x: current.x + Math.round((Math.random() - 0.5) * 130), + y: current.y + Math.round((Math.random() - 0.5) * 92), + }); + this.applyPosition(next, { save: false }); + this.rootEl?.classList.add("is-wandering"); + window.setTimeout(() => this.rootEl?.classList.remove("is-wandering"), 850); + this.schedulePositionSave(); + } + + flyToRect(rect, options = {}) { + if (!this.rootEl || !isUsableClientRect(rect)) { + return false; + } + const side = rect.right + VAULT_PET_WIDTH + 24 < window.innerWidth ? "right" : "left"; + const targetX = side === "right" + ? rect.right + 12 + : rect.left - VAULT_PET_WIDTH - 12; + const targetY = rect.top + (rect.height / 2) - (VAULT_PET_HEIGHT * 0.58); + this.applyPosition({ + x: targetX, + y: targetY, + }, { save: false }); + this.rootEl.classList.add("is-summoned"); + window.setTimeout(() => this.rootEl?.classList.remove("is-summoned"), 920); + this.schedulePositionSave(); + this.say(options.message || "Selection spotted.", { + mood: "curious", + timeoutMs: 3_800, + }); + return true; + } + + showSelectionChat(context = {}) { + if (!this.rootEl || !this.chatEl || !this.chatTextareaEl) { + return; + } + this.chatContext = { + ...context, + selectedText: String(context.selectedText || ""), + filePath: String(context.filePath || ""), + absolutePath: String(context.absolutePath || ""), + cwd: String(context.cwd || ""), + }; + this.chatPathEl.textContent = this.chatContext.filePath + ? `File: ${this.chatContext.filePath}` + : "File: current selection"; + this.chatPathEl.title = this.chatContext.absolutePath || this.chatContext.filePath || ""; + this.chatCwdEl.textContent = this.chatContext.cwd + ? `CWD: ${this.chatContext.cwd}` + : "CWD: default workspace"; + this.chatCwdEl.title = this.chatContext.cwd || ""; + this.chatTextareaEl.value = this.chatContext.selectedText; + this.chatTextareaEl.placeholder = "Ask about the selected text..."; + this.chatTextareaEl.disabled = false; + if (this.chatSendButtonEl) { + this.chatSendButtonEl.disabled = false; + this.chatSendButtonEl.textContent = "Send"; + } + const currentPosition = this.getCurrentPosition(); + this.rootEl.classList.toggle("is-chat-right", currentPosition.x < window.innerWidth / 2); + this.rootEl.classList.add("has-chat"); + window.setTimeout(() => { + this.chatTextareaEl?.focus(); + this.chatTextareaEl?.select(); + }, 80); + } + + showQuickChat() { + if (!this.rootEl || !this.chatEl || !this.chatTextareaEl) { + return; + } + const context = this.plugin.getVaultPetQuickChatContext(); + this.chatContext = context; + this.chatPathEl.textContent = context.filePath + ? `File: ${context.filePath}` + : "File: current vault"; + this.chatPathEl.title = context.absolutePath || context.filePath || ""; + this.chatCwdEl.textContent = context.cwd + ? `CWD: ${context.cwd}` + : "CWD: default workspace"; + this.chatCwdEl.title = context.cwd || ""; + this.chatTextareaEl.value = ""; + this.chatTextareaEl.placeholder = "Chat or search from here..."; + this.chatTextareaEl.disabled = false; + if (this.chatSendButtonEl) { + this.chatSendButtonEl.disabled = false; + this.chatSendButtonEl.textContent = "Send"; + } + const currentPosition = this.getCurrentPosition(); + this.rootEl.classList.toggle("is-chat-right", currentPosition.x < window.innerWidth / 2); + this.rootEl.classList.add("has-chat"); + this.say("Ready.", { + mood: "curious", + timeoutMs: 1_400, + }); + window.setTimeout(() => { + this.chatTextareaEl?.focus(); + }, 80); + } + + hideChat() { + this.rootEl?.classList.remove("has-chat"); + } + + async submitChat() { + if (this.chatIsSending || !this.chatContext || !this.chatTextareaEl) { + return; + } + const message = String(this.chatTextareaEl.value || "").trim(); + if (!message) { + this.say("Give me words to swim with.", { + mood: "surprised", + timeoutMs: 3_000, + }); + return; + } + + this.chatIsSending = true; + this.chatTextareaEl.disabled = true; + if (this.chatSendButtonEl) { + this.chatSendButtonEl.disabled = true; + this.chatSendButtonEl.textContent = "Sending..."; + } + try { + await this.plugin.startVaultPetChat(this.chatContext, message); + this.say("Thread launched.", { + mood: "happy", + timeoutMs: 3_600, + }); + this.hideChat(); + } catch (error) { + const messageText = String(error?.message || error || "Unable to start chat."); + new Notice(messageText); + this.say("The current got weird.", { + mood: "surprised", + timeoutMs: 3_600, + }); + this.chatTextareaEl.disabled = false; + if (this.chatSendButtonEl) { + this.chatSendButtonEl.disabled = false; + this.chatSendButtonEl.textContent = "Send"; + } + } finally { + this.chatIsSending = false; + } + } + + pet() { + this.say(pickRandomItem(VAULT_PET_ACTIVITY_PHRASES.pet), { + mood: "happy", + timeoutMs: 4_200, + }); + this.rootEl?.classList.add("is-petted"); + window.setTimeout(() => this.rootEl?.classList.remove("is-petted"), 720); + } + + handleVaultActivity(kind, file) { + if (!this.rootEl) { + return; + } + const filePath = String(file?.path || ""); + if (!filePath || filePath.startsWith(".obsidian/plugins/openagent/")) { + return; + } + const now = Date.now(); + if (now - this.lastActivityAt < VAULT_PET_ACTIVITY_COOLDOWN_MS) { + return; + } + this.lastActivityAt = now; + this.say(pickRandomItem(VAULT_PET_ACTIVITY_PHRASES[kind]) || "The vault moved.", { + mood: kind === "delete" ? "surprised" : "happy", + timeoutMs: 4_400, + }); + this.rootEl.classList.add("is-excited"); + window.setTimeout(() => this.rootEl?.classList.remove("is-excited"), 950); + } + + handleWorkspaceChange() { + if (!this.rootEl || Date.now() - this.lastActivityAt < VAULT_PET_ACTIVITY_COOLDOWN_MS) { + return; + } + this.lastActivityAt = Date.now(); + this.say(pickRandomItem(VAULT_PET_ACTIVITY_PHRASES.workspace), { + mood: "curious", + timeoutMs: 3_800, + }); + } + + say(message, options = {}) { + if (!this.rootEl || !this.bubbleEl) { + return; + } + const mood = String(options.mood || "idle"); + this.rootEl.dataset.mood = mood; + this.bubbleEl.textContent = String(message || "Bloop."); + this.rootEl.classList.add("has-message"); + window.clearTimeout(this.messageTimer); + this.messageTimer = window.setTimeout(() => { + this.rootEl?.classList.remove("has-message"); + if (this.rootEl) { + this.rootEl.dataset.mood = "idle"; + } + }, Number(options.timeoutMs || 4_000)); + } + + resetPosition() { + if (this.plugin.uiState) { + this.plugin.uiState.vaultPetState = {}; + } + this.applyPosition(this.getDefaultPosition()); + this.say("Back to my splash zone.", { + mood: "happy", + timeoutMs: 3_600, + }); + this.schedulePositionSave(); + } + + getStoredPosition() { + const state = this.plugin.uiState?.vaultPetState || {}; + const x = Number(state.x); + const y = Number(state.y); + if (Number.isFinite(x) && Number.isFinite(y)) { + return this.clampPosition({ x, y }); + } + return this.getDefaultPosition(); + } + + getDefaultPosition() { + return this.clampPosition({ + x: window.innerWidth - VAULT_PET_WIDTH - 34, + y: window.innerHeight - VAULT_PET_HEIGHT - 30, + }); + } + + getCurrentPosition() { + if (!this.rootEl) { + return this.getDefaultPosition(); + } + return this.clampPosition({ + x: Number.parseFloat(this.rootEl.style.left) || 0, + y: Number.parseFloat(this.rootEl.style.top) || 0, + }); + } + + clampPosition(position) { + const maxX = Math.max(VAULT_PET_MARGIN, window.innerWidth - VAULT_PET_WIDTH - VAULT_PET_MARGIN); + const maxY = Math.max(VAULT_PET_MARGIN, window.innerHeight - VAULT_PET_HEIGHT - VAULT_PET_MARGIN); + return { + x: Math.min(Math.max(Number(position.x) || VAULT_PET_MARGIN, VAULT_PET_MARGIN), maxX), + y: Math.min(Math.max(Number(position.y) || VAULT_PET_MARGIN, VAULT_PET_MARGIN), maxY), + }; + } + + applyPosition(position, options = {}) { + if (!this.rootEl) { + return; + } + const next = this.clampPosition(position); + this.rootEl.style.left = `${next.x}px`; + this.rootEl.style.top = `${next.y}px`; + if (options.save !== false) { + this.schedulePositionSave(); + } + } + + schedulePositionSave() { + window.clearTimeout(this.saveTimer); + this.saveTimer = window.setTimeout(() => { + if (!this.plugin.uiState || !this.rootEl) { + return; + } + const position = this.getCurrentPosition(); + this.plugin.uiState.vaultPetState = { + x: Math.round(position.x), + y: Math.round(position.y), + }; + void this.plugin.persistPluginState(); + }, 500); + } +} + +class OpenAgentSettingTab extends PluginSettingTab { + constructor(app, plugin) { + super(app, plugin); + this.plugin = plugin; + } + + display() { + const { containerEl } = this; + containerEl.empty(); + + containerEl.createEl("h2", { text: "OpenAgent" }); + + new Setting(containerEl) + .setName("Workspace root") + .setDesc("Vault folder where OpenAgent creates one symlinked project entry per repo.") + .addText((text) => { + text + .setPlaceholder("Workspaces") + .setValue(this.plugin.settings.workspaceRoot) + .onChange(async (value) => { + this.plugin.settings.workspaceRoot = String(value || "").trim() || DEFAULT_SETTINGS.workspaceRoot; + await this.plugin.persistPluginState(); + }); + }); + + new Setting(containerEl) + .setName("Automation folder") + .setDesc("Optional extra vault folder for automations. By default OpenAgent uses each workspace's Automations folder next to Main.canvas.") + .addText((text) => { + text + .setPlaceholder("Leave blank for workspace Automations folders") + .setValue(this.plugin.settings.automationFolder) + .onChange(async (value) => { + this.plugin.settings.automationFolder = String(value || "").trim(); + await this.plugin.persistPluginState(); + await this.plugin.ensureDefaultAutomationFile(); + this.plugin.scheduleAutomationSync(); + }); + }); + + new Setting(containerEl) + .setName("Codex server launch command") + .setDesc("Optional. When the server is offline, OpenAgent will run this shell command and retry automatically.") + .addText((text) => { + text + .setPlaceholder("cd /path/to/openagent && pnpm dev:daemon") + .setValue(this.plugin.settings.daemonLaunchCommand) + .onChange(async (value) => { + this.plugin.settings.daemonLaunchCommand = String(value || ""); + await this.plugin.persistPluginState(); + }); + }); + + new Setting(containerEl) + .setName("Codex server launch directory") + .setDesc("Optional working directory for the launch command above. Leave blank to use auto-discovery.") + .addText((text) => { + text + .setPlaceholder("/absolute/path/to/openagent") + .setValue(this.plugin.settings.daemonLaunchCwd) + .onChange(async (value) => { + this.plugin.settings.daemonLaunchCwd = String(value || ""); + await this.plugin.persistPluginState(); + }); + }); + + const daemonStatusSetting = new Setting(containerEl) + .setName(`Codex server status: ${this.plugin.getDaemonStatusLabel()}`) + .setDesc(this.plugin.getDaemonStatusDetail() || "Checks whether the local Codex server is reachable.") + .addButton((button) => { + button.setButtonText("Refresh"); + button.onClick(async () => { + await this.plugin.refreshDaemonStatus({ + silent: false, + forceRefresh: true, + }); + }); + }); + if (this.plugin.getDaemonStatus().state !== DAEMON_CONNECTION_STATES.ONLINE) { + daemonStatusSetting.addButton((button) => { + button.setButtonText("Start"); + button.onClick(async () => { + await this.plugin.startDaemonWithFeedback(); + }); + }); + } + + new Setting(containerEl) + .setName("Codex sandbox mode") + .setDesc("Choose how much access turns launched from this plugin get. Workspace-only stays inside the selected project folder. Full access removes the filesystem sandbox and should only be used for trusted work.") + .addDropdown((dropdown) => { + dropdown + .addOption(DAEMON_SANDBOX_MODE_OPTIONS.WORKSPACE_WRITE, "Workspace only (safer)") + .addOption(DAEMON_SANDBOX_MODE_OPTIONS.DANGER_FULL_ACCESS, "Full access (advanced)") + .setValue(normalizeDaemonSandboxMode(this.plugin.settings.daemonSandboxMode)) + .onChange(async (value) => { + this.plugin.settings.daemonSandboxMode = normalizeDaemonSandboxMode(value); + await this.plugin.persistPluginState(); + new Notice(`OpenAgent runtime mode: ${this.plugin.describeRuntimeConfig({ sandboxMode: value })}.`); + }); + }); + + containerEl.createDiv({ + cls: "oa-task-meta", + text: getDaemonSandboxModeHelpText(this.plugin.settings.daemonSandboxMode), + }); + + new Setting(containerEl) + .setName("Workspace") + .setDesc("Create or open a workspace folder that points to a real repo on disk.") + .addButton((button) => { + button.setButtonText("New workspace"); + button.onClick(() => { + this.plugin.showWorkspacePicker(); + }); + }); + + new Setting(containerEl) + .setName("Stop active task hotkey") + .setDesc("Optional. Click the box, then press a shortcut like Mod+. or Mod+Shift+S to interrupt the current OpenAgent run. Backspace clears it.") + .addText((text) => { + text + .setPlaceholder("Click and press a shortcut") + .setValue(this.plugin.getStopActiveTaskHotkeyLabel()); + text.inputEl.readOnly = true; + text.inputEl.classList.add(HOTKEY_CAPTURE_INPUT_CLASS); + text.inputEl.addEventListener("focus", () => { + text.inputEl.select(); + }); + text.inputEl.addEventListener("keydown", (event) => { + if (event.key === "Tab" && !event.metaKey && !event.ctrlKey && !event.altKey) { + return; + } + + event.preventDefault(); + event.stopPropagation(); + + if (event.isComposing || event.keyCode === 229) { + return; + } + + if (event.key === "Backspace" || event.key === "Delete") { + this.plugin.settings.stopActiveTaskHotkey = ""; + text.setValue(""); + this.plugin.requestViewRefresh(); + void this.plugin.persistPluginState(); + return; + } + + const nextHotkey = normalizeHotkeyFromEvent(event); + if (!nextHotkey) { + return; + } + + this.plugin.settings.stopActiveTaskHotkey = nextHotkey; + text.setValue(this.plugin.getStopActiveTaskHotkeyLabel()); + this.plugin.requestViewRefresh(); + void this.plugin.persistPluginState(); + }); + }) + .addButton((button) => { + button.setButtonText("Clear"); + button.onClick(async () => { + this.plugin.settings.stopActiveTaskHotkey = ""; + await this.plugin.persistPluginState(); + this.plugin.requestViewRefresh(); + this.display(); + }); + }); + + if (this.plugin.getDaemonStatus().state !== DAEMON_CONNECTION_STATES.ONLINE) { + new Setting(containerEl) + .setName("Start server now") + .setDesc("Useful after changing launch settings or if you want to warm it up before using Canvas.") + .addButton((button) => { + button.setButtonText("Start"); + button.onClick(async () => { + await this.plugin.startDaemonWithFeedback(); + }); + }); + } + + containerEl.createEl("h3", { text: "Vault pet" }); + + new Setting(containerEl) + .setName("Vault pet mascot") + .setDesc("Keep a small animated vault pet floating inside Obsidian.") + .addToggle((toggle) => { + toggle + .setValue(this.plugin.isVaultPetEnabled()) + .onChange(async (value) => { + this.plugin.settings.enableVaultPet = value === true; + this.plugin.configureVaultPet(); + await this.plugin.persistPluginState(); + }); + }) + .addButton((button) => { + button.setButtonText("Pet"); + button.onClick(() => { + this.plugin.petVaultOctopus(); + }); + }) + .addButton((button) => { + button.setButtonText("Reset spot"); + button.onClick(async () => { + this.plugin.resetVaultPetPosition(); + await this.plugin.persistPluginState(); + }); + }); + + containerEl.createEl("h3", { text: "Developer options" }); + + new Setting(containerEl) + .setName("Enable smoke request polling") + .setDesc("Developer only. Poll `.openagent/smoke-request.json` in the vault and execute smoke runs automatically.") + .addToggle((toggle) => { + toggle + .setValue(this.plugin.isDevSmokePollingEnabled()) + .onChange(async (value) => { + this.plugin.settings.enableDevSmokeRequests = value === true; + this.plugin.configureDevSmokePolling(); + await this.plugin.persistPluginState(); + }); + }); + + new Setting(containerEl) + .setName("Enable debug logging") + .setDesc("Developer only. Record OpenAgent debug events and append them to `.openagent/new-thread-debug.jsonl` in the vault.") + .addToggle((toggle) => { + toggle + .setValue(this.plugin.isDebugLoggingEnabled()) + .onChange(async (value) => { + this.plugin.settings.enableDebugLogging = value === true; + if (!this.plugin.isDebugLoggingEnabled()) { + this.plugin.debugEvents = []; + } + await this.plugin.persistPluginState(); + this.plugin.requestViewRefresh(); + }); + }); + } +} + +module.exports = class OpenAgentPlugin extends Plugin { + buildCanvasNodeTextCacheKey(canvasPath, nodeId) { + const normalizedCanvasPath = String(canvasPath || "").trim(); + const normalizedNodeId = String(nodeId || "").trim(); + if (!normalizedCanvasPath || !normalizedNodeId) { + return ""; + } + return `${normalizedCanvasPath}::${normalizedNodeId}`; + } + + setCanvasNodeEditorText(canvasPath, nodeId, text) { + const cacheKey = this.buildCanvasNodeTextCacheKey(canvasPath, nodeId); + if (!cacheKey) { + return; + } + this.liveCanvasNodeTextByKey.set(cacheKey, { + text: String(text || ""), + updatedAt: Date.now(), + }); + } + + getCanvasNodeEditorText(canvasPath, nodeId) { + const cacheKey = this.buildCanvasNodeTextCacheKey(canvasPath, nodeId); + if (!cacheKey) { + return undefined; + } + const entry = this.liveCanvasNodeTextByKey.get(cacheKey); + if (!entry) { + return undefined; + } + if (Date.now() - Number(entry.updatedAt || 0) > CANVAS_EDITOR_TEXT_CACHE_TTL_MS) { + this.liveCanvasNodeTextByKey.delete(cacheKey); + return undefined; + } + return String(entry.text || ""); + } + + registerCanvasEditorTrackingExtension() { + if (!EditorView || !editorInfoField || typeof this.registerEditorExtension !== "function") { + return; + } + + this.registerEditorExtension([ + EditorView.updateListener.of((update) => { + if (!update.docChanged) { + return; + } + + let editorInfo; + try { + editorInfo = update.state.field(editorInfoField); + } catch { + return; + } + + const node = editorInfo?.node; + const canvasPath = String(node?.canvas?.view?.file?.path || "").trim(); + const nodeId = String(node?.getData?.()?.id || node?.id || "").trim(); + if (!canvasPath || !nodeId) { + return; + } + + this.setCanvasNodeEditorText(canvasPath, nodeId, update.state.doc.toString()); + }), + ]); + } + + async onload() { + const savedState = (await this.loadData()) || {}; + const savedVaultId = String(savedState.vaultId || savedState.uiState?.vaultId || "").trim(); + const didCreateVaultId = !savedVaultId; + this.vaultId = savedVaultId || createVaultId(); + this.settings = { + ...DEFAULT_SETTINGS, + ...(savedState.settings || {}), + }; + if (this.settings.automationFolder === "OpenAgent/Automations") { + this.settings.automationFolder = ""; + } + this.settings.daemonSandboxMode = normalizeDaemonSandboxMode(this.settings.daemonSandboxMode); + this.settings.stopActiveTaskHotkey = normalizeStopHotkeySetting(this.settings.stopActiveTaskHotkey); + this.uiState = { + activeTaskId: savedState.uiState?.activeTaskId ?? savedState.activeTaskId ?? null, + panelTab: normalizePanelTab(savedState.uiState?.panelTab ?? savedState.panelTab), + taskDetailOpen: savedState.uiState?.taskDetailOpen === true, + draftsByTaskId: savedState.uiState?.draftsByTaskId ?? savedState.draftsByTaskId ?? {}, + canvasNodeHighlightStateByKey: savedState.uiState?.canvasNodeHighlightStateByKey ?? {}, + unreadTaskStateById: normalizeUnreadTaskState( + savedState.uiState?.unreadTaskStateById + ?? savedState.unreadTaskStateById + ?? {} + ), + vaultPetState: savedState.uiState?.vaultPetState ?? {}, + }; + const savedSyncState = savedState.syncState || {}; + this.daemonLauncher = new OpenAgentDaemonLauncher(this); + this.api = new OpenAgentApiClient({ + daemonLauncher: this.daemonLauncher, + }); + this.resolver = new CanvasSelectionResolver(this.app, this); + this.runtimeIssue = ""; + this.tasksById = {}; + this.viewRefreshTimer = null; + this.canvasSelectionCaptureTimer = null; + this.activeStreamDisposer = null; + this.activeStreamReconnectTimer = null; + this.activeStreamTaskId = ""; + this.activeStreamSessionId = 0; + this.runningTaskStreamDisposers = new Map(); + this.runningTaskStreamReconnectTimers = new Map(); + this.runningTaskStreamSessionIds = {}; + this.lastCanvasSelectionSnapshot = null; + this.devSmokeRunPromise = null; + this.lastProcessedSmokeRequestId = ""; + this.logoDataUrl = ""; + this.logoLoadPromise = null; + this.vaultPetMascotDataUrl = ""; + this.debugEvents = []; + this.devSmokePollIntervalId = null; + this.daemonStatus = createDaemonStatusSnapshot({ + state: DAEMON_CONNECTION_STATES.CHECKING, + }); + this.daemonStatusRefreshPromise = null; + this.settingTab = null; + this.resultNodeSyncStateByTaskId = normalizeResultNodeSyncState( + savedSyncState.resultNodeSyncStateByTaskId + || savedState.uiState?.resultNodeSyncStateByTaskId + || {} + ); + this.resultNodeSyncInFlight = new Set(); + this.canvasRunSourceRefByTaskId = normalizeCanvasRunSourceRefState( + savedSyncState.canvasRunSourceRefByTaskId + || savedState.uiState?.canvasRunSourceRefByTaskId + || {} + ); + this.canvasSnapshotCacheByPath = new Map(); + this.canvasMutationQueueByPath = new Map(); + this.canvasPopupMenuPatchStateByMenu = new Map(); + this.canvasNodeHighlightSyncInFlight = new Set(); + this.canvasNodeHighlightSyncPending = new Set(); + this.canvasAutoRunInFlightByKey = new Set(); + this.taskDetailRefreshInFlight = new Set(); + this.composerFocused = false; + this.taskListHovered = false; + this.pendingViewRefresh = false; + this.workspaceSummaryCacheByConfigPath = new Map(); + this.workspaceSummariesLoadPromise = null; + this.workspaceSummariesLoaded = false; + this.automationSyncTimer = null; + this.automationSyncPromise = null; + this.selectedGroupContextPreviewState = { + key: "", + preview: null, + loading: false, + }; + this.selectedGroupContextPreviewLoadPromise = null; + this.liveCanvasNodeTextByKey = new Map(); + this.vaultPet = null; + + this.registerView(VIEW_TYPE, (leaf) => new OpenAgentView(leaf, this)); + this.registerView(VOICE_GRAPH_VIEW_TYPE, (leaf) => new OpenAgentVoiceGraphView(leaf, this)); + this.registerCanvasEditorTrackingExtension(); + this.settingTab = new OpenAgentSettingTab(this.app, this); + this.addSettingTab(this.settingTab); + this.configureVaultPet(); + this.configureDevSmokePolling(); + this.registerInterval(window.setInterval(() => { + void this.refreshDaemonStatus({ + silent: true, + }); + }, DAEMON_STATUS_POLL_MS)); + this.registerEvent(this.app.vault.on("create", (file) => { + this.handleWorkspaceConfigMutation(file); + this.handleAutomationFileMutation(file); + this.handleSelectedGroupContextDependencyMutation(file); + this.handleCanvasFileMutation(file); + this.handleVaultPetActivity("create", file); + })); + this.registerEvent(this.app.vault.on("modify", (file) => { + this.handleWorkspaceConfigMutation(file); + this.handleAutomationFileMutation(file); + this.handleSelectedGroupContextDependencyMutation(file); + this.handleCanvasFileMutation(file); + this.handleVaultPetActivity("modify", file); + })); + this.registerEvent(this.app.vault.on("delete", (file) => { + this.handleWorkspaceConfigMutation(file); + this.handleAutomationFileMutation(file); + this.handleSelectedGroupContextDependencyMutation(file); + this.handleCanvasFileMutation(file); + this.handleVaultPetActivity("delete", file); + })); + this.registerEvent(this.app.vault.on("rename", (file, oldPath) => { + this.handleWorkspaceConfigMutation(file, oldPath); + this.handleAutomationFileMutation(file, oldPath); + this.handleSelectedGroupContextDependencyMutation(file, oldPath); + this.handleCanvasFileMutation(file); + this.handleVaultPetActivity("rename", file); + })); + this.registerEvent(this.app.workspace.on("active-leaf-change", () => { + this.ensureCanvasPopupMenuPatches(); + this.ensureCanvasSelectionTrackingPatches(); + this.captureCanvasSelectionSnapshot(); + const activeCanvasPath = this.getActiveCanvasPath(); + if (activeCanvasPath) { + this.getCanvasSnapshotSync(activeCanvasPath); + } + this.syncActiveTaskToCanvasContext({ markReadOnSelection: true }); + void this.refreshSelectedGroupContextPreview(); + this.requestViewRefresh(); + this.handleVaultPetWorkspaceChange(); + })); + this.registerEvent(this.app.workspace.on("layout-change", () => { + this.ensureCanvasPopupMenuPatches(); + this.ensureCanvasSelectionTrackingPatches(); + })); + this.registerDomEvent(document, "keydown", (event) => { + void this.handleStopActiveTaskHotkey(event); + }); + const initialCanvasPath = this.getActiveCanvasPath(); + if (initialCanvasPath) { + this.getCanvasSnapshotSync(initialCanvasPath); + } + this.ensureCanvasPopupMenuPatches(); + this.ensureCanvasSelectionTrackingPatches(); + this.captureCanvasSelectionSnapshot(); + + this.addCommand({ + id: "openagent-new-thread-from-selection", + name: "New thread from selection", + callback: () => this.handleNewThreadFromSelectionCommand(), + }); + + this.addCommand({ + id: "openagent-choose-repo-workspace", + name: "Choose workspace", + callback: () => this.showWorkspacePicker(), + }); + + this.addCommand({ + id: "openagent-open-panel", + name: "Open tasks", + callback: () => this.activateView(), + }); + + this.addCommand({ + id: "openagent-open-voice-graph", + name: "Open voice graph", + callback: () => { + void this.activateVoiceGraphView(); + }, + }); + + this.addCommand({ + id: "openagent-open-thread-list", + name: "Open thread list", + callback: () => { + void this.openThreadList(); + }, + }); + + this.addCommand({ + id: "openagent-resume-last-task", + name: "Resume last task", + callback: () => this.resumeLastTask(), + }); + + this.addCommand({ + id: "openagent-stop-active-task", + name: "Stop active task", + callback: () => this.stopActiveTask(), + }); + + this.addCommand({ + id: "openagent-auto-arrange-active-canvas", + name: "Auto arrange active canvas", + callback: () => this.autoArrangeActiveCanvas(), + }); + + this.addCommand({ + id: "openagent-start-daemon", + name: "Start Codex server", + callback: () => { + void this.startDaemonWithFeedback(); + }, + }); + + this.addCommand({ + id: "openagent-sync-automations", + name: "Sync automations", + callback: () => { + void this.syncAutomationsWithFeedback(); + }, + }); + + this.addCommand({ + id: "openagent-run-daily-vault-review", + name: "Run daily vault review", + callback: () => { + void this.runAutomationWithFeedback("daily-vault-review"); + }, + }); + + this.addCommand({ + id: "openagent-create-follow-up-node", + name: "Create follow-up node", + callback: () => { + void this.handleCreateFollowUpNodeCommand(); + }, + }); + + this.addCommand({ + id: "openagent-create-fork-node", + name: "Create fork node", + callback: () => { + void this.handleCreateForkNodeCommand(); + }, + }); + + this.addCommand({ + id: "openagent-toggle-vault-pet", + name: "Toggle vault mascot", + callback: () => { + void this.toggleVaultPet(); + }, + }); + + this.addCommand({ + id: "openagent-pet-vault-octopus", + name: "Pet vault mascot", + callback: () => { + this.petVaultOctopus(); + }, + }); + + this.addCommand({ + id: "openagent-summon-vault-octopus-to-selection", + name: "Summon vault mascot to selection", + hotkeys: [ + { + modifiers: ["Ctrl"], + key: "r", + }, + ], + callback: () => { + this.summonVaultPetToSelection(); + }, + }); + + this.app.workspace.onLayoutReady(() => { + this.ensureCanvasPopupMenuPatches(); + this.ensureCanvasSelectionTrackingPatches(); + this.captureCanvasSelectionSnapshot(); + void this.ensurePanelVisibleOnStartup(); + }); + void this.ensureWorkspaceSummariesLoaded(); + void this.ensureDefaultAutomationFile().then(() => this.syncAutomationsToDaemon()).catch(() => {}); + void this.ensurePluginLogoDataUrlLoaded(); + void this.refreshDaemonStatus(); + void this.refreshTasks().catch(() => {}); + if (didCreateVaultId) { + void this.persistPluginState(); + } + } + + async onunload() { + window.clearTimeout(this.canvasSelectionCaptureTimer); + this.canvasSelectionCaptureTimer = null; + window.clearTimeout(this.automationSyncTimer); + this.automationSyncTimer = null; + this.disposeActiveStream(); + this.disposeAllRunningTaskStreams(); + this.restoreCanvasPopupMenuPatches(); + this.vaultPet?.unmount(); + this.vaultPet = null; + try { + await this.persistPluginState(); + } catch { + // Ignore persistence failures during plugin teardown. + } + this.app.workspace.getLeavesOfType(VIEW_TYPE).forEach((leaf) => leaf.detach()); + this.app.workspace.getLeavesOfType(VOICE_GRAPH_VIEW_TYPE).forEach((leaf) => leaf.detach()); + } + + async persistPluginState() { + await this.saveData({ + vaultId: this.vaultId, + settings: this.settings, + uiState: this.uiState, + syncState: { + resultNodeSyncStateByTaskId: this.resultNodeSyncStateByTaskId, + canvasRunSourceRefByTaskId: this.canvasRunSourceRefByTaskId, + }, + }); + } + + isVaultPetEnabled() { + return this.settings?.enableVaultPet !== false; + } + + configureVaultPet() { + if (!this.isVaultPetEnabled()) { + this.vaultPet?.unmount(); + this.vaultPet = null; + return; + } + if (!this.vaultPet) { + this.vaultPet = new OpenAgentVaultPet(this); + } + this.vaultPet.mount(); + } + + async toggleVaultPet() { + this.settings.enableVaultPet = !this.isVaultPetEnabled(); + this.configureVaultPet(); + await this.persistPluginState(); + new Notice(this.isVaultPetEnabled() ? "Vault pet is awake." : "Vault pet is napping."); + this.settingTab?.display(); + } + + petVaultOctopus() { + if (!this.isVaultPetEnabled()) { + this.settings.enableVaultPet = true; + this.configureVaultPet(); + void this.persistPluginState(); + } + this.vaultPet?.pet(); + } + + summonVaultPetToSelection() { + if (!this.isVaultPetEnabled()) { + this.settings.enableVaultPet = true; + this.configureVaultPet(); + void this.persistPluginState(); + } else { + this.configureVaultPet(); + } + + const chatContext = this.getActiveMarkdownSelectionChatContext(); + const selectionRect = this.getActiveSelectionClientRect(); + if (!selectionRect || !chatContext?.selectedText) { + new Notice("Select some text first, then summon the vault pet."); + this.vaultPet?.say("Point me at some text.", { + mood: "surprised", + timeoutMs: 3_400, + }); + return; + } + + const didFly = this.vaultPet?.flyToRect(selectionRect, { + message: "I found the thought.", + }); + this.vaultPet?.showSelectionChat(chatContext); + if (!didFly) { + new Notice("Vault pet could not find a visible selection."); + } + } + + getActiveMarkdownSelectionChatContext() { + const file = this.app.workspace.activeLeaf?.view?.file || this.app.workspace.getActiveFile(); + if (!(file instanceof TFile)) { + return null; + } + const selectedText = this.getActiveSelectedText(); + if (!selectedText.trim()) { + return null; + } + + const vaultBasePath = this.getVaultBasePath(); + const absolutePath = vaultBasePath ? path.resolve(vaultBasePath, file.path) : ""; + const selectionContext = this.buildVaultPetSelectionContext({ + file, + selectedText, + absolutePath, + }); + const cwd = this.getDefaultCwdForVaultPetSelection(file.path, selectionContext); + return { + filePath: file.path, + fileName: file.basename || file.name || basenameVaultPath(file.path), + absolutePath, + selectedText, + cwd, + selectionContext, + }; + } + + getActiveSelectedText() { + const activeViewContainer = this.app.workspace.activeLeaf?.view?.containerEl; + const nativeSelection = document.getSelection?.(); + if (nativeSelection && !nativeSelection.isCollapsed) { + const selectedText = String(nativeSelection.toString() || ""); + if (selectedText.trim()) { + const anchorNode = nativeSelection.anchorNode; + const focusNode = nativeSelection.focusNode; + const anchorElement = anchorNode?.nodeType === Node.ELEMENT_NODE ? anchorNode : anchorNode?.parentElement; + const focusElement = focusNode?.nodeType === Node.ELEMENT_NODE ? focusNode : focusNode?.parentElement; + if ( + !activeViewContainer + || (anchorElement && activeViewContainer.contains(anchorElement)) + || (focusElement && activeViewContainer.contains(focusElement)) + ) { + return selectedText; + } + } + } + + const editorSelection = this.app.workspace.activeLeaf?.view?.editor?.getSelection?.(); + return String(editorSelection || ""); + } + + buildVaultPetSelectionContext({ file, selectedText, absolutePath }) { + const filePath = String(file?.path || "").trim(); + const selectionText = String(selectedText || "").trim(); + const selectionId = `vault-pet-${shortHash(`${filePath}\n${selectionText}\n${Date.now()}`)}`; + return { + canvasPath: filePath, + canvasName: file?.basename || file?.name || basenameVaultPath(filePath), + nodeIds: [selectionId], + textBlocks: [ + { + id: selectionId, + text: selectionText, + }, + ], + markdownFiles: [ + { + id: `file-${shortHash(filePath)}`, + path: filePath, + absolutePath: String(absolutePath || ""), + name: file?.name || basenameVaultPath(filePath), + content: "", + }, + ], + imageFiles: [], + warnings: [], + title: `${file?.basename || basenameVaultPath(filePath) || "Markdown"} selection`, + }; + } + + getDefaultCwdForVaultPetSelection(filePath, selectionContext) { + const workspace = this.getWorkspaceForVaultPath(filePath); + return workspace?.repoPath + || this.resolveWorkspaceRepoPathForSelection(selectionContext) + || this.resolver.inferWorkingDirectory(selectionContext) + || this.getVaultBasePath(); + } + + getVaultPetQuickChatContext(options = {}) { + const preferCanvas = options.preferCanvas === true; + const preferVaultCanvas = options.preferVaultCanvas === true; + const vaultCanvasFile = preferVaultCanvas ? this.getVaultRootCanvasFile() : null; + const explicitFile = options.file instanceof TFile ? options.file : null; + const activeLeafFile = this.app.workspace.activeLeaf?.view?.file; + const activeFile = activeLeafFile instanceof TFile ? activeLeafFile : null; + const canvasFile = this.resolver.getActiveCanvasView()?.file; + const activeCanvasFile = canvasFile instanceof TFile ? canvasFile : null; + const workspaceActiveFile = this.app.workspace.getActiveFile(); + const file = explicitFile + || vaultCanvasFile + || (preferCanvas ? activeCanvasFile : activeFile) + || workspaceActiveFile + || activeCanvasFile + || activeFile; + const filePath = file instanceof TFile ? String(file.path || "").trim() : ""; + const vaultBasePath = this.getVaultBasePath(); + const absolutePath = filePath && vaultBasePath ? path.resolve(vaultBasePath, filePath) : ""; + const selectionContext = this.buildVaultPetQuickChatSelectionContext({ + file, + filePath, + absolutePath, + }); + const cwd = this.getDefaultCwdForVaultPetSelection(filePath, selectionContext); + return { + mode: "quick", + filePath, + fileName: file instanceof TFile ? (file.basename || file.name || basenameVaultPath(filePath)) : "", + absolutePath, + selectedText: "", + cwd, + targetCanvasPath: vaultCanvasFile?.path || "", + selectionContext, + }; + } + + getVaultRootCanvasFile() { + const rootCanvasFiles = this.app.vault.getFiles() + .filter((file) => file instanceof TFile && file.extension === "canvas") + .filter((file) => { + const filePath = String(file.path || "").trim(); + return filePath && !filePath.includes("/") && !filePath.startsWith("."); + }) + .sort((left, right) => left.path.localeCompare(right.path)); + if (rootCanvasFiles.length === 0) { + return null; + } + + const vaultName = String(this.app.vault.getName?.() || "").trim(); + const vaultBaseName = path.basename(this.getVaultBasePath() || ""); + const preferredNames = [ + vaultName ? `${vaultName} Vault.canvas` : "", + vaultBaseName ? `${vaultBaseName} Vault.canvas` : "", + vaultName ? `${vaultName}.canvas` : "", + vaultBaseName ? `${vaultBaseName}.canvas` : "", + "Vault.canvas", + DEFAULT_WORKSPACE_CANVAS_FILE, + ] + .map((name) => name.toLowerCase()) + .filter(Boolean); + + return rootCanvasFiles.find((file) => preferredNames.includes(basenameVaultPath(file.path).toLowerCase())) + || rootCanvasFiles[0]; + } + + buildVaultPetQuickChatSelectionContext({ file, filePath, absolutePath }) { + const normalizedFilePath = String(filePath || "").trim(); + const baseTitle = file instanceof TFile + ? (file.basename || basenameVaultPath(normalizedFilePath) || "Vault") + : "Vault"; + const nodeId = `vault-pet-quick-${shortHash(`${normalizedFilePath}\n${Date.now()}`)}`; + const text = normalizedFilePath + ? `Quick chat from active file: ${normalizedFilePath}` + : "Quick chat from Obsidian vault."; + return { + canvasPath: normalizedFilePath || "vault-pet/quick-chat", + canvasName: baseTitle, + nodeIds: [nodeId], + textBlocks: [ + { + id: nodeId, + text, + }, + ], + markdownFiles: normalizedFilePath && !/\.canvas$/i.test(normalizedFilePath) + ? [ + { + id: `file-${shortHash(normalizedFilePath)}`, + path: normalizedFilePath, + absolutePath: String(absolutePath || ""), + name: file?.name || basenameVaultPath(normalizedFilePath), + content: "", + }, + ] + : [], + imageFiles: [], + warnings: [], + title: normalizedFilePath ? `${baseTitle} quick chat` : "Vault pet quick chat", + }; + } + + async startVaultPetChat(context = {}, message = "") { + return this.startVaultPetSelectionChat(context, message); + } + + async startVaultPetSelectionChat(context = {}, message = "") { + const rawPrompt = String(message || "").trim(); + if (!rawPrompt) { + throw new Error("Write a chat message first."); + } + const materialized = await this.materializeVaultPetChatOnCanvas(context, { + message, + rawPrompt, + }); + const selectionContext = materialized.selectionContext; + const cwd = materialized.cwd; + + await this.activateView(); + const response = await this.api.createTaskFromCanvasSelection(this.withVaultScope({ + ...selectionContext, + cwd, + forceNewTask: true, + runtimeConfig: this.buildRuntimeConfigPayload(), + })); + const task = this.mergeTask(response.task, { adoptCurrentVault: true }); + this.runtimeIssue = ""; + await this.setActiveTask(task.taskId, { revealInActiveTab: true }); + + if (!task.cwd) { + new Notice("Set a working directory in the OpenAgent panel to continue."); + return task; + } + + new Notice("Starting a new OpenAgent thread from this selection."); + await this.api.runTask(task.taskId, { + rawPrompt, + transcriptMessage: String(message || "").trim() || rawPrompt, + forceContext: false, + runtimeConfig: this.buildRuntimeConfigPayload(), + }); + await this.refreshTask(task.taskId); + void this.revealCanvasNode(selectionContext.canvasPath, selectionContext.nodeIds?.[0] || ""); + return this.tasksById[task.taskId] || task; + } + + async materializeVaultPetChatOnCanvas(context = {}, options = {}) { + const rawPrompt = String(options.rawPrompt || "").trim(); + const message = String(options.message || "").trim(); + const sourceSelection = context.selectionContext || this.buildVaultPetSelectionContext({ + file: this.app.workspace.getActiveFile(), + selectedText: context.selectedText, + absolutePath: context.absolutePath, + }); + const requestedCwd = String(context.cwd || "").trim() + || this.getDefaultCwdForVaultPetSelection(context.filePath, sourceSelection); + const cwd = normalizeRepoPath(requestedCwd || this.getVaultBasePath()); + const targetCanvasPath = String(context.targetCanvasPath || "").trim(); + let workspace = null; + let canvasPath = targetCanvasPath; + if (canvasPath) { + const targetCanvasFile = this.app.vault.getAbstractFileByPath(canvasPath); + if (!(targetCanvasFile instanceof TFile) || targetCanvasFile.extension !== "canvas") { + throw new Error(`Canvas file not found: ${canvasPath}`); + } + } else { + workspace = await this.ensureWorkspaceForRepoPath(cwd, path.basename(cwd || "Vault")); + canvasPath = joinVaultPath(workspace.folderPath, workspace.defaultCanvas || DEFAULT_WORKSPACE_CANVAS_FILE); + } + const sourceFilePath = String(context.filePath || "").trim(); + const absolutePath = String(context.absolutePath || "").trim(); + const title = context.mode === "quick" + ? "Vault pet quick chat" + : "Vault pet selection chat"; + const displayText = String(message || "").trim(); + const node = await this.appendVaultPetPromptNodeToCanvas(canvasPath, { + title, + text: displayText, + }); + const markdownFiles = sourceFilePath && !/\.canvas$/i.test(sourceFilePath) + ? [ + { + id: `file-${shortHash(sourceFilePath)}`, + path: sourceFilePath, + absolutePath, + name: context.fileName || basenameVaultPath(sourceFilePath), + content: "", + }, + ] + : []; + + return { + cwd: workspace?.repoPath || cwd, + selectionContext: this.withVaultScope({ + canvasPath, + canvasName: basenameVaultPath(canvasPath).replace(/\.canvas$/i, ""), + nodeIds: [node.id], + textBlocks: [ + { + id: node.id, + text: rawPrompt || displayText, + }, + ], + markdownFiles, + imageFiles: [], + warnings: [], + title: displayText.slice(0, 80) || title, + }), + }; + } + + async appendVaultPetPromptNodeToCanvas(canvasPath, options = {}) { + const normalizedCanvasPath = String(canvasPath || "").trim(); + if (!normalizedCanvasPath) { + throw new Error("Vault pet chat is missing a target Canvas."); + } + + return this.withSerializedCanvasMutation(normalizedCanvasPath, async () => { + const abstractFile = this.app.vault.getAbstractFileByPath(normalizedCanvasPath); + if (!(abstractFile instanceof TFile) || abstractFile.extension !== "canvas") { + throw new Error(`Canvas file not found: ${normalizedCanvasPath}`); + } + + const raw = await this.app.vault.cachedRead(abstractFile); + let parsed; + try { + parsed = raw.trim() ? JSON.parse(raw) : { nodes: [], edges: [] }; + } catch { + throw new Error(`Unable to parse canvas JSON for ${normalizedCanvasPath}`); + } + + const nodes = Array.isArray(parsed?.nodes) ? [...parsed.nodes] : []; + const edges = Array.isArray(parsed?.edges) ? [...parsed.edges] : []; + const text = String(options.text || "").trim() || "Vault pet chat"; + const width = RESULT_NODE_DEFAULT_WIDTH; + const height = FOLLOW_UP_NODE_DEFAULT_HEIGHT; + const maxBottom = nodes.reduce((maxValue, node) => { + const nodeY = toFiniteNumber(node?.y, 80); + const nodeHeight = toFiniteNumber(node?.height, FOLLOW_UP_NODE_DEFAULT_HEIGHT); + return Math.max(maxValue, nodeY + nodeHeight); + }, CANVAS_LAYOUT_BASE_Y - RESULT_NODE_Y_GAP); + const node = { + id: createCanvasObjectId("oa-pet-chat"), + type: "text", + x: 80, + y: maxBottom + RESULT_NODE_Y_GAP, + width, + height, + text, + }; + nodes.push(node); + + const nextParsed = { + ...parsed, + nodes, + edges, + }; + await this.app.vault.modify(abstractFile, JSON.stringify(nextParsed, null, 2) + "\n"); + this.primeCanvasSnapshot(normalizedCanvasPath, nextParsed); + return node; + }); + } + + getActiveSelectionClientRect() { + return this.getNativeSelectionClientRect() + || this.getCodeMirrorSelectionClientRect() + || this.getFocusedElementClientRect(); + } + + getNativeSelectionClientRect() { + const selection = document.getSelection?.(); + if (!selection || selection.rangeCount === 0 || selection.isCollapsed) { + return null; + } + const activeViewContainer = this.app.workspace.activeLeaf?.view?.containerEl; + const rects = []; + for (let index = 0; index < selection.rangeCount; index += 1) { + const range = selection.getRangeAt(index); + if ( + activeViewContainer + && range.commonAncestorContainer + && !activeViewContainer.contains( + range.commonAncestorContainer.nodeType === Node.ELEMENT_NODE + ? range.commonAncestorContainer + : range.commonAncestorContainer.parentElement + ) + ) { + continue; + } + rects.push(...Array.from(range.getClientRects())); + const boundingRect = range.getBoundingClientRect(); + if (isUsableClientRect(boundingRect)) { + rects.push(boundingRect); + } + } + return combineClientRects(rects); + } + + getCodeMirrorSelectionClientRect() { + const activeViewContainer = this.app.workspace.activeLeaf?.view?.containerEl; + if (!activeViewContainer) { + return null; + } + return combineClientRects( + Array.from(activeViewContainer.querySelectorAll(".cm-selectionBackground")) + .map((element) => element.getBoundingClientRect()) + ); + } + + getFocusedElementClientRect() { + const activeViewContainer = this.app.workspace.activeLeaf?.view?.containerEl; + const activeElement = document.activeElement; + if (!activeViewContainer || !activeElement || !activeViewContainer.contains(activeElement)) { + return null; + } + const rect = activeElement.getBoundingClientRect(); + return isUsableClientRect(rect) ? rect : null; + } + + resetVaultPetPosition() { + if (!this.uiState) { + return; + } + this.uiState.vaultPetState = {}; + if (!this.vaultPet && this.isVaultPetEnabled()) { + this.configureVaultPet(); + } + this.vaultPet?.resetPosition(); + } + + handleVaultPetActivity(kind, file) { + if (!this.isVaultPetEnabled()) { + return; + } + this.vaultPet?.handleVaultActivity(kind, file); + } + + handleVaultPetWorkspaceChange() { + if (!this.isVaultPetEnabled()) { + return; + } + this.vaultPet?.handleWorkspaceChange(); + } + + ensureCanvasPopupMenuPatches() { + this.app.workspace.getLeavesOfType("canvas").forEach((leaf) => { + this.ensureCanvasPopupMenuPatched(leaf?.view); + }); + } + + ensureCanvasSelectionTrackingPatches() { + this.app.workspace.getLeavesOfType("canvas").forEach((leaf) => { + this.ensureCanvasSelectionTrackingPatched(leaf?.view); + }); + } + + ensureCanvasSelectionTrackingPatched(view) { + const canvas = view?.canvas; + if (!canvas || typeof canvas !== "object") { + return false; + } + + if (canvas[CANVAS_SELECTION_TRACKING_PATCH_SYMBOL]) { + return true; + } + + const unpatches = []; + const scheduleCapture = () => { + this.scheduleCanvasSelectionSnapshotCapture(view); + }; + const patchTargetMethod = (target, methodName) => { + const unpatch = patchObjectMethod(target, methodName, (original) => { + return function patchedCanvasSelectionMethod(...args) { + const result = original.apply(this, args); + scheduleCapture(); + return result; + }; + }); + if (!unpatch) { + return false; + } + + unpatches.push(unpatch); + return true; + }; + + let patched = false; + [ + [canvas, "updateSelection"], + [canvas, "selectOnly"], + [canvas, "deselectAll"], + [canvas?.selectionManager, "updateSelection"], + [canvas?.selectionManager, "select"], + [canvas?.selectionManager, "selectOnly"], + [canvas?.selectionManager, "deselectAll"], + [canvas?.selectionManager, "clear"], + [canvas?.selectionManager, "setSelection"], + ].forEach(([target, methodName]) => { + patched = patchTargetMethod(target, methodName) || patched; + }); + + if (!patched) { + return false; + } + + const cleanup = () => { + unpatches.splice(0).reverse().forEach((unpatch) => { + try { + unpatch(); + } catch { + // Ignore cleanup failures during plugin unload or hot reload. + } + }); + delete canvas[CANVAS_SELECTION_TRACKING_PATCH_SYMBOL]; + }; + + canvas[CANVAS_SELECTION_TRACKING_PATCH_SYMBOL] = true; + this.register(cleanup); + return true; + } + + ensureCanvasPopupMenuPatched(view) { + const menu = view?.canvas?.menu; + if (!menu || typeof menu.render !== "function") { + return false; + } + + if (this.canvasPopupMenuPatchStateByMenu.has(menu)) { + this.syncCanvasPopupActionButtons(view); + return true; + } + + const originalRender = menu.render.bind(menu); + menu.render = (...args) => { + const result = originalRender(...args); + queueMicrotask(() => { + this.syncCanvasPopupActionButtons(view); + }); + return result; + }; + + this.canvasPopupMenuPatchStateByMenu.set(menu, originalRender); + this.syncCanvasPopupActionButtons(view); + return true; + } + + restoreCanvasPopupMenuPatches() { + this.canvasPopupMenuPatchStateByMenu.forEach((originalRender, menu) => { + if (menu && typeof originalRender === "function") { + menu.render = originalRender; + } + }); + this.canvasPopupMenuPatchStateByMenu.clear(); + } + + syncCanvasPopupActionButtons(view) { + const menuEl = view?.canvas?.menu?.menuEl; + if (!menuEl) { + return false; + } + + const selectedNodeIds = this.resolver.extractSelectedNodeIds(view); + const shouldShow = selectedNodeIds.length === 1; + const selectedCanvasPath = String(view?.file?.path || this.app.workspace.getActiveFile()?.path || "").trim(); + const selectedNode = shouldShow && selectedCanvasPath + ? this.getCanvasSnapshotSync(selectedCanvasPath)?.nodeById?.get(String(selectedNodeIds[0] || "").trim()) || null + : null; + const shouldShowFork = shouldShow && isOpenAgentAssistantResultNode(selectedNode); + const existingNewThreadButton = menuEl.querySelector(`#${CANVAS_NEW_THREAD_BUTTON_ID}`); + const existingFollowUpButton = menuEl.querySelector(`#${CANVAS_FOLLOW_UP_NODE_BUTTON_ID}`); + const existingForkButton = menuEl.querySelector(`#${CANVAS_FORK_NODE_BUTTON_ID}`); + + if (!shouldShow) { + existingNewThreadButton?.remove(); + existingFollowUpButton?.remove(); + existingForkButton?.remove(); + return false; + } + + if (!shouldShowFork) { + existingForkButton?.remove(); + } + + if (!existingNewThreadButton) { + const newThreadButton = document.createElement("button"); + newThreadButton.id = CANVAS_NEW_THREAD_BUTTON_ID; + newThreadButton.classList.add("clickable-icon"); + newThreadButton.setAttribute("type", "button"); + newThreadButton.setAttribute("aria-label", "New thread"); + setIcon(newThreadButton, "play"); + setTooltip(newThreadButton, "New thread", { placement: "top" }); + newThreadButton.addEventListener("click", (event) => { + event.preventDefault(); + event.stopPropagation(); + void this.handleNewThreadFromSelectionCommand(); + }); + + menuEl.appendChild(newThreadButton); + } + + if (!existingFollowUpButton) { + const followUpButton = document.createElement("button"); + followUpButton.id = CANVAS_FOLLOW_UP_NODE_BUTTON_ID; + followUpButton.classList.add("clickable-icon"); + followUpButton.setAttribute("type", "button"); + followUpButton.setAttribute("aria-label", "Create follow-up node"); + setIcon(followUpButton, "message-square"); + setTooltip(followUpButton, "Create follow-up node", { placement: "top" }); + followUpButton.addEventListener("click", (event) => { + event.preventDefault(); + event.stopPropagation(); + void this.handleCreateFollowUpNodeCommand(); + }); + + menuEl.appendChild(followUpButton); + } + + if (shouldShowFork && !existingForkButton) { + const forkButton = document.createElement("button"); + forkButton.id = CANVAS_FORK_NODE_BUTTON_ID; + forkButton.classList.add("clickable-icon"); + forkButton.setAttribute("type", "button"); + forkButton.setAttribute("aria-label", "Create fork node"); + setIcon(forkButton, "git-fork"); + setTooltip(forkButton, "Create fork node", { placement: "top" }); + forkButton.addEventListener("click", (event) => { + event.preventDefault(); + event.stopPropagation(); + void this.handleCreateForkNodeCommand(); + }); + + if (existingFollowUpButton?.nextSibling) { + menuEl.insertBefore(forkButton, existingFollowUpButton.nextSibling); + } else { + menuEl.appendChild(forkButton); + } + } + + return true; + } + + getSelectedSandboxMode() { + return normalizeDaemonSandboxMode(this.settings?.daemonSandboxMode); + } + + buildRuntimeConfigPayload(overrides = {}) { + return { + approvalPolicy: "never", + sandboxMode: normalizeDaemonSandboxMode(overrides?.sandboxMode || this.getSelectedSandboxMode()), + }; + } + + describeRuntimeConfig(runtimeConfig) { + const sandboxMode = normalizeDaemonSandboxMode(runtimeConfig?.sandboxMode || this.getSelectedSandboxMode()); + const label = getDaemonSandboxModeLabel(sandboxMode); + return sandboxMode === DAEMON_SANDBOX_MODE_OPTIONS.DANGER_FULL_ACCESS + ? `${label} - can access files outside the selected folder` + : `${label} - limited to the selected folder`; + } + + getPluginLogoDataUrl() { + return this.logoDataUrl; + } + + getVaultPetMascotDataUrl() { + if (this.vaultPetMascotDataUrl) { + return this.vaultPetMascotDataUrl; + } + + const mascotPath = this.getVaultPetMascotPath(); + if (!mascotPath) { + return DEFAULT_VAULT_PET_MASCOT_DATA_URL || this.getPluginLogoDataUrl(); + } + + try { + this.vaultPetMascotDataUrl = `data:image/png;base64,${fs.readFileSync(mascotPath).toString("base64")}`; + return this.vaultPetMascotDataUrl; + } catch { + return DEFAULT_VAULT_PET_MASCOT_DATA_URL || this.getPluginLogoDataUrl(); + } + } + + getVaultPetMascotPath() { + const candidatePaths = [ + this.getManifestPluginPath(VAULT_PET_MASCOT_FILE_NAME), + VAULT_PET_MASCOT_PATH, + ].filter(Boolean); + + return candidatePaths.find((candidatePath) => fs.existsSync(candidatePath)) || ""; + } + + async ensurePluginLogoDataUrlLoaded() { + if (this.logoDataUrl) { + return this.logoDataUrl; + } + + if (this.logoLoadPromise) { + return this.logoLoadPromise; + } + + const logoPath = this.getPluginLogoPath(); + if (!logoPath) { + this.logoDataUrl = DEFAULT_PLUGIN_LOGO_DATA_URL; + return this.logoDataUrl; + } + + this.logoLoadPromise = fs.promises.readFile(logoPath) + .then((buffer) => { + this.logoDataUrl = `data:image/png;base64,${buffer.toString("base64")}`; + this.requestViewRefresh(); + return this.logoDataUrl; + }) + .catch(() => DEFAULT_PLUGIN_LOGO_DATA_URL) + .finally(() => { + this.logoLoadPromise = null; + }); + + return this.logoLoadPromise; + } + + getPluginLogoPath() { + const candidatePaths = [ + this.getManifestPluginPath(PLUGIN_LOGO_FILE_NAME), + PLUGIN_LOGO_PATH, + ].filter(Boolean); + + return candidatePaths.find((candidatePath) => fs.existsSync(candidatePath)) || ""; + } + + getManifestPluginPath(fileName) { + const manifestDir = String(this.manifest?.dir || "").trim(); + if (!manifestDir) { + return ""; + } + + const adapter = this.app.vault?.adapter; + if (typeof adapter?.getBasePath !== "function") { + return ""; + } + + return path.join(adapter.getBasePath(), manifestDir, fileName); + } + + refreshSettingTab() { + if (!this.settingTab?.containerEl?.isConnected || typeof this.settingTab.display !== "function") { + return; + } + + this.settingTab.display(); + } + + getStoredDaemonConfigSummary() { + try { + if (!fs.existsSync(DAEMON_CONFIG_PATH)) { + return { + host: "", + port: "", + }; + } + + const parsed = JSON.parse(fs.readFileSync(DAEMON_CONFIG_PATH, "utf8")); + return { + host: String(parsed?.host || "").trim(), + port: String(parsed?.port || "").trim(), + }; + } catch { + return { + host: "", + port: "", + }; + } + } + + updateDaemonStatus(nextStatus, options = {}) { + const previousStatus = this.daemonStatus || createDaemonStatusSnapshot(); + const normalizedStatus = createDaemonStatusSnapshot(nextStatus); + const didChange = [ + "state", + "host", + "port", + "error", + "runtimeAvailable", + "runtimeMessage", + "lastRuntimeError", + ].some((key) => previousStatus[key] !== normalizedStatus[key]); + this.daemonStatus = normalizedStatus; + + if (didChange || options.forceRefresh === true) { + this.requestViewRefresh(); + this.refreshSettingTab(); + } + } + + getDaemonStatus() { + return this.daemonStatus || createDaemonStatusSnapshot(); + } + + getDaemonStatusLabel() { + const state = this.getDaemonStatus().state; + if (state === DAEMON_CONNECTION_STATES.ONLINE) { + return "Online"; + } + if (state === DAEMON_CONNECTION_STATES.OFFLINE) { + return "Offline"; + } + if (state === DAEMON_CONNECTION_STATES.CHECKING) { + return "Checking..."; + } + return "Unknown"; + } + + getDaemonStatusTagClassName() { + return getDaemonStatusTagClassName(this.getDaemonStatus().state); + } + + getDaemonStatusDetail() { + const status = this.getDaemonStatus(); + if (status.state === DAEMON_CONNECTION_STATES.ONLINE) { + if (status.runtimeAvailable === false) { + return status.runtimeMessage || "Connected, but Codex runtime needs attention."; + } + return "Connected to local Codex server."; + } + if (status.state === DAEMON_CONNECTION_STATES.OFFLINE) { + return formatDaemonStatusError(status.error); + } + if (status.state === DAEMON_CONNECTION_STATES.CHECKING) { + return "Checking local Codex server..."; + } + return "Status has not been checked yet."; + } + + async refreshDaemonStatus(options = {}) { + if (this.daemonStatusRefreshPromise) { + if (options.forceRefresh === true) { + this.requestViewRefresh(); + this.refreshSettingTab(); + } + return this.daemonStatusRefreshPromise; + } + + const storedConfig = this.getStoredDaemonConfigSummary(); + if (options.silent !== true) { + this.updateDaemonStatus({ + ...this.getDaemonStatus(), + ...storedConfig, + state: DAEMON_CONNECTION_STATES.CHECKING, + error: "", + }, { + forceRefresh: true, + }); + } + + this.daemonStatusRefreshPromise = this.api.getHealthOnce({ + reloadConfig: true, + }) + .then((health) => { + this.updateDaemonStatus({ + state: DAEMON_CONNECTION_STATES.ONLINE, + checkedAt: Date.now(), + host: String(health?.daemon?.host || storedConfig.host || "").trim(), + port: String(health?.daemon?.port || storedConfig.port || "").trim(), + error: "", + runtimeAvailable: typeof health?.runtime?.ok === "boolean" ? health.runtime.ok : null, + runtimeMessage: String(health?.runtime?.message || "").trim(), + lastRuntimeError: String(health?.lastRuntimeError || "").trim(), + }, { + forceRefresh: options.forceRefresh === true, + }); + return this.getDaemonStatus(); + }) + .catch((error) => { + this.updateDaemonStatus({ + state: DAEMON_CONNECTION_STATES.OFFLINE, + checkedAt: Date.now(), + host: storedConfig.host, + port: storedConfig.port, + error: String(error?.message || error), + runtimeAvailable: null, + runtimeMessage: "", + lastRuntimeError: "", + }, { + forceRefresh: options.forceRefresh === true || options.silent !== true, + }); + return this.getDaemonStatus(); + }) + .finally(() => { + this.daemonStatusRefreshPromise = null; + }); + + return this.daemonStatusRefreshPromise; + } + + async startDaemonWithFeedback() { + try { + await this.daemonLauncher.ensureStarted(); + this.runtimeIssue = ""; + await this.refreshDaemonStatus({ + silent: false, + forceRefresh: true, + }); + new Notice("Codex server is running."); + return true; + } catch (error) { + this.runtimeIssue = String(error?.message || error); + await this.refreshDaemonStatus({ + silent: false, + forceRefresh: true, + }).catch(() => {}); + new Notice(this.runtimeIssue); + this.requestViewRefresh(); + this.refreshSettingTab(); + return false; + } + } + + async pickDirectoryPath(options = {}) { + const defaultPath = normalizeRepoPath(options.defaultPath) || os.homedir(); + const title = String(options.title || "Choose folder"); + const candidates = []; + + try { + const electron = require("electron"); + if (electron?.remote?.dialog) { + candidates.push(electron.remote.dialog); + } + } catch { + // Ignore unavailable electron remote module. + } + + try { + const remote = require("@electron/remote"); + if (remote?.dialog) { + candidates.push(remote.dialog); + } + } catch { + // Ignore unavailable @electron/remote module. + } + + for (const dialog of candidates) { + if (typeof dialog?.showOpenDialogSync === "function") { + const filePaths = dialog.showOpenDialogSync({ + title, + defaultPath, + properties: ["openDirectory", "createDirectory"], + }); + if (Array.isArray(filePaths) && filePaths[0]) { + return normalizeRepoPath(filePaths[0]); + } + } + + if (typeof dialog?.showOpenDialog === "function") { + const result = await dialog.showOpenDialog({ + title, + defaultPath, + properties: ["openDirectory", "createDirectory"], + }); + if (result && !result.canceled && Array.isArray(result.filePaths) && result.filePaths[0]) { + return normalizeRepoPath(result.filePaths[0]); + } + } + } + + throw new Error("Folder picker is not available in this Obsidian runtime yet. Paste the repo path for now."); + } + + showWorkspacePicker() { + new WorkspacePickerModal(this.app, this).open(); + } + + getVaultBasePath() { + return this.resolver?.getVaultBasePath?.() || ""; + } + + getVaultScope() { + const vaultId = String(this.vaultId || "").trim(); + const vaultBasePath = this.getVaultBasePath(); + return { + scopeType: "obsidian-vault", + vaultId, + vaultName: String(this.app.vault?.getName?.() || "").trim(), + vaultPathHash: vaultBasePath ? stableShortHash(path.resolve(vaultBasePath)) : "", + }; + } + + withVaultScope(payload = {}) { + const vaultScope = this.getVaultScope(); + return { + ...payload, + vaultScope, + vaultId: vaultScope.vaultId, + vaultName: vaultScope.vaultName, + vaultPathHash: vaultScope.vaultPathHash, + }; + } + + isTaskForCurrentVault(task) { + const currentVaultId = String(this.vaultId || "").trim(); + if (!currentVaultId) { + return false; + } + + return this.getTaskVaultId(task) === currentVaultId; + } + + getTaskVaultId(task) { + return String(task?.vaultScope?.vaultId || task?.selectionContext?.vaultId || "").trim(); + } + + scopeTaskForCurrentVault(task) { + if (!task) { + return null; + } + + const scopedSelectionContext = task.selectionContext + ? this.withVaultScope(task.selectionContext) + : task.selectionContext; + const scopedTask = this.withVaultScope({ + ...task, + selectionContext: scopedSelectionContext, + }); + return scopedTask; + } + + getVaultPathForAbsolutePath(absolutePath) { + const normalizedAbsolutePath = String(absolutePath || "").trim(); + const vaultBasePath = this.getVaultBasePath(); + if (!normalizedAbsolutePath || !path.isAbsolute(normalizedAbsolutePath) || !vaultBasePath) { + return ""; + } + + const targetPath = normalizeFilesystemPathForCompare(normalizedAbsolutePath); + const normalizedVaultBasePath = normalizeFilesystemPathForCompare(vaultBasePath); + if (targetPath === normalizedVaultBasePath || targetPath.startsWith(`${normalizedVaultBasePath}/`)) { + const directVaultPath = normalizePromptPath(path.relative(vaultBasePath, normalizedAbsolutePath)); + const directFile = this.app.vault.getAbstractFileByPath(directVaultPath); + if (directFile instanceof TFile) { + return directFile.path; + } + } + + const targetRealpath = safeRealpathSync(normalizedAbsolutePath); + const targetComparablePath = targetRealpath + ? normalizeFilesystemPathForCompare(targetRealpath) + : targetPath; + + for (const file of this.app.vault.getFiles()) { + const fileAbsolutePath = this.getVaultAbsolutePath(file.path); + if (!fileAbsolutePath) { + continue; + } + + const fileRealpath = safeRealpathSync(fileAbsolutePath); + const fileComparablePath = fileRealpath + ? normalizeFilesystemPathForCompare(fileRealpath) + : normalizeFilesystemPathForCompare(fileAbsolutePath); + if (fileComparablePath === targetComparablePath) { + return file.path; + } + } + + return ""; + } + + normalizeAssistantLinksForObsidian(text) { + return rewriteMarkdownLinks(text, (rawTarget) => { + const parsed = parseLocalMarkdownLinkTarget(rawTarget); + if (!parsed) { + return ""; + } + + const vaultPath = this.getVaultPathForAbsolutePath(parsed.localPath); + if (vaultPath) { + return `${encodeMarkdownLinkPath(vaultPath)}${parsed.fragment || ""}`; + } + + return pathToFileURL(parsed.localPath).href; + }); + } + + getVaultAbsolutePath(vaultPath) { + const vaultBasePath = this.getVaultBasePath(); + if (!vaultBasePath) { + return ""; + } + return path.join(vaultBasePath, String(vaultPath || "").replace(/^\/+|\/+$/g, "")); + } + + getWorkspaceRootVaultPath() { + return String(this.settings.workspaceRoot || DEFAULT_SETTINGS.workspaceRoot).trim() || DEFAULT_SETTINGS.workspaceRoot; + } + + getRepoWorkspaceDirectory(repoPath) { + return path.join(repoPath, REPO_WORKSPACE_DIR_NAME); + } + + getWorkspaceConfigFiles() { + const workspaceRoot = this.getWorkspaceRootVaultPath(); + return this.app.vault.getFiles().filter((file) => { + return basenameVaultPath(file.path) === WORKSPACE_CONFIG_FILE_NAME + && isVaultPathWithinRoot(file.path, workspaceRoot); + }); + } + + isWorkspaceSummariesLoading() { + return Boolean(this.workspaceSummariesLoadPromise); + } + + getWorkspaceSummarySignature(workspace) { + if (!workspace) { + return ""; + } + + return [ + workspace.configPath, + workspace.folderPath, + workspace.name, + workspace.repoPath, + workspace.defaultCanvas, + ].join("|"); + } + + haveWorkspaceSummariesChanged(nextMap) { + const previousMap = this.workspaceSummaryCacheByConfigPath; + if (previousMap.size !== nextMap.size) { + return true; + } + + for (const [configPath, nextSummary] of nextMap.entries()) { + if (this.getWorkspaceSummarySignature(previousMap.get(configPath)) !== this.getWorkspaceSummarySignature(nextSummary)) { + return true; + } + } + + return false; + } + + async readWorkspaceConfigSummary(file) { + if (!(file instanceof TFile) || basenameVaultPath(file.path) !== WORKSPACE_CONFIG_FILE_NAME) { + return null; + } + + try { + const parsed = JSON.parse(await this.app.vault.cachedRead(file)); + const repoPath = normalizeRepoPath(parsed?.repoPath); + if (!repoPath) { + return null; + } + + return { + configPath: file.path, + folderPath: dirnameVaultPath(file.path), + name: String(parsed?.name || basenameVaultPath(dirnameVaultPath(file.path)) || "Workspace").trim(), + repoPath, + defaultCanvas: String(parsed?.defaultCanvas || DEFAULT_WORKSPACE_CANVAS_FILE).trim() || DEFAULT_WORKSPACE_CANVAS_FILE, + }; + } catch { + return null; + } + } + + async ensureWorkspaceSummariesLoaded(options = {}) { + const force = options.force === true; + if (!force && this.workspaceSummariesLoaded) { + return this.listWorkspaceSummaries(); + } + + if (!force && this.workspaceSummariesLoadPromise) { + return this.workspaceSummariesLoadPromise; + } + + this.workspaceSummariesLoadPromise = (async () => { + const nextMap = new Map(); + const summaries = await Promise.all(this.getWorkspaceConfigFiles().map((file) => this.readWorkspaceConfigSummary(file))); + summaries.filter(Boolean).forEach((summary) => { + nextMap.set(summary.configPath, summary); + }); + + const changed = this.haveWorkspaceSummariesChanged(nextMap); + this.workspaceSummaryCacheByConfigPath = nextMap; + this.workspaceSummariesLoaded = true; + if (changed || force) { + this.requestViewRefresh(); + } + + return this.listWorkspaceSummaries(); + })().finally(() => { + this.workspaceSummariesLoadPromise = null; + }); + + return this.workspaceSummariesLoadPromise; + } + + handleWorkspaceConfigMutation(file, oldPath = "") { + const nextPath = file instanceof TFile ? String(file.path || "").trim() : ""; + const normalizedOldPath = String(oldPath || "").trim(); + const workspaceRoot = this.getWorkspaceRootVaultPath(); + if (basenameVaultPath(nextPath) !== WORKSPACE_CONFIG_FILE_NAME && basenameVaultPath(normalizedOldPath) !== WORKSPACE_CONFIG_FILE_NAME) { + return; + } + if (!isVaultPathWithinRoot(nextPath, workspaceRoot) && !isVaultPathWithinRoot(normalizedOldPath, workspaceRoot)) { + return; + } + + this.workspaceSummariesLoaded = false; + void this.ensureWorkspaceSummariesLoaded({ force: true }); + } + + getAutomationFolderVaultPaths() { + const roots = []; + this.listWorkspaceSummaries().forEach((workspace) => { + if (workspace?.folderPath) { + roots.push(joinVaultPath(workspace.folderPath, WORKSPACE_AUTOMATION_DIR_NAME)); + } + }); + const customFolder = String(this.settings.automationFolder || "").trim(); + if (customFolder) { + roots.push(customFolder); + } + return Array.from(new Set(roots.filter(Boolean))); + } + + isAutomationFilePath(vaultPath) { + const normalizedPath = String(vaultPath || "").trim(); + return normalizedPath.endsWith(".md") + && this.getAutomationFolderVaultPaths().some((automationFolder) => isVaultPathWithinRoot(normalizedPath, automationFolder)); + } + + getAutomationFiles() { + return this.app.vault.getFiles().filter((file) => this.isAutomationFilePath(file.path)); + } + + async ensureDefaultAutomationFile() { + await this.ensureWorkspaceSummariesLoaded(); + const activeWorkspace = this.getActiveWorkspace(); + const workspace = activeWorkspace + || this.listWorkspaceSummaries().find((summary) => summary.name === "openagent") + || this.listWorkspaceSummaries()[0] + || null; + const automationFolder = workspace?.folderPath + ? joinVaultPath(workspace.folderPath, WORKSPACE_AUTOMATION_DIR_NAME) + : (String(this.settings.automationFolder || "").trim() || joinVaultPath(this.getWorkspaceRootVaultPath(), "openagent", WORKSPACE_AUTOMATION_DIR_NAME)); + await this.ensureVaultFolderExists(automationFolder); + const automationPath = joinVaultPath(automationFolder, DEFAULT_DAILY_VAULT_REVIEW_AUTOMATION_FILE); + if (this.app.vault.getAbstractFileByPath(automationPath)) { + return; + } + + await this.writeVaultFile(automationPath, [ + "---", + "openagent: automation", + "id: daily-vault-review", + "enabled: true", + "schedule: \"0 18 * * *\"", + `cwd: "${DEFAULT_DAILY_VAULT_REVIEW_CWD}"`, + "threadStrategy: resume", + "approvalPolicy: never", + "sandboxMode: workspace-write", + "---", + "", + "Use the Codex skill [[Codex Skills/daily-vault-review/SKILL|daily-vault-review]] to create or update today's daily review note.", + "", + "Run the normal daily vault review workflow for /Users/applefather/Documents/Applefather.", + "", + ].join("\n")); + } + + async readAutomationDefinition(file) { + if (!(file instanceof TFile) || !this.isAutomationFilePath(file.path)) { + return null; + } + + const raw = await this.app.vault.cachedRead(file); + const parsed = parseAutomationFrontmatter(raw); + if (String(parsed.data.openagent || "").trim() !== "automation") { + return null; + } + + const cwd = String(parsed.data.cwd || "").trim(); + const prompt = String(parsed.body || "").trim(); + const id = String(parsed.data.id || basenameVaultPath(file.path).replace(/\.md$/i, "")).trim(); + return { + id, + sourcePath: file.path, + title: String(parsed.data.title || basenameVaultPath(file.path).replace(/\.md$/i, "")).trim(), + enabled: parsed.data.enabled !== false, + schedule: String(parsed.data.schedule || "manual").trim() || "manual", + cwd, + prompt, + threadStrategy: String(parsed.data.threadStrategy || "resume").trim() === "new" ? "new" : "resume", + runtimeConfig: { + approvalPolicy: String(parsed.data.approvalPolicy || "never").trim(), + sandboxMode: normalizeDaemonSandboxMode(parsed.data.sandboxMode || this.getSelectedSandboxMode()), + }, + }; + } + + async readAutomationDefinitions() { + const definitions = await Promise.all(this.getAutomationFiles().map((file) => this.readAutomationDefinition(file))); + return definitions.filter(Boolean); + } + + scheduleAutomationSync() { + window.clearTimeout(this.automationSyncTimer); + this.automationSyncTimer = window.setTimeout(() => { + this.automationSyncTimer = null; + void this.syncAutomationsToDaemon().catch(() => {}); + }, 750); + } + + handleAutomationFileMutation(file, oldPath = "") { + const nextPath = file instanceof TFile ? String(file.path || "").trim() : ""; + const previousPath = String(oldPath || "").trim(); + if (!this.isAutomationFilePath(nextPath) && !this.isAutomationFilePath(previousPath)) { + return; + } + + this.scheduleAutomationSync(); + } + + async syncAutomationsToDaemon() { + if (this.automationSyncPromise) { + return this.automationSyncPromise; + } + + this.automationSyncPromise = (async () => { + const automations = await this.readAutomationDefinitions(); + await this.api.syncAutomations({ + source: this.getAutomationSyncSource(), + pruneMissing: true, + automations, + }); + return automations; + })().finally(() => { + this.automationSyncPromise = null; + }); + + return this.automationSyncPromise; + } + + getAutomationSyncSource() { + const basePath = String(this.app.vault.adapter?.basePath || "").trim(); + return basePath ? `vault:${basePath}` : "vault"; + } + + async syncAutomationsWithFeedback() { + try { + await this.ensureDefaultAutomationFile(); + const automations = await this.syncAutomationsToDaemon(); + new Notice(`Synced ${automations.length} OpenAgent automation${automations.length === 1 ? "" : "s"}.`); + } catch (error) { + new Notice(`Automation sync failed: ${String(error?.message || error)}`); + } + } + + async runAutomationWithFeedback(automationId) { + try { + await this.ensureDefaultAutomationFile(); + await this.syncAutomationsToDaemon(); + await this.api.runAutomation(automationId, { force: true }); + new Notice("Daily vault review automation started."); + } catch (error) { + new Notice(`Automation run failed: ${String(error?.message || error)}`); + } + } + + listWorkspaceSummaries() { + return Array.from(this.workspaceSummaryCacheByConfigPath.values()) + .sort((left, right) => left.name.localeCompare(right.name)); + } + + getWorkspaceForVaultPath(vaultPath) { + const normalizedPath = String(vaultPath || "").trim(); + if (!normalizedPath) { + return null; + } + + return this.listWorkspaceSummaries().find((workspace) => { + return normalizedPath === workspace.folderPath || normalizedPath.startsWith(`${workspace.folderPath}/`); + }) || null; + } + + getActiveWorkspace() { + const activeCanvasPath = this.getActiveCanvasPath(); + if (!activeCanvasPath) { + return null; + } + + return this.getWorkspaceForVaultPath(activeCanvasPath); + } + + resolveWorkspaceRepoPathForSelection(selection) { + const workspace = this.getWorkspaceForVaultPath(selection?.canvasPath); + return workspace?.repoPath || ""; + } + + async ensureVaultFolderExists(vaultFolderPath) { + const parts = String(vaultFolderPath || "").split("/").filter(Boolean); + let currentPath = ""; + + for (const part of parts) { + currentPath = joinVaultPath(currentPath, part); + if (!this.app.vault.getAbstractFileByPath(currentPath)) { + await this.app.vault.createFolder(currentPath); + } + } + } + + ensureFilesystemFolderExists(folderPath) { + fs.mkdirSync(folderPath, { recursive: true }); + } + + writeFilesystemFile(filePath, content) { + this.ensureFilesystemFolderExists(path.dirname(filePath)); + fs.writeFileSync(filePath, content, "utf8"); + } + + async writeVaultFile(vaultPath, content) { + const existing = this.app.vault.getAbstractFileByPath(vaultPath); + if (existing instanceof TFile) { + await this.app.vault.modify(existing, content); + return existing; + } + + return this.app.vault.create(vaultPath, content); + } + + buildDefaultWorkspaceCanvas(name, repoPath) { + return JSON.stringify({ + nodes: [ + { + id: "workspace-intro", + type: "text", + x: 80, + y: 80, + width: 440, + height: 280, + text: [ + `Workspace: ${name}`, + `Repo: ${repoPath}`, + "", + "- One text node = one prompt/thread", + "- Run: OpenAgent: New thread from selection", + "- Shortcut helper: OpenAgent: Create follow-up node", + "- Optional context: markdown file nodes, or markdown files in the same group", + "- Follow-up: create a follow-up node from a previous OpenAgent node, then run again", + "- OpenAgent writes the answer back to the canvas", + ].join("\n"), + }, + ], + edges: [], + }, null, 2) + "\n"; + } + + readRepoWorkspaceConfig(repoWorkspaceDir) { + const configPath = path.join(repoWorkspaceDir, WORKSPACE_CONFIG_FILE_NAME); + if (!fs.existsSync(configPath)) { + return null; + } + try { + const parsed = JSON.parse(fs.readFileSync(configPath, "utf8")); + const repoPath = normalizeRepoPath(parsed?.repoPath); + if (!repoPath) { + return null; + } + return { + configPath, + folderPath: repoWorkspaceDir, + name: String(parsed?.name || path.basename(repoPath) || "Workspace").trim() || "Workspace", + repoPath, + defaultCanvas: String(parsed?.defaultCanvas || DEFAULT_WORKSPACE_CANVAS_FILE).trim() || DEFAULT_WORKSPACE_CANVAS_FILE, + }; + } catch { + return null; + } + } + + ensureRepoWorkspaceFiles(repoPath, workspaceNameInput = "") { + const repoWorkspaceDir = this.getRepoWorkspaceDirectory(repoPath); + this.ensureFilesystemFolderExists(repoWorkspaceDir); + + const existing = this.readRepoWorkspaceConfig(repoWorkspaceDir); + const workspaceName = String(existing?.name || workspaceNameInput || path.basename(repoPath)).trim() || path.basename(repoPath); + const defaultCanvas = String(existing?.defaultCanvas || DEFAULT_WORKSPACE_CANVAS_FILE).trim() || DEFAULT_WORKSPACE_CANVAS_FILE; + const workspace = { + configPath: path.join(repoWorkspaceDir, WORKSPACE_CONFIG_FILE_NAME), + folderPath: repoWorkspaceDir, + name: workspaceName, + repoPath, + defaultCanvas, + }; + + if (!existing) { + this.writeFilesystemFile(workspace.configPath, JSON.stringify({ + name: workspace.name, + repoPath: workspace.repoPath, + defaultCanvas: workspace.defaultCanvas, + createdAt: new Date().toISOString(), + }, null, 2) + "\n"); + } + + const canvasPath = path.join(repoWorkspaceDir, workspace.defaultCanvas); + if (!fs.existsSync(canvasPath)) { + this.writeFilesystemFile(canvasPath, this.buildDefaultWorkspaceCanvas(workspace.name, workspace.repoPath)); + } + + return workspace; + } + + resolveVaultWorkspaceLink(folderName, repoWorkspaceDir) { + const workspaceRoot = this.getWorkspaceRootVaultPath(); + const repoWorkspaceRealPath = safeRealpathSync(repoWorkspaceDir) || path.normalize(repoWorkspaceDir); + let candidateFolderName = slugifyWorkspaceName(folderName); + let suffix = 2; + + while (candidateFolderName) { + const folderPath = joinVaultPath(workspaceRoot, candidateFolderName); + const linkPath = this.getVaultAbsolutePath(folderPath); + if (!linkPath) { + throw new Error("OpenAgent could not resolve the current vault path."); + } + + if (!pathExistsOrIsSymlink(linkPath)) { + return { folderPath, linkPath }; + } + + const existingTarget = safeRealpathSync(linkPath); + if (existingTarget && existingTarget === repoWorkspaceRealPath) { + return { folderPath, linkPath }; + } + + candidateFolderName = `${slugifyWorkspaceName(folderName)}-${suffix}`; + suffix += 1; + } + + throw new Error("OpenAgent could not allocate a workspace entry for this repo."); + } + + async ensureWorkspaceSymlink(repoWorkspaceDir, workspaceName) { + const workspaceRoot = this.getWorkspaceRootVaultPath(); + await this.ensureVaultFolderExists(workspaceRoot); + + const { folderPath, linkPath } = this.resolveVaultWorkspaceLink(workspaceName, repoWorkspaceDir); + if (!pathExistsOrIsSymlink(linkPath)) { + fs.symlinkSync(repoWorkspaceDir, linkPath, getDirectorySymlinkType()); + } + + return { folderPath, linkPath }; + } + + async waitForWorkspaceCanvasFile(vaultFolderPath, canvasFileName) { + const canvasPath = joinVaultPath(vaultFolderPath, canvasFileName); + const startedAt = Date.now(); + while (Date.now() - startedAt <= WORKSPACE_INDEX_WAIT_MS) { + const abstractFile = this.app.vault.getAbstractFileByPath(canvasPath); + if (abstractFile instanceof TFile) { + return abstractFile; + } + await sleep(WORKSPACE_INDEX_POLL_MS); + } + return null; + } + + async createWorkspaceFromRepoPath(repoPathInput, workspaceNameInput = "") { + const repoPath = normalizeRepoPath(repoPathInput); + if (!repoPath) { + throw new Error("Enter an absolute repo path."); + } + if (!fs.existsSync(repoPath) || !fs.statSync(repoPath).isDirectory()) { + throw new Error(`Repo path does not exist: ${repoPath}`); + } + + await this.ensureWorkspaceSummariesLoaded(); + const existing = this.listWorkspaceSummaries().find((workspace) => workspace.repoPath === repoPath); + if (existing) { + await this.openWorkspace(existing); + new Notice(`Opened existing workspace: ${existing.name}`); + return existing; + } + + const repoWorkspace = this.ensureRepoWorkspaceFiles(repoPath, workspaceNameInput); + const { folderPath } = await this.ensureWorkspaceSymlink(repoWorkspace.folderPath, repoWorkspace.name); + const workspace = { + configPath: joinVaultPath(folderPath, WORKSPACE_CONFIG_FILE_NAME), + folderPath, + name: repoWorkspace.name, + repoPath: repoWorkspace.repoPath, + defaultCanvas: repoWorkspace.defaultCanvas, + }; + + const canvasFile = await this.waitForWorkspaceCanvasFile(workspace.folderPath, workspace.defaultCanvas); + if (!canvasFile) { + throw new Error(`OpenAgent created ${REPO_WORKSPACE_DIR_NAME}, but Obsidian has not indexed the new workspace link yet. Try reopening the workspace picker.`); + } + + await this.ensureWorkspaceSummariesLoaded({ force: true }); + await this.openWorkspace(workspace); + new Notice(`Created workspace: ${workspace.name}`); + return workspace; + } + + async ensureWorkspaceForRepoPath(repoPathInput, workspaceNameInput = "") { + const repoPath = normalizeRepoPath(repoPathInput); + if (!repoPath) { + throw new Error("OpenAgent could not infer a working directory for this chat."); + } + if (!fs.existsSync(repoPath) || !fs.statSync(repoPath).isDirectory()) { + throw new Error(`Working directory does not exist: ${repoPath}`); + } + + await this.ensureWorkspaceSummariesLoaded(); + const existing = this.listWorkspaceSummaries().find((workspace) => workspace.repoPath === repoPath); + if (existing) { + return existing; + } + + const repoWorkspace = this.ensureRepoWorkspaceFiles(repoPath, workspaceNameInput); + const { folderPath } = await this.ensureWorkspaceSymlink(repoWorkspace.folderPath, repoWorkspace.name); + const workspace = { + configPath: joinVaultPath(folderPath, WORKSPACE_CONFIG_FILE_NAME), + folderPath, + name: repoWorkspace.name, + repoPath: repoWorkspace.repoPath, + defaultCanvas: repoWorkspace.defaultCanvas, + }; + + const canvasFile = await this.waitForWorkspaceCanvasFile(workspace.folderPath, workspace.defaultCanvas); + if (!canvasFile) { + throw new Error(`OpenAgent created a workspace canvas, but Obsidian has not indexed it yet: ${workspace.defaultCanvas}`); + } + + await this.ensureWorkspaceSummariesLoaded({ force: true }); + return workspace; + } + + async openWorkspace(workspace) { + const canvasPath = joinVaultPath(workspace.folderPath, workspace.defaultCanvas || DEFAULT_WORKSPACE_CANVAS_FILE); + const canvasFile = this.app.vault.getAbstractFileByPath(canvasPath); + if (!(canvasFile instanceof TFile)) { + throw new Error(`Default canvas not found: ${canvasPath}`); + } + + const leaf = this.app.workspace.getMostRecentLeaf() || this.app.workspace.getLeaf(true); + await leaf.openFile(canvasFile); + await this.activateView(); + await this.refreshTasks(); + } + + getDevSmokeAbsolutePath(relativePath) { + const vaultBasePath = this.getVaultBasePath(); + if (!vaultBasePath) { + return ""; + } + + return path.join(vaultBasePath, relativePath); + } + + isDevSmokePollingEnabled() { + return this.settings?.enableDevSmokeRequests === true; + } + + isDebugLoggingEnabled() { + return this.settings?.enableDebugLogging === true; + } + + configureDevSmokePolling() { + if (this.devSmokePollIntervalId != null) { + window.clearInterval(this.devSmokePollIntervalId); + this.devSmokePollIntervalId = null; + } + + if (!this.isDevSmokePollingEnabled()) { + void this.appendDebugEvent("dev_smoke_polling_configured", { + enabled: false, + requestPath: this.getDevSmokeAbsolutePath(DEV_SMOKE_REQUEST_RELATIVE_PATH), + }); + return; + } + + this.devSmokePollIntervalId = window.setInterval(() => { + this.maybeRunDevSmokeRequest(); + }, DEV_SMOKE_POLL_MS); + this.registerInterval(this.devSmokePollIntervalId); + void this.appendDebugEvent("dev_smoke_polling_configured", { + enabled: true, + pollMs: DEV_SMOKE_POLL_MS, + requestPath: this.getDevSmokeAbsolutePath(DEV_SMOKE_REQUEST_RELATIVE_PATH), + }); + this.maybeRunDevSmokeRequest(); + } + + maybeRunDevSmokeRequest() { + if (!this.isDevSmokePollingEnabled()) { + return; + } + + if (this.devSmokeRunPromise) { + return; + } + + this.devSmokeRunPromise = this.runDevSmokeRequest() + .catch(() => {}) + .finally(() => { + this.devSmokeRunPromise = null; + }); + } + + async runDevSmokeRequest() { + const requestPath = this.getDevSmokeAbsolutePath(DEV_SMOKE_REQUEST_RELATIVE_PATH); + if (!requestPath || !fs.existsSync(requestPath)) { + return; + } + + let request; + try { + request = JSON.parse(fs.readFileSync(requestPath, "utf8")); + } catch (error) { + fs.unlinkSync(requestPath); + await this.writeDevSmokeResult({ + status: "error", + requestId: "", + message: `Invalid smoke request: ${error.message}`, + finishedAt: new Date().toISOString(), + }); + return; + } + + const requestId = String(request?.id || "").trim() || `anonymous-${Date.now()}`; + if (requestId === this.lastProcessedSmokeRequestId) { + return; + } + this.lastProcessedSmokeRequestId = requestId; + void this.appendDebugEvent("dev_smoke_request_detected", { + requestId, + canvasPath: String(request?.canvasPath || ""), + mode: String(request?.mode || ""), + runTask: request?.runTask !== false, + forceNewTask: request?.forceNewTask === true, + }); + + try { + let selection = await this.resolveDevSmokeSelection(request); + const requestedCwd = this.normalizeWorkingDirectoryInput(request?.cwd); + const smokeMode = String(request?.mode || "").trim(); + let task = null; + let rawPrompt = ""; + let runMode = smokeMode || "default"; + + if (request?.runTask !== false && smokeMode === "new-thread" && request?.forceNewTask === true) { + selection = await this.resolver.addImplicitCanvasFileContext(selection); + const followUpResult = await this.runSelectionAsFollowUp(selection, { + confirmFork: async () => request?.forkBehavior === "fork" ? "fork" : "continue", + }); + if (followUpResult) { + if (followUpResult.cancelled) { + runMode = "cancelled"; + } else { + task = followUpResult.task; + rawPrompt = followUpResult.message; + runMode = followUpResult.mode || "follow-up"; + } + } + } + + if (!task) { + if (request?.forceNewTask === true) { + selection = await this.resolver.addImplicitCanvasFileContext(selection); + } + + const response = await this.api.createTaskFromCanvasSelection(this.withVaultScope({ + ...selection, + cwd: requestedCwd || this.resolver.inferWorkingDirectory(selection), + forceNewTask: request?.forceNewTask === true, + runtimeConfig: this.buildRuntimeConfigPayload(), + })); + task = this.mergeTask(response.task, { adoptCurrentVault: true }); + + if (request?.runTask !== false) { + if (smokeMode === "new-thread") { + rawPrompt = this.getNewThreadPromptFromSelection(selection, { + cwd: requestedCwd || this.resolver.inferWorkingDirectory(selection), + }); + const runResponse = await this.api.runTask(task.taskId, { + rawPrompt, + transcriptMessage: rawPrompt, + forceContext: false, + runtimeConfig: this.buildRuntimeConfigPayload(), + }); + this.mergeTask(runResponse.task, { adoptCurrentVault: true }); + } else { + const runResponse = await this.api.runTask(task.taskId, { + forceContext: true, + runtimeConfig: this.buildRuntimeConfigPayload(), + }); + this.mergeTask(runResponse.task, { adoptCurrentVault: true }); + } + } + } + + await this.refreshTask(task.taskId); + + await this.writeDevSmokeResult({ + status: "ok", + requestId, + taskId: task.taskId, + cwd: task.cwd || requestedCwd || "", + canvasPath: selection.canvasPath, + nodeIds: selection.nodeIds, + selectionSummary: this.summarizeSelection(selection), + selectionDebug: this.describeSelectionDebug(selection), + mode: runMode, + rawPrompt, + runTriggered: request?.runTask !== false, + finishedAt: new Date().toISOString(), + }); + } catch (error) { + await this.writeDevSmokeResult({ + status: "error", + requestId, + message: String(error?.message || error), + finishedAt: new Date().toISOString(), + }); + } finally { + if (fs.existsSync(requestPath)) { + fs.unlinkSync(requestPath); + } + } + } + + async resolveDevSmokeSelection(request) { + const canvasPath = String(request?.canvasPath || "").trim(); + if (!canvasPath) { + throw new Error("Smoke request is missing canvasPath."); + } + + const abstractFile = await this.waitForSmokeCanvasFile(canvasPath); + if (!(abstractFile instanceof TFile) || abstractFile.extension !== "canvas") { + throw new Error(`Smoke canvas not found: ${canvasPath}`); + } + + const explicitNodeIds = Array.isArray(request?.nodeIds) + ? request.nodeIds.map((nodeId) => String(nodeId)).filter(Boolean) + : []; + const nodeIds = explicitNodeIds.length > 0 + ? explicitNodeIds + : await this.resolveAllSupportedCanvasNodeIds(abstractFile); + + if (nodeIds.length === 0) { + throw new Error(`Smoke canvas has no supported nodes: ${canvasPath}`); + } + + return this.resolver.resolveCanvasSelection(abstractFile, nodeIds); + } + + async waitForSmokeCanvasFile(canvasPath, options = {}) { + const normalizedPath = String(canvasPath || "").trim(); + const timeoutMs = Math.max(0, toFiniteNumber(options.timeoutMs, 8_000)); + const pollMs = Math.max(25, toFiniteNumber(options.pollMs, 100)); + const deadline = Date.now() + timeoutMs; + + while (true) { + const abstractFile = this.app.vault.getAbstractFileByPath(normalizedPath); + if (!normalizedPath || abstractFile instanceof TFile) { + return abstractFile || null; + } + + if (Date.now() >= deadline) { + return abstractFile || null; + } + + await sleep(pollMs); + } + } + + async resolveAllSupportedCanvasNodeIds(file) { + const raw = await this.app.vault.cachedRead(file); + let parsed; + try { + parsed = JSON.parse(raw); + } catch { + throw new Error(`Unable to parse canvas JSON for ${file.path}`); + } + + return Array.isArray(parsed?.nodes) + ? parsed.nodes + .filter((node) => node && (node.type === "text" || node.type === "file")) + .map((node) => String(node.id || "")) + .filter(Boolean) + : []; + } + + async writeDevSmokeResult(result) { + const resultPath = this.getDevSmokeAbsolutePath(DEV_SMOKE_RESULT_RELATIVE_PATH); + if (!resultPath) { + return; + } + + fs.mkdirSync(path.dirname(resultPath), { recursive: true }); + fs.writeFileSync(resultPath, `${JSON.stringify(result, null, 2)}\n`, "utf8"); + } + + getDebugLogAbsolutePath() { + return this.getDevSmokeAbsolutePath(DEBUG_LOG_RELATIVE_PATH); + } + + getRecentDebugEvents() { + return this.debugEvents.slice(-8).reverse(); + } + + async appendDebugEvent(type, payload = {}) { + if (!this.isDebugLoggingEnabled()) { + return null; + } + + const event = { + type: String(type || "event"), + createdAt: new Date().toISOString(), + payload, + summary: this.formatDebugSummary(payload), + }; + + this.debugEvents.push(event); + if (this.debugEvents.length > MAX_DEBUG_EVENTS) { + this.debugEvents = this.debugEvents.slice(-MAX_DEBUG_EVENTS); + } + + const debugPath = this.getDebugLogAbsolutePath(); + if (debugPath) { + fs.mkdirSync(path.dirname(debugPath), { recursive: true }); + fs.appendFileSync(debugPath, `${JSON.stringify(event)}\n`, "utf8"); + } + + this.requestViewRefresh(); + return event; + } + + formatDebugSummary(payload = {}) { + return Object.entries(payload) + .filter(([, value]) => value != null && value !== "") + .map(([key, value]) => `${key}=${this.truncateInline(typeof value === "string" ? value : JSON.stringify(value), 180)}`) + .join(" | "); + } + + getTasks() { + return Object.values(this.tasksById).filter((task) => this.isTaskForCurrentVault(task)).sort((left, right) => { + return (right.updatedAt || "").localeCompare(left.updatedAt || ""); + }); + } + + getTaskConversationLastMessageTimestamp(task) { + const messages = Array.isArray(task?.messages) ? task.messages : []; + const lastMessage = messages.length > 0 ? messages[messages.length - 1] : null; + const lastMessageTimestamp = parseIsoTimestamp(lastMessage?.updatedAt || lastMessage?.createdAt || ""); + if (lastMessageTimestamp != null) { + return lastMessageTimestamp; + } + + if (String(task?.lastMessageRole || "") === "assistant") { + const latestAssistantTimestamp = parseIsoTimestamp( + task?.latestAssistantMessage?.updatedAt || task?.latestAssistantMessage?.createdAt || "" + ); + if (latestAssistantTimestamp != null) { + return latestAssistantTimestamp; + } + } + + return parseIsoTimestamp(task?.updatedAt || task?.createdAt || "") || 0; + } + + getTaskConversationLastMessageDayTimestamp(task) { + return getStartOfLocalDayTimestamp(this.getTaskConversationLastMessageTimestamp(task)); + } + + getTaskProjectLabel(task) { + const canvasWorkspace = this.getWorkspaceForVaultPath(task?.selectionContext?.canvasPath); + if (canvasWorkspace?.name) { + return canvasWorkspace.name; + } + + const cwd = normalizeRepoPath(task?.cwd); + if (!cwd) { + return ""; + } + + const cwdWorkspace = this.listWorkspaceSummaries().find((workspace) => { + return normalizeRepoPath(workspace?.repoPath) === cwd; + }); + return cwdWorkspace?.name || compactPathLabel(cwd); + } + + getTaskRootNodeRef(task) { + const canvasPath = this.getTaskCanvasPath(task); + const bindingRootNodeIds = Array.isArray(task?.canvasBinding?.rootNodeIds) + ? task.canvasBinding.rootNodeIds.map((nodeId) => String(nodeId)).filter(Boolean) + : []; + const selectionNodeIds = Array.isArray(task?.selectionContext?.nodeIds) + ? task.selectionContext.nodeIds.map((nodeId) => String(nodeId)).filter(Boolean) + : []; + const nodeIds = bindingRootNodeIds.length > 0 ? bindingRootNodeIds : selectionNodeIds; + if (!canvasPath || nodeIds.length === 0) { + return null; + } + + const rootNodeId = this.getConversationSelectionNodeIds(canvasPath, [nodeIds[0]])[0] || nodeIds[0]; + if (!rootNodeId) { + return null; + } + + return { + canvasPath, + nodeId: rootNodeId, + }; + } + + getTaskCanvasRunSourceRef(task) { + const bindingCanvasPath = String(task?.canvasBinding?.canvasPath || "").trim(); + const bindingNodeId = String(task?.canvasBinding?.activeSourceNodeId || "").trim(); + if (bindingCanvasPath && bindingNodeId) { + return { + canvasPath: bindingCanvasPath, + nodeId: bindingNodeId, + }; + } + + const taskId = String(task?.taskId || "").trim(); + const override = taskId ? this.canvasRunSourceRefByTaskId[taskId] : null; + const overrideCanvasPath = String(override?.canvasPath || "").trim(); + const overrideNodeId = String(override?.nodeId || "").trim(); + if (overrideCanvasPath && overrideNodeId) { + return { + canvasPath: overrideCanvasPath, + nodeId: overrideNodeId, + }; + } + + const canvasPath = String(task?.selectionContext?.canvasPath || "").trim(); + const nodeIds = Array.isArray(task?.selectionContext?.nodeIds) + ? task.selectionContext.nodeIds.map((nodeId) => String(nodeId)).filter(Boolean) + : []; + if (!canvasPath || nodeIds.length !== 1) { + return null; + } + + return { + canvasPath, + nodeId: nodeIds[0], + }; + } + + cacheTaskCanvasRunSourceRef(taskId, ref) { + const normalizedTaskId = String(taskId || "").trim(); + const normalizedCanvasPath = String(ref?.canvasPath || "").trim(); + const normalizedNodeId = String(ref?.nodeId || "").trim(); + if (!normalizedTaskId || !normalizedCanvasPath || !normalizedNodeId) { + return null; + } + + const previousRef = this.canvasRunSourceRefByTaskId[normalizedTaskId] || null; + if ( + previousRef + && String(previousRef.canvasPath || "") === normalizedCanvasPath + && String(previousRef.nodeId || "") === normalizedNodeId + ) { + return { + taskId: normalizedTaskId, + canvasPath: normalizedCanvasPath, + nodeId: normalizedNodeId, + }; + } + + this.canvasRunSourceRefByTaskId[normalizedTaskId] = { + canvasPath: normalizedCanvasPath, + nodeId: normalizedNodeId, + }; + + const affectedCanvasPaths = new Set([ + String(previousRef?.canvasPath || "").trim(), + normalizedCanvasPath, + ].filter(Boolean)); + affectedCanvasPaths.forEach((canvasPath) => { + this.scheduleCanvasNodeHighlightSync(canvasPath); + }); + void this.persistPluginState(); + return { + taskId: normalizedTaskId, + canvasPath: normalizedCanvasPath, + nodeId: normalizedNodeId, + }; + } + + async setTaskCanvasRunSourceRef(taskId, ref, options = {}) { + const cachedRef = this.cacheTaskCanvasRunSourceRef(taskId, ref); + if (!cachedRef) { + return this.tasksById[String(taskId || "").trim()] || null; + } + + if (options.persistDaemon === false) { + return this.tasksById[cachedRef.taskId] || null; + } + + try { + const response = await this.api.updateTaskCanvasBinding(cachedRef.taskId, { + canvasPath: cachedRef.canvasPath, + activeSourceNodeId: cachedRef.nodeId, + }); + return this.mergeTask(response.task); + } catch (error) { + if (options.throwOnError) { + throw error; + } + return this.tasksById[cachedRef.taskId] || null; + } + } + + findTaskByRootCanvasNode(canvasPath, nodeId) { + const normalizedCanvasPath = String(canvasPath || "").trim(); + const normalizedNodeId = String(nodeId || "").trim(); + if (!normalizedCanvasPath || !normalizedNodeId) { + return null; + } + + return this.getTasks().find((task) => { + const rootRef = this.getTaskRootNodeRef(task); + return rootRef?.canvasPath === normalizedCanvasPath && rootRef?.nodeId === normalizedNodeId; + }) || null; + } + + findTaskByCanvasRunSourceNode(canvasPath, nodeId) { + const normalizedCanvasPath = String(canvasPath || "").trim(); + const normalizedNodeId = String(nodeId || "").trim(); + if (!normalizedCanvasPath || !normalizedNodeId) { + return null; + } + + return this.getTasks().find((task) => { + const sourceRef = this.getTaskCanvasRunSourceRef(task); + return sourceRef?.canvasPath === normalizedCanvasPath && sourceRef?.nodeId === normalizedNodeId; + }) || null; + } + + findTaskByResultNode(canvasPath, resultNodeId, sourceNodeId) { + const normalizedCanvasPath = String(canvasPath || "").trim(); + const normalizedResultNodeId = String(resultNodeId || "").trim(); + const normalizedSourceNodeId = String(sourceNodeId || "").trim(); + if (!normalizedCanvasPath || !normalizedResultNodeId || !normalizedSourceNodeId) { + return null; + } + + return this.getTasks().find((task) => { + if (this.getTaskCanvasPath(task) !== normalizedCanvasPath) { + return false; + } + + const boundResultNodeId = String( + task?.canvasBinding?.resultNodesBySourceNodeId?.[normalizedSourceNodeId]?.resultNodeId || "" + ).trim(); + if (boundResultNodeId) { + return boundResultNodeId === normalizedResultNodeId; + } + + return buildTaskResultNodeId(task.taskId, normalizedSourceNodeId) === normalizedResultNodeId; + }) || null; + } + + findTaskByOpenAgentResultNode(canvasPath, resultNodeId) { + const normalizedCanvasPath = String(canvasPath || "").trim(); + const normalizedResultNodeId = String(resultNodeId || "").trim(); + if (!normalizedCanvasPath || !normalizedResultNodeId) { + return null; + } + + const resultNode = this.readCanvasNodeByIdSync(normalizedCanvasPath, normalizedResultNodeId); + if (!resultNode || !isOpenAgentAssistantResultNode(resultNode)) { + return null; + } + + const metadataTaskId = getOpenAgentResultTaskId(resultNode); + const metadataTask = metadataTaskId ? this.tasksById[metadataTaskId] : null; + if (metadataTask && this.getTaskCanvasPath(metadataTask) === normalizedCanvasPath) { + return metadataTask; + } + + const sourceNodeId = getOpenAgentResultSourceNodeId(resultNode) + || this.getConversationSelectionNodeIds(normalizedCanvasPath, [normalizedResultNodeId])[0] + || ""; + if (!sourceNodeId) { + return null; + } + + return this.findTaskByResultNode(normalizedCanvasPath, normalizedResultNodeId, sourceNodeId) + || this.findTaskByCanvasRunSourceNode(normalizedCanvasPath, sourceNodeId) + || this.findTaskByRootCanvasNode(normalizedCanvasPath, sourceNodeId) + || null; + } + + findConversationTaskForUpstreamNode(canvasPath, upstreamNodeId) { + const normalizedCanvasPath = String(canvasPath || "").trim(); + const normalizedUpstreamNodeId = String(upstreamNodeId || "").trim(); + if (!normalizedCanvasPath || !normalizedUpstreamNodeId) { + return null; + } + + const resultTask = this.findTaskByOpenAgentResultNode(normalizedCanvasPath, normalizedUpstreamNodeId); + if (resultTask) { + return resultTask; + } + + if (normalizedUpstreamNodeId.startsWith("oa-result-")) { + const sourceNodeId = this.getConversationSelectionNodeIds(normalizedCanvasPath, [normalizedUpstreamNodeId])[0] || ""; + if (!sourceNodeId) { + return null; + } + + return this.findTaskByResultNode(normalizedCanvasPath, normalizedUpstreamNodeId, sourceNodeId) + || this.findTaskByCanvasRunSourceNode(normalizedCanvasPath, sourceNodeId) + || this.findTaskByRootCanvasNode(normalizedCanvasPath, sourceNodeId) + || null; + } + + return this.findTaskByCanvasRunSourceNode(normalizedCanvasPath, normalizedUpstreamNodeId) + || this.findTaskByRootCanvasNode(normalizedCanvasPath, normalizedUpstreamNodeId) + || null; + } + + async resolveCanvasFollowUpTarget(selection) { + const canvasPath = String(selection?.canvasPath || "").trim(); + const nodeIds = Array.isArray(selection?.nodeIds) + ? selection.nodeIds.map((nodeId) => String(nodeId)).filter(Boolean) + : []; + const textBlocks = Array.isArray(selection?.textBlocks) ? selection.textBlocks : []; + if (!canvasPath || nodeIds.length !== 1 || textBlocks.length !== 1) { + return null; + } + + const followUpNodeId = nodeIds[0]; + const followUpMessage = String(textBlocks[0]?.text || "").trim(); + if (!followUpNodeId || !followUpMessage) { + return null; + } + + const abstractFile = this.app.vault.getAbstractFileByPath(canvasPath); + if (!(abstractFile instanceof TFile) || abstractFile.extension !== "canvas") { + return null; + } + + let parsed; + try { + parsed = JSON.parse(await this.app.vault.cachedRead(abstractFile)); + } catch { + return null; + } + + const edges = Array.isArray(parsed?.edges) ? parsed.edges : []; + const nodes = Array.isArray(parsed?.nodes) ? parsed.nodes : []; + const followUpNode = nodes.find((node) => String(node?.id || "").trim() === followUpNodeId) || null; + const followUpMetadata = getOpenAgentCanvasMetadata(followUpNode); + for (const edge of edges) { + const toNodeId = String(edge?.toNode || "").trim(); + if (toNodeId !== followUpNodeId) { + continue; + } + + const upstreamNodeId = String(edge?.fromNode || "").trim(); + const upstreamNode = nodes.find((node) => String(node?.id || "").trim() === upstreamNodeId) || null; + let task = this.findConversationTaskForUpstreamNode(canvasPath, upstreamNodeId); + if (!task) { + const metadataTaskId = getOpenAgentResultTaskId(upstreamNode); + if (metadataTaskId) { + try { + await this.refreshTask(metadataTaskId, { force: true, adoptCurrentVault: true }); + task = this.findConversationTaskForUpstreamNode(canvasPath, upstreamNodeId); + } catch { + task = null; + } + } + } + if (!task) { + continue; + } + + const upstreamResultSourceNodeId = getOpenAgentResultSourceNodeId(upstreamNode); + const canForkFromUpstream = isOpenAgentAssistantResultNode(upstreamNode); + const branchSourceNodeId = String( + followUpMetadata?.branchSourceNodeId + || followUpMetadata?.sourceNodeId + || upstreamResultSourceNodeId + || upstreamNodeId + ).trim(); + return { + task, + message: followUpMessage, + sourceNodeId: followUpNodeId, + upstreamNodeId, + branchSourceNodeId, + branchMessageId: String( + followUpMetadata?.branchMessageId + || getOpenAgentCanvasMetadata(upstreamNode)?.messageId + || task?.canvasBinding?.resultNodesBySourceNodeId?.[branchSourceNodeId]?.messageId + || "" + ).trim(), + canForkFromUpstream, + forceFork: followUpMetadata?.kind === "fork-request", + }; + } + + return null; + } + + countTaskUserTurnsAfterMessage(task, messageId) { + const normalizedMessageId = String(messageId || "").trim(); + if (!normalizedMessageId) { + return 0; + } + + const messages = Array.isArray(task?.messages) ? task.messages : []; + const index = messages.findIndex((message) => { + return String(message?.id || message?.streamKey || "").trim() === normalizedMessageId; + }); + if (index < 0) { + return 0; + } + + return messages.slice(index + 1).filter((message) => { + return String(message?.role || "") === "user" && String(message?.text || "").trim(); + }).length; + } + + buildCanvasForkTarget(selection, followUpTarget) { + const task = followUpTarget?.task; + const canvasPath = String(selection?.canvasPath || "").trim(); + const sourceNodeId = String(followUpTarget?.sourceNodeId || "").trim(); + const branchSourceNodeId = String(followUpTarget?.branchSourceNodeId || "").trim(); + const activeSourceNodeId = String(task?.canvasBinding?.activeSourceNodeId || "").trim(); + const forceFork = followUpTarget?.forceFork === true; + const canForkFromUpstream = followUpTarget?.canForkFromUpstream === true; + if (!task?.threadId || !canvasPath || !sourceNodeId || !branchSourceNodeId) { + return null; + } + + if (!forceFork && !canForkFromUpstream) { + return null; + } + + if (!forceFork && (!activeSourceNodeId || activeSourceNodeId === branchSourceNodeId)) { + return null; + } + + const branchMessageId = String(followUpTarget?.branchMessageId || "").trim(); + return { + task, + sourceNodeId, + branchSourceNodeId, + branchMessageId, + forceFork, + canForkFromUpstream, + upstreamNodeId: String(followUpTarget?.upstreamNodeId || "").trim(), + rollbackTurns: this.countTaskUserTurnsAfterMessage(task, branchMessageId), + }; + } + + async confirmCanvasForkFromHere(forkTarget) { + return new Promise((resolve) => { + new CanvasForkConfirmModal(this.app, { + originLabel: forkTarget?.branchSourceNodeId || "", + rollbackTurns: forkTarget?.rollbackTurns || 0, + resolve, + }).open(); + }); + } + + async runSelectionAsFollowUp(selection, options = {}) { + const followUpTarget = await this.resolveCanvasFollowUpTarget(selection); + if (!followUpTarget) { + return null; + } + + const forkTarget = this.buildCanvasForkTarget(selection, followUpTarget); + if (forkTarget) { + const decideFork = typeof options.confirmFork === "function" + ? await options.confirmFork(forkTarget) + : await this.confirmCanvasForkFromHere(forkTarget); + if (decideFork === "cancel" || decideFork === false || decideFork == null) { + return { + cancelled: true, + task: forkTarget.task, + message: followUpTarget.message, + }; + } + if (decideFork === "fork" || decideFork === true) { + return this.runSelectionAsFork(selection, followUpTarget, forkTarget); + } + } + + const task = followUpTarget.task; + await this.setTaskCanvasRunSourceRef(task.taskId, { + canvasPath: selection.canvasPath, + nodeId: followUpTarget.sourceNodeId, + }, { + throwOnError: true, + }); + + const markdownFiles = Array.isArray(selection?.markdownFiles) ? selection.markdownFiles : []; + const imageFiles = Array.isArray(selection?.imageFiles) ? selection.imageFiles : []; + const runtimeConfig = this.buildRuntimeConfigPayload(); + const rawPrompt = markdownFiles.length > 0 + ? buildCanvasSelectionPrompt(selection, followUpTarget.message, { cwd: task.cwd }) + : followUpTarget.message; + const response = (markdownFiles.length > 0 || imageFiles.length > 0) + ? await this.api.runTask(task.taskId, { + rawPrompt, + transcriptMessage: followUpTarget.message, + forceContext: false, + selectionContext: this.withVaultScope(selection), + runtimeConfig, + }) + : await this.api.sendMessage( + task.taskId, + followUpTarget.message, + runtimeConfig + ); + + return { + task: this.mergeTask(response.task, { adoptCurrentVault: true }), + message: followUpTarget.message, + }; + } + + async runSelectionAsFork(selection, followUpTarget, forkTarget) { + const task = followUpTarget.task; + const markdownFiles = Array.isArray(selection?.markdownFiles) ? selection.markdownFiles : []; + const imageFiles = Array.isArray(selection?.imageFiles) ? selection.imageFiles : []; + const runtimeConfig = this.buildRuntimeConfigPayload(); + const rawPrompt = (markdownFiles.length > 0 || imageFiles.length > 0) + ? buildCanvasSelectionPrompt(selection, followUpTarget.message, { cwd: task.cwd }) + : followUpTarget.message; + const response = await this.api.forkTask(task.taskId, this.withVaultScope({ + selectionContext: this.withVaultScope(selection), + cwd: task.cwd || this.resolver.inferWorkingDirectory(selection), + rawPrompt, + transcriptMessage: followUpTarget.message, + forceContext: false, + branchSourceNodeId: forkTarget.branchSourceNodeId, + branchMessageId: forkTarget.branchMessageId, + rollbackTurns: forkTarget.rollbackTurns, + runtimeConfig, + })); + + return { + task: this.mergeTask(response.task, { adoptCurrentVault: true }), + message: followUpTarget.message, + mode: "fork", + }; + } + + async handleCreateFollowUpNodeCommand() { + try { + const result = await this.createFollowUpNodeFromSelection(); + await this.revealCanvasNodeForEditing(result.canvasPath, result.followUpNodeId); + this.runtimeIssue = ""; + this.rememberSelectionSnapshot({ + canvasPath: result.canvasPath, + nodeIds: [result.followUpNodeId], + }); + await this.appendDebugEvent("canvas_follow_up_node_created", { + canvasPath: result.canvasPath, + selectedNodeId: result.selectedNodeId, + anchorNodeId: result.anchorNodeId, + followUpNodeId: result.followUpNodeId, + }); + } catch (error) { + this.runtimeIssue = String(error?.message || error); + await this.appendDebugEvent("canvas_follow_up_node_error", { + message: this.runtimeIssue, + }); + new Notice(this.runtimeIssue); + this.requestViewRefresh({ force: true }); + } + } + + async handleCreateForkNodeCommand() { + try { + const result = await this.createFollowUpNodeFromSelection({ fork: true }); + await this.revealCanvasNodeForEditing(result.canvasPath, result.followUpNodeId); + this.runtimeIssue = ""; + this.rememberSelectionSnapshot({ + canvasPath: result.canvasPath, + nodeIds: [result.followUpNodeId], + }); + await this.appendDebugEvent("canvas_fork_node_created", { + canvasPath: result.canvasPath, + selectedNodeId: result.selectedNodeId, + anchorNodeId: result.anchorNodeId, + followUpNodeId: result.followUpNodeId, + }); + } catch (error) { + this.runtimeIssue = String(error?.message || error); + await this.appendDebugEvent("canvas_fork_node_error", { + message: this.runtimeIssue, + }); + new Notice(this.runtimeIssue); + this.requestViewRefresh({ force: true }); + } + } + + async createFollowUpNodeFromSelection(options = {}) { + const view = this.resolver.getActiveCanvasView(); + if (!view) { + throw new Error("Open a Canvas view first."); + } + + const file = view.file || this.app.workspace.getActiveFile(); + if (!(file instanceof TFile) || file.extension !== "canvas") { + throw new Error("The active view is not backed by a .canvas file."); + } + + const selectedNodeIds = this.resolver.extractSelectedNodeIds(view).map((nodeId) => String(nodeId)).filter(Boolean); + if (selectedNodeIds.length !== 1) { + throw new Error("Select exactly one OpenAgent source or result node first."); + } + + await this.resolver.flushPendingCanvasEdits(view); + const selectedNodeId = selectedNodeIds[0]; + const result = await this.withSerializedCanvasMutation(file.path, async () => { + const raw = await this.app.vault.cachedRead(file); + let parsed; + try { + parsed = JSON.parse(raw); + } catch { + throw new Error(`Unable to parse canvas JSON for ${file.path}`); + } + + const nodes = Array.isArray(parsed?.nodes) ? [...parsed.nodes] : []; + const edges = Array.isArray(parsed?.edges) ? [...parsed.edges] : []; + const nodeById = new Map( + nodes + .map((node) => [String(node?.id || "").trim(), node]) + .filter(([nodeId]) => nodeId) + ); + const selectedNode = nodeById.get(selectedNodeId) || null; + if (!selectedNode) { + throw new Error("The selected Canvas node could not be found."); + } + if (options.fork === true && !isOpenAgentAssistantResultNode(selectedNode)) { + throw new Error("Select an OpenAgent result node before creating a fork."); + } + if (isOpenAgentAssistantResultNode(selectedNode)) { + await this.ensureTaskForOpenAgentResultNode(file.path, selectedNode); + } + + const anchor = this.resolveFollowUpAnchorForSelectedNode(file.path, selectedNodeId, nodeById); + const isForkNode = options.fork === true; + const followUpNodeId = createCanvasObjectId(isForkNode ? "oa-fork" : "oa-follow-up"); + const followUpNode = this.buildCanvasFollowUpNode(anchor.anchorNode, followUpNodeId, edges, nodeById, { + fork: isForkNode, + task: anchor.task, + branchSourceNodeId: this.getCanvasBranchSourceNodeId(anchor.anchorNode, anchor.anchorNodeId), + }); + const followUpEdge = { + id: createCanvasObjectId(isForkNode ? "oa-fork-edge" : "oa-follow-up-edge"), + fromNode: anchor.anchorNodeId, + toNode: followUpNodeId, + fromSide: "bottom", + toSide: "top", + ...(isForkNode + ? { + openagent: { + schemaVersion: OPENAGENT_CANVAS_SCHEMA_VERSION, + kind: "fork-edge", + taskId: String(anchor.task?.taskId || ""), + threadId: String(anchor.task?.threadId || ""), + sourceNodeId: this.getCanvasBranchSourceNodeId(anchor.anchorNode, anchor.anchorNodeId), + }, + } + : {}), + }; + + const nextParsed = { + ...parsed, + nodes: [...nodes, followUpNode], + edges: [...edges, followUpEdge], + }; + await this.app.vault.modify(file, JSON.stringify(nextParsed, null, 2) + "\n"); + this.primeCanvasSnapshot(file.path, nextParsed); + return { + canvasPath: file.path, + selectedNodeId, + anchorNodeId: anchor.anchorNodeId, + followUpNodeId, + }; + }); + + this.scheduleCanvasNodeHighlightSync(file.path); + this.requestViewRefresh({ force: true }); + return result; + } + + async ensureTaskForOpenAgentResultNode(canvasPath, resultNode) { + const normalizedCanvasPath = String(canvasPath || "").trim(); + const taskId = getOpenAgentResultTaskId(resultNode); + if (!normalizedCanvasPath || !taskId) { + return null; + } + + const cachedTask = this.tasksById[taskId] || null; + if (cachedTask && this.getTaskCanvasPath(cachedTask) === normalizedCanvasPath) { + return cachedTask; + } + + await this.refreshTask(taskId, { force: true, adoptCurrentVault: true }); + const refreshedTask = this.tasksById[taskId] || null; + return refreshedTask && this.getTaskCanvasPath(refreshedTask) === normalizedCanvasPath + ? refreshedTask + : null; + } + + resolveFollowUpAnchorForSelectedNode(canvasPath, selectedNodeId, nodeById) { + const normalizedCanvasPath = String(canvasPath || "").trim(); + const normalizedNodeId = String(selectedNodeId || "").trim(); + const selectedNode = nodeById?.get(normalizedNodeId) || null; + if (!normalizedCanvasPath || !normalizedNodeId || !selectedNode) { + throw new Error("The selected Canvas node could not be found."); + } + + if (isOpenAgentAssistantResultNode(selectedNode)) { + const task = this.findTaskByOpenAgentResultNode(normalizedCanvasPath, normalizedNodeId); + if (!task) { + throw new Error("Select an existing OpenAgent source or result node first."); + } + + return { + task, + anchorNodeId: normalizedNodeId, + anchorNode: selectedNode, + }; + } + + const task = this.findTaskByCanvasRunSourceNode(normalizedCanvasPath, normalizedNodeId) + || this.findTaskByRootCanvasNode(normalizedCanvasPath, normalizedNodeId); + if (!task) { + throw new Error("Select an existing OpenAgent source or result node first."); + } + + const resultNodeId = String( + task?.canvasBinding?.resultNodesBySourceNodeId?.[normalizedNodeId]?.resultNodeId + || buildTaskResultNodeId(task.taskId, normalizedNodeId) + ).trim(); + const resultNode = resultNodeId ? nodeById?.get(resultNodeId) || null : null; + if (resultNode && isOpenAgentAssistantResultNode(resultNode)) { + return { + task, + anchorNodeId: resultNodeId, + anchorNode: resultNode, + }; + } + + return { + task, + anchorNodeId: normalizedNodeId, + anchorNode: selectedNode, + }; + } + + getCanvasBranchSourceNodeId(anchorNode, fallbackNodeId = "") { + return getOpenAgentResultSourceNodeId(anchorNode) || String(fallbackNodeId || anchorNode?.id || "").trim(); + } + + getCanvasBranchMessageId(task, branchSourceNodeId, anchorNode = null) { + const nodeMessageId = String(getOpenAgentCanvasMetadata(anchorNode)?.messageId || "").trim(); + if (nodeMessageId) { + return nodeMessageId; + } + + return String(task?.canvasBinding?.resultNodesBySourceNodeId?.[branchSourceNodeId]?.messageId || "").trim(); + } + + buildCanvasFollowUpNode(anchorNode, followUpNodeId, edges, nodeById, options = {}) { + const width = clampNumber( + toFiniteNumber(anchorNode?.width, RESULT_NODE_DEFAULT_WIDTH), + RESULT_NODE_MIN_WIDTH, + RESULT_NODE_MAX_WIDTH + ); + const x = toFiniteNumber(anchorNode?.x, 0); + const childNodes = (Array.isArray(edges) ? edges : []) + .filter((edge) => String(edge?.fromNode || "").trim() === String(anchorNode?.id || "").trim()) + .map((edge) => nodeById?.get(String(edge?.toNode || "").trim()) || null) + .filter(Boolean) + .filter((node) => !isOpenAgentAssistantResultNode(node)); + const y = childNodes.length > 0 + ? childNodes.reduce((maxY, node) => { + const nextBottom = toFiniteNumber(node?.y, 0) + toFiniteNumber(node?.height, FOLLOW_UP_NODE_DEFAULT_HEIGHT); + return Math.max(maxY, nextBottom + RESULT_NODE_Y_GAP); + }, toFiniteNumber(anchorNode?.y, 0) + toFiniteNumber(anchorNode?.height, FOLLOW_UP_NODE_DEFAULT_HEIGHT) + RESULT_NODE_Y_GAP) + : toFiniteNumber(anchorNode?.y, 0) + toFiniteNumber(anchorNode?.height, FOLLOW_UP_NODE_DEFAULT_HEIGHT) + RESULT_NODE_Y_GAP; + + const isForkNode = options.fork === true; + const branchSourceNodeId = String(options.branchSourceNodeId || this.getCanvasBranchSourceNodeId(anchorNode)).trim(); + const branchMessageId = this.getCanvasBranchMessageId(options.task, branchSourceNodeId, anchorNode); + return { + id: followUpNodeId, + type: "text", + x, + y, + width, + height: FOLLOW_UP_NODE_DEFAULT_HEIGHT, + text: "", + ...(isForkNode + ? { + openagent: { + schemaVersion: OPENAGENT_CANVAS_SCHEMA_VERSION, + kind: "fork-request", + taskId: String(options.task?.taskId || ""), + threadId: String(options.task?.threadId || ""), + sourceNodeId: branchSourceNodeId, + branchSourceNodeId, + branchMessageId, + }, + } + : {}), + }; + } + + readCanvasNodeIdSetSync(canvasPath, cache = new Map()) { + const normalizedCanvasPath = String(canvasPath || "").trim(); + if (!normalizedCanvasPath) { + return null; + } + + if (cache.has(normalizedCanvasPath)) { + return cache.get(normalizedCanvasPath); + } + + const snapshot = this.getCanvasSnapshotSync(normalizedCanvasPath); + if (!snapshot) { + cache.set(normalizedCanvasPath, null); + return null; + } + + cache.set(normalizedCanvasPath, snapshot.nodeIds); + return snapshot.nodeIds; + } + + readCanvasNodeByIdSync(canvasPath, nodeId) { + const normalizedCanvasPath = String(canvasPath || "").trim(); + const normalizedNodeId = String(nodeId || "").trim(); + if (!normalizedCanvasPath || !normalizedNodeId) { + return null; + } + + const snapshot = this.getCanvasSnapshotSync(normalizedCanvasPath); + return snapshot?.nodeById.get(normalizedNodeId) || null; + } + + isTaskArchived(task, cache = new Map()) { + const rootRef = this.getTaskRootNodeRef(task); + if (!rootRef) { + return false; + } + + const nodeIds = this.readCanvasNodeIdSetSync(rootRef.canvasPath, cache); + if (!nodeIds) { + return false; + } + + return !nodeIds.has(rootRef.nodeId); + } + + getFilteredTasksByArchiveState(options = {}) { + const activeCanvasPath = this.getActiveCanvasPath(); + const scopedCanvasPath = activeCanvasPath || ""; + const archiveState = options.archived === true; + const archiveProbeCache = options.archiveProbeCache instanceof Map + ? options.archiveProbeCache + : new Map(); + const filteredTasks = this.getTasks().filter((task) => { + if (scopedCanvasPath && this.getTaskCanvasPath(task) !== scopedCanvasPath) { + return false; + } + + return this.isTaskArchived(task, archiveProbeCache) === archiveState; + }); + + if (!scopedCanvasPath) { + return filteredTasks; + } + + const latestBySelection = new Map(); + filteredTasks.forEach((task) => { + const key = this.getTaskSelectionIdentity(task); + if (!key || latestBySelection.has(key)) { + return; + } + latestBySelection.set(key, task); + }); + return Array.from(latestBySelection.values()); + } + + getVisibleTasks(options = {}) { + const archiveProbeCache = options.archiveProbeCache; + return this.getFilteredTasksByArchiveState({ archived: false, archiveProbeCache }); + } + + getArchivedTasks(options = {}) { + const archiveProbeCache = options.archiveProbeCache; + return this.getFilteredTasksByArchiveState({ archived: true, archiveProbeCache }); + } + + hasUnreadTask(taskId) { + const normalizedTaskId = String(taskId || "").trim(); + if (!normalizedTaskId) { + return false; + } + + return Boolean(this.uiState?.unreadTaskStateById?.[normalizedTaskId]); + } + + updateTaskUnreadState(taskId, unread) { + const normalizedTaskId = String(taskId || "").trim(); + if (!normalizedTaskId) { + return false; + } + + const currentState = normalizeUnreadTaskState(this.uiState?.unreadTaskStateById); + const hasUnread = Boolean(currentState[normalizedTaskId]); + if (unread) { + if (hasUnread) { + return false; + } + currentState[normalizedTaskId] = true; + } else { + if (!hasUnread) { + return false; + } + delete currentState[normalizedTaskId]; + } + + this.uiState.unreadTaskStateById = currentState; + return true; + } + + setTaskUnreadState(taskId, unread, options = {}) { + const changed = this.updateTaskUnreadState(taskId, unread); + if (!changed) { + return false; + } + + if (options.persist !== false) { + void this.persistPluginState(); + } + if (options.refresh !== false) { + this.requestViewRefresh(); + } + return true; + } + + pruneUnreadTaskState() { + const currentState = normalizeUnreadTaskState(this.uiState?.unreadTaskStateById); + const taskIds = new Set(Object.keys(this.tasksById)); + let changed = false; + + Object.keys(currentState).forEach((taskId) => { + if (!taskIds.has(taskId)) { + delete currentState[taskId]; + changed = true; + } + }); + + this.uiState.unreadTaskStateById = currentState; + return changed; + } + + getRunningTasks() { + const activeCanvasPath = this.getActiveCanvasPath(); + return this.getTasks().filter((task) => { + if (activeCanvasPath && this.getTaskCanvasPath(task) !== activeCanvasPath) { + return false; + } + + return this.isTaskRunning(task); + }); + } + + getPanelTab() { + return normalizePanelTab(this.uiState?.panelTab); + } + + isTaskDetailOpen() { + return this.uiState?.taskDetailOpen === true; + } + + async setTaskDetailOpen(isOpen) { + const nextValue = isOpen === true; + this.uiState.panelTab = PANEL_TAB_OPTIONS.TASK_LIST; + if (this.uiState.taskDetailOpen === nextValue) { + this.requestViewRefresh(); + return; + } + + this.uiState.taskDetailOpen = nextValue; + await this.persistPluginState(); + this.requestViewRefresh(); + } + + async setPanelTab(panelTab) { + const nextPanelTab = normalizePanelTab(panelTab); + const previousTaskDetailOpen = this.uiState.taskDetailOpen; + if (nextPanelTab !== PANEL_TAB_OPTIONS.TASK_LIST) { + this.uiState.taskDetailOpen = false; + } + if (this.uiState.panelTab === nextPanelTab) { + if (previousTaskDetailOpen !== this.uiState.taskDetailOpen) { + await this.persistPluginState(); + } + this.requestViewRefresh(); + return; + } + + this.uiState.panelTab = nextPanelTab; + await this.persistPluginState(); + this.requestViewRefresh(); + } + + getActiveTask() { + const task = this.tasksById[this.uiState.activeTaskId] || null; + if (!this.isTaskForCurrentVault(task)) { + return null; + } + const activeCanvasPath = this.getActiveCanvasPath(); + if (!activeCanvasPath) { + return task; + } + + return this.getTaskCanvasPath(task) === activeCanvasPath ? task : null; + } + + getDraft(taskId) { + return this.uiState.draftsByTaskId?.[taskId] || ""; + } + + setDraft(taskId, value) { + this.uiState.draftsByTaskId = this.uiState.draftsByTaskId || {}; + this.uiState.draftsByTaskId[taskId] = value; + } + + clearDraft(taskId) { + if (this.uiState.draftsByTaskId) { + delete this.uiState.draftsByTaskId[taskId]; + this.persistPluginState(); + } + } + + getCanvasNodeHighlightRef(task, archiveProbeCache = new Map()) { + if (this.isTaskArchived(task, archiveProbeCache)) { + return null; + } + + return this.getTaskCanvasRunSourceRef(task); + } + + getCanvasNodeHighlightKey(canvasPath, nodeId) { + const normalizedCanvasPath = String(canvasPath || "").trim(); + const normalizedNodeId = String(nodeId || "").trim(); + if (!normalizedCanvasPath || !normalizedNodeId) { + return ""; + } + + return `${normalizedCanvasPath}\0${normalizedNodeId}`; + } + + isTaskRunning(task) { + return Boolean(task?.currentTurnId) || ["starting", "running"].includes(String(task?.status || "")); + } + + shouldMarkTaskUnread(previousTask, nextTask) { + if (!previousTask || !nextTask?.taskId) { + return false; + } + + return this.isTaskRunning(previousTask) && !this.isTaskRunning(nextTask); + } + + maybeSyncCanvasNodeHighlights(task, previousTask) { + const canvasPaths = new Set(); + const archiveProbeCache = new Map(); + [task, previousTask].forEach((entry) => { + const ref = this.getCanvasNodeHighlightRef(entry, archiveProbeCache); + if (ref?.canvasPath) { + canvasPaths.add(ref.canvasPath); + } + }); + + canvasPaths.forEach((canvasPath) => { + this.scheduleCanvasNodeHighlightSync(canvasPath); + }); + } + + reconcileAllCanvasNodeHighlights() { + const canvasPaths = new Set(); + const archiveProbeCache = new Map(); + + Object.values(this.tasksById).forEach((task) => { + const ref = this.getCanvasNodeHighlightRef(task, archiveProbeCache); + if (ref?.canvasPath) { + canvasPaths.add(ref.canvasPath); + } + }); + + Object.values(this.uiState.canvasNodeHighlightStateByKey || {}).forEach((entry) => { + const canvasPath = String(entry?.canvasPath || "").trim(); + if (canvasPath) { + canvasPaths.add(canvasPath); + } + }); + + canvasPaths.forEach((canvasPath) => { + this.scheduleCanvasNodeHighlightSync(canvasPath); + }); + } + + getRunningCanvasNodeHighlightKeys(canvasPath) { + const runningKeys = new Set(); + const archiveProbeCache = new Map(); + + Object.values(this.tasksById).forEach((task) => { + const ref = this.getCanvasNodeHighlightRef(task, archiveProbeCache); + if (!ref || ref.canvasPath !== canvasPath || !this.isTaskRunning(task)) { + return; + } + + const key = this.getCanvasNodeHighlightKey(ref.canvasPath, ref.nodeId); + if (key) { + runningKeys.add(key); + } + }); + + return runningKeys; + } + + getCompletedCanvasNodeHighlightKeys(canvasPath) { + const completedKeys = new Set(); + const archiveProbeCache = new Map(); + + Object.values(this.tasksById).forEach((task) => { + const ref = this.getCanvasNodeHighlightRef(task, archiveProbeCache); + if (!ref || ref.canvasPath !== canvasPath || this.isTaskRunning(task)) { + return; + } + + const assistantMessage = this.getLatestAssistantMessage(task); + if ( + String(task?.status || "") !== "idle" + || !assistantMessage?.text?.trim() + || !this.isLatestAssistantMessageCurrentForActiveSource(task, assistantMessage) + ) { + return; + } + + const key = this.getCanvasNodeHighlightKey(ref.canvasPath, ref.nodeId); + if (key) { + completedKeys.add(key); + } + }); + + return completedKeys; + } + + getTrackedCanvasNodeHighlightKeys(canvasPath) { + const trackedKeys = new Set(); + + Object.keys(this.uiState.canvasNodeHighlightStateByKey || {}).forEach((key) => { + const [trackedCanvasPath] = key.split("\0"); + if (trackedCanvasPath === canvasPath) { + trackedKeys.add(key); + } + }); + + return trackedKeys; + } + + scheduleCanvasNodeHighlightSync(canvasPath) { + if (!canvasPath) { + return; + } + + if (this.canvasNodeHighlightSyncInFlight.has(canvasPath)) { + this.canvasNodeHighlightSyncPending.add(canvasPath); + return; + } + + this.canvasNodeHighlightSyncInFlight.add(canvasPath); + void this.syncCanvasNodeHighlightsForCanvas(canvasPath) + .catch(async (error) => { + await this.appendDebugEvent("canvas_highlight_error", { + canvasPath, + message: String(error?.message || error), + }); + }) + .finally(() => { + this.canvasNodeHighlightSyncInFlight.delete(canvasPath); + if (this.canvasNodeHighlightSyncPending.has(canvasPath)) { + this.canvasNodeHighlightSyncPending.delete(canvasPath); + this.scheduleCanvasNodeHighlightSync(canvasPath); + } + }); + } + + async syncCanvasNodeHighlightsForCanvas(canvasPath) { + await this.withSerializedCanvasMutation(canvasPath, async () => { + const runningKeys = this.getRunningCanvasNodeHighlightKeys(canvasPath); + const completedKeys = this.getCompletedCanvasNodeHighlightKeys(canvasPath); + const trackedKeys = this.getTrackedCanvasNodeHighlightKeys(canvasPath); + if (runningKeys.size === 0 && completedKeys.size === 0 && trackedKeys.size === 0) { + return; + } + + const highlightStateByKey = this.uiState.canvasNodeHighlightStateByKey || {}; + const abstractFile = this.app.vault.getAbstractFileByPath(canvasPath); + if (!(abstractFile instanceof TFile) || abstractFile.extension !== "canvas") { + let stateChanged = false; + trackedKeys.forEach((key) => { + if (highlightStateByKey[key]) { + delete highlightStateByKey[key]; + stateChanged = true; + } + }); + if (stateChanged) { + await this.persistPluginState(); + } + return; + } + + const raw = await this.app.vault.cachedRead(abstractFile); + let parsed; + try { + parsed = JSON.parse(raw); + } catch { + throw new Error(`Unable to parse canvas JSON for ${canvasPath}`); + } + + const nodes = Array.isArray(parsed?.nodes) ? [...parsed.nodes] : []; + const relevantKeys = new Set([...runningKeys, ...completedKeys, ...trackedKeys]); + const seenKeys = new Set(); + let nodesChanged = false; + let stateChanged = false; + + for (let index = 0; index < nodes.length; index += 1) { + const node = nodes[index]; + const nodeId = String(node?.id || "").trim(); + if (!nodeId) { + continue; + } + + const key = this.getCanvasNodeHighlightKey(canvasPath, nodeId); + if (!relevantKeys.has(key)) { + continue; + } + + seenKeys.add(key); + const shouldHighlight = runningKeys.has(key); + const shouldMarkCompleted = !shouldHighlight && completedKeys.has(key); + const hasColor = Object.prototype.hasOwnProperty.call(node, "color"); + const currentColor = hasColor ? String(node.color) : ""; + const storedState = highlightStateByKey[key] || null; + + if (shouldHighlight) { + if (!storedState) { + highlightStateByKey[key] = { + canvasPath, + nodeId, + originalColor: hasColor ? node.color : null, + }; + stateChanged = true; + } + + const autoFitHeight = getCanvasTextNodeAutoFitHeight(node); + const shouldResize = Number.isFinite(autoFitHeight) && autoFitHeight > toFiniteNumber(node?.height, 0); + if (!hasColor || currentColor !== RUNNING_CANVAS_NODE_COLOR || shouldResize) { + nodes[index] = { + ...node, + color: RUNNING_CANVAS_NODE_COLOR, + ...(shouldResize ? { height: autoFitHeight } : {}), + }; + nodesChanged = true; + } + continue; + } + + if (shouldMarkCompleted) { + if (!hasColor || String(node.color) !== COMPLETED_CANVAS_NODE_COLOR) { + nodes[index] = { + ...node, + color: COMPLETED_CANVAS_NODE_COLOR, + }; + nodesChanged = true; + } + + if (storedState) { + delete highlightStateByKey[key]; + stateChanged = true; + } + continue; + } + + if (!storedState) { + continue; + } + + if (!hasColor || String(node.color) !== COMPLETED_CANVAS_NODE_COLOR) { + nodes[index] = { + ...node, + color: COMPLETED_CANVAS_NODE_COLOR, + }; + nodesChanged = true; + } + + delete highlightStateByKey[key]; + stateChanged = true; + } + + trackedKeys.forEach((key) => { + if (seenKeys.has(key) || !highlightStateByKey[key]) { + return; + } + + delete highlightStateByKey[key]; + stateChanged = true; + }); + + if (nodesChanged) { + parsed = { + ...parsed, + nodes, + }; + await this.app.vault.modify(abstractFile, JSON.stringify(parsed, null, 2) + "\n"); + this.primeCanvasSnapshot(canvasPath, parsed); + } + + if (stateChanged) { + await this.persistPluginState(); + } + }); + } + + mergeTask(task, options = {}) { + if (!task) { + return null; + } + + const previousTask = this.tasksById[task.taskId] || null; + const inputTask = options.adoptCurrentVault === true && !this.getTaskVaultId(task) + ? this.scopeTaskForCurrentVault(task) + : task; + const nextTask = this.normalizeTaskForStore(inputTask, previousTask); + if (!nextTask) { + return null; + } + this.tasksById[nextTask.taskId] = nextTask; + const didMarkUnread = this.shouldMarkTaskUnread(previousTask, nextTask) + && this.updateTaskUnreadState(nextTask.taskId, true); + if (didMarkUnread) { + void this.persistPluginState(); + } + this.syncActiveTaskToCanvasContext(); + this.maybeSyncCanvasNodeHighlights(nextTask, previousTask); + this.maybeSyncTaskResultNode(nextTask, previousTask); + this.reconcileRunningTaskStreams(); + return nextTask; + } + + taskHasLoadedMessages(task) { + return Boolean(task) && task.messagesIncluded !== false; + } + + normalizeTaskForStore(task, previousTask = null) { + if (!task?.taskId) { + return null; + } + const scopedTask = !this.getTaskVaultId(task) && this.isTaskForCurrentVault(previousTask) + ? this.scopeTaskForCurrentVault(task) + : task; + if (!this.isTaskForCurrentVault(scopedTask)) { + return null; + } + + const nextTask = { + ...scopedTask, + }; + const shouldReuseDetailedMessages = ( + nextTask.messagesIncluded === false + && previousTask + && previousTask.messagesIncluded !== false + && String(previousTask.taskId || "") === String(this.uiState?.activeTaskId || "") + && Array.isArray(previousTask.messages) + ); + + if (shouldReuseDetailedMessages) { + nextTask.messages = previousTask.messages; + nextTask.messagesIncluded = true; + } else { + nextTask.messages = Array.isArray(task.messages) ? task.messages : []; + nextTask.messagesIncluded = task.messagesIncluded !== false; + } + + if (typeof nextTask.messageCount !== "number") { + nextTask.messageCount = nextTask.messages.length; + } + + if (!nextTask.latestAssistantMessage && previousTask?.latestAssistantMessage) { + nextTask.latestAssistantMessage = previousTask.latestAssistantMessage; + } + + return nextTask; + } + + getLatestAssistantMessage(task) { + const messages = Array.isArray(task?.messages) ? task.messages : []; + for (let index = messages.length - 1; index >= 0; index -= 1) { + const message = messages[index]; + const text = String(message?.text || ""); + if (message?.role === "assistant" && text.trim()) { + return { + ...message, + text, + }; + } + } + + const latestAssistantMessage = task?.latestAssistantMessage; + const text = String(latestAssistantMessage?.text || ""); + if (text.trim()) { + return { + ...latestAssistantMessage, + text, + }; + } + + return null; + } + + isLatestAssistantMessageCurrentForActiveSource(task, assistantMessage = null) { + const candidate = assistantMessage || this.getLatestAssistantMessage(task); + if (!candidate?.text?.trim()) { + return false; + } + + const activeSourceUpdatedAt = String(task?.canvasBinding?.activeSourceUpdatedAt || "").trim(); + if (!activeSourceUpdatedAt) { + return true; + } + + const assistantTimestamp = String(candidate.updatedAt || candidate.createdAt || "").trim(); + return !assistantTimestamp || assistantTimestamp >= activeSourceUpdatedAt; + } + + getTaskResultSyncSignature(task) { + const assistantMessage = this.getLatestAssistantMessage(task); + if (!assistantMessage?.text?.trim() || !this.isLatestAssistantMessageCurrentForActiveSource(task, assistantMessage)) { + return ""; + } + + const sourceRef = this.getTaskCanvasRunSourceRef(task); + return stableShortHash(`${task?.taskId || ""}\0${sourceRef?.nodeId || ""}\0${assistantMessage.text}`); + } + + getTaskResultNodeRef(task) { + const sourceRef = this.getTaskCanvasRunSourceRef(task); + const canvasPath = String(sourceRef?.canvasPath || "").trim(); + const sourceNodeId = String(sourceRef?.nodeId || "").trim(); + const boundResultNodeId = String( + task?.canvasBinding?.resultNodesBySourceNodeId?.[sourceNodeId]?.resultNodeId || "" + ).trim(); + const taskId = String(task?.taskId || "").trim(); + if (!canvasPath || !sourceNodeId || !taskId) { + return null; + } + + return { + canvasPath, + sourceNodeId, + resultNodeId: boundResultNodeId || buildTaskResultNodeId(taskId, sourceNodeId), + }; + } + + canSyncTaskResultNode(task, cache = new Map()) { + const ref = this.getTaskResultNodeRef(task); + if (!ref) { + return false; + } + + const nodeIds = this.readCanvasNodeIdSetSync(ref.canvasPath, cache); + return Boolean(nodeIds?.has(ref.sourceNodeId)); + } + + doesTaskResultNodeExist(task, cache = new Map()) { + const ref = this.getTaskResultNodeRef(task); + if (!ref) { + return false; + } + + const nodeIds = this.readCanvasNodeIdSetSync(ref.canvasPath, cache); + return Boolean(nodeIds?.has(ref.resultNodeId)); + } + + doesTaskResultNodeHaveCurrentMetadata(task, signature) { + const ref = this.getTaskResultNodeRef(task); + if (!ref) { + return false; + } + + const resultNode = this.readCanvasNodeByIdSync(ref.canvasPath, ref.resultNodeId); + const metadata = getOpenAgentCanvasMetadata(resultNode); + return Boolean( + metadata?.kind === "assistant-result" + && String(metadata.taskId || "") === String(task?.taskId || "") + && String(metadata.sourceNodeId || "") === ref.sourceNodeId + && String(metadata.syncSignature || "") === String(signature || "") + ); + } + + shouldAutoSyncTaskResultNode(task, previousTask) { + const selection = task?.selectionContext; + const nodeIds = Array.isArray(selection?.nodeIds) + ? selection.nodeIds.map((nodeId) => String(nodeId)).filter(Boolean) + : []; + const textBlocks = Array.isArray(selection?.textBlocks) ? selection.textBlocks : []; + const markdownFiles = Array.isArray(selection?.markdownFiles) ? selection.markdownFiles : []; + const imageFiles = Array.isArray(selection?.imageFiles) ? selection.imageFiles : []; + const hasSingleSupportedSource = ( + (textBlocks.length === 1) + || (textBlocks.length === 0 && markdownFiles.length === 1) + || (textBlocks.length === 0 && markdownFiles.length === 0 && imageFiles.length === 1) + ); + if (nodeIds.length !== 1 || !hasSingleSupportedSource) { + return false; + } + + if (String(task?.status || "") !== "idle" || task?.currentTurnId) { + return false; + } + + const canvasNodeCache = new Map(); + if (!this.canSyncTaskResultNode(task, canvasNodeCache)) { + return false; + } + + const signature = this.getTaskResultSyncSignature(task); + if (!signature) { + return false; + } + + const resultNodeExists = this.doesTaskResultNodeExist(task, canvasNodeCache); + const resultNodeMetadataCurrent = resultNodeExists + ? this.doesTaskResultNodeHaveCurrentMetadata(task, signature) + : false; + if ( + this.resultNodeSyncStateByTaskId[task.taskId] === signature + && resultNodeExists + && resultNodeMetadataCurrent + ) { + return false; + } + + if (resultNodeExists && !resultNodeMetadataCurrent) { + return true; + } + + if ( + previousTask + && ( + previousTask.currentTurnId + || ["starting", "running"].includes(String(previousTask.status || "")) + ) + ) { + return true; + } + + return !resultNodeExists; + } + + maybeSyncTaskResultNode(task, previousTask) { + if (!this.shouldAutoSyncTaskResultNode(task, previousTask)) { + return; + } + + if (this.resultNodeSyncInFlight.has(task.taskId)) { + return; + } + + const signature = this.getTaskResultSyncSignature(task); + if (!signature) { + return; + } + + this.resultNodeSyncInFlight.add(task.taskId); + void this.syncTaskResultNode(task, signature) + .catch((error) => { + const message = String(error?.message || error); + void this.appendDebugEvent("canvas_result_error", { + taskId: task.taskId, + message, + }); + new Notice(`Unable to write the Canvas result node: ${message}`); + }) + .finally(() => { + this.resultNodeSyncInFlight.delete(task.taskId); + }); + } + + async syncTaskResultNode(task, signature) { + const assistantMessage = this.getLatestAssistantMessage(task); + const result = await this.upsertCanvasResultNode(task, assistantMessage, signature); + const bindingResponse = await this.api.updateTaskCanvasBinding(task.taskId, { + canvasPath: result.canvasPath, + activeSourceNodeId: result.sourceNodeId, + resultNode: { + sourceNodeId: result.sourceNodeId, + resultNodeId: result.resultNodeId, + edgeId: result.edgeId, + messageId: String(assistantMessage?.id || ""), + syncSignature: signature, + }, + }); + this.mergeTask(bindingResponse.task); + this.resultNodeSyncStateByTaskId[task.taskId] = signature; + await this.persistPluginState(); + await this.appendDebugEvent("canvas_result_synced", { + taskId: task.taskId, + canvasPath: result.canvasPath, + sourceNodeId: result.sourceNodeId, + resultNodeId: result.resultNodeId, + }); + new Notice("Created a linked Canvas result node."); + } + + async upsertCanvasResultNode(task, assistantMessage, signature = "") { + const sourceRef = this.getTaskCanvasRunSourceRef(task); + const canvasPath = String(sourceRef?.canvasPath || task?.selectionContext?.canvasPath || "").trim(); + const sourceNodeId = String(sourceRef?.nodeId || task?.selectionContext?.nodeIds?.[0] || "").trim(); + if (!canvasPath || !sourceNodeId) { + throw new Error("Task is missing the Canvas source node."); + } + + const normalizedText = String(assistantMessage?.text || assistantMessage || "").trim(); + if (!normalizedText) { + throw new Error("There is no assistant result to write back into the Canvas."); + } + const canvasText = this.normalizeAssistantLinksForObsidian(normalizedText); + + return this.withSerializedCanvasMutation(canvasPath, async () => { + const abstractFile = this.app.vault.getAbstractFileByPath(canvasPath); + if (!(abstractFile instanceof TFile) || abstractFile.extension !== "canvas") { + throw new Error(`Canvas file not found: ${canvasPath}`); + } + + const raw = await this.app.vault.cachedRead(abstractFile); + let parsed; + try { + parsed = JSON.parse(raw); + } catch { + throw new Error(`Unable to parse canvas JSON for ${canvasPath}`); + } + + const nodes = Array.isArray(parsed?.nodes) ? [...parsed.nodes] : []; + const edges = Array.isArray(parsed?.edges) ? [...parsed.edges] : []; + const sourceNodeIndex = nodes.findIndex((node) => String(node?.id || "") === sourceNodeId); + if (sourceNodeIndex < 0) { + throw new Error(`Source Canvas node is missing: ${sourceNodeId}`); + } + + const sourceNode = nodes[sourceNodeIndex] || {}; + const nextSourceNode = { + ...sourceNode, + color: COMPLETED_CANVAS_NODE_COLOR, + }; + nodes[sourceNodeIndex] = nextSourceNode; + const resultNodeId = buildTaskResultNodeId(task.taskId, sourceNodeId); + const edgeId = `oa-edge-${stableShortHash(`${sourceNodeId}\0${task.taskId}`)}`; + const existingResultNodeIndex = nodes.findIndex((node) => String(node?.id || "") === resultNodeId); + const existingResultNode = existingResultNodeIndex >= 0 ? nodes[existingResultNodeIndex] : null; + const resultMetadata = { + ...(getOpenAgentCanvasMetadata(existingResultNode) || {}), + schemaVersion: OPENAGENT_CANVAS_SCHEMA_VERSION, + kind: "assistant-result", + taskId: String(task.taskId || ""), + threadId: String(task.threadId || ""), + sourceNodeId, + messageId: String(assistantMessage?.id || ""), + syncSignature: String(signature || this.getTaskResultSyncSignature(task) || ""), + }; + + const defaultWidth = clampNumber( + toFiniteNumber(existingResultNode?.width, toFiniteNumber(sourceNode.width, RESULT_NODE_DEFAULT_WIDTH)), + RESULT_NODE_MIN_WIDTH, + RESULT_NODE_MAX_WIDTH + ); + const defaultX = toFiniteNumber(sourceNode.x, 0); + const siblingResultNodes = edges + .filter((edge) => String(edge?.fromNode || "") === sourceNodeId) + .map((edge) => nodes.find((node) => String(node?.id || "") === String(edge?.toNode || ""))) + .filter((node) => node && String(node.id || "") !== resultNodeId && isOpenAgentAssistantResultNode(node)); + const defaultY = siblingResultNodes.length > 0 + ? siblingResultNodes.reduce((maxY, node) => { + const nextBottom = toFiniteNumber(node.y, toFiniteNumber(sourceNode.y, 0)) + toFiniteNumber(node.height, RESULT_NODE_MIN_HEIGHT); + return Math.max(maxY, nextBottom + RESULT_NODE_Y_GAP); + }, toFiniteNumber(sourceNode.y, 0) + toFiniteNumber(sourceNode.height, RESULT_NODE_MIN_HEIGHT) + RESULT_NODE_Y_GAP) + : toFiniteNumber(sourceNode.y, 0) + toFiniteNumber(sourceNode.height, RESULT_NODE_MIN_HEIGHT) + RESULT_NODE_Y_GAP; + const resultNode = { + ...(existingResultNode || {}), + id: resultNodeId, + type: "text", + x: existingResultNode ? toFiniteNumber(existingResultNode.x, defaultX) : defaultX, + y: existingResultNode ? toFiniteNumber(existingResultNode.y, defaultY) : defaultY, + width: existingResultNode ? clampNumber(toFiniteNumber(existingResultNode.width, defaultWidth), RESULT_NODE_MIN_WIDTH, RESULT_NODE_MAX_WIDTH) : defaultWidth, + height: estimateCanvasTextNodeHeight( + canvasText, + existingResultNode ? toFiniteNumber(existingResultNode.width, defaultWidth) : defaultWidth + ), + text: canvasText, + openagent: resultMetadata, + }; + + if (existingResultNodeIndex >= 0) { + nodes[existingResultNodeIndex] = resultNode; + } else { + nodes.push(resultNode); + } + + const existingEdgeIndex = edges.findIndex((edge) => ( + String(edge?.id || "") === edgeId + || ( + String(edge?.fromNode || "") === sourceNodeId + && String(edge?.toNode || "") === resultNodeId + ) + )); + const edge = { + ...(existingEdgeIndex >= 0 ? edges[existingEdgeIndex] : {}), + id: edgeId, + fromNode: sourceNodeId, + toNode: resultNodeId, + fromSide: existingEdgeIndex >= 0 ? String(edges[existingEdgeIndex]?.fromSide || "bottom") : "bottom", + toSide: existingEdgeIndex >= 0 ? String(edges[existingEdgeIndex]?.toSide || "top") : "top", + openagent: { + ...(existingEdgeIndex >= 0 ? getOpenAgentCanvasMetadata(edges[existingEdgeIndex]) || {} : {}), + schemaVersion: OPENAGENT_CANVAS_SCHEMA_VERSION, + kind: "result-edge", + taskId: String(task.taskId || ""), + threadId: String(task.threadId || ""), + sourceNodeId, + resultNodeId, + }, + }; + + if (existingEdgeIndex >= 0) { + edges[existingEdgeIndex] = edge; + } else { + edges.push(edge); + } + + parsed = { + ...parsed, + nodes, + edges, + }; + await this.app.vault.modify(abstractFile, JSON.stringify(parsed, null, 2) + "\n"); + this.primeCanvasSnapshot(canvasPath, parsed); + + return { + canvasPath, + sourceNodeId, + resultNodeId, + edgeId, + }; + }); + } + + async activateView() { + let leaf = this.app.workspace.getLeavesOfType(VIEW_TYPE)[0]; + if (!leaf) { + leaf = this.app.workspace.getRightLeaf(false); + await leaf.setViewState({ type: VIEW_TYPE, active: true }); + } + + this.app.workspace.revealLeaf(leaf); + this.requestViewRefresh(); + } + + async activateVoiceGraphView() { + let leaf = this.app.workspace.getLeavesOfType(VOICE_GRAPH_VIEW_TYPE)[0]; + if (!leaf) { + leaf = this.app.workspace.getLeaf("tab"); + await leaf.setViewState({ type: VOICE_GRAPH_VIEW_TYPE, active: true }); + } + + this.app.workspace.revealLeaf(leaf); + } + + async openThreadList() { + await this.activateView(); + await this.setPanelTab(PANEL_TAB_OPTIONS.WORKSPACE_SETTINGS); + } + + getTaskJumpNodeRef(task) { + if (!task) { + return null; + } + + const assistantMessage = this.getLatestAssistantMessage(task); + const resultRef = this.getTaskResultNodeRef(task); + if ( + resultRef + && assistantMessage?.text?.trim() + && this.isLatestAssistantMessageCurrentForActiveSource(task, assistantMessage) + && this.readCanvasNodeByIdSync(resultRef.canvasPath, resultRef.resultNodeId) + ) { + return { + canvasPath: resultRef.canvasPath, + nodeId: resultRef.resultNodeId, + }; + } + + const sourceRef = this.getTaskCanvasRunSourceRef(task); + if (sourceRef && this.readCanvasNodeByIdSync(sourceRef.canvasPath, sourceRef.nodeId)) { + return sourceRef; + } + + const rootRef = this.getTaskRootNodeRef(task); + if (rootRef && this.readCanvasNodeByIdSync(rootRef.canvasPath, rootRef.nodeId)) { + return rootRef; + } + + return sourceRef || rootRef || null; + } + + findCanvasLeafByPath(canvasPath) { + const normalizedCanvasPath = String(canvasPath || "").trim(); + if (!normalizedCanvasPath) { + return null; + } + + return this.app.workspace.getLeavesOfType("canvas").find((leaf) => { + return String(leaf?.view?.file?.path || "").trim() === normalizedCanvasPath; + }) || null; + } + + findCanvasRuntimeNodeById(canvas, nodeId) { + const normalizedNodeId = String(nodeId || "").trim(); + if (!canvas || !normalizedNodeId) { + return null; + } + + const getCandidateNodeId = (candidate) => { + if (!candidate || typeof candidate !== "object") { + return ""; + } + + const directIds = [ + candidate.id, + candidate.node?.id, + candidate.data?.id, + candidate.node?.data?.id, + candidate.item?.id, + candidate.item?.data?.id, + ]; + for (const candidateId of directIds) { + const normalizedCandidateId = String(candidateId || "").trim(); + if (normalizedCandidateId) { + return normalizedCandidateId; + } + } + + if (typeof candidate.getData === "function") { + try { + return String(candidate.getData()?.id || "").trim(); + } catch { + return ""; + } + } + + return ""; + }; + const matchesNodeId = (candidate) => getCandidateNodeId(candidate) === normalizedNodeId; + const candidateCollections = [ + canvas?.nodes, + canvas?.nodeMap, + canvas?.selectionManager?.nodes, + ].filter(Boolean); + + for (const collection of candidateCollections) { + if (collection instanceof Map) { + const directMatch = collection.get(normalizedNodeId); + if (directMatch) { + return directMatch; + } + + for (const value of collection.values()) { + if (matchesNodeId(value)) { + return value; + } + } + continue; + } + + if (Array.isArray(collection)) { + const directMatch = collection.find((value) => matchesNodeId(value)); + if (directMatch) { + return directMatch; + } + continue; + } + + if (typeof collection === "object") { + const directMatch = collection[normalizedNodeId]; + if (matchesNodeId(directMatch)) { + return directMatch; + } + + for (const value of Object.values(collection)) { + if (matchesNodeId(value)) { + return value; + } + } + } + } + + return null; + } + + selectCanvasNodeInView(view, nodeId, runtimeNode = null) { + const normalizedNodeId = String(nodeId || "").trim(); + const canvas = view?.canvas; + if (!normalizedNodeId || !canvas) { + return false; + } + + const deselectCalls = [ + [canvas, "deselectAll"], + [canvas?.selectionManager, "deselectAll"], + [canvas?.selectionManager, "clear"], + ]; + deselectCalls.forEach(([owner, method]) => { + if (typeof owner?.[method] !== "function") { + return; + } + + try { + owner[method](); + } catch { + // Ignore Canvas API differences across Obsidian versions. + } + }); + + if (runtimeNode) { + const selectCalls = [ + [canvas, "select"], + [canvas?.selectionManager, "select"], + ]; + for (const [owner, method] of selectCalls) { + if (typeof owner?.[method] !== "function") { + continue; + } + + try { + owner[method](runtimeNode); + return true; + } catch { + // Fall through to DOM selection fallback below. + } + } + } + + const selector = `[data-node-id="${escapeAttributeSelectorValue(normalizedNodeId)}"]`; + const nodeEl = view?.containerEl?.querySelector?.(selector); + if (!nodeEl) { + return false; + } + + ["mousedown", "mouseup", "click"].forEach((eventName) => { + try { + nodeEl.dispatchEvent(new MouseEvent(eventName, { + bubbles: true, + cancelable: true, + button: 0, + })); + } catch { + // Ignore DOM event dispatch failures on older runtimes. + } + }); + return true; + } + + getCanvasNodeElement(view, nodeId) { + const normalizedNodeId = String(nodeId || "").trim(); + if (!normalizedNodeId) { + return null; + } + + const selector = `[data-node-id="${escapeAttributeSelectorValue(normalizedNodeId)}"]`; + return view?.containerEl?.querySelector?.(selector) || null; + } + + getEditableCanvasNodeElement(view, nodeId) { + const nodeEl = this.getCanvasNodeElement(view, nodeId); + if (!nodeEl) { + return null; + } + + return nodeEl.querySelector?.("textarea, input, [contenteditable='true'], .cm-content") + || nodeEl; + } + + focusEditableCanvasNodeElement(element) { + if (!element || typeof element.focus !== "function") { + return false; + } + + try { + element.focus(); + } catch { + return false; + } + + if (typeof element.setSelectionRange === "function" && typeof element.value === "string") { + try { + const offset = element.value.length; + element.setSelectionRange(offset, offset); + } catch { + // Ignore selection placement failures on unsupported inputs. + } + } + + return true; + } + + getRuntimeEditableCanvasNodeElement(runtimeNode) { + if (!runtimeNode || typeof runtimeNode !== "object") { + return null; + } + + const directEditor = runtimeNode?.child?.editMode?.cm?.dom; + if (directEditor instanceof HTMLElement) { + return directEditor.querySelector?.(".cm-content") || directEditor; + } + + return null; + } + + async enterCanvasNodeEditModeInView(view, nodeId) { + const normalizedNodeId = String(nodeId || "").trim(); + if (!view?.canvas || !normalizedNodeId) { + return false; + } + + let runtimeNode = this.findCanvasRuntimeNodeById(view.canvas, normalizedNodeId); + this.selectCanvasNodeInView(view, normalizedNodeId, runtimeNode); + const tryStartEditing = () => { + runtimeNode = this.findCanvasRuntimeNodeById(view.canvas, normalizedNodeId) || runtimeNode; + if (typeof runtimeNode?.startEditing === "function") { + try { + runtimeNode.startEditing(); + return true; + } catch { + // Fall through to older edit-mode shims below. + } + } + + if (typeof runtimeNode?.setIsEditing === "function") { + try { + runtimeNode.setIsEditing(true); + return true; + } catch { + return false; + } + } + + return false; + }; + + for (let attempt = 0; attempt < 8; attempt += 1) { + if (attempt > 0) { + await sleep(attempt === 1 ? 0 : 50); + } + + tryStartEditing(); + const runtimeEditableElement = this.getRuntimeEditableCanvasNodeElement(runtimeNode); + if (this.resolver.isEditableCanvasElement(runtimeEditableElement) && this.focusEditableCanvasNodeElement(runtimeEditableElement)) { + return true; + } + + if (typeof runtimeNode?.focus === "function") { + try { + runtimeNode.focus(); + } catch { + // Ignore runtime focus failures and continue to DOM fallbacks. + } + } + + const editableElement = this.getEditableCanvasNodeElement(view, normalizedNodeId); + if (this.resolver.isEditableCanvasElement(editableElement) && this.focusEditableCanvasNodeElement(editableElement)) { + return true; + } + + const nodeEl = this.getCanvasNodeElement(view, normalizedNodeId); + if (!nodeEl) { + continue; + } + + const interactionTarget = nodeEl.querySelector?.(".canvas-node-content, .canvas-node-container") + || nodeEl; + try { + interactionTarget.dispatchEvent(new MouseEvent("dblclick", { + bubbles: true, + cancelable: true, + button: 0, + detail: 2, + })); + } catch { + // Ignore DOM interaction failures on older runtimes. + } + } + + return false; + } + + focusCanvasNodeInView(view, canvasPath, nodeId) { + const normalizedCanvasPath = String(canvasPath || "").trim(); + const normalizedNodeId = String(nodeId || "").trim(); + if (!view?.canvas || !normalizedCanvasPath || !normalizedNodeId) { + return false; + } + + const node = this.readCanvasNodeByIdSync(normalizedCanvasPath, normalizedNodeId); + if (!node) { + return false; + } + + if (typeof view.canvas.zoomToBbox === "function") { + try { + view.canvas.zoomToBbox({ + minX: toFiniteNumber(node?.x, 0) - Math.max(1, toFiniteNumber(node?.width, RESULT_NODE_DEFAULT_WIDTH)), + minY: toFiniteNumber(node?.y, 0) - Math.max(1, toFiniteNumber(node?.height, RESULT_NODE_MIN_HEIGHT)), + maxX: toFiniteNumber(node?.x, 0) + Math.max(1, toFiniteNumber(node?.width, RESULT_NODE_DEFAULT_WIDTH)), + maxY: toFiniteNumber(node?.y, 0) + Math.max(1, toFiniteNumber(node?.height, RESULT_NODE_MIN_HEIGHT)), + }); + } catch { + // Ignore zoom failures and still attempt selection below. + } + } + + const runtimeNode = this.findCanvasRuntimeNodeById(view.canvas, normalizedNodeId); + return this.selectCanvasNodeInView(view, normalizedNodeId, runtimeNode); + } + + async revealCanvasNode(canvasPath, nodeId) { + const normalizedCanvasPath = String(canvasPath || "").trim(); + const normalizedNodeId = String(nodeId || "").trim(); + if (!normalizedCanvasPath || !normalizedNodeId) { + return false; + } + + const abstractFile = this.app.vault.getAbstractFileByPath(normalizedCanvasPath); + if (!(abstractFile instanceof TFile) || abstractFile.extension !== "canvas") { + return false; + } + + const activeLeaf = this.app.workspace.activeLeaf; + const reusableLeaf = this.app.workspace.getMostRecentLeaf(); + const leaf = this.findCanvasLeafByPath(normalizedCanvasPath) + || (activeLeaf?.view?.getViewType?.() === "canvas" ? activeLeaf : null) + || this.app.workspace.getLeavesOfType("canvas")[0] + || (reusableLeaf?.view?.getViewType?.() === VIEW_TYPE ? null : reusableLeaf) + || this.app.workspace.getLeaf(true); + await leaf.openFile(abstractFile); + this.app.workspace.revealLeaf(leaf); + + for (let attempt = 0; attempt < 8; attempt += 1) { + const view = leaf?.view?.getViewType?.() === "canvas" + ? leaf.view + : this.resolver.getActiveCanvasView(); + const viewCanvasPath = String(view?.file?.path || "").trim(); + if (viewCanvasPath === normalizedCanvasPath && this.focusCanvasNodeInView(view, normalizedCanvasPath, normalizedNodeId)) { + return true; + } + + await sleep(attempt === 0 ? 0 : 50); + } + + return false; + } + + async revealCanvasNodeForEditing(canvasPath, nodeId) { + const didReveal = await this.revealCanvasNode(canvasPath, nodeId); + if (!didReveal) { + return false; + } + + for (let attempt = 0; attempt < 8; attempt += 1) { + const view = this.findCanvasLeafByPath(canvasPath)?.view || this.resolver.getActiveCanvasView(); + const viewCanvasPath = String(view?.file?.path || "").trim(); + if (viewCanvasPath === String(canvasPath || "").trim() && await this.enterCanvasNodeEditModeInView(view, nodeId)) { + return true; + } + + await sleep(attempt === 0 ? 0 : 50); + } + + return false; + } + + async revealTaskInCanvas(task) { + const ref = this.getTaskJumpNodeRef(task); + if (!ref) { + return false; + } + + return this.revealCanvasNode(ref.canvasPath, ref.nodeId); + } + + async activateTaskFromList(taskId, options = {}) { + const normalizedTaskId = String(taskId || "").trim(); + const task = this.tasksById[normalizedTaskId] || null; + if (!task) { + return false; + } + + await this.setActiveTask(normalizedTaskId, { + revealInActiveTab: options.revealInActiveTab !== false, + showDetail: options.showDetail === true, + }); + if (options.revealInCanvas === false) { + return true; + } + + return this.revealTaskInCanvas(this.tasksById[normalizedTaskId] || task); + } + + async ensurePanelVisibleOnStartup() { + if (this.app.workspace.getLeavesOfType(VIEW_TYPE).length > 0) { + return; + } + + try { + await this.activateView(); + } catch { + // Ignore startup panel errors and let the manual command remain the fallback. + } + } + + handleCanvasFileMutation(file) { + if (!(file instanceof TFile) || file.extension !== "canvas") { + return; + } + + const canvasPath = String(file.path || "").trim(); + if (!canvasPath) { + return; + } + + const previousSnapshot = this.canvasSnapshotCacheByPath.get(canvasPath) || null; + this.invalidateCanvasSnapshot(canvasPath); + + const activeCanvasPath = this.getActiveCanvasPath(); + const affectsKnownTask = Object.values(this.tasksById).some((task) => this.getTaskCanvasPath(task) === canvasPath); + if (!affectsKnownTask && activeCanvasPath !== canvasPath) { + return; + } + + const nextSnapshot = this.getCanvasSnapshotSync(canvasPath); + const autoRunNodeIds = this.getAutoRunCandidateNodeIds(previousSnapshot, nextSnapshot); + if (autoRunNodeIds.length > 0) { + void this.triggerAutoRunForCanvasNodes(canvasPath, autoRunNodeIds); + } + + this.syncActiveTaskToCanvasContext(); + void this.refreshSelectedGroupContextPreview(); + this.scheduleCanvasNodeHighlightSync(canvasPath); + this.requestViewRefresh(); + } + + hasFocusedComposer() { + return this.app.workspace.getLeavesOfType(VIEW_TYPE).some((leaf) => { + return typeof leaf.view?.hasFocusedComposer === "function" && leaf.view.hasFocusedComposer(); + }); + } + + hasHoveredTaskList() { + return this.app.workspace.getLeavesOfType(VIEW_TYPE).some((leaf) => { + return typeof leaf.view?.hasHoveredTaskList === "function" && leaf.view.hasHoveredTaskList(); + }); + } + + setComposerFocused(isFocused) { + this.composerFocused = Boolean(isFocused); + if (this.composerFocused) { + window.clearTimeout(this.viewRefreshTimer); + return; + } + + if (!this.pendingViewRefresh) { + return; + } + + window.clearTimeout(this.viewRefreshTimer); + this.viewRefreshTimer = window.setTimeout(() => { + if (this.pendingViewRefresh && !this.hasFocusedComposer() && !this.hasHoveredTaskList()) { + this.requestViewRefresh({ force: true }); + } + }, 0); + } + + setTaskListHovered(isHovered) { + this.taskListHovered = Boolean(isHovered); + if (this.taskListHovered) { + window.clearTimeout(this.viewRefreshTimer); + return; + } + + if (!this.pendingViewRefresh) { + return; + } + + window.clearTimeout(this.viewRefreshTimer); + this.viewRefreshTimer = window.setTimeout(() => { + if (this.pendingViewRefresh && !this.hasFocusedComposer() && !this.hasHoveredTaskList()) { + this.requestViewRefresh({ force: true }); + } + }, 0); + } + + requestViewRefresh(options = {}) { + const force = options.force === true; + const streamTask = options.streamTask || null; + if (!force && streamTask) { + const patchedAnyView = this.app.workspace.getLeavesOfType(VIEW_TYPE).some((leaf) => { + return typeof leaf.view?.tryApplyStreamingTaskUpdate === "function" + && leaf.view.tryApplyStreamingTaskUpdate(streamTask); + }); + if (patchedAnyView) { + this.pendingViewRefresh = false; + window.clearTimeout(this.viewRefreshTimer); + return; + } + } + + if (!force && (this.composerFocused || this.hasFocusedComposer() || this.taskListHovered || this.hasHoveredTaskList())) { + this.pendingViewRefresh = true; + window.clearTimeout(this.viewRefreshTimer); + return; + } + + this.pendingViewRefresh = false; + window.clearTimeout(this.viewRefreshTimer); + this.viewRefreshTimer = window.setTimeout(() => { + this.app.workspace.getLeavesOfType(VIEW_TYPE).forEach((leaf) => { + if (leaf.view?.render) { + leaf.view.render(); + } + }); + }, 50); + } + + async refreshTasks() { + try { + const response = await this.api.getTasks({ vaultId: this.vaultId }); + this.runtimeIssue = ""; + const previousTasksById = this.tasksById; + const nextTasksById = {}; + const liveTasksById = this.tasksById; + + for (const rawTask of response.tasks || []) { + const taskId = String(rawTask?.taskId || "").trim(); + if (!taskId) { + continue; + } + + const nextTask = this.normalizeTaskForStore( + rawTask, + previousTasksById[taskId] || liveTasksById[taskId] || null + ); + if (!nextTask) { + continue; + } + + nextTasksById[taskId] = nextTask; + } + + Object.values(liveTasksById).forEach((task) => { + const taskId = String(task?.taskId || "").trim(); + if (!taskId || nextTasksById[taskId] || !this.isTaskForCurrentVault(task)) { + return; + } + nextTasksById[taskId] = task; + }); + + this.tasksById = nextTasksById; + this.pruneUnreadTaskState(); + this.syncActiveTaskToCanvasContext(); + Object.values(this.tasksById).forEach((task) => { + const previousTask = previousTasksById[task.taskId] || null; + this.maybeSyncCanvasNodeHighlights(task, previousTask); + this.maybeSyncTaskResultNode(task, previousTask); + }); + this.reconcileRunningTaskStreams(); + this.reconcileAllCanvasNodeHighlights(); + + await this.persistPluginState(); + this.requestViewRefresh(); + const activeTask = this.getActiveTask(); + if (activeTask && !this.taskHasLoadedMessages(activeTask)) { + void this.hydrateTaskDetails(activeTask.taskId, { force: true }); + } + } catch (error) { + this.runtimeIssue = String(error?.message || error); + this.requestViewRefresh(); + } + } + + async refreshTask(taskId, options = {}) { + if (!taskId) { + return; + } + + try { + const response = await this.api.getTask(taskId); + this.mergeTask(response.task, { + adoptCurrentVault: options.adoptCurrentVault === true, + }); + this.requestViewRefresh(options); + } catch (error) { + this.runtimeIssue = String(error?.message || error); + this.requestViewRefresh(options); + } + } + + async hydrateTaskDetails(taskId, options = {}) { + const normalizedTaskId = String(taskId || "").trim(); + if (!normalizedTaskId) { + return; + } + + const task = this.tasksById[normalizedTaskId] || null; + if (!task || this.taskHasLoadedMessages(task) || this.taskDetailRefreshInFlight.has(normalizedTaskId)) { + return; + } + + this.taskDetailRefreshInFlight.add(normalizedTaskId); + try { + await this.refreshTask(normalizedTaskId, options); + } finally { + this.taskDetailRefreshInFlight.delete(normalizedTaskId); + } + } + + clearActiveStreamReconnectTimer() { + if (this.activeStreamReconnectTimer != null) { + window.clearTimeout(this.activeStreamReconnectTimer); + this.activeStreamReconnectTimer = null; + } + } + + disposeActiveStream() { + this.clearActiveStreamReconnectTimer(); + const disposer = this.activeStreamDisposer; + this.activeStreamDisposer = null; + this.activeStreamTaskId = ""; + this.activeStreamSessionId += 1; + if (typeof disposer === "function") { + disposer(); + } + } + + clearRunningTaskStreamReconnectTimer(taskId) { + const normalizedTaskId = String(taskId || "").trim(); + const existingTimer = normalizedTaskId ? this.runningTaskStreamReconnectTimers.get(normalizedTaskId) : null; + if (existingTimer != null) { + window.clearTimeout(existingTimer); + this.runningTaskStreamReconnectTimers.delete(normalizedTaskId); + } + } + + disposeRunningTaskStream(taskId, options = {}) { + const normalizedTaskId = String(taskId || "").trim(); + if (!normalizedTaskId) { + return; + } + + this.clearRunningTaskStreamReconnectTimer(normalizedTaskId); + const disposer = this.runningTaskStreamDisposers.get(normalizedTaskId) || null; + this.runningTaskStreamDisposers.delete(normalizedTaskId); + if (options.clearSession !== false) { + delete this.runningTaskStreamSessionIds[normalizedTaskId]; + } + if (typeof disposer === "function") { + disposer(); + } + } + + disposeAllRunningTaskStreams() { + Array.from(new Set([ + ...this.runningTaskStreamDisposers.keys(), + ...this.runningTaskStreamReconnectTimers.keys(), + ...Object.keys(this.runningTaskStreamSessionIds || {}), + ])).forEach((taskId) => { + this.disposeRunningTaskStream(taskId); + }); + } + + shouldWatchRunningTaskStream(taskId) { + const normalizedTaskId = String(taskId || "").trim(); + if (!normalizedTaskId || normalizedTaskId === String(this.activeStreamTaskId || "").trim()) { + return false; + } + + const task = this.tasksById[normalizedTaskId] || null; + return Boolean(task) && this.isTaskRunning(task); + } + + isRunningTaskStreamSessionCurrent(taskId, sessionId) { + const normalizedTaskId = String(taskId || "").trim(); + return Boolean(normalizedTaskId) + && this.shouldWatchRunningTaskStream(normalizedTaskId) + && Number(this.runningTaskStreamSessionIds[normalizedTaskId] || 0) === Number(sessionId || 0); + } + + scheduleRunningTaskStreamReconnect(taskId, sessionId) { + const normalizedTaskId = String(taskId || "").trim(); + if (!this.isRunningTaskStreamSessionCurrent(normalizedTaskId, sessionId)) { + return; + } + + this.clearRunningTaskStreamReconnectTimer(normalizedTaskId); + const timer = window.setTimeout(() => { + this.runningTaskStreamReconnectTimers.delete(normalizedTaskId); + if (!this.isRunningTaskStreamSessionCurrent(normalizedTaskId, sessionId)) { + return; + } + + this.connectRunningTaskStream(normalizedTaskId, sessionId); + }, ACTIVE_TASK_STREAM_RECONNECT_DELAY_MS); + this.runningTaskStreamReconnectTimers.set(normalizedTaskId, timer); + } + + connectRunningTaskStream(taskId, sessionId) { + const normalizedTaskId = String(taskId || "").trim(); + if (!this.isRunningTaskStreamSessionCurrent(normalizedTaskId, sessionId)) { + return; + } + + try { + this.runningTaskStreamDisposers.set(normalizedTaskId, this.api.openTaskStream(normalizedTaskId, { + onEvent: (_event, payload) => { + if (!this.isRunningTaskStreamSessionCurrent(normalizedTaskId, sessionId)) { + return; + } + + if (payload?.task) { + this.runtimeIssue = ""; + this.mergeTask(payload.task); + this.requestViewRefresh(); + } + }, + onClose: () => { + if (!this.isRunningTaskStreamSessionCurrent(normalizedTaskId, sessionId)) { + return; + } + + this.runningTaskStreamDisposers.delete(normalizedTaskId); + void this.refreshTask(normalizedTaskId, { force: true }) + .catch(() => {}) + .finally(() => { + this.scheduleRunningTaskStreamReconnect(normalizedTaskId, sessionId); + }); + }, + })); + } catch (error) { + if (!this.isRunningTaskStreamSessionCurrent(normalizedTaskId, sessionId)) { + return; + } + + this.runtimeIssue = String(error?.message || error); + this.requestViewRefresh(); + this.scheduleRunningTaskStreamReconnect(normalizedTaskId, sessionId); + } + } + + ensureRunningTaskStream(taskId) { + const normalizedTaskId = String(taskId || "").trim(); + if (!this.shouldWatchRunningTaskStream(normalizedTaskId)) { + this.disposeRunningTaskStream(normalizedTaskId); + return; + } + + if ( + this.runningTaskStreamDisposers.has(normalizedTaskId) + || this.runningTaskStreamReconnectTimers.has(normalizedTaskId) + ) { + return; + } + + const nextSessionId = Number(this.runningTaskStreamSessionIds[normalizedTaskId] || 0) + 1; + this.runningTaskStreamSessionIds[normalizedTaskId] = nextSessionId; + this.connectRunningTaskStream(normalizedTaskId, nextSessionId); + } + + reconcileRunningTaskStreams() { + const watchedTaskIds = new Set( + this.getTasks() + .filter((task) => this.shouldWatchRunningTaskStream(task?.taskId)) + .map((task) => String(task.taskId || "").trim()) + .filter(Boolean) + ); + + Array.from(new Set([ + ...this.runningTaskStreamDisposers.keys(), + ...this.runningTaskStreamReconnectTimers.keys(), + ...Object.keys(this.runningTaskStreamSessionIds || {}), + ])).forEach((taskId) => { + if (!watchedTaskIds.has(taskId)) { + this.disposeRunningTaskStream(taskId); + } + }); + + watchedTaskIds.forEach((taskId) => { + this.ensureRunningTaskStream(taskId); + }); + } + + isActiveStreamSessionCurrent(taskId, sessionId) { + const normalizedTaskId = String(taskId || "").trim(); + return Boolean(normalizedTaskId) + && normalizedTaskId === String(this.activeStreamTaskId || "").trim() + && normalizedTaskId === String(this.uiState?.activeTaskId || "").trim() + && sessionId === this.activeStreamSessionId; + } + + scheduleActiveStreamReconnect(taskId, sessionId) { + if (!this.isActiveStreamSessionCurrent(taskId, sessionId)) { + return; + } + + this.clearActiveStreamReconnectTimer(); + this.activeStreamReconnectTimer = window.setTimeout(() => { + this.activeStreamReconnectTimer = null; + if (!this.isActiveStreamSessionCurrent(taskId, sessionId)) { + return; + } + + this.connectActiveTaskStream(taskId, sessionId); + }, ACTIVE_TASK_STREAM_RECONNECT_DELAY_MS); + } + + connectActiveTaskStream(taskId, sessionId) { + if (!this.isActiveStreamSessionCurrent(taskId, sessionId)) { + return; + } + + try { + this.activeStreamDisposer = this.api.openTaskStream(taskId, { + onEvent: (_event, payload) => { + if (!this.isActiveStreamSessionCurrent(taskId, sessionId)) { + return; + } + + if (payload?.task) { + this.runtimeIssue = ""; + const task = this.mergeTask(payload.task); + this.requestViewRefresh({ streamTask: task }); + } + }, + onClose: () => { + if (!this.isActiveStreamSessionCurrent(taskId, sessionId)) { + return; + } + + this.activeStreamDisposer = null; + void this.refreshTask(taskId, { force: true }) + .catch(() => {}) + .finally(() => { + this.scheduleActiveStreamReconnect(taskId, sessionId); + }); + }, + }); + } catch (error) { + if (!this.isActiveStreamSessionCurrent(taskId, sessionId)) { + return; + } + + this.runtimeIssue = String(error?.message || error); + this.requestViewRefresh(); + this.scheduleActiveStreamReconnect(taskId, sessionId); + } + } + + subscribeToTask(taskId) { + this.disposeActiveStream(); + if (!taskId) { + this.reconcileRunningTaskStreams(); + return; + } + + this.activeStreamTaskId = String(taskId || "").trim(); + this.activeStreamSessionId += 1; + this.connectActiveTaskStream(this.activeStreamTaskId, this.activeStreamSessionId); + this.reconcileRunningTaskStreams(); + } + + async setActiveTask(taskId, options = {}) { + const normalizedTaskId = String(taskId || "").trim() || null; + this.uiState.activeTaskId = normalizedTaskId; + if (normalizedTaskId && options.markRead !== false) { + this.updateTaskUnreadState(normalizedTaskId, false); + } + if (options.revealInActiveTab) { + this.uiState.panelTab = PANEL_TAB_OPTIONS.TASK_LIST; + } + this.uiState.taskDetailOpen = options.showDetail === true; + await this.persistPluginState(); + this.subscribeToTask(normalizedTaskId); + this.requestViewRefresh(); + void this.hydrateTaskDetails(normalizedTaskId, { force: true }); + } + + async handleNewThreadFromSelectionCommand() { + await this.runActiveSelection({ forceNewTask: true }); + } + + async runActiveSelection(options = {}) { + const selection = await this.resolveSelectionForAction(); + return this.runResolvedSelection(selection, options); + } + + async runResolvedSelection(selection, options = {}) { + try { + let effectiveSelection = selection; + if (options.forceNewTask === true) { + effectiveSelection = await this.resolver.addImplicitCanvasFileContext(selection); + const followUpResult = await this.runSelectionAsFollowUp(effectiveSelection); + if (followUpResult) { + if (followUpResult.cancelled) { + await this.appendDebugEvent("follow_up_cancelled", { + taskId: followUpResult.task?.taskId || "", + sourceNodeId: effectiveSelection.nodeIds?.[0] || "", + }); + return; + } + await this.appendDebugEvent("follow_up_requested", { + taskId: followUpResult.task.taskId, + message: followUpResult.message, + sourceNodeId: effectiveSelection.nodeIds?.[0] || "", + mode: followUpResult.mode || "follow-up", + }); + this.runtimeIssue = ""; + await this.setActiveTask(followUpResult.task.taskId, { revealInActiveTab: true }); + new Notice( + followUpResult.mode === "fork" + ? "Forked this Canvas branch into a new OpenAgent thread." + : "Sent this selection to the existing OpenAgent thread." + ); + await this.refreshTask(followUpResult.task.taskId, { force: true }); + return; + } + } + + this.rememberSelectionSnapshot(effectiveSelection); + await this.appendDebugEvent("selection_resolved", { + mode: options.forceNewTask === true ? "new-thread" : "selection", + canvasPath: effectiveSelection.canvasPath, + nodeIds: effectiveSelection.nodeIds, + textCount: effectiveSelection.textBlocks?.length || 0, + fileCount: effectiveSelection.markdownFiles?.length || 0, + imageCount: effectiveSelection.imageFiles?.length || 0, + }); + const selectionSummary = this.summarizeSelection(effectiveSelection); + const inferredCwd = this.resolveWorkspaceRepoPathForSelection(effectiveSelection) || this.resolver.inferWorkingDirectory(effectiveSelection); + await this.appendDebugEvent("raw_prompt_prepared", { + promptPreview: options.forceNewTask === true + ? this.truncateInline(this.getNewThreadPromptFromSelection(effectiveSelection, { cwd: inferredCwd }), 180) + : "", + cwd: inferredCwd, + }); + await this.activateView(); + + const rawPrompt = this.getNewThreadPromptFromSelection(effectiveSelection, { cwd: inferredCwd }); + const response = await this.api.createTaskFromCanvasSelection(this.withVaultScope({ + ...effectiveSelection, + cwd: inferredCwd, + forceNewTask: options.forceNewTask === true, + runtimeConfig: this.buildRuntimeConfigPayload(), + })); + const task = this.mergeTask(response.task, { adoptCurrentVault: true }); + await this.appendDebugEvent("task_created", { + taskId: task.taskId, + threadId: task.threadId || "", + cwd: task.cwd, + selectionKey: task.selectionKey, + }); + this.runtimeIssue = ""; + await this.setActiveTask(task.taskId, { revealInActiveTab: true }); + + if (!task.cwd) { + new Notice( + selectionSummary + ? `${selectionSummary}. Set a working directory in the OpenAgent panel to continue.` + : "Set a working directory in the OpenAgent panel to continue." + ); + return; + } + + new Notice( + options.forceNewTask === true + ? "Starting a new OpenAgent thread." + : "OpenAgent is ready for this selection." + ); + + const runResponse = await this.api.runTask(task.taskId, { + rawPrompt, + transcriptMessage: rawPrompt, + forceContext: false, + runtimeConfig: this.buildRuntimeConfigPayload(), + }); + this.mergeTask(runResponse.task, { adoptCurrentVault: true }); + await this.appendDebugEvent("run_requested", { + taskId: task.taskId, + rawPrompt, + }); + await this.refreshTask(task.taskId); + const refreshedTask = this.tasksById[task.taskId] || task; + await this.appendDebugEvent("task_refreshed", { + taskId: refreshedTask.taskId, + threadId: refreshedTask.threadId || "", + status: refreshedTask.status || "", + lastMessage: Array.isArray(refreshedTask.messages) && refreshedTask.messages.length > 0 + ? String(refreshedTask.messages[refreshedTask.messages.length - 1]?.text || "") + : "", + }); + } catch (error) { + this.runtimeIssue = String(error?.message || error); + await this.appendDebugEvent("new_thread_error", { + message: this.runtimeIssue, + }); + new Notice(this.runtimeIssue); + this.requestViewRefresh(); + } + } + + async resolveSelectionForAction() { + try { + const selection = await this.resolver.resolveActiveSelection(); + this.rememberSelectionSnapshot(selection); + return selection; + } catch (error) { + if (!this.isMissingSelectionError(error)) { + throw error; + } + + const fallbackSelection = await this.resolveRecentSelectionSnapshot(); + this.rememberSelectionSnapshot(fallbackSelection); + new Notice("Using the most recent Canvas selection."); + return fallbackSelection; + } + } + + isMissingSelectionError(error) { + const message = String(error?.message || error || ""); + return message.includes("Select one or more Canvas nodes first."); + } + + scheduleCanvasSelectionSnapshotCapture(view = null, options = {}) { + const delayMs = Math.max(0, toFiniteNumber(options.delayMs, 0)); + window.clearTimeout(this.canvasSelectionCaptureTimer); + this.canvasSelectionCaptureTimer = window.setTimeout(() => { + this.canvasSelectionCaptureTimer = null; + this.captureCanvasSelectionSnapshot(view); + }, delayMs); + } + + captureCanvasSelectionSnapshot(viewOverride = null) { + try { + const view = viewOverride?.getViewType?.() === "canvas" + ? viewOverride + : this.resolver.getActiveCanvasView(); + if (!view) { + return; + } + + this.ensureCanvasSelectionTrackingPatched(view); + this.ensureCanvasPopupMenuPatched(view); + this.syncCanvasPopupActionButtons(view); + + const file = view.file || this.app.workspace.getActiveFile(); + if (!(file instanceof TFile) || file.extension !== "canvas") { + return; + } + + const nodeIds = this.resolver.extractSelectedNodeIds(view); + const previousSnapshot = this.lastCanvasSelectionSnapshot; + if (nodeIds.length === 0) { + const didClearSelection = ( + previousSnapshot?.canvasPath === file.path + && Array.isArray(previousSnapshot?.nodeIds) + && previousSnapshot.nodeIds.length > 0 + ); + this.lastCanvasSelectionSnapshot = { + canvasPath: file.path, + nodeIds: [], + selectionIdentity: "", + capturedAt: Date.now(), + selectedNodeType: "", + selectedNodeLabel: "", + }; + if (didClearSelection) { + this.syncActiveTaskToCanvasContext(); + void this.refreshSelectedGroupContextPreview({ force: true }); + this.requestViewRefresh(); + } + return; + } + + const liveNodeDataById = this.resolver.extractLiveSelectedNodeDataById(view, nodeIds); + const selectedNode = nodeIds.length === 1 ? liveNodeDataById.get(String(nodeIds[0] || "")) || null : null; + const nextSnapshot = { + canvasPath: file.path, + nodeIds: Array.from(new Set(nodeIds.map((nodeId) => String(nodeId)).filter(Boolean))).sort(), + selectionIdentity: this.buildTaskSelectionIdentityFromNodeIds(file.path, nodeIds), + capturedAt: Date.now(), + selectedNodeType: String(selectedNode?.type || "").trim(), + selectedNodeLabel: String(selectedNode?.label || "").trim(), + }; + const selectionUnchanged = ( + previousSnapshot?.canvasPath === nextSnapshot.canvasPath + && previousSnapshot?.selectionIdentity === nextSnapshot.selectionIdentity + && Array.isArray(previousSnapshot?.nodeIds) + && previousSnapshot.nodeIds.length === nextSnapshot.nodeIds.length + && previousSnapshot.nodeIds.every((nodeId, index) => nodeId === nextSnapshot.nodeIds[index]) + && String(previousSnapshot?.selectedNodeType || "") === nextSnapshot.selectedNodeType + && String(previousSnapshot?.selectedNodeLabel || "") === nextSnapshot.selectedNodeLabel + ); + this.lastCanvasSelectionSnapshot = nextSnapshot; + if (!selectionUnchanged) { + this.syncActiveTaskToCanvasContext({ markReadOnSelection: true }); + void this.refreshSelectedGroupContextPreview({ snapshot: nextSnapshot, force: true }); + this.requestViewRefresh(); + } + } catch { + // Ignore transient Canvas introspection failures during selection sync. + } + } + + async resolveRecentSelectionSnapshot() { + const snapshot = this.lastCanvasSelectionSnapshot; + if (!snapshot?.canvasPath || !Array.isArray(snapshot.nodeIds) || snapshot.nodeIds.length === 0) { + throw new Error("Select one or more Canvas nodes first."); + } + + if (Date.now() - Number(snapshot.capturedAt || 0) > RECENT_SELECTION_TTL_MS) { + throw new Error("Select one or more Canvas nodes first."); + } + + const abstractFile = this.app.vault.getAbstractFileByPath(snapshot.canvasPath); + if (!(abstractFile instanceof TFile) || abstractFile.extension !== "canvas") { + throw new Error("The recent Canvas selection is no longer available."); + } + + return this.resolver.resolveCanvasSelection(abstractFile, snapshot.nodeIds); + } + + rememberSelectionSnapshot(selection) { + const canvasPath = String(selection?.canvasPath || "").trim(); + const nodeIds = Array.isArray(selection?.nodeIds) ? selection.nodeIds.map((nodeId) => String(nodeId)).filter(Boolean) : []; + if (!canvasPath || nodeIds.length === 0) { + return; + } + + this.lastCanvasSelectionSnapshot = { + canvasPath, + nodeIds: Array.from(new Set(nodeIds)).sort(), + selectionIdentity: this.buildTaskSelectionIdentityFromNodeIds(canvasPath, nodeIds), + capturedAt: Date.now(), + selectedNodeType: "", + selectedNodeLabel: "", + }; + this.syncActiveTaskToCanvasContext(); + void this.refreshSelectedGroupContextPreview({ force: true }); + } + + getRecentSelectionSnapshotForActiveCanvas() { + const snapshot = this.lastCanvasSelectionSnapshot; + const activeCanvasPath = this.getActiveCanvasPath(); + if (!snapshot?.canvasPath || !activeCanvasPath || snapshot.canvasPath !== activeCanvasPath) { + return null; + } + + const nodeIds = Array.isArray(snapshot.nodeIds) + ? snapshot.nodeIds.map((nodeId) => String(nodeId)).filter(Boolean) + : []; + if (nodeIds.length === 0) { + return null; + } + + if (Date.now() - Number(snapshot.capturedAt || 0) > RECENT_SELECTION_TTL_MS) { + return null; + } + + return { + ...snapshot, + nodeIds, + }; + } + + buildSelectedGroupContextPreviewKey(snapshot = null) { + const targetSnapshot = snapshot || this.getRecentSelectionSnapshotForActiveCanvas(); + if (!targetSnapshot?.canvasPath || !Array.isArray(targetSnapshot.nodeIds) || targetSnapshot.nodeIds.length !== 1) { + return ""; + } + + if (String(targetSnapshot.selectedNodeType || "").trim() !== "group") { + return ""; + } + + return `${targetSnapshot.canvasPath}::${targetSnapshot.nodeIds[0]}`; + } + + buildLoadingSelectedGroupContextPreview(snapshot) { + if (!snapshot?.canvasPath) { + return null; + } + + return { + status: "loading", + canvasPath: snapshot.canvasPath, + groupNodeId: Array.isArray(snapshot.nodeIds) ? String(snapshot.nodeIds[0] || "").trim() : "", + groupLabel: String(snapshot.selectedNodeLabel || "").trim() || "Untitled group", + textBlocks: [], + markdownFiles: [], + warnings: [], + }; + } + + async readCanvasMarkdownFilePreviewEntry(node, warnings = []) { + const filePath = typeof node?.file === "string" ? node.file.trim() : ""; + const abstractFile = await this.resolver.waitForCanvasFile(filePath, { timeoutMs: 0 }); + if (!(abstractFile instanceof TFile)) { + warnings.push(`Missing file node target: ${filePath}`); + return null; + } + + if (abstractFile.extension !== "md") { + warnings.push(`Unsupported file node type skipped: ${filePath}`); + return null; + } + + const vaultBasePath = this.getVaultBasePath(); + return { + id: String(node?.id || ""), + path: abstractFile.path, + absolutePath: vaultBasePath + ? this.resolver.resolveVaultPath(vaultBasePath, abstractFile.path) + : "", + name: abstractFile.basename, + }; + } + + async buildSelectedGroupContextPreview(snapshot) { + const canvasPath = String(snapshot?.canvasPath || "").trim(); + const groupNodeId = Array.isArray(snapshot?.nodeIds) ? String(snapshot.nodeIds[0] || "").trim() : ""; + if (!canvasPath || !groupNodeId) { + return null; + } + + const abstractFile = this.app.vault.getAbstractFileByPath(canvasPath); + if (!(abstractFile instanceof TFile) || abstractFile.extension !== "canvas") { + return null; + } + + let parsed; + try { + parsed = JSON.parse(await this.app.vault.cachedRead(abstractFile)); + } catch { + return null; + } + + const nodeById = new Map( + (Array.isArray(parsed?.nodes) ? parsed.nodes : []).map((node) => [String(node?.id || "").trim(), node]) + ); + const selectedNode = nodeById.get(groupNodeId) || null; + if (String(selectedNode?.type || "") !== "group") { + return null; + } + + const containedNodes = Array.from(nodeById.values()) + .filter((node) => { + const nodeId = String(node?.id || "").trim(); + return nodeId && nodeId !== groupNodeId && this.resolver.doesCanvasGroupContainNode(selectedNode, node); + }) + .sort(compareCanvasNodeOrder); + + const textBlocks = []; + const markdownFiles = []; + const warnings = []; + const previewEntries = await Promise.all(containedNodes.map(async (node) => { + const nodeId = String(node?.id || "").trim(); + if (!nodeId) { + return null; + } + + if (String(node?.type || "") === "text") { + const text = typeof node?.text === "string" ? node.text.trim() : ""; + return text ? { kind: "text", value: { id: nodeId, text } } : null; + } + + if (String(node?.type || "") === "file") { + const nodeWarnings = []; + const markdownFile = await this.readCanvasMarkdownFilePreviewEntry(node, nodeWarnings); + return { + kind: "file", + value: markdownFile, + warnings: nodeWarnings, + }; + } + + return null; + })); + + previewEntries.forEach((entry) => { + if (!entry) { + return; + } + + if (entry.kind === "text" && entry.value) { + textBlocks.push(entry.value); + } + + if (entry.kind === "file" && entry.value) { + markdownFiles.push(entry.value); + } + + if (Array.isArray(entry.warnings) && entry.warnings.length > 0) { + warnings.push(...entry.warnings); + } + }); + + return { + status: "ready", + canvasPath, + groupNodeId, + groupLabel: String(selectedNode?.label || "").trim() || String(snapshot?.selectedNodeLabel || "").trim() || "Untitled group", + textBlocks, + markdownFiles, + warnings, + }; + } + + async refreshSelectedGroupContextPreview(options = {}) { + const snapshot = options.snapshot || this.getRecentSelectionSnapshotForActiveCanvas(); + const nextKey = this.buildSelectedGroupContextPreviewKey(snapshot); + if (!nextKey) { + const didChange = this.selectedGroupContextPreviewState.key || this.selectedGroupContextPreviewState.preview || this.selectedGroupContextPreviewState.loading; + this.selectedGroupContextPreviewState = { + key: "", + preview: null, + loading: false, + }; + if (didChange) { + this.requestViewRefresh(); + } + return null; + } + + if ( + options.force !== true + && this.selectedGroupContextPreviewState.key === nextKey + && (this.selectedGroupContextPreviewState.loading || this.selectedGroupContextPreviewState.preview) + ) { + return this.selectedGroupContextPreviewState.preview; + } + + this.selectedGroupContextPreviewState = { + key: nextKey, + preview: this.buildLoadingSelectedGroupContextPreview(snapshot), + loading: true, + }; + this.requestViewRefresh(); + + const loadPromise = this.buildSelectedGroupContextPreview(snapshot) + .then((preview) => { + if (this.selectedGroupContextPreviewState.key !== nextKey) { + return preview; + } + + this.selectedGroupContextPreviewState = { + key: nextKey, + preview, + loading: false, + }; + this.requestViewRefresh(); + return preview; + }) + .catch(() => { + if (this.selectedGroupContextPreviewState.key === nextKey) { + this.selectedGroupContextPreviewState = { + key: nextKey, + preview: null, + loading: false, + }; + this.requestViewRefresh(); + } + return null; + }) + .finally(() => { + if (this.selectedGroupContextPreviewLoadPromise === loadPromise) { + this.selectedGroupContextPreviewLoadPromise = null; + } + }); + + this.selectedGroupContextPreviewLoadPromise = loadPromise; + return loadPromise; + } + + handleSelectedGroupContextDependencyMutation(file, oldPath = "") { + const snapshot = this.getRecentSelectionSnapshotForActiveCanvas(); + const previewKey = this.buildSelectedGroupContextPreviewKey(snapshot); + if (!previewKey) { + return; + } + + const nextPath = file instanceof TFile ? String(file.path || "").trim() : ""; + const normalizedOldPath = String(oldPath || "").trim(); + const preview = this.selectedGroupContextPreviewState.preview; + const dependsOnCurrentFile = [nextPath, normalizedOldPath].some((candidatePath) => { + if (!candidatePath) { + return false; + } + + if (candidatePath === snapshot.canvasPath) { + return true; + } + + return Array.isArray(preview?.markdownFiles) && preview.markdownFiles.some((markdownFile) => markdownFile?.path === candidatePath); + }); + if (!dependsOnCurrentFile) { + return; + } + + void this.refreshSelectedGroupContextPreview({ snapshot, force: true }); + } + + getSelectedGroupContextPreview() { + const snapshot = this.getRecentSelectionSnapshotForActiveCanvas(); + const previewKey = this.buildSelectedGroupContextPreviewKey(snapshot); + if (!previewKey) { + return null; + } + + if (this.selectedGroupContextPreviewState.key !== previewKey) { + void this.refreshSelectedGroupContextPreview({ snapshot }); + return this.buildLoadingSelectedGroupContextPreview(snapshot); + } + + return this.selectedGroupContextPreviewState.preview || this.buildLoadingSelectedGroupContextPreview(snapshot); + } + + getActiveCanvasPath() { + const view = this.resolver.getActiveCanvasView(); + const file = view?.file || this.app.workspace.getActiveFile(); + return file instanceof TFile && file.extension === "canvas" ? file.path : ""; + } + + getCanvasAbsolutePath(canvasPath) { + const normalizedCanvasPath = String(canvasPath || "").trim(); + const vaultBasePath = this.getVaultBasePath(); + if (!normalizedCanvasPath || !vaultBasePath) { + return ""; + } + + return path.join(vaultBasePath, ...normalizedCanvasPath.split("/")); + } + + async autoArrangeActiveCanvas() { + try { + const view = this.resolver.getActiveCanvasView(); + if (!view) { + throw new Error("Open a Canvas view first."); + } + + const file = view.file || this.app.workspace.getActiveFile(); + if (!(file instanceof TFile) || file.extension !== "canvas") { + throw new Error("The active view is not backed by a .canvas file."); + } + + await this.resolver.flushPendingCanvasEdits(view); + const result = await this.withSerializedCanvasMutation(file.path, async () => { + const raw = await this.app.vault.cachedRead(file); + let parsed; + try { + parsed = JSON.parse(raw); + } catch { + throw new Error(`Unable to parse canvas JSON for ${file.path}`); + } + + const nodes = Array.isArray(parsed?.nodes) ? parsed.nodes : []; + const edges = Array.isArray(parsed?.edges) ? parsed.edges : []; + if (nodes.length === 0) { + throw new Error("This Canvas has no nodes to arrange."); + } + + const arrangedNodes = autoArrangeCanvasNodes(nodes, edges); + const changed = arrangedNodes.some((node, index) => { + const previousNode = nodes[index] || {}; + return ( + toFiniteNumber(node?.x, 0) !== toFiniteNumber(previousNode?.x, 0) + || toFiniteNumber(node?.y, 0) !== toFiniteNumber(previousNode?.y, 0) + ); + }); + if (!changed) { + return { changed: false, parsed }; + } + + const nextParsed = { + ...parsed, + nodes: arrangedNodes, + edges, + }; + await this.app.vault.modify(file, JSON.stringify(nextParsed, null, 2) + "\n"); + this.primeCanvasSnapshot(file.path, nextParsed); + return { + changed: true, + parsed: nextParsed, + nodeCount: arrangedNodes.length, + }; + }); + + this.runtimeIssue = ""; + this.scheduleCanvasNodeHighlightSync(file.path); + this.requestViewRefresh({ force: true }); + await this.appendDebugEvent("canvas_auto_arranged", { + canvasPath: file.path, + changed: result.changed, + nodeCount: result.nodeCount || 0, + }); + new Notice(result.changed + ? "Arranged Canvas nodes without changing edges." + : "Canvas nodes are already arranged."); + } catch (error) { + this.runtimeIssue = String(error?.message || error); + await this.appendDebugEvent("canvas_auto_arrange_error", { + message: this.runtimeIssue, + }); + new Notice(this.runtimeIssue); + this.requestViewRefresh({ force: true }); + } + } + + buildCanvasSnapshot(parsed) { + const safeParsed = parsed && typeof parsed === "object" ? parsed : {}; + const nodes = Array.isArray(safeParsed.nodes) ? safeParsed.nodes : []; + const edges = Array.isArray(safeParsed.edges) ? safeParsed.edges : []; + const nodeIds = new Set(); + const nodeById = new Map(); + const incomingEdgesByNodeId = new Map(); + const resultSourceNodeIdByResultNodeId = new Map(); + + nodes.forEach((node) => { + const nodeId = String(node?.id || "").trim(); + if (!nodeId) { + return; + } + + nodeIds.add(nodeId); + nodeById.set(nodeId, node); + + const sourceNodeId = getOpenAgentResultSourceNodeId(node); + if (sourceNodeId) { + resultSourceNodeIdByResultNodeId.set(nodeId, sourceNodeId); + } + }); + + edges.forEach((edge) => { + const fromNodeId = String(edge?.fromNode || "").trim(); + const toNodeId = String(edge?.toNode || "").trim(); + if (!fromNodeId || !toNodeId) { + return; + } + + const incomingEdges = incomingEdgesByNodeId.get(toNodeId) || []; + incomingEdges.push(edge); + incomingEdgesByNodeId.set(toNodeId, incomingEdges); + + if (toNodeId.startsWith("oa-result-") && !resultSourceNodeIdByResultNodeId.has(toNodeId)) { + resultSourceNodeIdByResultNodeId.set(toNodeId, fromNodeId); + } + }); + + return { + parsed: safeParsed, + nodeIds, + nodeById, + incomingEdgesByNodeId, + resultSourceNodeIdByResultNodeId, + }; + } + + primeCanvasSnapshot(canvasPath, parsed) { + const normalizedCanvasPath = String(canvasPath || "").trim(); + if (!normalizedCanvasPath) { + return null; + } + + const snapshot = this.buildCanvasSnapshot(parsed); + this.canvasSnapshotCacheByPath.set(normalizedCanvasPath, snapshot); + return snapshot; + } + + invalidateCanvasSnapshot(canvasPath) { + const normalizedCanvasPath = String(canvasPath || "").trim(); + if (!normalizedCanvasPath) { + return; + } + + this.canvasSnapshotCacheByPath.delete(normalizedCanvasPath); + } + + getCanvasSnapshotSync(canvasPath) { + const normalizedCanvasPath = String(canvasPath || "").trim(); + if (!normalizedCanvasPath) { + return null; + } + + if (this.canvasSnapshotCacheByPath.has(normalizedCanvasPath)) { + return this.canvasSnapshotCacheByPath.get(normalizedCanvasPath); + } + + const absoluteCanvasPath = this.getCanvasAbsolutePath(normalizedCanvasPath); + if (!absoluteCanvasPath || !fs.existsSync(absoluteCanvasPath)) { + this.canvasSnapshotCacheByPath.set(normalizedCanvasPath, null); + return null; + } + + try { + const parsed = JSON.parse(fs.readFileSync(absoluteCanvasPath, "utf8")); + return this.primeCanvasSnapshot(normalizedCanvasPath, parsed); + } catch { + this.canvasSnapshotCacheByPath.set(normalizedCanvasPath, null); + return null; + } + } + + normalizeCanvasNodeColorValue(value) { + return String(value || "").trim().toLowerCase(); + } + + isAutoRunTriggerCanvasNodeColor(value) { + const normalizedValue = this.normalizeCanvasNodeColorValue(value); + return normalizedValue !== "" && AUTO_RUN_TRIGGER_CANVAS_NODE_COLORS.includes(normalizedValue); + } + + isAutoRunnableCanvasNode(node) { + if (!node || typeof node !== "object" || isOpenAgentAssistantResultNode(node)) { + return false; + } + + const nodeType = String(node?.type || "").trim(); + return nodeType === "text" || nodeType === "file"; + } + + getAutoRunCandidateNodeIds(previousSnapshot, nextSnapshot) { + if (!nextSnapshot?.nodeById) { + return []; + } + + const nodeIds = Array.from(nextSnapshot.nodeById.keys()).filter((nodeId) => { + const nextNode = nextSnapshot.nodeById.get(nodeId); + if (!this.isAutoRunnableCanvasNode(nextNode)) { + return false; + } + + const nextColor = this.normalizeCanvasNodeColorValue(nextNode?.color); + if (!this.isAutoRunTriggerCanvasNodeColor(nextColor)) { + return false; + } + + const previousColor = this.normalizeCanvasNodeColorValue(previousSnapshot?.nodeById?.get(nodeId)?.color); + return !this.isAutoRunTriggerCanvasNodeColor(previousColor); + }); + + return nodeIds.sort((leftNodeId, rightNodeId) => { + return compareCanvasNodeOrder( + nextSnapshot.nodeById.get(leftNodeId), + nextSnapshot.nodeById.get(rightNodeId) + ); + }); + } + + async triggerAutoRunForCanvasNodes(canvasPath, nodeIds) { + const normalizedCanvasPath = String(canvasPath || "").trim(); + const normalizedNodeIds = Array.from(new Set( + (Array.isArray(nodeIds) ? nodeIds : []) + .map((nodeId) => String(nodeId || "").trim()) + .filter(Boolean) + )); + if (!normalizedCanvasPath || normalizedNodeIds.length === 0) { + return; + } + + const abstractFile = this.app.vault.getAbstractFileByPath(normalizedCanvasPath); + if (!(abstractFile instanceof TFile) || abstractFile.extension !== "canvas") { + return; + } + + for (const nodeId of normalizedNodeIds) { + const autoRunKey = this.getCanvasNodeHighlightKey(normalizedCanvasPath, nodeId); + if (!autoRunKey || this.canvasAutoRunInFlightByKey.has(autoRunKey)) { + continue; + } + + const existingTask = this.findTaskByCanvasRunSourceNode(normalizedCanvasPath, nodeId) + || this.findTaskByRootCanvasNode(normalizedCanvasPath, nodeId); + if (existingTask && this.isTaskRunning(existingTask)) { + continue; + } + + this.canvasAutoRunInFlightByKey.add(autoRunKey); + try { + const selection = await this.resolver.resolveCanvasSelection(abstractFile, [nodeId]); + await this.appendDebugEvent("canvas_auto_run_triggered", { + canvasPath: normalizedCanvasPath, + nodeId, + title: selection.title, + }); + this.rememberSelectionSnapshot(selection); + await this.runResolvedSelection(selection, { forceNewTask: true }); + } catch (error) { + const message = String(error?.message || error); + await this.appendDebugEvent("canvas_auto_run_error", { + canvasPath: normalizedCanvasPath, + nodeId, + message, + }); + new Notice(`Auto-run failed for Canvas node ${nodeId}: ${message}`); + } finally { + this.canvasAutoRunInFlightByKey.delete(autoRunKey); + } + } + } + + async withSerializedCanvasMutation(canvasPath, operation) { + const normalizedCanvasPath = String(canvasPath || "").trim(); + if (!normalizedCanvasPath) { + return operation(); + } + + const previous = this.canvasMutationQueueByPath.get(normalizedCanvasPath) || Promise.resolve(); + const next = previous + .catch(() => {}) + .then(() => operation()); + this.canvasMutationQueueByPath.set(normalizedCanvasPath, next); + + try { + return await next; + } finally { + if (this.canvasMutationQueueByPath.get(normalizedCanvasPath) === next) { + this.canvasMutationQueueByPath.delete(normalizedCanvasPath); + } + } + } + + getConversationSelectionNodeIds(canvasPath, nodeIds) { + const normalizedCanvasPath = String(canvasPath || "").trim(); + const normalizedNodeIds = Array.from(new Set( + (Array.isArray(nodeIds) ? nodeIds : []) + .map((nodeId) => String(nodeId || "").trim()) + .filter(Boolean) + )).sort(); + if (!normalizedCanvasPath || normalizedNodeIds.length === 0) { + return []; + } + + if (!normalizedNodeIds.some((nodeId) => nodeId.startsWith("oa-result-"))) { + return normalizedNodeIds; + } + + const snapshot = this.getCanvasSnapshotSync(normalizedCanvasPath); + if (!snapshot) { + return normalizedNodeIds; + } + + return Array.from(new Set( + normalizedNodeIds.map((nodeId) => snapshot.resultSourceNodeIdByResultNodeId.get(nodeId) || nodeId) + )).sort(); + } + + buildTaskSelectionIdentityFromNodeIds(canvasPath, nodeIds) { + const normalizedCanvasPath = String(canvasPath || "").trim(); + const canonicalNodeIds = this.getConversationSelectionNodeIds(normalizedCanvasPath, nodeIds); + if (!normalizedCanvasPath || canonicalNodeIds.length === 0) { + return ""; + } + + return `${normalizedCanvasPath}\n${canonicalNodeIds.join("\n")}`; + } + + getTaskCanvasPath(task) { + return String(task?.canvasBinding?.canvasPath || task?.selectionContext?.canvasPath || task?.sourceRef || "").trim(); + } + + getTaskSelectionIdentity(task) { + const canvasPath = this.getTaskCanvasPath(task); + const nodeIds = Array.isArray(task?.selectionContext?.nodeIds) + ? task.selectionContext.nodeIds.map((nodeId) => String(nodeId)).filter(Boolean).sort() + : []; + return this.buildTaskSelectionIdentityFromNodeIds(canvasPath, nodeIds); + } + + getRecentSelectionIdentityForCanvas(canvasPath) { + const snapshot = this.lastCanvasSelectionSnapshot; + if (!canvasPath || !snapshot || snapshot.canvasPath !== canvasPath) { + return ""; + } + + if (Date.now() - Number(snapshot.capturedAt || 0) > RECENT_SELECTION_TTL_MS) { + return ""; + } + + const nodeIds = Array.isArray(snapshot.nodeIds) + ? snapshot.nodeIds.map((nodeId) => String(nodeId)).filter(Boolean).sort() + : []; + if (nodeIds.length === 0) { + return ""; + } + + return snapshot.selectionIdentity || this.buildTaskSelectionIdentityFromNodeIds(canvasPath, nodeIds); + } + + resolveFollowUpTaskForCanvasNodeIds(canvasPath, nodeIds) { + const normalizedCanvasPath = String(canvasPath || "").trim(); + const normalizedNodeIds = Array.from(new Set( + (Array.isArray(nodeIds) ? nodeIds : []) + .map((nodeId) => String(nodeId || "").trim()) + .filter(Boolean) + )).sort(); + if (!normalizedCanvasPath || normalizedNodeIds.length !== 1) { + return null; + } + + const selectedNodeId = normalizedNodeIds[0]; + if (!selectedNodeId) { + return null; + } + + if (selectedNodeId.startsWith("oa-result-")) { + return this.findTaskByOpenAgentResultNode(normalizedCanvasPath, selectedNodeId); + } + + const snapshot = this.getCanvasSnapshotSync(normalizedCanvasPath); + if (!snapshot) { + return null; + } + + const selectedNode = snapshot.nodeById.get(selectedNodeId); + if (!selectedNode || String(selectedNode?.type || "").trim() !== "text") { + return null; + } + + for (const edge of snapshot.incomingEdgesByNodeId.get(selectedNodeId) || []) { + const upstreamNodeId = String(edge?.fromNode || "").trim(); + const task = this.findConversationTaskForUpstreamNode(normalizedCanvasPath, upstreamNodeId); + if (task) { + return task; + } + } + + return null; + } + + getRecentSelectionTaskForCanvas(canvasPath) { + const snapshot = this.lastCanvasSelectionSnapshot; + if (!canvasPath || !snapshot || snapshot.canvasPath !== canvasPath) { + return null; + } + + if (Date.now() - Number(snapshot.capturedAt || 0) > RECENT_SELECTION_TTL_MS) { + return null; + } + + return this.resolveFollowUpTaskForCanvasNodeIds(canvasPath, snapshot.nodeIds); + } + + hasExplicitCanvasDeselection(canvasPath) { + const normalizedCanvasPath = String(canvasPath || "").trim(); + const snapshot = this.lastCanvasSelectionSnapshot; + if (!normalizedCanvasPath || !snapshot || snapshot.canvasPath !== normalizedCanvasPath) { + return false; + } + + if (Date.now() - Number(snapshot.capturedAt || 0) > RECENT_SELECTION_TTL_MS) { + return false; + } + + const nodeIds = Array.isArray(snapshot.nodeIds) + ? snapshot.nodeIds.map((nodeId) => String(nodeId)).filter(Boolean) + : []; + return nodeIds.length === 0; + } + + syncActiveTaskToCanvasContext(options = {}) { + const activeCanvasPath = this.getActiveCanvasPath(); + if (!activeCanvasPath) { + return; + } + + const archiveProbeCache = new Map(); + const visibleTasks = this.getVisibleTasks({ archiveProbeCache }); + const archivedTasks = this.getArchivedTasks({ archiveProbeCache }); + const selectedIdentity = this.getRecentSelectionIdentityForCanvas(activeCanvasPath); + const currentTask = this.tasksById[this.uiState.activeTaskId] || null; + let nextTask = null; + + if (selectedIdentity) { + nextTask = visibleTasks.find((task) => this.getTaskSelectionIdentity(task) === selectedIdentity) + || archivedTasks.find((task) => this.getTaskSelectionIdentity(task) === selectedIdentity) + || null; + + if (!nextTask) { + nextTask = this.getRecentSelectionTaskForCanvas(activeCanvasPath); + } + } else if (this.hasExplicitCanvasDeselection(activeCanvasPath)) { + nextTask = null; + } else { + if (currentTask && this.getTaskCanvasPath(currentTask) === activeCanvasPath) { + nextTask = currentTask; + } + + if (!nextTask && visibleTasks.length > 0) { + nextTask = visibleTasks[0]; + } + + if (!nextTask && archivedTasks.length > 0) { + nextTask = archivedTasks[0]; + } + } + + const nextTaskId = nextTask?.taskId || null; + const shouldMarkRead = options.markReadOnSelection === true + && nextTaskId + && this.hasUnreadTask(nextTaskId); + if (nextTaskId === this.uiState.activeTaskId) { + if (shouldMarkRead) { + this.setTaskUnreadState(nextTaskId, false); + } + return; + } + + this.uiState.activeTaskId = nextTaskId; + this.uiState.taskDetailOpen = false; + if (shouldMarkRead) { + this.updateTaskUnreadState(nextTaskId, false); + } + this.persistPluginState(); + if (nextTaskId) { + this.subscribeToTask(nextTaskId); + void this.hydrateTaskDetails(nextTaskId, { force: true }); + } else { + this.disposeActiveStream(); + } + this.reconcileRunningTaskStreams(); + this.requestViewRefresh(); + } + + async saveCwdForTask(taskId, cwd) { + const task = this.tasksById[taskId]; + if (!task?.selectionContext) { + new Notice("This task does not have enough selection context to change its project folder."); + return; + } + + const normalizedCwd = this.normalizeWorkingDirectoryInput(cwd); + if (!normalizedCwd) { + new Notice("Enter an absolute project folder path."); + return; + } + + try { + const response = await this.api.createTaskFromCanvasSelection(this.withVaultScope({ + ...task.selectionContext, + title: task.title, + cwd: normalizedCwd, + runtimeConfig: this.buildRuntimeConfigPayload(), + })); + const nextTask = this.mergeTask(response.task, { adoptCurrentVault: true }); + await this.setActiveTask(nextTask.taskId, { revealInActiveTab: true }); + this.runtimeIssue = ""; + new Notice(`Project folder set to ${nextTask.cwd || "(not set)"}`); + await this.refreshTask(nextTask.taskId); + } catch (error) { + this.runtimeIssue = String(error?.message || error); + new Notice(this.runtimeIssue); + this.requestViewRefresh(); + } + } + + async sendDraftForTask(taskId, options = {}) { + const task = this.tasksById[taskId]; + if (task?.status === "running") { + new Notice("Wait for the current turn to finish before sending another message."); + return; + } + + const draftSource = Object.prototype.hasOwnProperty.call(options, "draft") + ? String(options.draft || "") + : this.getDraft(taskId); + if (Object.prototype.hasOwnProperty.call(options, "draft")) { + this.setDraft(taskId, draftSource); + } + + const draft = draftSource.trim(); + if (!draft) { + new Notice("Write a follow-up message first."); + return; + } + + try { + await this.api.sendMessage(taskId, draft, this.buildRuntimeConfigPayload()); + this.clearDraft(taskId); + await this.refreshTask(taskId, { force: true }); + } catch (error) { + this.runtimeIssue = String(error?.message || error); + new Notice(this.runtimeIssue); + this.requestViewRefresh(); + } + } + + async resumeLastTask() { + await this.activateView(); + await this.refreshTasks(); + const task = this.getActiveTask() || this.getTasks()[0]; + if (!task) { + new Notice("No OpenAgent task yet."); + return; + } + + await this.activateTaskFromList(task.taskId); + } + + async stopActiveTask() { + const task = this.getActiveTask(); + if (!task?.currentTurnId) { + new Notice("No active turn to stop."); + return; + } + + try { + await this.api.interruptTask(task.taskId); + await this.refreshTask(task.taskId); + } catch (error) { + this.runtimeIssue = String(error?.message || error); + new Notice(this.runtimeIssue); + this.requestViewRefresh(); + } + } + + getStopActiveTaskHotkey() { + return normalizeStopHotkeySetting(this.settings?.stopActiveTaskHotkey); + } + + getStopActiveTaskHotkeyLabel() { + return formatHotkeyForDisplay(this.getStopActiveTaskHotkey()); + } + + getStopActiveTaskButtonTitle() { + const hotkey = this.getStopActiveTaskHotkeyLabel(); + return hotkey ? `Stop (${hotkey})` : "Stop"; + } + + async handleStopActiveTaskHotkey(event) { + if (!event || event.defaultPrevented || event.repeat) { + return; + } + if (event.isComposing || event.keyCode === 229) { + return; + } + if (isHotkeyCaptureTarget(event.target)) { + return; + } + if (!hotkeyMatchesKeyboardEvent(this.getStopActiveTaskHotkey(), event)) { + return; + } + + const task = this.getActiveTask(); + if (!task?.currentTurnId) { + return; + } + + event.preventDefault(); + event.stopPropagation(); + await this.stopActiveTask(); + } + + normalizeWorkingDirectoryInput(cwd) { + const trimmedCwd = String(cwd || "").trim(); + if (!trimmedCwd || !path.isAbsolute(trimmedCwd)) { + return ""; + } + + return path.normalize(trimmedCwd); + } + + summarizeSelection(selection) { + const textCount = Array.isArray(selection?.textBlocks) ? selection.textBlocks.length : 0; + const fileCount = Array.isArray(selection?.markdownFiles) ? selection.markdownFiles.length : 0; + const imageCount = Array.isArray(selection?.imageFiles) ? selection.imageFiles.length : 0; + const parts = []; + + if (textCount > 0) { + parts.push(`${textCount} text node${textCount === 1 ? "" : "s"}`); + } + + if (fileCount > 0) { + parts.push(`${fileCount} markdown file${fileCount === 1 ? "" : "s"}`); + } + + if (imageCount > 0) { + parts.push(`${imageCount} image file${imageCount === 1 ? "" : "s"}`); + } + + const firstText = selection?.textBlocks?.[0]?.text; + if (parts.length === 1 && textCount === 1 && typeof firstText === "string" && firstText.trim()) { + return `Captured ${parts[0]}: ${this.truncateInline(firstText.trim(), 80)}`; + } + + if (parts.length > 0) { + return `Captured ${parts.join(" and ")}`; + } + + return ""; + } + + getSelectionImageResourceUrl(file) { + const normalizedPath = String(file?.path || "").trim(); + if (!normalizedPath) { + return String(file?.absolutePath || "").trim(); + } + + const abstractFile = this.app.vault.getAbstractFileByPath(normalizedPath); + if (abstractFile instanceof TFile) { + if (typeof this.app.vault.getResourcePath === "function") { + try { + return this.app.vault.getResourcePath(abstractFile); + } catch { + // Fall through to adapter-based lookup. + } + } + + if (typeof this.app.vault.adapter?.getResourcePath === "function") { + try { + return this.app.vault.adapter.getResourcePath(normalizedPath); + } catch { + // Fall through to absolute path. + } + } + } + + return String(file?.absolutePath || "").trim(); + } + + renderSelectionImageMessage(container, selection) { + const imageFiles = Array.isArray(selection?.imageFiles) + ? selection.imageFiles.filter((file) => String(file?.path || file?.absolutePath || "").trim()) + : []; + if (imageFiles.length === 0) { + return null; + } + + const item = container.createDiv({ + cls: "oa-message oa-chat-message oa-role-user oa-selection-message", + }); + item.createDiv({ + cls: "oa-message-label", + text: imageFiles.length === 1 ? "Selected image" : "Selected images", + }); + + const gallery = item.createDiv({ cls: "oa-selection-image-gallery" }); + imageFiles.forEach((file, index) => { + const src = this.getSelectionImageResourceUrl(file); + const card = gallery.createDiv({ cls: "oa-selection-image-card" }); + if (src) { + card.createEl("img", { + cls: "oa-selection-image", + attr: { + src, + alt: String(file?.name || file?.path || `Selected image ${index + 1}`), + }, + }); + } else { + card.createDiv({ + cls: "oa-selection-image-fallback", + text: "Image preview unavailable", + }); + } + card.createDiv({ + cls: "oa-selection-image-caption", + text: String(file?.path || file?.name || `Image file ${index + 1}`), + }); + }); + + return item; + } + + describeSelectionDebug(selection) { + const nodeIds = Array.isArray(selection?.nodeIds) ? selection.nodeIds.filter(Boolean) : []; + const textCount = Array.isArray(selection?.textBlocks) ? selection.textBlocks.length : 0; + const fileCount = Array.isArray(selection?.markdownFiles) ? selection.markdownFiles.length : 0; + const imageCount = Array.isArray(selection?.imageFiles) ? selection.imageFiles.length : 0; + const warningCount = Array.isArray(selection?.warnings) ? selection.warnings.length : 0; + const idsPreview = nodeIds.length > 0 ? nodeIds.join(", ") : "(none)"; + return `Selection debug: ids=${idsPreview} | text=${textCount} | files=${fileCount} | images=${imageCount} | warnings=${warningCount}`; + } + + getNewThreadPromptFromSelection(selection, options = {}) { + const textBlocks = Array.isArray(selection?.textBlocks) ? selection.textBlocks : []; + const markdownFiles = Array.isArray(selection?.markdownFiles) ? selection.markdownFiles : []; + const imageFiles = Array.isArray(selection?.imageFiles) ? selection.imageFiles : []; + if (textBlocks.length === 0 && markdownFiles.length > 0 && imageFiles.length === 0) { + return buildCanvasSelectionPrompt( + selection, + markdownFiles.length === 1 + ? "Use the selected markdown file as the primary context and continue with the most helpful next step." + : "Use the selected markdown files as the primary context and continue with the most helpful next step.", + options, + ); + } + + if (textBlocks.length === 0 && markdownFiles.length === 0 && imageFiles.length > 0) { + return ""; + } + + if (textBlocks.length === 0 && (markdownFiles.length > 0 || imageFiles.length > 0)) { + return markdownFiles.length > 0 ? buildCanvasSelectionPrompt(selection, "", options) : ""; + } + + if (textBlocks.length !== 1) { + throw new Error("New thread currently requires selecting exactly one text node, optionally with file context, or selecting only supported file nodes."); + } + + const text = String(textBlocks[0]?.text || ""); + if (!text.trim()) { + throw new Error("The selected text node is empty."); + } + + if (markdownFiles.length > 0) { + return buildCanvasSelectionPrompt(selection, text, options); + } + + return text; + } + + truncateInline(value, maxLength) { + const singleLine = String(value || "").replace(/\s+/g, " ").trim(); + if (!singleLine) { + return ""; + } + + if (singleLine.length <= maxLength) { + return singleLine; + } + + return `${singleLine.slice(0, Math.max(0, maxLength - 1)).trimEnd()}...`; + } +}; diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..0805ee2 --- /dev/null +++ b/manifest.json @@ -0,0 +1,10 @@ +{ + "id": "openagent", + "name": "OpenAgent", + "version": "0.1.2", + "minAppVersion": "1.5.0", + "description": "Run OpenAgent tasks from Obsidian Canvas through a local Codex server and Codex Desktop.", + "author": "OpenAgentMarket", + "authorUrl": "https://github.com/openagentmarket/openagent-obsidian-plugin", + "isDesktopOnly": true +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..94ea377 --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "openagent-obsidian-plugin", + "version": "0.1.2", + "description": "Run OpenAgent tasks from Obsidian Canvas through a local Codex server and Codex Desktop.", + "license": "MIT", + "type": "commonjs", + "main": "main.js", + "files": [ + "main.js", + "manifest.json", + "styles.css" + ], + "scripts": { + "check": "node --check main.js", + "package": "node scripts/package-release.mjs" + } +} diff --git a/scripts/package-release.mjs b/scripts/package-release.mjs new file mode 100644 index 0000000..4be1011 --- /dev/null +++ b/scripts/package-release.mjs @@ -0,0 +1,67 @@ +import { createHash } from "node:crypto"; +import { execFileSync } from "node:child_process"; +import fs from "node:fs"; +import path from "node:path"; +import process from "node:process"; + +const root = process.cwd(); +const dist = path.join(root, "dist"); +const manifest = JSON.parse(fs.readFileSync(path.join(root, "manifest.json"), "utf8")); +const requiredFiles = ["main.js", "manifest.json", "styles.css"]; +const optionalFiles = ["logo.png", "vault-pet-mascot.png"]; +const archiveName = `openagent-obsidian-plugin-${manifest.version}.zip`; +const bundleDir = path.join(dist, manifest.id); + +fs.rmSync(dist, { recursive: true, force: true }); +fs.mkdirSync(bundleDir, { recursive: true }); + +for (const file of [...requiredFiles, ...optionalFiles]) { + const source = path.join(root, file); + if (!fs.existsSync(source)) { + if (requiredFiles.includes(file)) { + throw new Error(`Missing required release file: ${file}`); + } + continue; + } + + fs.copyFileSync(source, path.join(dist, file)); + fs.copyFileSync(source, path.join(bundleDir, file)); +} + +execFileSync("zip", ["-qr", archiveName, manifest.id], { cwd: dist }); + +const files = [...fs.readdirSync(dist).filter((file) => file !== manifest.id)].sort(); +const checksums = files + .map((file) => `${sha256(path.join(dist, file))} ${file}`) + .join("\n"); + +fs.writeFileSync(path.join(dist, "SHA256SUMS.txt"), `${checksums}\n`, "utf8"); +fs.writeFileSync(path.join(dist, "release-notes.md"), releaseNotes(manifest, archiveName), "utf8"); + +console.log(`Prepared release ${manifest.version}`); +for (const file of [...files, "SHA256SUMS.txt", "release-notes.md"]) { + console.log(`dist/${file}`); +} + +function sha256(filePath) { + const hash = createHash("sha256"); + hash.update(fs.readFileSync(filePath)); + return hash.digest("hex"); +} + +function releaseNotes(manifest, archiveName) { + return [ + `## ${manifest.name} ${manifest.version}`, + "", + "Community plugin release assets:", + "- `main.js`", + "- `manifest.json`", + "- `styles.css`", + "", + "Manual install bundle:", + `- \`${archiveName}\``, + "", + "Checksums are attached in `SHA256SUMS.txt`.", + "", + ].join("\n"); +} diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..0332099 --- /dev/null +++ b/styles.css @@ -0,0 +1,1844 @@ +.openagent-view { + --oa-space-1: 4px; + --oa-space-2: 8px; + --oa-space-3: 12px; + --oa-space-4: 16px; + --oa-space-5: 20px; + --oa-radius-sm: 8px; + --oa-radius-md: 12px; + --oa-radius-lg: 16px; + --oa-border: color-mix(in srgb, var(--background-modifier-border) 78%, transparent); + --oa-border-soft: color-mix(in srgb, var(--background-modifier-border) 52%, transparent); + --oa-surface: color-mix(in srgb, var(--background-secondary) 84%, transparent); + --oa-surface-strong: color-mix(in srgb, var(--background-secondary) 94%, var(--background-primary)); + --oa-surface-hover: color-mix(in srgb, var(--background-modifier-hover) 76%, transparent); + --oa-accent-soft: color-mix(in srgb, var(--interactive-accent) 12%, transparent); + --oa-panel-gutter: 0px; + height: 100%; + padding: 0; + display: flex; + flex-direction: column; + overflow: hidden; + background: var(--background-primary); +} + +.openagent-view .oa-panel-scroll, +.openagent-view .oa-panel-scroll *, +.openagent-view button, +.openagent-view .clickable-icon { + -webkit-user-select: none; + user-select: none; +} + +.openagent-view input, +.openagent-view textarea { + -webkit-user-select: text; + user-select: text; +} + +.oa-voice-graph-view { + height: 100%; + min-height: 0; + display: flex; + flex-direction: column; + overflow: hidden; + background: var(--background-primary); +} + +.oa-voice-graph-toolbar { + flex: 0 0 auto; + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + align-items: center; + gap: 12px; + padding: 8px 12px; + border-bottom: 1px solid color-mix(in srgb, var(--background-modifier-border) 62%, transparent); + background: color-mix(in srgb, var(--background-primary) 96%, transparent); + backdrop-filter: blur(12px); +} + +.oa-voice-graph-title { + display: flex; + flex-direction: column; + gap: 2px; + min-width: 0; +} + +.oa-voice-graph-title span { + color: var(--text-normal); + font-size: 0.9rem; + font-weight: 600; + line-height: 1.2; +} + +.oa-voice-graph-title small { + color: var(--text-muted); + font-size: 0.76rem; + line-height: 1.25; +} + +.oa-voice-graph-controls { + display: flex; + align-items: center; + justify-content: flex-end; + gap: 6px; + min-width: 0; +} + +.oa-voice-graph-icon-button { + width: 28px; + height: 28px; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 8px; + border: 1px solid color-mix(in srgb, var(--background-modifier-border) 62%, transparent); + background: transparent; + color: var(--text-muted); +} + +.oa-voice-graph-icon-button:hover { + color: var(--text-normal); + background: var(--background-modifier-hover); +} + +.oa-voice-graph-slider { + display: grid; + grid-template-columns: auto 112px; + align-items: center; + gap: 8px; + margin-left: 4px; + color: var(--text-muted); + font-size: 0.75rem; + white-space: nowrap; +} + +.oa-voice-graph-slider input { + width: 112px; +} + +.oa-voice-graph-stage { + position: relative; + flex: 1 1 auto; + min-height: 0; + overflow: hidden; +} + +.oa-voice-graph-canvas { + width: 100%; + height: 100%; + display: block; + touch-action: none; + cursor: grab; +} + +.oa-voice-graph-hud { + position: absolute; + left: 12px; + bottom: 12px; + display: flex; + flex-wrap: wrap; + gap: 6px; + max-width: calc(100% - 24px); + pointer-events: none; +} + +.oa-voice-graph-dictation { + position: absolute; + right: 12px; + bottom: 12px; + width: min(360px, calc(100% - 24px)); + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 6px; + align-items: end; +} + +.oa-voice-graph-dictation textarea { + width: 100%; + min-height: 48px; + max-height: 120px; + resize: vertical; + border: 1px solid color-mix(in srgb, var(--background-modifier-border) 52%, transparent); + border-radius: 8px; + padding: 7px 8px; + background: color-mix(in srgb, var(--background-primary) 88%, transparent); + color: var(--text-normal); + font-size: 0.78rem; + line-height: 1.35; + backdrop-filter: blur(10px); +} + +.oa-voice-graph-dictation textarea:focus { + outline: none; + border-color: color-mix(in srgb, var(--text-muted) 58%, var(--background-modifier-border)); +} + +.oa-voice-graph-dictation button { + height: 30px; + min-height: 30px; + padding: 0 10px; + border: 1px solid color-mix(in srgb, var(--background-modifier-border) 55%, transparent); + border-radius: 8px; + background: color-mix(in srgb, var(--background-secondary) 84%, transparent); + color: var(--text-normal); + font-size: 0.76rem; +} + +.oa-voice-graph-dictation button:disabled { + opacity: 0.45; +} + +.oa-voice-graph-hud span { + padding: 4px 7px; + border: 1px solid color-mix(in srgb, var(--background-modifier-border) 46%, transparent); + border-radius: 8px; + background: color-mix(in srgb, var(--background-primary) 82%, transparent); + color: var(--text-muted); + font-size: 0.7rem; + line-height: 1.2; + backdrop-filter: blur(10px); +} + +@media (max-width: 680px) { + .oa-voice-graph-toolbar { + grid-template-columns: 1fr; + } + + .oa-voice-graph-controls { + justify-content: flex-start; + flex-wrap: wrap; + } + + .oa-voice-graph-slider { + width: 100%; + grid-template-columns: auto minmax(96px, 1fr); + } + + .oa-voice-graph-slider input { + width: 100%; + } + + .oa-voice-graph-dictation { + left: 12px; + right: 12px; + bottom: 42px; + width: auto; + } +} + +.openagent-view .oa-message-text, +.openagent-view .oa-message-text * { + -webkit-user-select: text; + user-select: text; +} + +.oa-panel-scroll { + flex: 1 1 auto; + min-height: 0; + padding: 0; + overflow-y: auto; +} + +.oa-panel-scroll > * + * { + margin-top: var(--oa-space-3); +} + +.oa-header-top { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + align-items: center; + gap: 6px; +} + +.oa-brand { + display: flex; + align-items: center; + gap: 6px; + min-width: 0; +} + +.oa-brand-text { + display: flex; + flex-direction: column; + gap: 2px; + min-width: 0; +} + +.oa-brand-title-row { + display: flex; + align-items: center; + gap: 6px; + min-width: 0; +} + +.oa-brand-text h2 { + margin: 0; + font-size: 1rem; + font-weight: 600; + line-height: 1.2; +} + +.oa-daemon-online-dot { + width: 8px; + height: 8px; + border-radius: 999px; + background: var(--color-green); + box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-green) 16%, transparent); + flex: 0 0 auto; +} + +.oa-brand-subtitle { + color: var(--text-muted); + font-size: 0.8rem; + line-height: 1.35; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.oa-logo { + width: 32px; + height: 32px; + border-radius: 8px; + display: inline-flex; + align-items: center; + justify-content: center; + background: var(--oa-surface); + border: 1px solid var(--oa-border-soft); + flex: 0 0 auto; +} + +.oa-logo-mark { + color: var(--text-muted); + font-size: 0.92rem; + font-weight: 700; + letter-spacing: 0; +} + +.oa-logo-image { + width: 24px; + height: 24px; + object-fit: contain; + display: block; +} + +.oa-header, +.oa-task-section { + margin: 0; + padding: 0; + border: 0; + border-radius: 0; + background: transparent; +} + +.oa-header { + position: sticky; + top: 0; + z-index: 5; + margin: 0 calc(var(--oa-panel-gutter) * -1); + padding: 10px var(--oa-panel-gutter) 8px; + border-bottom: 1px solid var(--oa-border-soft); + background: color-mix(in srgb, var(--background-primary) 92%, transparent); + backdrop-filter: blur(18px); +} + +.oa-detail-section, +.oa-settings-body { + display: flex; + flex-direction: column; + gap: var(--oa-space-3); +} + +.oa-detail-section { + padding: 10px 0 8px; +} + +.oa-messages-section { + margin-top: 16px; + padding-top: 14px; + border-top: 1px solid var(--oa-border-soft); +} + +.oa-detail-section.is-empty-state { + display: flex; + flex-direction: column; + gap: 10px; + padding-top: 0; + padding-bottom: 0; +} + +.oa-detail-section.is-empty-state .oa-action-row { + margin-top: 0; +} + +.oa-message-pagination { + display: flex; + flex-direction: column; + gap: 6px; + margin-bottom: 10px; +} + +.oa-message-pagination-summary { + color: var(--text-muted); + font-size: 0.78rem; + line-height: 1.4; +} + +.oa-message-pagination-actions { + margin-top: 0; +} + +.oa-composer-section { + flex: 0 0 auto; + margin: 0; + padding: 12px 0 6px; + border-top: 1px solid var(--oa-border); + background: color-mix(in srgb, var(--background-primary) 90%, transparent); + backdrop-filter: blur(18px); + box-shadow: none; +} + +.oa-action-row.oa-header-actions { + display: flex; + justify-content: flex-end; + gap: 4px; + margin-top: 0; + width: auto; +} + +.oa-action-row.oa-header-actions button { + padding: 5px 10px; + border-radius: 999px; + background: color-mix(in srgb, var(--background-secondary) 74%, transparent); + font-size: 0.78rem; + line-height: 1.2; + white-space: nowrap; +} + +.oa-action-row { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin-top: 8px; +} + +.oa-action-row button { + border-radius: var(--oa-radius-sm); + padding: 7px 11px; + min-width: 0; + min-height: auto; + background: var(--oa-surface); + border: 1px solid var(--oa-border-soft); + box-shadow: none; + color: var(--text-normal); + overflow: hidden; + text-overflow: ellipsis; +} + +.oa-action-row button:hover:not(:disabled) { + background: var(--oa-surface-hover); + color: var(--text-normal); +} + +.oa-action-row button:disabled { + opacity: 0.45; +} + +.oa-settings-body { + min-width: 0; +} + +.oa-settings-page-content { + display: flex; + flex-direction: column; + gap: 12px; +} + +.oa-panel-tabs { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 6px; +} + +.oa-panel-tab { + appearance: none; + min-width: 0; + padding: 6px 8px; + border: 1px solid var(--oa-border-soft); + border-radius: var(--oa-radius-sm); + background: transparent; + color: var(--text-muted); + cursor: pointer; + font: inherit; + font-size: 0.78rem; + line-height: 1.2; + text-align: center; + box-shadow: none; +} + +.oa-panel-tab:hover { + color: var(--text-normal); + border-color: var(--oa-border); +} + +.oa-panel-tab.is-active { + color: var(--text-normal); + border-color: color-mix(in srgb, var(--interactive-accent) 30%, var(--oa-border-soft)); + background: color-mix(in srgb, var(--interactive-accent) 8%, transparent); +} + +.openagent-view .oa-panel-tab, +.openagent-view .oa-panel-tab:hover, +.openagent-view .oa-panel-tab:focus, +.openagent-view .oa-panel-tab:focus-visible, +.openagent-view .oa-panel-tab:active, +.openagent-view .oa-settings-inline-tab, +.openagent-view .oa-settings-inline-tab:hover, +.openagent-view .oa-settings-inline-tab:focus, +.openagent-view .oa-settings-inline-tab:focus-visible, +.openagent-view .oa-settings-inline-tab:active { + box-shadow: none !important; +} + +.oa-task-list, +.oa-message-list, +.oa-workspace-list { + display: flex; + flex-direction: column; + gap: 8px; +} + +.oa-task-list-date-heading { + margin: 14px 0 2px; + color: var(--text-muted); + font-size: 0.72rem; + font-weight: 600; + line-height: 1.45; + letter-spacing: 0.01em; + text-transform: none; +} + +.oa-task-list-date-heading:first-child { + margin-top: 2px; +} + +.oa-task-item, +.oa-message, +.oa-workspace-item { + border: 1px solid var(--oa-border-soft); + border-radius: var(--oa-radius-md); + padding: 11px 12px; + background: var(--oa-surface-strong); + min-width: 0; +} + +.oa-group-context-card { + gap: 8px; +} + +.oa-group-context-list { + display: flex; + flex-direction: column; + gap: 8px; + margin-top: 8px; +} + +.oa-group-context-item { + border: 1px solid var(--oa-border-soft); + border-radius: var(--oa-radius-sm); + padding: 8px 10px; + background: color-mix(in srgb, var(--background-primary) 58%, transparent); +} + +.oa-group-context-label { + color: var(--text-muted); + font-size: 0.74rem; + line-height: 1.35; + margin-bottom: 2px; +} + +.oa-group-context-value { + color: var(--text-normal); + font-size: 0.82rem; + line-height: 1.45; + overflow-wrap: anywhere; +} + +.oa-task-item { + cursor: pointer; + transition: + border-color 120ms ease, + background-color 120ms ease; +} + +.oa-task-item:hover { + border-color: color-mix(in srgb, var(--background-modifier-border-hover) 70%, transparent); + background: var(--oa-surface-hover); +} + +.oa-task-item.is-active { + border-color: color-mix(in srgb, var(--interactive-accent) 30%, var(--oa-border-soft)); + background: var(--oa-accent-soft); +} + +.oa-thread-list-item { + padding: 10px 12px; + background: color-mix(in srgb, var(--background-primary) 76%, var(--background-secondary)); +} + +.oa-thread-list-item:hover { + border-color: color-mix(in srgb, var(--background-modifier-border-hover) 72%, transparent); + background: color-mix(in srgb, var(--background-modifier-hover) 72%, var(--background-primary)); +} + +.oa-thread-list-item.is-active { + border-color: color-mix(in srgb, var(--interactive-accent) 30%, var(--oa-border-soft)); + background: color-mix(in srgb, var(--interactive-accent) 10%, var(--background-primary)); + box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--interactive-accent) 12%, transparent); +} + +.oa-thread-list-item.is-running, +.oa-thread-list-item.is-running:hover, +.oa-thread-list-item.is-running.is-active { + border-color: color-mix(in srgb, var(--color-yellow) 56%, var(--oa-border-soft)); + background: color-mix(in srgb, var(--color-yellow) 13%, var(--background-primary)); + box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-yellow) 18%, transparent); +} + +.oa-thread-list-main { + min-width: 0; + display: flex; + flex-direction: column; + gap: 2px; +} + +.oa-thread-list-title { + font-size: 0.84rem; + font-weight: 600; + line-height: 1.35; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.oa-thread-list-meta { + color: var(--text-faint); + font-size: 0.72rem; + line-height: 1.35; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.oa-thread-list-arrow { + appearance: none; + flex: 0 0 auto; + color: var(--text-faint); + width: 20px; + height: 20px; + border: 1px solid var(--oa-border-soft); + border-radius: 999px; + display: inline-flex; + align-items: center; + justify-content: center; + background: color-mix(in srgb, var(--background-secondary) 72%, transparent); + cursor: pointer; + padding: 0; + box-shadow: none; + font: inherit; + font-size: 0.8rem; + line-height: 1; +} + +.oa-thread-list-arrow:hover { + color: var(--text-normal); + border-color: var(--oa-border); +} + +.openagent-view .oa-thread-list-arrow, +.openagent-view .oa-thread-list-arrow:hover, +.openagent-view .oa-thread-list-arrow:focus, +.openagent-view .oa-thread-list-arrow:focus-visible, +.openagent-view .oa-thread-list-arrow:active { + box-shadow: none !important; +} + +.oa-thread-list-unread-dot { + flex: 0 0 auto; + width: 10px; + height: 10px; + margin-top: 4px; + border-radius: 999px; + background: #8b5cf6; + box-shadow: 0 0 0 4px color-mix(in srgb, #8b5cf6 16%, transparent); +} + +.oa-task-title, +.oa-detail-title { + font-weight: 600; +} + +.oa-conversation-heading { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 8px; + margin-top: 12px; + padding-top: 10px; + border-top: 1px solid var(--oa-border-soft); + min-width: 0; +} + +.oa-task-item-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 8px; + min-width: 0; +} + +.oa-detail-title { + font-size: 0.98rem; + line-height: 1.35; + overflow-wrap: anywhere; + min-width: 0; +} + +.oa-status-tag { + flex: 0 0 auto; + max-width: 38%; + padding: 3px 8px; + border: 1px solid var(--oa-border-soft); + border-radius: var(--oa-radius-sm); + background: var(--oa-surface); + color: var(--text-muted); + font-size: 0.72rem; + line-height: 1.35; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.oa-status-tag.is-running { + border-color: color-mix(in srgb, var(--color-yellow) 42%, transparent); + background: color-mix(in srgb, var(--color-yellow) 14%, transparent); + color: var(--text-normal); +} + +.oa-status-tag.is-online { + border-color: color-mix(in srgb, var(--color-green) 42%, transparent); + background: color-mix(in srgb, var(--color-green) 14%, transparent); + color: var(--text-normal); +} + +.oa-status-tag.is-offline { + border-color: color-mix(in srgb, var(--color-red) 42%, transparent); + background: color-mix(in srgb, var(--color-red) 12%, transparent); + color: var(--text-normal); +} + +.oa-status-tag.is-checking { + border-color: color-mix(in srgb, var(--interactive-accent) 36%, transparent); + background: color-mix(in srgb, var(--interactive-accent) 10%, transparent); + color: var(--text-normal); +} + +.oa-task-meta, +.oa-message-label, +.oa-muted { + color: var(--text-muted); + font-size: 0.8rem; + line-height: 1.45; + overflow-wrap: anywhere; +} + +.oa-task-section > h3, +.oa-detail-section > h3, +.oa-settings-section > h3, +.oa-messages-section > h4 { + margin: 0 0 8px; + color: var(--text-muted); + font-size: 0.78rem; + font-weight: 500; + letter-spacing: 0; + text-transform: none; +} + +.oa-role-user { + border-color: color-mix(in srgb, var(--interactive-accent) 28%, var(--oa-border-soft)); + background: color-mix(in srgb, var(--interactive-accent) 8%, var(--background-primary)); +} + +.oa-role-assistant { + background: var(--oa-surface-strong); +} + +.oa-role-system { + background: color-mix(in srgb, var(--background-secondary) 68%, transparent); +} + +.oa-message-text { + white-space: pre-wrap; + word-break: break-word; + line-height: 1.5; +} + +.oa-message-text.is-hidden { + display: none; +} + +.oa-message-label + .oa-message-text, +.oa-task-title + .oa-task-meta { + margin-top: 4px; +} + +.oa-message-tool { + border: 0; + padding: 8px 10px; + background: transparent; + box-shadow: none; +} + +.oa-chat-message { + border: 0; + border-radius: 0; + padding: 0; + background: transparent; + box-shadow: none; +} + +.oa-chat-message.oa-role-user, +.oa-chat-message.oa-role-assistant, +.oa-chat-message.oa-role-system { + border: 0; + background: transparent; + box-shadow: none; +} + +.oa-chat-message.oa-role-user { + align-self: flex-end; + width: fit-content; + max-width: min(78%, 42rem); + padding: 10px 12px 10px 14px; + border: 1px solid color-mix(in srgb, var(--interactive-accent) 18%, var(--oa-border-soft)); + border-radius: var(--oa-radius-md); + background: color-mix(in srgb, var(--interactive-accent) 7%, var(--background-primary)); +} + +.oa-selection-message { + width: min(78%, 42rem); +} + +.oa-selection-image-gallery { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); + gap: 10px; + margin-top: 6px; +} + +.oa-selection-image-card { + display: flex; + flex-direction: column; + gap: 6px; +} + +.oa-selection-image { + display: block; + width: 100%; + max-height: 220px; + object-fit: contain; + border-radius: 12px; + border: 1px solid color-mix(in srgb, var(--interactive-accent) 12%, var(--oa-border-soft)); + background: color-mix(in srgb, var(--background-secondary) 72%, transparent); +} + +.oa-selection-image-fallback { + display: flex; + align-items: center; + justify-content: center; + min-height: 120px; + border-radius: 12px; + border: 1px dashed var(--oa-border-soft); + color: var(--text-muted); + font-size: 0.76rem; + text-align: center; + padding: 12px; +} + +.oa-selection-image-caption { + color: var(--text-muted); + font-size: 0.72rem; + line-height: 1.4; + overflow-wrap: anywhere; +} + +.oa-chat-message.oa-message-tool, +.oa-chat-message.oa-message-tool.oa-role-user, +.oa-chat-message.oa-message-tool.oa-role-assistant, +.oa-chat-message.oa-message-tool.oa-role-system { + border: 0; + background: transparent; + box-shadow: none; +} + +.oa-message-toggle { + appearance: none; + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + align-items: center; + gap: 10px; + width: 100%; + padding: 0; + border: 0; + border-radius: 0; + background: transparent; + box-shadow: none; + color: inherit; + text-align: left; + cursor: pointer; + min-height: 0; +} + +.oa-chat-message.oa-message-tool .oa-message-toggle, +.oa-chat-message.oa-message-tool .oa-message-toggle:hover, +.oa-chat-message.oa-message-tool .oa-message-toggle:focus, +.oa-chat-message.oa-message-tool .oa-message-toggle:focus-visible, +.oa-chat-message.oa-message-tool .oa-message-toggle:active { + border: 0 !important; + outline: 0; + background: transparent !important; + box-shadow: none !important; +} + +.oa-message-toggle:hover .oa-message-preview { + color: var(--text-normal); +} + +.oa-message-toggle-text { + min-width: 0; +} + +.oa-message-preview { + margin-top: 2px; + color: var(--text-muted); + font-size: 0.8rem; + line-height: 1.4; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.oa-message-toggle-icon { + width: 20px; + height: 20px; + border: 0; + border-radius: 999px; + display: inline-flex; + align-items: center; + justify-content: center; + background: transparent; + box-shadow: none; + color: var(--text-muted); + font-size: 0.85rem; + line-height: 1; + flex: 0 0 auto; +} + +.oa-message-tool.is-expanded .oa-message-text { + margin-top: 8px; + padding-top: 8px; + border-top: 0; +} + +.oa-banner { + border-radius: 8px; + padding: 9px 11px; + margin: 0; + border: 1px solid var(--oa-border-soft); + line-height: 1.45; + overflow-wrap: anywhere; +} + +.oa-banner-error { + background: color-mix(in srgb, var(--color-red) 9%, transparent); + border-color: color-mix(in srgb, var(--color-red) 24%, transparent); +} + +.oa-banner-warning { + background: color-mix(in srgb, var(--color-orange) 9%, transparent); + border-color: color-mix(in srgb, var(--color-orange) 24%, transparent); +} + +.oa-workspace-section { + margin-top: 16px; +} + +.oa-workspace-modal h2 { + margin-bottom: 4px; +} + +.oa-workspace-modal-subtitle { + color: var(--text-muted); + font-size: 0.88rem; + line-height: 1.45; + margin-bottom: 18px; + max-width: 560px; +} + +.oa-workspace-modal .oa-workspace-section > h3, +.oa-workspace-create-panel > h3 { + margin: 0 0 10px; + color: var(--text-muted); + font-size: 0.82rem; + font-weight: 600; + letter-spacing: 0; +} + +.oa-workspace-modal .oa-workspace-list { + gap: 8px; +} + +.oa-workspace-modal .oa-workspace-item { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 12px; + align-items: center; + padding: 12px; + background: color-mix(in srgb, var(--background-primary) 62%, var(--background-secondary)); +} + +.oa-workspace-item-main { + min-width: 0; +} + +.oa-workspace-open-button { + min-width: 70px; +} + +.oa-workspace-create-panel { + padding-top: 16px; + border-top: 1px solid var(--oa-border-soft); +} + +.oa-settings-section, +.oa-settings-card { + margin: 0; + padding: 0; + border: 0; + border-radius: 0; + background: transparent; + box-shadow: none; +} + +.oa-workspace-settings-section { + background: transparent; +} + +.oa-settings-section-block { + display: flex; + flex-direction: column; + gap: 10px; +} + +.oa-settings-section-block + .oa-settings-section-block { + padding-top: 14px; + border-top: 1px solid var(--oa-border-soft); +} + +.oa-settings-section-header { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 10px; + min-width: 0; +} + +.oa-settings-section-title { + margin: 0; + color: var(--text-normal); + font-size: 0.9rem; + font-weight: 650; + line-height: 1.25; +} + +.oa-settings-inline-tabs { + display: flex; + align-items: center; + gap: 6px; + flex: 0 0 auto; + flex-wrap: wrap; + width: 100%; +} + +.oa-settings-inline-tab { + appearance: none; + padding: 5px 9px; + border: 1px solid var(--oa-border-soft); + border-radius: 999px; + background: color-mix(in srgb, var(--background-primary) 82%, transparent); + color: var(--text-muted); + cursor: pointer; + font: inherit; + font-size: 0.78rem; + line-height: 1.2; + box-shadow: none; + flex: 1 1 78px; + text-align: center; +} + +.oa-settings-inline-tab:hover { + color: var(--text-normal); + border-color: var(--oa-border); +} + +.oa-settings-inline-tab.is-active { + color: var(--text-normal); + border-color: color-mix(in srgb, var(--interactive-accent) 28%, var(--oa-border-soft)); +} + +.oa-thread-filter-shell { + width: auto; + flex: 0 0 auto; +} + +.oa-thread-filter-select { + appearance: none; + width: 18px; + min-height: 0; + padding: 0; + border: 0; + border-radius: 0; + background: + linear-gradient(45deg, transparent 50%, var(--text-muted) 50%) 4px 55% / 5px 5px no-repeat, + linear-gradient(135deg, var(--text-muted) 50%, transparent 50%) 9px 55% / 5px 5px no-repeat; + color: transparent; + cursor: pointer; + font: inherit; + font-size: 0.84rem; + font-weight: 500; + line-height: 1.25; + box-shadow: none; +} + +.oa-thread-filter-select:hover, +.oa-thread-filter-select:focus, +.oa-thread-filter-select:focus-visible { + color: transparent; + box-shadow: none; + outline: none; +} + +.oa-thread-filter-select option { + color: var(--text-normal); +} + +.oa-thread-filter-header { + flex-direction: row; + align-items: center; + justify-content: flex-start; + gap: 10px; +} + +.oa-thread-filter-header .oa-settings-section-title { + flex: 0 0 auto; + min-width: 0; +} + +.oa-workspace-overview { + padding: 12px; + border: 1px solid var(--oa-border-soft); + border-radius: 14px; + background: transparent; + box-shadow: none; +} + +.oa-workspace-overview.is-ready { + border-color: color-mix(in srgb, var(--interactive-accent) 24%, var(--oa-border-soft)); +} + +.oa-workspace-overview.is-empty { + border-color: color-mix(in srgb, var(--color-orange) 28%, var(--oa-border-soft)); +} + +.oa-workspace-overview-top { + display: flex; + flex-direction: column; + gap: 6px; + min-width: 0; +} + +.oa-workspace-status { + align-self: flex-start; + padding: 2px 7px; + border: 1px solid var(--oa-border-soft); + border-radius: var(--oa-radius-sm); + color: var(--text-muted); + font-size: 0.72rem; + font-weight: 600; + line-height: 1.35; +} + +.oa-workspace-overview.is-ready .oa-workspace-status { + border-color: color-mix(in srgb, var(--interactive-accent) 26%, transparent); + background: transparent; + color: var(--text-normal); +} + +.oa-workspace-overview.is-empty .oa-workspace-status { + border-color: color-mix(in srgb, var(--color-orange) 28%, transparent); + background: transparent; + color: var(--text-normal); +} + +.oa-workspace-name { + color: var(--text-normal); + font-size: 1.02rem; + font-weight: 650; + line-height: 1.3; + overflow-wrap: anywhere; +} + +.oa-workspace-detail-grid { + display: grid; + gap: 8px; + margin-top: 12px; +} + +.oa-workspace-detail { + min-width: 0; + padding-top: 8px; + border-top: 1px solid var(--oa-border-soft); +} + +.oa-workspace-detail-label { + color: var(--text-faint); + font-size: 0.72rem; + line-height: 1.35; +} + +.oa-workspace-detail-value { + margin-top: 2px; + color: var(--text-normal); + font-size: 0.82rem; + line-height: 1.4; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.oa-settings-list { + display: flex; + flex-direction: column; + gap: 0; +} + +.oa-settings-subsection { + margin-top: 14px; + padding-top: 14px; + border-top: 1px solid var(--oa-border-soft); +} + +.oa-settings-note { + margin-top: 12px; + padding-top: 12px; + border-top: 1px solid var(--oa-border-soft); +} + +.oa-settings-row { + display: grid; + grid-template-columns: minmax(0, 1fr); + gap: 4px; + min-width: 0; + align-items: start; + padding: 10px 0; + border-top: 1px solid var(--oa-border-soft); +} + +.oa-settings-row:first-child { + padding-top: 0; + border-top: 0; +} + +.oa-settings-label { + color: var(--text-faint); + font-size: 0.74rem; + font-weight: 600; + line-height: 1.35; +} + +.oa-settings-value { + min-width: 0; + color: var(--text-normal); + font-size: 0.82rem; + line-height: 1.35; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.oa-settings-value-stack { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 6px; + overflow: visible; + text-overflow: clip; + white-space: normal; +} + +.oa-settings-value-path { + overflow: visible; + text-overflow: clip; + white-space: normal; + overflow-wrap: anywhere; + word-break: break-word; +} + +.oa-settings-actions { + display: grid; + grid-template-columns: minmax(0, 1fr); + gap: 8px; +} + +.oa-settings-actions button { + width: 100%; + justify-content: center; +} + +.oa-settings-task-section { + padding-top: 2px; +} + +.oa-header-actions .oa-icon-button.is-icon-only { + width: 32px; + height: 32px; + min-width: 32px; + padding: 0; + align-items: center; + justify-content: center; +} + +.oa-header-actions .oa-icon-button.is-icon-only svg { + width: 16px; + height: 16px; +} + +.oa-composer-input-shell { + position: relative; + border: 1px solid var(--oa-border-soft); + border-radius: var(--oa-radius-md); + background: var(--oa-surface-strong); +} + +.oa-composer-input-shell:focus-within { + border-color: color-mix(in srgb, var(--interactive-accent) 36%, var(--oa-border-soft)); + background: var(--oa-surface-strong); +} + +.oa-composer-input { + display: block; + width: 100%; + min-height: 64px; + resize: vertical; + border: 0; + border-radius: 8px; + padding: 10px 72px 10px 12px; + background: transparent; + color: var(--text-normal); + box-sizing: border-box; + line-height: 1.45; +} + +.oa-composer-input::placeholder { + color: var(--text-faint); +} + +.oa-composer-input:focus { + outline: none; + box-shadow: none; +} + +.oa-composer-action-button { + position: absolute; + right: 8px; + top: 50%; + transform: translateY(-50%); + display: inline-flex; + align-items: center; + justify-content: center; + height: 30px; + min-height: 30px; + padding: 0; + border: 1px solid var(--oa-border-soft); + border-radius: 8px; + box-shadow: none; + line-height: 1; +} + +.oa-composer-action-button.is-send { + width: 30px; + min-width: 30px; + background: var(--interactive-accent); + color: var(--text-on-accent); +} + +.oa-composer-action-button.is-stop { + min-width: 52px; + padding: 0 10px; + border-color: color-mix(in srgb, var(--color-red) 42%, var(--oa-border-soft)); + background: color-mix(in srgb, var(--color-red) 15%, var(--oa-surface-strong)); + color: color-mix(in srgb, var(--color-red) 82%, var(--text-normal)); + font-size: 0.78rem; + font-weight: 600; +} + +.oa-composer-action-button:hover:not(:disabled) { + color: var(--text-on-accent); +} + +.oa-composer-action-button.is-send:hover:not(:disabled) { + background: var(--interactive-accent-hover); + color: var(--text-on-accent); +} + +.oa-composer-action-button.is-stop:hover:not(:disabled) { + border-color: color-mix(in srgb, var(--color-red) 54%, var(--oa-border-soft)); + background: color-mix(in srgb, var(--color-red) 22%, var(--oa-surface-strong)); + color: color-mix(in srgb, var(--color-red) 88%, var(--text-normal)); +} + +.oa-composer-action-button:disabled { + opacity: 0.45; +} + +.oa-hotkey-capture-input { + cursor: pointer; +} + +.openagent-view .oa-settings-section .setting-item { + margin-top: 0; + padding: 16px 0; + border-top: 1px solid var(--oa-border-soft); + display: grid; + grid-template-columns: minmax(0, 1fr); + gap: 10px; +} + +.openagent-view .oa-settings-section .setting-item:first-of-type { + padding-top: 0; + border-top: 0; +} + +.openagent-view .oa-settings-section .setting-item-info { + padding-right: 0; + width: 100%; +} + +.openagent-view .oa-settings-section .setting-item-control { + display: grid; + grid-template-columns: minmax(0, 1fr); + gap: 8px; + margin-top: 0; + width: 100%; +} + +.openagent-view .oa-settings-section .setting-item-control input, +.openagent-view .oa-settings-section .setting-item-control select { + width: 100%; + min-width: 0; + border-radius: 11px; + background: transparent; + border: 1px solid var(--oa-border-soft); + box-shadow: none; +} + +.openagent-view .oa-settings-section .setting-item-control button { + white-space: nowrap; + border-radius: 11px; + background: transparent; + box-shadow: none; +} + +.openagent-view .oa-settings-section .setting-item-name { + font-size: 0.9rem; + font-weight: 600; +} + +.openagent-view .oa-settings-section .setting-item-description { + color: var(--text-muted); + font-size: 0.82em; + line-height: 1.45; +} + +.oa-settings-body .oa-action-row button { + background: transparent; + box-shadow: none; +} + +.oa-settings-body .oa-action-row button:hover:not(:disabled) { + background: transparent; +} + +.oa-panel-scroll.is-settings-screen button, +.oa-panel-scroll.is-settings-screen button:hover:not(:disabled), +.oa-panel-scroll.is-settings-screen button:focus, +.oa-panel-scroll.is-settings-screen button:focus-visible, +.oa-panel-scroll.is-settings-screen button:active { + box-shadow: none !important; +} + +.oa-settings-task-section .oa-task-item, +.oa-settings-task-section .oa-workspace-item { + background: color-mix(in srgb, var(--background-primary) 82%, transparent); + box-shadow: none; +} + +.oa-settings-task-section .oa-thread-list-item.is-running, +.oa-settings-task-section .oa-thread-list-item.is-running:hover, +.oa-settings-task-section .oa-thread-list-item.is-running.is-active { + border-color: color-mix(in srgb, var(--color-yellow) 56%, var(--oa-border-soft)); + background: color-mix(in srgb, var(--color-yellow) 13%, var(--background-primary)); + box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-yellow) 18%, transparent); +} + +.oa-fork-modal { + display: grid; + gap: 12px; +} + +.oa-fork-modal h2 { + margin: 0; +} + +.oa-fork-modal-subtitle, +.oa-fork-modal-details { + color: var(--text-muted); + font-size: 0.9rem; + line-height: 1.45; +} + +.oa-fork-modal-details { + display: grid; + gap: 4px; +} + +.oa-fork-modal-actions { + justify-content: flex-end; + margin-top: 4px; +} + +.oa-vault-pet { + --oa-pet-shell: #ff7b72; + --oa-pet-shell-dark: #d94d66; + --oa-pet-glow: #7dd3fc; + --oa-pet-ink: #243142; + --oa-pet-cream: #fff7ed; + position: fixed; + z-index: 1000; + width: 118px; + height: 142px; + left: calc(100vw - 152px); + top: calc(100vh - 172px); + display: grid; + place-items: end center; + pointer-events: auto; + cursor: grab; + user-select: none; + filter: drop-shadow(0 16px 28px rgb(0 0 0 / 0.2)); + transition: left 520ms cubic-bezier(0.2, 0.9, 0.2, 1), top 520ms cubic-bezier(0.2, 0.9, 0.2, 1); +} + +.oa-vault-pet:focus-visible { + outline: 2px solid var(--interactive-accent); + outline-offset: 4px; + border-radius: 999px; +} + +.oa-vault-pet.is-dragging { + cursor: grabbing; + transition: none; +} + +.oa-vault-pet-creature { + position: relative; + width: 112px; + height: 112px; + animation: oa-pet-bob 3.8s ease-in-out infinite; + transform-origin: 50% 72%; +} + +.oa-vault-pet.is-excited .oa-vault-pet-creature, +.oa-vault-pet.is-petted .oa-vault-pet-creature { + animation: oa-pet-party 720ms cubic-bezier(0.2, 1.4, 0.3, 1); +} + +.oa-vault-pet.is-wandering .oa-vault-pet-creature { + animation: oa-pet-swim 840ms ease-in-out; +} + +.oa-vault-pet.is-summoned .oa-vault-pet-creature { + animation: oa-pet-summon 900ms cubic-bezier(0.16, 1.2, 0.28, 1); +} + +.oa-vault-pet-mascot { + position: absolute; + inset: 0; + width: 112px; + height: 112px; + object-fit: contain; + display: block; + pointer-events: none; + -webkit-user-drag: none; + user-select: none; +} + +.oa-vault-pet-shadow { + position: absolute; + left: 26px; + bottom: 0; + width: 66px; + height: 14px; + border-radius: 999px; + background: rgb(0 0 0 / 0.18); + filter: blur(3px); + animation: oa-pet-shadow 3.8s ease-in-out infinite; +} + +.oa-vault-pet-bubble { + position: absolute; + left: 50%; + bottom: 119px; + width: max-content; + min-width: 160px; + max-width: min(360px, calc(100vw - 36px)); + padding: 9px 12px; + border: 1px solid color-mix(in srgb, var(--background-modifier-border) 70%, transparent); + border-radius: 12px; + background: color-mix(in srgb, var(--background-primary) 94%, transparent); + color: var(--text-normal); + box-shadow: 0 12px 28px rgb(0 0 0 / 0.18); + font-size: 0.78rem; + line-height: 1.35; + text-align: center; + opacity: 0; + pointer-events: none; + transform: translate(-50%, 8px) scale(0.96); + transition: opacity 160ms ease, transform 160ms ease; + white-space: normal; + overflow-wrap: anywhere; + word-break: normal; +} + +.oa-vault-pet-bubble::after { + content: ""; + position: absolute; + left: 50%; + bottom: -7px; + width: 12px; + height: 12px; + border-right: 1px solid color-mix(in srgb, var(--background-modifier-border) 70%, transparent); + border-bottom: 1px solid color-mix(in srgb, var(--background-modifier-border) 70%, transparent); + background: color-mix(in srgb, var(--background-primary) 94%, transparent); + transform: translateX(-50%) rotate(45deg); +} + +.oa-vault-pet.has-message .oa-vault-pet-bubble, +.oa-vault-pet:hover .oa-vault-pet-bubble, +.oa-vault-pet:focus-visible .oa-vault-pet-bubble { + opacity: 1; + transform: translate(-50%, 0) scale(1); +} + +.oa-vault-pet-chat { + position: absolute; + right: 86px; + bottom: 30px; + width: min(360px, calc(100vw - 42px)); + display: grid; + gap: 7px; + padding: 10px; + border: 1px solid color-mix(in srgb, var(--background-modifier-border) 76%, transparent); + border-radius: 12px; + background: color-mix(in srgb, var(--background-primary) 96%, transparent); + box-shadow: 0 18px 38px rgb(0 0 0 / 0.24); + opacity: 0; + pointer-events: none; + transform: translateY(10px) scale(0.97); + transform-origin: 100% 100%; + transition: opacity 160ms ease, transform 160ms ease; +} + +.oa-vault-pet.is-chat-right .oa-vault-pet-chat { + left: 86px; + right: auto; + transform-origin: 0 100%; +} + +.oa-vault-pet.has-chat .oa-vault-pet-chat { + opacity: 1; + pointer-events: auto; + transform: translateY(0) scale(1); +} + +.oa-vault-pet.has-chat .oa-vault-pet-bubble { + opacity: 0; +} + +.oa-vault-pet-chat-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} + +.oa-vault-pet-chat-title { + min-width: 0; + color: var(--text-normal); + font-size: 0.84rem; + font-weight: 700; + line-height: 1.2; +} + +.oa-vault-pet-chat-close { + width: 24px; + height: 24px; + min-width: 24px; + min-height: 24px; + padding: 0; + border-radius: 7px; + display: inline-flex; + align-items: center; + justify-content: center; + background: transparent; + color: var(--text-muted); + box-shadow: none; +} + +.oa-vault-pet-chat-path, +.oa-vault-pet-chat-cwd { + color: var(--text-muted); + font-size: 0.72rem; + line-height: 1.3; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.oa-vault-pet-chat-input { + width: 100%; + min-height: 118px; + max-height: 280px; + resize: vertical; + box-sizing: border-box; + padding: 9px 10px; + border: 1px solid color-mix(in srgb, var(--background-modifier-border) 78%, transparent); + border-radius: 10px; + background: color-mix(in srgb, var(--background-secondary) 60%, transparent); + color: var(--text-normal); + font-size: 0.8rem; + line-height: 1.42; + box-shadow: none; +} + +.oa-vault-pet-chat-input:focus { + outline: none; + border-color: color-mix(in srgb, var(--interactive-accent) 48%, var(--background-modifier-border)); + box-shadow: 0 0 0 2px color-mix(in srgb, var(--interactive-accent) 16%, transparent); +} + +.oa-vault-pet-chat-actions { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} + +.oa-vault-pet-chat-hint { + min-width: 0; + color: var(--text-faint); + font-size: 0.7rem; + line-height: 1.2; +} + +.oa-vault-pet-chat-send { + min-height: 28px; + padding: 0 11px; + border-radius: 8px; + background: var(--interactive-accent); + color: var(--text-on-accent); + box-shadow: none; + font-size: 0.78rem; + font-weight: 650; +} + +.oa-vault-pet-chat-send:hover:not(:disabled) { + background: var(--interactive-accent-hover); + color: var(--text-on-accent); +} + +.oa-vault-pet-chat-send:disabled, +.oa-vault-pet-chat-input:disabled { + opacity: 0.58; +} + +@keyframes oa-pet-bob { + 0%, 100% { transform: translateY(0) rotate(-1deg); } + 50% { transform: translateY(-9px) rotate(1.5deg); } +} + +@keyframes oa-pet-shadow { + 0%, 100% { transform: scaleX(1); opacity: 0.72; } + 50% { transform: scaleX(0.78); opacity: 0.42; } +} + +@keyframes oa-pet-party { + 0% { transform: translateY(0) rotate(0); } + 30% { transform: translateY(-18px) rotate(-8deg) scale(1.04); } + 60% { transform: translateY(-6px) rotate(7deg) scale(1.02); } + 100% { transform: translateY(0) rotate(0); } +} + +@keyframes oa-pet-swim { + 0%, 100% { transform: translateX(0) rotate(0); } + 35% { transform: translateX(-8px) rotate(-5deg); } + 70% { transform: translateX(6px) rotate(4deg); } +} + +@keyframes oa-pet-summon { + 0% { transform: translateY(8px) rotate(-8deg) scale(0.92); } + 45% { transform: translateY(-17px) rotate(7deg) scale(1.05); } + 72% { transform: translateY(-4px) rotate(-3deg) scale(1.02); } + 100% { transform: translateY(0) rotate(0) scale(1); } +} + +@media (prefers-reduced-motion: reduce) { + .oa-vault-pet, + .oa-vault-pet *, + .oa-vault-pet::before, + .oa-vault-pet::after { + animation: none !important; + transition: none !important; + } +} + +@media (min-width: 680px) { + .openagent-view .oa-settings-section .setting-item { + grid-template-columns: minmax(0, 1fr) minmax(180px, 220px); + align-items: center; + } + + .openagent-view .oa-settings-section .setting-item-control { + grid-template-columns: minmax(0, 1fr) auto; + justify-self: end; + width: min(100%, 220px); + } + + .oa-settings-actions { + grid-template-columns: repeat(2, minmax(0, max-content)); + } + + .oa-settings-actions button { + width: auto; + } +} diff --git a/vault-pet-mascot.png b/vault-pet-mascot.png new file mode 100644 index 0000000..2eacf08 Binary files /dev/null and b/vault-pet-mascot.png differ