zidane 9.1.9 → 9.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{acp-Bha_yCgU.js → acp-CxFKfjZ7.js} +3 -3
- package/dist/{acp-Bha_yCgU.js.map → acp-CxFKfjZ7.js.map} +1 -1
- package/dist/acp-cli.js +4 -4
- package/dist/acp.js +1 -1
- package/dist/{auth-store-port-BvxVXx9g.js → auth-store-port-8BMUBfqw.js} +2 -2
- package/dist/{auth-store-port-BvxVXx9g.js.map → auth-store-port-8BMUBfqw.js.map} +1 -1
- package/dist/{auto-update-restart-CF3uPnr4.js → auto-update-restart-_S0w600V.js} +10 -10
- package/dist/{auto-update-restart-CF3uPnr4.js.map → auto-update-restart-_S0w600V.js.map} +1 -1
- package/dist/chat.js +9 -9
- package/dist/{cli-shared-C17TUz6H.js → cli-shared-C6qeun-_.js} +3 -3
- package/dist/{cli-shared-C17TUz6H.js.map → cli-shared-C6qeun-_.js.map} +1 -1
- package/dist/{credentials-BZP-MgtZ.js → credentials-BPG17Xuo.js} +2 -2
- package/dist/{credentials-BZP-MgtZ.js.map → credentials-BPG17Xuo.js.map} +1 -1
- package/dist/daemon-cli.js +2 -2
- package/dist/{discovery-Czp7bZZP.js → discovery-DWH6e_zd.js} +4 -4
- package/dist/{discovery-Czp7bZZP.js.map → discovery-DWH6e_zd.js.map} +1 -1
- package/dist/{engine-CDuOhwbt.js → engine-C_Ndjp0u.js} +3 -3
- package/dist/{engine-CDuOhwbt.js.map → engine-C_Ndjp0u.js.map} +1 -1
- package/dist/{executor-CoHqHsCp.js → executor-BUWZhsfn.js} +2 -2
- package/dist/{executor-CoHqHsCp.js.map → executor-BUWZhsfn.js.map} +1 -1
- package/dist/extensions/testing.js +2 -2
- package/dist/{extensions-BEaWxX4e.js → extensions-D4psC965.js} +6 -6
- package/dist/{extensions-BEaWxX4e.js.map → extensions-D4psC965.js.map} +1 -1
- package/dist/extensions.js +5 -5
- package/dist/{google-Bzt7uvy9.js → google-CTQu7Zz-.js} +2 -2
- package/dist/{google-Bzt7uvy9.js.map → google-CTQu7Zz-.js.map} +1 -1
- package/dist/{host-DkqB2P7L.js → host-D-sOuh0K.js} +9 -9
- package/dist/{host-DkqB2P7L.js.map → host-D-sOuh0K.js.map} +1 -1
- package/dist/{host-signals-Ca_oUeYl.js → host-signals-Dlxd0YYP.js} +5 -5
- package/dist/{host-signals-Ca_oUeYl.js.map → host-signals-Dlxd0YYP.js.map} +1 -1
- package/dist/{host-version-BwON7XSr.js → host-version-smAFds1j.js} +2 -2
- package/dist/{host-version-BwON7XSr.js.map → host-version-smAFds1j.js.map} +1 -1
- package/dist/index.js +5 -5
- package/dist/ipc/remote.js +3 -3
- package/dist/{ipc-Dlqz_xIe.js → ipc-DnSKoy00.js} +4 -4
- package/dist/{ipc-Dlqz_xIe.js.map → ipc-DnSKoy00.js.map} +1 -1
- package/dist/ipc.js +4 -4
- package/dist/{links-B5Dz7jku.js → links-BcfGBoRt.js} +8 -8
- package/dist/{links-B5Dz7jku.js.map → links-BcfGBoRt.js.map} +1 -1
- package/dist/{loaded-entry-BGN69Fns.js → loaded-entry-C0h8OMwN.js} +2 -2
- package/dist/{loaded-entry-BGN69Fns.js.map → loaded-entry-C0h8OMwN.js.map} +1 -1
- package/dist/{memory-ZWFqa7di.js → memory-CZNbjYxJ.js} +15 -15
- package/dist/{memory-ZWFqa7di.js.map → memory-CZNbjYxJ.js.map} +1 -1
- package/dist/{path-display-B3Jeqx9E.js → path-display-DfFjoqUy.js} +3 -3
- package/dist/{path-display-B3Jeqx9E.js.map → path-display-DfFjoqUy.js.map} +1 -1
- package/dist/providers/google.js +1 -1
- package/dist/{providers-ChifjOYS.js → providers-CCPHw25p.js} +2 -2
- package/dist/{providers-ChifjOYS.js.map → providers-CCPHw25p.js.map} +1 -1
- package/dist/providers.js +1 -1
- package/dist/{registry-mmAX5qBS.js → registry-C-U_tMPi.js} +2 -2
- package/dist/{registry-mmAX5qBS.js.map → registry-C-U_tMPi.js.map} +1 -1
- package/dist/{registry-CYEN4C4M.js → registry-DHSQ4fdV.js} +2 -2
- package/dist/{registry-CYEN4C4M.js.map → registry-DHSQ4fdV.js.map} +1 -1
- package/dist/{registry-TW3C94iD.js → registry-d-R3YZs1.js} +2 -2
- package/dist/{registry-TW3C94iD.js.map → registry-d-R3YZs1.js.map} +1 -1
- package/dist/{remote-hosts-Ctg9fwQY.js → remote-hosts-D77F5uTa.js} +4 -4
- package/dist/{remote-hosts-Ctg9fwQY.js.map → remote-hosts-D77F5uTa.js.map} +1 -1
- package/dist/{setting-DPim5iCP.js → setting-Cog4WQc1.js} +2 -2
- package/dist/{setting-DPim5iCP.js.map → setting-Cog4WQc1.js.map} +1 -1
- package/dist/{settings-keys-BIWy4T_0.js → settings-keys-B1q2NwvM.js} +2 -2
- package/dist/{settings-keys-BIWy4T_0.js.map → settings-keys-B1q2NwvM.js.map} +1 -1
- package/dist/{system-freeze-CEuTQzNu.js → system-freeze-CMY7QnoD.js} +2 -2
- package/dist/{system-freeze-CEuTQzNu.js.map → system-freeze-CMY7QnoD.js.map} +1 -1
- package/dist/tui.js +15 -15
- package/dist/{version-CU8ppdCQ.js → version-DiGN0H-_.js} +2 -2
- package/dist/{version-CU8ppdCQ.js.map → version-DiGN0H-_.js.map} +1 -1
- package/dist/{wire-GwOPpxJg.js → wire-D4NlIUMg.js} +2 -2
- package/dist/{wire-GwOPpxJg.js.map → wire-D4NlIUMg.js.map} +1 -1
- package/dist/{ws-CqlZkB4w.js → ws-6EUa8pzM.js} +4 -4
- package/dist/{ws-CqlZkB4w.js.map → ws-6EUa8pzM.js.map} +1 -1
- package/dist/{ws-client-Bn_asPwE.js → ws-client-5i2lbQnC.js} +2 -2
- package/dist/{ws-client-Bn_asPwE.js.map → ws-client-5i2lbQnC.js.map} +1 -1
- package/package.json +1 -1
package/dist/daemon-cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { i as fixPath, r as startIpcDaemon, t as connectInProcess } from "./memory-
|
|
2
|
+
import { i as fixPath, r as startIpcDaemon, t as connectInProcess } from "./memory-CZNbjYxJ.js";
|
|
3
3
|
import { t as resolveStorageDirs } from "./xdg-DFSAQQG8.js";
|
|
4
|
-
import { l as defaultSocketPath } from "./settings-keys-
|
|
4
|
+
import { l as defaultSocketPath } from "./settings-keys-B1q2NwvM.js";
|
|
5
5
|
import { t as cliStyle } from "./cli-style-DBEm6CyT.js";
|
|
6
6
|
import { createRequire } from "node:module";
|
|
7
7
|
import { join } from "node:path";
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-BhDjJH2R.js";
|
|
2
2
|
import { g as errorMessage } from "./errors-BuX_vK2H.js";
|
|
3
|
-
import { Ht as looksLikeDefinition, qt as isSettingsIcon } from "./registry-
|
|
3
|
+
import { Ht as looksLikeDefinition, qt as isSettingsIcon } from "./registry-d-R3YZs1.js";
|
|
4
4
|
import { t as createDebugLog } from "./debug-log-DvAIzaYW.js";
|
|
5
5
|
import { t as projectUserPaths } from "./project-user-paths-Chsi83mp.js";
|
|
6
|
-
import { t as checkZidaneVersion } from "./host-version-
|
|
6
|
+
import { t as checkZidaneVersion } from "./host-version-smAFds1j.js";
|
|
7
7
|
import { r as createLogger, t as consoleSink } from "./logger-BQj8QCJp.js";
|
|
8
8
|
import { a as registerExtensionApiHostModules, s as resolveExtensionHostModules } from "./host-modules-C2GGiSjg.js";
|
|
9
|
-
import { a as runtimeLoadedExtension, t as NO_DEFINITION_ERROR } from "./loaded-entry-
|
|
9
|
+
import { a as runtimeLoadedExtension, t as NO_DEFINITION_ERROR } from "./loaded-entry-C0h8OMwN.js";
|
|
10
10
|
import { createRequire } from "node:module";
|
|
11
11
|
import { basename, delimiter, dirname, join, resolve, sep } from "node:path";
|
|
12
12
|
import { lstatSync, readFileSync, readdirSync, realpathSync, rmSync, statSync } from "node:fs";
|
|
@@ -620,4 +620,4 @@ function isExtensionManifest(value) {
|
|
|
620
620
|
//#endregion
|
|
621
621
|
export { precompileExtension as _, discovery_exports as a, defineExtension as b, inlineLoadedExtension as c, parseExtensionScanPathEnv as d, readExtensionManifest as f, COMPILE_CACHE_MAX_AGE_MS as g, withTimeout as h, discoverExtensions as i, loadExtensionFolder as l, scanExtensionDirectories as m, defaultExtensionScanPaths as n, findEntryFile as o, releaseExtensionModules as p, definitionExport as r, importExtensionEntry as s, ENTRY_CANDIDATES as t, loadableExtensionDirs as u, pruneExtensionCompileCache as v, EXTENSION_API_VERSION as y };
|
|
622
622
|
|
|
623
|
-
//# sourceMappingURL=discovery-
|
|
623
|
+
//# sourceMappingURL=discovery-DWH6e_zd.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discovery-Czp7bZZP.js","names":[],"sources":["../src/extensions/api/define.ts","../src/extensions/compile-cache.ts","../src/extensions/with-timeout.ts","../src/extensions/discovery.ts"],"sourcesContent":["/**\n * Extension API: the definition an extension module exports.\n */\n\nimport type { ExtensionContext } from './context'\nimport type { Cleanup } from './core'\nimport type { SettingsSchema } from './settings'\n\n/** The API version this runtime supports. */\nexport const EXTENSION_API_VERSION = 1\n\n/**\n * An extension. Export it as the module's default export, either through\n * `defineExtension` or as a plain object (no import needed).\n *\n * @typeParam S - The settings schema, so `ctx.settings.get` is typed.\n */\nexport interface ExtensionDefinition<S extends SettingsSchema = SettingsSchema> {\n /** Must be `1`. */\n apiVersion: 1\n /** Lowercase id, words joined by `-`. Equals the folder name for discovered extensions. Prefixes every id the extension contributes. */\n id: string\n /** Name shown in Settings. Defaults to `id`. */\n displayName?: string\n /**\n * The extension's icon in each app's vocabulary: `gui` an icon class for\n * the desktop and web apps (a Phosphor class such as `'i-ph-git-branch'`),\n * `tui` a short glyph for the terminal app. The settings sidebar draws it\n * on the extension's entry. Absent: a letter badge of the display name.\n */\n icon?: { gui?: string, tui?: string }\n /** Semver of the extension. */\n version?: string\n /** One line shown in Settings. */\n description?: string\n /**\n * Other extensions this one builds on: `{ id: semver range }`. Sets the load\n * order and gives install hints. A missing dependency never fails a load;\n * `ctx.use` reports it as inactive.\n */\n dependencies?: Readonly<Record<string, string>>\n /** Settings the extension reads. Shown in Settings even when the extension is off. */\n settings?: S\n /** Entry exporting React components for the desktop and web apps. Default `ui.tsx` when present. */\n ui?: string\n /** Entry exporting OpenTUI React components for the terminal app. Default `tui.tsx` when present. */\n tui?: string\n /**\n * Runs once per load. Register what the extension adds. May return a\n * cleanup that runs when the extension unloads. Registrations made later\n * (after an await, from a handler) work until the extension unloads.\n */\n setup: (ctx: ExtensionContext<S>) => void | Cleanup | Promise<void | Cleanup>\n}\n\n/**\n * Returns `definition` unchanged. It anchors type inference for `ctx`.\n *\n * @param definition - The extension.\n * @returns The same `definition`.\n */\nexport function defineExtension<S extends SettingsSchema = SettingsSchema>(definition: ExtensionDefinition<S>): ExtensionDefinition<S> {\n return definition\n}\n","/**\n * Compiled extension code kept on disk, for Node hosts (the desktop app):\n * they load extensions through jiti, which compiles each TypeScript file\n * with Babel on the calling thread, tens of ms per file, on every load. Bun\n * hosts import natively and never use it.\n *\n * jiti's own file cache does the keeping: one entry per source path (an edit\n * rewrites it), valid only while it ends with the hash of the source it was\n * compiled from, so a changed file is compiled again, never served stale. A\n * load and {@link precompileExtension} write the same entries (the same\n * options, {@link extensionJitiOptions}), so a host can compile on another\n * thread ahead of a load, and the load only reads.\n *\n * Entries are written for files that change; an entry not rewritten in\n * {@link COMPILE_CACHE_MAX_AGE_MS} goes ({@link pruneExtensionCompileCache}),\n * and a file still in use compiles once more.\n */\nimport type { JitiOptions } from 'jiti'\nimport type { Dirent } from 'node:fs'\nimport { readdirSync, readFileSync, realpathSync, rmSync, statSync } from 'node:fs'\nimport { join } from 'node:path'\n\n/** How old an entry may grow without being rewritten before {@link pruneExtensionCompileCache} drops it. */\nexport const COMPILE_CACHE_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000\n\n/** Files jiti always compiles: TypeScript. */\nconst COMPILED_FILE = /\\.[cm]?tsx?$/\n\n/**\n * jiti, imported by a name the Bun binary's bundler leaves alone: a Node host\n * resolves it from zidane's dependencies, the Bun binary never runs it.\n */\nexport async function loadJiti(): Promise<typeof import('jiti')> {\n const specifier = 'jiti'\n return import(specifier) as Promise<typeof import('jiti')>\n}\n\n/**\n * The jiti options every extension load and precompile share: the cache\n * entries' names depend on them, so both must use exactly these.\n *\n * @param compileCache - The cache folder; none: nothing is kept.\n */\nexport function extensionJitiOptions(compileCache: string | undefined): JitiOptions {\n return { moduleCache: true, fsCache: compileCache ?? false, interopDefault: false }\n}\n\n/**\n * The names jiti keeps a file under: the path a load is given (the entry, by\n * the scanned folder), and the real path Node's resolver gives the files it\n * imports. They differ only below a symlinked folder (macOS's `/var`).\n */\nfunction cacheNames(path: string): string[] {\n try {\n const real = realpathSync(path)\n return real === path ? [path] : [path, real]\n }\n catch {\n return [path]\n }\n}\n\n/** An extension folder's TypeScript files, `node_modules` and dot-folders left out. */\nfunction compiledFiles(dir: string): string[] {\n const files: string[] = []\n const walk = (folder: string): void => {\n let entries: Dirent[]\n try {\n entries = readdirSync(folder, { withFileTypes: true })\n }\n catch {\n return\n }\n for (const entry of entries) {\n if (entry.name === 'node_modules' || entry.name.startsWith('.'))\n continue\n const path = join(folder, entry.name)\n if (entry.isDirectory())\n walk(path)\n else if (COMPILED_FILE.test(entry.name))\n files.push(path)\n }\n }\n walk(dir)\n return files\n}\n\n/**\n * Compiles an extension folder's TypeScript into the cache, as a load would.\n * Files whose entry is current are only checked. A file that does not compile\n * is skipped (the load reports it).\n *\n * @param dir - The extension folder.\n * @param compileCache - The cache folder.\n * @returns The files checked or compiled.\n */\nexport async function precompileExtension(dir: string, compileCache: string): Promise<number> {\n const { createJiti } = await loadJiti()\n const jiti = createJiti(dir, extensionJitiOptions(compileCache))\n const files = compiledFiles(dir)\n for (const file of files) {\n let source: string\n try {\n source = readFileSync(file, 'utf8')\n }\n catch {\n continue\n }\n for (const filename of cacheNames(file)) {\n try {\n // What a load passes for a module it imports (`async`): the same entry name and content.\n jiti.transform({ filename, source, ts: true, async: true, jsx: jiti.options.jsx })\n }\n catch {\n // Not compilable: the load says why.\n }\n }\n }\n return files.length\n}\n\n/**\n * Drops the entries not rewritten in `maxAgeMs`.\n *\n * @param compileCache - The cache folder.\n * @param maxAgeMs - The age past which an entry goes.\n * @param now - The current time.\n * @returns The entries dropped.\n */\nexport function pruneExtensionCompileCache(compileCache: string, maxAgeMs = COMPILE_CACHE_MAX_AGE_MS, now = Date.now()): number {\n let names: string[]\n try {\n names = readdirSync(compileCache)\n }\n catch {\n return 0\n }\n let dropped = 0\n for (const name of names) {\n const path = join(compileCache, name)\n try {\n if (now - statSync(path).mtimeMs <= maxAgeMs)\n continue\n rmSync(path, { force: true })\n dropped += 1\n }\n catch {\n // Gone meanwhile.\n }\n }\n return dropped\n}\n","/**\n * Internal: race a promise against a timeout. Shared by the discovery\n * loader (module imports) and the registry builder (extension `setup()`).\n * The timer is always cleared so a settled promise doesn't pin the\n * event loop for the full timeout.\n */\nexport function withTimeout<T>(\n promise: Promise<T>,\n timeoutMs: number,\n message: string,\n onTimeout?: () => void,\n): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(() => {\n onTimeout?.()\n reject(new Error(message))\n }, timeoutMs)\n })\n return Promise.race([promise, timeout]).finally(() => {\n if (timer)\n clearTimeout(timer)\n })\n}\n","/**\n * Extension discovery.\n *\n * Walks the standard project + user roots\n * (`{project,user}/.{agents,zidane}/extensions/<name>/`) using the\n * shared {@link projectUserPaths} convention, then dynamic-imports only\n * trusted `<name>/index.{ts,tsx,mjs,js}` files (probed in\n * that preference order — see {@link ENTRY_CANDIDATES}). The first valid\n * module per name wins — project beats user, `.agents/` beats\n * `.<prefix>/` within a single scope (see `projectUserPaths` for the\n * full order).\n *\n * Loading uses native `await import(pathToFileURL(...))` under Bun, which\n * handles `.ts`/`.tsx` and JS variants in `bun run` and in\n * `bun build --compile` binaries; Node hosts load through jiti (see\n * {@link importExtensionEntry}).\n *\n * Before each import the extension directory's module graph is EVICTED\n * from the module cache (`require.cache`: Bun's covers ESM too, jiti's\n * Node graph lives there), so\n * re-running discovery — the `/reload` command, a cwd-change rebuild, an\n * overwrite-install — loads the current on-disk code, deep imports\n * included. Module-level state resets per reload; the teardown contract\n * covers cleanup of the outgoing instance.\n *\n * Failures are isolation-first: a broken module produces a\n * {@link LoadedExtension} with `error` set instead of throwing — one\n * vibe-coded syntax error mustn't keep the TUI from booting.\n */\n\nimport type { LogSink } from '../logger'\nimport type { ExtensionDefinition } from './api/define'\nimport type { ExtensionManifest, LoadedExtension, LoadedExtensionSource } from './types'\nimport { lstatSync, readdirSync, readFileSync, realpathSync } from 'node:fs'\nimport { createRequire } from 'node:module'\nimport { basename, delimiter, dirname, resolve, sep } from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport { projectUserPaths } from '../chat/project-user-paths'\nimport { createDebugLog } from '../debug-log'\nimport { errorMessage } from '../errors'\nimport { consoleSink, createLogger } from '../logger'\nimport { EXTENSION_API_VERSION } from './api/define'\nimport { extensionJitiOptions, loadJiti } from './compile-cache'\nimport { registerExtensionApiHostModules, resolveExtensionHostModules } from './host-modules'\nimport { checkZidaneVersion } from './host-version'\nimport { NO_DEFINITION_ERROR, runtimeLoadedExtension } from './runtime/loaded-entry'\nimport { looksLikeDefinition } from './runtime/validate-definition'\nimport { isSettingsIcon } from './settings/validate'\nimport { withTimeout } from './with-timeout'\n\n/**\n * Scoped `ZIDANE_DEBUG` logger. A `const` rather than a hoisted function\n * declaration, so it must precede its call sites.\n */\nconst debugLog = createDebugLog('extensions')\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/**\n * Entry-point filenames probed inside each `<name>/` directory, in\n * preference order. Bun loads `.ts` / `.tsx` natively; `.mjs` / `.js`\n * are accepted so pre-compiled extensions (or vibe-coded JS-only ones)\n * work too. `index.js` is last because most authors will reach for\n * `.ts` first.\n */\nconst ENTRY_CANDIDATES = ['index.ts', 'index.tsx', 'index.mjs', 'index.js'] as const\n\n/** A discovered (but not yet loaded) extension entry. */\ninterface ExtensionScanEntry {\n /** Folder name — the canonical persisted extension identity. */\n name: string\n /** Absolute path to the extension directory. */\n dir: string\n /** Absolute path to the entry file picked from `ENTRY_CANDIDATES`. */\n entry: string\n /** Manifest read from the extension, when it has a valid one. */\n manifest?: ExtensionManifest\n /** Where the discovery root lives. */\n source: LoadedExtensionSource\n}\n\ninterface ExtensionScanRoot {\n path: string\n source: 'project' | 'user'\n}\n\n/**\n * One extension folder a config entry links in place (a local `source` in\n * `config.json`), scanned ahead of every root. Its scope gates it like a\n * scan root's: a `project` link loads only in a trusted project.\n */\nexport interface LinkedExtensionDir {\n /** Absolute extension folder. */\n readonly dir: string\n /** The config file that links it. */\n readonly source: 'project' | 'user'\n}\n\n// ---------------------------------------------------------------------------\n// Path resolution\n// ---------------------------------------------------------------------------\n\n/**\n * Default scan roots for extension discovery. Mirrors\n * {@link defaultSkillScanPaths} / {@link defaultMcpsConfigPaths}; the\n * subPath is `'extensions'` so each root expands to\n * `<scope>/.<convention>/extensions/`.\n *\n * @param opts - Overrides for the working directory (`cwd`), home directory (`home`) and storage prefix (`prefix`).\n */\nexport function defaultExtensionScanPaths(opts: {\n cwd?: string\n home?: string\n prefix?: string\n} = {}): ExtensionScanRoot[] {\n return projectUserPaths({ subPath: 'extensions', ...opts })\n}\n\n/**\n * Parse a platform-delimited list of additional extension scan roots.\n * Useful for local dogfooding / host-provided packages:\n *\n * ZIDANE_EXTENSION_DIRS=/repo/extensions bun run tui\n *\n * Each path is a scan ROOT whose children are extension dirs/packages\n * (`<root>/<name>/index.ts`), not a single extension directory.\n *\n * @param value - Delimited list of scan roots. Default: `ZIDANE_EXTENSION_DIRS`.\n */\nexport function parseExtensionScanPathEnv(value: string | undefined = process.env.ZIDANE_EXTENSION_DIRS): string[] {\n if (!value)\n return []\n return value.split(delimiter).map(p => p.trim()).filter(Boolean)\n}\n\n/**\n * Walk every scan root, collecting one {@link ExtensionScanEntry} per\n * `<root>/<name>/index.*` hit. First-found per `name` wins (matches the\n * project-user precedence used elsewhere — see `projectUserPaths` JSDoc).\n *\n * Non-existent roots are skipped silently. Stat errors inside a present\n * root are surfaced under `ZIDANE_DEBUG` only — they shouldn't bring\n * boot to a halt.\n *\n * @param opts - Overrides for `cwd`, `home` and `prefix`, extra scan roots in `extensionScanPaths`, and config-linked folders in `linkedExtensionDirs` (scanned first).\n */\nexport function scanExtensionDirectories(opts: {\n cwd?: string\n home?: string\n prefix?: string\n extensionScanPaths?: readonly string[]\n linkedExtensionDirs?: readonly LinkedExtensionDir[]\n} = {}): ExtensionScanEntry[] {\n const seen = new Map<string, ExtensionScanEntry>()\n for (const entry of scanAllExtensionDirectories(opts)) {\n if (!seen.has(entry.name))\n seen.set(entry.name, entry)\n }\n return [...seen.values()]\n}\n\nfunction scanAllExtensionDirectories(opts: {\n cwd?: string\n home?: string\n prefix?: string\n extensionScanPaths?: readonly string[]\n linkedExtensionDirs?: readonly LinkedExtensionDir[]\n} = {}): ExtensionScanEntry[] {\n const roots: ExtensionScanRoot[] = [\n ...(opts.extensionScanPaths ?? []).map(path => ({ path: resolve(path), source: 'user' as const })),\n ...defaultExtensionScanPaths(opts),\n ]\n const entriesOut: ExtensionScanEntry[] = []\n\n // Linked folders were named one by one in a config file: no root containment to check.\n for (const link of opts.linkedExtensionDirs ?? []) {\n const dir = resolve(link.dir)\n const entry = findEntryFile(dir)\n if (entry)\n entriesOut.push({ name: basename(dir), dir, entry, source: link.source, ...readExtensionManifest(dir) })\n }\n\n for (const root of roots) {\n try {\n // The scan root and its parent (`.zidane` / `.agents`) are part of\n // the trust boundary too — a symlinked parent would let every entry\n // resolve outside the configured roots while the per-entry lstat\n // checks below still pass.\n if (lstatSync(dirname(root.path)).isSymbolicLink())\n continue\n if (!lstatSync(root.path).isDirectory())\n continue\n }\n catch {\n continue\n }\n let realRoot: string\n try {\n realRoot = realpathSync(root.path)\n }\n catch (err) {\n debugLog(`realpath ${root.path}`, err)\n continue\n }\n let entries: string[]\n try {\n // readdir order is the filesystem's (sorted on APFS, hash or creation\n // order on ext4 / tmpfs); load order must not depend on it.\n entries = readdirSync(root.path).sort()\n }\n catch (err) {\n debugLog(`readdir ${root.path}`, err)\n continue\n }\n for (const name of entries) {\n if (name.startsWith('.'))\n continue\n const dir = resolve(root.path, name)\n try {\n // Extension roots and entry files are trust boundaries. Do not follow\n // symlinks out of the configured scan roots.\n if (!lstatSync(dir).isDirectory())\n continue\n }\n catch (err) {\n debugLog(`stat ${dir}`, err)\n continue\n }\n const entry = findEntryFile(dir)\n if (!entry)\n continue\n // Containment: the canonical entry file must stay under the\n // canonical scan root. Catches symlinks at any level the lstat\n // checks above didn't cover.\n try {\n const realEntry = realpathSync(entry)\n if (!realEntry.startsWith(realRoot + sep)) {\n debugLog(`skip ${entry}`, new Error(`resolves outside scan root ${realRoot}`))\n continue\n }\n }\n catch (err) {\n debugLog(`realpath ${entry}`, err)\n continue\n }\n entriesOut.push({ name, dir, entry, source: root.source, ...readExtensionManifest(dir) })\n }\n }\n return entriesOut\n}\n\n/**\n * Pick the first existing `index.*` file inside `dir`, or `null` if none.\n * Exported for the installer, which probes fetched trees with the same rule.\n */\nexport function findEntryFile(dir: string): string | null {\n for (const candidate of ENTRY_CANDIDATES) {\n const full = resolve(dir, candidate)\n try {\n if (lstatSync(full).isFile())\n return full\n }\n catch {\n continue\n }\n }\n return null\n}\n\n// ---------------------------------------------------------------------------\n// Loading\n// ---------------------------------------------------------------------------\n\n/**\n * Discover + load every trusted extension reachable from the default\n * scan paths — including ones the user's enable allowlist has toggled\n * OFF. Disabled extensions still import (and later run `setup()`) so\n * their config schema is browsable in Settings before enabling; the\n * allowlist gates their CONTRIBUTIONS at registry level, not their code.\n * The trust boundary stays with `trustProjectExtensions`. Returns one {@link LoadedExtension} per scan entry:\n * skipped entries have no definition, failed entries carry `error`.\n * Loaders never throw — the host can decide what to do with load errors\n * (typically: ship them to the Settings panel).\n *\n * Imports run in parallel: a slow extension can't block the rest. The\n * returned array preserves the original scan order (project-first,\n * then user) so first-found-wins precedence stays intact.\n *\n * `signal` is honored opportunistically — if the abort fires BEFORE\n * we dispatch imports, the whole set is skipped. Once `Promise.all` is\n * in flight, individual imports can't be cancelled (the module system\n * has no abort affordance).\n *\n * Project-scoped extensions execute arbitrary local code, so they're\n * skipped unless `trustProjectExtensions` resolves to `true` — either\n * via the option or the `ZIDANE_TRUST_PROJECT_EXTENSIONS=1` env var\n * fallback. User-scoped extensions always load.\n *\n * Each extension's module graph is evicted from the runtime cache\n * before import, so calling this again (e.g. `/reload`) re-evaluates\n * the current on-disk code — edits, overwrites, and updates apply\n * without a restart.\n *\n * @param opts - Scan overrides (`cwd`, `home`, `prefix`, `extensionScanPaths`), an abort `signal`, `trustProjectExtensions`, `importTimeoutMs` (default 10000) as the per-extension import limit, and the `sink` non-blocking load warnings go to (default: `warn`+ to the console, like `buildExtensionRegistry`).\n */\nexport async function discoverExtensions(opts: {\n cwd?: string\n home?: string\n prefix?: string\n extensionScanPaths?: readonly string[]\n linkedExtensionDirs?: readonly LinkedExtensionDir[]\n signal?: AbortSignal\n trustProjectExtensions?: boolean\n importTimeoutMs?: number\n sink?: LogSink\n /** Node hosts: the folder compiled extension code is kept in (`./compile-cache`). */\n compileCache?: string\n} = {}): Promise<LoadedExtension[]> {\n const entries = selectExtensionEntries(scanAllExtensionDirectories(opts), opts)\n if (opts.signal?.aborted || entries.length === 0)\n return []\n const sink = opts.sink ?? consoleSink({ minLevel: 'warn' })\n return Promise.all(entries.map((entry) => {\n if (!('entry' in entry))\n return Promise.resolve(entry)\n return loadExtensionFromDisk(entry, opts.importTimeoutMs ?? 10_000, sink, opts.compileCache)\n }))\n}\n\n/**\n * The folders {@link discoverExtensions} would import for the same options:\n * a trusted extension's, never an untrusted project's. What a host compiles\n * ahead of a load (`precompileExtension`).\n *\n * @param opts - Scan overrides and `trustProjectExtensions`, as for {@link discoverExtensions}.\n */\nexport function loadableExtensionDirs(opts: {\n cwd?: string\n home?: string\n prefix?: string\n extensionScanPaths?: readonly string[]\n linkedExtensionDirs?: readonly LinkedExtensionDir[]\n trustProjectExtensions?: boolean\n} = {}): string[] {\n return selectExtensionEntries(scanAllExtensionDirectories(opts), opts).flatMap(entry => ('entry' in entry ? [entry.dir] : []))\n}\n\nfunction selectExtensionEntries(\n entries: readonly ExtensionScanEntry[],\n opts: { trustProjectExtensions?: boolean },\n): Array<ExtensionScanEntry | LoadedExtension> {\n // Safe by default: project extensions are untrusted unless the host\n // opts in explicitly or the user sets the env var the error message\n // below points at.\n const trustProjectExtensions = opts.trustProjectExtensions\n ?? (process.env.ZIDANE_TRUST_PROJECT_EXTENSIONS === '1')\n const selected = new Map<string, ExtensionScanEntry | LoadedExtension>()\n const locked = new Set<string>()\n\n for (const entry of entries) {\n if (locked.has(entry.name))\n continue\n if (entry.source === 'project' && !trustProjectExtensions) {\n if (!selected.has(entry.name)) {\n selected.set(entry.name, {\n name: entry.name,\n dir: entry.dir,\n source: entry.source,\n ...(entry.manifest ? { manifest: entry.manifest } : {}),\n error: 'Project extensions are disabled until this project is trusted. Set ZIDANE_TRUST_PROJECT_EXTENSIONS=1 or pass trustProjectExtensions to enable them.',\n })\n }\n continue\n }\n selected.set(entry.name, entry)\n locked.add(entry.name)\n }\n\n return [...selected.values()]\n}\n\nconst requireForCache = createRequire(import.meta.url)\n\n/**\n * Bun only: monotonic import generation, appended as a query so each\n * discovery pass gets a fresh ENTRY module. The query alone is not enough: relative\n * imports resolve to plain file paths and hit the cache, which is why\n * {@link evictModuleGraph} runs first. Both together are what makes\n * `/reload` a true hot-reload (verified empirically: query-only keeps\n * deep imports stale; eviction-only keeps the entry stale).\n */\nlet importGeneration = 0\n\n/**\n * Evict everything under `dir` from the module cache. Under Bun,\n * `require.cache` deletion covers the ES registry for these keys and the\n * entry itself is refreshed by the generation query above; under Node,\n * jiti keeps the whole graph there, entry included, so eviction alone\n * refreshes it. The extension's own `node_modules` are evicted\n * too so a dependency bump applies on update without a restart.\n */\nfunction evictModuleGraph(dir: string): void {\n const cache = requireForCache.cache\n if (!cache)\n return\n // Cache keys are PHYSICAL paths — realpath the dir or the prefix match\n // silently misses through symlinks (macOS `/var` → `/private/var`).\n let physical: string\n try {\n physical = realpathSync(dir)\n }\n catch {\n physical = dir\n }\n for (const root of physical === dir ? [dir] : [dir, physical]) {\n const prefix = root.endsWith(sep) ? root : root + sep\n for (const key of Object.keys(cache)) {\n if (key.startsWith(prefix))\n delete cache[key]\n }\n }\n}\n\n/**\n * Import an extension entry fresh from disk, its relative import graph\n * included. Every import of an extension goes through here (discovery and\n * install validation), so both runtimes get the same reload semantics.\n *\n * Bun: native `import()` — `.ts`/`.tsx`, extensionless relative imports\n * and host modules (`Bun.plugin`) all resolve natively.\n *\n * Node (the Electron GUI): jiti, NOT native `import()`. Node's ESM cache\n * can't be evicted, so a cache-busting query per reload would pin every\n * generation's module graph for the life of the process. jiti evaluates\n * the graph as CJS into `require.cache`, which {@link evictModuleGraph}\n * clears — old generations become collectible once their registry is\n * disposed (asserted by test/extensions/node-reload.test.ts). Stable URLs\n * also keep the cache flat. jiti additionally gives Node full TS/TSX and\n * `./foo` → `foo.ts` resolution, and maps the host's modules\n * (`'zidane'`, …) through `virtualModules` to the LIVE instances.\n * With a `compileCache` folder, compiled files are kept there and only a\n * changed one is compiled again (`./compile-cache`); without one, every\n * load compiles.\n *\n * @param entryPath - The extension's entry file.\n * @param dir - The extension's folder, whose module graph is evicted first.\n * @param options - `compileCache`: Node hosts' compile cache folder.\n * @param options.compileCache - The folder compiled code is kept in.\n */\nexport async function importExtensionEntry(entryPath: string, dir: string, options: { compileCache?: string } = {}): Promise<unknown> {\n // Every import gets the host's zidane, whatever ran before it in this process (an install's check runs\n // where nothing may have loaded extensions yet). Idempotent and lazy.\n registerExtensionApiHostModules()\n evictModuleGraph(dir)\n if (typeof Bun !== 'undefined') {\n importGeneration += 1\n return import(`${pathToFileURL(entryPath).href}?zidane-gen=${importGeneration}`)\n }\n const [{ createJiti }, virtualModules] = await Promise.all([loadJiti(), resolveExtensionHostModules()])\n const jiti = createJiti(entryPath, { ...extensionJitiOptions(options.compileCache), virtualModules })\n return jiti.import(entryPath)\n}\n\n/**\n * Drop an extension directory's modules from the cache without importing\n * again — for a one-off import whose directory is about to move or be\n * deleted (install validation), so nothing stays pinned under that path.\n */\nexport function releaseExtensionModules(dir: string): void {\n evictModuleGraph(dir)\n}\n\n/**\n * Import + validate a single extension entry. Always returns a\n * {@link LoadedExtension} — the load error (if any) lives on the\n * returned object, never as a thrown exception.\n */\nasync function loadExtensionFromDisk(entry: ExtensionScanEntry, timeoutMs: number, sink: LogSink, compileCache?: string): Promise<LoadedExtension> {\n // Before the import: a module written against a newer zidane would fail on\n // a missing export, which says nothing about the actual problem.\n const version = checkZidaneVersion(entry.dir)\n if (version?.level === 'error') {\n return {\n name: entry.name,\n dir: entry.dir,\n source: entry.source,\n ...(entry.manifest ? { manifest: entry.manifest } : {}),\n error: version.message,\n }\n }\n const failed = (error: string): LoadedExtension => ({\n name: entry.name,\n dir: entry.dir,\n source: entry.source,\n ...(entry.manifest ? { manifest: entry.manifest } : {}),\n error,\n })\n const manifestVersion = entry.manifest?.apiVersion\n if (manifestVersion !== undefined && manifestVersion !== EXTENSION_API_VERSION)\n return failed(`Unsupported extension API version \"${manifestVersion}\". This runtime supports apiVersion 1.`)\n if (entry.manifest?.name && entry.manifest.name !== entry.name)\n return failed(`Extension manifest name mismatch: folder is \"${entry.name}\" but manifest declares \"${entry.manifest.name}\".`)\n if (version)\n createLogger(sink, { extension: entry.name }).warn(version.message)\n try {\n const mod = await withTimeout(\n importExtensionEntry(entry.entry, entry.dir, compileCache !== undefined ? { compileCache } : {}),\n timeoutMs,\n `Timed out importing ${entry.entry} after ${timeoutMs}ms. The module's top-level code may still be running; an import can't be cancelled.`,\n )\n const where = { name: entry.name, dir: entry.dir, source: entry.source, ...(entry.manifest ? { manifest: entry.manifest } : {}) }\n const exported = moduleExports(mod)\n const definition = definitionExport(exported)\n if (definition !== undefined)\n return runtimeLoadedExtension(definition, where)\n return failed(`Module at ${entry.entry} does not export an extension definition (expected \\`default\\` or named \\`extension\\` with \\`apiVersion: 1\\`).`)\n }\n catch (err) {\n return {\n name: entry.name,\n dir: entry.dir,\n source: entry.source,\n ...(entry.manifest ? { manifest: entry.manifest } : {}),\n error: `Failed to import ${entry.entry}: ${errorMessage(err)}`,\n }\n }\n}\n\n/**\n * Imports one extension folder fresh from disk, as discovery would (version\n * check, manifest, module graph evicted first). Never rejects: a folder with\n * no entry file or a module that fails to import comes back with `error`.\n * The caller decides whether the folder may load at all (trust, precedence):\n * {@link loadableExtensionDirs} answers that.\n *\n * @param dir - The extension folder.\n * @param opts - Its source, the import bound (default 10 s), the warning sink and Node's compile cache.\n * @param opts.source - Where the folder was found.\n * @param opts.importTimeoutMs - How long the import may take.\n * @param opts.sink - Where load warnings go. Default: warnings to stderr.\n * @param opts.compileCache - Node hosts: the folder compiled code is kept in.\n * @returns The loaded entry, or one carrying the error.\n */\nexport async function loadExtensionFolder(dir: string, opts: { source: LoadedExtensionSource, importTimeoutMs?: number, sink?: LogSink, compileCache?: string }): Promise<LoadedExtension> {\n const name = basename(dir)\n const entry = findEntryFile(dir)\n const manifest = readExtensionManifest(dir)\n if (!entry)\n return { name, dir, source: opts.source, ...manifest, error: `No entry file (${ENTRY_CANDIDATES.join(', ')}) in ${dir}.` }\n return loadExtensionFromDisk({ name, dir, entry, source: opts.source, ...manifest }, opts.importTimeoutMs ?? 10_000, opts.sink ?? consoleSink({ minLevel: 'warn' }), opts.compileCache)\n}\n\n/**\n * The export the runtime validates: one claiming `apiVersion: 1`, else the\n * first object (its validation then says what is missing).\n *\n * @param exported - A module's `default` and `extension` exports.\n * @returns The export, or `undefined` when none is an object.\n */\nexport function definitionExport(exported: readonly unknown[]): unknown {\n return exported.find(looksLikeDefinition) ?? exported.find(value => !!value && typeof value === 'object')\n}\n\n/** The `default` and `extension` exports of a module, in that order of preference. */\nfunction moduleExports(mod: unknown): unknown[] {\n if (!mod || typeof mod !== 'object')\n return []\n const m = mod as Record<string, unknown>\n return [m.default, m.extension].filter(value => value !== undefined)\n}\n\n/**\n * Tag an inline (host-passed) extension into the same shape disk-\n * loaded ones produce, so host-bundled extensions (e.g. those passed\n * via `RunTuiOptions.extensions`) flow through `buildExtensionRegistry`\n * identically to disk-discovered ones. The runtime validates the\n * definition and lists it inactive with what is wrong.\n *\n * @param definition - Extension definition the host built in code.\n */\nexport function inlineLoadedExtension(definition: Pick<ExtensionDefinition, 'apiVersion'> | object): LoadedExtension {\n if (definition && typeof definition === 'object')\n return runtimeLoadedExtension(definition, { source: 'inline' })\n return { name: 'unknown', source: 'inline', error: NO_DEFINITION_ERROR }\n}\n\n/**\n * Read `extension.json` (or `package.json#zidane`) as a shape-validated\n * manifest. Exported for the installer so \"what counts as a manifest\" has\n * exactly one definition — a malformed manifest is ignored identically at\n * install-validation time and at discovery time.\n */\nexport function readExtensionManifest(dir: string): { manifest?: ExtensionManifest } {\n const fromExtensionJson = readJson(resolve(dir, 'extension.json'))\n if (isExtensionManifest(fromExtensionJson))\n return { manifest: fromExtensionJson }\n const pkg = readJson(resolve(dir, 'package.json'))\n if (pkg && typeof pkg === 'object' && !Array.isArray(pkg)) {\n const manifest = (pkg as { zidane?: unknown }).zidane\n if (isExtensionManifest(manifest))\n return { manifest }\n }\n return {}\n}\n\nfunction readJson(path: string): unknown {\n try {\n return JSON.parse(readFileSync(path, 'utf8')) as unknown\n }\n catch {\n return null\n }\n}\n\nfunction isExtensionManifest(value: unknown): value is ExtensionManifest {\n if (!value || typeof value !== 'object' || Array.isArray(value))\n return false\n const v = value as Record<string, unknown>\n // `apiVersion`, when present, must be a number. We still recognize an\n // unsupported version (e.g. 1) as a manifest so the loader can surface a\n // clear \"unsupported API version\" error rather than silently ignoring it —\n // but the rest of the shape is still validated.\n if (v.apiVersion !== undefined && typeof v.apiVersion !== 'number')\n return false\n return (v.name === undefined || typeof v.name === 'string')\n && (v.displayName === undefined || typeof v.displayName === 'string')\n && (v.description === undefined || typeof v.description === 'string')\n && (v.icon === undefined || isSettingsIcon(v.icon))\n}\n\n// ---------------------------------------------------------------------------\n// Internals\n// ---------------------------------------------------------------------------\n\n/** Re-export for tests / advanced hosts that want to log the scan results before loading. */\nexport type { ExtensionScanEntry }\n/** Re-export so callers can read the canonical entry file list. */\nexport { ENTRY_CANDIDATES }\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"discovery-DWH6e_zd.js","names":[],"sources":["../src/extensions/api/define.ts","../src/extensions/compile-cache.ts","../src/extensions/with-timeout.ts","../src/extensions/discovery.ts"],"sourcesContent":["/**\n * Extension API: the definition an extension module exports.\n */\n\nimport type { ExtensionContext } from './context'\nimport type { Cleanup } from './core'\nimport type { SettingsSchema } from './settings'\n\n/** The API version this runtime supports. */\nexport const EXTENSION_API_VERSION = 1\n\n/**\n * An extension. Export it as the module's default export, either through\n * `defineExtension` or as a plain object (no import needed).\n *\n * @typeParam S - The settings schema, so `ctx.settings.get` is typed.\n */\nexport interface ExtensionDefinition<S extends SettingsSchema = SettingsSchema> {\n /** Must be `1`. */\n apiVersion: 1\n /** Lowercase id, words joined by `-`. Equals the folder name for discovered extensions. Prefixes every id the extension contributes. */\n id: string\n /** Name shown in Settings. Defaults to `id`. */\n displayName?: string\n /**\n * The extension's icon in each app's vocabulary: `gui` an icon class for\n * the desktop and web apps (a Phosphor class such as `'i-ph-git-branch'`),\n * `tui` a short glyph for the terminal app. The settings sidebar draws it\n * on the extension's entry. Absent: a letter badge of the display name.\n */\n icon?: { gui?: string, tui?: string }\n /** Semver of the extension. */\n version?: string\n /** One line shown in Settings. */\n description?: string\n /**\n * Other extensions this one builds on: `{ id: semver range }`. Sets the load\n * order and gives install hints. A missing dependency never fails a load;\n * `ctx.use` reports it as inactive.\n */\n dependencies?: Readonly<Record<string, string>>\n /** Settings the extension reads. Shown in Settings even when the extension is off. */\n settings?: S\n /** Entry exporting React components for the desktop and web apps. Default `ui.tsx` when present. */\n ui?: string\n /** Entry exporting OpenTUI React components for the terminal app. Default `tui.tsx` when present. */\n tui?: string\n /**\n * Runs once per load. Register what the extension adds. May return a\n * cleanup that runs when the extension unloads. Registrations made later\n * (after an await, from a handler) work until the extension unloads.\n */\n setup: (ctx: ExtensionContext<S>) => void | Cleanup | Promise<void | Cleanup>\n}\n\n/**\n * Returns `definition` unchanged. It anchors type inference for `ctx`.\n *\n * @param definition - The extension.\n * @returns The same `definition`.\n */\nexport function defineExtension<S extends SettingsSchema = SettingsSchema>(definition: ExtensionDefinition<S>): ExtensionDefinition<S> {\n return definition\n}\n","/**\n * Compiled extension code kept on disk, for Node hosts (the desktop app):\n * they load extensions through jiti, which compiles each TypeScript file\n * with Babel on the calling thread, tens of ms per file, on every load. Bun\n * hosts import natively and never use it.\n *\n * jiti's own file cache does the keeping: one entry per source path (an edit\n * rewrites it), valid only while it ends with the hash of the source it was\n * compiled from, so a changed file is compiled again, never served stale. A\n * load and {@link precompileExtension} write the same entries (the same\n * options, {@link extensionJitiOptions}), so a host can compile on another\n * thread ahead of a load, and the load only reads.\n *\n * Entries are written for files that change; an entry not rewritten in\n * {@link COMPILE_CACHE_MAX_AGE_MS} goes ({@link pruneExtensionCompileCache}),\n * and a file still in use compiles once more.\n */\nimport type { JitiOptions } from 'jiti'\nimport type { Dirent } from 'node:fs'\nimport { readdirSync, readFileSync, realpathSync, rmSync, statSync } from 'node:fs'\nimport { join } from 'node:path'\n\n/** How old an entry may grow without being rewritten before {@link pruneExtensionCompileCache} drops it. */\nexport const COMPILE_CACHE_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000\n\n/** Files jiti always compiles: TypeScript. */\nconst COMPILED_FILE = /\\.[cm]?tsx?$/\n\n/**\n * jiti, imported by a name the Bun binary's bundler leaves alone: a Node host\n * resolves it from zidane's dependencies, the Bun binary never runs it.\n */\nexport async function loadJiti(): Promise<typeof import('jiti')> {\n const specifier = 'jiti'\n return import(specifier) as Promise<typeof import('jiti')>\n}\n\n/**\n * The jiti options every extension load and precompile share: the cache\n * entries' names depend on them, so both must use exactly these.\n *\n * @param compileCache - The cache folder; none: nothing is kept.\n */\nexport function extensionJitiOptions(compileCache: string | undefined): JitiOptions {\n return { moduleCache: true, fsCache: compileCache ?? false, interopDefault: false }\n}\n\n/**\n * The names jiti keeps a file under: the path a load is given (the entry, by\n * the scanned folder), and the real path Node's resolver gives the files it\n * imports. They differ only below a symlinked folder (macOS's `/var`).\n */\nfunction cacheNames(path: string): string[] {\n try {\n const real = realpathSync(path)\n return real === path ? [path] : [path, real]\n }\n catch {\n return [path]\n }\n}\n\n/** An extension folder's TypeScript files, `node_modules` and dot-folders left out. */\nfunction compiledFiles(dir: string): string[] {\n const files: string[] = []\n const walk = (folder: string): void => {\n let entries: Dirent[]\n try {\n entries = readdirSync(folder, { withFileTypes: true })\n }\n catch {\n return\n }\n for (const entry of entries) {\n if (entry.name === 'node_modules' || entry.name.startsWith('.'))\n continue\n const path = join(folder, entry.name)\n if (entry.isDirectory())\n walk(path)\n else if (COMPILED_FILE.test(entry.name))\n files.push(path)\n }\n }\n walk(dir)\n return files\n}\n\n/**\n * Compiles an extension folder's TypeScript into the cache, as a load would.\n * Files whose entry is current are only checked. A file that does not compile\n * is skipped (the load reports it).\n *\n * @param dir - The extension folder.\n * @param compileCache - The cache folder.\n * @returns The files checked or compiled.\n */\nexport async function precompileExtension(dir: string, compileCache: string): Promise<number> {\n const { createJiti } = await loadJiti()\n const jiti = createJiti(dir, extensionJitiOptions(compileCache))\n const files = compiledFiles(dir)\n for (const file of files) {\n let source: string\n try {\n source = readFileSync(file, 'utf8')\n }\n catch {\n continue\n }\n for (const filename of cacheNames(file)) {\n try {\n // What a load passes for a module it imports (`async`): the same entry name and content.\n jiti.transform({ filename, source, ts: true, async: true, jsx: jiti.options.jsx })\n }\n catch {\n // Not compilable: the load says why.\n }\n }\n }\n return files.length\n}\n\n/**\n * Drops the entries not rewritten in `maxAgeMs`.\n *\n * @param compileCache - The cache folder.\n * @param maxAgeMs - The age past which an entry goes.\n * @param now - The current time.\n * @returns The entries dropped.\n */\nexport function pruneExtensionCompileCache(compileCache: string, maxAgeMs = COMPILE_CACHE_MAX_AGE_MS, now = Date.now()): number {\n let names: string[]\n try {\n names = readdirSync(compileCache)\n }\n catch {\n return 0\n }\n let dropped = 0\n for (const name of names) {\n const path = join(compileCache, name)\n try {\n if (now - statSync(path).mtimeMs <= maxAgeMs)\n continue\n rmSync(path, { force: true })\n dropped += 1\n }\n catch {\n // Gone meanwhile.\n }\n }\n return dropped\n}\n","/**\n * Internal: race a promise against a timeout. Shared by the discovery\n * loader (module imports) and the registry builder (extension `setup()`).\n * The timer is always cleared so a settled promise doesn't pin the\n * event loop for the full timeout.\n */\nexport function withTimeout<T>(\n promise: Promise<T>,\n timeoutMs: number,\n message: string,\n onTimeout?: () => void,\n): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(() => {\n onTimeout?.()\n reject(new Error(message))\n }, timeoutMs)\n })\n return Promise.race([promise, timeout]).finally(() => {\n if (timer)\n clearTimeout(timer)\n })\n}\n","/**\n * Extension discovery.\n *\n * Walks the standard project + user roots\n * (`{project,user}/.{agents,zidane}/extensions/<name>/`) using the\n * shared {@link projectUserPaths} convention, then dynamic-imports only\n * trusted `<name>/index.{ts,tsx,mjs,js}` files (probed in\n * that preference order — see {@link ENTRY_CANDIDATES}). The first valid\n * module per name wins — project beats user, `.agents/` beats\n * `.<prefix>/` within a single scope (see `projectUserPaths` for the\n * full order).\n *\n * Loading uses native `await import(pathToFileURL(...))` under Bun, which\n * handles `.ts`/`.tsx` and JS variants in `bun run` and in\n * `bun build --compile` binaries; Node hosts load through jiti (see\n * {@link importExtensionEntry}).\n *\n * Before each import the extension directory's module graph is EVICTED\n * from the module cache (`require.cache`: Bun's covers ESM too, jiti's\n * Node graph lives there), so\n * re-running discovery — the `/reload` command, a cwd-change rebuild, an\n * overwrite-install — loads the current on-disk code, deep imports\n * included. Module-level state resets per reload; the teardown contract\n * covers cleanup of the outgoing instance.\n *\n * Failures are isolation-first: a broken module produces a\n * {@link LoadedExtension} with `error` set instead of throwing — one\n * vibe-coded syntax error mustn't keep the TUI from booting.\n */\n\nimport type { LogSink } from '../logger'\nimport type { ExtensionDefinition } from './api/define'\nimport type { ExtensionManifest, LoadedExtension, LoadedExtensionSource } from './types'\nimport { lstatSync, readdirSync, readFileSync, realpathSync } from 'node:fs'\nimport { createRequire } from 'node:module'\nimport { basename, delimiter, dirname, resolve, sep } from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport { projectUserPaths } from '../chat/project-user-paths'\nimport { createDebugLog } from '../debug-log'\nimport { errorMessage } from '../errors'\nimport { consoleSink, createLogger } from '../logger'\nimport { EXTENSION_API_VERSION } from './api/define'\nimport { extensionJitiOptions, loadJiti } from './compile-cache'\nimport { registerExtensionApiHostModules, resolveExtensionHostModules } from './host-modules'\nimport { checkZidaneVersion } from './host-version'\nimport { NO_DEFINITION_ERROR, runtimeLoadedExtension } from './runtime/loaded-entry'\nimport { looksLikeDefinition } from './runtime/validate-definition'\nimport { isSettingsIcon } from './settings/validate'\nimport { withTimeout } from './with-timeout'\n\n/**\n * Scoped `ZIDANE_DEBUG` logger. A `const` rather than a hoisted function\n * declaration, so it must precede its call sites.\n */\nconst debugLog = createDebugLog('extensions')\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/**\n * Entry-point filenames probed inside each `<name>/` directory, in\n * preference order. Bun loads `.ts` / `.tsx` natively; `.mjs` / `.js`\n * are accepted so pre-compiled extensions (or vibe-coded JS-only ones)\n * work too. `index.js` is last because most authors will reach for\n * `.ts` first.\n */\nconst ENTRY_CANDIDATES = ['index.ts', 'index.tsx', 'index.mjs', 'index.js'] as const\n\n/** A discovered (but not yet loaded) extension entry. */\ninterface ExtensionScanEntry {\n /** Folder name — the canonical persisted extension identity. */\n name: string\n /** Absolute path to the extension directory. */\n dir: string\n /** Absolute path to the entry file picked from `ENTRY_CANDIDATES`. */\n entry: string\n /** Manifest read from the extension, when it has a valid one. */\n manifest?: ExtensionManifest\n /** Where the discovery root lives. */\n source: LoadedExtensionSource\n}\n\ninterface ExtensionScanRoot {\n path: string\n source: 'project' | 'user'\n}\n\n/**\n * One extension folder a config entry links in place (a local `source` in\n * `config.json`), scanned ahead of every root. Its scope gates it like a\n * scan root's: a `project` link loads only in a trusted project.\n */\nexport interface LinkedExtensionDir {\n /** Absolute extension folder. */\n readonly dir: string\n /** The config file that links it. */\n readonly source: 'project' | 'user'\n}\n\n// ---------------------------------------------------------------------------\n// Path resolution\n// ---------------------------------------------------------------------------\n\n/**\n * Default scan roots for extension discovery. Mirrors\n * {@link defaultSkillScanPaths} / {@link defaultMcpsConfigPaths}; the\n * subPath is `'extensions'` so each root expands to\n * `<scope>/.<convention>/extensions/`.\n *\n * @param opts - Overrides for the working directory (`cwd`), home directory (`home`) and storage prefix (`prefix`).\n */\nexport function defaultExtensionScanPaths(opts: {\n cwd?: string\n home?: string\n prefix?: string\n} = {}): ExtensionScanRoot[] {\n return projectUserPaths({ subPath: 'extensions', ...opts })\n}\n\n/**\n * Parse a platform-delimited list of additional extension scan roots.\n * Useful for local dogfooding / host-provided packages:\n *\n * ZIDANE_EXTENSION_DIRS=/repo/extensions bun run tui\n *\n * Each path is a scan ROOT whose children are extension dirs/packages\n * (`<root>/<name>/index.ts`), not a single extension directory.\n *\n * @param value - Delimited list of scan roots. Default: `ZIDANE_EXTENSION_DIRS`.\n */\nexport function parseExtensionScanPathEnv(value: string | undefined = process.env.ZIDANE_EXTENSION_DIRS): string[] {\n if (!value)\n return []\n return value.split(delimiter).map(p => p.trim()).filter(Boolean)\n}\n\n/**\n * Walk every scan root, collecting one {@link ExtensionScanEntry} per\n * `<root>/<name>/index.*` hit. First-found per `name` wins (matches the\n * project-user precedence used elsewhere — see `projectUserPaths` JSDoc).\n *\n * Non-existent roots are skipped silently. Stat errors inside a present\n * root are surfaced under `ZIDANE_DEBUG` only — they shouldn't bring\n * boot to a halt.\n *\n * @param opts - Overrides for `cwd`, `home` and `prefix`, extra scan roots in `extensionScanPaths`, and config-linked folders in `linkedExtensionDirs` (scanned first).\n */\nexport function scanExtensionDirectories(opts: {\n cwd?: string\n home?: string\n prefix?: string\n extensionScanPaths?: readonly string[]\n linkedExtensionDirs?: readonly LinkedExtensionDir[]\n} = {}): ExtensionScanEntry[] {\n const seen = new Map<string, ExtensionScanEntry>()\n for (const entry of scanAllExtensionDirectories(opts)) {\n if (!seen.has(entry.name))\n seen.set(entry.name, entry)\n }\n return [...seen.values()]\n}\n\nfunction scanAllExtensionDirectories(opts: {\n cwd?: string\n home?: string\n prefix?: string\n extensionScanPaths?: readonly string[]\n linkedExtensionDirs?: readonly LinkedExtensionDir[]\n} = {}): ExtensionScanEntry[] {\n const roots: ExtensionScanRoot[] = [\n ...(opts.extensionScanPaths ?? []).map(path => ({ path: resolve(path), source: 'user' as const })),\n ...defaultExtensionScanPaths(opts),\n ]\n const entriesOut: ExtensionScanEntry[] = []\n\n // Linked folders were named one by one in a config file: no root containment to check.\n for (const link of opts.linkedExtensionDirs ?? []) {\n const dir = resolve(link.dir)\n const entry = findEntryFile(dir)\n if (entry)\n entriesOut.push({ name: basename(dir), dir, entry, source: link.source, ...readExtensionManifest(dir) })\n }\n\n for (const root of roots) {\n try {\n // The scan root and its parent (`.zidane` / `.agents`) are part of\n // the trust boundary too — a symlinked parent would let every entry\n // resolve outside the configured roots while the per-entry lstat\n // checks below still pass.\n if (lstatSync(dirname(root.path)).isSymbolicLink())\n continue\n if (!lstatSync(root.path).isDirectory())\n continue\n }\n catch {\n continue\n }\n let realRoot: string\n try {\n realRoot = realpathSync(root.path)\n }\n catch (err) {\n debugLog(`realpath ${root.path}`, err)\n continue\n }\n let entries: string[]\n try {\n // readdir order is the filesystem's (sorted on APFS, hash or creation\n // order on ext4 / tmpfs); load order must not depend on it.\n entries = readdirSync(root.path).sort()\n }\n catch (err) {\n debugLog(`readdir ${root.path}`, err)\n continue\n }\n for (const name of entries) {\n if (name.startsWith('.'))\n continue\n const dir = resolve(root.path, name)\n try {\n // Extension roots and entry files are trust boundaries. Do not follow\n // symlinks out of the configured scan roots.\n if (!lstatSync(dir).isDirectory())\n continue\n }\n catch (err) {\n debugLog(`stat ${dir}`, err)\n continue\n }\n const entry = findEntryFile(dir)\n if (!entry)\n continue\n // Containment: the canonical entry file must stay under the\n // canonical scan root. Catches symlinks at any level the lstat\n // checks above didn't cover.\n try {\n const realEntry = realpathSync(entry)\n if (!realEntry.startsWith(realRoot + sep)) {\n debugLog(`skip ${entry}`, new Error(`resolves outside scan root ${realRoot}`))\n continue\n }\n }\n catch (err) {\n debugLog(`realpath ${entry}`, err)\n continue\n }\n entriesOut.push({ name, dir, entry, source: root.source, ...readExtensionManifest(dir) })\n }\n }\n return entriesOut\n}\n\n/**\n * Pick the first existing `index.*` file inside `dir`, or `null` if none.\n * Exported for the installer, which probes fetched trees with the same rule.\n */\nexport function findEntryFile(dir: string): string | null {\n for (const candidate of ENTRY_CANDIDATES) {\n const full = resolve(dir, candidate)\n try {\n if (lstatSync(full).isFile())\n return full\n }\n catch {\n continue\n }\n }\n return null\n}\n\n// ---------------------------------------------------------------------------\n// Loading\n// ---------------------------------------------------------------------------\n\n/**\n * Discover + load every trusted extension reachable from the default\n * scan paths — including ones the user's enable allowlist has toggled\n * OFF. Disabled extensions still import (and later run `setup()`) so\n * their config schema is browsable in Settings before enabling; the\n * allowlist gates their CONTRIBUTIONS at registry level, not their code.\n * The trust boundary stays with `trustProjectExtensions`. Returns one {@link LoadedExtension} per scan entry:\n * skipped entries have no definition, failed entries carry `error`.\n * Loaders never throw — the host can decide what to do with load errors\n * (typically: ship them to the Settings panel).\n *\n * Imports run in parallel: a slow extension can't block the rest. The\n * returned array preserves the original scan order (project-first,\n * then user) so first-found-wins precedence stays intact.\n *\n * `signal` is honored opportunistically — if the abort fires BEFORE\n * we dispatch imports, the whole set is skipped. Once `Promise.all` is\n * in flight, individual imports can't be cancelled (the module system\n * has no abort affordance).\n *\n * Project-scoped extensions execute arbitrary local code, so they're\n * skipped unless `trustProjectExtensions` resolves to `true` — either\n * via the option or the `ZIDANE_TRUST_PROJECT_EXTENSIONS=1` env var\n * fallback. User-scoped extensions always load.\n *\n * Each extension's module graph is evicted from the runtime cache\n * before import, so calling this again (e.g. `/reload`) re-evaluates\n * the current on-disk code — edits, overwrites, and updates apply\n * without a restart.\n *\n * @param opts - Scan overrides (`cwd`, `home`, `prefix`, `extensionScanPaths`), an abort `signal`, `trustProjectExtensions`, `importTimeoutMs` (default 10000) as the per-extension import limit, and the `sink` non-blocking load warnings go to (default: `warn`+ to the console, like `buildExtensionRegistry`).\n */\nexport async function discoverExtensions(opts: {\n cwd?: string\n home?: string\n prefix?: string\n extensionScanPaths?: readonly string[]\n linkedExtensionDirs?: readonly LinkedExtensionDir[]\n signal?: AbortSignal\n trustProjectExtensions?: boolean\n importTimeoutMs?: number\n sink?: LogSink\n /** Node hosts: the folder compiled extension code is kept in (`./compile-cache`). */\n compileCache?: string\n} = {}): Promise<LoadedExtension[]> {\n const entries = selectExtensionEntries(scanAllExtensionDirectories(opts), opts)\n if (opts.signal?.aborted || entries.length === 0)\n return []\n const sink = opts.sink ?? consoleSink({ minLevel: 'warn' })\n return Promise.all(entries.map((entry) => {\n if (!('entry' in entry))\n return Promise.resolve(entry)\n return loadExtensionFromDisk(entry, opts.importTimeoutMs ?? 10_000, sink, opts.compileCache)\n }))\n}\n\n/**\n * The folders {@link discoverExtensions} would import for the same options:\n * a trusted extension's, never an untrusted project's. What a host compiles\n * ahead of a load (`precompileExtension`).\n *\n * @param opts - Scan overrides and `trustProjectExtensions`, as for {@link discoverExtensions}.\n */\nexport function loadableExtensionDirs(opts: {\n cwd?: string\n home?: string\n prefix?: string\n extensionScanPaths?: readonly string[]\n linkedExtensionDirs?: readonly LinkedExtensionDir[]\n trustProjectExtensions?: boolean\n} = {}): string[] {\n return selectExtensionEntries(scanAllExtensionDirectories(opts), opts).flatMap(entry => ('entry' in entry ? [entry.dir] : []))\n}\n\nfunction selectExtensionEntries(\n entries: readonly ExtensionScanEntry[],\n opts: { trustProjectExtensions?: boolean },\n): Array<ExtensionScanEntry | LoadedExtension> {\n // Safe by default: project extensions are untrusted unless the host\n // opts in explicitly or the user sets the env var the error message\n // below points at.\n const trustProjectExtensions = opts.trustProjectExtensions\n ?? (process.env.ZIDANE_TRUST_PROJECT_EXTENSIONS === '1')\n const selected = new Map<string, ExtensionScanEntry | LoadedExtension>()\n const locked = new Set<string>()\n\n for (const entry of entries) {\n if (locked.has(entry.name))\n continue\n if (entry.source === 'project' && !trustProjectExtensions) {\n if (!selected.has(entry.name)) {\n selected.set(entry.name, {\n name: entry.name,\n dir: entry.dir,\n source: entry.source,\n ...(entry.manifest ? { manifest: entry.manifest } : {}),\n error: 'Project extensions are disabled until this project is trusted. Set ZIDANE_TRUST_PROJECT_EXTENSIONS=1 or pass trustProjectExtensions to enable them.',\n })\n }\n continue\n }\n selected.set(entry.name, entry)\n locked.add(entry.name)\n }\n\n return [...selected.values()]\n}\n\nconst requireForCache = createRequire(import.meta.url)\n\n/**\n * Bun only: monotonic import generation, appended as a query so each\n * discovery pass gets a fresh ENTRY module. The query alone is not enough: relative\n * imports resolve to plain file paths and hit the cache, which is why\n * {@link evictModuleGraph} runs first. Both together are what makes\n * `/reload` a true hot-reload (verified empirically: query-only keeps\n * deep imports stale; eviction-only keeps the entry stale).\n */\nlet importGeneration = 0\n\n/**\n * Evict everything under `dir` from the module cache. Under Bun,\n * `require.cache` deletion covers the ES registry for these keys and the\n * entry itself is refreshed by the generation query above; under Node,\n * jiti keeps the whole graph there, entry included, so eviction alone\n * refreshes it. The extension's own `node_modules` are evicted\n * too so a dependency bump applies on update without a restart.\n */\nfunction evictModuleGraph(dir: string): void {\n const cache = requireForCache.cache\n if (!cache)\n return\n // Cache keys are PHYSICAL paths — realpath the dir or the prefix match\n // silently misses through symlinks (macOS `/var` → `/private/var`).\n let physical: string\n try {\n physical = realpathSync(dir)\n }\n catch {\n physical = dir\n }\n for (const root of physical === dir ? [dir] : [dir, physical]) {\n const prefix = root.endsWith(sep) ? root : root + sep\n for (const key of Object.keys(cache)) {\n if (key.startsWith(prefix))\n delete cache[key]\n }\n }\n}\n\n/**\n * Import an extension entry fresh from disk, its relative import graph\n * included. Every import of an extension goes through here (discovery and\n * install validation), so both runtimes get the same reload semantics.\n *\n * Bun: native `import()` — `.ts`/`.tsx`, extensionless relative imports\n * and host modules (`Bun.plugin`) all resolve natively.\n *\n * Node (the Electron GUI): jiti, NOT native `import()`. Node's ESM cache\n * can't be evicted, so a cache-busting query per reload would pin every\n * generation's module graph for the life of the process. jiti evaluates\n * the graph as CJS into `require.cache`, which {@link evictModuleGraph}\n * clears — old generations become collectible once their registry is\n * disposed (asserted by test/extensions/node-reload.test.ts). Stable URLs\n * also keep the cache flat. jiti additionally gives Node full TS/TSX and\n * `./foo` → `foo.ts` resolution, and maps the host's modules\n * (`'zidane'`, …) through `virtualModules` to the LIVE instances.\n * With a `compileCache` folder, compiled files are kept there and only a\n * changed one is compiled again (`./compile-cache`); without one, every\n * load compiles.\n *\n * @param entryPath - The extension's entry file.\n * @param dir - The extension's folder, whose module graph is evicted first.\n * @param options - `compileCache`: Node hosts' compile cache folder.\n * @param options.compileCache - The folder compiled code is kept in.\n */\nexport async function importExtensionEntry(entryPath: string, dir: string, options: { compileCache?: string } = {}): Promise<unknown> {\n // Every import gets the host's zidane, whatever ran before it in this process (an install's check runs\n // where nothing may have loaded extensions yet). Idempotent and lazy.\n registerExtensionApiHostModules()\n evictModuleGraph(dir)\n if (typeof Bun !== 'undefined') {\n importGeneration += 1\n return import(`${pathToFileURL(entryPath).href}?zidane-gen=${importGeneration}`)\n }\n const [{ createJiti }, virtualModules] = await Promise.all([loadJiti(), resolveExtensionHostModules()])\n const jiti = createJiti(entryPath, { ...extensionJitiOptions(options.compileCache), virtualModules })\n return jiti.import(entryPath)\n}\n\n/**\n * Drop an extension directory's modules from the cache without importing\n * again — for a one-off import whose directory is about to move or be\n * deleted (install validation), so nothing stays pinned under that path.\n */\nexport function releaseExtensionModules(dir: string): void {\n evictModuleGraph(dir)\n}\n\n/**\n * Import + validate a single extension entry. Always returns a\n * {@link LoadedExtension} — the load error (if any) lives on the\n * returned object, never as a thrown exception.\n */\nasync function loadExtensionFromDisk(entry: ExtensionScanEntry, timeoutMs: number, sink: LogSink, compileCache?: string): Promise<LoadedExtension> {\n // Before the import: a module written against a newer zidane would fail on\n // a missing export, which says nothing about the actual problem.\n const version = checkZidaneVersion(entry.dir)\n if (version?.level === 'error') {\n return {\n name: entry.name,\n dir: entry.dir,\n source: entry.source,\n ...(entry.manifest ? { manifest: entry.manifest } : {}),\n error: version.message,\n }\n }\n const failed = (error: string): LoadedExtension => ({\n name: entry.name,\n dir: entry.dir,\n source: entry.source,\n ...(entry.manifest ? { manifest: entry.manifest } : {}),\n error,\n })\n const manifestVersion = entry.manifest?.apiVersion\n if (manifestVersion !== undefined && manifestVersion !== EXTENSION_API_VERSION)\n return failed(`Unsupported extension API version \"${manifestVersion}\". This runtime supports apiVersion 1.`)\n if (entry.manifest?.name && entry.manifest.name !== entry.name)\n return failed(`Extension manifest name mismatch: folder is \"${entry.name}\" but manifest declares \"${entry.manifest.name}\".`)\n if (version)\n createLogger(sink, { extension: entry.name }).warn(version.message)\n try {\n const mod = await withTimeout(\n importExtensionEntry(entry.entry, entry.dir, compileCache !== undefined ? { compileCache } : {}),\n timeoutMs,\n `Timed out importing ${entry.entry} after ${timeoutMs}ms. The module's top-level code may still be running; an import can't be cancelled.`,\n )\n const where = { name: entry.name, dir: entry.dir, source: entry.source, ...(entry.manifest ? { manifest: entry.manifest } : {}) }\n const exported = moduleExports(mod)\n const definition = definitionExport(exported)\n if (definition !== undefined)\n return runtimeLoadedExtension(definition, where)\n return failed(`Module at ${entry.entry} does not export an extension definition (expected \\`default\\` or named \\`extension\\` with \\`apiVersion: 1\\`).`)\n }\n catch (err) {\n return {\n name: entry.name,\n dir: entry.dir,\n source: entry.source,\n ...(entry.manifest ? { manifest: entry.manifest } : {}),\n error: `Failed to import ${entry.entry}: ${errorMessage(err)}`,\n }\n }\n}\n\n/**\n * Imports one extension folder fresh from disk, as discovery would (version\n * check, manifest, module graph evicted first). Never rejects: a folder with\n * no entry file or a module that fails to import comes back with `error`.\n * The caller decides whether the folder may load at all (trust, precedence):\n * {@link loadableExtensionDirs} answers that.\n *\n * @param dir - The extension folder.\n * @param opts - Its source, the import bound (default 10 s), the warning sink and Node's compile cache.\n * @param opts.source - Where the folder was found.\n * @param opts.importTimeoutMs - How long the import may take.\n * @param opts.sink - Where load warnings go. Default: warnings to stderr.\n * @param opts.compileCache - Node hosts: the folder compiled code is kept in.\n * @returns The loaded entry, or one carrying the error.\n */\nexport async function loadExtensionFolder(dir: string, opts: { source: LoadedExtensionSource, importTimeoutMs?: number, sink?: LogSink, compileCache?: string }): Promise<LoadedExtension> {\n const name = basename(dir)\n const entry = findEntryFile(dir)\n const manifest = readExtensionManifest(dir)\n if (!entry)\n return { name, dir, source: opts.source, ...manifest, error: `No entry file (${ENTRY_CANDIDATES.join(', ')}) in ${dir}.` }\n return loadExtensionFromDisk({ name, dir, entry, source: opts.source, ...manifest }, opts.importTimeoutMs ?? 10_000, opts.sink ?? consoleSink({ minLevel: 'warn' }), opts.compileCache)\n}\n\n/**\n * The export the runtime validates: one claiming `apiVersion: 1`, else the\n * first object (its validation then says what is missing).\n *\n * @param exported - A module's `default` and `extension` exports.\n * @returns The export, or `undefined` when none is an object.\n */\nexport function definitionExport(exported: readonly unknown[]): unknown {\n return exported.find(looksLikeDefinition) ?? exported.find(value => !!value && typeof value === 'object')\n}\n\n/** The `default` and `extension` exports of a module, in that order of preference. */\nfunction moduleExports(mod: unknown): unknown[] {\n if (!mod || typeof mod !== 'object')\n return []\n const m = mod as Record<string, unknown>\n return [m.default, m.extension].filter(value => value !== undefined)\n}\n\n/**\n * Tag an inline (host-passed) extension into the same shape disk-\n * loaded ones produce, so host-bundled extensions (e.g. those passed\n * via `RunTuiOptions.extensions`) flow through `buildExtensionRegistry`\n * identically to disk-discovered ones. The runtime validates the\n * definition and lists it inactive with what is wrong.\n *\n * @param definition - Extension definition the host built in code.\n */\nexport function inlineLoadedExtension(definition: Pick<ExtensionDefinition, 'apiVersion'> | object): LoadedExtension {\n if (definition && typeof definition === 'object')\n return runtimeLoadedExtension(definition, { source: 'inline' })\n return { name: 'unknown', source: 'inline', error: NO_DEFINITION_ERROR }\n}\n\n/**\n * Read `extension.json` (or `package.json#zidane`) as a shape-validated\n * manifest. Exported for the installer so \"what counts as a manifest\" has\n * exactly one definition — a malformed manifest is ignored identically at\n * install-validation time and at discovery time.\n */\nexport function readExtensionManifest(dir: string): { manifest?: ExtensionManifest } {\n const fromExtensionJson = readJson(resolve(dir, 'extension.json'))\n if (isExtensionManifest(fromExtensionJson))\n return { manifest: fromExtensionJson }\n const pkg = readJson(resolve(dir, 'package.json'))\n if (pkg && typeof pkg === 'object' && !Array.isArray(pkg)) {\n const manifest = (pkg as { zidane?: unknown }).zidane\n if (isExtensionManifest(manifest))\n return { manifest }\n }\n return {}\n}\n\nfunction readJson(path: string): unknown {\n try {\n return JSON.parse(readFileSync(path, 'utf8')) as unknown\n }\n catch {\n return null\n }\n}\n\nfunction isExtensionManifest(value: unknown): value is ExtensionManifest {\n if (!value || typeof value !== 'object' || Array.isArray(value))\n return false\n const v = value as Record<string, unknown>\n // `apiVersion`, when present, must be a number. We still recognize an\n // unsupported version (e.g. 1) as a manifest so the loader can surface a\n // clear \"unsupported API version\" error rather than silently ignoring it —\n // but the rest of the shape is still validated.\n if (v.apiVersion !== undefined && typeof v.apiVersion !== 'number')\n return false\n return (v.name === undefined || typeof v.name === 'string')\n && (v.displayName === undefined || typeof v.displayName === 'string')\n && (v.description === undefined || typeof v.description === 'string')\n && (v.icon === undefined || isSettingsIcon(v.icon))\n}\n\n// ---------------------------------------------------------------------------\n// Internals\n// ---------------------------------------------------------------------------\n\n/** Re-export for tests / advanced hosts that want to log the scan results before loading. */\nexport type { ExtensionScanEntry }\n/** Re-export so callers can read the canonical entry file list. */\nexport { ENTRY_CANDIDATES }\n"],"mappings":"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"}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { i as writeJsonFileAtomic, t as OWNER_ONLY } from "./atomic-write-BewQOXlD.js";
|
|
2
2
|
import { t as projectUserPaths } from "./project-user-paths-Chsi83mp.js";
|
|
3
3
|
import { p as oauthAccountLabel } from "./cred-keys-C0KaSRp7.js";
|
|
4
|
-
import { f as readProviderCredential, m as readProviderCredentials } from "./credentials-
|
|
4
|
+
import { f as readProviderCredential, m as readProviderCredentials } from "./credentials-BPG17Xuo.js";
|
|
5
5
|
import { A as readProjects, p as parseMcpsFile, r as environmentCredentialsPath, s as userSystemMdPath } from "./environment-credentials-D7OqnCn4.js";
|
|
6
6
|
import { d as loadState } from "./store-DqYnBpwH.js";
|
|
7
7
|
import { D as DEFAULT_SETTINGS, N as settingsFromStored } from "./settings-catalog-qhQdrMkb.js";
|
|
8
8
|
import { f as readEnvironmentSettings, o as settingsToWire } from "./host-scoped-settings-Dh4EILMN.js";
|
|
9
|
-
import { n as hashValue, r as isRecord, t as isSyncableSetting } from "./settings-keys-
|
|
9
|
+
import { n as hashValue, r as isRecord, t as isSyncableSetting } from "./settings-keys-B1q2NwvM.js";
|
|
10
10
|
import { a as SYNC_MODES, c as fieldCategory, l as serverSyncCategories, n as SYNCABLE_ENVIRONMENT_KINDS, r as SYNC_CATEGORIES } from "./types-Skz6Z48Z.js";
|
|
11
11
|
import { basename, dirname, join, relative, sep } from "node:path";
|
|
12
12
|
import { existsSync, lstatSync, readFileSync, readdirSync, statSync, watch } from "node:fs";
|
|
@@ -829,4 +829,4 @@ function syncLocalInfo(sources) {
|
|
|
829
829
|
//#endregion
|
|
830
830
|
export { createSyncEngine as t };
|
|
831
831
|
|
|
832
|
-
//# sourceMappingURL=engine-
|
|
832
|
+
//# sourceMappingURL=engine-C_Ndjp0u.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine-CDuOhwbt.js","names":[],"sources":["../src/chat/sync/collect.ts","../src/chat/sync/local.ts","../src/chat/sync/pusher.ts","../src/chat/sync/watchers.ts","../src/chat/sync/engine.ts"],"sourcesContent":["/**\n * This computer's state for each sync category, read from disk as a\n * `sync.push` payload (`./types` `SyncPayloads`): what the user set here,\n * never what a server session put in a window.\n *\n * - settings: `state.json`'s settings over the defaults, syncable keys only;\n * - safelists: `projects.json`;\n * - files: the user-scope `SYSTEM.md`, and the user-scope skills\n * (`~/.agents/skills`, `~/.<prefix>/skills`; the first of a name wins),\n * bounded like the server bounds them (a skill over them is left out);\n * - mcps: the user-scope `mcps.json` files (the first of a name wins);\n * - extensions: user-scope extensions installed from a source (their\n * `.install.json`); the others are listed as not syncable;\n * - keys: the stored API keys of the providers opted in.\n */\nimport type { ProviderDescriptor } from '../providers'\nimport type { SyncCategory, SyncFile, SyncPayloads } from './types'\nimport { existsSync, lstatSync, readdirSync, readFileSync } from 'node:fs'\nimport { join, relative, sep } from 'node:path'\nimport { oauthAccountLabel } from '../cred-keys'\nimport { readProviderCredential, readProviderCredentials } from '../credentials'\nimport { environmentCredentialsPath } from '../environment-credentials'\nimport { readEnvironmentSettings } from '../environment-settings'\nimport { parseMcpsFile } from '../mcps-discovery'\nimport { projectUserPaths } from '../project-user-paths'\nimport { readProjects } from '../safe-mode'\nimport { DEFAULT_SETTINGS, settingsFromStored } from '../settings-defaults'\nimport { loadState } from '../store'\nimport { userSystemMdPath } from '../system-md'\nimport { isSyncableSetting, settingsToWire } from './settings-keys'\nimport { SYNCABLE_ENVIRONMENT_KINDS } from './types'\n\nexport interface SyncSources {\n /** The user dir (`projects.json`, `credentials.json`). */\n dataDir: string\n /** This computer's `state.json`. */\n statePath: string\n home: string\n prefix: string\n providers: () => Readonly<Record<string, ProviderDescriptor>>\n}\n\nconst MAX_FILE_BYTES = 1024 * 1024\nconst MAX_SKILL_BYTES = 4 * 1024 * 1024\nconst MAX_SKILL_FILES = 200\nconst MAX_DEPTH = 8\n/** Not part of a skill: version control and installed dependencies. */\nconst SKIPPED_DIRS = new Set(['.git', 'node_modules'])\n\nfunction userPaths(sources: SyncSources, subPath: string): string[] {\n return projectUserPaths({ subPath, cwd: sources.home, home: sources.home, prefix: sources.prefix }).filter(p => p.source === 'user').map(p => p.path)\n}\n\n/** Every file and folder a category reads: what the watchers watch. */\nexport function syncWatchTargets(sources: SyncSources): { category: SyncCategory, path: string, recursive: boolean }[] {\n return [\n { category: 'settings', path: sources.statePath, recursive: false },\n { category: 'safelists', path: join(sources.dataDir, 'projects.json'), recursive: false },\n { category: 'files', path: userSystemMdPath({ home: sources.home, prefix: sources.prefix }), recursive: false },\n ...userPaths(sources, 'skills').map(path => ({ category: 'files' as const, path, recursive: true })),\n ...userPaths(sources, 'mcps.json').map(path => ({ category: 'mcps' as const, path, recursive: false })),\n ...userPaths(sources, 'extensions').map(path => ({ category: 'extensions' as const, path, recursive: true })),\n { category: 'environments', path: sources.statePath, recursive: false },\n { category: 'environments', path: environmentCredentialsPath(sources.dataDir), recursive: false },\n { category: 'keys', path: join(sources.dataDir, 'credentials.json'), recursive: false },\n ]\n}\n\nfunction settingsPayload(sources: SyncSources): SyncPayloads['settings'] {\n const stored = settingsFromStored(loadState(sources.statePath).settings ?? {})\n const merged = { ...DEFAULT_SETTINGS, ...stored } as Record<string, unknown>\n return { values: settingsToWire(Object.fromEntries(Object.entries(merged).filter(([key]) => key in DEFAULT_SETTINGS && isSyncableSetting(key)))) }\n}\n\nfunction safelistsPayload(sources: SyncSources): SyncPayloads['safelists'] {\n const projects = readProjects(sources.dataDir)\n return { projects: Object.fromEntries(Object.entries(projects).flatMap(([path, entry]) => (Array.isArray(entry?.safelist) ? [[path, [...entry.safelist]]] : []))) }\n}\n\n/** A skill folder's files, or null when it is over the bounds (or not a plain folder). */\nfunction skillFiles(dir: string): SyncFile[] | null {\n const files: SyncFile[] = []\n let total = 0\n const walk = (at: string, depth: number): boolean => {\n if (depth > MAX_DEPTH)\n return false\n for (const name of readdirSync(at).sort()) {\n const path = join(at, name)\n const stat = lstatSync(path)\n if (stat.isDirectory()) {\n if (SKIPPED_DIRS.has(name))\n continue\n if (!walk(path, depth + 1))\n return false\n }\n else if (stat.isFile()) {\n if (stat.size > MAX_FILE_BYTES || files.length >= MAX_SKILL_FILES)\n return false\n total += stat.size\n if (total > MAX_SKILL_BYTES)\n return false\n files.push({ path: relative(dir, path).split(sep).join('/'), data: readFileSync(path).toString('base64') })\n }\n // Links and anything else: not copied.\n }\n return true\n }\n try {\n return walk(dir, 1) && files.length > 0 ? files : null\n }\n catch {\n return null\n }\n}\n\n/** The user-scope skills: name → folder, the first root of a name winning (as discovery does). */\nexport function userSkillDirs(sources: SyncSources): Map<string, string> {\n const found = new Map<string, string>()\n for (const root of userPaths(sources, 'skills')) {\n let names: string[]\n try {\n names = readdirSync(root).sort()\n }\n catch {\n continue\n }\n for (const name of names) {\n const dir = join(root, name)\n if (!found.has(name) && !name.startsWith('.') && existsSync(join(dir, 'SKILL.md')) && lstatSync(dir).isDirectory())\n found.set(name, dir)\n }\n }\n return found\n}\n\nfunction filesPayload(sources: SyncSources): SyncPayloads['files'] {\n const mdPath = userSystemMdPath({ home: sources.home, prefix: sources.prefix })\n const systemMd = existsSync(mdPath) ? readFileSync(mdPath, 'utf8') : null\n const skills: SyncPayloads['files']['skills'] = []\n for (const [name, dir] of userSkillDirs(sources)) {\n const files = skillFiles(dir)\n if (files)\n skills.push({ name, files })\n }\n return { systemMd, skills }\n}\n\nfunction mcpsPayload(sources: SyncSources): SyncPayloads['mcps'] {\n const servers = new Map<string, Record<string, unknown>>()\n for (const path of userPaths(sources, 'mcps.json')) {\n if (!existsSync(path))\n continue\n try {\n for (const server of parseMcpsFile(readFileSync(path, 'utf8'))) {\n if (!servers.has(server.name))\n servers.set(server.name, server as unknown as Record<string, unknown>)\n }\n }\n catch {\n // A file discovery can't read either: nothing to sync from it.\n }\n }\n return { servers: [...servers.values()] }\n}\n\n/** User-scope extensions: those with a source (syncable) and the local ones (not). */\nexport function userExtensions(sources: SyncSources): { syncable: SyncPayloads['extensions']['extensions'], local: string[] } {\n const syncable: SyncPayloads['extensions']['extensions'] = []\n const local: string[] = []\n const seen = new Set<string>()\n for (const root of userPaths(sources, 'extensions')) {\n let names: string[]\n try {\n names = readdirSync(root).sort()\n }\n catch {\n continue\n }\n for (const name of names) {\n const dir = join(root, name)\n if (name.startsWith('.') || seen.has(name) || !lstatSync(dir).isDirectory())\n continue\n seen.add(name)\n // `.install.json` (`src/extensions/install.ts`): the spec an install replays.\n let provenance: { spec?: unknown, extension?: unknown } | null = null\n try {\n provenance = JSON.parse(readFileSync(join(dir, '.install.json'), 'utf8'))\n }\n catch {}\n if (provenance && typeof provenance.spec === 'string' && provenance.spec.trim() !== '')\n syncable.push({ name, spec: provenance.spec, ...(typeof provenance.extension === 'string' ? { extension: provenance.extension } : {}) })\n else\n local.push(name)\n }\n }\n return { syncable, local }\n}\n\n/** One provider with what is stored for it here: its API key, its sign-ins (by label). */\nexport interface LocalProviderAccounts {\n key: string\n label: string\n /** An API key is stored: the keys category can copy it. */\n apiKey: boolean\n /** OAuth sign-ins, by name. Never copied: a refreshed grant would sign the other side out. */\n signIns: string[]\n}\n\n/** Every provider with something stored here, API key or sign-in. */\nexport function storedProviderAccounts(sources: SyncSources): LocalProviderAccounts[] {\n return Object.values(sources.providers()).flatMap((descriptor) => {\n const stored = readProviderCredentials(sources.dataDir, descriptor)\n const apiKey = stored.some(({ credential }) => credential.kind === 'apikey' && credential.value !== '')\n const signIns = stored.flatMap(({ credential }) => (credential.kind === 'oauth' ? [oauthAccountLabel(credential)] : []))\n return apiKey || signIns.length > 0 ? [{ key: descriptor.key, label: descriptor.label, apiKey, signIns }] : []\n })\n}\n\n/** Providers with an API key stored here: what the keys category can offer. */\nexport function storedApiKeyProviders(sources: SyncSources): { key: string, label: string }[] {\n return Object.values(sources.providers()).flatMap((descriptor) => {\n const credential = readProviderCredential(sources.dataDir, descriptor)\n return credential?.kind === 'apikey' && credential.value ? [{ key: descriptor.key, label: descriptor.label }] : []\n })\n}\n\nfunction keysPayload(sources: SyncSources, optedIn: readonly string[]): SyncPayloads['keys'] {\n const providers = sources.providers()\n return {\n keys: optedIn.flatMap((provider) => {\n const descriptor = providers[provider]\n const credential = descriptor ? readProviderCredential(sources.dataDir, descriptor) : undefined\n return credential?.kind === 'apikey' && credential.value ? [{ provider, key: credential.value }] : []\n }),\n }\n}\n\n/** Stored environment secrets (`environment-credentials.json`); unreadable reads as none. */\nfunction environmentSecrets(dataDir: string): Record<string, Record<string, string>> {\n try {\n const parsed = JSON.parse(readFileSync(environmentCredentialsPath(dataDir), 'utf8')) as unknown\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed as Record<string, Record<string, string>> : {}\n }\n catch {\n return {}\n }\n}\n\n/**\n * The cloud sandbox kinds this computer set up: turned on or off, given an\n * option, or holding a key. A kind left at its defaults is not sent.\n */\nfunction environmentsPayload(sources: SyncSources): SyncPayloads['environments'] {\n const settings = readEnvironmentSettings(loadState(sources.statePath).settings ?? {})\n const secrets = environmentSecrets(sources.dataDir)\n const kinds = SYNCABLE_ENVIRONMENT_KINDS.flatMap((kind) => {\n const enabled = settings.enabled.includes(kind) ? true : settings.disabled.includes(kind) ? false : undefined\n const values = { ...settings.config[kind] }\n const keys = Object.fromEntries(Object.entries(secrets[kind] ?? {}).filter(([, value]) => typeof value === 'string' && value !== ''))\n if (enabled === undefined && Object.keys(values).length === 0 && Object.keys(keys).length === 0)\n return []\n return [{ kind, ...(enabled !== undefined ? { enabled } : {}), values, secrets: keys }]\n })\n return { kinds }\n}\n\nexport function collectSyncPayload(category: SyncCategory, sources: SyncSources, optedInKeys: readonly string[] = []): SyncPayloads[SyncCategory] {\n switch (category) {\n case 'settings': return settingsPayload(sources)\n case 'safelists': return safelistsPayload(sources)\n case 'files': return filesPayload(sources)\n case 'mcps': return mcpsPayload(sources)\n case 'extensions': return { extensions: userExtensions(sources).syncable }\n case 'environments': return environmentsPayload(sources)\n case 'keys': return keysPayload(sources, optedInKeys)\n }\n}\n","/**\n * This computer's side of settings sync, on disk:\n *\n * - `<dataDir>/device.json` (0600): the device id servers know this computer\n * by (random, made once) and its name (the host name).\n * - `<dataDir>/hosts-sync.json` (0600), next to `hosts.json`: per saved\n * server (by host id), the mode, the categories, the API keys opted in,\n * the categories still to push (the offline queue: one entry per category,\n * the newest value wins, so at most six) and the outcome of the last push.\n */\nimport type { SyncCategory, SyncMode, SyncPushResult } from './types'\nimport { randomUUID } from 'node:crypto'\nimport { existsSync, readFileSync } from 'node:fs'\nimport { hostname } from 'node:os'\nimport { join } from 'node:path'\nimport { OWNER_ONLY, writeJsonFileAtomic } from '../../atomic-write'\nimport { isRecord } from './hash'\nimport { SYNC_CATEGORIES, SYNC_MODES } from './types'\n\nexport interface DeviceIdentity {\n id: string\n name: string\n}\n\n/** Saved sync settings for one server, kept in `hosts-sync.json`. */\nexport interface ServerSyncConfig {\n /** Whether and how this computer syncs to the server. */\n mode: SyncMode\n /** Categories sent to the server. */\n categories: SyncCategory[]\n /** Providers whose stored API key goes to this server. */\n keys: string[]\n /** Categories changed here and not pushed yet (offline queue). */\n pending: SyncCategory[]\n /** Per category, a hash of what was last pushed (a restart finds what changed meanwhile). */\n sent: Partial<Record<SyncCategory, string>>\n /** Time of the last successful push in epoch ms. */\n lastSyncAt?: number\n /** Message of the last failed push. */\n lastError?: string\n /** What the last push of each category did there. */\n results: Partial<Record<SyncCategory, SyncPushResult>>\n}\n\ninterface SyncConfigFile {\n version: 1\n servers: Record<string, ServerSyncConfig>\n}\n\nexport const DEFAULT_SERVER_SYNC: Readonly<ServerSyncConfig> = { mode: 'off', categories: ['settings'], keys: [], pending: [], sent: {}, results: {} }\n\nexport function hostsSyncPath(dataDir: string): string {\n return join(dataDir, 'hosts-sync.json')\n}\n\nexport function deviceIdentityPath(dataDir: string): string {\n return join(dataDir, 'device.json')\n}\n\nfunction readJson(path: string): unknown {\n if (!existsSync(path))\n return undefined\n try {\n return JSON.parse(readFileSync(path, 'utf8'))\n }\n catch {\n return undefined\n }\n}\n\n/** This computer's device id and name, made on first use. */\nexport function readDeviceIdentity(dataDir: string, name?: string): DeviceIdentity {\n const raw = readJson(deviceIdentityPath(dataDir))\n if (isRecord(raw) && typeof raw.id === 'string' && raw.id !== '')\n return { id: raw.id, name: name ?? (typeof raw.name === 'string' && raw.name ? raw.name : hostname()) }\n const identity = { id: randomUUID(), name: name ?? hostname() }\n writeJsonFileAtomic(deviceIdentityPath(dataDir), identity, OWNER_ONLY)\n return identity\n}\n\nfunction categoriesOf(value: unknown): SyncCategory[] {\n return Array.isArray(value) ? SYNC_CATEGORIES.filter(c => value.includes(c)) : []\n}\n\nfunction serverConfigOf(raw: unknown): ServerSyncConfig {\n if (!isRecord(raw))\n return { ...DEFAULT_SERVER_SYNC }\n return {\n mode: SYNC_MODES.includes(raw.mode as SyncMode) ? raw.mode as SyncMode : 'off',\n categories: categoriesOf(raw.categories),\n keys: Array.isArray(raw.keys) ? raw.keys.filter((k): k is string => typeof k === 'string') : [],\n pending: categoriesOf(raw.pending),\n sent: isRecord(raw.sent) ? Object.fromEntries(Object.entries(raw.sent).filter(([c, h]) => SYNC_CATEGORIES.includes(c as SyncCategory) && typeof h === 'string')) : {},\n ...(typeof raw.lastSyncAt === 'number' ? { lastSyncAt: raw.lastSyncAt } : {}),\n ...(typeof raw.lastError === 'string' ? { lastError: raw.lastError } : {}),\n results: isRecord(raw.results) ? raw.results as ServerSyncConfig['results'] : {},\n }\n}\n\nexport function readSyncConfigs(dataDir: string): Record<string, ServerSyncConfig> {\n const raw = readJson(hostsSyncPath(dataDir))\n if (!isRecord(raw) || !isRecord(raw.servers))\n return {}\n return Object.fromEntries(Object.entries(raw.servers).map(([key, value]) => [key, serverConfigOf(value)]))\n}\n\nexport function writeSyncConfigs(dataDir: string, servers: Record<string, ServerSyncConfig>): void {\n const file: SyncConfigFile = { version: 1, servers }\n writeJsonFileAtomic(hostsSyncPath(dataDir), file, OWNER_ONLY)\n}\n","/**\n * The push side of settings sync (`./engine`): per server, its config (read\n * from and saved to `hosts-sync.json`), its connection, what it said last\n * (`sync.status`, a conflicting source), and the queue flush: claim, then\n * push each pending category's snapshot, one flush at a time per server.\n */\nimport type { SyncSources } from './collect'\nimport type { SyncClient } from './engine'\nimport type { DeviceIdentity, ServerSyncConfig } from './local'\nimport type { SyncCategory, SyncStatus } from './types'\nimport { collectSyncPayload } from './collect'\nimport { hashValue } from './hash'\nimport { DEFAULT_SERVER_SYNC, readSyncConfigs, writeSyncConfigs } from './local'\nimport { serverSyncCategories, SYNC_CATEGORIES } from './types'\n\nconst errorText = (err: unknown): string => (err instanceof Error ? err.message : String(err))\n\nexport function createSyncPusher({ sources, device, emit }: { sources: SyncSources, device: DeviceIdentity, emit: () => void }) {\n const configs = readSyncConfigs(sources.dataDir)\n const clients = new Map<string, SyncClient>()\n const statuses = new Map<string, SyncStatus>()\n const conflicts = new Map<string, string>()\n const flushing = new Map<string, Promise<void>>()\n const again = new Set<string>()\n let stopped = false\n\n const configOf = (key: string): ServerSyncConfig => configs[key] ?? { ...DEFAULT_SERVER_SYNC, categories: [...DEFAULT_SERVER_SYNC.categories] }\n const save = (key: string, config: ServerSyncConfig): void => {\n configs[key] = config\n writeSyncConfigs(sources.dataDir, configs)\n emit()\n }\n const fail = (key: string, err: unknown): void => save(key, { ...configOf(key), lastError: errorText(err) })\n\n /** Categories whose snapshot here differs from what was last pushed to the server. */\n const changedCategories = (config: ServerSyncConfig, only?: SyncCategory): SyncCategory[] =>\n config.categories.filter((category) => {\n if (only && category !== only)\n return false\n try {\n return hashValue(collectSyncPayload(category, sources, config.keys)) !== config.sent[category]\n }\n catch {\n return true\n }\n })\n\n const queue = (key: string, categories: readonly SyncCategory[]): void => {\n const config = configOf(key)\n const pending = SYNC_CATEGORIES.filter(c => config.pending.includes(c) || categories.includes(c))\n if (pending.length !== config.pending.length)\n save(key, { ...config, pending })\n }\n\n async function claim(key: string, client: SyncClient, takeover: boolean): Promise<boolean> {\n const config = configOf(key)\n // An older server refuses a claim naming a category it doesn't know: claim only what it takes.\n const taken = serverSyncCategories(client.hello)\n const categories = config.categories.filter(c => taken.includes(c))\n const result = await client.request('sync.claim', { deviceId: device.id, deviceName: device.name, categories, keys: config.keys, ...(takeover ? { takeover: true } : {}) })\n statuses.set(key, result.status)\n if (result.claimed)\n conflicts.delete(key)\n else\n conflicts.set(key, result.status.source?.deviceName ?? 'another device')\n return result.claimed\n }\n\n async function flushOnce(key: string, takeover: boolean): Promise<void> {\n const client = clients.get(key)\n let config = configOf(key)\n if (!client || !client.hello.sync || config.mode === 'off' || (config.pending.length === 0 && !takeover) || stopped)\n return\n try {\n if (!(await claim(key, client, takeover)))\n return\n for (const category of [...configOf(key).pending]) {\n config = configOf(key)\n if (!config.categories.includes(category)) {\n save(key, { ...config, pending: config.pending.filter(c => c !== category) })\n continue\n }\n if (!serverSyncCategories(client.hello).includes(category)) {\n save(key, {\n ...config,\n pending: config.pending.filter(c => c !== category),\n results: { ...config.results, [category]: { applied: [], unchanged: [], skipped: [{ field: category, reason: 'needs a newer server' }], warnings: [] } },\n })\n continue\n }\n const payload = collectSyncPayload(category, sources, config.keys)\n const result = await client.request('sync.push', { deviceId: device.id, category, payload })\n config = configOf(key)\n const { lastError: _dropped, ...rest } = config\n save(key, {\n ...rest,\n pending: config.pending.filter(c => c !== category),\n sent: { ...config.sent, [category]: hashValue(payload) },\n results: { ...config.results, [category]: result },\n lastSyncAt: Date.now(),\n })\n }\n statuses.set(key, await client.request('sync.status', {}))\n }\n catch (err) {\n fail(key, err)\n }\n }\n\n /** Push the queue if the server is connected and this device is its source; `takeover` claims it first. */\n function flush(key: string, takeover = false): Promise<void> {\n const running = flushing.get(key)\n if (running && !takeover) {\n again.add(key)\n return running\n }\n const run = (async () => {\n if (running)\n await running\n await flushOnce(key, takeover)\n // A change while pushing: once more (`flushOnce` stops on its own once stopped).\n while (again.delete(key))\n await flushOnce(key, false)\n })().finally(() => {\n if (flushing.get(key) === run)\n flushing.delete(key)\n emit()\n })\n flushing.set(key, run)\n emit()\n return run\n }\n\n /** Read the server's record: who syncs there now. */\n async function refresh(key: string): Promise<void> {\n const client = clients.get(key)\n if (!client?.hello.sync)\n return\n try {\n const status = await client.request('sync.status', {})\n statuses.set(key, status)\n if (!status.source || status.source.deviceId === device.id)\n conflicts.delete(key)\n else if (configOf(key).mode !== 'off')\n conflicts.set(key, status.source.deviceName)\n }\n catch {}\n emit()\n }\n\n /** This device stops being the server's source (the server keeps what it has). */\n async function release(key: string): Promise<void> {\n const client = clients.get(key)\n conflicts.delete(key)\n if (!client?.hello.sync)\n return\n await client.request('sync.release', { deviceId: device.id }).catch(() => undefined)\n const status = await client.request('sync.status', {}).catch(() => null)\n if (status)\n statuses.set(key, status)\n }\n\n function forget(key: string): void {\n clients.delete(key)\n statuses.delete(key)\n conflicts.delete(key)\n if (configs[key]) {\n delete configs[key]\n writeSyncConfigs(sources.dataDir, configs)\n }\n }\n\n return {\n configs,\n clients,\n statuses,\n conflicts,\n flushing,\n configOf,\n save,\n fail,\n changedCategories,\n queue,\n claim,\n flush,\n refresh,\n release,\n forget,\n stop: () => {\n stopped = true\n clients.clear()\n },\n }\n}\n","/**\n * File watchers for settings sync (`./engine`): on each file and folder a\n * category reads (`syncWatchTargets`), or the folder it would appear in.\n * Events are debounced into one check of the categories they touched. After\n * each check the watchers are rebuilt only when what they should watch\n * changed (a folder created since, such as the first skill). A rebuild is\n * followed by one check of everything: a write that landed while no watcher\n * was open (or before a new one delivered events) is not lost.\n */\nimport type { SyncSources } from './collect'\nimport type { SyncCategory } from './types'\nimport { existsSync, statSync, watch } from 'node:fs'\nimport { basename, dirname } from 'node:path'\nimport { syncWatchTargets } from './collect'\n\nexport interface SyncWatchers {\n /** Start (or restart) the watchers when `active`, else close them. */\n refresh: (active: boolean) => void\n /** Report a change (a watcher, or the app's own settings stream). */\n schedule: (category?: SyncCategory) => void\n count: () => number\n close: () => void\n}\n\nexport function createSyncWatchers({ sources, debounceMs, check }: {\n sources: SyncSources\n debounceMs: number\n /** After the debounce: the categories that changed (`all`: unknown which), then the watchers are refreshed by the caller. */\n check: (which: readonly (SyncCategory | 'all')[]) => void\n}): SyncWatchers {\n let watchers: { close: () => void }[] = []\n /** What the open watchers cover (`null`: none open). */\n let watchedKey: string | null = null\n let timer: ReturnType<typeof setTimeout> | null = null\n let closed = false\n const dirty = new Set<SyncCategory | 'all'>()\n\n const closeAll = (): void => {\n for (const w of watchers)\n w.close()\n watchers = []\n watchedKey = null\n }\n\n /** Each target, resolved to the folder watched now: a change here means a rebuild. */\n const plan = () => syncWatchTargets(sources).map((target) => {\n const isDir = existsSync(target.path) && statSync(target.path).isDirectory()\n const at = isDir ? target.path : dirname(target.path)\n return { ...target, isDir, at, watchable: existsSync(at) }\n })\n\n const schedule = (category?: SyncCategory): void => {\n if (closed)\n return\n dirty.add(category ?? 'all')\n if (timer)\n return\n timer = setTimeout(() => {\n timer = null\n const which = [...dirty]\n dirty.clear()\n check(which)\n }, debounceMs)\n }\n\n return {\n schedule,\n refresh(active) {\n if (closed || !active) {\n closeAll()\n return\n }\n const targets = plan()\n const key = JSON.stringify(targets.map(t => [t.category, t.path, t.isDir, t.watchable]))\n if (key === watchedKey)\n return\n closeAll()\n watchedKey = key\n for (const { category, path, isDir, at, watchable, recursive } of targets) {\n if (!watchable)\n continue\n const target = { category, path, recursive }\n const name = basename(target.path)\n try {\n // A file's folder: only that file's events, and its atomic writes'\n // temp files (`<name>.<pid>.<n>.tmp`, `../../atomic-write`): a rename\n // over the target can be reported under the temp name only (macOS).\n watchers.push(watch(at, { recursive: isDir && target.recursive, persistent: false }, (_event, changed) => {\n if (isDir || changed === null || String(changed) === name || String(changed).startsWith(`${name}.`))\n schedule(target.category)\n }))\n }\n catch {\n // A folder that can't be watched: the app's own reports still find its changes.\n }\n }\n // New watchers: catch what changed while none was listening.\n schedule()\n },\n count: () => watchers.length,\n close() {\n closed = true\n if (timer)\n clearTimeout(timer)\n timer = null\n closeAll()\n },\n }\n}\n","/**\n * Settings sync, the client side shared by the TUI and the GUI main process:\n * for each saved server, push this computer's categories to it\n * (`sync.claim`, `sync.push`), now (Import once) or as they change (Keep in\n * sync). Loaded by each app behind its `remoteHosts` gate only, with the\n * saved hosts; nothing here dials: the app hands over each server's\n * connection (`connected` / `disconnected`).\n *\n * Change detection: file watchers on what each category reads\n * (`syncWatchTargets`), only while some server keeps in sync, plus the\n * app's own reports (`notifyLocalChange`: its settings stream). Either one\n * schedules a check (debounced): a category whose snapshot hashes\n * differently from what was last pushed is queued (`pending`, persisted in\n * `hosts-sync.json`), and the queue flushes when the server is connected.\n * Offline, it waits; the newest value wins, there being one source.\n */\nimport type { ProviderDescriptor } from '../providers'\nimport type { LocalProviderAccounts } from './collect'\nimport type { DeviceIdentity, ServerSyncConfig } from './local'\nimport type { SyncCategory, SyncClaimResult, SyncPayload, SyncPayloads, SyncPushResult, SyncStatus } from './types'\nimport { collectSyncPayload, storedApiKeyProviders, storedProviderAccounts, userExtensions } from './collect'\nimport { hashValue } from './hash'\nimport { readDeviceIdentity } from './local'\nimport { createSyncPusher } from './pusher'\nimport { fieldCategory, SYNC_CATEGORIES } from './types'\nimport { createSyncWatchers } from './watchers'\n\n/** The part of a host connection sync uses (an `IpcClient` is one). */\nexport interface SyncClient {\n /** Sends a sync RPC to the server and resolves with its reply. */\n request: ((method: 'sync.status', params: Record<string, never>) => Promise<SyncStatus>)\n & ((method: 'sync.claim', params: { deviceId: string, deviceName: string, takeover?: boolean, categories?: SyncCategory[], keys?: string[] }) => Promise<SyncClaimResult>)\n & ((method: 'sync.push', params: { deviceId: string, category: SyncCategory, payload: SyncPayload }) => Promise<SyncPushResult>)\n & ((method: 'sync.reset', params: { field: string }) => Promise<{ reset: boolean }>)\n & ((method: 'sync.release', params: { deviceId: string }) => Promise<{ released: boolean }>)\n /** What the server announced when it connected. `sync` is set when it supports settings sync. */\n hello: { sync?: boolean, syncCategories?: SyncCategory[] }\n}\n\n/** Options for {@link createSyncEngine}. */\nexport interface SyncEngineOptions {\n /** Directory holding the sync state files and the device identity. */\n dataDir: string\n /** Path of the file holding the app's saved settings. */\n statePath: string\n /** User home directory, where user-scope files and skills are read from. */\n home: string\n /** Storage prefix, such as `.zidane`. */\n prefix: string\n /** Returns the provider descriptors used to list API keys that can be opted in. */\n providers: () => Readonly<Record<string, ProviderDescriptor>>\n /** Default: this computer's host name. */\n deviceName?: string\n /** Wait after a change before checking (a save is often several writes). Default 500 ms. */\n debounceMs?: number\n /** File watchers (default on); off, only `notifyLocalChange` finds changes. */\n watch?: boolean\n}\n\n/** What a UI shows for one server. */\nexport interface SyncView {\n /** Saved sync settings for the server. */\n config: ServerSyncConfig\n /** Whether a connection to the server is open. */\n connected: boolean\n /** The server takes sync (`ipc.hello.sync`); false for an older server. */\n supported: boolean\n /** The server's record, when read. */\n status: SyncStatus | null\n /** Another device syncs to this server: its name. */\n conflict: string | null\n /** A push or refresh is running. */\n busy: boolean\n}\n\n/** Pushes this computer's settings to saved servers and tracks the state each one reports. Servers are identified by key. */\nexport interface SyncEngine {\n /** Identity of this computer. */\n device: DeviceIdentity\n /** Returns what a UI shows for the server. */\n view: (key: string) => SyncView\n /** Change the mode, categories or opted-in keys; pushes what that calls for. */\n configure: (key: string, patch: Partial<Pick<ServerSyncConfig, 'mode' | 'categories' | 'keys'>>) => Promise<void>\n /** Push every selected category now (whatever the mode, but off). */\n pushAll: (key: string) => Promise<void>\n /** Become the source of a server another device syncs to, then push. */\n takeOver: (key: string) => Promise<void>\n /** Drop an override on the server (the next push writes the field again). */\n reset: (key: string, field: string) => Promise<void>\n /** Read the server's record again. */\n refresh: (key: string) => Promise<void>\n /** Reports that a connection to the server opened. Reads its record and pushes what its mode calls for. */\n connected: (key: string, client: SyncClient) => void\n /** Reports that the connection to the server closed. */\n disconnected: (key: string) => void\n /** A server's `sync:changed` signal. */\n serverChanged: (key: string) => void\n /** The server was removed here: forget its sync config (the server keeps what it has). */\n removed: (key: string) => void\n /** This computer changed something (the app's settings stream): check, push if it differs. */\n notifyLocalChange: (category?: SyncCategory) => void\n /** What this computer has that the UIs mention: API keys to opt in, extensions that can't sync, stdio MCP servers. */\n localInfo: () => SyncLocalInfo\n /** File watchers are running (some server keeps in sync). */\n watching: () => boolean\n /** Calls `listener` when any view changes. Returns a function that removes it. */\n subscribe: (listener: () => void) => () => void\n /** Stops the file watchers and pending pushes. */\n stop: () => void\n}\n\n/** What this computer has that sync UIs mention. */\nexport interface SyncLocalInfo {\n /** Providers with an API key that can be opted in, with display labels. */\n keyProviders: { key: string, label: string }[]\n /** Every provider with an API key or a sign-in here (the GUI's accounts list). */\n accounts: LocalProviderAccounts[]\n /** Names of extensions that cannot sync because they are not installed from npm or GitHub. */\n localExtensions: string[]\n /** Names of stdio MCP servers, whose commands may be missing on the server. */\n stdioMcps: string[]\n}\n\n/**\n * Engines CREATED in this process so far: monotonic (never decremented when\n * one stops), not a live count. The containment tests compare it before and\n * after a mount to prove an engine did, or did not, start.\n */\nlet engines = 0\nexport function syncEngineCount(): number {\n return engines\n}\n\n/**\n * Creates a sync engine for this computer. Starts file watchers once a server is set to keep in sync.\n *\n * @param options - Paths, providers and timing for the engine.\n */\nexport function createSyncEngine(options: SyncEngineOptions): SyncEngine {\n engines++\n const sources = { dataDir: options.dataDir, statePath: options.statePath, home: options.home, prefix: options.prefix, providers: options.providers }\n const device = readDeviceIdentity(options.dataDir, options.deviceName)\n const listeners = new Set<() => void>()\n const emit = (): void => {\n for (const listener of [...listeners]) {\n try {\n listener()\n }\n catch {}\n }\n }\n const pusher = createSyncPusher({ sources, device, emit })\n const { configOf, save, queue, flush, clients, statuses, conflicts } = pusher\n const keeping = (): string[] => Object.entries(pusher.configs).filter(([, c]) => c.mode === 'keep').map(([key]) => key)\n\n const watchers = createSyncWatchers({\n sources,\n debounceMs: options.debounceMs ?? 500,\n // Each server that keeps in sync: queue what changed, push it.\n check: (which) => {\n for (const key of keeping()) {\n const config = configOf(key)\n const changed = which.includes('all') ? pusher.changedCategories(config) : which.flatMap(c => pusher.changedCategories(config, c as SyncCategory))\n if (changed.length > 0) {\n queue(key, changed)\n void flush(key)\n }\n }\n refreshWatchers()\n },\n })\n function refreshWatchers(): void {\n watchers.refresh(options.watch !== false && keeping().length > 0)\n emit()\n }\n\n /** A config patch applied: what it turns on gets pushed (all of it when a mode starts). */\n async function apply(key: string, before: ServerSyncConfig, next: ServerSyncConfig, patch: Partial<ServerSyncConfig>): Promise<void> {\n if (next.mode === 'off') {\n const turnedOff = before.mode !== 'off'\n save(key, turnedOff ? { ...next, pending: [], sent: {} } : next)\n if (turnedOff) {\n refreshWatchers()\n // The server keeps what it has; it stops naming this device as its source.\n await pusher.release(key)\n emit()\n }\n return\n }\n const fresh = before.mode === 'off' || patch.mode === 'once'\n const added = next.categories.filter(c => !before.categories.includes(c))\n const keysChanged = patch.keys !== undefined && hashValue(before.keys) !== hashValue(next.keys)\n const toPush = fresh ? next.categories : [...added, ...(keysChanged && next.categories.includes('keys') ? ['keys' as const] : [])]\n next.pending = SYNC_CATEGORIES.filter(c => next.categories.includes(c) && (next.pending.includes(c) || toPush.includes(c)))\n if (fresh)\n next.sent = {}\n save(key, next)\n refreshWatchers()\n // New categories or keys with nothing queued: claim again, the server checks pushes against them.\n const client = clients.get(key)\n if (client?.hello.sync && next.pending.length === 0 && (added.length > 0 || keysChanged)) {\n await pusher.claim(key, client, false).catch(err => pusher.fail(key, err))\n emit()\n }\n await flush(key)\n }\n\n const engine: SyncEngine = {\n device,\n\n view: key => ({\n config: configOf(key),\n connected: clients.has(key),\n supported: clients.get(key)?.hello.sync === true,\n status: statuses.get(key) ?? null,\n conflict: conflicts.get(key) ?? null,\n busy: pusher.flushing.has(key),\n }),\n\n configure(key, patch) {\n const before = configOf(key)\n const next: ServerSyncConfig = {\n ...before,\n ...(patch.mode ? { mode: patch.mode } : {}),\n ...(patch.categories ? { categories: SYNC_CATEGORIES.filter(c => patch.categories!.includes(c)) } : {}),\n ...(patch.keys ? { keys: [...new Set(patch.keys)] } : {}),\n }\n return apply(key, before, next, patch)\n },\n\n async pushAll(key) {\n const config = configOf(key)\n if (config.mode === 'off')\n return\n save(key, { ...config, pending: [...config.categories], sent: {} })\n await flush(key)\n },\n\n async takeOver(key) {\n const config = configOf(key)\n if (config.mode === 'off')\n return\n save(key, { ...config, pending: [...config.categories], sent: {} })\n await flush(key, true)\n },\n\n async reset(key, field) {\n const client = clients.get(key)\n if (!client)\n throw new Error('The server is not connected.')\n await client.request('sync.reset', { field })\n await pusher.refresh(key)\n // That field is sync's again: send its category.\n const category = fieldCategory(field)\n const config = configOf(key)\n if (category && config.categories.includes(category) && config.mode !== 'off') {\n save(key, { ...config, sent: { ...config.sent, [category]: '' } })\n queue(key, [category])\n await flush(key)\n }\n },\n\n refresh: key => pusher.refresh(key),\n\n connected(key, client) {\n clients.set(key, client)\n emit()\n if (!client.hello.sync)\n return\n const config = configOf(key)\n // Keep in sync: what changed here while away (or while this app was closed) joins the queue.\n if (config.mode === 'keep') {\n const changed = pusher.changedCategories(config)\n if (changed.length > 0)\n queue(key, changed)\n }\n void pusher.refresh(key).then(() => flush(key))\n },\n\n disconnected(key) {\n clients.delete(key)\n emit()\n },\n\n serverChanged: (key) => {\n const before = statuses.get(key)\n void pusher.refresh(key).then(() => {\n // An override reset there (from any window): its category is sync's again, push it.\n const after = statuses.get(key)\n const config = configOf(key)\n if (!before || !after || after.source?.deviceId !== device.id || config.mode === 'off')\n return\n const back = Object.keys(before.overrides).filter(field => !after.overrides[field]).map(fieldCategory).filter((c): c is SyncCategory => c !== undefined && config.categories.includes(c))\n if (back.length === 0)\n return\n save(key, { ...config, sent: Object.fromEntries(Object.entries(config.sent).filter(([c]) => !back.includes(c as SyncCategory))) })\n queue(key, back)\n void flush(key)\n })\n },\n\n removed(key) {\n if (configOf(key).mode !== 'off')\n void pusher.release(key)\n pusher.forget(key)\n refreshWatchers()\n },\n\n notifyLocalChange(category) {\n if (keeping().length > 0)\n watchers.schedule(category)\n },\n\n localInfo: () => syncLocalInfo(sources),\n\n watching: () => watchers.count() > 0,\n\n subscribe(listener) {\n listeners.add(listener)\n return () => listeners.delete(listener)\n },\n\n stop() {\n watchers.close()\n pusher.stop()\n listeners.clear()\n },\n }\n\n refreshWatchers()\n return engine\n}\n\nfunction syncLocalInfo(sources: Parameters<typeof storedApiKeyProviders>[0]): SyncLocalInfo {\n const read = <T>(fn: () => T, fallback: T): T => {\n try {\n return fn()\n }\n catch {\n return fallback\n }\n }\n return {\n keyProviders: read(() => storedApiKeyProviders(sources), []),\n accounts: read(() => storedProviderAccounts(sources), []),\n localExtensions: read(() => userExtensions(sources).local, []),\n stdioMcps: read(() => (collectSyncPayload('mcps', sources) as SyncPayloads['mcps']).servers.filter(s => typeof s.command === 'string' && s.command !== '').map(s => String(s.name)), []),\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"engine-C_Ndjp0u.js","names":[],"sources":["../src/chat/sync/collect.ts","../src/chat/sync/local.ts","../src/chat/sync/pusher.ts","../src/chat/sync/watchers.ts","../src/chat/sync/engine.ts"],"sourcesContent":["/**\n * This computer's state for each sync category, read from disk as a\n * `sync.push` payload (`./types` `SyncPayloads`): what the user set here,\n * never what a server session put in a window.\n *\n * - settings: `state.json`'s settings over the defaults, syncable keys only;\n * - safelists: `projects.json`;\n * - files: the user-scope `SYSTEM.md`, and the user-scope skills\n * (`~/.agents/skills`, `~/.<prefix>/skills`; the first of a name wins),\n * bounded like the server bounds them (a skill over them is left out);\n * - mcps: the user-scope `mcps.json` files (the first of a name wins);\n * - extensions: user-scope extensions installed from a source (their\n * `.install.json`); the others are listed as not syncable;\n * - keys: the stored API keys of the providers opted in.\n */\nimport type { ProviderDescriptor } from '../providers'\nimport type { SyncCategory, SyncFile, SyncPayloads } from './types'\nimport { existsSync, lstatSync, readdirSync, readFileSync } from 'node:fs'\nimport { join, relative, sep } from 'node:path'\nimport { oauthAccountLabel } from '../cred-keys'\nimport { readProviderCredential, readProviderCredentials } from '../credentials'\nimport { environmentCredentialsPath } from '../environment-credentials'\nimport { readEnvironmentSettings } from '../environment-settings'\nimport { parseMcpsFile } from '../mcps-discovery'\nimport { projectUserPaths } from '../project-user-paths'\nimport { readProjects } from '../safe-mode'\nimport { DEFAULT_SETTINGS, settingsFromStored } from '../settings-defaults'\nimport { loadState } from '../store'\nimport { userSystemMdPath } from '../system-md'\nimport { isSyncableSetting, settingsToWire } from './settings-keys'\nimport { SYNCABLE_ENVIRONMENT_KINDS } from './types'\n\nexport interface SyncSources {\n /** The user dir (`projects.json`, `credentials.json`). */\n dataDir: string\n /** This computer's `state.json`. */\n statePath: string\n home: string\n prefix: string\n providers: () => Readonly<Record<string, ProviderDescriptor>>\n}\n\nconst MAX_FILE_BYTES = 1024 * 1024\nconst MAX_SKILL_BYTES = 4 * 1024 * 1024\nconst MAX_SKILL_FILES = 200\nconst MAX_DEPTH = 8\n/** Not part of a skill: version control and installed dependencies. */\nconst SKIPPED_DIRS = new Set(['.git', 'node_modules'])\n\nfunction userPaths(sources: SyncSources, subPath: string): string[] {\n return projectUserPaths({ subPath, cwd: sources.home, home: sources.home, prefix: sources.prefix }).filter(p => p.source === 'user').map(p => p.path)\n}\n\n/** Every file and folder a category reads: what the watchers watch. */\nexport function syncWatchTargets(sources: SyncSources): { category: SyncCategory, path: string, recursive: boolean }[] {\n return [\n { category: 'settings', path: sources.statePath, recursive: false },\n { category: 'safelists', path: join(sources.dataDir, 'projects.json'), recursive: false },\n { category: 'files', path: userSystemMdPath({ home: sources.home, prefix: sources.prefix }), recursive: false },\n ...userPaths(sources, 'skills').map(path => ({ category: 'files' as const, path, recursive: true })),\n ...userPaths(sources, 'mcps.json').map(path => ({ category: 'mcps' as const, path, recursive: false })),\n ...userPaths(sources, 'extensions').map(path => ({ category: 'extensions' as const, path, recursive: true })),\n { category: 'environments', path: sources.statePath, recursive: false },\n { category: 'environments', path: environmentCredentialsPath(sources.dataDir), recursive: false },\n { category: 'keys', path: join(sources.dataDir, 'credentials.json'), recursive: false },\n ]\n}\n\nfunction settingsPayload(sources: SyncSources): SyncPayloads['settings'] {\n const stored = settingsFromStored(loadState(sources.statePath).settings ?? {})\n const merged = { ...DEFAULT_SETTINGS, ...stored } as Record<string, unknown>\n return { values: settingsToWire(Object.fromEntries(Object.entries(merged).filter(([key]) => key in DEFAULT_SETTINGS && isSyncableSetting(key)))) }\n}\n\nfunction safelistsPayload(sources: SyncSources): SyncPayloads['safelists'] {\n const projects = readProjects(sources.dataDir)\n return { projects: Object.fromEntries(Object.entries(projects).flatMap(([path, entry]) => (Array.isArray(entry?.safelist) ? [[path, [...entry.safelist]]] : []))) }\n}\n\n/** A skill folder's files, or null when it is over the bounds (or not a plain folder). */\nfunction skillFiles(dir: string): SyncFile[] | null {\n const files: SyncFile[] = []\n let total = 0\n const walk = (at: string, depth: number): boolean => {\n if (depth > MAX_DEPTH)\n return false\n for (const name of readdirSync(at).sort()) {\n const path = join(at, name)\n const stat = lstatSync(path)\n if (stat.isDirectory()) {\n if (SKIPPED_DIRS.has(name))\n continue\n if (!walk(path, depth + 1))\n return false\n }\n else if (stat.isFile()) {\n if (stat.size > MAX_FILE_BYTES || files.length >= MAX_SKILL_FILES)\n return false\n total += stat.size\n if (total > MAX_SKILL_BYTES)\n return false\n files.push({ path: relative(dir, path).split(sep).join('/'), data: readFileSync(path).toString('base64') })\n }\n // Links and anything else: not copied.\n }\n return true\n }\n try {\n return walk(dir, 1) && files.length > 0 ? files : null\n }\n catch {\n return null\n }\n}\n\n/** The user-scope skills: name → folder, the first root of a name winning (as discovery does). */\nexport function userSkillDirs(sources: SyncSources): Map<string, string> {\n const found = new Map<string, string>()\n for (const root of userPaths(sources, 'skills')) {\n let names: string[]\n try {\n names = readdirSync(root).sort()\n }\n catch {\n continue\n }\n for (const name of names) {\n const dir = join(root, name)\n if (!found.has(name) && !name.startsWith('.') && existsSync(join(dir, 'SKILL.md')) && lstatSync(dir).isDirectory())\n found.set(name, dir)\n }\n }\n return found\n}\n\nfunction filesPayload(sources: SyncSources): SyncPayloads['files'] {\n const mdPath = userSystemMdPath({ home: sources.home, prefix: sources.prefix })\n const systemMd = existsSync(mdPath) ? readFileSync(mdPath, 'utf8') : null\n const skills: SyncPayloads['files']['skills'] = []\n for (const [name, dir] of userSkillDirs(sources)) {\n const files = skillFiles(dir)\n if (files)\n skills.push({ name, files })\n }\n return { systemMd, skills }\n}\n\nfunction mcpsPayload(sources: SyncSources): SyncPayloads['mcps'] {\n const servers = new Map<string, Record<string, unknown>>()\n for (const path of userPaths(sources, 'mcps.json')) {\n if (!existsSync(path))\n continue\n try {\n for (const server of parseMcpsFile(readFileSync(path, 'utf8'))) {\n if (!servers.has(server.name))\n servers.set(server.name, server as unknown as Record<string, unknown>)\n }\n }\n catch {\n // A file discovery can't read either: nothing to sync from it.\n }\n }\n return { servers: [...servers.values()] }\n}\n\n/** User-scope extensions: those with a source (syncable) and the local ones (not). */\nexport function userExtensions(sources: SyncSources): { syncable: SyncPayloads['extensions']['extensions'], local: string[] } {\n const syncable: SyncPayloads['extensions']['extensions'] = []\n const local: string[] = []\n const seen = new Set<string>()\n for (const root of userPaths(sources, 'extensions')) {\n let names: string[]\n try {\n names = readdirSync(root).sort()\n }\n catch {\n continue\n }\n for (const name of names) {\n const dir = join(root, name)\n if (name.startsWith('.') || seen.has(name) || !lstatSync(dir).isDirectory())\n continue\n seen.add(name)\n // `.install.json` (`src/extensions/install.ts`): the spec an install replays.\n let provenance: { spec?: unknown, extension?: unknown } | null = null\n try {\n provenance = JSON.parse(readFileSync(join(dir, '.install.json'), 'utf8'))\n }\n catch {}\n if (provenance && typeof provenance.spec === 'string' && provenance.spec.trim() !== '')\n syncable.push({ name, spec: provenance.spec, ...(typeof provenance.extension === 'string' ? { extension: provenance.extension } : {}) })\n else\n local.push(name)\n }\n }\n return { syncable, local }\n}\n\n/** One provider with what is stored for it here: its API key, its sign-ins (by label). */\nexport interface LocalProviderAccounts {\n key: string\n label: string\n /** An API key is stored: the keys category can copy it. */\n apiKey: boolean\n /** OAuth sign-ins, by name. Never copied: a refreshed grant would sign the other side out. */\n signIns: string[]\n}\n\n/** Every provider with something stored here, API key or sign-in. */\nexport function storedProviderAccounts(sources: SyncSources): LocalProviderAccounts[] {\n return Object.values(sources.providers()).flatMap((descriptor) => {\n const stored = readProviderCredentials(sources.dataDir, descriptor)\n const apiKey = stored.some(({ credential }) => credential.kind === 'apikey' && credential.value !== '')\n const signIns = stored.flatMap(({ credential }) => (credential.kind === 'oauth' ? [oauthAccountLabel(credential)] : []))\n return apiKey || signIns.length > 0 ? [{ key: descriptor.key, label: descriptor.label, apiKey, signIns }] : []\n })\n}\n\n/** Providers with an API key stored here: what the keys category can offer. */\nexport function storedApiKeyProviders(sources: SyncSources): { key: string, label: string }[] {\n return Object.values(sources.providers()).flatMap((descriptor) => {\n const credential = readProviderCredential(sources.dataDir, descriptor)\n return credential?.kind === 'apikey' && credential.value ? [{ key: descriptor.key, label: descriptor.label }] : []\n })\n}\n\nfunction keysPayload(sources: SyncSources, optedIn: readonly string[]): SyncPayloads['keys'] {\n const providers = sources.providers()\n return {\n keys: optedIn.flatMap((provider) => {\n const descriptor = providers[provider]\n const credential = descriptor ? readProviderCredential(sources.dataDir, descriptor) : undefined\n return credential?.kind === 'apikey' && credential.value ? [{ provider, key: credential.value }] : []\n }),\n }\n}\n\n/** Stored environment secrets (`environment-credentials.json`); unreadable reads as none. */\nfunction environmentSecrets(dataDir: string): Record<string, Record<string, string>> {\n try {\n const parsed = JSON.parse(readFileSync(environmentCredentialsPath(dataDir), 'utf8')) as unknown\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed as Record<string, Record<string, string>> : {}\n }\n catch {\n return {}\n }\n}\n\n/**\n * The cloud sandbox kinds this computer set up: turned on or off, given an\n * option, or holding a key. A kind left at its defaults is not sent.\n */\nfunction environmentsPayload(sources: SyncSources): SyncPayloads['environments'] {\n const settings = readEnvironmentSettings(loadState(sources.statePath).settings ?? {})\n const secrets = environmentSecrets(sources.dataDir)\n const kinds = SYNCABLE_ENVIRONMENT_KINDS.flatMap((kind) => {\n const enabled = settings.enabled.includes(kind) ? true : settings.disabled.includes(kind) ? false : undefined\n const values = { ...settings.config[kind] }\n const keys = Object.fromEntries(Object.entries(secrets[kind] ?? {}).filter(([, value]) => typeof value === 'string' && value !== ''))\n if (enabled === undefined && Object.keys(values).length === 0 && Object.keys(keys).length === 0)\n return []\n return [{ kind, ...(enabled !== undefined ? { enabled } : {}), values, secrets: keys }]\n })\n return { kinds }\n}\n\nexport function collectSyncPayload(category: SyncCategory, sources: SyncSources, optedInKeys: readonly string[] = []): SyncPayloads[SyncCategory] {\n switch (category) {\n case 'settings': return settingsPayload(sources)\n case 'safelists': return safelistsPayload(sources)\n case 'files': return filesPayload(sources)\n case 'mcps': return mcpsPayload(sources)\n case 'extensions': return { extensions: userExtensions(sources).syncable }\n case 'environments': return environmentsPayload(sources)\n case 'keys': return keysPayload(sources, optedInKeys)\n }\n}\n","/**\n * This computer's side of settings sync, on disk:\n *\n * - `<dataDir>/device.json` (0600): the device id servers know this computer\n * by (random, made once) and its name (the host name).\n * - `<dataDir>/hosts-sync.json` (0600), next to `hosts.json`: per saved\n * server (by host id), the mode, the categories, the API keys opted in,\n * the categories still to push (the offline queue: one entry per category,\n * the newest value wins, so at most six) and the outcome of the last push.\n */\nimport type { SyncCategory, SyncMode, SyncPushResult } from './types'\nimport { randomUUID } from 'node:crypto'\nimport { existsSync, readFileSync } from 'node:fs'\nimport { hostname } from 'node:os'\nimport { join } from 'node:path'\nimport { OWNER_ONLY, writeJsonFileAtomic } from '../../atomic-write'\nimport { isRecord } from './hash'\nimport { SYNC_CATEGORIES, SYNC_MODES } from './types'\n\nexport interface DeviceIdentity {\n id: string\n name: string\n}\n\n/** Saved sync settings for one server, kept in `hosts-sync.json`. */\nexport interface ServerSyncConfig {\n /** Whether and how this computer syncs to the server. */\n mode: SyncMode\n /** Categories sent to the server. */\n categories: SyncCategory[]\n /** Providers whose stored API key goes to this server. */\n keys: string[]\n /** Categories changed here and not pushed yet (offline queue). */\n pending: SyncCategory[]\n /** Per category, a hash of what was last pushed (a restart finds what changed meanwhile). */\n sent: Partial<Record<SyncCategory, string>>\n /** Time of the last successful push in epoch ms. */\n lastSyncAt?: number\n /** Message of the last failed push. */\n lastError?: string\n /** What the last push of each category did there. */\n results: Partial<Record<SyncCategory, SyncPushResult>>\n}\n\ninterface SyncConfigFile {\n version: 1\n servers: Record<string, ServerSyncConfig>\n}\n\nexport const DEFAULT_SERVER_SYNC: Readonly<ServerSyncConfig> = { mode: 'off', categories: ['settings'], keys: [], pending: [], sent: {}, results: {} }\n\nexport function hostsSyncPath(dataDir: string): string {\n return join(dataDir, 'hosts-sync.json')\n}\n\nexport function deviceIdentityPath(dataDir: string): string {\n return join(dataDir, 'device.json')\n}\n\nfunction readJson(path: string): unknown {\n if (!existsSync(path))\n return undefined\n try {\n return JSON.parse(readFileSync(path, 'utf8'))\n }\n catch {\n return undefined\n }\n}\n\n/** This computer's device id and name, made on first use. */\nexport function readDeviceIdentity(dataDir: string, name?: string): DeviceIdentity {\n const raw = readJson(deviceIdentityPath(dataDir))\n if (isRecord(raw) && typeof raw.id === 'string' && raw.id !== '')\n return { id: raw.id, name: name ?? (typeof raw.name === 'string' && raw.name ? raw.name : hostname()) }\n const identity = { id: randomUUID(), name: name ?? hostname() }\n writeJsonFileAtomic(deviceIdentityPath(dataDir), identity, OWNER_ONLY)\n return identity\n}\n\nfunction categoriesOf(value: unknown): SyncCategory[] {\n return Array.isArray(value) ? SYNC_CATEGORIES.filter(c => value.includes(c)) : []\n}\n\nfunction serverConfigOf(raw: unknown): ServerSyncConfig {\n if (!isRecord(raw))\n return { ...DEFAULT_SERVER_SYNC }\n return {\n mode: SYNC_MODES.includes(raw.mode as SyncMode) ? raw.mode as SyncMode : 'off',\n categories: categoriesOf(raw.categories),\n keys: Array.isArray(raw.keys) ? raw.keys.filter((k): k is string => typeof k === 'string') : [],\n pending: categoriesOf(raw.pending),\n sent: isRecord(raw.sent) ? Object.fromEntries(Object.entries(raw.sent).filter(([c, h]) => SYNC_CATEGORIES.includes(c as SyncCategory) && typeof h === 'string')) : {},\n ...(typeof raw.lastSyncAt === 'number' ? { lastSyncAt: raw.lastSyncAt } : {}),\n ...(typeof raw.lastError === 'string' ? { lastError: raw.lastError } : {}),\n results: isRecord(raw.results) ? raw.results as ServerSyncConfig['results'] : {},\n }\n}\n\nexport function readSyncConfigs(dataDir: string): Record<string, ServerSyncConfig> {\n const raw = readJson(hostsSyncPath(dataDir))\n if (!isRecord(raw) || !isRecord(raw.servers))\n return {}\n return Object.fromEntries(Object.entries(raw.servers).map(([key, value]) => [key, serverConfigOf(value)]))\n}\n\nexport function writeSyncConfigs(dataDir: string, servers: Record<string, ServerSyncConfig>): void {\n const file: SyncConfigFile = { version: 1, servers }\n writeJsonFileAtomic(hostsSyncPath(dataDir), file, OWNER_ONLY)\n}\n","/**\n * The push side of settings sync (`./engine`): per server, its config (read\n * from and saved to `hosts-sync.json`), its connection, what it said last\n * (`sync.status`, a conflicting source), and the queue flush: claim, then\n * push each pending category's snapshot, one flush at a time per server.\n */\nimport type { SyncSources } from './collect'\nimport type { SyncClient } from './engine'\nimport type { DeviceIdentity, ServerSyncConfig } from './local'\nimport type { SyncCategory, SyncStatus } from './types'\nimport { collectSyncPayload } from './collect'\nimport { hashValue } from './hash'\nimport { DEFAULT_SERVER_SYNC, readSyncConfigs, writeSyncConfigs } from './local'\nimport { serverSyncCategories, SYNC_CATEGORIES } from './types'\n\nconst errorText = (err: unknown): string => (err instanceof Error ? err.message : String(err))\n\nexport function createSyncPusher({ sources, device, emit }: { sources: SyncSources, device: DeviceIdentity, emit: () => void }) {\n const configs = readSyncConfigs(sources.dataDir)\n const clients = new Map<string, SyncClient>()\n const statuses = new Map<string, SyncStatus>()\n const conflicts = new Map<string, string>()\n const flushing = new Map<string, Promise<void>>()\n const again = new Set<string>()\n let stopped = false\n\n const configOf = (key: string): ServerSyncConfig => configs[key] ?? { ...DEFAULT_SERVER_SYNC, categories: [...DEFAULT_SERVER_SYNC.categories] }\n const save = (key: string, config: ServerSyncConfig): void => {\n configs[key] = config\n writeSyncConfigs(sources.dataDir, configs)\n emit()\n }\n const fail = (key: string, err: unknown): void => save(key, { ...configOf(key), lastError: errorText(err) })\n\n /** Categories whose snapshot here differs from what was last pushed to the server. */\n const changedCategories = (config: ServerSyncConfig, only?: SyncCategory): SyncCategory[] =>\n config.categories.filter((category) => {\n if (only && category !== only)\n return false\n try {\n return hashValue(collectSyncPayload(category, sources, config.keys)) !== config.sent[category]\n }\n catch {\n return true\n }\n })\n\n const queue = (key: string, categories: readonly SyncCategory[]): void => {\n const config = configOf(key)\n const pending = SYNC_CATEGORIES.filter(c => config.pending.includes(c) || categories.includes(c))\n if (pending.length !== config.pending.length)\n save(key, { ...config, pending })\n }\n\n async function claim(key: string, client: SyncClient, takeover: boolean): Promise<boolean> {\n const config = configOf(key)\n // An older server refuses a claim naming a category it doesn't know: claim only what it takes.\n const taken = serverSyncCategories(client.hello)\n const categories = config.categories.filter(c => taken.includes(c))\n const result = await client.request('sync.claim', { deviceId: device.id, deviceName: device.name, categories, keys: config.keys, ...(takeover ? { takeover: true } : {}) })\n statuses.set(key, result.status)\n if (result.claimed)\n conflicts.delete(key)\n else\n conflicts.set(key, result.status.source?.deviceName ?? 'another device')\n return result.claimed\n }\n\n async function flushOnce(key: string, takeover: boolean): Promise<void> {\n const client = clients.get(key)\n let config = configOf(key)\n if (!client || !client.hello.sync || config.mode === 'off' || (config.pending.length === 0 && !takeover) || stopped)\n return\n try {\n if (!(await claim(key, client, takeover)))\n return\n for (const category of [...configOf(key).pending]) {\n config = configOf(key)\n if (!config.categories.includes(category)) {\n save(key, { ...config, pending: config.pending.filter(c => c !== category) })\n continue\n }\n if (!serverSyncCategories(client.hello).includes(category)) {\n save(key, {\n ...config,\n pending: config.pending.filter(c => c !== category),\n results: { ...config.results, [category]: { applied: [], unchanged: [], skipped: [{ field: category, reason: 'needs a newer server' }], warnings: [] } },\n })\n continue\n }\n const payload = collectSyncPayload(category, sources, config.keys)\n const result = await client.request('sync.push', { deviceId: device.id, category, payload })\n config = configOf(key)\n const { lastError: _dropped, ...rest } = config\n save(key, {\n ...rest,\n pending: config.pending.filter(c => c !== category),\n sent: { ...config.sent, [category]: hashValue(payload) },\n results: { ...config.results, [category]: result },\n lastSyncAt: Date.now(),\n })\n }\n statuses.set(key, await client.request('sync.status', {}))\n }\n catch (err) {\n fail(key, err)\n }\n }\n\n /** Push the queue if the server is connected and this device is its source; `takeover` claims it first. */\n function flush(key: string, takeover = false): Promise<void> {\n const running = flushing.get(key)\n if (running && !takeover) {\n again.add(key)\n return running\n }\n const run = (async () => {\n if (running)\n await running\n await flushOnce(key, takeover)\n // A change while pushing: once more (`flushOnce` stops on its own once stopped).\n while (again.delete(key))\n await flushOnce(key, false)\n })().finally(() => {\n if (flushing.get(key) === run)\n flushing.delete(key)\n emit()\n })\n flushing.set(key, run)\n emit()\n return run\n }\n\n /** Read the server's record: who syncs there now. */\n async function refresh(key: string): Promise<void> {\n const client = clients.get(key)\n if (!client?.hello.sync)\n return\n try {\n const status = await client.request('sync.status', {})\n statuses.set(key, status)\n if (!status.source || status.source.deviceId === device.id)\n conflicts.delete(key)\n else if (configOf(key).mode !== 'off')\n conflicts.set(key, status.source.deviceName)\n }\n catch {}\n emit()\n }\n\n /** This device stops being the server's source (the server keeps what it has). */\n async function release(key: string): Promise<void> {\n const client = clients.get(key)\n conflicts.delete(key)\n if (!client?.hello.sync)\n return\n await client.request('sync.release', { deviceId: device.id }).catch(() => undefined)\n const status = await client.request('sync.status', {}).catch(() => null)\n if (status)\n statuses.set(key, status)\n }\n\n function forget(key: string): void {\n clients.delete(key)\n statuses.delete(key)\n conflicts.delete(key)\n if (configs[key]) {\n delete configs[key]\n writeSyncConfigs(sources.dataDir, configs)\n }\n }\n\n return {\n configs,\n clients,\n statuses,\n conflicts,\n flushing,\n configOf,\n save,\n fail,\n changedCategories,\n queue,\n claim,\n flush,\n refresh,\n release,\n forget,\n stop: () => {\n stopped = true\n clients.clear()\n },\n }\n}\n","/**\n * File watchers for settings sync (`./engine`): on each file and folder a\n * category reads (`syncWatchTargets`), or the folder it would appear in.\n * Events are debounced into one check of the categories they touched. After\n * each check the watchers are rebuilt only when what they should watch\n * changed (a folder created since, such as the first skill). A rebuild is\n * followed by one check of everything: a write that landed while no watcher\n * was open (or before a new one delivered events) is not lost.\n */\nimport type { SyncSources } from './collect'\nimport type { SyncCategory } from './types'\nimport { existsSync, statSync, watch } from 'node:fs'\nimport { basename, dirname } from 'node:path'\nimport { syncWatchTargets } from './collect'\n\nexport interface SyncWatchers {\n /** Start (or restart) the watchers when `active`, else close them. */\n refresh: (active: boolean) => void\n /** Report a change (a watcher, or the app's own settings stream). */\n schedule: (category?: SyncCategory) => void\n count: () => number\n close: () => void\n}\n\nexport function createSyncWatchers({ sources, debounceMs, check }: {\n sources: SyncSources\n debounceMs: number\n /** After the debounce: the categories that changed (`all`: unknown which), then the watchers are refreshed by the caller. */\n check: (which: readonly (SyncCategory | 'all')[]) => void\n}): SyncWatchers {\n let watchers: { close: () => void }[] = []\n /** What the open watchers cover (`null`: none open). */\n let watchedKey: string | null = null\n let timer: ReturnType<typeof setTimeout> | null = null\n let closed = false\n const dirty = new Set<SyncCategory | 'all'>()\n\n const closeAll = (): void => {\n for (const w of watchers)\n w.close()\n watchers = []\n watchedKey = null\n }\n\n /** Each target, resolved to the folder watched now: a change here means a rebuild. */\n const plan = () => syncWatchTargets(sources).map((target) => {\n const isDir = existsSync(target.path) && statSync(target.path).isDirectory()\n const at = isDir ? target.path : dirname(target.path)\n return { ...target, isDir, at, watchable: existsSync(at) }\n })\n\n const schedule = (category?: SyncCategory): void => {\n if (closed)\n return\n dirty.add(category ?? 'all')\n if (timer)\n return\n timer = setTimeout(() => {\n timer = null\n const which = [...dirty]\n dirty.clear()\n check(which)\n }, debounceMs)\n }\n\n return {\n schedule,\n refresh(active) {\n if (closed || !active) {\n closeAll()\n return\n }\n const targets = plan()\n const key = JSON.stringify(targets.map(t => [t.category, t.path, t.isDir, t.watchable]))\n if (key === watchedKey)\n return\n closeAll()\n watchedKey = key\n for (const { category, path, isDir, at, watchable, recursive } of targets) {\n if (!watchable)\n continue\n const target = { category, path, recursive }\n const name = basename(target.path)\n try {\n // A file's folder: only that file's events, and its atomic writes'\n // temp files (`<name>.<pid>.<n>.tmp`, `../../atomic-write`): a rename\n // over the target can be reported under the temp name only (macOS).\n watchers.push(watch(at, { recursive: isDir && target.recursive, persistent: false }, (_event, changed) => {\n if (isDir || changed === null || String(changed) === name || String(changed).startsWith(`${name}.`))\n schedule(target.category)\n }))\n }\n catch {\n // A folder that can't be watched: the app's own reports still find its changes.\n }\n }\n // New watchers: catch what changed while none was listening.\n schedule()\n },\n count: () => watchers.length,\n close() {\n closed = true\n if (timer)\n clearTimeout(timer)\n timer = null\n closeAll()\n },\n }\n}\n","/**\n * Settings sync, the client side shared by the TUI and the GUI main process:\n * for each saved server, push this computer's categories to it\n * (`sync.claim`, `sync.push`), now (Import once) or as they change (Keep in\n * sync). Loaded by each app behind its `remoteHosts` gate only, with the\n * saved hosts; nothing here dials: the app hands over each server's\n * connection (`connected` / `disconnected`).\n *\n * Change detection: file watchers on what each category reads\n * (`syncWatchTargets`), only while some server keeps in sync, plus the\n * app's own reports (`notifyLocalChange`: its settings stream). Either one\n * schedules a check (debounced): a category whose snapshot hashes\n * differently from what was last pushed is queued (`pending`, persisted in\n * `hosts-sync.json`), and the queue flushes when the server is connected.\n * Offline, it waits; the newest value wins, there being one source.\n */\nimport type { ProviderDescriptor } from '../providers'\nimport type { LocalProviderAccounts } from './collect'\nimport type { DeviceIdentity, ServerSyncConfig } from './local'\nimport type { SyncCategory, SyncClaimResult, SyncPayload, SyncPayloads, SyncPushResult, SyncStatus } from './types'\nimport { collectSyncPayload, storedApiKeyProviders, storedProviderAccounts, userExtensions } from './collect'\nimport { hashValue } from './hash'\nimport { readDeviceIdentity } from './local'\nimport { createSyncPusher } from './pusher'\nimport { fieldCategory, SYNC_CATEGORIES } from './types'\nimport { createSyncWatchers } from './watchers'\n\n/** The part of a host connection sync uses (an `IpcClient` is one). */\nexport interface SyncClient {\n /** Sends a sync RPC to the server and resolves with its reply. */\n request: ((method: 'sync.status', params: Record<string, never>) => Promise<SyncStatus>)\n & ((method: 'sync.claim', params: { deviceId: string, deviceName: string, takeover?: boolean, categories?: SyncCategory[], keys?: string[] }) => Promise<SyncClaimResult>)\n & ((method: 'sync.push', params: { deviceId: string, category: SyncCategory, payload: SyncPayload }) => Promise<SyncPushResult>)\n & ((method: 'sync.reset', params: { field: string }) => Promise<{ reset: boolean }>)\n & ((method: 'sync.release', params: { deviceId: string }) => Promise<{ released: boolean }>)\n /** What the server announced when it connected. `sync` is set when it supports settings sync. */\n hello: { sync?: boolean, syncCategories?: SyncCategory[] }\n}\n\n/** Options for {@link createSyncEngine}. */\nexport interface SyncEngineOptions {\n /** Directory holding the sync state files and the device identity. */\n dataDir: string\n /** Path of the file holding the app's saved settings. */\n statePath: string\n /** User home directory, where user-scope files and skills are read from. */\n home: string\n /** Storage prefix, such as `.zidane`. */\n prefix: string\n /** Returns the provider descriptors used to list API keys that can be opted in. */\n providers: () => Readonly<Record<string, ProviderDescriptor>>\n /** Default: this computer's host name. */\n deviceName?: string\n /** Wait after a change before checking (a save is often several writes). Default 500 ms. */\n debounceMs?: number\n /** File watchers (default on); off, only `notifyLocalChange` finds changes. */\n watch?: boolean\n}\n\n/** What a UI shows for one server. */\nexport interface SyncView {\n /** Saved sync settings for the server. */\n config: ServerSyncConfig\n /** Whether a connection to the server is open. */\n connected: boolean\n /** The server takes sync (`ipc.hello.sync`); false for an older server. */\n supported: boolean\n /** The server's record, when read. */\n status: SyncStatus | null\n /** Another device syncs to this server: its name. */\n conflict: string | null\n /** A push or refresh is running. */\n busy: boolean\n}\n\n/** Pushes this computer's settings to saved servers and tracks the state each one reports. Servers are identified by key. */\nexport interface SyncEngine {\n /** Identity of this computer. */\n device: DeviceIdentity\n /** Returns what a UI shows for the server. */\n view: (key: string) => SyncView\n /** Change the mode, categories or opted-in keys; pushes what that calls for. */\n configure: (key: string, patch: Partial<Pick<ServerSyncConfig, 'mode' | 'categories' | 'keys'>>) => Promise<void>\n /** Push every selected category now (whatever the mode, but off). */\n pushAll: (key: string) => Promise<void>\n /** Become the source of a server another device syncs to, then push. */\n takeOver: (key: string) => Promise<void>\n /** Drop an override on the server (the next push writes the field again). */\n reset: (key: string, field: string) => Promise<void>\n /** Read the server's record again. */\n refresh: (key: string) => Promise<void>\n /** Reports that a connection to the server opened. Reads its record and pushes what its mode calls for. */\n connected: (key: string, client: SyncClient) => void\n /** Reports that the connection to the server closed. */\n disconnected: (key: string) => void\n /** A server's `sync:changed` signal. */\n serverChanged: (key: string) => void\n /** The server was removed here: forget its sync config (the server keeps what it has). */\n removed: (key: string) => void\n /** This computer changed something (the app's settings stream): check, push if it differs. */\n notifyLocalChange: (category?: SyncCategory) => void\n /** What this computer has that the UIs mention: API keys to opt in, extensions that can't sync, stdio MCP servers. */\n localInfo: () => SyncLocalInfo\n /** File watchers are running (some server keeps in sync). */\n watching: () => boolean\n /** Calls `listener` when any view changes. Returns a function that removes it. */\n subscribe: (listener: () => void) => () => void\n /** Stops the file watchers and pending pushes. */\n stop: () => void\n}\n\n/** What this computer has that sync UIs mention. */\nexport interface SyncLocalInfo {\n /** Providers with an API key that can be opted in, with display labels. */\n keyProviders: { key: string, label: string }[]\n /** Every provider with an API key or a sign-in here (the GUI's accounts list). */\n accounts: LocalProviderAccounts[]\n /** Names of extensions that cannot sync because they are not installed from npm or GitHub. */\n localExtensions: string[]\n /** Names of stdio MCP servers, whose commands may be missing on the server. */\n stdioMcps: string[]\n}\n\n/**\n * Engines CREATED in this process so far: monotonic (never decremented when\n * one stops), not a live count. The containment tests compare it before and\n * after a mount to prove an engine did, or did not, start.\n */\nlet engines = 0\nexport function syncEngineCount(): number {\n return engines\n}\n\n/**\n * Creates a sync engine for this computer. Starts file watchers once a server is set to keep in sync.\n *\n * @param options - Paths, providers and timing for the engine.\n */\nexport function createSyncEngine(options: SyncEngineOptions): SyncEngine {\n engines++\n const sources = { dataDir: options.dataDir, statePath: options.statePath, home: options.home, prefix: options.prefix, providers: options.providers }\n const device = readDeviceIdentity(options.dataDir, options.deviceName)\n const listeners = new Set<() => void>()\n const emit = (): void => {\n for (const listener of [...listeners]) {\n try {\n listener()\n }\n catch {}\n }\n }\n const pusher = createSyncPusher({ sources, device, emit })\n const { configOf, save, queue, flush, clients, statuses, conflicts } = pusher\n const keeping = (): string[] => Object.entries(pusher.configs).filter(([, c]) => c.mode === 'keep').map(([key]) => key)\n\n const watchers = createSyncWatchers({\n sources,\n debounceMs: options.debounceMs ?? 500,\n // Each server that keeps in sync: queue what changed, push it.\n check: (which) => {\n for (const key of keeping()) {\n const config = configOf(key)\n const changed = which.includes('all') ? pusher.changedCategories(config) : which.flatMap(c => pusher.changedCategories(config, c as SyncCategory))\n if (changed.length > 0) {\n queue(key, changed)\n void flush(key)\n }\n }\n refreshWatchers()\n },\n })\n function refreshWatchers(): void {\n watchers.refresh(options.watch !== false && keeping().length > 0)\n emit()\n }\n\n /** A config patch applied: what it turns on gets pushed (all of it when a mode starts). */\n async function apply(key: string, before: ServerSyncConfig, next: ServerSyncConfig, patch: Partial<ServerSyncConfig>): Promise<void> {\n if (next.mode === 'off') {\n const turnedOff = before.mode !== 'off'\n save(key, turnedOff ? { ...next, pending: [], sent: {} } : next)\n if (turnedOff) {\n refreshWatchers()\n // The server keeps what it has; it stops naming this device as its source.\n await pusher.release(key)\n emit()\n }\n return\n }\n const fresh = before.mode === 'off' || patch.mode === 'once'\n const added = next.categories.filter(c => !before.categories.includes(c))\n const keysChanged = patch.keys !== undefined && hashValue(before.keys) !== hashValue(next.keys)\n const toPush = fresh ? next.categories : [...added, ...(keysChanged && next.categories.includes('keys') ? ['keys' as const] : [])]\n next.pending = SYNC_CATEGORIES.filter(c => next.categories.includes(c) && (next.pending.includes(c) || toPush.includes(c)))\n if (fresh)\n next.sent = {}\n save(key, next)\n refreshWatchers()\n // New categories or keys with nothing queued: claim again, the server checks pushes against them.\n const client = clients.get(key)\n if (client?.hello.sync && next.pending.length === 0 && (added.length > 0 || keysChanged)) {\n await pusher.claim(key, client, false).catch(err => pusher.fail(key, err))\n emit()\n }\n await flush(key)\n }\n\n const engine: SyncEngine = {\n device,\n\n view: key => ({\n config: configOf(key),\n connected: clients.has(key),\n supported: clients.get(key)?.hello.sync === true,\n status: statuses.get(key) ?? null,\n conflict: conflicts.get(key) ?? null,\n busy: pusher.flushing.has(key),\n }),\n\n configure(key, patch) {\n const before = configOf(key)\n const next: ServerSyncConfig = {\n ...before,\n ...(patch.mode ? { mode: patch.mode } : {}),\n ...(patch.categories ? { categories: SYNC_CATEGORIES.filter(c => patch.categories!.includes(c)) } : {}),\n ...(patch.keys ? { keys: [...new Set(patch.keys)] } : {}),\n }\n return apply(key, before, next, patch)\n },\n\n async pushAll(key) {\n const config = configOf(key)\n if (config.mode === 'off')\n return\n save(key, { ...config, pending: [...config.categories], sent: {} })\n await flush(key)\n },\n\n async takeOver(key) {\n const config = configOf(key)\n if (config.mode === 'off')\n return\n save(key, { ...config, pending: [...config.categories], sent: {} })\n await flush(key, true)\n },\n\n async reset(key, field) {\n const client = clients.get(key)\n if (!client)\n throw new Error('The server is not connected.')\n await client.request('sync.reset', { field })\n await pusher.refresh(key)\n // That field is sync's again: send its category.\n const category = fieldCategory(field)\n const config = configOf(key)\n if (category && config.categories.includes(category) && config.mode !== 'off') {\n save(key, { ...config, sent: { ...config.sent, [category]: '' } })\n queue(key, [category])\n await flush(key)\n }\n },\n\n refresh: key => pusher.refresh(key),\n\n connected(key, client) {\n clients.set(key, client)\n emit()\n if (!client.hello.sync)\n return\n const config = configOf(key)\n // Keep in sync: what changed here while away (or while this app was closed) joins the queue.\n if (config.mode === 'keep') {\n const changed = pusher.changedCategories(config)\n if (changed.length > 0)\n queue(key, changed)\n }\n void pusher.refresh(key).then(() => flush(key))\n },\n\n disconnected(key) {\n clients.delete(key)\n emit()\n },\n\n serverChanged: (key) => {\n const before = statuses.get(key)\n void pusher.refresh(key).then(() => {\n // An override reset there (from any window): its category is sync's again, push it.\n const after = statuses.get(key)\n const config = configOf(key)\n if (!before || !after || after.source?.deviceId !== device.id || config.mode === 'off')\n return\n const back = Object.keys(before.overrides).filter(field => !after.overrides[field]).map(fieldCategory).filter((c): c is SyncCategory => c !== undefined && config.categories.includes(c))\n if (back.length === 0)\n return\n save(key, { ...config, sent: Object.fromEntries(Object.entries(config.sent).filter(([c]) => !back.includes(c as SyncCategory))) })\n queue(key, back)\n void flush(key)\n })\n },\n\n removed(key) {\n if (configOf(key).mode !== 'off')\n void pusher.release(key)\n pusher.forget(key)\n refreshWatchers()\n },\n\n notifyLocalChange(category) {\n if (keeping().length > 0)\n watchers.schedule(category)\n },\n\n localInfo: () => syncLocalInfo(sources),\n\n watching: () => watchers.count() > 0,\n\n subscribe(listener) {\n listeners.add(listener)\n return () => listeners.delete(listener)\n },\n\n stop() {\n watchers.close()\n pusher.stop()\n listeners.clear()\n },\n }\n\n refreshWatchers()\n return engine\n}\n\nfunction syncLocalInfo(sources: Parameters<typeof storedApiKeyProviders>[0]): SyncLocalInfo {\n const read = <T>(fn: () => T, fallback: T): T => {\n try {\n return fn()\n }\n catch {\n return fallback\n }\n }\n return {\n keyProviders: read(() => storedApiKeyProviders(sources), []),\n accounts: read(() => storedProviderAccounts(sources), []),\n localExtensions: read(() => userExtensions(sources).local, []),\n stdioMcps: read(() => (collectSyncPayload('mcps', sources) as SyncPayloads['mcps']).servers.filter(s => typeof s.command === 'string' && s.command !== '').map(s => String(s.name)), []),\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { n as entryPortOf, t as createProcessContext } from "./contexts-DLZTSRHU.js";
|
|
2
2
|
import { t as resolveStorageDirs } from "./xdg-DFSAQQG8.js";
|
|
3
3
|
import { b as workspacePath, d as normalizeEnvironmentCheckout } from "./environment-Cxws_RyM.js";
|
|
4
|
-
import { Gn as prepareWorktree, Kn as teardownWorktree, an as resolveSandboxForRoots, dn as createWorkspaceConfinement, fn as isInside, on as sandboxUnreadableDirs } from "./auto-update-restart-
|
|
4
|
+
import { Gn as prepareWorktree, Kn as teardownWorktree, an as resolveSandboxForRoots, dn as createWorkspaceConfinement, fn as isInside, on as sandboxUnreadableDirs } from "./auto-update-restart-_S0w600V.js";
|
|
5
5
|
import { ht as MACHINE_STREAM_CHUNK_BYTES } from "./repo-url-BymRbAjC.js";
|
|
6
6
|
import { Buffer } from "node:buffer";
|
|
7
7
|
import { isAbsolute, join, resolve } from "node:path";
|
|
@@ -706,4 +706,4 @@ function createMachineExecutor(options) {
|
|
|
706
706
|
//#endregion
|
|
707
707
|
export { createMachineExecutor, shareableRoot };
|
|
708
708
|
|
|
709
|
-
//# sourceMappingURL=executor-
|
|
709
|
+
//# sourceMappingURL=executor-BUWZhsfn.js.map
|