zidane 6.6.3 → 7.0.0

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.
Files changed (159) hide show
  1. package/dist/{acp-BQqhozmt.js → acp-Wr8l4IDI.js} +7 -180
  2. package/dist/acp-Wr8l4IDI.js.map +1 -0
  3. package/dist/acp-cli.js +6 -6
  4. package/dist/acp.d.ts +1 -1
  5. package/dist/acp.d.ts.map +1 -1
  6. package/dist/acp.js +2 -1
  7. package/dist/{agent-DTxH7RHW.js → agent-C-camKQW.js} +162 -12
  8. package/dist/agent-C-camKQW.js.map +1 -0
  9. package/dist/agent.d.ts +1 -1
  10. package/dist/agent.js +1 -1
  11. package/dist/{anthropic-DRNRnd6W.js → anthropic-BDOGzhtj.js} +3 -3
  12. package/dist/{anthropic-DRNRnd6W.js.map → anthropic-BDOGzhtj.js.map} +1 -1
  13. package/dist/{transcript-anchors-BjH-68k2.js → approvals-CqqxE5Vh.js} +4924 -275
  14. package/dist/approvals-CqqxE5Vh.js.map +1 -0
  15. package/dist/{auth-7rv6ZfWk.js → auth-BdwDROYg.js} +12 -8
  16. package/dist/{auth-7rv6ZfWk.js.map → auth-BdwDROYg.js.map} +1 -1
  17. package/dist/chat/pure.d.ts +221 -4
  18. package/dist/chat/pure.d.ts.map +1 -0
  19. package/dist/chat/pure.js +4 -2
  20. package/dist/chat.d.ts +6 -2911
  21. package/dist/chat.js +8 -261
  22. package/dist/{context-transport-D8yeGdpd.js → context-transport-BqYwzJYE.js} +2 -2
  23. package/dist/{context-transport-D8yeGdpd.js.map → context-transport-BqYwzJYE.js.map} +1 -1
  24. package/dist/contexts/daytona.d.ts +2 -1
  25. package/dist/contexts/daytona.d.ts.map +1 -1
  26. package/dist/contexts/daytona.js +14 -3
  27. package/dist/contexts/daytona.js.map +1 -1
  28. package/dist/contexts/e2b.d.ts +1 -1
  29. package/dist/contexts/e2b.js +1 -1
  30. package/dist/daemon-cli.d.ts +1 -0
  31. package/dist/daemon-cli.js +90 -0
  32. package/dist/daemon-cli.js.map +1 -0
  33. package/dist/eval.d.ts +1 -1
  34. package/dist/eval.js +1 -1
  35. package/dist/extensions.d.ts +2 -2
  36. package/dist/extensions.js +2 -2
  37. package/dist/headless.d.ts +1 -1
  38. package/dist/headless.js +4 -4
  39. package/dist/{index-D0O-QO7Z.d.ts → index-B-FSaM3_.d.ts} +2863 -2375
  40. package/dist/index-B-FSaM3_.d.ts.map +1 -0
  41. package/dist/index-DgdzXRPH.d.ts +3590 -0
  42. package/dist/index-DgdzXRPH.d.ts.map +1 -0
  43. package/dist/index.d.ts +2 -2
  44. package/dist/index.js +14 -1179
  45. package/dist/ipc-CIv87xuH.js +331 -0
  46. package/dist/ipc-CIv87xuH.js.map +1 -0
  47. package/dist/ipc.d.ts +849 -0
  48. package/dist/ipc.d.ts.map +1 -0
  49. package/dist/ipc.js +4 -0
  50. package/dist/json-rpc-rSWMUh9b.js +192 -0
  51. package/dist/json-rpc-rSWMUh9b.js.map +1 -0
  52. package/dist/{logger-DBX9uYOw.js → logger--C1VQjwJ.js} +3 -13
  53. package/dist/logger--C1VQjwJ.js.map +1 -0
  54. package/dist/logs-view-CWG8aUAG.js +44 -0
  55. package/dist/logs-view-CWG8aUAG.js.map +1 -0
  56. package/dist/{mcp-CVtSQTWg.js → mcp-BIWa57eY.js} +3 -3
  57. package/dist/{mcp-CVtSQTWg.js.map → mcp-BIWa57eY.js.map} +1 -1
  58. package/dist/mcp.d.ts +1 -1
  59. package/dist/mcp.js +1 -1
  60. package/dist/{messages-CZMUuKzo.js → messages-21XfHxyF.js} +3 -3
  61. package/dist/{messages-CZMUuKzo.js.map → messages-21XfHxyF.js.map} +1 -1
  62. package/dist/{openai-compat-BoTi-9fd.js → openai-compat-DY-AKxCg.js} +2 -2
  63. package/dist/{openai-compat-BoTi-9fd.js.map → openai-compat-DY-AKxCg.js.map} +1 -1
  64. package/dist/output/stream-json.d.ts +1 -1
  65. package/dist/output/terminal.d.ts +1 -1
  66. package/dist/{plugins-onDvTHEm.js → plugins-B531Tz5b.js} +2 -2
  67. package/dist/{plugins-onDvTHEm.js.map → plugins-B531Tz5b.js.map} +1 -1
  68. package/dist/plugins.d.ts +1 -1
  69. package/dist/plugins.js +1 -1
  70. package/dist/presets.d.ts +1 -1
  71. package/dist/presets.js +1 -1
  72. package/dist/providers/anthropic.d.ts +1 -1
  73. package/dist/providers/anthropic.js +1 -1
  74. package/dist/providers/arcee.d.ts +1 -1
  75. package/dist/providers/arcee.js +1 -1
  76. package/dist/providers/baseten.d.ts +1 -1
  77. package/dist/providers/baseten.js +1 -1
  78. package/dist/providers/cerebras.d.ts +1 -1
  79. package/dist/providers/cerebras.js +1 -1
  80. package/dist/providers/local.d.ts +1 -1
  81. package/dist/providers/local.js +1 -1
  82. package/dist/providers/openai-compat.d.ts +1 -1
  83. package/dist/providers/openai-compat.js +1 -1
  84. package/dist/providers/openai.d.ts +1 -1
  85. package/dist/providers/openai.js +3 -3
  86. package/dist/providers/openrouter.d.ts +1 -1
  87. package/dist/providers/openrouter.js +1 -1
  88. package/dist/providers/xai.d.ts +1 -1
  89. package/dist/providers/xai.js +1 -1
  90. package/dist/{providers-CmhdtD7w.js → providers-DX91jwir.js} +2 -2
  91. package/dist/{providers-CmhdtD7w.js.map → providers-DX91jwir.js.map} +1 -1
  92. package/dist/providers.d.ts +1 -1
  93. package/dist/providers.js +3 -3
  94. package/dist/restate.d.ts +1 -1
  95. package/dist/restate.js +8 -8
  96. package/dist/restate.js.map +1 -1
  97. package/dist/server-DJqp9dIR.js +1317 -0
  98. package/dist/server-DJqp9dIR.js.map +1 -0
  99. package/dist/session/sqlite.d.ts +1 -1
  100. package/dist/session/sqlite.d.ts.map +1 -1
  101. package/dist/session/sqlite.js +42 -4
  102. package/dist/session/sqlite.js.map +1 -1
  103. package/dist/{session-CyRLSsVa.js → session-K3icbPH9.js} +8 -2
  104. package/dist/session-K3icbPH9.js.map +1 -0
  105. package/dist/session.d.ts +1 -1
  106. package/dist/session.js +2 -2
  107. package/dist/{skills-BMGH_KK1.js → skills-PJyiSRCp.js} +3 -21
  108. package/dist/skills-PJyiSRCp.js.map +1 -0
  109. package/dist/skills.d.ts +1 -1
  110. package/dist/skills.js +1 -1
  111. package/dist/src-DzzhE2YO.js +2461 -0
  112. package/dist/src-DzzhE2YO.js.map +1 -0
  113. package/dist/todo-edits-Dw2oO2ON.js +378 -0
  114. package/dist/todo-edits-Dw2oO2ON.js.map +1 -0
  115. package/dist/{tool-formatters-NrzD6Fkk.d.ts → tool-formatters-DO56YBBw.d.ts} +104 -4
  116. package/dist/tool-formatters-DO56YBBw.d.ts.map +1 -0
  117. package/dist/tools/fetch-url.d.ts +1 -1
  118. package/dist/tools/web-search.d.ts +1 -1
  119. package/dist/tools.d.ts +1 -1
  120. package/dist/tools.js +1 -1
  121. package/dist/{transcript-anchors-UhC58Tmj.d.ts → transcript-anchors-DXqWWGbq.d.ts} +53 -4
  122. package/dist/transcript-anchors-DXqWWGbq.d.ts.map +1 -0
  123. package/dist/tui.d.ts +94 -13
  124. package/dist/tui.d.ts.map +1 -1
  125. package/dist/tui.js +2241 -1009
  126. package/dist/tui.js.map +1 -1
  127. package/dist/{turn-operations-mMxDRgLY.js → turn-operations-CZLsNgw-.js} +896 -11
  128. package/dist/turn-operations-CZLsNgw-.js.map +1 -0
  129. package/dist/{turn-operations-Cuc9pwiB.d.ts → turn-operations-DKFYWP3O.d.ts} +501 -4
  130. package/dist/turn-operations-DKFYWP3O.d.ts.map +1 -0
  131. package/dist/{types-BIarq1qE.js → types-CWgENVLY.js} +24 -2
  132. package/dist/types-CWgENVLY.js.map +1 -0
  133. package/dist/types.d.ts +1 -1
  134. package/dist/types.js +1 -1
  135. package/dist/xml-D5HBhJle.js +22 -0
  136. package/dist/xml-D5HBhJle.js.map +1 -0
  137. package/docs/CHAT.md +39 -0
  138. package/docs/IPC.md +420 -0
  139. package/docs/TUI.md +62 -0
  140. package/package.json +8 -2
  141. package/dist/acp-BQqhozmt.js.map +0 -1
  142. package/dist/agent-DTxH7RHW.js.map +0 -1
  143. package/dist/chat.d.ts.map +0 -1
  144. package/dist/chat.js.map +0 -1
  145. package/dist/index-D0O-QO7Z.d.ts.map +0 -1
  146. package/dist/index.js.map +0 -1
  147. package/dist/inline-autocomplete-D0MPvBaS.js +0 -596
  148. package/dist/inline-autocomplete-D0MPvBaS.js.map +0 -1
  149. package/dist/logger-DBX9uYOw.js.map +0 -1
  150. package/dist/login-CB23LyI1.js +0 -707
  151. package/dist/login-CB23LyI1.js.map +0 -1
  152. package/dist/session-CyRLSsVa.js.map +0 -1
  153. package/dist/skills-BMGH_KK1.js.map +0 -1
  154. package/dist/tool-formatters-NrzD6Fkk.d.ts.map +0 -1
  155. package/dist/transcript-anchors-BjH-68k2.js.map +0 -1
  156. package/dist/transcript-anchors-UhC58Tmj.d.ts.map +0 -1
  157. package/dist/turn-operations-Cuc9pwiB.d.ts.map +0 -1
  158. package/dist/turn-operations-mMxDRgLY.js.map +0 -1
  159. package/dist/types-BIarq1qE.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"login-CB23LyI1.js","names":[],"sources":["../src/extension-authoring.ts","../src/mcp/oauth-callback.ts","../src/mcp/login.ts"],"sourcesContent":["/**\n * Host glue for the \"edit / create extension\" and \"edit skill\" authoring\n * flows (TUI + GUI).\n *\n * Pure, renderer-neutral helpers consumed by the hosts — deliberately kept OUT\n * of `src/extensions/**` (the extension loader / registry core) so the\n * authoring UX can never entangle with discovery / registration. Imports only\n * the skills lib and node builtins.\n *\n * - {@link extensionScaffold} — minimal `index.ts` for a new extension.\n * - {@link resolveExtensionDocsDir} — locate the shipped `docs/` directory.\n * - {@link buildZidaneExtensionSkill} — the inline extension docs skill.\n * - {@link buildZidaneSkillEditSkill} — the inline skill-editing skill.\n * - {@link withZidaneExtensionSkill} / {@link withZidaneSkillEditSkill}\n * — inject either into a `SkillsConfig`.\n * - {@link editSessionMetadata} — the ONE canonical \"open an editing session\n * with preset context\" seed both hosts stamp at session creation.\n * - {@link EXTENSION_EDIT_METADATA_KEY} / {@link SKILL_EDIT_METADATA_KEY} /\n * {@link ZIDANE_EXTENSION_SKILL_NAME} / {@link ZIDANE_SKILL_EDIT_SKILL_NAME}\n * — constants shared by both hosts.\n */\nimport type { SkillConfig, SkillsConfig } from './skills'\nimport { existsSync } from 'node:fs'\nimport { createRequire } from 'node:module'\nimport { basename, dirname, join, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { defineSkill } from './skills'\n\n/**\n * Session-metadata key marking an \"edit / create extension\" session. The value\n * is an {@link ExtensionEditMarker}. Both hosts stamp it at session creation so\n * the agent build can scope the docs skill to exactly these sessions (and it\n * survives reload because metadata is persisted).\n */\nexport const EXTENSION_EDIT_METADATA_KEY = 'zidane.extensionEdit'\n\n/** Name of the inline docs skill auto-activated in extension edit sessions. */\nexport const ZIDANE_EXTENSION_SKILL_NAME = 'zidane-extension'\n\n/**\n * Session-metadata key marking an \"edit skill\" session — the skill-editing\n * counterpart of {@link EXTENSION_EDIT_METADATA_KEY}. The value is a\n * {@link SkillEditMarker}. Stamped at session creation by both hosts (via\n * {@link editSessionMetadata}); persisted metadata means it survives reload.\n */\nexport const SKILL_EDIT_METADATA_KEY = 'zidane.skillEdit'\n\n/** Name of the inline preset skill auto-activated in skill edit sessions. */\nexport const ZIDANE_SKILL_EDIT_SKILL_NAME = 'zidane-skill'\n\n/** Per-edit-session marker persisted on `session.metadata[EXTENSION_EDIT_METADATA_KEY]`. */\nexport interface ExtensionEditMarker {\n /** Absolute path to the extension's directory (the session's cwd). */\n dir: string\n}\n\n/** Per-edit-session marker persisted on `session.metadata[SKILL_EDIT_METADATA_KEY]`. */\nexport interface SkillEditMarker {\n /** Absolute path to the skill's directory (the session's cwd). */\n dir: string\n /** The skill's name (frontmatter-first, as discovered). */\n name: string\n}\n\n/**\n * What an editing session targets. `dir` becomes the session's cwd; `name`\n * feeds the preset wording (and the skill marker).\n */\nexport interface EditSessionTarget {\n kind: 'extension' | 'skill'\n name: string\n dir: string\n}\n\n/**\n * THE canonical seed for \"open an editing session with preset context\" —\n * the session metadata both hosts stamp at creation time. The engines read\n * the marker back and inject + auto-activate the matching inline preset\n * skill ({@link buildZidaneExtensionSkill} / {@link buildZidaneSkillEditSkill}),\n * so the preset rides as real conversation context from turn one.\n */\nexport function editSessionMetadata(target: EditSessionTarget): Record<string, unknown> {\n return target.kind === 'extension'\n ? { [EXTENSION_EDIT_METADATA_KEY]: { dir: target.dir } satisfies ExtensionEditMarker }\n : { [SKILL_EDIT_METADATA_KEY]: { dir: target.dir, name: target.name } satisfies SkillEditMarker }\n}\n\n/**\n * Parse a persisted {@link SkillEditMarker} back out of session metadata.\n * Tolerant: anything that isn't `{ dir: string, name: string }` → `null`\n * (older sessions, manual metadata edits, type drift).\n */\nexport function readSkillEditMarker(raw: unknown): SkillEditMarker | null {\n if (typeof raw !== 'object' || raw === null)\n return null\n const { dir, name } = raw as Record<string, unknown>\n if (typeof dir !== 'string' || dir.length === 0 || typeof name !== 'string' || name.length === 0)\n return null\n return { dir, name }\n}\n\n/**\n * Minimal valid extension module for a freshly-created extension. `name` MUST\n * equal the folder name or the loader rejects it (see `src/extensions/discovery`).\n * Does nothing by default — the author registers contributions via `ctx.*`\n * inside `setup`; the auto-activated docs skill guides them from there.\n */\nexport function extensionScaffold(name: string): string {\n return `import { defineExtension } from 'zidane'\n\n/**\n * ${name} — a zidane extension.\n *\n * Register tools / hooks / skills / MCP servers / subagents / config / themes /\n * agents / keybindings via the \\`ctx\\` surface inside \\`setup\\`.\n *\n * Authoring reference: activate the \\`${ZIDANE_EXTENSION_SKILL_NAME}\\` skill — it carries\n * the full \\`ctx\\` API + invariants and is available whenever you open this\n * folder in zidane (or run \\`zidane\\` from inside it).\n */\nexport default defineExtension({\n name: '${name}',\n setup(ctx) {\n // e.g. ctx.registerTool('${name}_hello', {\n // spec: { name: '${name}_hello', description: 'say hi', inputSchema: { type: 'object', properties: {} } },\n // isConcurrencySafe: true,\n // async execute() { return 'hello from ${name}' },\n // })\n },\n})\n`\n}\n\n/**\n * Locate the shipped `docs/` directory (`docs/EXTENSIONS.md` + siblings).\n *\n * The docs ship via `package.json` `files`, so an installed consumer has\n * `<root>/node_modules/zidane/docs` and the repo itself has `<repo>/docs`.\n * Returns the first existing candidate, or `null` (the skill then falls back to\n * the literal `node_modules/zidane/docs` hint, which the model's file tools can\n * still attempt from cwd).\n */\nexport function resolveExtensionDocsDir(cwd: string = process.cwd()): string | null {\n const candidates: string[] = []\n\n // 1) The installed (or self-linked) zidane package, via its package.json\n // (exposed through `exports[\"./package.json\"]`).\n try {\n const req = createRequire(import.meta.url)\n candidates.push(join(dirname(req.resolve('zidane/package.json')), 'docs'))\n }\n catch {\n // No resolvable package (compiled binary / odd layout) — fall through.\n }\n\n // 2) Relative to this module: `dist/extension-authoring.js` -> `../docs`, or\n // walking up from `src/` when running from source.\n try {\n let dir = dirname(fileURLToPath(import.meta.url))\n for (let i = 0; i < 6; i++) {\n if (existsSync(join(dir, 'docs', 'EXTENSIONS.md'))) {\n candidates.push(join(dir, 'docs'))\n break\n }\n dir = dirname(dir)\n }\n }\n catch {\n // `import.meta.url` is virtual under `bun build --compile` — fall through.\n }\n\n // 3) cwd-relative — the project the author is actually editing.\n candidates.push(join(cwd, 'node_modules', 'zidane', 'docs'))\n\n for (const candidate of candidates) {\n if (existsSync(join(candidate, 'EXTENSIONS.md')))\n return candidate\n }\n return null\n}\n\n/** Entry-file probe order for an extension dir (mirrors the loader's discovery). */\nconst EXTENSION_ENTRY_FILES = ['index.ts', 'index.tsx', 'index.mjs', 'index.js'] as const\n\n/**\n * If `cwd` sits inside a zidane extension, return that extension's root dir,\n * else `null`. Mirrors the discovery layout\n * `<scope>/.{agents,zidane}/extensions/<name>/` — the root is the `<name>` dir\n * (parent `extensions`, grandparent `.agents` / `.zidane`) holding an entry\n * file. Walks up from `cwd`, so the nearest enclosing extension wins. Lets the\n * hosts surface the authoring skill for a plain `zidane` launched from an\n * extension folder, not just sessions opened through the edit/create flow.\n */\nexport function extensionDirForCwd(cwd: string = process.cwd()): string | null {\n let dir = resolve(cwd)\n // Bounded walk to the filesystem root (the `parent === dir` guard is the real\n // terminator; the cap is just belt-and-braces against pathological inputs).\n for (let i = 0; i < 64; i++) {\n const parent = dirname(dir)\n if (\n basename(parent) === 'extensions'\n && (basename(dirname(parent)) === '.agents' || basename(dirname(parent)) === '.zidane')\n && EXTENSION_ENTRY_FILES.some(f => existsSync(join(dir, f)))\n ) {\n return dir\n }\n if (parent === dir)\n break\n dir = parent\n }\n return null\n}\n\n/**\n * The authoring reference embedded into the skill body. Kept INLINE (not a\n * filesystem path) on purpose: a `bun build --compile` TUI binary has no\n * resolvable `node_modules/zidane/docs`, no real `import.meta.url`, and the\n * edited extension's own cwd has no `node_modules` — so a path reference dead-\n * ends there. The model gets the API verbatim on activation regardless of host.\n *\n * Mirrors `ExtensionContext` (src/extensions/types.ts) — keep in sync when the\n * `ctx.*` surface changes.\n */\nconst EXTENSION_AUTHORING_CHEATSHEET = `## defineExtension\n\nA zidane extension is a module at \\`<scope>/.{agents,zidane}/extensions/<name>/index.{ts,tsx,mjs,js}\\`\nthat default-exports \\`defineExtension({ ... })\\`. It runs in-process in the host (TUI / GUI / SDK).\n\n\\`\\`\\`ts\nimport { defineExtension } from 'zidane'\n\nexport default defineExtension({\n name: 'my-ext', // MUST equal the folder name\n apiVersion: 1, // optional; omitted means v1\n version: '0.1.0', // optional — shown in Settings → Extensions\n displayName: 'My Ext', // optional UI label\n description: 'what it does',\n capabilities: ['tools'], // optional descriptive manifest labels\n permissions: [], // optional descriptive labels; not a sandbox\n setup(ctx) { /* register everything here; may be async */ },\n teardown() {}, // optional, reserved (no hot-reload yet)\n})\n\\`\\`\\`\n\nZero-import form — required INSIDE compiled binaries, which can't \\`import 'zidane'\\` at runtime:\n\\`\\`\\`ts\nimport type { ExtensionContext } from 'zidane' // type-only: erased at runtime\nexport default {\n name: 'my-ext',\n setup(ctx: ExtensionContext) { /* ... */ },\n}\n\\`\\`\\`\n\n## ctx surface (call everything inside setup)\n\nIdentity / utilities:\n- ctx.name / ctx.version / ctx.dir / ctx.scope ('project' | 'user' | 'inline' | 'builtin')\n- ctx.logger — Logger namespaced \\`extension:<name>\\`\n- ctx.config — read THIS extension's config, default-first: \\`ctx.config.get(key)\\`\n- ctx.paths — cwd/projectRoot/dataDir/cacheDir/configDir/stateDir\n- ctx.storage.path(scope, ...segments) — extension-scoped storage path\n- ctx.session — JSON-only per-session state: get / set / delete\n\nRenderer-agnostic registration:\n- ctx.registerTool(name, toolDef)\n- ctx.registerHook(event, handler) — an AgentHooks event; fires on the PARENT agent (use child:* for subagents)\n- ctx.registerMcpServer(config)\n- ctx.registerSkill(skillConfig) — a defineSkill() shape\n- ctx.registerCompletionProvider(provider) — single-char '/' '@' '!' trigger\n- ctx.tui.notify(text, { tone? }) — one-shot transcript line (info/warn/error); buffered pre-mount\n- ctx.tui.setStatus(text | null, { loader?, timeout? }) — single dim status line (transcript-tail slot); loader glyph, auto-clear ms\n- ctx.tui.setBanner(lines | null, { timeout? }) — short block above the prompt (stacked with the todo indicator); auto-clear ms\n- ctx.submitPrompt(text, { delivery? }) — submit as the user: 'auto' (run-or-queue), 'steer', 'followUp'; false when detached\n- ctx.registerToolPolicy({ tools?, decide }) — gate tool calls: allow / { block } / { patch } (patches re-validated by the loop)\n- ctx.registerToolResultTransform(fn) — middleware over finished tool results (chained)\n- ctx.registerContextTransform(fn) — rewrite wire messages before each provider call\n- ctx.registerPromptTransform(fn) — transform or consume submitted prompts (after / commands, before skills)\n- ctx.runtime / ctx.sessions — late-bound host controls: model()/thinking()/setThinking(), sessions list/switch/create\n- ctx.appendNote(blocks) — durable display-only transcript note (command-output block vocabulary)\n- ctx.workspace.exec/readFile/writeFile/listFiles/fileSize — shell + files through the SESSION'S execution context (sandbox-correct; host child_process silently targets the wrong filesystem under docker/e2b). Detached ⇒ rejects; guard with \\`await ctx.workspace.available()\\`.\n- ctx.tui.registerToolRenderer({ tools?, render }) — custom view for tool-result rows; return null to fall through to the default. View-layer only: never persisted, error-bounded per row, TUI-scoped.\n- ctx.tui.registerRenderer(id, render) + emit/appendNote \\`custom: { renderer, data }\\` blocks — custom components in the transcript. \\`data\\` must be JSON-safe (it persists); the block's \\`text\\` is the MANDATORY markdown fallback (disabled extension, headless host, GUI, renderer throw). appendNote auto-prefixes bare ids; command emit needs \\`<ext>/<id>\\`.\n- Renderer components: import \\`react\\` / \\`@opentui/react\\` BARE (never as your own dependency) — the host maps them to its live instances at load time, so hooks work. Async data: render() stays sync; return a component that fetches with useState/useEffect.\n- Renderer async caching: \\`useSessionState(id, initial)\\` (per-row, session-persisted — rehydrates on reload) + \\`useRowVisible()\\` (latched viewport gate) from \\`zidane/tui\\` — fetch once, only when scrolled in.\n- ctx.registerCommand(command) — a '/<name>' workflow (handler or steps[]); step ctx: emit (local output),\n runAgent (interactive sub-run), generateData (silent zod/JSON-Schema JSON),\n session? (read-only turns/runs view), interaction? (confirm / askUser forms /\n presentPlan approval — both optional; guard for bare hosts)\n- ctx.registerTheme(theme)\n- ctx.registerAgent(profile) — extra agent profile in the picker\n- ctx.registerSubagent(def) — spawnable subagent (the spawn tool's subagent_type)\n- ctx.registerSubagentOverride(name, fn) — edit/delete an existing subagent\n- ctx.registerProvider(descriptor)\n- ctx.registerSystemPromptFragment(text)\n- ctx.registerContextBreakdownSection(fn) — render extension-owned rows in the context panel\n- ctx.registerContextPanelSection(section) — richer context panel section with rows/load\n- ctx.registerToolResultMetadata(registration) — project namespaced tool-result metadata into context\n- ctx.registerRunTransform(fn) — sync rewrite of agent.run options\n- ctx.registerBehaviorTransform(fn) — sync rewrite of resolved behavior\n- ctx.registerProviderMiddleware(fn) — wrap provider instances\n- ctx.registerToolCatalogTransform(fn) — shape/hide/rename tools before disclosure/execution\n- ctx.registerMcpConfigTransform(fn) — rewrite MCP server configs before connect\n- ctx.registerSkillPolicy(policy) — auto-activate skills by run context\n- ctx.registerConfigCategory(category)\n- ctx.registerConfigItem([categoryId,] item) — typed setting; supports scope/onChange metadata\n\nTUI-only (ctx.tui.* — silently ignored on non-TUI hosts):\n- ctx.tui.registerKeybinding(binding)\n- ctx.tui.registerFooterHint(hint)\n- ctx.tui.registerModal(modal) — keep UI lazy: \\`node: () => import('./panel')\\`\n\nGUI-only:\n- ctx.gui.registerPanel(panel) — collected, not rendered yet\n\nCompletion providers may also implement submit hooks:\n- resolveReferences(refs, ctx, signal)\n- onSubmit({ text, cursor, references }, signal)\nBoth TUI and GUI call these before dispatching a submitted prompt.\n\n## ToolDef shape\n\\`\\`\\`ts\nctx.registerTool('my_tool', {\n spec: { name: 'my_tool', description: '...', inputSchema: { type: 'object', properties: {} } },\n isConcurrencySafe: true, // default false (barrier/write); true = read-only, fans out in parallel\n async execute(args) { return 'result' },\n})\n\\`\\`\\`\n\n## Invariants\n- The folder name MUST equal \\`name\\`, or the loader rejects the extension.\n- Optional metadata lives in \\`extension.json\\` or \\`package.json#zidane\\`; unsupported \\`apiVersion\\` is rejected.\n- Export as \\`default\\` (or a named \\`extension\\` export).\n- Register ONLY inside \\`setup\\` — calls after it resolves are dropped (logger.warn).\n- A throw in \\`setup\\` is caught and shown on the extension's Settings row; the rest of boot continues.\n- Discovery order, first per name wins: project \\`.agents\\` → project \\`.zidane\\` → \\`~/.agents\\` → \\`~/.zidane\\`.\n- \\`/reload\\` is a TRUE hot-reload: re-discover + re-run setup, and each extension's module graph is re-imported fresh — code edits (deep imports included) apply live. Module-level state resets; clean up external resources in \\`teardown\\`.\n\n## Distribution\n- Users install via Settings → Extensions → \\`ctrl+g\\` (source input + scope picker) — GitHub (\\`owner/repo[#ref][:sub/dir]\\`, tree URLs) or npm (\\`pkg[@version]\\`, exact versions / dist-tags).\n- Update / remove: Settings → Extensions, \\`ctrl+u\\` / \\`ctrl+x\\` on the focused row (update replays the recorded \\`.install.json\\` provenance in place, skipping when the recorded commit/version still matches the remote).\n- Shape your repo/package as a root \\`index.ts\\` (single extension) or \\`extensions/<name>/index.*\\` dirs (collection).\n- Reference packages to copy: \\`extensions/git-flow\\` in the zidane repo (zero-dep \\`/commit\\` command) and \\`extensions/headroom\\` (hooks + tool + config).\n- Declare runtime deps in \\`package.json#dependencies\\` — installed with bun/npm/… WITHOUT lifecycle scripts; don't rely on postinstall.\n- The installed folder is named from your exported \\`definition.name\\` — pick it to be folder-safe ([A-Za-z0-9_.-]).`\n\n// ---------------------------------------------------------------------------\n// Edit-session presets — the first line of each inline skill's instructions.\n// Kept next to each other so the two flows stay phrased consistently.\n// ---------------------------------------------------------------------------\n\n/** Preset preamble for \"edit / create extension\" sessions. */\nconst EXTENSION_EDIT_PRESET\n = 'You are authoring / editing a zidane extension. Authoritative API reference:'\n\n/** Preset preamble for \"edit skill\" sessions — same spirit, skill-flavored. */\nfunction skillEditPreset(marker: SkillEditMarker): string {\n return `We are going to work on ${marker.name}, in ${marker.dir} directory, `\n + 'next messages will be requests at modifying and improving the skill or '\n + 'understanding how it works better.'\n}\n\n/**\n * The inline `zidane-extension` skill. Carries the authoring reference IN its\n * body ({@link EXTENSION_AUTHORING_CHEATSHEET}) so it works in every host —\n * incl. compiled binaries where the on-disk docs aren't resolvable. The shipped\n * `EXTENSIONS.md` (prose long-form) is appended as a \"see also\" only when found\n * on disk. `source: 'inline'` (the `defineSkill` default) survives the\n * project-trust gate.\n */\nexport function buildZidaneExtensionSkill(opts: { cwd?: string } = {}): SkillConfig {\n const docsDir = resolveExtensionDocsDir(opts.cwd)\n const seeAlso = docsDir\n ? `\\n\\n---\\nFull prose reference on disk: ${join(docsDir, 'EXTENSIONS.md')}\\n`\n + 'Sibling docs cover related surfaces (ARCHITECTURE.md, CHAT.md, TUI.md, SKILL.md, HEADLESS.md, …).'\n : '\\n\\n---\\nFuller prose reference, when present: node_modules/zidane/docs/EXTENSIONS.md '\n + '(often absent in compiled binaries — the API above is authoritative).'\n return defineSkill({\n name: ZIDANE_EXTENSION_SKILL_NAME,\n description:\n 'Authoring or editing a zidane extension (a module calling defineExtension, under '\n + '.zidane/extensions/** or .agents/extensions/**). Carries the extension authoring reference.',\n instructions: `${EXTENSION_EDIT_PRESET}\\n\\n${EXTENSION_AUTHORING_CHEATSHEET}${seeAlso}`,\n })\n}\n\n/**\n * Inject the `zidane-extension` skill into an already-merged {@link SkillsConfig}.\n *\n * Call this on the FINAL merged skills object (after folding extension / preset\n * skills): `composePresets` is last-wins on `catalog`, so injecting earlier\n * would be silently dropped whenever an extension also contributes skills.\n *\n * Adds the skill to the catalog and, when an `enabled` allowlist is in force,\n * force-includes the name so the allowlist filter can't hide it (mirrors how\n * extension-contributed skill names are force-included). When skills are fully\n * disabled (`enabled === false`), the edit session still gets just this skill.\n */\nexport function withZidaneExtensionSkill(skills: SkillsConfig, opts: { cwd?: string } = {}): SkillsConfig {\n return injectInlineSkill(skills, buildZidaneExtensionSkill(opts))\n}\n\n/**\n * Compact skill-authoring reference embedded into the skill-edit skill body.\n * Inline for the same reason as {@link EXTENSION_AUTHORING_CHEATSHEET}: works\n * in compiled binaries where on-disk docs don't resolve.\n */\nconst SKILL_AUTHORING_CHEATSHEET = `## SKILL.md anatomy\n\nA skill is a directory holding a \\`SKILL.md\\` (YAML frontmatter + markdown body)\nper the Agent Skills standard (agentskills.io/specification):\n\n\\`\\`\\`md\n---\nname: my-skill # 1-64 chars, lowercase alphanumeric + hyphens; SHOULD match the directory name\ndescription: what it does AND when to use it (drives activation — 1-1024 chars)\nlicense: MIT # optional\ncompatibility: … # optional environment requirements\nallowed-tools: # optional — restricts the agent to these tools while active\n - read_file\nmetadata: # optional flat string map; zidane-specific keys use the \\`zidane.\\` prefix\n some-key: value\n---\nThe body is the skill's instructions — injected into context when the skill\nis activated. Keep it token-lean: move depth into references/.\n\\`\\`\\`\n\n## Bundled resources (all optional, discovered automatically)\n- \\`references/\\` — docs loaded on demand via \\`skills_read\\`\n- \\`scripts/\\` — executables run via \\`skills_run_script\\` (honor shebangs)\n- \\`assets/\\` — templates / static files\n\n## Guidance\n- The description is the activation trigger — write it for the ROUTER (\"use when …\"), the body for the ACTIVATED agent.\n- Prefer small bodies + lazy references over one giant SKILL.md.\n- Shell interpolation (exclamation mark + backtick-quoted command) in the body runs at activation — use sparingly; hosts may gate or disable it.\n- Validate names: mismatched frontmatter name vs directory loads with a warning diagnostic.`\n\n/**\n * The inline skill auto-activated in \"edit skill\" sessions. Body = the\n * session preset (which skill, which directory, what the next messages will\n * be about) + a compact SKILL.md authoring reference — the skill-editing\n * counterpart of {@link buildZidaneExtensionSkill}.\n */\nexport function buildZidaneSkillEditSkill(marker: SkillEditMarker, opts: { cwd?: string } = {}): SkillConfig {\n const docsDir = resolveExtensionDocsDir(opts.cwd)\n const seeAlso = docsDir && existsSync(join(docsDir, 'SKILL.md'))\n ? `\\n\\n---\\nFull prose reference on disk: ${join(docsDir, 'SKILL.md')}`\n : ''\n return defineSkill({\n name: ZIDANE_SKILL_EDIT_SKILL_NAME,\n description:\n `Editing or improving the \"${marker.name}\" agent skill (its SKILL.md, references, `\n + 'scripts, and assets). Carries the skill-authoring reference for this edit session.',\n instructions: `${skillEditPreset(marker)}\\n\\n${SKILL_AUTHORING_CHEATSHEET}${seeAlso}`,\n })\n}\n\n/**\n * Inject the skill-edit preset skill into an already-merged {@link SkillsConfig}.\n * Same contract as {@link withZidaneExtensionSkill} — call on the FINAL merged\n * skills object.\n */\nexport function withZidaneSkillEditSkill(\n skills: SkillsConfig,\n marker: SkillEditMarker,\n opts: { cwd?: string } = {},\n): SkillsConfig {\n return injectInlineSkill(skills, buildZidaneSkillEditSkill(marker, opts))\n}\n\n/**\n * Shared injector: add `skill` to the catalog and, when an `enabled`\n * allowlist is in force, force-include its name so the filter can't hide it\n * (mirrors how extension-contributed skill names are force-included). When\n * skills are fully disabled (`enabled === false`), the edit session still\n * gets just this skill.\n */\nfunction injectInlineSkill(skills: SkillsConfig, skill: SkillConfig): SkillsConfig {\n const catalog = [...(skills.catalog ?? []), skill]\n const enabled = skills.enabled\n const nextEnabled: SkillsConfig['enabled']\n = enabled === undefined\n ? undefined\n : Array.isArray(enabled)\n ? (enabled.includes(skill.name) ? enabled : [...enabled, skill.name])\n : [skill.name]\n return {\n ...skills,\n catalog,\n ...(nextEnabled !== undefined ? { enabled: nextEnabled } : {}),\n }\n}\n","/**\n * Local loopback HTTP callback for OAuth 2.0 authorization code flows.\n *\n * Stands up a one-shot server on `127.0.0.1:<random>` that captures the\n * `?code=...` redirect from a browser-driven OAuth flow and resolves a\n * promise with the code. Used as the `redirectUrl` half of the MCP SDK's\n * `OAuthClientProvider` (the persistence half lives separately).\n *\n * Design:\n * - Loopback-only (`127.0.0.1`) — the OAuth spec treats `http://127.0.0.1:<port>`\n * as a public-client redirect URI per RFC 8252 §7.3. Browsers do NOT block it,\n * and Anthropic / OpenAI / Linear / GitHub all accept it.\n * - Random port (`port = 0`) — the OS picks an unused one. We read the actual\n * port back from `server.address()` after `listen()`.\n * - Single-shot — the first GET to `path` with a `code` (or `error`) wins;\n * subsequent requests get 404. The server keeps listening (in case the user\n * hits \"back\" and re-authorizes), so callers must `close()` once they have\n * the code or have given up.\n * - Abort-aware — wiring an external `AbortSignal` rejects the promise and\n * closes the server immediately. Required for the TUI's \"esc cancels login\"\n * UX.\n * - No HTML framework — a single inline `<html>` string keeps this isolated\n * from any UI dependency.\n */\n\nimport type { AddressInfo } from 'node:net'\nimport { createServer } from 'node:http'\n\n/**\n * Result of a successful callback. `state` is forwarded verbatim from the\n * query string — callers verify it against their pre-flight value to defend\n * against CSRF (the MCP SDK does this internally when it controls `state`).\n */\nexport interface OAuthCallbackResult {\n code: string\n state?: string\n}\n\nexport interface OAuthCallbackHandle {\n /**\n * Full URI to register with the authorization server, e.g.\n * `http://127.0.0.1:51823/callback`. Stable for the lifetime of the\n * handle.\n */\n redirectUri: string\n /**\n * Resolves with `{ code, state }` on a successful callback. Rejects with:\n * - The OAuth-spec `error` field (`access_denied`, `server_error`, ...)\n * when the authorization server redirects with `?error=...`.\n * - `'OAuth callback aborted'` when the external `AbortSignal` fires.\n * - `'OAuth callback server closed'` when `close()` is called before any\n * callback arrives.\n *\n * Single-shot — only the first matching request resolves the promise.\n */\n promise: Promise<OAuthCallbackResult>\n /**\n * Idempotent shutdown. Safe to call from a `finally` block whether the\n * flow succeeded, failed, or was aborted. Resolves once the server stops\n * accepting connections.\n */\n close: () => Promise<void>\n}\n\nexport interface OAuthCallbackOptions {\n /** Cancels the flow — rejects `promise` and closes the server. */\n signal?: AbortSignal\n /**\n * Renders the post-redirect browser page — see\n * {@link McpOAuthCallbackPageRenderer}. Default: a minimal unbranded\n * page; hosts inject their themed renderer.\n */\n renderPage?: McpOAuthCallbackPageRenderer\n /** Display name handed to the page renderer. Default: `'MCP server'`. */\n provider?: string\n /**\n * Path component the authorization server should redirect to. Defaults\n * to `/callback`. Useful when matching a pre-registered URI that uses a\n * different path.\n */\n path?: string\n /**\n * Override the loopback host. Defaults to `127.0.0.1`. Don't bind to\n * `0.0.0.0` here — the OAuth code is a one-time secret and the server\n * would otherwise accept it from any host on the LAN.\n */\n host?: string\n /**\n * Override the port. Defaults to `0` (OS-assigned). Pin to a fixed port\n * only when the authorization server requires a pre-registered redirect\n * URI; the random-port path is preferred so concurrent flows don't clash.\n */\n port?: number\n}\n\nconst DEFAULT_PATH = '/callback'\nconst DEFAULT_HOST = '127.0.0.1'\n\n/**\n * Page the browser lands on after the redirect. INJECTABLE: hosts pass a\n * renderer so the MCP login ends on the same branded page as their\n * provider OAuth flows (zidane's TUI injects `renderDefaultCallbackPage`\n * from `chat/oauth-page` — `src/mcp` stays layering-clean by not\n * importing the chat module itself). The shape mirrors chat's\n * `OAuthCallbackPage` structurally, so that renderer plugs in verbatim.\n */\nexport interface McpOAuthCallbackPage {\n kind: 'success' | 'error'\n /** Headline message — one sentence, safe for display. */\n message: string\n /** Optional secondary details (e.g. the server's `error_description`). */\n details?: string\n /** Display name for the thing being logged into (the MCP server name). */\n provider: string\n}\n\nexport type McpOAuthCallbackPageRenderer = (page: McpOAuthCallbackPage) => string\n\n/**\n * Fallback renderer for hosts that don't inject one (bare SDK use) —\n * deliberately minimal, no framework, no branding. All interpolations are\n * escaped: a malicious authorization server could craft an\n * `error_description` containing markup, and even a one-shot loopback\n * page must not XSS.\n */\nconst renderMinimalPage: McpOAuthCallbackPageRenderer = (page) => {\n const escaped = (value: string) => value\n .replaceAll('&', '&amp;')\n .replaceAll('<', '&lt;')\n .replaceAll('>', '&gt;')\n const ok = page.kind === 'success'\n return `<!doctype html>\n<html><head><meta charset=\"utf-8\"><title>${ok ? 'Logged in' : 'Login failed'}</title>\n<style>body{font:14px/1.5 -apple-system,system-ui,sans-serif;margin:6rem auto;max-width:28rem;text-align:center;color:#1d1d1f}.ok{color:#1f8a4c;font-weight:600}.err{color:#c43c2c;font-weight:600}</style>\n</head><body>\n<p class=\"${ok ? 'ok' : 'err'}\">${ok ? 'Logged in.' : 'Login failed.'}</p>\n${page.details ? `<p>${escaped(page.details)}</p>` : ''}\n<p>${escaped(page.message)}</p>\n</body></html>`\n}\n\nconst CLOSE_TAB_MESSAGE = 'You can close this tab and return to the terminal.'\n\n/**\n * Start a one-shot OAuth callback server. The returned handle's `redirectUri`\n * should be passed to the authorization server as the `redirect_uri` query\n * parameter; `promise` resolves once the user finishes the browser flow.\n *\n * Always `await handle.close()` in a `finally` block — even on success, the\n * server stays open until told to shut down (so it can serve the\n * \"you can close this tab\" page).\n */\nexport async function startOAuthCallback(\n opts: OAuthCallbackOptions = {},\n): Promise<OAuthCallbackHandle> {\n const path = opts.path ?? DEFAULT_PATH\n const host = opts.host ?? DEFAULT_HOST\n const port = opts.port ?? 0\n const renderPage = opts.renderPage ?? renderMinimalPage\n const provider = opts.provider ?? 'MCP server'\n\n if (!path.startsWith('/'))\n throw new Error(`OAuth callback path must start with \"/\" (got: ${JSON.stringify(path)})`)\n\n if (opts.signal?.aborted)\n throw new Error('OAuth callback aborted')\n\n let resolveResult: (value: OAuthCallbackResult) => void\n let rejectResult: (error: Error) => void\n const promise = new Promise<OAuthCallbackResult>((resolve, reject) => {\n resolveResult = resolve\n rejectResult = reject\n })\n // Attach a default no-op catch so a rejection without a consumer doesn't\n // raise an unhandled-rejection warning. Callers that DO `await promise`\n // still receive the rejection — Promises fan out to all listeners.\n promise.catch(() => {})\n\n let settled = false\n const resolveOnce = (value: OAuthCallbackResult) => {\n if (settled)\n return\n settled = true\n resolveResult(value)\n }\n const rejectOnce = (error: Error) => {\n if (settled)\n return\n settled = true\n rejectResult(error)\n }\n\n const server = createServer((req, res) => {\n const url = new URL(req.url ?? '/', `http://${host}`)\n if (url.pathname !== path) {\n res.writeHead(404, { 'content-type': 'text/plain' })\n res.end('Not Found')\n return\n }\n\n // `no-store` prevents the browser from replaying the page (with the\n // now-spent code in its URL bar) when the user hits back, refreshes,\n // or restores the tab from history. The code is a one-time secret.\n const htmlHeaders = {\n 'content-type': 'text/html; charset=utf-8',\n 'cache-control': 'no-store',\n }\n\n const error = url.searchParams.get('error')\n if (error) {\n const desc = url.searchParams.get('error_description') ?? error\n res.writeHead(400, htmlHeaders)\n res.end(renderPage({ kind: 'error', message: CLOSE_TAB_MESSAGE, details: desc, provider }))\n rejectOnce(new Error(`OAuth authorization failed: ${desc}`))\n return\n }\n\n const code = url.searchParams.get('code')\n if (!code) {\n // No code, no error — likely a stray probe. Serve a minimal page,\n // don't resolve. The browser will eventually arrive with the real\n // redirect, or the caller will time out via signal.\n res.writeHead(400, { 'content-type': 'text/plain' })\n res.end('Missing \"code\" query parameter.')\n return\n }\n\n const state = url.searchParams.get('state') ?? undefined\n res.writeHead(200, htmlHeaders)\n res.end(renderPage({ kind: 'success', message: CLOSE_TAB_MESSAGE, provider }))\n resolveOnce({ code, state })\n })\n\n // Surface socket errors that arrive before the request handler — e.g. EADDRINUSE\n // on a pinned port, or an early TLS-style probe that the parser rejects.\n server.on('error', (err) => {\n rejectOnce(err instanceof Error ? err : new Error(String(err)))\n })\n\n await new Promise<void>((resolve, reject) => {\n server.once('error', reject)\n server.listen(port, host, () => {\n server.off('error', reject)\n resolve()\n })\n })\n\n const addr = server.address() as AddressInfo | null\n if (!addr || typeof addr === 'string') {\n server.close()\n throw new Error('OAuth callback server did not bind to a TCP port')\n }\n\n // `closeServer` and `onAbort` reference each other — pre-declare both so\n // either ordering reads cleanly and the lint doesn't trip on the cycle.\n let closing: Promise<void> | undefined\n let closeServer: () => Promise<void>\n const onAbort = () => {\n rejectOnce(new Error('OAuth callback aborted'))\n void closeServer()\n }\n closeServer = (): Promise<void> => {\n if (closing)\n return closing\n closing = new Promise<void>((resolve) => {\n // `close()` on a Node http server waits for in-flight requests to drain.\n // The flow has already settled (or is being aborted), so we also call\n // `closeAllConnections()` to drop keep-alive sockets immediately — a\n // browser keeps the connection open after the success page, which would\n // otherwise hang the close for the keep-alive timeout (~5s).\n const anyServer = server as { closeAllConnections?: () => void }\n anyServer.closeAllConnections?.()\n server.close(() => {\n opts.signal?.removeEventListener('abort', onAbort)\n // If neither a callback nor an abort happened before close(), reject\n // pending awaits so callers don't hang. Tag the rejection as\n // 'aborted' when the signal already fired — Bun fires the abort\n // listener via a microtask, so server.close() may resolve first\n // even though semantically the abort came first.\n if (opts.signal?.aborted)\n rejectOnce(new Error('OAuth callback aborted'))\n else\n rejectOnce(new Error('OAuth callback server closed'))\n resolve()\n })\n })\n return closing\n }\n opts.signal?.addEventListener('abort', onAbort, { once: true })\n\n return {\n redirectUri: `http://${host}:${addr.port}${path}`,\n promise,\n close: closeServer,\n }\n}\n","/**\n * Interactive OAuth login orchestrator for one MCP server.\n *\n * Composes the three pieces shipped separately:\n * - `startOAuthCallback` (`./oauth-callback`) — local loopback HTTP\n * listener that catches the `?code=...` redirect.\n * - `McpOAuthProvider` (`./oauth-provider`) — SDK adapter that persists\n * tokens / client info via an injected `McpCredentialStore`.\n * - The MCP SDK's transport `authProvider` + `finishAuth` plumbing —\n * `client.connect()` triggers the SDK to call `redirectToAuthorization`\n * (which opens the browser); we wait for the loopback callback to\n * deliver the code, hand it to `transport.finishAuth`, and reconnect.\n *\n * Returns once tokens are persisted. Caller decides whether to immediately\n * reconnect the server (re-run `connectMcpServers` / `agent.warmup()`) or\n * defer to the next session activation. The returned `tools` array is\n * provided as a convenience — callers that want the connection live in\n * this call rather than rebuilding can wire them in directly.\n */\n\nimport type { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js'\nimport type { Hookable } from 'hookable'\nimport type { AgentHooks } from '../agent'\nimport type { McpServerConfig } from '../types'\nimport type { McpOAuthCallbackPageRenderer, OAuthCallbackHandle } from './oauth-callback'\nimport type { McpCredentialStore } from './oauth-provider'\nimport { expandMcpConfigEnv } from './index'\nimport { startOAuthCallback } from './oauth-callback'\nimport { isOAuthTokenRejectionError, McpOAuthProvider } from './oauth-provider'\nimport { sseToJsonFetchIfNeeded } from './sse-to-json-fetch'\nimport { createTolerantClient } from './tolerant-client'\n\nexport interface LoginMcpServerOptions {\n /** Persistence — same store the bootstrap path reads from. */\n store: McpCredentialStore\n /**\n * Invoked with the authorization URL once it's ready. Hosts typically\n * (a) emit `mcp:auth:url` for the TUI, and (b) call `tryOpenBrowser`.\n * The URL is identical to the one passed to the `mcp:auth:url` hook\n * fired automatically — this callback is a synchronous hook for callers\n * that don't want to wire the agent hook machinery.\n */\n onAuthorizationUrl?: (url: URL) => void | Promise<void>\n /** Cancels the flow (esc / close modal / SIGINT). */\n signal?: AbortSignal\n /**\n * Renders the post-redirect browser page — hosts pass their themed\n * renderer (zidane's TUI: `renderDefaultCallbackPage`) so MCP logins\n * end on the same screen as provider OAuth logins. The server name\n * rides as `page.provider`.\n */\n renderCallbackPage?: McpOAuthCallbackPageRenderer\n /** Agent hooks. The flow emits `mcp:auth:url`/`success`/`error` when wired. */\n hooks?: Hookable<AgentHooks>\n /** Override `client_name` shown on consent screens. Default: 'zidane'. */\n clientName?: string\n /** Override the requested OAuth scope. */\n scope?: string\n /**\n * Override the loopback callback path. Default: `/callback`. Useful only\n * for servers that pinned a different path during registration.\n */\n callbackPath?: string\n /**\n * Maximum time to wait for the user to complete the browser flow, in ms.\n * The user can also cancel via `signal`. Default: 5 minutes.\n */\n timeoutMs?: number\n /**\n * Maximum time for the PRE-authorization phase — the `client.connect()`\n * that triggers discovery, dynamic registration, and the authorization\n * redirect. Default: 30s.\n *\n * Separate from {@link timeoutMs} because it covers machine-speed HTTP,\n * not a human in a browser. It exists because the MCP SDK's `connect()`\n * accepts no `AbortSignal`: an unreachable or slow-loris server would\n * otherwise hang here forever, un-cancellable, with the host UI parked on\n * a spinner that no keystroke can dismiss.\n */\n connectTimeoutMs?: number\n}\n\nexport interface LoginMcpServerResult {\n /** Stored OAuth tokens after a successful exchange. */\n tokens: NonNullable<ReturnType<McpOAuthProvider['tokens']>>\n /**\n * Upstream tool descriptors discovered after re-connecting with the new\n * tokens. Already filtered by the server's `enabledTools` / `disabledTools`\n * is NOT applied here — that's a bootstrap concern. Hosts that want filtering\n * should pass the result through `connectMcpServers` rebuild on the next\n * session activation rather than reusing this list verbatim.\n */\n tools: Array<{ name: string, description?: string | null, inputSchema?: unknown }>\n}\n\nconst DEFAULT_LOGIN_TIMEOUT_MS = 5 * 60_000\nconst DEFAULT_CONNECT_TIMEOUT_MS = 30_000\n\n/**\n * Run the full interactive OAuth flow for `config`. Only supports `sse` and\n * `streamable-http` transports — `stdio` MCP servers don't speak OAuth.\n *\n * Throws on:\n * - Wrong transport.\n * - Abort signal.\n * - Browser-side error (user denied, server rejected, etc.).\n * - Code exchange failure.\n * - Post-exchange connect failure.\n *\n * Always closes the loopback callback server before returning, success or\n * failure.\n */\nexport async function loginMcpServer(\n config: McpServerConfig,\n options: LoginMcpServerOptions,\n): Promise<LoginMcpServerResult> {\n if (config.transport !== 'sse' && config.transport !== 'streamable-http')\n throw new Error(`MCP OAuth: cannot login for transport \"${config.transport}\" — only sse / streamable-http are supported`)\n if (!config.url)\n throw new Error(`MCP OAuth: server \"${config.name}\" is missing a url`)\n\n const hooks = options.hooks\n\n // Flipped in `finally`. A lost `raceAbort` ABANDONS `client.connect()`\n // rather than cancelling it (the SDK takes no signal), so the SDK can\n // reach the authorization step long after this function already threw —\n // timeout or abort. The `onAuthorizationUrl` closure below must go dead\n // at that point: a late `mcp:auth:url` would resurrect the host's auth UI\n // (and pop a browser) for a flow whose loopback callback server is\n // already closed and which nothing is driving.\n let settled = false\n\n let callback: OAuthCallbackHandle | undefined\n try {\n callback = await startOAuthCallback({\n signal: options.signal,\n path: options.callbackPath,\n ...(options.renderCallbackPage ? { renderPage: options.renderCallbackPage } : {}),\n provider: config.name,\n })\n\n const handle = callback\n const provider = new McpOAuthProvider({\n name: config.name,\n store: options.store,\n redirectUri: handle.redirectUri,\n clientName: options.clientName,\n scope: options.scope,\n onAuthorizationUrl: async (url) => {\n // Late callback from an abandoned connect — see `settled` above.\n // The signal check additionally covers an abort that races the\n // authorization step while the flow is still nominally live.\n if (settled || options.signal?.aborted)\n return\n await hooks?.callHook('mcp:auth:url', { name: config.name, url: url.toString() })\n await options.onAuthorizationUrl?.(url)\n },\n })\n\n let transport = await createInteractiveTransport(config, provider)\n const client = await createTolerantClient({ name: 'zidane', version: '1.0.0' })\n\n // First connect → SDK has no tokens, so it calls\n // `provider.redirectToAuthorization(url)` (which routes to our\n // `onAuthorizationUrl`), then throws `UnauthorizedError`. This is the\n // expected path — we catch and wait on the loopback for the code.\n //\n // With STALE tokens stored, the SDK first attempts a refresh instead —\n // and when the refresh token itself is dead, `auth()` rethrows the\n // token-endpoint rejection (`InvalidGrantError`) WITHOUT opening the\n // browser. Wipe the dead tokens and retry once: the tokenless retry\n // routes straight to the authorization redirect. Without this, a\n // revoked refresh token made the panel re-login itself fail — the one\n // flow that's supposed to recover from dead credentials.\n let needsAuth = false\n const connectTimeoutMs = options.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS\n for (let attempt = 0; ; attempt++) {\n try {\n // Bounded + cancellable: `connect()` takes no signal, so we race it\n // rather than await it bare. The losing connect keeps running in the\n // background until its own sockets give up — acceptable, because the\n // alternative is a host UI stuck on \"starting login flow…\" with no\n // way out.\n await raceAbort(\n client.connect(transport),\n options.signal,\n connectTimeoutMs,\n `MCP OAuth: \"${config.name}\" did not respond within ${connectTimeoutMs}ms — the server may be unreachable`,\n )\n break\n }\n catch (err) {\n if (isUnauthorizedError(err)) {\n needsAuth = true\n break\n }\n if (attempt === 0 && isOAuthTokenRejectionError(err)) {\n await provider.invalidateCredentials('tokens')\n await transport.close().catch(() => {})\n transport = await createInteractiveTransport(config, provider)\n continue\n }\n await client.close().catch(() => {})\n throw err\n }\n }\n\n if (needsAuth) {\n const { code } = await raceLoginCallback(handle, options.timeoutMs ?? DEFAULT_LOGIN_TIMEOUT_MS, options.signal)\n // SDK exchanges code → tokens; provider.saveTokens persists them.\n await (transport as { finishAuth: (code: string) => Promise<void> }).finishAuth(code)\n // Reconnect with a FRESH transport. The previous one is already in\n // the \"started\" state from the failed connect — reusing it throws\n // \"StreamableHTTPClientTransport already started!\" inside\n // `transport.start()`. The SDK's canonical example\n // (`simpleOAuthClient.js`) uses the same pattern: recurse with a\n // new transport, reuse the client. Close the stale transport\n // first so its abort controllers + sockets don't leak.\n await transport.close().catch(() => {})\n const freshTransport = await createInteractiveTransport(config, provider)\n // Bounded for the same reason as the first connect: tokens are already\n // persisted at this point, so a server that stops responding here must\n // surface as an error the user can retry from, not a permanent spinner.\n await raceAbort(\n client.connect(freshTransport),\n options.signal,\n connectTimeoutMs,\n `MCP OAuth: \"${config.name}\" did not respond after the token exchange (within ${connectTimeoutMs}ms)`,\n )\n }\n\n const { tools } = await raceAbort(\n client.listTools(),\n options.signal,\n connectTimeoutMs,\n `MCP OAuth: \"${config.name}\" did not return its tool list within ${connectTimeoutMs}ms`,\n )\n await client.close()\n\n const tokens = provider.tokens()\n if (!tokens)\n throw new Error(`MCP OAuth: login for \"${config.name}\" returned no tokens (server may have rejected the exchange silently)`)\n\n await hooks?.callHook('mcp:auth:success', { name: config.name })\n return { tokens, tools }\n }\n catch (err) {\n const error = err instanceof Error ? err : new Error(String(err))\n await hooks?.callHook('mcp:auth:error', { name: config.name, error })\n throw error\n }\n finally {\n settled = true\n await callback?.close()\n }\n}\n\n/**\n * Build an `sse` / `streamable-http` transport pre-wired with `authProvider`.\n * Mirrors the bootstrap-side `createTransport` shape but inlined here so the\n * login flow doesn't depend on the bootstrap module's private export.\n */\nasync function createInteractiveTransport(rawConfig: McpServerConfig, authProvider: OAuthClientProvider) {\n // Same `${VAR}` resolution as the bootstrap-side `createTransport` —\n // url/headers may carry env placeholders that must never hit the wire raw.\n const config = expandMcpConfigEnv(rawConfig)\n if (config.transport === 'sse') {\n const { SSEClientTransport } = await import('@modelcontextprotocol/sdk/client/sse.js')\n return new SSEClientTransport(new URL(config.url!), {\n requestInit: config.headers ? { headers: config.headers } : undefined,\n authProvider,\n })\n }\n // streamable-http\n const { StreamableHTTPClientTransport } = await import('@modelcontextprotocol/sdk/client/streamableHttp.js')\n return new StreamableHTTPClientTransport(new URL(config.url!), {\n requestInit: config.headers ? { headers: config.headers } : undefined,\n fetch: sseToJsonFetchIfNeeded(),\n authProvider,\n })\n}\n\nfunction isUnauthorizedError(err: unknown): boolean {\n if (!err || typeof err !== 'object')\n return false\n const e = err as { name?: string, message?: string, constructor?: { name?: string } }\n if (e.name === 'UnauthorizedError' || e.constructor?.name === 'UnauthorizedError')\n return true\n return typeof e.message === 'string' && e.message.toLowerCase().startsWith('unauthorized')\n}\n\n/**\n * Race `promise` against the external abort signal and a hard timeout.\n *\n * Used for phases we can't cancel from the inside (the SDK's `connect()`\n * accepts no signal). The underlying work isn't stopped — it's abandoned —\n * so callers must treat a rejection as \"this attempt is over\" and close\n * whatever they own. Rejects with `'OAuth login aborted'` on abort so the\n * host can tell a user cancel apart from a genuine failure.\n */\nasync function raceAbort<T>(\n promise: Promise<T>,\n signal: AbortSignal | undefined,\n timeoutMs: number,\n timeoutMessage: string,\n): Promise<T> {\n if (signal?.aborted)\n throw new Error('OAuth login aborted')\n // Nothing consumes the rejection when the race is lost — pre-attach a\n // no-op catch so an abandoned connect can't raise an unhandled rejection.\n promise.catch(() => {})\n let timer: ReturnType<typeof setTimeout> | undefined\n let onAbort: (() => void) | undefined\n try {\n return await new Promise<T>((resolve, reject) => {\n timer = setTimeout(() => reject(new Error(timeoutMessage)), timeoutMs)\n if (signal) {\n onAbort = () => reject(new Error('OAuth login aborted'))\n signal.addEventListener('abort', onAbort, { once: true })\n }\n promise.then(resolve, reject)\n })\n }\n finally {\n if (timer)\n clearTimeout(timer)\n if (signal && onAbort)\n signal.removeEventListener('abort', onAbort)\n }\n}\n\n/**\n * Wait on the callback handle, with a hard timeout AND honor the external\n * abort signal. Unlike `callback.promise` alone, this provides a deterministic\n * failure mode for \"user opened the browser then walked away\" — the modal\n * doesn't hang forever waiting for a redirect that may never come.\n */\nasync function raceLoginCallback(\n handle: OAuthCallbackHandle,\n timeoutMs: number,\n signal: AbortSignal | undefined,\n): Promise<{ code: string, state?: string }> {\n if (signal?.aborted)\n throw new Error('OAuth login aborted')\n let timer: ReturnType<typeof setTimeout> | undefined\n let onAbort: (() => void) | undefined\n try {\n return await new Promise<{ code: string, state?: string }>((resolve, reject) => {\n timer = setTimeout(\n () => reject(new Error(`OAuth login timed out after ${timeoutMs}ms`)),\n timeoutMs,\n )\n if (signal) {\n onAbort = () => reject(new Error('OAuth login aborted'))\n signal.addEventListener('abort', onAbort, { once: true })\n }\n handle.promise.then(resolve, reject)\n })\n }\n finally {\n if (timer)\n clearTimeout(timer)\n if (signal && onAbort)\n signal.removeEventListener('abort', onAbort)\n }\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"session-CyRLSsVa.js","names":[],"sources":["../src/session/file-map.ts","../src/session/memory.ts","../src/session/remote.ts","../src/session/index.ts"],"sourcesContent":["/**\n * File-map session store.\n *\n * Wraps a narrow 3-method adapter (`get` / `save` / `delete`) that exchanges a flat\n * map of filename → string content. Useful for embedding zidane sessions inside\n * host-provided session backends that only speak in file maps (not zidane's native\n * `SessionStore` shape).\n *\n * Serialization format:\n * - `turns.jsonl` — one `SessionTurn` per line.\n * - `meta.json` — session metadata (id, agentId, status, runs, metadata, timestamps).\n *\n * JSONL for turns keeps history inspectable with tools like `jq` and resilient to\n * partial corruption — parse up to the first bad line and you still have a valid\n * prefix. Metadata lives in its own file so large turn logs don't bloat the\n * metadata path.\n *\n * Scope: each `createFileMapStore` handles a **single session** — the adapter's\n * file map holds at most one zidane session at a time. This matches how host SDKs\n * scope their session stores per conversation.\n *\n * Divergences from the built-in memory / sqlite stores:\n * - `appendTurns` / `updateStatus` / `updateRun` auto-create a minimal `SessionData`\n * record on first write, instead of silently no-oping when the session hasn't been\n * explicitly `save()`-ed. This matches the host-SDK integration path where\n * `createSession(...)` → `agent.run(...)` directly without an explicit `save()` call.\n * - `updateRun` inserts the run if not found in the cached record (rather than\n * silently dropping). Run records therefore always reach the adapter.\n */\n\nimport type { SessionData, SessionRun, SessionStore } from '.'\nimport type { SessionTurn } from '../types'\n\n/**\n * Host-provided file-map adapter. Three methods exchanging `Record<string, string>`\n * payloads — the whole persistence surface the wrapper needs.\n */\nexport interface FileMapAdapter {\n /** Load the current file map. Returns an empty `files` record when nothing is persisted. */\n get: () => Promise<{ files: Record<string, string> }>\n /** Replace the persisted file map. Full-rewrite semantics. */\n save: (files: Record<string, string>) => Promise<void>\n /** Delete all persisted state. */\n delete: () => Promise<void>\n}\n\nexport interface FileMapStoreOptions {\n /** Filename for the JSONL turns log. Default: `turns.jsonl`. */\n turnsFile?: string\n /** Filename for the metadata JSON. Default: `meta.json`. */\n metaFile?: string\n}\n\ninterface MetaShape {\n id: string\n agentId?: string\n /** Optional — absent on files written before projectRoot persistence. */\n projectRoot?: string\n runs: SessionRun[]\n status: SessionData['status']\n metadata: Record<string, unknown>\n createdAt: number\n updatedAt: number\n}\n\nfunction toMeta(data: SessionData): MetaShape {\n return {\n id: data.id,\n agentId: data.agentId,\n projectRoot: data.projectRoot,\n runs: data.runs,\n status: data.status,\n metadata: data.metadata,\n createdAt: data.createdAt,\n updatedAt: data.updatedAt,\n }\n}\n\nfunction toData(meta: MetaShape, turns: SessionTurn[]): SessionData {\n return {\n id: meta.id,\n agentId: meta.agentId,\n ...(typeof meta.projectRoot === 'string' ? { projectRoot: meta.projectRoot } : {}),\n turns,\n runs: meta.runs,\n status: meta.status,\n metadata: meta.metadata,\n createdAt: meta.createdAt,\n updatedAt: meta.updatedAt,\n }\n}\n\nfunction parseTurnsJsonl(jsonl: string): SessionTurn[] {\n if (!jsonl)\n return []\n const turns: SessionTurn[] = []\n for (const line of jsonl.split('\\n')) {\n const trimmed = line.trim()\n if (!trimmed)\n continue\n try {\n turns.push(JSON.parse(trimmed) as SessionTurn)\n }\n catch {\n // Skip malformed lines — preserves the valid prefix on partial corruption.\n }\n }\n return turns\n}\n\nfunction serializeTurnsJsonl(turns: SessionTurn[]): string {\n if (turns.length === 0)\n return ''\n return `${turns.map(t => JSON.stringify(t)).join('\\n')}\\n`\n}\n\n/**\n * Create a single-session `SessionStore` backed by a file-map adapter.\n *\n * @example\n * ```ts\n * const session = await createSession({\n * store: createFileMapStore(hostSessionStore),\n * })\n * ```\n */\nexport function createFileMapStore(\n adapter: FileMapAdapter,\n options: FileMapStoreOptions = {},\n): SessionStore {\n const turnsFile = options.turnsFile ?? 'turns.jsonl'\n const metaFile = options.metaFile ?? 'meta.json'\n\n // Cached view of the persisted session. Populated lazily on first access so the\n // factory itself doesn't do I/O.\n let cached: SessionData | null = null\n let hydrated = false\n // In-flight hydrate memo: concurrent first calls share one adapter.get()\n // instead of racing two reads (the loser's view would clobber the winner's\n // subsequent writes — a lost-update race).\n let hydrating: Promise<void> | null = null\n // Turns salvaged from turns.jsonl when meta.json was present but corrupt.\n // We can't trust the (unparseable) meta for the session id, so we hold the\n // turns aside and re-attach them when the next write establishes an id —\n // the alternative (treating the session as missing) would let that write\n // overwrite an intact turns.jsonl with an empty one.\n let recoveredTurns: SessionTurn[] | null = null\n\n async function hydrate(): Promise<void> {\n if (hydrated)\n return\n if (!hydrating) {\n hydrating = (async () => {\n const { files } = await adapter.get()\n const metaRaw = files[metaFile]\n if (metaRaw) {\n let meta: MetaShape | null = null\n try {\n meta = JSON.parse(metaRaw) as MetaShape\n }\n catch {\n meta = null\n }\n if (meta) {\n cached = toData(meta, parseTurnsJsonl(files[turnsFile] ?? ''))\n }\n else {\n recoveredTurns = parseTurnsJsonl(files[turnsFile] ?? '')\n console.error(`[zidane/file-map] ${metaFile} exists but is not valid JSON — preserving ${recoveredTurns.length} turn(s) from ${turnsFile} for the next write instead of treating the session as missing`)\n }\n }\n hydrated = true\n })().finally(() => {\n hydrating = null\n })\n }\n return hydrating\n }\n\n async function persist(data: SessionData): Promise<void> {\n const meta = toMeta(data)\n await adapter.save({\n [metaFile]: JSON.stringify(meta, null, 2),\n [turnsFile]: serializeTurnsJsonl(data.turns),\n })\n }\n\n // Ensure `cached` exists for `sessionId`, creating a minimal record when first written.\n // Returns false when `cached` already holds a different sessionId (request ignored).\n async function ensureCachedFor(sessionId: string): Promise<boolean> {\n await hydrate()\n if (cached) {\n return cached.id === sessionId\n }\n const now = Date.now()\n cached = {\n id: sessionId,\n // Re-attach turns salvaged from a corrupt-meta hydrate so the next\n // persist() doesn't destroy an intact turns.jsonl.\n turns: recoveredTurns ?? [],\n runs: [],\n status: 'idle',\n metadata: {},\n createdAt: now,\n updatedAt: now,\n }\n recoveredTurns = null\n hydrated = true\n return true\n }\n\n return {\n async load(sessionId: string): Promise<SessionData | null> {\n await hydrate()\n if (!cached || cached.id !== sessionId)\n return null\n return structuredClone(cached)\n },\n\n async save(data: SessionData): Promise<void> {\n cached = structuredClone(data)\n hydrated = true\n await persist(cached)\n },\n\n async delete(sessionId: string): Promise<void> {\n await hydrate()\n if (cached && cached.id !== sessionId)\n return\n cached = null\n await adapter.delete()\n },\n\n async list(filter): Promise<string[]> {\n await hydrate()\n if (!cached)\n return []\n if (filter?.agentId && cached.agentId !== filter.agentId)\n return []\n // file-map stores exactly one session, so the projectRoot filter\n // either keeps it (match / axis off) or drops it entirely.\n if (filter && 'projectRoot' in filter) {\n const v = filter.projectRoot\n if (v === null && cached.projectRoot != null)\n return []\n if (typeof v === 'string' && cached.projectRoot !== v)\n return []\n }\n return [cached.id]\n },\n\n async appendTurns(sessionId: string, turns: SessionTurn[]): Promise<void> {\n const ok = await ensureCachedFor(sessionId)\n if (!ok)\n return\n cached!.turns.push(...structuredClone(turns))\n cached!.updatedAt = Date.now()\n await persist(cached!)\n },\n\n async getTurns(sessionId: string, from = 0, limit?: number): Promise<SessionTurn[]> {\n await hydrate()\n if (!cached || cached.id !== sessionId)\n return []\n const slice = cached.turns.slice(from, limit !== undefined ? from + limit : undefined)\n return structuredClone(slice) as SessionTurn[]\n },\n\n async updateRun(sessionId: string, run: SessionRun): Promise<void> {\n const ok = await ensureCachedFor(sessionId)\n if (!ok)\n return\n const idx = cached!.runs.findIndex(r => r.id === run.id)\n if (idx >= 0)\n cached!.runs[idx] = structuredClone(run)\n else\n cached!.runs.push(structuredClone(run))\n cached!.updatedAt = Date.now()\n await persist(cached!)\n },\n\n async updateStatus(sessionId: string, status: SessionData['status']): Promise<void> {\n const ok = await ensureCachedFor(sessionId)\n if (!ok)\n return\n cached!.status = status\n cached!.updatedAt = Date.now()\n await persist(cached!)\n },\n }\n}\n","/**\n * In-memory session store.\n * Useful for development and testing. Data is lost when the process exits.\n */\n\nimport type { SessionData, SessionRun, SessionStore } from '.'\nimport type { SessionTurn } from '../types'\n\nexport function createMemoryStore(): SessionStore {\n const sessions = new Map<string, SessionData>()\n\n // Auto-create a minimal record when turns arrive for an unknown id. Mirrors\n // the file-map store: `createSession({store})` + `agent.run()` appends\n // turns without an explicit `save()` first — a get-or-no-op `appendTurns`\n // would silently lose that history. Deliberately scoped to `appendTurns`\n // only: a late `updateRun`/`updateStatus` racing a `delete()` must stay a\n // no-op rather than resurrect a deleted session.\n function ensureSession(sessionId: string): SessionData {\n let data = sessions.get(sessionId)\n if (!data) {\n const now = Date.now()\n data = {\n id: sessionId,\n turns: [],\n runs: [],\n status: 'idle',\n metadata: {},\n createdAt: now,\n updatedAt: now,\n }\n sessions.set(sessionId, data)\n }\n return data\n }\n\n return {\n async load(sessionId: string) {\n const data = sessions.get(sessionId)\n return data ? structuredClone(data) : null\n },\n\n async save(session: SessionData) {\n sessions.set(session.id, structuredClone(session))\n },\n\n async delete(sessionId: string) {\n sessions.delete(sessionId)\n },\n\n async list(filter) {\n let ids = Array.from(sessions.keys())\n if (filter?.agentId) {\n ids = ids.filter(id => sessions.get(id)?.agentId === filter.agentId)\n }\n // `projectRoot` mirrors the SQLite store's tri-state contract:\n // string → only sessions tagged with that root\n // null → only UNTAGGED sessions (legacy / pre-v3 rows)\n // undefined → axis ignored (tagged + untagged both returned)\n if (filter && 'projectRoot' in filter) {\n const v = filter.projectRoot\n if (v === null)\n ids = ids.filter(id => sessions.get(id)?.projectRoot == null)\n else if (typeof v === 'string')\n ids = ids.filter(id => sessions.get(id)?.projectRoot === v)\n }\n if (filter?.limit) {\n ids = ids.slice(0, filter.limit)\n }\n return ids\n },\n\n async appendTurns(sessionId: string, turns: SessionTurn[]) {\n const data = ensureSession(sessionId)\n data.turns.push(...structuredClone(turns))\n data.updatedAt = Date.now()\n },\n\n async getTurns(sessionId: string, from = 0, limit?: number) {\n const data = sessions.get(sessionId)\n if (!data)\n return []\n const sliced = data.turns.slice(from, limit !== undefined ? from + limit : undefined)\n return structuredClone(sliced) as SessionTurn[]\n },\n\n async updateRun(sessionId: string, run: SessionRun) {\n const data = sessions.get(sessionId)\n if (!data)\n return\n // Upsert — see the matching comment in sqlite.ts's\n // `txnUpdateRun` for the failure mode. tl;dr: the agent's\n // `startRun()` only updates in-memory state; the store first\n // sees the run via `updateRun()` at finalize time, so an\n // update-only impl silently drops every new run.\n const idx = data.runs.findIndex(r => r.id === run.id)\n if (idx >= 0)\n data.runs[idx] = structuredClone(run)\n else\n data.runs.push(structuredClone(run))\n data.updatedAt = Date.now()\n },\n\n async updateStatus(sessionId: string, status: SessionData['status']) {\n const data = sessions.get(sessionId)\n if (!data)\n return\n data.status = status\n data.updatedAt = Date.now()\n },\n }\n}\n","/**\n * Remote session store via HTTP API.\n *\n * Expects a REST API with:\n * GET {url}/sessions/{id} -> SessionData | 404\n * PUT {url}/sessions/{id} -> save SessionData\n * DELETE {url}/sessions/{id} -> delete\n * GET {url}/sessions?agentId=&limit=&projectRoot= -> { ids: string[] }\n * `projectRoot=__null__` is the wire encoding for \"untagged only\".\n * POST {url}/sessions/{id}/turns -> append turns\n * GET {url}/sessions/{id}/turns?from=&limit= -> SessionTurn[]\n * PUT {url}/sessions/{id}/runs/{runId} -> update run\n * PATCH {url}/sessions/{id} -> { status }\n */\n\nimport type { SessionData, SessionRun, SessionStore } from '.'\nimport type { SessionTurn } from '../types'\n\nexport interface RemoteStoreOptions {\n /** Base URL of the session API */\n url: string\n /** Optional headers (e.g. for authentication) */\n headers?: Record<string, string>\n /** Per-request timeout in ms. Default 30s — bounds a hung backend. */\n timeoutMs?: number\n}\n\nconst TRAILING_SLASH = /\\/$/\nconst DEFAULT_TIMEOUT_MS = 30_000\n\nexport function createRemoteStore(options: RemoteStoreOptions): SessionStore {\n const baseUrl = options.url.replace(TRAILING_SLASH, '')\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS\n const defaultHeaders: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...options.headers,\n }\n\n async function request(path: string, init?: RequestInit): Promise<Response> {\n const url = `${baseUrl}${path}`\n // Bound every call so a hung backend can't wedge the caller forever.\n const res = await fetch(url, {\n ...init,\n headers: { ...defaultHeaders, ...init?.headers },\n signal: init?.signal ?? AbortSignal.timeout(timeoutMs),\n })\n return res\n }\n\n /**\n * Parse a JSON response body, rethrowing parse failures with URL/status\n * context so a malformed backend reply produces a diagnosable error\n * instead of an opaque `Unexpected token` from deep in `res.json()`.\n */\n async function parseJson<T>(res: Response, path: string): Promise<T> {\n try {\n return await res.json() as T\n }\n catch (cause) {\n throw new Error(\n `Remote session: malformed JSON from ${path} (${res.status} ${res.statusText})`,\n { cause },\n )\n }\n }\n\n return {\n async load(sessionId: string) {\n const res = await request(`/sessions/${encodeURIComponent(sessionId)}`)\n if (!res.ok) {\n if (res.status === 404)\n return null\n throw new Error(`Remote session load failed: ${res.status} ${res.statusText}`)\n }\n return await parseJson<SessionData>(res, `/sessions/${sessionId}`)\n },\n\n async save(session: SessionData) {\n const res = await request(`/sessions/${encodeURIComponent(session.id)}`, {\n method: 'PUT',\n body: JSON.stringify(session),\n })\n if (!res.ok) {\n throw new Error(`Remote session save failed: ${res.status} ${res.statusText}`)\n }\n },\n\n async delete(sessionId: string) {\n const res = await request(`/sessions/${encodeURIComponent(sessionId)}`, {\n method: 'DELETE',\n })\n if (!res.ok && res.status !== 404) {\n throw new Error(`Remote session delete failed: ${res.status} ${res.statusText}`)\n }\n },\n\n async list(filter) {\n const params = new URLSearchParams()\n if (filter?.agentId)\n params.set('agentId', filter.agentId)\n if (filter?.limit)\n params.set('limit', String(filter.limit))\n if (filter && 'projectRoot' in filter) {\n const v = filter.projectRoot\n if (v === null)\n params.set('projectRoot', '__null__')\n else if (typeof v === 'string')\n params.set('projectRoot', v)\n }\n\n const query = params.toString()\n const path = query ? `/sessions?${query}` : '/sessions'\n const res = await request(path)\n\n if (!res.ok) {\n throw new Error(`Remote session list failed: ${res.status} ${res.statusText}`)\n }\n\n const body = await parseJson<{ ids: string[] }>(res, path)\n if (!Array.isArray(body?.ids)) {\n throw new TypeError(`Remote session list: expected { ids: string[] } from ${path}, got ${typeof body?.ids}`)\n }\n return body.ids\n },\n\n async appendTurns(sessionId: string, turns: SessionTurn[]) {\n const res = await request(`/sessions/${encodeURIComponent(sessionId)}/turns`, {\n method: 'POST',\n body: JSON.stringify(turns),\n })\n if (!res.ok) {\n throw new Error(`Remote appendTurns failed: ${res.status} ${res.statusText}`)\n }\n },\n\n async getTurns(sessionId: string, from = 0, limit?: number) {\n const params = new URLSearchParams()\n if (from)\n params.set('from', String(from))\n if (limit !== undefined)\n params.set('limit', String(limit))\n\n const query = params.toString()\n const path = `/sessions/${encodeURIComponent(sessionId)}/turns${query ? `?${query}` : ''}`\n const res = await request(path)\n\n if (!res.ok) {\n throw new Error(`Remote getTurns failed: ${res.status} ${res.statusText}`)\n }\n\n const turns = await parseJson<SessionTurn[]>(res, path)\n if (!Array.isArray(turns)) {\n throw new TypeError(`Remote getTurns: expected SessionTurn[] from ${path}, got ${typeof turns}`)\n }\n return turns\n },\n\n async updateRun(sessionId: string, run: SessionRun) {\n const res = await request(\n `/sessions/${encodeURIComponent(sessionId)}/runs/${encodeURIComponent(run.id)}`,\n {\n method: 'PUT',\n body: JSON.stringify(run),\n },\n )\n if (!res.ok) {\n throw new Error(`Remote updateRun failed: ${res.status} ${res.statusText}`)\n }\n },\n\n async updateStatus(sessionId: string, status: SessionData['status']) {\n const res = await request(`/sessions/${encodeURIComponent(sessionId)}`, {\n method: 'PATCH',\n body: JSON.stringify({ status }),\n })\n if (!res.ok) {\n throw new Error(`Remote updateStatus failed: ${res.status} ${res.statusText}`)\n }\n },\n }\n}\n","/**\n * Session management for agents.\n *\n * A session tracks identity, turn history, and run metadata.\n * Plug in any storage backend by implementing the SessionStore interface,\n * or use one of the built-in stores: memory, sqlite, remote.\n */\n\nimport type { JsonValue, SessionMessage, SessionTurn, TurnUsage } from '../types'\nimport type { PairingDrop, ToWireMessagesOptions } from './messages'\nimport { AsyncLocalStorage } from 'node:async_hooks'\nimport { Buffer } from 'node:buffer'\nimport { dropInvalidToolPairings, toWireMessages } from './messages'\n\nexport type { SessionContentBlock, SessionMessage, SessionTurn } from '../types'\nexport { createFileMapStore } from './file-map'\n\nconst EXTENSION_SESSION_METADATA_KEY = 'extensions'\nconst EXTENSION_SESSION_ITEM_MAX_BYTES = 16_384\nconst EXTENSION_SESSION_TOTAL_MAX_BYTES = 65_536\nconst DANGEROUS_RECORD_KEYS = new Set(['__proto__', 'prototype', 'constructor'])\nconst extensionSessionStorage = new AsyncLocalStorage<Session>()\n\n/**\n * Run `fn` with `session` bound as the active extension session for the\n * duration of the call (and every async continuation it spawns). Uses\n * `AsyncLocalStorage.run` rather than `enterWith` so concurrent agents in the\n * same process (e.g. the GUI's multi-session host) never observe each other's\n * session through `currentExtensionSession()`.\n */\nexport function withExtensionSession<T>(session: Session | undefined, fn: () => T): T {\n if (!session)\n return fn()\n return extensionSessionStorage.run(session, fn)\n}\n\nexport function currentExtensionSession(): Session | undefined {\n return extensionSessionStorage.getStore()\n}\n\nexport function getExtensionSessionState<T = JsonValue>(\n session: Session | undefined,\n extensionName: string,\n key: string,\n): T | undefined {\n if (!session || !isSafeExtensionStateKey(extensionName) || !isSafeExtensionStateKey(key))\n return undefined\n const root = session.metadata[EXTENSION_SESSION_METADATA_KEY]\n if (!root || typeof root !== 'object' || Array.isArray(root))\n return undefined\n const byExtension = (root as Record<string, unknown>)[extensionName]\n if (!byExtension || typeof byExtension !== 'object' || Array.isArray(byExtension))\n return undefined\n const stored = (byExtension as Record<string, unknown>)[key]\n if (stored === undefined)\n return undefined\n // Hand back a copy so a caller mutating the result can't reach into the live\n // session metadata behind `setMeta`/`save`.\n return jsonClone(stored as JsonValue) as T\n}\n\nexport async function setExtensionSessionState(\n session: Session | undefined,\n extensionName: string,\n key: string,\n value: JsonValue,\n): Promise<void> {\n if (!session)\n return\n assertSafeExtensionStateKey(extensionName, 'extensionName')\n assertSafeExtensionStateKey(key, 'key')\n assertJsonBounded(value, EXTENSION_SESSION_ITEM_MAX_BYTES, `extension session state \"${extensionName}.${key}\"`)\n\n const root = cloneExtensionStateRoot(session.metadata[EXTENSION_SESSION_METADATA_KEY])\n const byExtension = clonePlainRecord(root[extensionName])\n // Deep-copy on write so later mutations to the caller's object don't bleed\n // into persisted state.\n byExtension[key] = jsonClone(value)\n root[extensionName] = byExtension\n assertJsonBounded(root as JsonValue, EXTENSION_SESSION_TOTAL_MAX_BYTES, 'extension session state')\n session.setMeta(EXTENSION_SESSION_METADATA_KEY, root)\n if (session.store)\n await session.save()\n}\n\nexport async function deleteExtensionSessionState(\n session: Session | undefined,\n extensionName: string,\n key: string,\n): Promise<void> {\n if (!session || !isSafeExtensionStateKey(extensionName) || !isSafeExtensionStateKey(key))\n return\n const root = cloneExtensionStateRoot(session.metadata[EXTENSION_SESSION_METADATA_KEY])\n const byExtension = clonePlainRecord(root[extensionName])\n if (!(key in byExtension))\n return\n delete byExtension[key]\n if (Object.keys(byExtension).length > 0)\n root[extensionName] = byExtension\n else\n delete root[extensionName]\n session.setMeta(EXTENSION_SESSION_METADATA_KEY, root)\n if (session.store)\n await session.save()\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Transitive `resumedFromRunId` chain starting at `runId` (inclusive).\n *\n * A resumed run only persists the turns IT produced — the turns it was\n * seeded with still carry the original run's id. So reconstructing a\n * resumed child's full conversation means collecting turns from every run\n * in its resume lineage, not just the latest link. Cycle-guarded (a\n * malformed chain terminates instead of looping).\n */\nexport function resumeChainRunIds(runs: readonly SessionRun[], runId: string): Set<string> {\n const chain = new Set<string>()\n let id: string | undefined = runId\n while (id && !chain.has(id)) {\n chain.add(id)\n id = runs.find(r => r.id === id)?.resumedFromRunId\n }\n return chain\n}\n\nexport interface SessionRun {\n id: string\n startedAt: number\n endedAt?: number\n prompt: string\n status: 'running' | 'completed' | 'aborted' | 'error'\n turns?: number\n tokensIn?: number\n tokensOut?: number\n error?: string\n /** Per-turn usage breakdown */\n turnUsage?: TurnUsage[]\n /** Total usage across all turns */\n totalUsage?: TurnUsage\n /** Estimated cost in USD */\n cost?: number\n /**\n * The run that spawned this one, when the agent is a subagent sharing its\n * parent's session. Undefined on top-level `agent.run()`. Consumers can walk\n * `runs` by `parentRunId` to reconstruct the subagent tree.\n */\n parentRunId?: string\n /**\n * The prior run this one continues (see `AgentRunOptions.resumeRunId`).\n * Set when a stopped/failed subagent run was resumed — consumers can\n * chain runs by `resumedFromRunId` to render “child-4 (resumes child-2)”\n * style markers. Absent on normal runs.\n */\n resumedFromRunId?: string\n /**\n * Host-assigned display label (e.g. the spawn tool's `child-3`), persisted\n * so label → run resolution is a stored fact instead of a positional\n * guess. Positional `child-N` numbering can desynchronize from announced\n * labels (concurrent spawns `startRun` out of reservation order, a\n * pre-`startRun` throw advances the counter without a run, and reload\n * ordering ties break lexicographically) — resolving by stored label is\n * immune to all three. Absent on top-level runs and legacy sessions.\n */\n label?: string\n /**\n * Zero-based subagent depth. 0 = top-level run, 1 = direct child, …\n * Recorded here so hosts can query/filter by level without walking the tree.\n */\n depth?: number\n /**\n * How this run was initiated when not a plain user prompt. `command` marks\n * a sub-run driven from a `/` command's `ctx.runAgent`; `display` is the\n * short line the transcript shows instead of the full injected prompt\n * (which often embeds bulky material like a diff). Presentation-only —\n * the wire/model payload is unaffected. Absent on normal runs.\n */\n origin?: { kind: 'command', display?: string }\n}\n\nexport interface SessionData {\n id: string\n agentId?: string\n /**\n * Absolute path of the project this session belongs to — typically\n * the git root resolved from `cwd` at creation time, falling back to\n * `cwd` itself when not in a git repo. Set ONCE on creation and never\n * mutated thereafter (the session \"belongs\" to that project forever).\n *\n * Used by the TUI's sessions list to filter rows by current project,\n * so the user only sees conversations relevant to where they are\n * working — without needing one `.{prefix}/` directory per project.\n *\n * `undefined` on pre-tagging legacy sessions; the chat layer treats\n * those as \"untagged\" and hides them unless `Settings.showAllProjects`\n * is on.\n */\n projectRoot?: string\n turns: SessionTurn[]\n runs: SessionRun[]\n status: 'idle' | 'running' | 'completed' | 'error'\n metadata: Record<string, unknown>\n createdAt: number\n updatedAt: number\n}\n\n/**\n * Compact per-session summary returned by {@link SessionStore.listMeta}.\n * Raw facts only — presentation (title derivation, fallback strings)\n * stays in the chat layer (`listSessionMeta` / `deriveSessionTitle`).\n */\nexport interface SessionMetaRow {\n id: string\n /** Header metadata (may carry a stored `title`). */\n metadata: Record<string, unknown>\n /** Absent for untagged (legacy) sessions. */\n projectRoot?: string\n updatedAt: number\n turnCount: number\n /** Count of `role: 'user'` turns. */\n userMessageCount: number\n runCount: number\n /**\n * First `role: 'user'` turn — the title-derivation source. Stores may\n * omit it when `metadata.title` is already set (no derivation needed).\n */\n firstUserTurn?: SessionTurn\n}\n\n// ---------------------------------------------------------------------------\n// SessionStore interface (pluggable backend)\n// ---------------------------------------------------------------------------\n\nexport interface SessionStore {\n /** Optional: generate a session ID server-side (e.g. a database-issued UUID). */\n generateSessionId?: () => string | Promise<string>\n\n /**\n * Optional: generate a turn ID server-side for session-managed user turns.\n *\n * Provider-loop assistant and tool-result turns use AgentClock.randomUUID()\n * instead, so durable-execution adapters can keep those IDs replay-stable.\n */\n generateTurnId?: () => string | Promise<string>\n\n /** Load a session by ID. Returns null if not found. */\n load: (sessionId: string) => Promise<SessionData | null>\n\n /** Save a session (create or update, full document). */\n save: (session: SessionData) => Promise<void>\n\n /** Delete a session. */\n delete: (sessionId: string) => Promise<void>\n\n /**\n * List session IDs, optionally filtered. `projectRoot` restricts to\n * sessions whose `SessionData.projectRoot` matches exactly — untagged\n * (legacy) sessions are NOT returned under that filter; pass `null`\n * explicitly to ask for untagged ones, or omit the field to ignore\n * the axis entirely. `agentId` filters by recorded agent; the two\n * conditions AND together when both are set.\n */\n list: (filter?: { agentId?: string, limit?: number, projectRoot?: string | null }) => Promise<string[]>\n\n /**\n * Optional: list session summaries without materializing full sessions.\n *\n * The generic fallback (`listSessionMeta` in `zidane/chat`) is `list()`\n * + `load()` per id — O(every turn in the store) because each `load`\n * parses the full turn history just to count rows and derive a title.\n * Stores that can answer from an index (SQL aggregates, a denormalized\n * header) should implement this; the sqlite store turns a multi-hundred-\n * ms TUI boot cost into a single query.\n *\n * Same filter contract and ordering (most recently updated first) as\n * {@link list}.\n */\n listMeta?: (filter?: { agentId?: string, limit?: number, projectRoot?: string | null }) => Promise<SessionMetaRow[]>\n\n /** Append new turns to a session (incremental, avoids full re-save). */\n appendTurns: (sessionId: string, turns: SessionTurn[]) => Promise<void>\n\n /** Return a slice of turns for a session. */\n getTurns: (sessionId: string, from?: number, limit?: number) => Promise<SessionTurn[]>\n\n /**\n * Persist a run record (called after completeRun / abortRun / errorRun).\n *\n * **Upsert semantics required.** The agent's run lifecycle calls\n * `session.startRun()` to register a new run in IN-MEMORY state and\n * then `updateRun()` at finalize. The store doesn't see the run any\n * earlier — `appendTurns` is the only intermediate call, and it\n * persists turns but not runs. An update-only implementation would\n * silently drop every newly-created run record at finalize, leaving\n * the on-disk `runs[]` missing entries that the persisted turns\n * reference (which `eventsFromTurns` then mis-classifies via\n * `ancestryOf` → broken depth, broken `child-N` labels, broken\n * `userPrompts` history).\n *\n * Implementations MUST insert when the id doesn't exist and update\n * when it does. The bundled `sqlite` / `memory` / `file-map` stores\n * all follow this contract; remote stores using HTTP PUT get upsert\n * for free via REST semantics.\n */\n updateRun: (sessionId: string, run: SessionRun) => Promise<void>\n\n /** Update the top-level status of a session. */\n updateStatus: (sessionId: string, status: SessionData['status']) => Promise<void>\n\n /**\n * Optional: release backend resources (e.g. close the SQLite handle).\n * Hosts should call this on teardown when present; stores backed by\n * memory or remote HTTP have nothing to release and may omit it.\n */\n close?: () => void\n}\n\n// ---------------------------------------------------------------------------\n// Session (live instance wrapping a SessionData)\n// ---------------------------------------------------------------------------\n\nexport interface Session {\n /** Session ID */\n readonly id: string\n\n /** Agent ID (optional label) */\n readonly agentId?: string\n\n /**\n * Project this session was created under — see {@link SessionData.projectRoot}.\n * Set once on creation; surfaces here for read-only inspection.\n */\n readonly projectRoot?: string\n\n /**\n * Raw persisted turn history — kept pristine for fidelity (the model's\n * reasoning chain stays intact across reloads).\n *\n * **DO NOT pass this directly to a provider request.** Mid-pair\n * persistence (interrupted runs, partial-result writes, compaction\n * boundaries) leaves orphan `tool_use` / `tool_result` blocks here that\n * every provider 400s on (`tool_result must be preceded by a tool_call\n * with the same toolCallId`). The agent's own loop repairs these\n * just-in-time at wire-send time and never writes the repair back to\n * `turns`.\n *\n * For provider requests built outside `agent.run()`, use\n * {@link Session.toMessages} — it applies the same defensive pairing\n * repair and resolves to a guaranteed-safe `SessionMessage[]`.\n */\n readonly turns: SessionTurn[]\n\n /**\n * True when this session has no turns yet.\n *\n * Use this as a first-prompt signal when setting up a run — e.g. writing initial\n * configuration only on fresh sessions. Equivalent to `turns.length === 0`.\n */\n readonly isEmpty: boolean\n\n /** Top-level session status */\n readonly status: SessionData['status']\n\n /** All runs in this session */\n readonly runs: SessionRun[]\n\n /** Arbitrary metadata */\n readonly metadata: Record<string, unknown>\n\n /**\n * Start tracking a new run. `extras.parentRunId` + `extras.depth` are\n * populated by the spawn tool when a child agent shares its parent's\n * session; regular top-level `agent.run()` calls omit them.\n */\n startRun: (runId: string, prompt?: string, extras?: { parentRunId?: string, depth?: number, resumedFromRunId?: string, label?: string }) => void\n\n /** Mark a run as completed */\n completeRun: (runId: string, stats: { turns: number, tokensIn: number, tokensOut: number, turnUsage?: TurnUsage[], cost?: number }) => void\n\n /** Mark a run as aborted */\n /**\n * Optional `stats` lets the agent backfill the run's token totals when\n * the abort happened *after* the loop accumulated meaningful usage —\n * common when the user presses esc mid-streaming. Without it, the run\n * record reads `0 in / 0 out` on reload regardless of how much was\n * spent before the abort. Same shape as `completeRun`'s stats so the\n * persisted `totalUsage` aggregate stays consistent across paths.\n */\n abortRun: (runId: string, stats?: { turns: number, tokensIn: number, tokensOut: number, turnUsage?: TurnUsage[], cost?: number }) => void\n\n /** Mark a run as errored */\n /** Optional `stats` — same rationale as `abortRun.stats`. */\n errorRun: (runId: string, error: string, stats?: { turns: number, tokensIn: number, tokensOut: number, turnUsage?: TurnUsage[], cost?: number }) => void\n\n /** Append turns to in-memory history AND persist via store.appendTurns (if store present) */\n appendTurns: (turns: SessionTurn[]) => Promise<void>\n\n /** Replace all turns in-memory (does not persist — use save() for that) */\n setTurns: (turns: SessionTurn[]) => void\n\n /**\n * Replace all runs in-memory (does not persist — use save() for that).\n * Mirrors {@link setTurns} for the fork / restore case: callers that\n * bootstrap a session from an externally-derived snapshot (e.g.\n * `onForkTurn` copying parent runs into a child session) need this so\n * the cloned runs land in `data.runs` before the first `save()`.\n * Production agent runs continue to mutate runs via `startRun` /\n * `completeRun` / `updateRun`; this is the bulk-replace escape hatch.\n */\n setRuns: (runs: SessionRun[]) => void\n\n /** Update the session status in memory AND via store.updateStatus (if store present) */\n updateStatus: (status: SessionData['status']) => Promise<void>\n\n /** Persist an updated run record via store.updateRun (if store present) */\n updateRun: (run: SessionRun) => Promise<void>\n\n /** Generate a session-managed turn ID using store.generateTurnId if available, else crypto.randomUUID() */\n generateTurnId: () => string | Promise<string>\n\n /** Set metadata key */\n setMeta: (key: string, value: unknown) => void\n\n /** Persist the full session document to the store */\n save: () => Promise<void>\n\n /**\n * Project the session's persisted turns into wire-ready messages safe to\n * hand to any provider.\n *\n * The canonical answer to \"I need to send these turns to Anthropic /\n * OpenAI from outside `agent.run()`\" (sub-agent classifier, side-channel\n * summarizer, replay worker, …). Runs the same defensive pairing repair\n * the agent loop applies just-in-time at wire-send time\n * ({@link ensureToolResultPairing}), so orphan `tool_use` blocks and\n * dangling `tool_result`s never reach the provider as the 400\n * \"tool_result must be preceded by a tool_call with the same toolCallId\".\n *\n * Pass `provider` to also enforce the user-tail invariant\n * ({@link ensureEndsWithUserMessage}) — required for opus 4.7 / o-series\n * style models that reject assistant-prefill.\n *\n * Pure + idempotent. Resolves content refs only in the returned messages\n * and does NOT mutate `session.turns`. **Do not write the result back via\n * `setTurns` / `appendTurns`** — synthetic placeholders or resolved\n * payloads will contaminate persisted history.\n */\n toMessages: (options?: ToWireMessagesOptions) => Promise<SessionMessage[]>\n\n /** Serialize to SessionData */\n toJSON: () => SessionData\n\n /**\n * The backing store, when one was provided. Exposed so hosts can call\n * its optional `close()` on teardown (e.g. release the SQLite handle).\n */\n readonly store?: SessionStore\n}\n\n// ---------------------------------------------------------------------------\n// createSession\n// ---------------------------------------------------------------------------\n\nexport interface CreateSessionOptions {\n /** Session ID. If omitted and store provides generateSessionId, that is used. */\n id?: string\n /** Agent ID label */\n agentId?: string\n /**\n * Project tag — see {@link SessionData.projectRoot}. Stamped once on\n * creation; ignored when `_data` is set (restoring an existing\n * session preserves whatever was already persisted there). The TUI\n * resolves this from `findGitRoot(cwd) ?? cwd` so sessions started\n * from the same repo (no matter which subdir) share one tag.\n */\n projectRoot?: string\n /** Initial metadata */\n metadata?: Record<string, unknown>\n /** Storage backend (optional, enables save/load) */\n store?: SessionStore\n /**\n * Observability hook for the load-time pairing heal: fired once per block\n * or turn `dropInvalidToolPairings` deleted while restoring `_data`.\n * Deletions are permanent once the session is saved, so hosts that alert\n * on wire-time `pairing:repair` events should wire this to the same sink.\n * Only fires on the `_data` restore path; fresh sessions never heal.\n */\n onPairingDrop?: (drop: PairingDrop) => void\n /**\n * @internal: restore from existing data (bypasses id/agentId/metadata\n * options). The session ALIASES this object for its lifetime — every\n * mutation (appendTurns, run records, updatedAt) writes through to it,\n * and the load-time pairing heal may replace its `turns` array in place\n * before the session is returned. Callers must not retain independent\n * ownership expectations.\n */\n _data?: SessionData\n}\n\n/**\n * Create a new session.\n * Async so stores that generate IDs server-side (e.g. a database) can be supported.\n */\nexport async function createSession(options: CreateSessionOptions = {}): Promise<Session> {\n const store = options.store\n const now = Date.now()\n\n let sessionId = options.id\n if (!sessionId && store?.generateSessionId) {\n sessionId = await store.generateSessionId()\n }\n if (!sessionId) {\n sessionId = generateId()\n }\n\n // Load-time drop-only healing: restored sessions get structurally invalid\n // tool blocks (duplicate tool_call ids, orphan tool_results) deleted before\n // any consumer sees them. Pure deletions — never synthetic content — so\n // writing the result back to the store is safe (unlike `toMessages`\n // output). Without this, a transcript corrupted once (e.g. a retry that\n // re-appended an already-persisted turn) re-triggers wire-time pairing\n // repairs on every turn forever. `loadSession` persists the healed\n // transcript; direct `createSession({ _data })` callers get the in-memory\n // heal and persist on their next `save()`. The heal replaces `_data.turns`\n // IN PLACE: the session aliases `_data` for its whole lifetime (see the\n // option doc), so an on-the-side copy would make the corrupt path the only\n // one where a caller's retained reference diverges from the live session.\n // `loadSession` detects healing by comparing `session.turns` against its\n // pre-heal reference.\n if (options._data) {\n const restoreData = options._data\n const healed = dropInvalidToolPairings(restoreData.turns, {\n laneForRun: wireViewLaneResolver(restoreData.runs),\n ...(options.onPairingDrop ? { onDrop: options.onPairingDrop } : {}),\n })\n if (healed !== restoreData.turns)\n restoreData.turns = healed\n }\n\n const data: SessionData = options._data ?? {\n id: sessionId,\n agentId: options.agentId,\n // Stamp the project tag at creation only — restored sessions (the\n // `_data` branch above) keep whatever was already persisted, even\n // if `options.projectRoot` differs. A session's project identity\n // is sticky for its lifetime; loading the same session from a\n // different cwd doesn't re-home it.\n ...(options.projectRoot ? { projectRoot: options.projectRoot } : {}),\n turns: [],\n runs: [],\n status: 'idle',\n metadata: options.metadata ?? {},\n createdAt: now,\n updatedAt: now,\n }\n\n function touch() {\n data.updatedAt = Date.now()\n }\n\n function findRun(runId: string): SessionRun | undefined {\n return data.runs.find(r => r.id === runId)\n }\n\n /**\n * Apply per-run usage stats onto a SessionRun. Shared by `completeRun`,\n * `abortRun`, and `errorRun` so the on-disk shape stays consistent across\n * exit paths — historically only `completeRun` filled these in, and\n * aborted/errored runs rendered `0 in / 0 out` on reload regardless of\n * how much was actually consumed.\n */\n function applyRunStats(run: SessionRun, stats: { turns: number, tokensIn: number, tokensOut: number, turnUsage?: TurnUsage[], cost?: number }) {\n run.turns = stats.turns\n run.tokensIn = stats.tokensIn\n run.tokensOut = stats.tokensOut\n if (stats.turnUsage) {\n run.turnUsage = stats.turnUsage\n const total = stats.turnUsage.reduce((acc, t) => ({\n input: acc.input + t.input,\n output: acc.output + t.output,\n cacheCreation: (acc.cacheCreation ?? 0) + (t.cacheCreation ?? 0),\n cacheRead: (acc.cacheRead ?? 0) + (t.cacheRead ?? 0),\n thinking: (acc.thinking ?? 0) + (t.thinking ?? 0),\n }), { input: 0, output: 0, cacheCreation: 0, cacheRead: 0, thinking: 0 })\n run.totalUsage = {\n input: total.input,\n output: total.output,\n ...(total.cacheCreation ? { cacheCreation: total.cacheCreation } : {}),\n ...(total.cacheRead ? { cacheRead: total.cacheRead } : {}),\n ...(total.thinking ? { thinking: total.thinking } : {}),\n }\n }\n if (stats.cost !== undefined)\n run.cost = stats.cost\n }\n\n const session: Session = {\n ...(store ? { store } : {}),\n get id() { return data.id },\n get agentId() { return data.agentId },\n get projectRoot() { return data.projectRoot },\n get turns() { return data.turns },\n get isEmpty() { return data.turns.length === 0 },\n get status() { return data.status },\n get runs() { return data.runs },\n get metadata() { return data.metadata },\n\n startRun(runId: string, prompt?: string, extras?: { parentRunId?: string, depth?: number, resumedFromRunId?: string, label?: string }) {\n data.runs.push({\n id: runId,\n startedAt: Date.now(),\n prompt: prompt ?? '',\n status: 'running',\n ...(extras?.parentRunId ? { parentRunId: extras.parentRunId } : {}),\n ...(typeof extras?.depth === 'number' ? { depth: extras.depth } : {}),\n ...(extras?.resumedFromRunId ? { resumedFromRunId: extras.resumedFromRunId } : {}),\n ...(extras?.label ? { label: extras.label } : {}),\n })\n touch()\n },\n\n completeRun(runId: string, stats: { turns: number, tokensIn: number, tokensOut: number, turnUsage?: TurnUsage[], cost?: number }) {\n const run = findRun(runId)\n if (run) {\n run.status = 'completed'\n run.endedAt = Date.now()\n applyRunStats(run, stats)\n }\n touch()\n },\n\n abortRun(runId: string, stats?: { turns: number, tokensIn: number, tokensOut: number, turnUsage?: TurnUsage[], cost?: number }) {\n const run = findRun(runId)\n if (run) {\n run.status = 'aborted'\n run.endedAt = Date.now()\n // Backfill tokens when available so an aborted run's session\n // ledger reflects actual consumption rather than `0 in / 0 out`.\n if (stats)\n applyRunStats(run, stats)\n }\n touch()\n },\n\n errorRun(runId: string, error: string, stats?: { turns: number, tokensIn: number, tokensOut: number, turnUsage?: TurnUsage[], cost?: number }) {\n const run = findRun(runId)\n if (run) {\n run.status = 'error'\n run.endedAt = Date.now()\n run.error = error\n if (stats)\n applyRunStats(run, stats)\n }\n touch()\n },\n\n async appendTurns(turns: SessionTurn[]) {\n data.turns.push(...turns)\n touch()\n if (store) {\n await store.appendTurns(data.id, turns)\n }\n },\n\n setTurns(turns: SessionTurn[]) {\n data.turns = turns\n touch()\n },\n\n setRuns(runs: SessionRun[]) {\n data.runs = runs\n touch()\n },\n\n async updateStatus(status: SessionData['status']) {\n data.status = status\n touch()\n if (store) {\n await store.updateStatus(data.id, status)\n }\n },\n\n async updateRun(run: SessionRun) {\n if (store) {\n await store.updateRun(data.id, run)\n }\n },\n\n generateTurnId() {\n if (store?.generateTurnId) {\n return store.generateTurnId()\n }\n return crypto.randomUUID()\n },\n\n setMeta(key: string, value: unknown) {\n data.metadata[key] = value\n touch()\n },\n\n async save() {\n if (!store) {\n throw new Error('No SessionStore configured. Pass a store to createSession() to enable persistence.')\n }\n await store.save(data)\n },\n\n toMessages(options?: ToWireMessagesOptions) {\n return toWireMessages(data.turns, options)\n },\n\n toJSON() {\n return structuredClone(data)\n },\n }\n\n return session\n}\n\n/**\n * Demote a persisted `'running'` status to `'idle'` (in place) and return the\n * data. A persisted `'running'` is necessarily stale: the run that set it lived\n * in another process (crash, kill -9, power loss) and cannot still be executing\n * in a freshly-loaded session. Call this on the result of a raw `store.load()`\n * so consumers don't render a ghost in-progress state or refuse a new run.\n * `loadSession` applies it automatically; direct `store.load()` callers (e.g.\n * the TUI) should run loaded data through this.\n *\n * Run RECORDS get the same treatment: the agent persists each record at\n * run start with `status: 'running'` (crash durability — see the\n * `SessionStore.updateRun` contract), so a freshly-loaded session may\n * carry `'running'` runs that died with their process. They demote to\n * `'aborted'` so replay renders a truthful \"aborted\" state instead of a\n * perpetually-in-flight run.\n */\nexport function demoteStaleRunningStatus(data: SessionData): SessionData {\n if (data.status === 'running')\n data.status = 'idle'\n for (const run of data.runs) {\n if (run.status === 'running') {\n run.status = 'aborted'\n run.endedAt ??= run.startedAt\n }\n }\n return data\n}\n\nexport interface LoadSessionOptions {\n /** Forwarded to `createSession` — see {@link CreateSessionOptions.onPairingDrop}. */\n onPairingDrop?: (drop: PairingDrop) => void\n}\n\n/**\n * Load an existing session from a store.\n */\nexport async function loadSession(store: SessionStore, sessionId: string, options: LoadSessionOptions = {}): Promise<Session | null> {\n const loaded = await store.load(sessionId)\n if (!loaded)\n return null\n\n demoteStaleRunningStatus(loaded)\n\n // createSession heals structurally invalid tool pairings on the `_data`\n // branch (drop-only — see dropInvalidToolPairings). When it changed the\n // transcript, persist the healed version so the corruption never re-heals\n // (and never re-fires wire-time repairs) on subsequent loads.\n //\n // Best-effort: the in-memory heal is what correctness needs — persistence\n // is an optimization that retries on the next save(). A save failure\n // (read-only store, transient IO) must not make a loadable session throw\n // at load time. Note save() is a bulk overwrite of the loaded snapshot;\n // like every other save() caller (compact, fork), this assumes no other\n // process is appending to the session concurrently.\n const originalTurns = loaded.turns\n const originalTurnCount = originalTurns.length\n const session = await createSession({\n store,\n _data: loaded,\n ...(options.onPairingDrop ? { onPairingDrop: options.onPairingDrop } : {}),\n })\n if (session.turns !== originalTurns) {\n try {\n // Shrink the concurrent-writer window: another process may have\n // appended turns between our load() and now — a bulk save() would\n // silently discard them. Re-count before overwriting; on mismatch,\n // keep the heal in-memory and let a future clean load persist it.\n const current = await store.getTurns(sessionId)\n if (current.length === originalTurnCount)\n await session.save()\n }\n catch {\n // Healed in memory; persistence retries on the next save().\n }\n }\n return session\n}\n\n// ---------------------------------------------------------------------------\n// Re-export stores\n// ---------------------------------------------------------------------------\n\nexport type { FileMapAdapter, FileMapStoreOptions } from './file-map'\nexport { createMemoryStore } from './memory'\nexport { autoDetectAndConvert, dropInvalidToolPairings, fromAnthropic, fromOpenAI, toAnthropic, toOpenAI, toWireMessages } from './messages'\nexport type { PairingDrop, PairingDropKind } from './messages'\nexport type { ToWireMessagesOptions } from './messages'\nexport { createRemoteStore } from './remote'\nexport type { RemoteStoreOptions } from './remote'\n\n// NOTE: `createSqliteStore` is intentionally NOT re-exported here. It lives behind\n// the dedicated `zidane/session/sqlite` subpath so that non-Bun consumers don't\n// transitively evaluate `bun:sqlite` when they import from `zidane/session`.\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Build a `laneForRun` resolver (see `dropInvalidToolPairings`) from a\n * session's run records, mapping each runId to the wire view its turns reach:\n *\n * - depth-0 runs (and turns with no runId — legacy sessions) all share the\n * MAIN view: the top-level conversation spans every prompt's run, so\n * duplicate tool_call ids across those runs DO collide in one request.\n * - depth>0 runs get their resume-chain ROOT as lane: a resumed subagent\n * replays its ancestors' turns in its own view, but parallel subagents\n * never see each other's.\n * - runIds with no run record (the historical dropped-subagent-run bug) are\n * isolated in their own lane — conservative: same-runId corruption still\n * heals, and an untracked child's counter-style ids can't collide with the\n * main lane's.\n */\nfunction wireViewLaneResolver(runs: readonly SessionRun[]): (runId: string | undefined) => string {\n const MAIN_LANE = '\\0main'\n const byId = new Map(runs.map(r => [r.id, r]))\n const cache = new Map<string, string>()\n return (runId) => {\n if (runId === undefined)\n return MAIN_LANE\n const cached = cache.get(runId)\n if (cached !== undefined)\n return cached\n // Walk the resume chain to its root (cycle-guarded); the root decides.\n let lane = runId\n let cur: string | undefined = runId\n const seen = new Set<string>()\n while (cur !== undefined && !seen.has(cur)) {\n seen.add(cur)\n const rec = byId.get(cur)\n if (!rec)\n break // unknown run: isolate at the deepest known link\n lane = cur\n if ((rec.depth ?? 0) === 0) {\n lane = MAIN_LANE\n break\n }\n cur = rec.resumedFromRunId\n }\n cache.set(runId, lane)\n return lane\n }\n}\n\nfunction generateId(): string {\n return `ses_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`\n}\n\nfunction isSafeExtensionStateKey(key: string): boolean {\n return typeof key === 'string' && key.length > 0 && !DANGEROUS_RECORD_KEYS.has(key)\n}\n\nfunction jsonClone<T extends JsonValue>(value: T): T {\n return JSON.parse(JSON.stringify(value)) as T\n}\n\nfunction assertSafeExtensionStateKey(key: string, label: string): void {\n if (!isSafeExtensionStateKey(key))\n throw new Error(`Invalid extension session state ${label}.`)\n}\n\nfunction cloneExtensionStateRoot(value: unknown): Record<string, Record<string, JsonValue>> {\n return clonePlainRecord(value) as Record<string, Record<string, JsonValue>>\n}\n\nfunction clonePlainRecord(value: unknown): Record<string, JsonValue> {\n if (!value || typeof value !== 'object' || Array.isArray(value))\n return {}\n const out: Record<string, JsonValue> = {}\n for (const [key, child] of Object.entries(value as Record<string, unknown>)) {\n if (!isSafeExtensionStateKey(key))\n continue\n if (isJsonValue(child))\n out[key] = child\n }\n return out\n}\n\nfunction isJsonValue(value: unknown, depth = 0): value is JsonValue {\n if (value === null)\n return true\n if (typeof value === 'string' || typeof value === 'boolean')\n return true\n if (typeof value === 'number')\n return Number.isFinite(value)\n if (typeof value !== 'object')\n return false\n if (depth >= 8)\n return false\n if (Array.isArray(value))\n return value.every(item => isJsonValue(item, depth + 1))\n return Object.entries(value).every(([key, child]) =>\n isSafeExtensionStateKey(key) && isJsonValue(child, depth + 1),\n )\n}\n\nfunction assertJsonBounded(value: JsonValue, maxBytes: number, label: string): void {\n const json = JSON.stringify(value)\n if (json === undefined)\n throw new Error(`${label} must be JSON serializable.`)\n if (Buffer.byteLength(json) > maxBytes)\n throw new Error(`${label} exceeds ${maxBytes} bytes.`)\n}\n"],"mappings":"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"}