zidane 7.0.30 → 7.0.32

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 (97) hide show
  1. package/README.md +2 -2
  2. package/dist/{acp-Cu879kus.js → acp-bBDF-dhy.js} +2 -2
  3. package/dist/{acp-Cu879kus.js.map → acp-bBDF-dhy.js.map} +1 -1
  4. package/dist/acp-cli.js +3 -3
  5. package/dist/acp.d.ts +1 -1
  6. package/dist/acp.js +1 -1
  7. package/dist/{agent-BipX4BBj.js → agent-BCG9JcZl.js} +199 -31
  8. package/dist/agent-BCG9JcZl.js.map +1 -0
  9. package/dist/agent.d.ts +1 -1
  10. package/dist/agent.js +1 -1
  11. package/dist/{approvals-staFZZ5o.js → approvals-DkwGBMZ9.js} +372 -19
  12. package/dist/approvals-DkwGBMZ9.js.map +1 -0
  13. package/dist/{auth-D4MpTfvF.js → auth-D0BHBb02.js} +2 -2
  14. package/dist/{auth-D4MpTfvF.js.map → auth-D0BHBb02.js.map} +1 -1
  15. package/dist/chat/pure.d.ts +3 -3
  16. package/dist/chat/pure.js +1 -1
  17. package/dist/chat.d.ts +6 -6
  18. package/dist/chat.js +6 -6
  19. package/dist/{context-transport-BqYwzJYE.js → context-transport-T_6AYIvy.js} +2 -2
  20. package/dist/{context-transport-BqYwzJYE.js.map → context-transport-T_6AYIvy.js.map} +1 -1
  21. package/dist/contexts/daytona.d.ts +1 -1
  22. package/dist/contexts/e2b.d.ts +1 -1
  23. package/dist/daemon-cli.js +1 -1
  24. package/dist/eval.d.ts +1 -1
  25. package/dist/extensions.d.ts +2 -2
  26. package/dist/extensions.js +2 -2
  27. package/dist/headless.d.ts +1 -1
  28. package/dist/headless.js +1 -1
  29. package/dist/{index-C0y2tSgk.d.ts → index-CCB05-JZ.d.ts} +104 -10
  30. package/dist/index-CCB05-JZ.d.ts.map +1 -0
  31. package/dist/{index-CnXKeZ8p.d.ts → index-Ogi2CeHf.d.ts} +179 -6
  32. package/dist/index-Ogi2CeHf.d.ts.map +1 -0
  33. package/dist/index.d.ts +2 -2
  34. package/dist/index.js +3 -3
  35. package/dist/{ipc-CbkrpfkI.js → ipc-C10lnS-a.js} +2 -2
  36. package/dist/{ipc-CbkrpfkI.js.map → ipc-C10lnS-a.js.map} +1 -1
  37. package/dist/ipc.d.ts +5 -5
  38. package/dist/ipc.js +3 -3
  39. package/dist/{mcp-BIWa57eY.js → mcp-CgNd-f3F.js} +3 -3
  40. package/dist/{mcp-BIWa57eY.js.map → mcp-CgNd-f3F.js.map} +1 -1
  41. package/dist/mcp.d.ts +1 -1
  42. package/dist/mcp.js +1 -1
  43. package/dist/output/stream-json.d.ts +1 -1
  44. package/dist/output/terminal.d.ts +1 -1
  45. package/dist/plugins.d.ts +1 -1
  46. package/dist/presets.d.ts +1 -1
  47. package/dist/presets.js +1 -1
  48. package/dist/providers/anthropic.d.ts +1 -1
  49. package/dist/providers/arcee.d.ts +1 -1
  50. package/dist/providers/baseten.d.ts +1 -1
  51. package/dist/providers/cerebras.d.ts +1 -1
  52. package/dist/providers/local.d.ts +1 -1
  53. package/dist/providers/openai-compat.d.ts +1 -1
  54. package/dist/providers/openai.d.ts +1 -1
  55. package/dist/providers/openrouter.d.ts +1 -1
  56. package/dist/providers/xai.d.ts +1 -1
  57. package/dist/providers.d.ts +1 -1
  58. package/dist/restate.d.ts +1 -1
  59. package/dist/restate.js +1 -1
  60. package/dist/{server-GJ2gNRt_.js → server--mwNA7oC.js} +7 -7
  61. package/dist/{server-GJ2gNRt_.js.map → server--mwNA7oC.js.map} +1 -1
  62. package/dist/session/sqlite.d.ts +1 -1
  63. package/dist/session.d.ts +1 -1
  64. package/dist/skills.d.ts +1 -1
  65. package/dist/{src-DzzhE2YO.js → src-CjkY6fBj.js} +2 -2
  66. package/dist/{src-DzzhE2YO.js.map → src-CjkY6fBj.js.map} +1 -1
  67. package/dist/{stdio-loader-CI4sPFve.js → stdio-loader-Bbx19dMZ.js} +2 -2
  68. package/dist/{stdio-loader-CI4sPFve.js.map → stdio-loader-Bbx19dMZ.js.map} +1 -1
  69. package/dist/{tool-formatters-bbgUM3zI.d.ts → tool-formatters-Bu4repDJ.d.ts} +2 -2
  70. package/dist/{tool-formatters-bbgUM3zI.d.ts.map → tool-formatters-Bu4repDJ.d.ts.map} +1 -1
  71. package/dist/tools/fetch-url.d.ts +1 -1
  72. package/dist/tools/web-search.d.ts +1 -1
  73. package/dist/tools.d.ts +1 -1
  74. package/dist/tools.js +1 -1
  75. package/dist/{transcript-anchors-BHyrChoO.d.ts → transcript-anchors-CJCxyVE-.d.ts} +8 -2
  76. package/dist/transcript-anchors-CJCxyVE-.d.ts.map +1 -0
  77. package/dist/tui.d.ts +3 -3
  78. package/dist/tui.d.ts.map +1 -1
  79. package/dist/tui.js +69 -14
  80. package/dist/tui.js.map +1 -1
  81. package/dist/{turn-operations-BgqpLlOG.js → turn-operations-HX3g3XIc.js} +11 -1
  82. package/dist/{turn-operations-BgqpLlOG.js.map → turn-operations-HX3g3XIc.js.map} +1 -1
  83. package/dist/{turn-operations-ZP5-2OWi.d.ts → turn-operations-qcv9UFcL.d.ts} +3 -3
  84. package/dist/{turn-operations-ZP5-2OWi.d.ts.map → turn-operations-qcv9UFcL.d.ts.map} +1 -1
  85. package/dist/types-CWgENVLY.js.map +1 -1
  86. package/dist/types.d.ts +2 -2
  87. package/docs/ARCHITECTURE.md +11 -2
  88. package/docs/CHAT.md +1 -0
  89. package/docs/INTERACTIONS.md +14 -2
  90. package/docs/SKILL.md +3 -1
  91. package/docs/TUI.md +5 -3
  92. package/package.json +1 -1
  93. package/dist/agent-BipX4BBj.js.map +0 -1
  94. package/dist/approvals-staFZZ5o.js.map +0 -1
  95. package/dist/index-C0y2tSgk.d.ts.map +0 -1
  96. package/dist/index-CnXKeZ8p.d.ts.map +0 -1
  97. package/dist/transcript-anchors-BHyrChoO.d.ts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"src-DzzhE2YO.js","names":["prefixed"],"sources":["../src/behaviors/local-reliability.ts","../src/chat/commands.ts","../src/zod.ts","../src/chat/generate-title.ts","../src/chat/generate-data.ts","../src/chat/inline-autocomplete.ts","../src/extension-authoring.ts","../src/mcp/oauth-callback.ts","../src/mcp/login.ts","../src/metrics.ts","../src/mutation-validation.ts","../src/tracing.ts"],"sourcesContent":["import type { AgentBehavior } from '../types'\n\nconst DEFAULT_LOCAL_RELIABILITY: AgentBehavior = {\n maxConcurrentTools: 3,\n readFileDefaults: {\n limit: 400,\n maxBytes: 64 * 1024,\n },\n toolOutputBudget: 32 * 1024,\n textToolCallRescue: { mode: 'steer' },\n thinkingOverrunPolicy: {\n mode: 'steer-next-turn',\n thresholdTokens: 2048,\n },\n}\n\n/**\n * Opt-in behavior overlay for brittle/local OpenAI-compatible endpoints.\n * Returns plain `AgentBehavior` so hosts can spread it before or after their\n * own overrides; no built-in profile uses this helper by default.\n */\nexport function localReliabilityBehavior(overrides: AgentBehavior = {}): AgentBehavior {\n return {\n ...DEFAULT_LOCAL_RELIABILITY,\n ...overrides,\n readFileDefaults: {\n ...DEFAULT_LOCAL_RELIABILITY.readFileDefaults,\n ...(overrides.readFileDefaults ?? {}),\n },\n textToolCallRescue: overrides.textToolCallRescue ?? DEFAULT_LOCAL_RELIABILITY.textToolCallRescue,\n thinkingOverrunPolicy: overrides.thinkingOverrunPolicy ?? DEFAULT_LOCAL_RELIABILITY.thinkingOverrunPolicy,\n }\n}\n","/**\n * `/` command model — an extension-contributed workflow invoked by typing\n * `/<name> [args]` as the whole prompt. A command is an ordered list of\n * handler **steps**; a bare `run` is sugar for a single step. Steps share a\n * mutable `data` bag, emit local output blocks (rendered in the transcript,\n * never sent to the model), and can drive the agent via `ctx.runAgent` so a\n * workflow can interact with the LLM mid-flight.\n *\n * This module is renderer-free and node-free: the types are the authoring\n * surface extensions compile against, and `executeCommand` / the parse\n * helper are pure so they're unit-testable without a TUI. The host supplies\n * the side-effecting capabilities on the step context: `emit` (render +\n * persist local output), `runAgent` (interactive sub-run), `generateData`\n * (silent schema-pinned JSON), and the optional `session` (read-only view)\n * and `interaction` (confirm / forms / plan approval) surfaces.\n */\n\nimport type { Logger } from '../logger'\nimport type { SessionRun } from '../session'\nimport type { JsonValue, SessionTurn } from '../types'\nimport type { InferSchemaOutput, SchemaInput, StandardSchemaLike } from '../zod'\nimport type { AnswerValue, PlanPayload, PlanResponse, QuestionPayload } from './interactions'\nimport type { StreamEvent } from './types'\n\n/**\n * A block of local output a step renders into the chat.\n *\n * Plain blocks (`text` only) render as full markdown, both live and on\n * reload — use them for the output the user should read in full (e.g. a\n * final commit message). Blocks with `detail` render as a collapsed\n * `command-detail` row instead (dim `▸ label` + line-capped body with\n * \"… N more lines\") — use them for bulky supporting output (diffs, logs,\n * listings) that shouldn't drown the transcript.\n */\nexport interface CommandOutputBlock {\n /**\n * The block body — full markdown, the detail body, or (for `custom`\n * blocks) the MANDATORY fallback markdown rendered wherever the\n * renderer isn't available: extension uninstalled or disabled,\n * headless host, GUI, renderer threw. The fallback is what persists\n * as the block's output, so pre-custom hosts render it natively.\n */\n text: string\n /**\n * Render as a collapsed detail row (dim `▸ label` + line-capped body)\n * instead of full markdown. `syntax: 'diff'` additionally colors\n * `+N` / `−N` count tokens (added green / deleted red) in both label\n * and body — for diff-stat-style output.\n */\n detail?: { label?: string, syntax?: 'diff' }\n /**\n * Render through an extension-registered custom renderer\n * (`ctx.tui.registerRenderer`). `renderer` must be the NAMESPACED id\n * (`<extension>/<id>`) — `ctx.appendNote` auto-prefixes bare ids for\n * its own extension; command `emit` requires the full form. `data`\n * must be JSON-safe (it persists in the session). Takes precedence\n * over `detail` when both are set.\n */\n custom?: { renderer: string, data?: JsonValue }\n}\n\n/**\n * User-interaction surface for commands — the same prompt-slot primitives\n * the `ask_user` / `present_plan` TOOLS render, but WITHOUT the tools: a\n * command enqueues directly into the host's interaction queue. No tool\n * calls, no turns, no model involvement — the answer routes straight back\n * to the awaiting step. Every method rejects on cancel/esc, which the\n * executor classifies as a quiet abort.\n */\nexport interface CommandInteraction {\n /**\n * One-tap yes/no — sugar over a single `confirm`-type question:\n *\n * if (await ctx.interaction.confirm('Push to origin/main?')) …\n */\n confirm: (prompt: string, opts?: {\n /** Optional context shown above the question (Markdown). */\n intro?: string\n /** Label for the affirmative option. Default `'yes'`. */\n affirmLabel?: string\n /** Label for the negative option. Default `'no'`. */\n denyLabel?: string\n }) => Promise<boolean>\n /**\n * Show a form (text / textarea / select / confirm questions — identical\n * UI to the ask_user tool) and resolve with the answers keyed by\n * `Question.id` (`string` for text/select, `boolean` for confirm,\n * `string[]` of choice ids for `multi: true` selects).\n */\n askUser: (form: QuestionPayload) => Promise<Readonly<Record<string, AnswerValue>>>\n /**\n * Present a plan for the full approval ceremony (approve / reject /\n * revise + optional comment — identical UI to present_plan). For\n * lightweight gating prefer {@link confirm}.\n */\n presentPlan: (plan: PlanPayload) => Promise<PlanResponse>\n}\n\n/**\n * Read-only view over the session a command runs inside — live getters, so\n * turns appended mid-workflow (e.g. by a `runAgent` sub-run) are visible on\n * the next read. Deliberately projection-only: no mutators, so commands\n * can't bypass the display-only `command`-turn contract or the wire guard\n * (`isWireTurn`). Persisting output goes through `emit`; driving the\n * conversation goes through `runAgent`.\n */\nexport interface CommandSessionView {\n readonly id: string\n /** Full persisted history, including display-only `command` turns. */\n readonly turns: readonly SessionTurn[]\n /** Run records (status, usage, cost, `origin`, …), newest last. */\n readonly runs: readonly SessionRun[]\n readonly metadata: Readonly<Record<string, unknown>>\n}\n\n/** Options for `ctx.generateData` — a silent, schema-pinned model call. */\nexport interface CommandGenerateDataOptions {\n /** Instruction describing the value to produce. */\n prompt: string\n /**\n * Shape of the result — a zod v4 schema (return type inferred, no casts)\n * or a plain JSON Schema object (name the type via the generic).\n */\n schema: SchemaInput\n /** Optional system prompt override. */\n system?: string\n /**\n * Fold a compacted transcript of the recent conversation into the call as\n * context. Default false — most workflow values derive from data the\n * command already gathered itself.\n */\n includeConversation?: boolean\n /**\n * Model id to run the call on (must exist in the host's model catalog —\n * see `ctx.runtime.models()` on the extension context). Omit to use the\n * session's active model.\n */\n model?: string\n}\n\n/** One task for {@link CommandStepContext.spawnAgents} — an isolated child agent run. */\nexport interface CommandSpawnTask {\n /** Task prompt for the child agent. Brief it like a colleague — it has no conversation context. */\n prompt: string\n /**\n * Model id from the host catalog (`ctx.runtime.models()` on the extension\n * context). Omit to inherit the session's active model. Unknown ids reject\n * the task (`status: 'rejected'`) without running anything.\n */\n model?: string\n /** System prompt override for the child. Inherits the agent's system prompt when omitted. */\n system?: string\n /** Short display label carried back on the result (defaults to the child id). */\n label?: string\n}\n\n/** Outcome of one {@link CommandSpawnTask}. */\nexport interface CommandSpawnResult {\n /** The task's `label` (or the child id when none was given). */\n label: string\n /** Model the task asked for, when pinned. */\n model?: string\n /** Terminal status. `'rejected'` = refused before running (unknown model, caps). */\n status: 'completed' | 'aborted' | 'timeout' | 'error' | 'rejected'\n /** The child's final assistant text. Empty unless `status === 'completed'`. */\n text: string\n /** Full formatted result / diagnostic (always set; useful for non-completed statuses). */\n message: string\n}\n\n/** Result of driving the agent from within a step via `ctx.runAgent`. */\nexport interface CommandAgentResult {\n /** The assistant's final text for this sub-run. */\n text: string\n}\n\n/**\n * A prompt attachment forwarded into a command invocation — captured when\n * the user submits `/cmd …` with pasted images/files attached. `data` is\n * base64 for EVERY media type (including text/plain): one encoding keeps\n * the contract lossless for binary and host-agnostic, at the cost of a\n * trivial decode for text consumers.\n */\nexport interface CommandAttachment {\n /** Display name from the capture surface (e.g. `clipboard.png`). */\n readonly name: string\n /** MIME type (e.g. `image/png`, `text/plain`). */\n readonly mediaType: string\n /** Base64-encoded content. */\n readonly data: string\n}\n\n/** Capabilities + inputs handed to every step. */\nexport interface CommandStepContext {\n /** Raw text after `/<name>` (trimmed of the leading space). */\n readonly args: string\n /** `args` split on whitespace, empties removed. */\n readonly argv: readonly string[]\n /**\n * Attachments submitted with the invocation (images/files pasted into the\n * prompt), when the host captures them. ABSENT — not empty — on hosts\n * without an attachment surface; guard with `ctx.attachments?.length`.\n */\n readonly attachments?: readonly CommandAttachment[]\n /**\n * The command token the user typed (lowercased) — the alias when one\n * was used, else the canonical name. Optional: hosts that dispatch\n * programmatically may not have one. See {@link CommandInvocation.invokedAs}.\n */\n readonly invokedAs?: string\n /** Mutable bag threaded across steps; a step's returned `data` is merged in. */\n readonly data: Record<string, unknown>\n /** Render + persist a local output block. */\n readonly emit: (block: CommandOutputBlock) => void\n /**\n * Drive one agent run to completion and return its result. The prompt is\n * injected as a real user turn (model-visible, unchanged on the wire) and\n * the response streams into the transcript. Serialized with the host's run\n * slot, so a workflow can interleave deterministic steps with LLM\n * interaction. Any output `emit`ted before this call is flushed first,\n * preserving transcript order.\n *\n * `opts.display` replaces the full-prompt echo in the transcript with a\n * short line (the full prompt still renders beneath it as a collapsed\n * detail row, and still reaches the model verbatim) — pass it when the\n * prompt embeds bulky material like a diff.\n */\n readonly runAgent: (prompt: string, opts?: { readonly display?: string }) => Promise<CommandAgentResult>\n /**\n * Generate schema-conforming JSON with ONE silent model call — no session\n * turns, no transcript output, nothing added to the conversation (the\n * workflow presents results itself via `emit`). Reach for this when a\n * step needs a VALUE (a commit message, a classification, extracted\n * fields); reach for `runAgent` when it needs an interactive sub-run\n * with tools and streaming.\n */\n readonly generateData: {\n /** zod v4 schema → the result type is inferred from the schema. */\n <S extends StandardSchemaLike>(opts: Omit<CommandGenerateDataOptions, 'schema'> & { schema: S }): Promise<InferSchemaOutput<S>>\n /** Plain JSON Schema → name the result type explicitly. */\n <T = unknown>(opts: CommandGenerateDataOptions): Promise<T>\n }\n /** Logger with a `command=<name>` binding, routed to the host's log funnel. */\n readonly logger: Logger\n /** Aborts when the user presses esc during execution. */\n readonly signal: AbortSignal\n /** Working directory the command runs against. */\n readonly cwd: string\n /**\n * The surrounding session, when the host runs commands inside one (the\n * TUI always does). ABSENT in session-less contexts — a command that\n * reads it must guard (`if (!ctx.session) …`) or degrade gracefully.\n */\n readonly session?: CommandSessionView\n /**\n * User interaction (confirm / forms / plan approval), when the host has\n * an interactive surface (the TUI always does). ABSENT on UI-less hosts\n * — guard (`if (!ctx.interaction) …`) and degrade gracefully.\n */\n readonly interaction?: CommandInteraction\n /**\n * Run one or more ISOLATED child agents — each with its own context\n * window and the full tool set, optionally pinned to a different model\n * — and resolve when the whole batch settles. Tasks run in parallel\n * (bounded by `opts.maxConcurrent`, default 3). Children stream and\n * persist into the transcript exactly like `spawn`-tool subagents, so\n * the user sees their activity live and on reload; their context stays\n * isolated from the conversation.\n *\n * Complements {@link runAgent}: `runAgent` drives THE conversation\n * (model-visible turns, active model, serialized); `spawnAgents` fans\n * work out to throwaway workers. Results come back to the command —\n * persist what matters via {@link emit} (user-facing) and\n * {@link appendContext} (model-facing).\n *\n * ABSENT on hosts without an agent (guard like `session`).\n */\n readonly spawnAgents?: (\n tasks: readonly CommandSpawnTask[],\n opts?: { readonly maxConcurrent?: number },\n ) => Promise<readonly CommandSpawnResult[]>\n /**\n * Persist a compact MODEL-VISIBLE context note — a real user-role turn,\n * sent on the wire unchanged from the next run onward, without\n * triggering a run now. This is how a command's outcome becomes part of\n * the conversation: the next user prompt sees what the workflow\n * found/did without replaying subagent transcripts.\n *\n * `opts.display` swaps the transcript echo for a short one-line marker —\n * live AND on reload (the marker persists on the turn; replay renders it\n * with the full note as a collapsed detail), so note bodies the command\n * also emitted never display twice. Keep notes compact regardless — they\n * occupy context for the rest of the conversation. ABSENT on session-less\n * hosts.\n */\n readonly appendContext?: (text: string, opts?: { readonly display?: string }) => Promise<void>\n}\n\n/** Optional per-step return — `data` is merged into the shared bag for later steps. */\nexport interface CommandStepResult {\n data?: Record<string, unknown>\n}\n\nexport type CommandStepFn = (\n ctx: CommandStepContext,\n) => void | CommandStepResult | Promise<void | CommandStepResult>\n\nexport interface CommandStep {\n run: CommandStepFn\n}\n\n/** One argument suggestion returned by {@link Command.completeArgs}. */\nexport interface CommandArgSuggestion {\n /** Text inserted after `/<name> ` on accept. */\n value: string\n /** Display label. Defaults to `value`. */\n label?: string\n description?: string\n}\n\n/** An extension-contributed `/` command. Provide EITHER `run` (one step) or `steps`. */\nexport interface Command {\n /** Invoked as `/<name>`. Lowercase, no whitespace. */\n name: string\n /**\n * Alternate invocation names (same constraints as `name`). `/alias`\n * dispatches to this command; the popover shows the canonical name and\n * lists aliases in the description. Aliases participate in the same\n * first-registration-wins dedupe as names.\n */\n aliases?: readonly string[]\n /**\n * Grouping key for completion — the registry stamps the source\n * extension's name here, so typing `/<extension>` in the popover lists\n * every command that extension contributes. Not an invocation name:\n * `parseCommandInvocation` ignores it.\n */\n group?: string\n /** One-line summary shown in the completion popover. */\n description: string\n /** Single-step sugar. */\n run?: CommandStepFn\n /** Explicit ordered pipeline. Takes precedence over `run` when both are set. */\n steps?: readonly CommandStep[]\n /**\n * Argument completion — called while the user types `/<name> <prefix>▌`\n * with everything after the command name (may itself contain spaces).\n * Return suggestions to show in the `/` popover; return `[]`/`undefined`\n * for none. Sync or async.\n */\n completeArgs?: (argsPrefix: string) => readonly CommandArgSuggestion[] | undefined | Promise<readonly CommandArgSuggestion[] | undefined>\n}\n\n/**\n * Persisted form of output blocks inside a display-only `command` turn:\n * plain blocks → `text` content; `detail` blocks → `tool_result` content\n * with the label/syntax carried in namespaced metadata (rebuilt into the\n * collapsed rendering on reload). Shared by the command emit flush and\n * `ctx.appendNote` so both persist identically.\n */\nexport function commandBlocksToContent(blocks: readonly CommandOutputBlock[]): SessionTurn['content'] {\n return blocks.map((block) => {\n if (!block.detail && !block.custom)\n return { type: 'text' as const, text: block.text }\n // Custom blocks persist with the FALLBACK as the output — an older\n // zidane (or any host without the renderer) reads this turn as a\n // plain detail block and renders the fallback natively. The renderer\n // binding rides namespaced metadata, exactly like detail label/syntax.\n const commandMeta: Record<string, JsonValue> = block.custom\n ? { custom: { renderer: block.custom.renderer, ...(block.custom.data !== undefined ? { data: block.custom.data } : {}) } }\n : {\n ...(block.detail?.label ? { label: block.detail.label } : {}),\n ...(block.detail?.syntax ? { syntax: block.detail.syntax } : {}),\n }\n return {\n type: 'tool_result' as const,\n callId: `command_detail_${crypto.randomUUID()}`,\n output: block.text,\n ...(Object.keys(commandMeta).length > 0 ? { metadata: { command: commandMeta } } : {}),\n }\n })\n}\n\n/**\n * Map ONE command-output block to its LIVE transcript event — the\n * in-memory mirror of {@link commandBlocksToContent}'s persisted shape\n * (whose replay side lives in `eventsFromTurns`). One definition shared\n * by the command emit flush and the `ctx.appendNote` host impl so live\n * rendering can never drift from reloaded rendering — the exact drift\n * that shipped `custom` blocks rendering only after a reload.\n *\n * Precedence matches the persisted side: `custom` > `detail` > markdown.\n */\nexport function commandBlockToEvent(block: CommandOutputBlock): StreamEvent {\n if (block.custom) {\n return {\n kind: 'custom',\n text: block.text,\n custom: {\n renderer: block.custom.renderer,\n ...(block.custom.data !== undefined ? { data: block.custom.data } : {}),\n },\n }\n }\n if (block.detail) {\n return {\n kind: 'command-detail',\n text: block.text,\n ...(block.detail.label ? { label: block.detail.label } : {}),\n ...(block.detail.syntax ? { syntax: block.detail.syntax } : {}),\n }\n }\n return { kind: 'markdown', text: block.text, streaming: false }\n}\n\n/** Normalize a command to its ordered step list (`steps` wins over `run`). */\nexport function commandSteps(command: Command): readonly CommandStep[] {\n if (command.steps && command.steps.length > 0)\n return command.steps\n if (command.run)\n return [{ run: command.run }]\n return []\n}\n\n/** Parsed `/<name> [args]` invocation. */\nexport interface CommandInvocation {\n name: string\n /**\n * The token the user actually typed (lowercased, no slash) — `name`\n * when the canonical name was used, the alias otherwise. Lets commands\n * keep legacy per-alias behavior (e.g. bare `/fix-comments` defaulting\n * to comments mode after the command was folded into `/fix`).\n */\n invokedAs: string\n /** Text after the name, trimmed. */\n args: string\n /** `args` split on whitespace. */\n argv: string[]\n}\n\n/**\n * Parse a prompt as a command invocation, but only when the command token is\n * the LEADING token of the whole prompt and names a known command. This is\n * what keeps mid-prompt `/skill` mentions routed to the skills provider:\n * `\"summarize /research\"` is not a command, `\"/research foo\"` is.\n *\n * `known` is matched case-insensitively; entries may be bare names or\n * `{ name, aliases }` records — an alias hit canonicalizes to the entry's\n * `name` (registered casing), so dispatch-by-name downstream just works.\n */\nexport function parseCommandInvocation(\n prompt: string,\n known: readonly (string | Pick<Command, 'name' | 'aliases'>)[],\n): CommandInvocation | null {\n const body = prompt.trimStart()\n if (body[0] !== '/')\n return null\n // First token = the command name; the remainder (after the first run of\n // whitespace) is the args. Linear scan — no backtracking-prone regex.\n const afterSlash = body.slice(1)\n const wsIdx = afterSlash.search(/\\s/)\n const typed = (wsIdx === -1 ? afterSlash : afterSlash.slice(0, wsIdx)).toLowerCase()\n if (typed.length === 0)\n return null\n let canonical: string | undefined\n for (const entry of known) {\n const name = typeof entry === 'string' ? entry : entry.name\n if (name.toLowerCase() === typed) {\n canonical = name\n break\n }\n if (typeof entry !== 'string' && entry.aliases?.some(a => a.toLowerCase() === typed)) {\n canonical = name\n break\n }\n }\n if (canonical === undefined)\n return null\n const args = (wsIdx === -1 ? '' : afterSlash.slice(wsIdx + 1)).trim()\n return { name: canonical, invokedAs: typed, args, argv: args.length > 0 ? args.split(/\\s+/) : [] }\n}\n\n/** Outcome of a command run. `error` is set when a step threw (pipeline stops). */\nexport interface CommandExecution {\n ok: boolean\n /** Number of steps that ran (including the one that threw). */\n ranSteps: number\n error?: unknown\n /** True when the run stopped because `ctx.signal` aborted. */\n aborted?: boolean\n}\n\n/**\n * Run a command's steps in order, threading `ctx.data`. Stops at the first\n * step that throws (error isolated + returned, not rethrown) or when the\n * signal aborts. Pure orchestration — all I/O lives in the host-supplied\n * `ctx` capabilities.\n */\nexport async function executeCommand(\n command: Command,\n ctx: CommandStepContext,\n): Promise<CommandExecution> {\n const steps = commandSteps(command)\n let ranSteps = 0\n for (const step of steps) {\n if (ctx.signal.aborted)\n return { ok: false, ranSteps, aborted: true }\n ranSteps++\n try {\n const result = await step.run(ctx)\n if (result && result.data)\n Object.assign(ctx.data, result.data)\n }\n catch (error) {\n // An esc mid-step usually surfaces as a thrown AbortError from whatever\n // I/O the handler had in flight. Classify by the signal so hosts can\n // stay quiet on user-initiated aborts instead of painting an error.\n if (ctx.signal.aborted)\n return { ok: false, ranSteps, aborted: true }\n return { ok: false, ranSteps, error }\n }\n }\n return { ok: true, ranSteps }\n}\n","/**\n * Zod v4 integration helpers.\n *\n * Zod is an OPTIONAL peer dependency, so this module never imports it at the\n * top level. Schema-accepting APIs (`ctx.generateData`, `generateStructuredData`)\n * take either a plain JSON Schema object or any Standard-Schema-carrying\n * value (https://standardschema.dev — zod v4 implements it). Zod schemas are\n * detected structurally by vendor and converted with a lazy\n * `import('zod')` — safe because whoever constructed the schema necessarily\n * has zod installed. Result types are inferred from the standard-schema\n * `types.output` slot, so callers get full typing without a single cast:\n *\n * const CommitMessage = z.object({ subject: z.string(), body: z.string().optional() })\n * const msg = await ctx.generateData({ prompt, schema: CommitMessage })\n * // ^? { subject: string, body?: string }\n */\n\n/**\n * Structural stand-in for a Standard Schema (zod v4 et al.) — enough surface\n * to detect the vendor and infer the output type WITHOUT importing zod's\n * types into our public API.\n */\nexport interface StandardSchemaLike<Output = unknown> {\n readonly '~standard': {\n readonly version: number\n readonly vendor: string\n /** Type-level only (undefined at runtime) — carries the inferred output type. */\n readonly types?: { readonly output: Output } | undefined\n }\n}\n\n/** Output type carried by a {@link StandardSchemaLike}; `unknown` for plain JSON Schema. */\nexport type InferSchemaOutput<S> = S extends StandardSchemaLike<infer O> ? O : unknown\n\n/** A schema-accepting input: plain JSON Schema, or a Standard Schema (e.g. zod v4). */\nexport type SchemaInput = Record<string, unknown> | StandardSchemaLike\n\nexport function isStandardSchema(value: unknown): value is StandardSchemaLike {\n if (typeof value !== 'object' || value === null)\n return false\n const marker = (value as Record<string, unknown>)['~standard']\n return typeof marker === 'object' && marker !== null\n && typeof (marker as Record<string, unknown>).vendor === 'string'\n}\n\n/**\n * Resolve a {@link SchemaInput} to the plain JSON Schema object providers\n * expect. Zod schemas convert via the lazily-imported `z.toJSONSchema`\n * (v4 native); other Standard Schema vendors are rejected with a clear\n * error — conversion is vendor-specific and only zod is wired today.\n */\nexport async function resolveSchemaInput(schema: SchemaInput): Promise<Record<string, unknown>> {\n if (!isStandardSchema(schema))\n return schema\n const vendor = schema['~standard'].vendor\n if (vendor !== 'zod') {\n throw new Error(\n `unsupported schema vendor \"${vendor}\" — pass a zod v4 schema or a plain JSON Schema object`,\n )\n }\n // Lazy + guarded: zod is an optional peer, but a zod schema in hand means\n // the caller's process has zod — this import only fails on truly broken\n // installs, and then with an actionable message.\n let toJSONSchema: (schema: unknown) => Record<string, unknown>\n try {\n const zod = await import('zod')\n toJSONSchema = zod.z.toJSONSchema as typeof toJSONSchema\n }\n catch {\n throw new Error('a zod schema was passed but the \"zod\" package could not be imported — install zod >= 4')\n }\n return zodToJsonSchema(toJSONSchema(schema))\n}\n\n/**\n * Normalize a JSON Schema (e.g. from zod v4's z.toJSONSchema()) for use\n * as a ToolSpec.inputSchema.\n *\n * Strips the $schema key that some providers reject.\n */\nexport function zodToJsonSchema(jsonSchema: Record<string, unknown>): Record<string, unknown> {\n const { $schema, ...rest } = jsonSchema\n return rest\n}\n","/**\n * One-shot title generation for an existing session — feeds the opening\n * user turn plus the last N turns to the provider as a LABELING task and\n * returns the cleaned-up result.\n *\n * Provider-agnostic: works with anything implementing the renderer-\n * shared `Provider` interface. No tools, no session mutation, no\n * caching — this is a cheap, isolated completion that should be safe to\n * fire even mid-session without touching the live agent loop.\n */\n\nimport type { Provider } from '../providers'\nimport type { SessionTurn } from '../types'\n\n/** Hard cap on the result length. Anything longer is truncated client-side. */\nconst TITLE_MAX_CHARS = 60\n\n/** Default turn count fed to the model. 10 covers most exchanges without bloat. */\nconst DEFAULT_MAX_TURNS = 10\n\n/** Tokens budgeted for the model's reply. 64 fits any sane title with headroom. */\nconst TITLE_RESPONSE_MAX_TOKENS = 64\n\n/** Per-turn body cap when assembling the prompt — keeps the request payload tight. */\nconst PROMPT_TURN_CHAR_MAX = 2000\n\n/**\n * System prompt for the titling call.\n *\n * Written as a LABELING task, not a chat turn: handed a bare \"generate a\n * title for this conversation\" instruction with the transcript inline, a\n * cheap model reads the trailing assistant message and just keeps\n * talking — observed in the wild as a session titled `I don't have a\n * \"plan tool\" available in this conversation…`, i.e. the model answering\n * the last user turn instead of naming it. The rules below say what the\n * model IS (an annotator, not a participant), and the examples show the\n * shape — including the exact failure case (a transcript whose last turn\n * is a refusal) so the answer-shaped completion has a labeled\n * counter-example to copy instead.\n */\nconst TITLE_SYSTEM_PROMPT = [\n 'You label conversations. Each input is a transcript of a session between a user and a coding agent.',\n 'Name what the session is ABOUT. You are an annotator, never a participant in the conversation.',\n '',\n 'Rules:',\n '- Reply with ONLY the label: no quotes, no markdown, no trailing punctuation, no preamble.',\n `- 3 to 7 words, under ${TITLE_MAX_CHARS} characters, written as a noun phrase or an imperative.`,\n '- Name the user\\'s topic or task using concrete nouns from the transcript: files, tools, features, errors.',\n '- NEVER answer, continue, or comment on the conversation — not even when the last message is a question, a refusal, or an error.',\n '- No first person (\"I\", \"we\"), no \"the user\", and no filler words like conversation, session, chat, request, discussion, help.',\n '',\n 'Examples of a transcript and its label:',\n '- user asks why `bun test` hangs after an upgrade, agent finds a stuck worker → Bun test hangs on stuck worker',\n '- user asks for a --json flag on the CLI → Add JSON output flag to the CLI',\n '- user asks about a plan tool, agent replies that no such tool exists → Missing plan tool in the agent toolset',\n].join('\\n')\n\n/**\n * Openers that mark a model reply rather than a label — the model\n * answering the transcript instead of naming it. Treated as a failed\n * generation (see {@link cleanTitle}) so the auto-titler retries on the\n * next run rather than persisting a sentence as the session name.\n *\n * Deliberately conservative: only phrasings that a real title would\n * never start with. Excluded on purpose because they head perfectly good\n * titles: `no` / `there is` (\"No JSON output in the CLI\"), `ok` / `yes` /\n * `great` (\"OK button never fires\", \"Yes/no confirm regression\").\n *\n * The pronoun cases require FOLLOWING WHITESPACE rather than a word\n * boundary — `\\b` after `i` also matches punctuation, which rejected\n * \"I/O timeout in the docker context\". Contractions are matched\n * explicitly instead, with both the straight and typographic apostrophe\n * (models emit U+2019 freely, and `here's` / `let's` have no bare-word\n * alternative to fall back on).\n */\nconst ANSWER_SHAPED_OPENER\n = /^(?:i\\s|i['’](?:m|ve|ll|d)\\b|we\\s|we['’](?:re|ve|ll)\\b|let['’]s\\b|let\\s+(?:me|us)\\b|sure\\b|certainly\\b|okay\\b|sorry\\b|thanks\\b|unfortunately\\b|here['’]s\\b|here\\s+is\\b|as\\s+an\\b|you\\s+(?:can|should|need)\\b|it\\s+(?:looks|seems|appears)\\b)/i\n\nexport interface GenerateSessionTitleOptions {\n provider: Provider\n /** Model id used for the call. */\n model: string\n /** Conversation history to summarize. Empty → throws synchronously. */\n turns: readonly SessionTurn[]\n /**\n * Cap on how many TRAILING turns to feed the model. Defaults to 10.\n * The session's opening user turn is always included on top when the\n * window cuts it off (see `renderTitleTranscript`).\n */\n maxTurns?: number\n /** Optional cancellation signal — forwarded to the provider's stream. */\n signal?: AbortSignal\n}\n\n/**\n * Drive the provider's `stream()` once and return a concise title for\n * the conversation represented by `turns`. Throws when:\n * - `turns` contains no text-bearing content (nothing to summarize),\n * - the provider stream completes with no output text,\n * - the model answered the transcript instead of labeling it (see\n * {@link cleanTitle}) — a failed generation the caller retries,\n * - `signal` is aborted (rethrown verbatim).\n *\n * Output is trimmed, single-line, with surrounding quotes / trailing\n * punctuation stripped, and truncated to `TITLE_MAX_CHARS`.\n */\nexport async function generateSessionTitle({\n provider,\n model,\n turns,\n maxTurns = DEFAULT_MAX_TURNS,\n signal,\n}: GenerateSessionTitleOptions): Promise<string> {\n const transcript = renderTitleTranscript(turns, maxTurns)\n if (!transcript)\n throw new Error('No text content in the recent turns to summarize.')\n\n // Transcript first, instruction last: the final thing the model reads\n // is \"write the label\", not the assistant's last reply — which is what\n // pulled it into answering when the instruction led. The delimiters\n // mark the transcript as data to annotate rather than a turn to\n // respond to.\n const userPrompt = [\n '<transcript>',\n transcript,\n '</transcript>',\n '',\n 'Write the label for the session in the transcript above. Label only.',\n ].join('\\n')\n\n let text = ''\n const result = await provider.stream(\n {\n model,\n system: TITLE_SYSTEM_PROMPT,\n tools: [],\n messages: [provider.userMessage(userPrompt)],\n maxTokens: TITLE_RESPONSE_MAX_TOKENS,\n ...(signal ? { signal } : {}),\n },\n {\n onText: (delta) => { text += delta },\n },\n )\n // Prefer the streamed-text accumulator (driven by deltas) but fall\n // back to the provider's already-concatenated `result.text` for\n // adapters that batch on the final frame instead of emitting deltas.\n const raw = text.trim().length > 0 ? text : result.text\n return cleanTitle(raw)\n}\n\n/**\n * Decide whether a session is ready for one-shot automatic titling.\n *\n * Returns `true` iff the conversation has had its first exchange — at\n * least two turns, i.e. the opening user message plus the assistant's\n * reply (this is exactly the \"N turns\" count the sessions list shows) —\n * AND no explicit title has been set yet. An existing `metadata.title`\n * (a previous auto-generation, or a manual title from the session-details\n * \"generate title\" action) is never clobbered.\n *\n * Pure + total. The runtime layers its own \"is one already in flight?\"\n * and \"is a provider picked?\" guards on top of this; the predicate only\n * encodes the content-level rule so it can be pinned in tests without a\n * mock provider — same rationale as the exported {@link cleanTitle}.\n */\nexport function shouldAutoGenerateTitle(\n turns: readonly SessionTurn[],\n metadata?: Record<string, unknown>,\n): boolean {\n const existing = typeof metadata?.title === 'string' ? metadata.title.trim() : ''\n if (existing.length > 0)\n return false\n return turns.length >= 2\n}\n\n/**\n * Assemble the transcript fed to the titling model: the session's\n * OPENING user turn (when it falls outside the window) followed by the\n * last `maxTurns` turns, separated by an `…` gap marker.\n *\n * The tail alone is not enough. A session's topic is set by the first\n * thing the user asked; after a dozen turns that message is off the\n * window and the model ends up naming whatever tangent the conversation\n * was on when titling fired — or, worse, the assistant's last reply.\n * Anchoring on the opening intent keeps the label about the session.\n *\n * `maxTurns` keeps its meaning (a cap on TRAILING turns); the anchor is\n * one extra entry on top, and only exists when the window actually cut\n * it off. Returns `''` when nothing in the selection carries text.\n */\nfunction renderTitleTranscript(turns: readonly SessionTurn[], maxTurns: number): string {\n const cap = Math.max(1, maxTurns)\n const tail = renderTurnsForPrompt(turns.slice(-cap))\n const earlier = turns.slice(0, Math.max(0, turns.length - cap))\n const opening = earlier.find(turn => turn.role === 'user' && textBodyOf(turn).length > 0)\n const anchor = opening ? renderTurnsForPrompt([opening]) : ''\n if (anchor && tail)\n return `${anchor}\\n\\n…\\n\\n${tail}`\n return anchor || tail\n}\n\n/**\n * Compact a turn list into a transcript-style prompt:\n *\n * user: …\n * assistant: …\n * user: …\n *\n * Tool calls and tool results are dropped entirely — a cheap title model\n * happily echoes plumbing like `[tool: list_files]` back as \"the title\"\n * when a marker is the only assistant content it sees. The topical text\n * is what a title needs. Per-turn text is clipped at `PROMPT_TURN_CHAR_MAX`\n * so a single huge code dump doesn't blow the budget.\n *\n * Returns an empty string when the slice has no text-bearing content\n * (e.g. a turn list that's only tool plumbing).\n *\n * Exported for reuse by other silent one-shot helpers (`generate-data`'s\n * optional conversation context) so every side-channel inference compacts\n * history the same way.\n */\nexport function renderTurnsForPrompt(turns: readonly SessionTurn[]): string {\n const lines: string[] = []\n for (const turn of turns) {\n const body = textBodyOf(turn)\n if (!body)\n continue\n const role = turn.role === 'assistant' ? 'assistant' : turn.role === 'user' ? 'user' : 'system'\n lines.push(`${role}: ${clip(body, PROMPT_TURN_CHAR_MAX)}`)\n }\n return lines.join('\\n\\n')\n}\n\nfunction textBodyOf(turn: SessionTurn): string {\n const parts: string[] = []\n for (const block of turn.content) {\n if (block.type === 'text' && block.text.trim())\n parts.push(block.text.trim())\n }\n return parts.join(' ')\n}\n\n/** Strip one layer of matched surrounding quotes (`\"foo\"`, `'foo'`, `` `foo` ``, `“foo”`). */\nfunction unwrapQuotes(s: string): string {\n if (s.length < 2)\n return s\n const first = s.charAt(0)\n const last = s.charAt(s.length - 1)\n const matched = (first === '\"' && last === '\"')\n || (first === '\\'' && last === '\\'')\n || (first === '`' && last === '`')\n || (first === '“' && last === '”')\n return matched ? s.slice(1, -1).trim() : s\n}\n\n/**\n * Normalize a model-generated title into the shape we want to persist:\n *\n * - Collapse internal whitespace + take the first line only (some\n * models emit \"Title: foo\\n\\nReason: …\" despite instructions).\n * - Strip surrounding quotes, a leading `Title:` / `title -` preamble,\n * and a `user:` / `assistant:` role prefix echoed from the transcript\n * — repeatedly, in any nesting order.\n * - Reject placeholders and answer-shaped replies (see\n * {@link ANSWER_SHAPED_OPENER}) by throwing.\n * - Strip trailing punctuation (`.`, `!`, `?`) — titles read cleaner\n * without it.\n * - Truncate to `TITLE_MAX_CHARS` with a trailing `…` when over.\n *\n * Exported as `cleanTitle` so tests can pin the normalization rules\n * without going through a mock provider.\n */\nexport function cleanTitle(raw: string): string {\n let s = raw.split('\\n')[0]?.trim() ?? ''\n // Peel wrappers until the string stops changing. Models nest them\n // (`\"assistant: Fix the flaky test\"`), and each strip can expose the\n // next — order-sensitive single passes let a quoted role prefix through,\n // which then also dodged the answer-shape guard below. Bounded so a\n // pathological input can't spin here.\n for (let pass = 0; pass < 4; pass++) {\n const before = s\n s = unwrapQuotes(s)\n // Strip a \"Title:\" / \"Title -\" preamble the model sometimes adds.\n s = s.replace(/^\\s*title\\s*[:\\-–—]\\s*/i, '')\n // Strip a transcript role prefix echoed back from the prompt format\n // (`assistant: Fix the flaky test`).\n s = s.replace(/^\\s*(?:user|assistant|system)\\s*:\\s*/i, '')\n if (s === before)\n break\n }\n // Trim trailing terminal punctuation — titles don't take periods.\n s = s.replace(/[.!?]+$/, '').trim()\n if (s.length === 0)\n throw new Error('Model returned an empty title.')\n // A bracketed marker (e.g. `[tool: list_files]`) is transcript plumbing\n // echoed back, never a real title. Treat it as a failed generation — the\n // auto-title guards re-evaluate on the next run, so it naturally retries.\n if (/^\\[.*\\]$/.test(s))\n throw new Error('Model returned a placeholder instead of a title.')\n // Same deal for an answer instead of a label: the model replied to the\n // transcript rather than naming it. Rejecting keeps the sentence out of\n // `metadata.title` and lets the next run try again.\n if (ANSWER_SHAPED_OPENER.test(s))\n throw new Error('Model answered the conversation instead of titling it.')\n return s.length > TITLE_MAX_CHARS ? `${s.slice(0, TITLE_MAX_CHARS - 1)}…` : s\n}\n\nfunction clip(text: string, max: number): string {\n return text.length > max ? `${text.slice(0, max)}…` : text\n}\n","/**\n * Silent structured-output generation — one provider call that returns\n * schema-conforming JSON without touching the session or the transcript.\n *\n * Modeled on the two proven side-channel patterns in the codebase:\n * - `generate-title.ts`: a single `provider.stream()` with no session\n * writes and no transcript events (optionally seeded with a compacted\n * conversation transcript), and\n * - the loop's `__output__` schema enforcement: a synthetic tool whose\n * `inputSchema` IS the requested JSON Schema, with `toolChoice` pinned\n * so the model must answer with a conforming payload.\n *\n * Used by `/` commands via `ctx.generateData` to derive values from the\n * model (or from the conversation) mid-workflow without bloating the chat\n * history — e.g. `/commit` generating `{ subject, body }` from a diff.\n * For genuinely interactive sub-runs (real turns, streaming, tools), use\n * `ctx.runAgent` instead.\n */\n\nimport type { Provider, ToolSpec } from '../providers'\nimport type { SessionTurn } from '../types'\nimport type { InferSchemaOutput, SchemaInput, StandardSchemaLike } from '../zod'\nimport { resolveSchemaInput } from '../zod'\nimport { renderTurnsForPrompt } from './generate-title'\n\n/** Synthetic forced-choice tool name (never persisted — the call is side-channel only). */\nconst DATA_TOOL_NAME = '__data__'\n\n/** Default cap on conversation turns folded into the context block. */\nconst DEFAULT_CONTEXT_TURNS = 12\n\nexport interface GenerateStructuredDataOptions {\n provider: Provider\n model: string\n /** Instruction describing the value to produce. */\n prompt: string\n /**\n * Shape of the result — a plain JSON Schema object, or a zod v4 schema\n * (converted automatically; the return type is then inferred from it).\n * Becomes the forced tool's inputSchema.\n */\n schema: SchemaInput\n /** Optional system prompt. Defaults to a terse data-extraction persona. */\n system?: string\n /**\n * Conversation turns to fold in as compacted context (same clipping as\n * session auto-titling). Omit or pass empty for a context-free call.\n */\n turns?: readonly SessionTurn[]\n /** Cap on turns rendered into the context block. Default 12 (most recent). */\n maxContextTurns?: number\n signal?: AbortSignal\n}\n\n/**\n * Drive one schema-pinned provider call and return the conforming payload.\n * Throws when the provider returns no structured payload (and rethrows\n * aborts verbatim). The caller owns presentation — nothing is rendered or\n * persisted here.\n *\n * Pass a zod v4 schema and the return type is inferred from it; pass a\n * plain JSON Schema object and name the type via the generic.\n */\nexport async function generateStructuredData<S extends StandardSchemaLike>(\n options: GenerateStructuredDataOptions & { schema: S },\n): Promise<InferSchemaOutput<S>>\nexport async function generateStructuredData<T = unknown>(\n options: GenerateStructuredDataOptions,\n): Promise<T>\nexport async function generateStructuredData(\n options: GenerateStructuredDataOptions,\n): Promise<unknown> {\n const { provider, model, prompt, signal } = options\n const spec: ToolSpec = {\n name: DATA_TOOL_NAME,\n description: 'Return the result. The input of this call IS the answer — it must conform to the schema.',\n inputSchema: await resolveSchemaInput(options.schema),\n }\n\n const contextTurns = options.turns && options.turns.length > 0\n ? options.turns.slice(-(options.maxContextTurns ?? DEFAULT_CONTEXT_TURNS))\n : []\n const transcript = contextTurns.length > 0 ? renderTurnsForPrompt(contextTurns) : ''\n const userPrompt = transcript\n ? `<conversation>\\n${transcript}\\n</conversation>\\n\\n${prompt}`\n : prompt\n\n const result = await provider.stream(\n {\n model,\n system: options.system\n ?? `You produce structured data. Call the ${DATA_TOOL_NAME} tool exactly once with the requested value. No prose.`,\n tools: provider.formatTools([spec]),\n messages: [provider.userMessage(userPrompt)],\n toolChoice: { type: 'tool', name: DATA_TOOL_NAME },\n ...(signal ? { signal } : {}),\n },\n {\n onText: () => {},\n },\n )\n\n const payload = result.toolCalls.find(tc => tc.name === DATA_TOOL_NAME)?.input\n if (payload === undefined)\n throw new Error('generateStructuredData: the model returned no structured payload')\n return payload\n}\n","/**\n * Inline (ghost-text) autocomplete for the prompt.\n *\n * Distinct from the trigger-based completion popover (`@` files, `/`\n * commands): providers here suggest plain text SUFFIX candidates to\n * append at the cursor — the first renders as dim ghost text, up/down\n * rotates through the rest, Tab accepts. No popover.\n *\n * Providers are queried in registration order with a shared AbortSignal;\n * the engine returns the first provider's non-empty candidate list.\n * Extensions add providers via `ctx.registerInlineAutocompleteProvider`\n * — the built-in path provider ({@link createPathAutocompleteProvider})\n * is always first.\n */\n\nimport { readdirSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { basename, dirname, isAbsolute, join, resolve } from 'node:path'\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface InlineAutocompleteContext {\n /** Full prompt buffer. */\n text: string\n /** Cursor offset into `text`. Suggestions complete the token ENDING here. */\n cursor: number\n /** Workspace directory relative paths resolve against. */\n cwd: string\n /** Aborted when the buffer changes before the provider resolves. */\n signal: AbortSignal\n}\n\nexport interface InlineAutocompleteProvider {\n /** Stable id — used for first-wins dedupe across extensions. */\n id: string\n /**\n * Per-provider deadline override (ms), capped at\n * {@link MAX_PROVIDER_DEADLINE_MS}. The default\n * ({@link PROVIDER_DEADLINE_MS}) suits instant sources like directory\n * listings; a provider backed by a slow source (e.g. a local LLM) can\n * extend its own budget — the wait is async (typing is never blocked,\n * an edit aborts the query) and only delays providers ordered AFTER\n * this one.\n */\n deadlineMs?: number\n /**\n * Return the ghost suffix (or an ordered list of candidate suffixes —\n * the host shows the first and lets the user rotate with up/down) to\n * append at the cursor, or null/''/[] for no suggestion. Must respect\n * its deadline budget — it runs per keystroke (debounced by the\n * host).\n */\n suggest: (ctx: InlineAutocompleteContext) => string | readonly string[] | null | Promise<string | readonly string[] | null>\n}\n\n// ---------------------------------------------------------------------------\n// Engine\n// ---------------------------------------------------------------------------\n\n/** Default per-provider deadline — a hung provider is treated as \"no suggestion\". */\nconst PROVIDER_DEADLINE_MS = 150\n/** Hard cap on a provider's `deadlineMs` — past this, waiting for a ghost reads as broken. */\nexport const MAX_PROVIDER_DEADLINE_MS = 2_000\n\n/**\n * Query `providers` in order; the first provider yielding a non-empty\n * candidate list wins (a bare string normalizes to a 1-element list).\n * Provider errors are swallowed (a broken extension provider must not\n * break typing), and each provider races its deadline —\n * {@link PROVIDER_DEADLINE_MS} by default, a provider's own `deadlineMs`\n * (capped at {@link MAX_PROVIDER_DEADLINE_MS}) when declared — so one\n * hung provider can't permanently suppress the ghosts of providers\n * behind it. Returns null when aborted or nothing matched.\n */\nexport async function resolveInlineSuggestion(\n providers: readonly InlineAutocompleteProvider[],\n ctx: InlineAutocompleteContext,\n deadlineMs = PROVIDER_DEADLINE_MS,\n // Injectable cap — the host always uses the real MAX_PROVIDER_DEADLINE_MS;\n // tests pass a small value to prove clamping without a wall-clock stall.\n maxDeadlineMs = MAX_PROVIDER_DEADLINE_MS,\n): Promise<{ suffixes: readonly string[], providerId: string } | null> {\n const TIMEOUT = Symbol('timeout')\n for (const provider of providers) {\n if (ctx.signal.aborted)\n return null\n try {\n let timer: ReturnType<typeof setTimeout> | undefined\n const budget = provider.deadlineMs !== undefined\n ? Math.min(Math.max(provider.deadlineMs, 1), maxDeadlineMs)\n : deadlineMs\n const result = await Promise.race([\n Promise.resolve(provider.suggest(ctx)),\n new Promise<typeof TIMEOUT>((r) => { timer = setTimeout(r, budget, TIMEOUT) }),\n ])\n if (timer !== undefined)\n clearTimeout(timer)\n if (result === TIMEOUT)\n continue\n if (ctx.signal.aborted)\n return null\n const suffixes = (typeof result === 'string' ? [result] : result ?? [])\n .filter(s => typeof s === 'string' && s.length > 0)\n if (suffixes.length > 0)\n return { suffixes, providerId: provider.id }\n }\n catch {\n // Isolation-first: skip broken providers.\n }\n }\n return null\n}\n\n// ---------------------------------------------------------------------------\n// Built-in path provider\n// ---------------------------------------------------------------------------\n\n/**\n * Extract the whitespace-delimited token that ends at `cursor`. Returns\n * null when the cursor sits right after whitespace (nothing to complete).\n */\nexport function tokenEndingAtCursor(text: string, cursor: number): string | null {\n const before = text.slice(0, cursor)\n const match = before.match(/\\S+$/)\n return match ? match[0] : null\n}\n\n/**\n * Does this token carry an explicit path shape (`/` separator or a `~`\n * prefix)? Path-shaped tokens complete from the first character after\n * the separator; bare tokens (`sr` → `c/`) still complete against the\n * cwd listing but need ≥2 chars first — see the provider.\n */\nfunction looksLikePath(token: string): boolean {\n return token.includes('/') || token.startsWith('~')\n}\n\n/**\n * Cap on candidates returned for an ambiguous fragment — rotation past\n * a dozen-odd entries stops being faster than typing another character.\n */\nconst MAX_PATH_CANDIDATES = 16\n\n/** Directory listing seam — injectable for tests. */\nexport interface PathProviderIo {\n readdir?: (dir: string) => { name: string, isDirectory: boolean }[]\n}\n\nfunction defaultReaddir(dir: string): { name: string, isDirectory: boolean }[] {\n return readdirSync(dir, { withFileTypes: true }).map(e => ({ name: e.name, isDirectory: e.isDirectory() }))\n}\n\n/**\n * Built-in provider: complete filesystem paths under `cwd` (or absolute /\n * `~` paths). Given the token `src/chat/comp`, lists `src/chat/` and\n * returns every matching continuation of `comp` as a candidate list —\n * directories first, alphabetical, `/`-terminated for directories so\n * typing can continue. The host ghosts the first candidate and rotates\n * through the rest on up/down.\n *\n * Bare tokens (no `/` yet) are matched against the cwd listing too, so\n * a directory ghost appears as soon as the first characters of `src`\n * are typed — but only from the second character on, so 1-letter prose\n * words don't ghost-spam.\n *\n * Dotfiles are only offered when the typed fragment itself starts with\n * `.` — the usual completion convention.\n */\nexport function createPathAutocompleteProvider(opts: {\n cwd?: () => string\n io?: PathProviderIo\n} = {}): InlineAutocompleteProvider {\n const readdir = opts.io?.readdir ?? defaultReaddir\n // Prefer the per-invocation `ctx.cwd` (host truth); fall back to the\n // factory-supplied getter, then `process.cwd()`.\n const fallbackCwd = opts.cwd ?? (() => process.cwd())\n return {\n id: 'paths',\n suggest(ctx) {\n const token = tokenEndingAtCursor(ctx.text, ctx.cursor)\n if (!token)\n return null\n // Bare tokens complete against the cwd listing (dirname('sr') is '.',\n // so the resolution below just works) but require ≥2 typed chars.\n if (!looksLikePath(token) && token.length < 2)\n return null\n // Resolve the directory being completed and the fragment typed so\n // far. A trailing `/` means \"complete entries of this directory\".\n const expanded = token.startsWith('~')\n ? join(homedir(), token.slice(1))\n : token\n const endsWithSep = expanded.endsWith('/')\n const dirPart = endsWithSep ? expanded : dirname(expanded)\n const fragment = endsWithSep ? '' : basename(expanded)\n const baseCwd = ctx.cwd || fallbackCwd()\n const dir = isAbsolute(dirPart) ? dirPart : resolve(baseCwd, dirPart)\n let entries: { name: string, isDirectory: boolean }[]\n try {\n entries = readdir(dir)\n }\n catch {\n return null\n }\n const matches = entries.filter(e =>\n e.name.startsWith(fragment)\n && (fragment.startsWith('.') || !e.name.startsWith('.')),\n )\n // Directories first (they're what you're navigating through), then\n // alphabetical within each group.\n matches.sort((a, b) =>\n a.isDirectory !== b.isDirectory\n ? (a.isDirectory ? -1 : 1)\n : a.name.localeCompare(b.name))\n const suffixes = matches\n .map(e => e.name.slice(fragment.length) + (e.isDirectory ? '/' : ''))\n .filter(s => s.length > 0)\n .slice(0, MAX_PATH_CANDIDATES)\n return suffixes.length > 0 ? suffixes : null\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Host-side ghost reconciliation\n// ---------------------------------------------------------------------------\n\nexport interface InlineGhost {\n suffixes: readonly string[]\n index: number\n text: string\n cursor: number\n}\n\n/**\n * Optimistically transform the current ghost across a buffer edit so it\n * stays visible while the debounced provider re-query is in flight —\n * without this, every keystroke inside a completable token blinks the\n * ghost (hide → 120 ms → reshow).\n *\n * Two edits are reconcilable (everything else returns null and waits for\n * the authoritative result):\n * - typing FORWARD along the suggestion (`bi` → `bin` with ghost\n * `n/`): candidates are filtered to those starting with the typed\n * chars and trimmed;\n * - deleting BACKWARD within the token (`bin` → `bi` with ghost `/`):\n * the deleted chars are re-prepended to every candidate. Guarded to\n * non-whitespace so crossing a token boundary drops the ghost.\n *\n * The patched ghost is provisional — the provider's answer replaces it\n * (and may add candidates the patch couldn't know about).\n */\nexport function patchInlineGhost(prev: InlineGhost | null, text: string, cursor: number): InlineGhost | null {\n if (!prev)\n return null\n if (text === prev.text && cursor === prev.cursor)\n return prev\n // The text after the caret must be untouched — edits there invalidate\n // the completion context entirely.\n if (text.slice(cursor) !== prev.text.slice(prev.cursor))\n return null\n if (cursor > prev.cursor) {\n if (text.slice(0, prev.cursor) !== prev.text.slice(0, prev.cursor))\n return null\n const typed = text.slice(prev.cursor, cursor)\n const shown = prev.suffixes[prev.index]\n const suffixes = prev.suffixes\n .filter(s => s.startsWith(typed) && s.length > typed.length)\n .map(s => s.slice(typed.length))\n if (suffixes.length === 0)\n return null\n // Keep the rotated-to candidate selected when it survives the filter.\n const shownTrimmed = shown.startsWith(typed) ? shown.slice(typed.length) : null\n const index = shownTrimmed !== null ? Math.max(0, suffixes.indexOf(shownTrimmed)) : 0\n return { suffixes, index, text, cursor }\n }\n if (prev.text.slice(0, cursor) !== text.slice(0, cursor))\n return null\n const deleted = prev.text.slice(cursor, prev.cursor)\n if (deleted.length === 0 || /\\s/.test(deleted))\n return null\n return { suffixes: prev.suffixes.map(s => deleted + s), index: prev.index, text, cursor }\n}\n","/**\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","/**\n * Metrics helper — wraps agent lifecycle hooks in caller-provided meters.\n *\n * Symmetric with {@link createTracingHooks} from `./tracing`. Where the\n * tracer answers \"what happened in this run?\" by producing spans, the\n * metrics helper answers \"how is the fleet behaving?\" by emitting\n * counters / histograms / up-down counters. Both are tracer-agnostic —\n * the caller supplies a `Meter` (OTel-API-shaped) and the helper plugs\n * the right hook on each metric.\n *\n * Instrument families:\n *\n * ### Histograms (distribution metrics)\n * - `gen_ai.client.operation.duration` (ms) — wall-clock per turn (`turn:before` → `turn:after`).\n * - `gen_ai.client.token.usage` — `input` / `output` token counts per turn (tagged by `gen_ai.token.type`).\n * - `gen_ai.client.time_to_first_token` (ms) — per-turn TTFT from `TurnUsage.timeToFirstTokenMs`.\n * - `gen_ai.tool.duration` (ms) — native tool execution time (`tool:before` → `tool:after`).\n * - `gen_ai.tool.output_bytes` — `tool:after.outputBytes`.\n * - `gen_ai.mcp.tool.duration` (ms) — MCP tool execution time.\n * - `gen_ai.mcp.bootstrap.duration` (ms) — MCP server bootstrap (already measured by harness).\n *\n * ### Counters (monotonic)\n * - `gen_ai.agent.runs` — `agent:start`.\n * - `gen_ai.agent.runs.completed` — `agent:done`.\n * - `gen_ai.agent.aborts` — `agent:abort`.\n * - `gen_ai.tool.calls` — `tool:dispatched` (tagged by `outcome`).\n * - `gen_ai.tool.errors` — `tool:error`.\n * - `gen_ai.mcp.tool.errors` — `mcp:tool:error`.\n * - `gen_ai.stream.errors` — `stream:error` (tagged by `status_code`).\n * - `gen_ai.validation.rejects` — `validation:reject`.\n * - `gen_ai.validation.coercions` — `validation:coerce`.\n * - `gen_ai.gate.blocks` — `tool:dispatched` outcome `gate-block`.\n * - `gen_ai.budget.exceeded` — `budget:exceeded`.\n * - `gen_ai.tool_budget.exceeded` — `tool-budget:exceeded` (tagged by `mode`).\n * - `gen_ai.pairing.repairs` — `pairing:repair` (tagged by `mode`).\n * - `gen_ai.oauth.refreshes` — `oauth:refresh`.\n * - `gen_ai.mcp.errors` — `mcp:error`.\n * - `gen_ai.mcp.auth.required` — `mcp:auth:required`.\n * - `gen_ai.cost_usd` — accumulated cost (added on `turn:after`).\n *\n * ### Up-down counter (gauge-shaped)\n * - `gen_ai.agent.runs.active` — incremented on `agent:start`, decremented on `agent:done`.\n *\n * The `Meter` shape mirrors `@opentelemetry/api`'s `Meter` exactly — passing\n * `metrics.getMeter('zidane')` works directly. Hosts using a different\n * metrics backend (StatsD, Prometheus client, custom in-memory aggregator)\n * implement the same three factory methods and the helper plugs in\n * unchanged.\n */\n\nimport type { Hookable } from 'hookable'\nimport type { AgentHooks } from './agent'\n\n// ---------------------------------------------------------------------------\n// OTel-shaped Meter interface\n// ---------------------------------------------------------------------------\n\nexport type MetricAttributes = Record<string, string | number | boolean | undefined>\n\nexport interface Counter {\n add: (value: number, attributes?: MetricAttributes) => void\n}\n\nexport interface Histogram {\n record: (value: number, attributes?: MetricAttributes) => void\n}\n\nexport interface UpDownCounter {\n add: (value: number, attributes?: MetricAttributes) => void\n}\n\nexport interface InstrumentOptions {\n description?: string\n unit?: string\n}\n\n/**\n * Minimal Meter interface — structurally identical to OTel's `Meter`.\n * Hosts passing `metrics.getMeter(name)` (from `@opentelemetry/api`)\n * satisfy this without adaptation.\n */\nexport interface Meter {\n createCounter: (name: string, options?: InstrumentOptions) => Counter\n createHistogram: (name: string, options?: InstrumentOptions) => Histogram\n createUpDownCounter: (name: string, options?: InstrumentOptions) => UpDownCounter\n}\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\nexport interface MetricsHooksOptions {\n meter: Meter\n /**\n * Optional prefix prepended to every instrument name. Default: no prefix\n * (instrument names follow OTel Gen AI semantic conventions verbatim,\n * which is the most-portable shape). Set to e.g. `'zidane.'` to\n * namespace inside a shared meter registry.\n */\n namespace?: string\n /**\n * Optional baseline attributes applied to every measurement. Typical\n * use: `{ service: 'tui', env: 'prod' }`. Per-event attributes win on\n * key collision.\n */\n baseAttributes?: MetricAttributes\n /**\n * Error sink for meter failures. The helper still swallows the throw\n * so a broken backend can't crash a run; this callback surfaces the\n * failure for ops dashboards.\n */\n onError?: (kind: string, err: unknown) => void\n}\n\nexport interface MetricsHookSet {\n install: (hooks: Hookable<AgentHooks>) => () => void\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction prefixed(prefix: string | undefined, name: string): string {\n return prefix ? `${prefix}${name}` : name\n}\n\n/**\n * Drop `undefined` entries before handing attributes to a metrics\n * backend. OTel's `@opentelemetry/api` rejects `undefined` attribute\n * values (and Prometheus / StatsD adapters silently mis-label them); the\n * helper's own API surface allows `undefined` for ergonomic call sites\n * (`{...(cond ? { foo } : {})}`-style spreads), so we strip here at the\n * boundary.\n */\nfunction mergeAttrs(\n base: MetricAttributes | undefined,\n extra: MetricAttributes,\n): Record<string, string | number | boolean> {\n const out: Record<string, string | number | boolean> = {}\n if (base) {\n for (const [k, v] of Object.entries(base)) {\n if (v !== undefined)\n out[k] = v\n }\n }\n for (const [k, v] of Object.entries(extra)) {\n if (v !== undefined)\n out[k] = v\n }\n return out\n}\n\n// ---------------------------------------------------------------------------\n// createMetricsHooks\n// ---------------------------------------------------------------------------\n\n/**\n * Build a set of metrics hook handlers that can be installed on an agent.\n *\n * @example OpenTelemetry\n * ```ts\n * import { metrics } from '@opentelemetry/api'\n * const meter = metrics.getMeter('zidane')\n * const m = createMetricsHooks({ meter, baseAttributes: { service: 'tui' } })\n * const uninstall = m.install(agent.hooks)\n * try { await agent.run({ prompt }) }\n * finally { uninstall() }\n * ```\n */\nexport function createMetricsHooks(options: MetricsHooksOptions): MetricsHookSet {\n const ns = options.namespace\n const base = options.baseAttributes\n const onError = options.onError ?? (() => {})\n\n const make = {\n counter: (name: string, opts?: InstrumentOptions) =>\n safeFactory(() => options.meter.createCounter(prefixed(ns, name), opts), name, onError),\n histogram: (name: string, opts?: InstrumentOptions) =>\n safeFactory(() => options.meter.createHistogram(prefixed(ns, name), opts), name, onError),\n upDown: (name: string, opts?: InstrumentOptions) =>\n safeFactory(() => options.meter.createUpDownCounter(prefixed(ns, name), opts), name, onError),\n }\n\n // Instruments — created once at install time.\n const turnDuration = make.histogram('gen_ai.client.operation.duration', { unit: 'ms', description: 'Per-turn LLM operation duration.' })\n const tokenUsage = make.histogram('gen_ai.client.token.usage', { unit: 'tokens', description: 'Per-turn token usage (tagged by gen_ai.token.type).' })\n const ttft = make.histogram('gen_ai.client.time_to_first_token', { unit: 'ms', description: 'Per-turn time to first token.' })\n const toolDuration = make.histogram('gen_ai.tool.duration', { unit: 'ms', description: 'Native tool execution wall clock.' })\n const toolOutput = make.histogram('gen_ai.tool.output_bytes', { unit: 'By', description: 'Native tool output bytes.' })\n const mcpToolDuration = make.histogram('gen_ai.mcp.tool.duration', { unit: 'ms', description: 'MCP tool execution wall clock.' })\n const mcpBootstrapDuration = make.histogram('gen_ai.mcp.bootstrap.duration', { unit: 'ms', description: 'MCP server bootstrap duration.' })\n\n const runsStarted = make.counter('gen_ai.agent.runs', { description: 'Runs started.' })\n const runsCompleted = make.counter('gen_ai.agent.runs.completed', { description: 'Runs completed.' })\n const aborts = make.counter('gen_ai.agent.aborts', { description: 'Run abort events.' })\n const toolCalls = make.counter('gen_ai.tool.calls', { description: 'Tool dispatches (tagged by outcome).' })\n const toolErrors = make.counter('gen_ai.tool.errors', { description: 'Native tool errors.' })\n const mcpToolErrors = make.counter('gen_ai.mcp.tool.errors', { description: 'MCP tool errors.' })\n const streamErrors = make.counter('gen_ai.stream.errors', { description: 'Provider stream errors.' })\n const validationRejects = make.counter('gen_ai.validation.rejects', { description: 'Tool input validation rejects.' })\n const validationCoercions = make.counter('gen_ai.validation.coercions', { description: 'Tool input auto-coercions.' })\n const gateBlocks = make.counter('gen_ai.gate.blocks', { description: 'Tool-gate refusals.' })\n const budgetExceeded = make.counter('gen_ai.budget.exceeded', { description: 'Per-turn byte budget exceedances.' })\n const toolBudgetExceeded = make.counter('gen_ai.tool_budget.exceeded', { description: 'Per-tool dispatch budget exceedances.' })\n const pairingRepairs = make.counter('gen_ai.pairing.repairs', { description: 'Tool-pairing repair counts.' })\n const oauthRefreshes = make.counter('gen_ai.oauth.refreshes', { description: 'Provider OAuth credential refreshes.' })\n const mcpErrors = make.counter('gen_ai.mcp.errors', { description: 'MCP server errors.' })\n const mcpAuthRequired = make.counter('gen_ai.mcp.auth.required', { description: 'MCP bootstrap blocked on missing auth.' })\n const costMeter = make.counter('gen_ai.cost_usd', { unit: 'USD', description: 'Cumulative cost across turns.' })\n\n const runsActive = make.upDown('gen_ai.agent.runs.active', { description: 'In-flight agent runs.' })\n\n return {\n install(hooks: Hookable<AgentHooks>): () => void {\n // Per-instrument book-keeping. Each map tracks instrument start times\n // keyed by the identifier that distinguishes concurrent in-flight\n // instances of that kind.\n const turnStart = new Map<string, number>() // turnId -> startedAt\n const toolStart = new Map<string, number>() // callId -> startedAt\n const mcpStart = new Map<string, number>() // callId -> startedAt\n\n const unregisters: Array<() => void> = []\n\n const record = <T extends { record: (v: number, a?: MetricAttributes) => void }>(\n instrument: T | undefined,\n value: number,\n attrs: MetricAttributes,\n kind: string,\n ): void => {\n if (!instrument)\n return\n try {\n instrument.record(value, mergeAttrs(base, attrs))\n }\n catch (err) {\n try {\n onError(kind, err)\n }\n catch { /* ignore */ }\n }\n }\n\n const add = <T extends { add: (v: number, a?: MetricAttributes) => void }>(\n instrument: T | undefined,\n value: number,\n attrs: MetricAttributes,\n kind: string,\n ): void => {\n if (!instrument)\n return\n try {\n instrument.add(value, mergeAttrs(base, attrs))\n }\n catch (err) {\n try {\n onError(kind, err)\n }\n catch { /* ignore */ }\n }\n }\n\n // ---- Agent lifecycle --------------------------------------------\n\n unregisters.push(hooks.hook('agent:start', (ctx) => {\n const attrs: MetricAttributes = {\n 'gen_ai.agent.depth': ctx.depth,\n ...(ctx.agentName ? { 'gen_ai.agent.name': ctx.agentName } : {}),\n }\n add(runsStarted, 1, attrs, 'runs')\n add(runsActive, 1, attrs, 'runs.active')\n }))\n\n unregisters.push(hooks.hook('agent:done', () => {\n add(runsCompleted, 1, {}, 'runs.completed')\n add(runsActive, -1, {}, 'runs.active')\n // Aborted turns skip `turn:after` / `tool:after` / `mcp:tool:after`,\n // stranding their start entries until uninstall — the run is over,\n // so flush them here.\n turnStart.clear()\n toolStart.clear()\n mcpStart.clear()\n }))\n\n unregisters.push(hooks.hook('agent:abort', () => {\n add(aborts, 1, {}, 'aborts')\n }))\n\n // ---- Turn / stream metrics -------------------------------------\n\n unregisters.push(hooks.hook('turn:before', (ctx) => {\n turnStart.set(ctx.turnId, Date.now())\n }))\n\n unregisters.push(hooks.hook('turn:after', (ctx) => {\n const started = turnStart.get(ctx.turnId)\n turnStart.delete(ctx.turnId)\n const tagsBase: MetricAttributes = {\n 'gen_ai.operation.name': 'chat',\n ...(ctx.usage.modelId ? { 'gen_ai.response.model': ctx.usage.modelId } : {}),\n ...(ctx.usage.finishReason ? { 'gen_ai.response.finish_reason': ctx.usage.finishReason } : {}),\n }\n if (typeof started === 'number')\n record(turnDuration, Date.now() - started, tagsBase, 'turn.duration')\n if (typeof ctx.usage.timeToFirstTokenMs === 'number')\n record(ttft, ctx.usage.timeToFirstTokenMs, tagsBase, 'ttft')\n\n record(tokenUsage, ctx.usage.input, { ...tagsBase, 'gen_ai.token.type': 'input' }, 'token.input')\n record(tokenUsage, ctx.usage.output, { ...tagsBase, 'gen_ai.token.type': 'output' }, 'token.output')\n if (typeof ctx.usage.cacheRead === 'number' && ctx.usage.cacheRead > 0)\n record(tokenUsage, ctx.usage.cacheRead, { ...tagsBase, 'gen_ai.token.type': 'cache_read' }, 'token.cache_read')\n if (typeof ctx.usage.cacheCreation === 'number' && ctx.usage.cacheCreation > 0)\n record(tokenUsage, ctx.usage.cacheCreation, { ...tagsBase, 'gen_ai.token.type': 'cache_creation' }, 'token.cache_creation')\n\n if (typeof ctx.usage.cost === 'number' && ctx.usage.cost > 0)\n add(costMeter, ctx.usage.cost, tagsBase, 'cost')\n }))\n\n unregisters.push(hooks.hook('stream:error', (ctx) => {\n add(streamErrors, 1, {\n 'http.response.status_code': ctx.statusCode,\n 'error.type': ctx.err instanceof Error ? ctx.err.name : 'unknown',\n }, 'stream.error')\n }))\n\n // ---- Native tool metrics ---------------------------------------\n\n unregisters.push(hooks.hook('tool:before', (ctx) => {\n toolStart.set(ctx.callId, Date.now())\n }))\n\n unregisters.push(hooks.hook('tool:after', (ctx) => {\n const started = toolStart.get(ctx.callId)\n toolStart.delete(ctx.callId)\n const tags: MetricAttributes = { 'gen_ai.tool.name': ctx.name }\n if (typeof started === 'number')\n record(toolDuration, Date.now() - started, tags, 'tool.duration')\n record(toolOutput, ctx.outputBytes, tags, 'tool.output_bytes')\n }))\n\n // NOTE: `tool:error` must NOT delete `toolStart[callId]` — on the\n // execute-error path the loop still fires `tool:after` afterwards\n // (with the `Tool error: …` payload), and that's where the errored\n // call's duration gets recorded + the entry cleaned up.\n unregisters.push(hooks.hook('tool:error', (ctx) => {\n add(toolErrors, 1, {\n 'gen_ai.tool.name': ctx.name,\n 'error.type': ctx.error.name,\n }, 'tool.errors')\n }))\n\n // Mid-flight per-call cancels close with `tool:cancelled` instead of\n // `tool:after` — clean up the start entry here or it leaks.\n unregisters.push(hooks.hook('tool:cancelled', (ctx) => {\n toolStart.delete(ctx.callId)\n }))\n\n unregisters.push(hooks.hook('tool:dispatched', (ctx) => {\n add(toolCalls, 1, {\n 'gen_ai.tool.name': ctx.name,\n 'outcome': ctx.outcome,\n }, 'tool.calls')\n if (ctx.outcome === 'gate-block') {\n add(gateBlocks, 1, {\n 'gen_ai.tool.name': ctx.name,\n ...(ctx.reason ? { reason: ctx.reason } : {}),\n }, 'gate.blocks')\n }\n // Unknown-tool dispatches fire `tool:before` (with `unknown: true`)\n // but never `tool:after` — drop the start entry so hallucinated\n // tool names can't grow the map unboundedly. `tool:dispatched` for\n // real executions fires BEFORE `tool:before`, so this only affects\n // the unknown path.\n if (ctx.outcome === 'unknown')\n toolStart.delete(ctx.callId)\n }))\n\n unregisters.push(hooks.hook('validation:reject', (ctx) => {\n add(validationRejects, 1, { 'gen_ai.tool.name': ctx.name }, 'validation.rejects')\n }))\n\n unregisters.push(hooks.hook('validation:coerce', (ctx) => {\n add(validationCoercions, 1, {\n 'gen_ai.tool.name': ctx.name,\n 'coercions': ctx.coercions.length,\n }, 'validation.coercions')\n }))\n\n unregisters.push(hooks.hook('budget:exceeded', (ctx) => {\n add(budgetExceeded, 1, {\n bytes: ctx.bytes,\n budget: ctx.budget,\n }, 'budget.exceeded')\n }))\n\n unregisters.push(hooks.hook('tool-budget:exceeded', (ctx) => {\n add(toolBudgetExceeded, 1, {\n 'gen_ai.tool.name': ctx.tool,\n 'mode': ctx.mode,\n 'count': ctx.count,\n 'max': ctx.max,\n }, 'tool_budget.exceeded')\n }))\n\n unregisters.push(hooks.hook('pairing:repair', (ctx) => {\n add(pairingRepairs, 1, { mode: ctx.mode }, 'pairing.repairs')\n }))\n\n unregisters.push(hooks.hook('oauth:refresh', (ctx) => {\n add(oauthRefreshes, 1, { provider: ctx.provider, source: ctx.source }, 'oauth.refreshes')\n }))\n\n // ---- MCP metrics -----------------------------------------------\n\n unregisters.push(hooks.hook('mcp:tool:before', (ctx) => {\n mcpStart.set(ctx.callId, Date.now())\n }))\n\n unregisters.push(hooks.hook('mcp:tool:after', (ctx) => {\n const started = mcpStart.get(ctx.callId)\n mcpStart.delete(ctx.callId)\n const tags: MetricAttributes = {\n 'gen_ai.mcp.server': ctx.server,\n 'gen_ai.tool.name': ctx.displayName,\n }\n if (typeof started === 'number')\n record(mcpToolDuration, Date.now() - started, tags, 'mcp.tool.duration')\n }))\n\n unregisters.push(hooks.hook('mcp:tool:error', (ctx) => {\n mcpStart.delete(ctx.callId)\n add(mcpToolErrors, 1, {\n 'gen_ai.mcp.server': ctx.server,\n 'gen_ai.tool.name': ctx.displayName,\n 'error.type': ctx.error.name,\n }, 'mcp.tool.errors')\n }))\n\n unregisters.push(hooks.hook('mcp:bootstrap:end', (ctx) => {\n record(mcpBootstrapDuration, ctx.durationMs, {\n 'gen_ai.mcp.server': ctx.name,\n 'ok': ctx.ok,\n }, 'mcp.bootstrap.duration')\n }))\n\n unregisters.push(hooks.hook('mcp:error', (ctx) => {\n add(mcpErrors, 1, {\n 'gen_ai.mcp.server': ctx.name,\n 'error.type': ctx.error.name,\n }, 'mcp.errors')\n }))\n\n unregisters.push(hooks.hook('mcp:auth:required', (ctx) => {\n add(mcpAuthRequired, 1, {\n 'gen_ai.mcp.server': ctx.name,\n 'transport': ctx.transport,\n 'reason': ctx.reason,\n }, 'mcp.auth.required')\n }))\n\n // -----------------------------------------------------------------\n // Disposal\n // -----------------------------------------------------------------\n\n let disposed = false\n return function uninstall() {\n if (disposed)\n return\n disposed = true\n for (const un of unregisters) {\n try {\n un()\n }\n catch { /* ignore */ }\n }\n turnStart.clear()\n toolStart.clear()\n mcpStart.clear()\n }\n },\n }\n}\n\n/**\n * Defensive factory wrapper — returns undefined when meter creation\n * throws. Lets a half-broken meter (one unsupported instrument) still\n * give partial metrics rather than collapsing the whole install.\n */\nfunction safeFactory<T>(\n factory: () => T,\n name: string,\n onError: (kind: string, err: unknown) => void,\n): T | undefined {\n try {\n return factory()\n }\n catch (err) {\n try {\n onError(`createInstrument:${name}`, err)\n }\n catch { /* ignore */ }\n return undefined\n }\n}\n","import type { AgentHookMap } from './agent'\nimport type { ToolOutcome, ToolResultContent } from './types'\n\ntype MaybePromise<T> = T | Promise<T>\n\nexport type SuccessfulMutationOutcome = Extract<ToolOutcome, 'created' | 'updated' | 'edited'>\n\nexport interface MutationValidationMutation {\n turnId: string\n callId: string\n tool: string\n displayName: string\n input: Record<string, unknown>\n outcome: SuccessfulMutationOutcome\n result: string | ToolResultContent[]\n}\n\nexport interface MutationValidationBatch {\n turnId: string\n mutations: readonly MutationValidationMutation[]\n}\n\nexport type MutationValidationResult\n = | { ok: true }\n | { ok: false, message?: string, errors?: readonly string[] }\n\nexport interface MutationValidationFailure {\n batch: MutationValidationBatch\n result: Extract<MutationValidationResult, { ok: false }>\n message: string\n failureCount: number\n}\n\nexport interface MutationValidationHooksOptions {\n validate: (batch: MutationValidationBatch) => MaybePromise<MutationValidationResult | void>\n /**\n * Called when validation fails and the run has not crossed\n * `maxFailuresPerRun`. Hosts typically call `agent.followUp(message)` here.\n */\n onFailure?: (failure: MutationValidationFailure) => MaybePromise<void>\n buildFailureMessage?: (failure: Omit<MutationValidationFailure, 'message'>) => string\n onError?: (error: unknown, batch: MutationValidationBatch) => MaybePromise<void>\n maxFailuresPerRun?: number\n}\n\nfunction defaultFailureMessage(failure: Omit<MutationValidationFailure, 'message'>): string {\n const changed = failure.batch.mutations\n .map(m => `${m.displayName} ${String(m.input.path ?? m.callId)}`)\n .join(', ')\n const details = failure.result.message\n ?? failure.result.errors?.join('\\n')\n ?? 'Validation failed after the last file mutation.'\n const seen = failure.failureCount > 1 ? ` (failure ${failure.failureCount})` : ''\n return `[Mutation validation failed${seen}] ${changed}\\n${details}\\nFix the validation errors with the smallest focused change.`\n}\n\nfunction isSuccessfulMutation(outcome: ToolOutcome | undefined): outcome is SuccessfulMutationOutcome {\n return outcome === 'created' || outcome === 'updated' || outcome === 'edited'\n}\n\n/**\n * Build host-side hooks for post-mutation validation without changing the\n * built-in edit/write tools. The helper only observes successful mutations\n * reported through `tool:after.outcome`; validation commands and recovery\n * prompts remain host policy.\n */\nexport function createMutationValidationHooks(options: MutationValidationHooksOptions): AgentHookMap {\n const pending: MutationValidationMutation[] = []\n let failureCount = 0\n const maxFailures = Math.max(1, Math.floor(options.maxFailuresPerRun ?? 2))\n\n return {\n 'tool:after': (ctx) => {\n if (!isSuccessfulMutation(ctx.outcome))\n return\n pending.push({\n turnId: ctx.turnId,\n callId: ctx.callId,\n tool: ctx.name,\n displayName: ctx.displayName,\n input: ctx.input,\n outcome: ctx.outcome,\n result: ctx.result,\n })\n },\n 'tool-results:after': async (ctx) => {\n if (pending.length === 0)\n return\n const mutations = pending.splice(0)\n const batch: MutationValidationBatch = {\n turnId: ctx.turnId,\n mutations,\n }\n let result: MutationValidationResult | void\n try {\n result = await options.validate(batch)\n }\n catch (err) {\n await options.onError?.(err, batch)\n return\n }\n if (!result || result.ok)\n return\n failureCount++\n if (failureCount > maxFailures)\n return\n const withoutMessage = { batch, result, failureCount }\n const message = options.buildFailureMessage?.(withoutMessage)\n ?? defaultFailureMessage(withoutMessage)\n await options.onFailure?.({ ...withoutMessage, message })\n },\n 'agent:done': () => {\n pending.length = 0\n // `agent:done` fires at the end of every run, so resetting here gives\n // `maxFailuresPerRun` true per-run semantics even when this hook map is\n // registered at agent level and reused across runs.\n failureCount = 0\n },\n }\n}\n","/**\n * Tracing helper — wraps agent lifecycle hooks in caller-provided spans.\n *\n * Zidane is hooks-native; rather than introducing a plugin abstraction just for\n * observability, we ship a small helper that turns an arbitrary `startSpan`\n * function into a bundle of hook handlers. Works with Sentry, OpenTelemetry,\n * Datadog — any tracer that can hand back an object with an `end()` method.\n *\n * Attribute conventions follow the Sentry AI Agents spec (which itself\n * tracks OpenTelemetry Gen AI semconv v1.36, with a few divergences). The\n * `conventions` option lets callers pick `'sentry'` (default — Sentry's\n * exact keys), `'otel'` (vendor-neutral OTel 1.36), or `'both'` (emit\n * every variant for maximum portability).\n *\n * Span coverage:\n *\n * - `invoke_agent <name?>` — root span, opens on `agent:start`, closes on `agent:done`. Sentry op `gen_ai.invoke_agent`.\n * - `chat <model?>` — per-turn LLM call, opens on `turn:before`, closes on `turn:after`. Sentry op `gen_ai.chat`.\n * - `execute_tool <name>` — native tool execution, opens on `tool:before`, closes on `tool:after` / `tool:error`. Sentry op `gen_ai.execute_tool`.\n * - `execute_tool <server>.<name>` — MCP tool variant, same op. Tagged with `gen_ai.tool.type: 'extension'`.\n * - `handoff to <task>` — subagent handoff (parent→child), opens on `spawn:before`, closes on `spawn:complete` / `spawn:error`. Sentry op `gen_ai.handoff`.\n * Also injects parent trace context into the spawn carrier when `propagator` is configured.\n * - `mcp.bootstrap <server>` — server bootstrap, opens on `mcp:bootstrap:start`, closes on `mcp:bootstrap:end`. Zidane-specific (no Sentry op equivalent).\n *\n * Span events (not spans — recorded on the active span via `addEvent`):\n *\n * - `gen_ai.gate.block` — a `tool:gate` listener refused a call.\n * - `gen_ai.validation.reject` — tool input failed schema validation.\n * - `gen_ai.validation.coerce` — tool input was auto-coerced to satisfy the schema.\n * - `gen_ai.budget.exceeded` — byte budget exceeded (`budget:exceeded`).\n * - `gen_ai.tool_budget.exceeded` — per-tool dispatch cap exceeded.\n * - `gen_ai.pairing.repair` — pre-send pairing repair fired.\n * - `gen_ai.stream.error` — provider stream errored (carries `statusCode` / `requestId`).\n * - `gen_ai.mcp.auth.required` — MCP server bootstrap blocked on missing OAuth tokens.\n * - `gen_ai.oauth.refresh` — provider OAuth credentials refreshed.\n *\n * Attribute naming follows the Sentry AI Agents conventions\n * (https://develop.sentry.dev/sdk/telemetry/traces/modules/ai-agents/) by\n * default — which closely track OTel Gen AI semconv v1.36 with a few\n * intentional differences (`usage.input_tokens.cached` instead of\n * `usage.cache_read_input_tokens`, `cost.total_tokens` instead of\n * `usage.cost_usd`, seconds instead of ms for `time_to_first_token`).\n * Set `conventions: 'otel'` to emit only the vendor-neutral OTel keys, or\n * `'both'` to emit everything for maximum portability. Legacy attribute\n * names (`inputTokens`, `toolName`, `displayName`, …) are also emitted by\n * default for back-compat; set `legacyAttributes: false` to drop them.\n *\n * On `agent:done` or `uninstall()` it closes any spans still open (defensive —\n * normally all tool/turn spans have matching end hooks, but an abort\n * mid-execution can leave dangling ones).\n *\n * Returns an `install(hooks)` helper that registers every handler at once and\n * yields an `uninstall()` function for cleanup. Safe across multiple runs.\n */\n\nimport type { Hookable } from 'hookable'\nimport type { AgentHooks } from './agent'\nimport type { SessionMessage } from './types'\n\n/** Minimal span shape — any tracer that provides these methods is compatible. */\nexport interface Span {\n /** Close the span. Called exactly once per span. */\n end: () => void\n /** Optional: attach additional attributes after the span is started (ignored if unsupported). */\n setAttributes?: (attrs: Record<string, unknown>) => void\n /**\n * Optional: record a structured event on the span. Used for non-span\n * occurrences (gate blocks, validation rejects, budget hits, pairing\n * repairs). Maps to OTel's `Span.addEvent` and Sentry's `addBreadcrumb`.\n * Tracers without an event surface should treat this as a no-op (the\n * helper falls back to `setAttributes` with an `event.*` key prefix).\n */\n addEvent?: (name: string, attrs?: Record<string, unknown>) => void\n}\n\n/**\n * Function that opens a span. Caller-provided so we stay tracer-agnostic.\n *\n * `parentContext` carries opaque trace metadata propagated from another\n * agent (typically a W3C `{ traceparent, tracestate }` carrier received on\n * `agent:start.tracingContext` after a parent agent's tracer injected it on\n * `spawn:before`). Implementations that integrate with OTel should extract\n * the carrier into a `Context` and use it as the parent when opening the\n * span; implementations that don't care about cross-agent linkage can\n * ignore the third argument entirely.\n */\nexport type StartSpan = (\n name: string,\n attrs?: Record<string, unknown>,\n parentContext?: Readonly<Record<string, string>>,\n) => Span\n\nexport type TracingConventions = 'sentry' | 'otel' | 'both'\n\nexport interface TracingHooksOptions {\n /** Tracer seam. Receives a span name + attributes; must return a `Span`. */\n startSpan: StartSpan\n /**\n * Optional attribute namespace. Prepended to every span name with a `/`\n * separator (e.g. `\"agent\"` → `agent/chat <model>`, `agent/execute_tool Bash`).\n *\n * Empty / undefined = no prefix.\n */\n namespace?: string\n /**\n * Which Gen-AI attribute conventions to emit.\n *\n * - `'sentry'` (default) — Sentry AI Agents conventions. Most common\n * choice — Sentry, Datadog AI, Honeycomb derived dashboards and\n * Langfuse all consume these.\n * - `'otel'` — vendor-neutral OpenTelemetry Gen AI semconv v1.36.\n * Diverges from Sentry on cache-token keys, cost keys, and TTFT unit.\n * - `'both'` — emit every variant. Doubles the per-span attribute\n * payload; pick when shipping to multiple backends with different\n * ingestion conventions.\n */\n conventions?: TracingConventions\n /**\n * Capture the actual prompt / completion text on spans\n * (`gen_ai.input.messages`, `gen_ai.output.messages`,\n * `gen_ai.system_instructions`, `gen_ai.tool.definitions`,\n * `gen_ai.tool.call.arguments`, `gen_ai.tool.call.result`).\n *\n * Defaults to `true` (required for the Sentry AI Agents conversation\n * viewer). Full prompts and tool arguments can carry secrets and PII;\n * captured content flows through `redact`, so pair the default with a\n * redactor when payloads may hold secrets, or set `false` to keep all\n * content-shaped data in-process.\n */\n captureMessageContent?: boolean\n /**\n * Emit legacy attribute keys (`inputTokens`, `outputTokens`, `toolName`,\n * `displayName`, `finishReason`, `modelId`) alongside the new\n * `gen_ai.*` semconv keys. Defaults to `true` so existing dashboards\n * keep working. Set `false` once you've migrated all consumers off the\n * legacy names.\n */\n legacyAttributes?: boolean\n /**\n * Error sink for tracer failures. Replaces the historic silent-swallow.\n * Called when `Span.end()`, `setAttributes()`, `addEvent()`, or\n * `startSpan()` throws — so degraded tracers stay visible. The helper\n * still does not let tracer errors propagate into the agent loop.\n *\n * `kind` identifies which step failed (`'startSpan'`, `'end'`,\n * `'setAttributes'`, `'addEvent'`, `'redact'`, `'getActiveTraceContext'`).\n * Default: no-op (silent).\n */\n onError?: (kind: string, err: unknown) => void\n /**\n * Optional callback the helper invokes on `spawn:before` (after opening\n * the spawn span) to read the active trace context. Whatever it returns\n * is written into `ctx.tracingContext` and forwarded to the child's\n * `agent.run({ tracingContext })`. Typical use:\n *\n * ```ts\n * getActiveTraceContext: () => {\n * const carrier: Record<string, string> = {}\n * propagation.inject(context.active(), carrier)\n * return carrier\n * }\n * ```\n *\n * Returning `undefined` or an empty object leaves the carrier empty\n * and the child runs with no parent context. The helper's own\n * `propagator` is preferred over this when both are set (the\n * propagator runs inside the helper; this callback is the escape\n * hatch for hosts wanting full control).\n */\n getActiveTraceContext?: () => Readonly<Record<string, string>> | undefined\n /**\n * Optional in-process span redactor. When set, every potentially\n * sensitive string (tool input, tool result, system prompt) flows\n * through this fn before landing on a span attribute / event. Returning\n * the input unchanged is a no-op.\n *\n * Composes with the harness-wide `tracing:redact` hook: if a host has\n * already registered a hook handler that mutates `ctx.redacted`, the\n * tracer reuses that pipeline; setting `redact` here registers an extra\n * step that runs only for the tracer's attributes. The hook is the\n * canonical path; `redact` is the convenience surface for tracer-only\n * setups.\n */\n redact?: (kind: string, value: string, meta?: Readonly<Record<string, unknown>>) => string\n}\n\n/** Value returned by {@link createTracingHooks} — install entrypoint. */\nexport interface TracingHookSet {\n /**\n * Attach every hook handler to the given agent hooks instance.\n *\n * @returns an `uninstall` function that detaches every handler and closes any\n * still-open spans. Safe to call multiple times.\n */\n install: (hooks: Hookable<AgentHooks>) => () => void\n}\n\n// ---------------------------------------------------------------------------\n// Gen AI semantic-convention attribute keys\n// ---------------------------------------------------------------------------\n\n/**\n * Stable keys we emit unconditionally — present in BOTH Sentry and OTel\n * conventions with identical semantics, so no `conventions` branching is\n * needed at the call site.\n */\nconst GEN_AI = {\n system: 'gen_ai.system',\n operationName: 'gen_ai.operation.name',\n requestModel: 'gen_ai.request.model',\n responseModel: 'gen_ai.response.model',\n responseFinishReasons: 'gen_ai.response.finish_reasons',\n responseId: 'gen_ai.response.id',\n responseStreaming: 'gen_ai.response.streaming',\n responseTokensPerSecond: 'gen_ai.response.tokens_per_second',\n // Sentry uses seconds; OTel uses ms. We emit one or both based on `conventions`.\n responseTimeToFirstTokenSeconds: 'gen_ai.response.time_to_first_token',\n responseTimeToFirstTokenMs: 'gen_ai.client.time_to_first_token',\n // Usage — Sentry expects input_tokens to INCLUDE cached, with subkeys for cache slices.\n usageInputTokens: 'gen_ai.usage.input_tokens',\n usageOutputTokens: 'gen_ai.usage.output_tokens',\n usageTotalTokens: 'gen_ai.usage.total_tokens',\n // Sentry-specific cache keys.\n usageInputTokensCached: 'gen_ai.usage.input_tokens.cached',\n usageInputTokensCacheWrite: 'gen_ai.usage.input_tokens.cache_write',\n usageOutputTokensReasoning: 'gen_ai.usage.output_tokens.reasoning',\n // OTel cache keys (older / vendor-neutral).\n usageCacheReadInputTokens: 'gen_ai.usage.cache_read_input_tokens',\n usageCacheCreationInputTokens: 'gen_ai.usage.cache_creation_input_tokens',\n usageReasoningTokens: 'gen_ai.usage.reasoning_tokens',\n // Cost — Sentry splits, OTel rolls up.\n costTotalTokens: 'gen_ai.cost.total_tokens',\n costInputTokens: 'gen_ai.cost.input_tokens',\n costOutputTokens: 'gen_ai.cost.output_tokens',\n usageCostUsd: 'gen_ai.usage.cost_usd',\n // Tool — Sentry uses `tool.call.arguments` / `tool.call.result`; OTel uses `tool.input` / `tool.output` (deprecated in Sentry).\n toolName: 'gen_ai.tool.name',\n toolDescription: 'gen_ai.tool.description',\n toolType: 'gen_ai.tool.type',\n toolCallId: 'gen_ai.tool.call.id',\n toolCallArguments: 'gen_ai.tool.call.arguments',\n toolCallResult: 'gen_ai.tool.call.result',\n toolMessage: 'gen_ai.tool.message',\n toolInputDeprecated: 'gen_ai.tool.input',\n toolOutputDeprecated: 'gen_ai.tool.output',\n // Request configuration.\n requestMaxTokens: 'gen_ai.request.max_tokens',\n requestTemperature: 'gen_ai.request.temperature',\n requestTopP: 'gen_ai.request.top_p',\n requestTopK: 'gen_ai.request.top_k',\n requestSeed: 'gen_ai.request.seed',\n requestFrequencyPenalty: 'gen_ai.request.frequency_penalty',\n requestPresencePenalty: 'gen_ai.request.presence_penalty',\n // Message content surfaces (Sentry conversation viewer).\n inputMessages: 'gen_ai.input.messages',\n outputMessages: 'gen_ai.output.messages',\n systemInstructions: 'gen_ai.system_instructions',\n toolDefinitions: 'gen_ai.tool.definitions',\n // Agent identity.\n agentName: 'gen_ai.agent.name',\n agentRunId: 'gen_ai.agent.run.id',\n agentParentRunId: 'gen_ai.agent.parent_run.id',\n agentDepth: 'gen_ai.agent.depth',\n pipelineName: 'gen_ai.pipeline.name',\n turn: 'gen_ai.agent.turn',\n // MCP-specific (zidane extension; no Sentry op equivalent).\n mcpServer: 'gen_ai.mcp.server',\n mcpToolCount: 'gen_ai.mcp.tool_count',\n} as const\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Compose namespace prefix once. */\nfunction prefixed(namespace: string | undefined, name: string): string {\n return namespace ? `${namespace}/${name}` : name\n}\n\n/** Drop undefined entries — tracers vary on how they render `undefined`. */\nfunction compact(attrs: Record<string, unknown>): Record<string, unknown> {\n const out: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(attrs)) {\n if (v !== undefined)\n out[k] = v\n }\n return out\n}\n\n/**\n * Convert a zidane `SessionMessage` (or `StreamOptions.messages` entry)\n * into Sentry's `{role, parts: [{type, content}]}` shape, JSON-stringify\n * the result. Returns `undefined` on serialization failure so the caller\n * skips the attribute rather than poisoning the span.\n *\n * Per Sentry footnote [7], binary blobs in image content are replaced\n * with `\"[Blob substitute]\"`. Tool calls become `{type: 'tool_call'}`\n * parts; tool results become `{type: 'tool_result'}` parts.\n */\ninterface SentryMessagePart {\n type: 'text' | 'tool_call' | 'tool_result' | 'reasoning'\n content?: string\n name?: string\n arguments?: string\n result?: string\n call_id?: string\n}\ninterface SentryMessage {\n role: 'user' | 'assistant' | 'system' | 'tool'\n parts: SentryMessagePart[]\n}\n\n/**\n * Wide-shape block record. `SessionContentBlock` is a discriminated\n * union — accepting the wide record lets us peek at the discriminator\n * without re-importing the entire union at this layer.\n */\ninterface AnyBlock { type: string, [k: string]: unknown }\n\nfunction blockToParts(block: AnyBlock): SentryMessagePart[] {\n switch (block.type) {\n case 'text':\n return [{ type: 'text', content: typeof block.text === 'string' ? block.text : '' }]\n case 'image':\n case 'audio':\n case 'video':\n case 'document':\n return [{ type: 'text', content: '[Blob substitute]' }]\n case 'tool_call':\n return [{\n type: 'tool_call',\n ...(typeof block.name === 'string' ? { name: block.name } : {}),\n ...(typeof block.id === 'string' ? { call_id: block.id } : {}),\n arguments: safeJson(block.input),\n }]\n case 'tool_result': {\n const output = block.output\n const text = typeof output === 'string'\n ? output\n : Array.isArray(output)\n ? (output as Array<{ type: string, text?: string }>)\n .map(b => (b.type === 'image' || b.type === 'audio' || b.type === 'video' || b.type === 'document') ? '[Blob substitute]' : (b.text ?? ''))\n .join('\\n')\n : ''\n return [{\n type: 'tool_result',\n ...(typeof block.callId === 'string' ? { call_id: block.callId } : {}),\n content: text,\n }]\n }\n case 'thinking':\n return [{ type: 'reasoning', content: typeof block.text === 'string' ? block.text : '' }]\n default:\n return []\n }\n}\n\nfunction messageToSentry(msg: { role: 'user' | 'assistant', content: readonly AnyBlock[] }): SentryMessage {\n const parts: SentryMessagePart[] = []\n for (const block of msg.content)\n parts.push(...blockToParts(block))\n return { role: msg.role, parts }\n}\n\nfunction safeJson(value: unknown): string {\n try {\n return JSON.stringify(value ?? null)\n }\n catch {\n return '\"[unserializable]\"'\n }\n}\n\n/**\n * Stringify an array of `SessionMessage`s into Sentry's `gen_ai.input.messages`\n * format. Each entry's role + content is mapped via `messageToSentry`.\n * Returns `undefined` when the input is empty or unserializable.\n */\nfunction stringifyMessages(messages: readonly SessionMessage[]): string | undefined {\n if (messages.length === 0)\n return undefined\n try {\n const sentry = messages.map(m => messageToSentry({\n role: m.role,\n content: m.content as readonly AnyBlock[],\n }))\n return JSON.stringify(sentry)\n }\n catch {\n return undefined\n }\n}\n\n/**\n * Stringify a `StreamOptions.tools` array into Sentry's\n * `gen_ai.tool.definitions` format: `[{name, description?, parameters?}, …]`.\n *\n * The input is `unknown[]` because `StreamOptions.tools` carries the\n * provider-formatted shape (Anthropic / OpenAI / OpenAI-compat all\n * differ). We probe the three names that overlap (`name`, `description`,\n * `inputSchema` / `parameters`) and degrade gracefully otherwise.\n */\nfunction stringifyToolDefs(tools: readonly unknown[] | undefined): string | undefined {\n if (!tools || tools.length === 0)\n return undefined\n try {\n return JSON.stringify(tools.map((raw) => {\n if (!raw || typeof raw !== 'object')\n return { name: 'anonymous' }\n const t = raw as Record<string, unknown>\n const name = typeof t.name === 'string' ? t.name : 'anonymous'\n const description = typeof t.description === 'string' ? t.description : undefined\n // OpenAI's wire format calls it `parameters`; Anthropic / ToolSpec\n // call it `input_schema` / `inputSchema`. Probe all three.\n const parameters = t.parameters ?? t.input_schema ?? t.inputSchema\n return {\n name,\n ...(description ? { description } : {}),\n ...(parameters !== undefined ? { parameters } : {}),\n }\n }))\n }\n catch {\n return undefined\n }\n}\n\n/**\n * Build the usage attribute set per the configured conventions. Sentry\n * expects `input_tokens` to include cached, with `input_tokens.cached`\n * + `input_tokens.cache_write` subkeys; OTel splits cache into\n * separate top-level keys. `'both'` emits everything.\n */\nfunction buildUsageAttrs(\n usage: {\n input: number\n output: number\n cacheRead?: number\n cacheCreation?: number\n thinking?: number\n cost?: number\n modelId?: string\n finishReason?: string\n timeToFirstTokenMs?: number\n },\n conventions: TracingConventions,\n): Record<string, unknown> {\n const cacheRead = usage.cacheRead ?? 0\n const cacheWrite = usage.cacheCreation ?? 0\n // Per Sentry: `input_tokens` includes cached. zidane reports `input` as\n // the uncached new-input slice, so the headline number is the sum.\n const inputInclCache = usage.input + cacheRead + cacheWrite\n const totalTokens = inputInclCache + usage.output\n const wantSentry = conventions === 'sentry' || conventions === 'both'\n const wantOtel = conventions === 'otel' || conventions === 'both'\n\n const out: Record<string, unknown> = {\n [GEN_AI.responseModel]: usage.modelId,\n [GEN_AI.responseFinishReasons]: usage.finishReason ? [usage.finishReason] : undefined,\n [GEN_AI.usageOutputTokens]: usage.output,\n [GEN_AI.usageTotalTokens]: totalTokens,\n }\n\n if (wantSentry) {\n out[GEN_AI.usageInputTokens] = inputInclCache\n if (cacheRead > 0)\n out[GEN_AI.usageInputTokensCached] = cacheRead\n if (cacheWrite > 0)\n out[GEN_AI.usageInputTokensCacheWrite] = cacheWrite\n if (typeof usage.thinking === 'number' && usage.thinking > 0)\n out[GEN_AI.usageOutputTokensReasoning] = usage.thinking\n if (typeof usage.cost === 'number')\n out[GEN_AI.costTotalTokens] = usage.cost\n if (typeof usage.timeToFirstTokenMs === 'number')\n out[GEN_AI.responseTimeToFirstTokenSeconds] = usage.timeToFirstTokenMs / 1000\n }\n if (wantOtel) {\n // OTel: input_tokens is the uncached slice; cache is separate.\n if (!wantSentry)\n out[GEN_AI.usageInputTokens] = usage.input\n if (cacheRead > 0)\n out[GEN_AI.usageCacheReadInputTokens] = cacheRead\n if (cacheWrite > 0)\n out[GEN_AI.usageCacheCreationInputTokens] = cacheWrite\n if (typeof usage.thinking === 'number' && usage.thinking > 0)\n out[GEN_AI.usageReasoningTokens] = usage.thinking\n if (typeof usage.cost === 'number')\n out[GEN_AI.usageCostUsd] = usage.cost\n if (typeof usage.timeToFirstTokenMs === 'number')\n out[GEN_AI.responseTimeToFirstTokenMs] = usage.timeToFirstTokenMs\n }\n return out\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Build a set of tracing hook handlers that can be installed on an agent.\n *\n * @example Sentry\n * ```ts\n * const tracing = createTracingHooks({\n * startSpan: (name, attrs) => Sentry.startInactiveSpan({ name, attributes: attrs }),\n * })\n * const uninstall = tracing.install(agent.hooks)\n * try { await agent.run({ prompt }) }\n * finally { uninstall() }\n * ```\n *\n * @example OpenTelemetry with parent-context propagation\n * ```ts\n * import { trace, context, propagation } from '@opentelemetry/api'\n *\n * const tracer = trace.getTracer('zidane')\n * const tracing = createTracingHooks({\n * startSpan: (name, attrs, parentCtx) => {\n * const ctx = parentCtx ? propagation.extract(context.active(), parentCtx) : context.active()\n * return tracer.startSpan(name, { attributes: attrs }, ctx)\n * },\n * getActiveTraceContext: () => {\n * const carrier: Record<string, string> = {}\n * propagation.inject(context.active(), carrier)\n * return carrier\n * },\n * })\n * ```\n */\nexport function createTracingHooks(options: TracingHooksOptions): TracingHookSet {\n const namespace = options.namespace\n const legacy = options.legacyAttributes ?? true\n const conventions = options.conventions ?? 'sentry'\n const wantSentry = conventions === 'sentry' || conventions === 'both'\n const captureContent = options.captureMessageContent !== false\n const onError = options.onError ?? (() => {})\n\n return {\n install(hooks: Hookable<AgentHooks>): () => void {\n // Span book-keeping. Each map keys by the identifier that's unique\n // across concurrent in-flight spans of that kind.\n const runSpans = new Map<string, Span>() // keyed by runId\n const turnSpans = new Map<string, Span>() // keyed by turnId\n const toolSpans = new Map<string, Span>() // keyed by callId\n const mcpSpans = new Map<string, Span>() // keyed by callId\n const spawnSpans = new Map<string, Span>() // keyed by spawn id\n const bootstrapSpans = new Map<string, Span>() // keyed by server name\n // Provider system identifier (`anthropic`, `openai`, …) captured at\n // `agent:start`. Stamped onto every chat / tool span belonging to\n // this run so Sentry's per-vendor breakdowns work. Reset on\n // `agent:done`.\n let activeSystem: string | undefined\n // Active turn book-keeping for tokens-per-second derivation.\n const turnTrack = new Map<string, { startedAt: number }>()\n // Active run span ref — used so non-span events (gate blocks,\n // budget hits, validation rejects, policy/pairing repairs) can\n // attach via `addEvent` on the in-flight run span. The run span is\n // the natural parent for cross-cutting policy events: it outlives\n // turn spans (which close before tool dispatch starts in the\n // current loop), and tool spans (which haven't opened on a\n // gate-block path).\n let activeRunSpan: Span | undefined\n\n function safeStartSpan(\n name: string,\n attrs: Record<string, unknown>,\n parentContext?: Readonly<Record<string, string>>,\n ): Span | undefined {\n try {\n return options.startSpan(prefixed(namespace, name), compact(attrs), parentContext)\n }\n catch (err) {\n try {\n onError('startSpan', err)\n }\n catch { /* ignore */ }\n return undefined\n }\n }\n\n function safeSetAttrs(span: Span | undefined, attrs: Record<string, unknown>): void {\n if (!span?.setAttributes)\n return\n try {\n span.setAttributes(compact(attrs))\n }\n catch (err) {\n try {\n onError('setAttributes', err)\n }\n catch { /* ignore */ }\n }\n }\n\n function safeEnd(map: Map<string, Span>, key: string): void {\n const span = map.get(key)\n if (!span)\n return\n try {\n span.end()\n }\n catch (err) {\n try {\n onError('end', err)\n }\n catch { /* ignore */ }\n }\n map.delete(key)\n }\n\n function endAll(map: Map<string, Span>): void {\n for (const [, span] of map) {\n try {\n span.end()\n }\n catch (err) {\n try {\n onError('end', err)\n }\n catch { /* ignore */ }\n }\n }\n map.clear()\n }\n\n function safeAddEvent(span: Span | undefined, name: string, attrs: Record<string, unknown>): void {\n if (!span)\n return\n if (typeof span.addEvent === 'function') {\n try {\n span.addEvent(name, compact(attrs))\n }\n catch (err) {\n try {\n onError('addEvent', err)\n }\n catch { /* ignore */ }\n }\n return\n }\n // Tracer doesn't support events — fall back to a single\n // `event.<name>.*` attribute so the data lands somewhere.\n safeSetAttrs(span, Object.fromEntries(\n Object.entries(compact(attrs)).map(([k, v]) => [`event.${name}.${k}`, v]),\n ))\n }\n\n function redact(kind: string, value: string, meta?: Readonly<Record<string, unknown>>): string {\n if (typeof options.redact !== 'function')\n return value\n try {\n return options.redact(kind, value, meta)\n }\n catch (err) {\n try {\n onError('redact', err)\n }\n catch { /* ignore */ }\n return value\n }\n }\n\n const unregisters: Array<() => void> = []\n\n // -----------------------------------------------------------------\n // agent.run (root)\n // -----------------------------------------------------------------\n\n unregisters.push(hooks.hook('agent:start', (ctx) => {\n activeSystem = ctx.providerName\n // Empty-string names produce `invoke_agent ` (trailing space).\n // Treat empty as \"no name\" so the span name reads cleanly.\n const displayName = ctx.agentName && ctx.agentName.length > 0\n ? ctx.agentName\n : 'agent'\n const span = safeStartSpan(\n `invoke_agent ${displayName}`,\n {\n [GEN_AI.operationName]: 'invoke_agent',\n [GEN_AI.system]: activeSystem,\n [GEN_AI.agentName]: ctx.agentName,\n [GEN_AI.agentRunId]: ctx.runId,\n [GEN_AI.agentParentRunId]: ctx.parentRunId,\n [GEN_AI.agentDepth]: ctx.depth,\n // Sentry-specific span op hint — backends that key off\n // `op` (Sentry Python/JS) read this directly; OTel\n // backends ignore unknown keys, so it's harmless.\n 'sentry.op': 'gen_ai.invoke_agent',\n ...(legacy\n ? {\n runId: ctx.runId,\n parentRunId: ctx.parentRunId,\n agentName: ctx.agentName,\n depth: ctx.depth,\n }\n : {}),\n },\n ctx.tracingContext,\n )\n if (span) {\n runSpans.set(ctx.runId, span)\n activeRunSpan = span\n }\n }))\n\n unregisters.push(hooks.hook('agent:done', (stats) => {\n // `agent:done` doesn't carry a runId on its payload (it's `AgentStats`).\n // The 99% case is a single in-flight run — close every open run span\n // and stamp the totals on each. Multi-run-concurrent users plug a\n // runId into stats via their own integration; for now we close all.\n const usageAttrs = buildUsageAttrs({\n input: stats.totalIn,\n output: stats.totalOut,\n cacheRead: stats.totalCacheRead,\n cacheCreation: stats.totalCacheCreation,\n cost: stats.cost,\n timeToFirstTokenMs: stats.timeTillFirstTokenMs,\n }, conventions)\n for (const [, span] of runSpans) {\n safeSetAttrs(span, {\n ...usageAttrs,\n [GEN_AI.turn]: stats.turns,\n 'gen_ai.agent.elapsed_ms': stats.elapsed,\n ...(legacy\n ? {\n totalIn: stats.totalIn,\n totalOut: stats.totalOut,\n totalCacheRead: stats.totalCacheRead,\n totalCacheCreation: stats.totalCacheCreation,\n cost: stats.cost,\n turns: stats.turns,\n elapsed: stats.elapsed,\n }\n : {}),\n })\n }\n endAll(runSpans)\n // Defensive — close anything else still hanging open.\n endAll(turnSpans)\n endAll(toolSpans)\n endAll(mcpSpans)\n endAll(spawnSpans)\n endAll(bootstrapSpans)\n activeRunSpan = undefined\n activeSystem = undefined\n turnTrack.clear()\n }))\n\n unregisters.push(hooks.hook('agent:abort', () => {\n for (const [, span] of runSpans)\n safeSetAttrs(span, { 'gen_ai.agent.status': 'aborted' })\n }))\n\n // -----------------------------------------------------------------\n // Turn span — OTel Gen AI `chat` operation\n // -----------------------------------------------------------------\n\n unregisters.push(hooks.hook('turn:before', (ctx) => {\n const requestedModel = ctx.options.model\n // Built-in Sentry/OTel attrs that don't depend on content capture.\n const baseAttrs: Record<string, unknown> = {\n [GEN_AI.operationName]: 'chat',\n [GEN_AI.system]: activeSystem,\n [GEN_AI.requestModel]: requestedModel,\n [GEN_AI.turn]: ctx.turn,\n [GEN_AI.responseStreaming]: true,\n [GEN_AI.requestMaxTokens]: ctx.options.maxTokens,\n 'sentry.op': 'gen_ai.chat',\n 'gen_ai.agent.turn_id': ctx.turnId,\n ...(legacy ? { turnId: ctx.turnId, turn: ctx.turn } : {}),\n }\n // Optionally surface conversation content — prompts, system\n // instructions, available tools. Each piece flows through the\n // redact seam so hosts can scrub secrets without losing the\n // structural attribute. Skipping content keeps the span cheap\n // and privacy-safe.\n if (captureContent) {\n const systemText = ctx.options.system\n if (systemText) {\n baseAttrs[GEN_AI.systemInstructions] = redact('system', systemText, {\n turnId: ctx.turnId,\n turn: ctx.turn,\n })\n }\n const inputMsgs = stringifyMessages(ctx.options.messages)\n if (inputMsgs) {\n baseAttrs[GEN_AI.inputMessages] = redact('prompt', inputMsgs, {\n turnId: ctx.turnId,\n turn: ctx.turn,\n })\n }\n const toolDefs = stringifyToolDefs(ctx.options.tools)\n if (toolDefs)\n baseAttrs[GEN_AI.toolDefinitions] = toolDefs\n }\n const span = safeStartSpan(\n `chat${requestedModel ? ` ${requestedModel}` : ''}`,\n baseAttrs,\n )\n if (span) {\n turnSpans.set(ctx.turnId, span)\n turnTrack.set(ctx.turnId, { startedAt: Date.now() })\n }\n }))\n\n unregisters.push(hooks.hook('stream:start', (ctx) => {\n safeAddEvent(turnSpans.get(ctx.turnId), 'gen_ai.stream.start', {\n startedAt: ctx.startedAt,\n })\n }))\n\n unregisters.push(hooks.hook('turn:after', (ctx) => {\n const span = turnSpans.get(ctx.turnId)\n const tracked = turnTrack.get(ctx.turnId)\n turnTrack.delete(ctx.turnId)\n // Tokens-per-second derivation. Use wall-clock duration since the\n // turn span opened, fall back to TTFT-adjusted slice when present.\n let tokensPerSecond: number | undefined\n if (tracked && ctx.usage.output > 0) {\n const elapsedMs = Date.now() - tracked.startedAt\n if (elapsedMs > 0)\n tokensPerSecond = (ctx.usage.output / elapsedMs) * 1000\n }\n const usageAttrs = buildUsageAttrs(ctx.usage, conventions)\n // Cumulative-through-this-turn totals. We compute `input_tokens`\n // here to mirror `gen_ai.usage.input_tokens`'s semantic on the\n // same span: it INCLUDES cached when Sentry conventions are in\n // play, matches the uncached slice when OTel-only. Without this\n // alignment the two keys (`usage.*` vs `cumulative.*`) would\n // silently differ in arithmetic.\n const cumInputTokens = wantSentry\n ? ctx.cumulativeUsage.input + ctx.cumulativeUsage.cacheRead + ctx.cumulativeUsage.cacheCreation\n : ctx.cumulativeUsage.input\n const attrs: Record<string, unknown> = {\n ...usageAttrs,\n [GEN_AI.responseTokensPerSecond]: tokensPerSecond,\n 'gen_ai.agent.cumulative.input_tokens': cumInputTokens,\n 'gen_ai.agent.cumulative.output_tokens': ctx.cumulativeUsage.output,\n 'gen_ai.agent.cumulative.total_tokens': cumInputTokens + ctx.cumulativeUsage.output,\n 'gen_ai.agent.cumulative.cache_read_input_tokens': ctx.cumulativeUsage.cacheRead,\n 'gen_ai.agent.cumulative.cache_creation_input_tokens': ctx.cumulativeUsage.cacheCreation,\n 'gen_ai.agent.cumulative.cost_usd': ctx.cumulativeUsage.cost,\n 'gen_ai.agent.cumulative.turns': ctx.cumulativeUsage.turns,\n ...(legacy\n ? {\n inputTokens: ctx.usage.input,\n outputTokens: ctx.usage.output,\n finishReason: ctx.usage.finishReason,\n modelId: ctx.usage.modelId,\n }\n : {}),\n }\n // Output messages — the assistant turn that just landed. Sentry's\n // conversation viewer keys off this; skipped when content capture\n // is off. `system` turns aren't serialized (Sentry's schema only\n // accepts user / assistant / system / tool — and we never emit\n // system role from the loop).\n if (captureContent && ctx.message && ctx.message.role !== 'system' && ctx.message.role !== 'command') {\n const outputMsgs = stringifyMessages([{\n role: ctx.message.role,\n content: ctx.message.content,\n }])\n if (outputMsgs)\n attrs[GEN_AI.outputMessages] = redact('stream-text', outputMsgs, { turnId: ctx.turnId, turn: ctx.turn })\n }\n safeSetAttrs(span, attrs)\n safeEnd(turnSpans, ctx.turnId)\n }))\n\n unregisters.push(hooks.hook('stream:error', (ctx) => {\n const span = turnSpans.get(ctx.turnId)\n const message = ctx.err instanceof Error ? ctx.err.message : String(ctx.err)\n safeSetAttrs(span, {\n 'error.type': ctx.err instanceof Error ? ctx.err.name : 'unknown',\n 'error.message': message,\n 'http.response.status_code': ctx.statusCode,\n 'gen_ai.request.id': ctx.requestId,\n })\n safeAddEvent(span, 'gen_ai.stream.error', {\n 'error.type': ctx.err instanceof Error ? ctx.err.name : 'unknown',\n 'error.message': message,\n 'http.response.status_code': ctx.statusCode,\n 'gen_ai.request.id': ctx.requestId,\n })\n }))\n\n // -----------------------------------------------------------------\n // Native tool span — OTel Gen AI `execute_tool` operation\n // -----------------------------------------------------------------\n\n unregisters.push(hooks.hook('tool:before', (ctx) => {\n const span = safeStartSpan(\n `execute_tool ${ctx.displayName}`,\n {\n [GEN_AI.operationName]: 'execute_tool',\n [GEN_AI.system]: activeSystem,\n [GEN_AI.toolName]: ctx.name,\n [GEN_AI.toolType]: 'function',\n [GEN_AI.toolCallId]: ctx.callId,\n 'sentry.op': 'gen_ai.execute_tool',\n 'gen_ai.tool.display_name': ctx.displayName,\n 'gen_ai.agent.turn_id': ctx.turnId,\n 'gen_ai.agent.depth': ctx.depth,\n 'gen_ai.agent.run.id': ctx.runId,\n 'gen_ai.agent.parent_run.id': ctx.parentRunId,\n ...(legacy\n ? {\n toolName: ctx.name,\n displayName: ctx.displayName,\n turnId: ctx.turnId,\n callId: ctx.callId,\n }\n : {}),\n },\n )\n if (span) {\n toolSpans.set(ctx.callId, span)\n if (captureContent) {\n // Attach (possibly redacted) input as an attribute. Sentry's\n // canonical key is `gen_ai.tool.call.arguments`; OTel's\n // older key is `gen_ai.tool.input` (now deprecated in\n // Sentry). Emit one or both based on `conventions`.\n try {\n const serialized = JSON.stringify(ctx.input)\n const redacted = redact('tool-input', serialized, {\n toolName: ctx.name,\n displayName: ctx.displayName,\n callId: ctx.callId,\n turnId: ctx.turnId,\n })\n const attrs: Record<string, unknown> = {}\n if (wantSentry)\n attrs[GEN_AI.toolCallArguments] = redacted\n if (conventions === 'otel' || conventions === 'both')\n attrs[GEN_AI.toolInputDeprecated] = redacted\n safeSetAttrs(span, attrs)\n }\n catch {\n // Non-serializable input (circular refs) — skip the attr;\n // the tool name and call id are still on the span.\n }\n }\n }\n }))\n\n unregisters.push(hooks.hook('tool:after', (ctx) => {\n const span = toolSpans.get(ctx.callId)\n safeSetAttrs(span, {\n 'gen_ai.tool.output_bytes': ctx.outputBytes,\n 'gen_ai.tool.coercions': ctx.coercions?.length ?? 0,\n })\n if (captureContent && typeof ctx.result === 'string') {\n const redacted = redact('tool-result', ctx.result, {\n toolName: ctx.name,\n callId: ctx.callId,\n })\n const attrs: Record<string, unknown> = {}\n if (wantSentry)\n attrs[GEN_AI.toolCallResult] = redacted\n if (conventions === 'otel' || conventions === 'both')\n attrs[GEN_AI.toolOutputDeprecated] = redacted\n safeSetAttrs(span, attrs)\n }\n safeEnd(toolSpans, ctx.callId)\n }))\n\n unregisters.push(hooks.hook('tool:error', (ctx) => {\n safeSetAttrs(toolSpans.get(ctx.callId), {\n 'error.type': ctx.error.name,\n 'error.message': ctx.error.message,\n })\n safeEnd(toolSpans, ctx.callId)\n }))\n\n // Tool gate / dispatch outcomes. Listen on `tool:dispatched` (fires\n // AFTER every `tool:gate` listener has had a chance to mutate\n // `block` / `result`) so the recorded `outcome` reflects the final\n // decision rather than the as-entered state. Attaches to the\n // in-flight run span; the chat span has already closed by tool\n // dispatch time in the current loop ordering, and no tool span has\n // opened for a `gate-block` / `unknown` / `invalid-input` path.\n unregisters.push(hooks.hook('tool:dispatched', (ctx) => {\n if (ctx.outcome === 'gate-block') {\n safeAddEvent(activeRunSpan, 'gen_ai.gate.block', {\n [GEN_AI.toolName]: ctx.name,\n [GEN_AI.toolCallId]: ctx.callId,\n reason: ctx.reason,\n })\n }\n else if (ctx.outcome === 'gate-substitute') {\n safeAddEvent(activeRunSpan, 'gen_ai.gate.substitute', {\n [GEN_AI.toolName]: ctx.name,\n [GEN_AI.toolCallId]: ctx.callId,\n })\n }\n else if (ctx.outcome === 'unknown') {\n safeAddEvent(activeRunSpan, 'gen_ai.tool.unknown', {\n [GEN_AI.toolName]: ctx.name,\n [GEN_AI.toolCallId]: ctx.callId,\n })\n }\n else if (ctx.outcome === 'invalid-input') {\n safeAddEvent(activeRunSpan, 'gen_ai.tool.invalid_input', {\n [GEN_AI.toolName]: ctx.name,\n [GEN_AI.toolCallId]: ctx.callId,\n })\n }\n }))\n\n unregisters.push(hooks.hook('validation:reject', (ctx) => {\n safeAddEvent(activeRunSpan, 'gen_ai.validation.reject', {\n [GEN_AI.toolName]: ctx.name,\n [GEN_AI.toolCallId]: ctx.callId,\n reason: ctx.reason,\n })\n }))\n\n unregisters.push(hooks.hook('validation:coerce', (ctx) => {\n safeAddEvent(activeRunSpan, 'gen_ai.validation.coerce', {\n [GEN_AI.toolName]: ctx.name,\n [GEN_AI.toolCallId]: ctx.callId,\n coercions: ctx.coercions,\n })\n }))\n\n unregisters.push(hooks.hook('budget:exceeded', (ctx) => {\n safeAddEvent(activeRunSpan, 'gen_ai.budget.exceeded', {\n turnId: ctx.turnId,\n bytes: ctx.bytes,\n budget: ctx.budget,\n })\n }))\n\n unregisters.push(hooks.hook('tool-budget:exceeded', (ctx) => {\n safeAddEvent(activeRunSpan, 'gen_ai.tool_budget.exceeded', {\n [GEN_AI.toolName]: ctx.tool,\n turnId: ctx.turnId,\n count: ctx.count,\n max: ctx.max,\n mode: ctx.mode,\n })\n }))\n\n unregisters.push(hooks.hook('pairing:repair', (ctx) => {\n safeAddEvent(activeRunSpan, 'gen_ai.pairing.repair', {\n mode: ctx.mode,\n callId: ctx.callId,\n turnId: ctx.turnId,\n })\n }))\n\n unregisters.push(hooks.hook('oauth:refresh', (ctx) => {\n safeAddEvent(activeRunSpan, 'gen_ai.oauth.refresh', {\n provider: ctx.provider,\n providerId: ctx.providerId,\n source: ctx.source,\n })\n }))\n\n // -----------------------------------------------------------------\n // MCP tool span — OTel Gen AI `execute_tool` operation, MCP variant\n // -----------------------------------------------------------------\n\n unregisters.push(hooks.hook('mcp:tool:before', (ctx) => {\n const span = safeStartSpan(\n `execute_tool ${ctx.server}.${ctx.tool}`,\n {\n [GEN_AI.operationName]: 'execute_tool',\n [GEN_AI.system]: activeSystem,\n [GEN_AI.toolName]: ctx.displayName,\n // MCP tools are Sentry's \"extension\" tool type — backed by an\n // out-of-process server rather than a local function.\n [GEN_AI.toolType]: 'extension',\n [GEN_AI.toolCallId]: ctx.callId,\n [GEN_AI.mcpServer]: ctx.server,\n 'sentry.op': 'gen_ai.execute_tool',\n 'gen_ai.mcp.tool': ctx.tool,\n 'gen_ai.agent.turn_id': ctx.turnId,\n 'gen_ai.agent.run.id': ctx.runId,\n ...(legacy\n ? {\n server: ctx.server,\n tool: ctx.tool,\n displayName: ctx.displayName,\n turnId: ctx.turnId,\n callId: ctx.callId,\n }\n : {}),\n },\n )\n if (span) {\n mcpSpans.set(ctx.callId, span)\n if (captureContent) {\n try {\n const redacted = redact('mcp-tool-input', JSON.stringify(ctx.input), {\n server: ctx.server,\n tool: ctx.tool,\n callId: ctx.callId,\n })\n const attrs: Record<string, unknown> = {}\n if (wantSentry)\n attrs[GEN_AI.toolCallArguments] = redacted\n if (conventions === 'otel' || conventions === 'both')\n attrs[GEN_AI.toolInputDeprecated] = redacted\n safeSetAttrs(span, attrs)\n }\n catch { /* ignore */ }\n }\n }\n }))\n\n unregisters.push(hooks.hook('mcp:tool:after', (ctx) => {\n const span = mcpSpans.get(ctx.callId)\n safeSetAttrs(span, { 'gen_ai.tool.output_bytes': ctx.outputBytes })\n if (captureContent && typeof ctx.result === 'string') {\n const redacted = redact('mcp-tool-result', ctx.result, {\n server: ctx.server,\n tool: ctx.tool,\n callId: ctx.callId,\n })\n const attrs: Record<string, unknown> = {}\n if (wantSentry)\n attrs[GEN_AI.toolCallResult] = redacted\n if (conventions === 'otel' || conventions === 'both')\n attrs[GEN_AI.toolOutputDeprecated] = redacted\n safeSetAttrs(span, attrs)\n }\n safeEnd(mcpSpans, ctx.callId)\n }))\n\n unregisters.push(hooks.hook('mcp:tool:error', (ctx) => {\n safeSetAttrs(mcpSpans.get(ctx.callId), {\n 'error.type': ctx.error.name,\n 'error.message': ctx.error.message,\n })\n safeEnd(mcpSpans, ctx.callId)\n }))\n\n // -----------------------------------------------------------------\n // MCP server bootstrap span\n // -----------------------------------------------------------------\n\n unregisters.push(hooks.hook('mcp:bootstrap:start', (ctx) => {\n const span = safeStartSpan(\n `mcp.bootstrap ${ctx.name}`,\n {\n [GEN_AI.operationName]: 'mcp.bootstrap',\n [GEN_AI.system]: activeSystem,\n [GEN_AI.mcpServer]: ctx.name,\n 'gen_ai.mcp.transport': ctx.transport,\n },\n )\n if (span)\n bootstrapSpans.set(ctx.name, span)\n }))\n\n unregisters.push(hooks.hook('mcp:bootstrap:end', (ctx) => {\n const span = bootstrapSpans.get(ctx.name)\n const ok = ctx.ok\n safeSetAttrs(span, {\n 'gen_ai.mcp.bootstrap.duration_ms': ctx.durationMs,\n 'gen_ai.mcp.bootstrap.ok': ok,\n ...(ok\n ? {\n [GEN_AI.mcpToolCount]: ctx.toolCount,\n 'gen_ai.mcp.lazy': ctx.lazy,\n 'gen_ai.mcp.cached': ctx.cached,\n }\n : {\n 'error.type': ctx.error.name,\n 'error.message': ctx.error.message,\n }),\n })\n safeEnd(bootstrapSpans, ctx.name)\n }))\n\n unregisters.push(hooks.hook('mcp:auth:required', (ctx) => {\n const span = bootstrapSpans.get(ctx.name)\n safeAddEvent(span ?? activeRunSpan, 'gen_ai.mcp.auth.required', {\n server: ctx.name,\n transport: ctx.transport,\n reason: ctx.reason,\n })\n }))\n\n unregisters.push(hooks.hook('mcp:error', (ctx) => {\n safeAddEvent(activeRunSpan, 'gen_ai.mcp.error', {\n 'server': ctx.name,\n 'error.type': ctx.error.name,\n 'error.message': ctx.error.message,\n })\n }))\n\n // -----------------------------------------------------------------\n // Spawn span — parent-side. Also injects trace context for the child.\n // -----------------------------------------------------------------\n\n unregisters.push(hooks.hook('spawn:before', (ctx) => {\n // Sentry: \"handoff from {from_agent} to {to_agent}\". We don't\n // always know the child agent's name at spawn time — fall back\n // to the truncated task as the destination identifier.\n const toAgent = ctx.task.length > 60 ? `${ctx.task.slice(0, 57)}…` : ctx.task\n const span = safeStartSpan(\n `handoff to ${toAgent}`,\n {\n [GEN_AI.operationName]: 'handoff',\n [GEN_AI.system]: activeSystem,\n 'sentry.op': 'gen_ai.handoff',\n 'gen_ai.agent.spawn.id': ctx.id,\n 'gen_ai.agent.spawn.task': ctx.task,\n 'gen_ai.agent.depth': ctx.depth,\n },\n )\n if (span)\n spawnSpans.set(ctx.id, span)\n\n // Inject parent trace context for the child agent. The spawn tool\n // passes whatever we write into `ctx.tracingContext` to the child's\n // `agent.run({ tracingContext })` — see `tools/spawn.ts`.\n if (typeof options.getActiveTraceContext === 'function' && ctx.tracingContext) {\n let carrier: Readonly<Record<string, string>> | undefined\n try {\n carrier = options.getActiveTraceContext()\n }\n catch (err) {\n try {\n onError('getActiveTraceContext', err)\n }\n catch { /* ignore */ }\n }\n if (carrier) {\n for (const [k, v] of Object.entries(carrier))\n ctx.tracingContext[k] = v\n }\n }\n }))\n\n unregisters.push(hooks.hook('spawn:complete', (ctx) => {\n const span = spawnSpans.get(ctx.id)\n const childUsage = buildUsageAttrs({\n input: ctx.stats.totalIn,\n output: ctx.stats.totalOut,\n cacheRead: ctx.stats.totalCacheRead,\n cacheCreation: ctx.stats.totalCacheCreation,\n cost: ctx.stats.cost,\n }, conventions)\n safeSetAttrs(span, {\n 'gen_ai.agent.spawn.status': ctx.status ?? 'completed',\n 'gen_ai.agent.spawn.depth': ctx.depth,\n ...childUsage,\n })\n safeEnd(spawnSpans, ctx.id)\n }))\n\n unregisters.push(hooks.hook('spawn:error', (ctx) => {\n safeSetAttrs(spawnSpans.get(ctx.id), {\n 'error.type': ctx.error.name,\n 'error.message': ctx.error.message,\n })\n safeEnd(spawnSpans, ctx.id)\n }))\n\n // -----------------------------------------------------------------\n // Disposal\n // -----------------------------------------------------------------\n\n let disposed = false\n return function uninstall() {\n if (disposed)\n return\n disposed = true\n for (const un of unregisters) {\n try {\n un()\n }\n catch { /* ignore */ }\n }\n endAll(runSpans)\n endAll(turnSpans)\n endAll(toolSpans)\n endAll(mcpSpans)\n endAll(spawnSpans)\n endAll(bootstrapSpans)\n activeRunSpan = undefined\n }\n },\n }\n}\n\n/**\n * OpenTelemetry Gen AI semantic-convention attribute keys used by the\n * built-in tracer. Exported so consumers integrating with raw OTel SDKs\n * (instead of going through `createTracingHooks`) can stay aligned with\n * the names the harness itself emits.\n */\nexport const GEN_AI_ATTRIBUTES = GEN_AI\n"],"mappings":"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"}
1
+ {"version":3,"file":"src-CjkY6fBj.js","names":["prefixed"],"sources":["../src/behaviors/local-reliability.ts","../src/chat/commands.ts","../src/zod.ts","../src/chat/generate-title.ts","../src/chat/generate-data.ts","../src/chat/inline-autocomplete.ts","../src/extension-authoring.ts","../src/mcp/oauth-callback.ts","../src/mcp/login.ts","../src/metrics.ts","../src/mutation-validation.ts","../src/tracing.ts"],"sourcesContent":["import type { AgentBehavior } from '../types'\n\nconst DEFAULT_LOCAL_RELIABILITY: AgentBehavior = {\n maxConcurrentTools: 3,\n readFileDefaults: {\n limit: 400,\n maxBytes: 64 * 1024,\n },\n toolOutputBudget: 32 * 1024,\n textToolCallRescue: { mode: 'steer' },\n thinkingOverrunPolicy: {\n mode: 'steer-next-turn',\n thresholdTokens: 2048,\n },\n}\n\n/**\n * Opt-in behavior overlay for brittle/local OpenAI-compatible endpoints.\n * Returns plain `AgentBehavior` so hosts can spread it before or after their\n * own overrides; no built-in profile uses this helper by default.\n */\nexport function localReliabilityBehavior(overrides: AgentBehavior = {}): AgentBehavior {\n return {\n ...DEFAULT_LOCAL_RELIABILITY,\n ...overrides,\n readFileDefaults: {\n ...DEFAULT_LOCAL_RELIABILITY.readFileDefaults,\n ...(overrides.readFileDefaults ?? {}),\n },\n textToolCallRescue: overrides.textToolCallRescue ?? DEFAULT_LOCAL_RELIABILITY.textToolCallRescue,\n thinkingOverrunPolicy: overrides.thinkingOverrunPolicy ?? DEFAULT_LOCAL_RELIABILITY.thinkingOverrunPolicy,\n }\n}\n","/**\n * `/` command model — an extension-contributed workflow invoked by typing\n * `/<name> [args]` as the whole prompt. A command is an ordered list of\n * handler **steps**; a bare `run` is sugar for a single step. Steps share a\n * mutable `data` bag, emit local output blocks (rendered in the transcript,\n * never sent to the model), and can drive the agent via `ctx.runAgent` so a\n * workflow can interact with the LLM mid-flight.\n *\n * This module is renderer-free and node-free: the types are the authoring\n * surface extensions compile against, and `executeCommand` / the parse\n * helper are pure so they're unit-testable without a TUI. The host supplies\n * the side-effecting capabilities on the step context: `emit` (render +\n * persist local output), `runAgent` (interactive sub-run), `generateData`\n * (silent schema-pinned JSON), and the optional `session` (read-only view)\n * and `interaction` (confirm / forms / plan approval) surfaces.\n */\n\nimport type { Logger } from '../logger'\nimport type { SessionRun } from '../session'\nimport type { JsonValue, SessionTurn } from '../types'\nimport type { InferSchemaOutput, SchemaInput, StandardSchemaLike } from '../zod'\nimport type { AnswerValue, PlanPayload, PlanResponse, QuestionPayload } from './interactions'\nimport type { StreamEvent } from './types'\n\n/**\n * A block of local output a step renders into the chat.\n *\n * Plain blocks (`text` only) render as full markdown, both live and on\n * reload — use them for the output the user should read in full (e.g. a\n * final commit message). Blocks with `detail` render as a collapsed\n * `command-detail` row instead (dim `▸ label` + line-capped body with\n * \"… N more lines\") — use them for bulky supporting output (diffs, logs,\n * listings) that shouldn't drown the transcript.\n */\nexport interface CommandOutputBlock {\n /**\n * The block body — full markdown, the detail body, or (for `custom`\n * blocks) the MANDATORY fallback markdown rendered wherever the\n * renderer isn't available: extension uninstalled or disabled,\n * headless host, GUI, renderer threw. The fallback is what persists\n * as the block's output, so pre-custom hosts render it natively.\n */\n text: string\n /**\n * Render as a collapsed detail row (dim `▸ label` + line-capped body)\n * instead of full markdown. `syntax: 'diff'` additionally colors\n * `+N` / `−N` count tokens (added green / deleted red) in both label\n * and body — for diff-stat-style output.\n */\n detail?: { label?: string, syntax?: 'diff' }\n /**\n * Render through an extension-registered custom renderer\n * (`ctx.tui.registerRenderer`). `renderer` must be the NAMESPACED id\n * (`<extension>/<id>`) — `ctx.appendNote` auto-prefixes bare ids for\n * its own extension; command `emit` requires the full form. `data`\n * must be JSON-safe (it persists in the session). Takes precedence\n * over `detail` when both are set.\n */\n custom?: { renderer: string, data?: JsonValue }\n}\n\n/**\n * User-interaction surface for commands — the same prompt-slot primitives\n * the `ask_user` / `present_plan` TOOLS render, but WITHOUT the tools: a\n * command enqueues directly into the host's interaction queue. No tool\n * calls, no turns, no model involvement — the answer routes straight back\n * to the awaiting step. Every method rejects on cancel/esc, which the\n * executor classifies as a quiet abort.\n */\nexport interface CommandInteraction {\n /**\n * One-tap yes/no — sugar over a single `confirm`-type question:\n *\n * if (await ctx.interaction.confirm('Push to origin/main?')) …\n */\n confirm: (prompt: string, opts?: {\n /** Optional context shown above the question (Markdown). */\n intro?: string\n /** Label for the affirmative option. Default `'yes'`. */\n affirmLabel?: string\n /** Label for the negative option. Default `'no'`. */\n denyLabel?: string\n }) => Promise<boolean>\n /**\n * Show a form (text / textarea / select / confirm questions — identical\n * UI to the ask_user tool) and resolve with the answers keyed by\n * `Question.id` (`string` for text/select, `boolean` for confirm,\n * `string[]` of choice ids for `multi: true` selects).\n */\n askUser: (form: QuestionPayload) => Promise<Readonly<Record<string, AnswerValue>>>\n /**\n * Present a plan for the full approval ceremony (approve / reject /\n * revise + optional comment — identical UI to present_plan). For\n * lightweight gating prefer {@link confirm}.\n */\n presentPlan: (plan: PlanPayload) => Promise<PlanResponse>\n}\n\n/**\n * Read-only view over the session a command runs inside — live getters, so\n * turns appended mid-workflow (e.g. by a `runAgent` sub-run) are visible on\n * the next read. Deliberately projection-only: no mutators, so commands\n * can't bypass the display-only `command`-turn contract or the wire guard\n * (`isWireTurn`). Persisting output goes through `emit`; driving the\n * conversation goes through `runAgent`.\n */\nexport interface CommandSessionView {\n readonly id: string\n /** Full persisted history, including display-only `command` turns. */\n readonly turns: readonly SessionTurn[]\n /** Run records (status, usage, cost, `origin`, …), newest last. */\n readonly runs: readonly SessionRun[]\n readonly metadata: Readonly<Record<string, unknown>>\n}\n\n/** Options for `ctx.generateData` — a silent, schema-pinned model call. */\nexport interface CommandGenerateDataOptions {\n /** Instruction describing the value to produce. */\n prompt: string\n /**\n * Shape of the result — a zod v4 schema (return type inferred, no casts)\n * or a plain JSON Schema object (name the type via the generic).\n */\n schema: SchemaInput\n /** Optional system prompt override. */\n system?: string\n /**\n * Fold a compacted transcript of the recent conversation into the call as\n * context. Default false — most workflow values derive from data the\n * command already gathered itself.\n */\n includeConversation?: boolean\n /**\n * Model id to run the call on (must exist in the host's model catalog —\n * see `ctx.runtime.models()` on the extension context). Omit to use the\n * session's active model.\n */\n model?: string\n}\n\n/** One task for {@link CommandStepContext.spawnAgents} — an isolated child agent run. */\nexport interface CommandSpawnTask {\n /** Task prompt for the child agent. Brief it like a colleague — it has no conversation context. */\n prompt: string\n /**\n * Model id from the host catalog (`ctx.runtime.models()` on the extension\n * context). Omit to inherit the session's active model. Unknown ids reject\n * the task (`status: 'rejected'`) without running anything.\n */\n model?: string\n /** System prompt override for the child. Inherits the agent's system prompt when omitted. */\n system?: string\n /** Short display label carried back on the result (defaults to the child id). */\n label?: string\n}\n\n/** Outcome of one {@link CommandSpawnTask}. */\nexport interface CommandSpawnResult {\n /** The task's `label` (or the child id when none was given). */\n label: string\n /** Model the task asked for, when pinned. */\n model?: string\n /** Terminal status. `'rejected'` = refused before running (unknown model, caps). */\n status: 'completed' | 'aborted' | 'timeout' | 'error' | 'rejected'\n /** The child's final assistant text. Empty unless `status === 'completed'`. */\n text: string\n /** Full formatted result / diagnostic (always set; useful for non-completed statuses). */\n message: string\n}\n\n/** Result of driving the agent from within a step via `ctx.runAgent`. */\nexport interface CommandAgentResult {\n /** The assistant's final text for this sub-run. */\n text: string\n}\n\n/**\n * A prompt attachment forwarded into a command invocation — captured when\n * the user submits `/cmd …` with pasted images/files attached. `data` is\n * base64 for EVERY media type (including text/plain): one encoding keeps\n * the contract lossless for binary and host-agnostic, at the cost of a\n * trivial decode for text consumers.\n */\nexport interface CommandAttachment {\n /** Display name from the capture surface (e.g. `clipboard.png`). */\n readonly name: string\n /** MIME type (e.g. `image/png`, `text/plain`). */\n readonly mediaType: string\n /** Base64-encoded content. */\n readonly data: string\n}\n\n/** Capabilities + inputs handed to every step. */\nexport interface CommandStepContext {\n /** Raw text after `/<name>` (trimmed of the leading space). */\n readonly args: string\n /** `args` split on whitespace, empties removed. */\n readonly argv: readonly string[]\n /**\n * Attachments submitted with the invocation (images/files pasted into the\n * prompt), when the host captures them. ABSENT — not empty — on hosts\n * without an attachment surface; guard with `ctx.attachments?.length`.\n */\n readonly attachments?: readonly CommandAttachment[]\n /**\n * The command token the user typed (lowercased) — the alias when one\n * was used, else the canonical name. Optional: hosts that dispatch\n * programmatically may not have one. See {@link CommandInvocation.invokedAs}.\n */\n readonly invokedAs?: string\n /** Mutable bag threaded across steps; a step's returned `data` is merged in. */\n readonly data: Record<string, unknown>\n /** Render + persist a local output block. */\n readonly emit: (block: CommandOutputBlock) => void\n /**\n * Drive one agent run to completion and return its result. The prompt is\n * injected as a real user turn (model-visible, unchanged on the wire) and\n * the response streams into the transcript. Serialized with the host's run\n * slot, so a workflow can interleave deterministic steps with LLM\n * interaction. Any output `emit`ted before this call is flushed first,\n * preserving transcript order.\n *\n * `opts.display` replaces the full-prompt echo in the transcript with a\n * short line (the full prompt still renders beneath it as a collapsed\n * detail row, and still reaches the model verbatim) — pass it when the\n * prompt embeds bulky material like a diff.\n */\n readonly runAgent: (prompt: string, opts?: { readonly display?: string }) => Promise<CommandAgentResult>\n /**\n * Generate schema-conforming JSON with ONE silent model call — no session\n * turns, no transcript output, nothing added to the conversation (the\n * workflow presents results itself via `emit`). Reach for this when a\n * step needs a VALUE (a commit message, a classification, extracted\n * fields); reach for `runAgent` when it needs an interactive sub-run\n * with tools and streaming.\n */\n readonly generateData: {\n /** zod v4 schema → the result type is inferred from the schema. */\n <S extends StandardSchemaLike>(opts: Omit<CommandGenerateDataOptions, 'schema'> & { schema: S }): Promise<InferSchemaOutput<S>>\n /** Plain JSON Schema → name the result type explicitly. */\n <T = unknown>(opts: CommandGenerateDataOptions): Promise<T>\n }\n /** Logger with a `command=<name>` binding, routed to the host's log funnel. */\n readonly logger: Logger\n /** Aborts when the user presses esc during execution. */\n readonly signal: AbortSignal\n /** Working directory the command runs against. */\n readonly cwd: string\n /**\n * The surrounding session, when the host runs commands inside one (the\n * TUI always does). ABSENT in session-less contexts — a command that\n * reads it must guard (`if (!ctx.session) …`) or degrade gracefully.\n */\n readonly session?: CommandSessionView\n /**\n * User interaction (confirm / forms / plan approval), when the host has\n * an interactive surface (the TUI always does). ABSENT on UI-less hosts\n * — guard (`if (!ctx.interaction) …`) and degrade gracefully.\n */\n readonly interaction?: CommandInteraction\n /**\n * Run one or more ISOLATED child agents — each with its own context\n * window and the full tool set, optionally pinned to a different model\n * — and resolve when the whole batch settles. Tasks run in parallel\n * (bounded by `opts.maxConcurrent`, default 3). Children stream and\n * persist into the transcript exactly like `spawn`-tool subagents, so\n * the user sees their activity live and on reload; their context stays\n * isolated from the conversation.\n *\n * Complements {@link runAgent}: `runAgent` drives THE conversation\n * (model-visible turns, active model, serialized); `spawnAgents` fans\n * work out to throwaway workers. Results come back to the command —\n * persist what matters via {@link emit} (user-facing) and\n * {@link appendContext} (model-facing).\n *\n * ABSENT on hosts without an agent (guard like `session`).\n */\n readonly spawnAgents?: (\n tasks: readonly CommandSpawnTask[],\n opts?: { readonly maxConcurrent?: number },\n ) => Promise<readonly CommandSpawnResult[]>\n /**\n * Persist a compact MODEL-VISIBLE context note — a real user-role turn,\n * sent on the wire unchanged from the next run onward, without\n * triggering a run now. This is how a command's outcome becomes part of\n * the conversation: the next user prompt sees what the workflow\n * found/did without replaying subagent transcripts.\n *\n * `opts.display` swaps the transcript echo for a short one-line marker —\n * live AND on reload (the marker persists on the turn; replay renders it\n * with the full note as a collapsed detail), so note bodies the command\n * also emitted never display twice. Keep notes compact regardless — they\n * occupy context for the rest of the conversation. ABSENT on session-less\n * hosts.\n */\n readonly appendContext?: (text: string, opts?: { readonly display?: string }) => Promise<void>\n}\n\n/** Optional per-step return — `data` is merged into the shared bag for later steps. */\nexport interface CommandStepResult {\n data?: Record<string, unknown>\n}\n\nexport type CommandStepFn = (\n ctx: CommandStepContext,\n) => void | CommandStepResult | Promise<void | CommandStepResult>\n\nexport interface CommandStep {\n run: CommandStepFn\n}\n\n/** One argument suggestion returned by {@link Command.completeArgs}. */\nexport interface CommandArgSuggestion {\n /** Text inserted after `/<name> ` on accept. */\n value: string\n /** Display label. Defaults to `value`. */\n label?: string\n description?: string\n}\n\n/** An extension-contributed `/` command. Provide EITHER `run` (one step) or `steps`. */\nexport interface Command {\n /** Invoked as `/<name>`. Lowercase, no whitespace. */\n name: string\n /**\n * Alternate invocation names (same constraints as `name`). `/alias`\n * dispatches to this command; the popover shows the canonical name and\n * lists aliases in the description. Aliases participate in the same\n * first-registration-wins dedupe as names.\n */\n aliases?: readonly string[]\n /**\n * Grouping key for completion — the registry stamps the source\n * extension's name here, so typing `/<extension>` in the popover lists\n * every command that extension contributes. Not an invocation name:\n * `parseCommandInvocation` ignores it.\n */\n group?: string\n /** One-line summary shown in the completion popover. */\n description: string\n /** Single-step sugar. */\n run?: CommandStepFn\n /** Explicit ordered pipeline. Takes precedence over `run` when both are set. */\n steps?: readonly CommandStep[]\n /**\n * Argument completion — called while the user types `/<name> <prefix>▌`\n * with everything after the command name (may itself contain spaces).\n * Return suggestions to show in the `/` popover; return `[]`/`undefined`\n * for none. Sync or async.\n */\n completeArgs?: (argsPrefix: string) => readonly CommandArgSuggestion[] | undefined | Promise<readonly CommandArgSuggestion[] | undefined>\n}\n\n/**\n * Persisted form of output blocks inside a display-only `command` turn:\n * plain blocks → `text` content; `detail` blocks → `tool_result` content\n * with the label/syntax carried in namespaced metadata (rebuilt into the\n * collapsed rendering on reload). Shared by the command emit flush and\n * `ctx.appendNote` so both persist identically.\n */\nexport function commandBlocksToContent(blocks: readonly CommandOutputBlock[]): SessionTurn['content'] {\n return blocks.map((block) => {\n if (!block.detail && !block.custom)\n return { type: 'text' as const, text: block.text }\n // Custom blocks persist with the FALLBACK as the output — an older\n // zidane (or any host without the renderer) reads this turn as a\n // plain detail block and renders the fallback natively. The renderer\n // binding rides namespaced metadata, exactly like detail label/syntax.\n const commandMeta: Record<string, JsonValue> = block.custom\n ? { custom: { renderer: block.custom.renderer, ...(block.custom.data !== undefined ? { data: block.custom.data } : {}) } }\n : {\n ...(block.detail?.label ? { label: block.detail.label } : {}),\n ...(block.detail?.syntax ? { syntax: block.detail.syntax } : {}),\n }\n return {\n type: 'tool_result' as const,\n callId: `command_detail_${crypto.randomUUID()}`,\n output: block.text,\n ...(Object.keys(commandMeta).length > 0 ? { metadata: { command: commandMeta } } : {}),\n }\n })\n}\n\n/**\n * Map ONE command-output block to its LIVE transcript event — the\n * in-memory mirror of {@link commandBlocksToContent}'s persisted shape\n * (whose replay side lives in `eventsFromTurns`). One definition shared\n * by the command emit flush and the `ctx.appendNote` host impl so live\n * rendering can never drift from reloaded rendering — the exact drift\n * that shipped `custom` blocks rendering only after a reload.\n *\n * Precedence matches the persisted side: `custom` > `detail` > markdown.\n */\nexport function commandBlockToEvent(block: CommandOutputBlock): StreamEvent {\n if (block.custom) {\n return {\n kind: 'custom',\n text: block.text,\n custom: {\n renderer: block.custom.renderer,\n ...(block.custom.data !== undefined ? { data: block.custom.data } : {}),\n },\n }\n }\n if (block.detail) {\n return {\n kind: 'command-detail',\n text: block.text,\n ...(block.detail.label ? { label: block.detail.label } : {}),\n ...(block.detail.syntax ? { syntax: block.detail.syntax } : {}),\n }\n }\n return { kind: 'markdown', text: block.text, streaming: false }\n}\n\n/** Normalize a command to its ordered step list (`steps` wins over `run`). */\nexport function commandSteps(command: Command): readonly CommandStep[] {\n if (command.steps && command.steps.length > 0)\n return command.steps\n if (command.run)\n return [{ run: command.run }]\n return []\n}\n\n/** Parsed `/<name> [args]` invocation. */\nexport interface CommandInvocation {\n name: string\n /**\n * The token the user actually typed (lowercased, no slash) — `name`\n * when the canonical name was used, the alias otherwise. Lets commands\n * keep legacy per-alias behavior (e.g. bare `/fix-comments` defaulting\n * to comments mode after the command was folded into `/fix`).\n */\n invokedAs: string\n /** Text after the name, trimmed. */\n args: string\n /** `args` split on whitespace. */\n argv: string[]\n}\n\n/**\n * Parse a prompt as a command invocation, but only when the command token is\n * the LEADING token of the whole prompt and names a known command. This is\n * what keeps mid-prompt `/skill` mentions routed to the skills provider:\n * `\"summarize /research\"` is not a command, `\"/research foo\"` is.\n *\n * `known` is matched case-insensitively; entries may be bare names or\n * `{ name, aliases }` records — an alias hit canonicalizes to the entry's\n * `name` (registered casing), so dispatch-by-name downstream just works.\n */\nexport function parseCommandInvocation(\n prompt: string,\n known: readonly (string | Pick<Command, 'name' | 'aliases'>)[],\n): CommandInvocation | null {\n const body = prompt.trimStart()\n if (body[0] !== '/')\n return null\n // First token = the command name; the remainder (after the first run of\n // whitespace) is the args. Linear scan — no backtracking-prone regex.\n const afterSlash = body.slice(1)\n const wsIdx = afterSlash.search(/\\s/)\n const typed = (wsIdx === -1 ? afterSlash : afterSlash.slice(0, wsIdx)).toLowerCase()\n if (typed.length === 0)\n return null\n let canonical: string | undefined\n for (const entry of known) {\n const name = typeof entry === 'string' ? entry : entry.name\n if (name.toLowerCase() === typed) {\n canonical = name\n break\n }\n if (typeof entry !== 'string' && entry.aliases?.some(a => a.toLowerCase() === typed)) {\n canonical = name\n break\n }\n }\n if (canonical === undefined)\n return null\n const args = (wsIdx === -1 ? '' : afterSlash.slice(wsIdx + 1)).trim()\n return { name: canonical, invokedAs: typed, args, argv: args.length > 0 ? args.split(/\\s+/) : [] }\n}\n\n/** Outcome of a command run. `error` is set when a step threw (pipeline stops). */\nexport interface CommandExecution {\n ok: boolean\n /** Number of steps that ran (including the one that threw). */\n ranSteps: number\n error?: unknown\n /** True when the run stopped because `ctx.signal` aborted. */\n aborted?: boolean\n}\n\n/**\n * Run a command's steps in order, threading `ctx.data`. Stops at the first\n * step that throws (error isolated + returned, not rethrown) or when the\n * signal aborts. Pure orchestration — all I/O lives in the host-supplied\n * `ctx` capabilities.\n */\nexport async function executeCommand(\n command: Command,\n ctx: CommandStepContext,\n): Promise<CommandExecution> {\n const steps = commandSteps(command)\n let ranSteps = 0\n for (const step of steps) {\n if (ctx.signal.aborted)\n return { ok: false, ranSteps, aborted: true }\n ranSteps++\n try {\n const result = await step.run(ctx)\n if (result && result.data)\n Object.assign(ctx.data, result.data)\n }\n catch (error) {\n // An esc mid-step usually surfaces as a thrown AbortError from whatever\n // I/O the handler had in flight. Classify by the signal so hosts can\n // stay quiet on user-initiated aborts instead of painting an error.\n if (ctx.signal.aborted)\n return { ok: false, ranSteps, aborted: true }\n return { ok: false, ranSteps, error }\n }\n }\n return { ok: true, ranSteps }\n}\n","/**\n * Zod v4 integration helpers.\n *\n * Zod is an OPTIONAL peer dependency, so this module never imports it at the\n * top level. Schema-accepting APIs (`ctx.generateData`, `generateStructuredData`)\n * take either a plain JSON Schema object or any Standard-Schema-carrying\n * value (https://standardschema.dev — zod v4 implements it). Zod schemas are\n * detected structurally by vendor and converted with a lazy\n * `import('zod')` — safe because whoever constructed the schema necessarily\n * has zod installed. Result types are inferred from the standard-schema\n * `types.output` slot, so callers get full typing without a single cast:\n *\n * const CommitMessage = z.object({ subject: z.string(), body: z.string().optional() })\n * const msg = await ctx.generateData({ prompt, schema: CommitMessage })\n * // ^? { subject: string, body?: string }\n */\n\n/**\n * Structural stand-in for a Standard Schema (zod v4 et al.) — enough surface\n * to detect the vendor and infer the output type WITHOUT importing zod's\n * types into our public API.\n */\nexport interface StandardSchemaLike<Output = unknown> {\n readonly '~standard': {\n readonly version: number\n readonly vendor: string\n /** Type-level only (undefined at runtime) — carries the inferred output type. */\n readonly types?: { readonly output: Output } | undefined\n }\n}\n\n/** Output type carried by a {@link StandardSchemaLike}; `unknown` for plain JSON Schema. */\nexport type InferSchemaOutput<S> = S extends StandardSchemaLike<infer O> ? O : unknown\n\n/** A schema-accepting input: plain JSON Schema, or a Standard Schema (e.g. zod v4). */\nexport type SchemaInput = Record<string, unknown> | StandardSchemaLike\n\nexport function isStandardSchema(value: unknown): value is StandardSchemaLike {\n if (typeof value !== 'object' || value === null)\n return false\n const marker = (value as Record<string, unknown>)['~standard']\n return typeof marker === 'object' && marker !== null\n && typeof (marker as Record<string, unknown>).vendor === 'string'\n}\n\n/**\n * Resolve a {@link SchemaInput} to the plain JSON Schema object providers\n * expect. Zod schemas convert via the lazily-imported `z.toJSONSchema`\n * (v4 native); other Standard Schema vendors are rejected with a clear\n * error — conversion is vendor-specific and only zod is wired today.\n */\nexport async function resolveSchemaInput(schema: SchemaInput): Promise<Record<string, unknown>> {\n if (!isStandardSchema(schema))\n return schema\n const vendor = schema['~standard'].vendor\n if (vendor !== 'zod') {\n throw new Error(\n `unsupported schema vendor \"${vendor}\" — pass a zod v4 schema or a plain JSON Schema object`,\n )\n }\n // Lazy + guarded: zod is an optional peer, but a zod schema in hand means\n // the caller's process has zod — this import only fails on truly broken\n // installs, and then with an actionable message.\n let toJSONSchema: (schema: unknown) => Record<string, unknown>\n try {\n const zod = await import('zod')\n toJSONSchema = zod.z.toJSONSchema as typeof toJSONSchema\n }\n catch {\n throw new Error('a zod schema was passed but the \"zod\" package could not be imported — install zod >= 4')\n }\n return zodToJsonSchema(toJSONSchema(schema))\n}\n\n/**\n * Normalize a JSON Schema (e.g. from zod v4's z.toJSONSchema()) for use\n * as a ToolSpec.inputSchema.\n *\n * Strips the $schema key that some providers reject.\n */\nexport function zodToJsonSchema(jsonSchema: Record<string, unknown>): Record<string, unknown> {\n const { $schema, ...rest } = jsonSchema\n return rest\n}\n","/**\n * One-shot title generation for an existing session — feeds the opening\n * user turn plus the last N turns to the provider as a LABELING task and\n * returns the cleaned-up result.\n *\n * Provider-agnostic: works with anything implementing the renderer-\n * shared `Provider` interface. No tools, no session mutation, no\n * caching — this is a cheap, isolated completion that should be safe to\n * fire even mid-session without touching the live agent loop.\n */\n\nimport type { Provider } from '../providers'\nimport type { SessionTurn } from '../types'\n\n/** Hard cap on the result length. Anything longer is truncated client-side. */\nconst TITLE_MAX_CHARS = 60\n\n/** Default turn count fed to the model. 10 covers most exchanges without bloat. */\nconst DEFAULT_MAX_TURNS = 10\n\n/** Tokens budgeted for the model's reply. 64 fits any sane title with headroom. */\nconst TITLE_RESPONSE_MAX_TOKENS = 64\n\n/** Per-turn body cap when assembling the prompt — keeps the request payload tight. */\nconst PROMPT_TURN_CHAR_MAX = 2000\n\n/**\n * System prompt for the titling call.\n *\n * Written as a LABELING task, not a chat turn: handed a bare \"generate a\n * title for this conversation\" instruction with the transcript inline, a\n * cheap model reads the trailing assistant message and just keeps\n * talking — observed in the wild as a session titled `I don't have a\n * \"plan tool\" available in this conversation…`, i.e. the model answering\n * the last user turn instead of naming it. The rules below say what the\n * model IS (an annotator, not a participant), and the examples show the\n * shape — including the exact failure case (a transcript whose last turn\n * is a refusal) so the answer-shaped completion has a labeled\n * counter-example to copy instead.\n */\nconst TITLE_SYSTEM_PROMPT = [\n 'You label conversations. Each input is a transcript of a session between a user and a coding agent.',\n 'Name what the session is ABOUT. You are an annotator, never a participant in the conversation.',\n '',\n 'Rules:',\n '- Reply with ONLY the label: no quotes, no markdown, no trailing punctuation, no preamble.',\n `- 3 to 7 words, under ${TITLE_MAX_CHARS} characters, written as a noun phrase or an imperative.`,\n '- Name the user\\'s topic or task using concrete nouns from the transcript: files, tools, features, errors.',\n '- NEVER answer, continue, or comment on the conversation — not even when the last message is a question, a refusal, or an error.',\n '- No first person (\"I\", \"we\"), no \"the user\", and no filler words like conversation, session, chat, request, discussion, help.',\n '',\n 'Examples of a transcript and its label:',\n '- user asks why `bun test` hangs after an upgrade, agent finds a stuck worker → Bun test hangs on stuck worker',\n '- user asks for a --json flag on the CLI → Add JSON output flag to the CLI',\n '- user asks about a plan tool, agent replies that no such tool exists → Missing plan tool in the agent toolset',\n].join('\\n')\n\n/**\n * Openers that mark a model reply rather than a label — the model\n * answering the transcript instead of naming it. Treated as a failed\n * generation (see {@link cleanTitle}) so the auto-titler retries on the\n * next run rather than persisting a sentence as the session name.\n *\n * Deliberately conservative: only phrasings that a real title would\n * never start with. Excluded on purpose because they head perfectly good\n * titles: `no` / `there is` (\"No JSON output in the CLI\"), `ok` / `yes` /\n * `great` (\"OK button never fires\", \"Yes/no confirm regression\").\n *\n * The pronoun cases require FOLLOWING WHITESPACE rather than a word\n * boundary — `\\b` after `i` also matches punctuation, which rejected\n * \"I/O timeout in the docker context\". Contractions are matched\n * explicitly instead, with both the straight and typographic apostrophe\n * (models emit U+2019 freely, and `here's` / `let's` have no bare-word\n * alternative to fall back on).\n */\nconst ANSWER_SHAPED_OPENER\n = /^(?:i\\s|i['’](?:m|ve|ll|d)\\b|we\\s|we['’](?:re|ve|ll)\\b|let['’]s\\b|let\\s+(?:me|us)\\b|sure\\b|certainly\\b|okay\\b|sorry\\b|thanks\\b|unfortunately\\b|here['’]s\\b|here\\s+is\\b|as\\s+an\\b|you\\s+(?:can|should|need)\\b|it\\s+(?:looks|seems|appears)\\b)/i\n\nexport interface GenerateSessionTitleOptions {\n provider: Provider\n /** Model id used for the call. */\n model: string\n /** Conversation history to summarize. Empty → throws synchronously. */\n turns: readonly SessionTurn[]\n /**\n * Cap on how many TRAILING turns to feed the model. Defaults to 10.\n * The session's opening user turn is always included on top when the\n * window cuts it off (see `renderTitleTranscript`).\n */\n maxTurns?: number\n /** Optional cancellation signal — forwarded to the provider's stream. */\n signal?: AbortSignal\n}\n\n/**\n * Drive the provider's `stream()` once and return a concise title for\n * the conversation represented by `turns`. Throws when:\n * - `turns` contains no text-bearing content (nothing to summarize),\n * - the provider stream completes with no output text,\n * - the model answered the transcript instead of labeling it (see\n * {@link cleanTitle}) — a failed generation the caller retries,\n * - `signal` is aborted (rethrown verbatim).\n *\n * Output is trimmed, single-line, with surrounding quotes / trailing\n * punctuation stripped, and truncated to `TITLE_MAX_CHARS`.\n */\nexport async function generateSessionTitle({\n provider,\n model,\n turns,\n maxTurns = DEFAULT_MAX_TURNS,\n signal,\n}: GenerateSessionTitleOptions): Promise<string> {\n const transcript = renderTitleTranscript(turns, maxTurns)\n if (!transcript)\n throw new Error('No text content in the recent turns to summarize.')\n\n // Transcript first, instruction last: the final thing the model reads\n // is \"write the label\", not the assistant's last reply — which is what\n // pulled it into answering when the instruction led. The delimiters\n // mark the transcript as data to annotate rather than a turn to\n // respond to.\n const userPrompt = [\n '<transcript>',\n transcript,\n '</transcript>',\n '',\n 'Write the label for the session in the transcript above. Label only.',\n ].join('\\n')\n\n let text = ''\n const result = await provider.stream(\n {\n model,\n system: TITLE_SYSTEM_PROMPT,\n tools: [],\n messages: [provider.userMessage(userPrompt)],\n maxTokens: TITLE_RESPONSE_MAX_TOKENS,\n ...(signal ? { signal } : {}),\n },\n {\n onText: (delta) => { text += delta },\n },\n )\n // Prefer the streamed-text accumulator (driven by deltas) but fall\n // back to the provider's already-concatenated `result.text` for\n // adapters that batch on the final frame instead of emitting deltas.\n const raw = text.trim().length > 0 ? text : result.text\n return cleanTitle(raw)\n}\n\n/**\n * Decide whether a session is ready for one-shot automatic titling.\n *\n * Returns `true` iff the conversation has had its first exchange — at\n * least two turns, i.e. the opening user message plus the assistant's\n * reply (this is exactly the \"N turns\" count the sessions list shows) —\n * AND no explicit title has been set yet. An existing `metadata.title`\n * (a previous auto-generation, or a manual title from the session-details\n * \"generate title\" action) is never clobbered.\n *\n * Pure + total. The runtime layers its own \"is one already in flight?\"\n * and \"is a provider picked?\" guards on top of this; the predicate only\n * encodes the content-level rule so it can be pinned in tests without a\n * mock provider — same rationale as the exported {@link cleanTitle}.\n */\nexport function shouldAutoGenerateTitle(\n turns: readonly SessionTurn[],\n metadata?: Record<string, unknown>,\n): boolean {\n const existing = typeof metadata?.title === 'string' ? metadata.title.trim() : ''\n if (existing.length > 0)\n return false\n return turns.length >= 2\n}\n\n/**\n * Assemble the transcript fed to the titling model: the session's\n * OPENING user turn (when it falls outside the window) followed by the\n * last `maxTurns` turns, separated by an `…` gap marker.\n *\n * The tail alone is not enough. A session's topic is set by the first\n * thing the user asked; after a dozen turns that message is off the\n * window and the model ends up naming whatever tangent the conversation\n * was on when titling fired — or, worse, the assistant's last reply.\n * Anchoring on the opening intent keeps the label about the session.\n *\n * `maxTurns` keeps its meaning (a cap on TRAILING turns); the anchor is\n * one extra entry on top, and only exists when the window actually cut\n * it off. Returns `''` when nothing in the selection carries text.\n */\nfunction renderTitleTranscript(turns: readonly SessionTurn[], maxTurns: number): string {\n const cap = Math.max(1, maxTurns)\n const tail = renderTurnsForPrompt(turns.slice(-cap))\n const earlier = turns.slice(0, Math.max(0, turns.length - cap))\n const opening = earlier.find(turn => turn.role === 'user' && textBodyOf(turn).length > 0)\n const anchor = opening ? renderTurnsForPrompt([opening]) : ''\n if (anchor && tail)\n return `${anchor}\\n\\n…\\n\\n${tail}`\n return anchor || tail\n}\n\n/**\n * Compact a turn list into a transcript-style prompt:\n *\n * user: …\n * assistant: …\n * user: …\n *\n * Tool calls and tool results are dropped entirely — a cheap title model\n * happily echoes plumbing like `[tool: list_files]` back as \"the title\"\n * when a marker is the only assistant content it sees. The topical text\n * is what a title needs. Per-turn text is clipped at `PROMPT_TURN_CHAR_MAX`\n * so a single huge code dump doesn't blow the budget.\n *\n * Returns an empty string when the slice has no text-bearing content\n * (e.g. a turn list that's only tool plumbing).\n *\n * Exported for reuse by other silent one-shot helpers (`generate-data`'s\n * optional conversation context) so every side-channel inference compacts\n * history the same way.\n */\nexport function renderTurnsForPrompt(turns: readonly SessionTurn[]): string {\n const lines: string[] = []\n for (const turn of turns) {\n const body = textBodyOf(turn)\n if (!body)\n continue\n const role = turn.role === 'assistant' ? 'assistant' : turn.role === 'user' ? 'user' : 'system'\n lines.push(`${role}: ${clip(body, PROMPT_TURN_CHAR_MAX)}`)\n }\n return lines.join('\\n\\n')\n}\n\nfunction textBodyOf(turn: SessionTurn): string {\n const parts: string[] = []\n for (const block of turn.content) {\n if (block.type === 'text' && block.text.trim())\n parts.push(block.text.trim())\n }\n return parts.join(' ')\n}\n\n/** Strip one layer of matched surrounding quotes (`\"foo\"`, `'foo'`, `` `foo` ``, `“foo”`). */\nfunction unwrapQuotes(s: string): string {\n if (s.length < 2)\n return s\n const first = s.charAt(0)\n const last = s.charAt(s.length - 1)\n const matched = (first === '\"' && last === '\"')\n || (first === '\\'' && last === '\\'')\n || (first === '`' && last === '`')\n || (first === '“' && last === '”')\n return matched ? s.slice(1, -1).trim() : s\n}\n\n/**\n * Normalize a model-generated title into the shape we want to persist:\n *\n * - Collapse internal whitespace + take the first line only (some\n * models emit \"Title: foo\\n\\nReason: …\" despite instructions).\n * - Strip surrounding quotes, a leading `Title:` / `title -` preamble,\n * and a `user:` / `assistant:` role prefix echoed from the transcript\n * — repeatedly, in any nesting order.\n * - Reject placeholders and answer-shaped replies (see\n * {@link ANSWER_SHAPED_OPENER}) by throwing.\n * - Strip trailing punctuation (`.`, `!`, `?`) — titles read cleaner\n * without it.\n * - Truncate to `TITLE_MAX_CHARS` with a trailing `…` when over.\n *\n * Exported as `cleanTitle` so tests can pin the normalization rules\n * without going through a mock provider.\n */\nexport function cleanTitle(raw: string): string {\n let s = raw.split('\\n')[0]?.trim() ?? ''\n // Peel wrappers until the string stops changing. Models nest them\n // (`\"assistant: Fix the flaky test\"`), and each strip can expose the\n // next — order-sensitive single passes let a quoted role prefix through,\n // which then also dodged the answer-shape guard below. Bounded so a\n // pathological input can't spin here.\n for (let pass = 0; pass < 4; pass++) {\n const before = s\n s = unwrapQuotes(s)\n // Strip a \"Title:\" / \"Title -\" preamble the model sometimes adds.\n s = s.replace(/^\\s*title\\s*[:\\-–—]\\s*/i, '')\n // Strip a transcript role prefix echoed back from the prompt format\n // (`assistant: Fix the flaky test`).\n s = s.replace(/^\\s*(?:user|assistant|system)\\s*:\\s*/i, '')\n if (s === before)\n break\n }\n // Trim trailing terminal punctuation — titles don't take periods.\n s = s.replace(/[.!?]+$/, '').trim()\n if (s.length === 0)\n throw new Error('Model returned an empty title.')\n // A bracketed marker (e.g. `[tool: list_files]`) is transcript plumbing\n // echoed back, never a real title. Treat it as a failed generation — the\n // auto-title guards re-evaluate on the next run, so it naturally retries.\n if (/^\\[.*\\]$/.test(s))\n throw new Error('Model returned a placeholder instead of a title.')\n // Same deal for an answer instead of a label: the model replied to the\n // transcript rather than naming it. Rejecting keeps the sentence out of\n // `metadata.title` and lets the next run try again.\n if (ANSWER_SHAPED_OPENER.test(s))\n throw new Error('Model answered the conversation instead of titling it.')\n return s.length > TITLE_MAX_CHARS ? `${s.slice(0, TITLE_MAX_CHARS - 1)}…` : s\n}\n\nfunction clip(text: string, max: number): string {\n return text.length > max ? `${text.slice(0, max)}…` : text\n}\n","/**\n * Silent structured-output generation — one provider call that returns\n * schema-conforming JSON without touching the session or the transcript.\n *\n * Modeled on the two proven side-channel patterns in the codebase:\n * - `generate-title.ts`: a single `provider.stream()` with no session\n * writes and no transcript events (optionally seeded with a compacted\n * conversation transcript), and\n * - the loop's `__output__` schema enforcement: a synthetic tool whose\n * `inputSchema` IS the requested JSON Schema, with `toolChoice` pinned\n * so the model must answer with a conforming payload.\n *\n * Used by `/` commands via `ctx.generateData` to derive values from the\n * model (or from the conversation) mid-workflow without bloating the chat\n * history — e.g. `/commit` generating `{ subject, body }` from a diff.\n * For genuinely interactive sub-runs (real turns, streaming, tools), use\n * `ctx.runAgent` instead.\n */\n\nimport type { Provider, ToolSpec } from '../providers'\nimport type { SessionTurn } from '../types'\nimport type { InferSchemaOutput, SchemaInput, StandardSchemaLike } from '../zod'\nimport { resolveSchemaInput } from '../zod'\nimport { renderTurnsForPrompt } from './generate-title'\n\n/** Synthetic forced-choice tool name (never persisted — the call is side-channel only). */\nconst DATA_TOOL_NAME = '__data__'\n\n/** Default cap on conversation turns folded into the context block. */\nconst DEFAULT_CONTEXT_TURNS = 12\n\nexport interface GenerateStructuredDataOptions {\n provider: Provider\n model: string\n /** Instruction describing the value to produce. */\n prompt: string\n /**\n * Shape of the result — a plain JSON Schema object, or a zod v4 schema\n * (converted automatically; the return type is then inferred from it).\n * Becomes the forced tool's inputSchema.\n */\n schema: SchemaInput\n /** Optional system prompt. Defaults to a terse data-extraction persona. */\n system?: string\n /**\n * Conversation turns to fold in as compacted context (same clipping as\n * session auto-titling). Omit or pass empty for a context-free call.\n */\n turns?: readonly SessionTurn[]\n /** Cap on turns rendered into the context block. Default 12 (most recent). */\n maxContextTurns?: number\n signal?: AbortSignal\n}\n\n/**\n * Drive one schema-pinned provider call and return the conforming payload.\n * Throws when the provider returns no structured payload (and rethrows\n * aborts verbatim). The caller owns presentation — nothing is rendered or\n * persisted here.\n *\n * Pass a zod v4 schema and the return type is inferred from it; pass a\n * plain JSON Schema object and name the type via the generic.\n */\nexport async function generateStructuredData<S extends StandardSchemaLike>(\n options: GenerateStructuredDataOptions & { schema: S },\n): Promise<InferSchemaOutput<S>>\nexport async function generateStructuredData<T = unknown>(\n options: GenerateStructuredDataOptions,\n): Promise<T>\nexport async function generateStructuredData(\n options: GenerateStructuredDataOptions,\n): Promise<unknown> {\n const { provider, model, prompt, signal } = options\n const spec: ToolSpec = {\n name: DATA_TOOL_NAME,\n description: 'Return the result. The input of this call IS the answer — it must conform to the schema.',\n inputSchema: await resolveSchemaInput(options.schema),\n }\n\n const contextTurns = options.turns && options.turns.length > 0\n ? options.turns.slice(-(options.maxContextTurns ?? DEFAULT_CONTEXT_TURNS))\n : []\n const transcript = contextTurns.length > 0 ? renderTurnsForPrompt(contextTurns) : ''\n const userPrompt = transcript\n ? `<conversation>\\n${transcript}\\n</conversation>\\n\\n${prompt}`\n : prompt\n\n const result = await provider.stream(\n {\n model,\n system: options.system\n ?? `You produce structured data. Call the ${DATA_TOOL_NAME} tool exactly once with the requested value. No prose.`,\n tools: provider.formatTools([spec]),\n messages: [provider.userMessage(userPrompt)],\n toolChoice: { type: 'tool', name: DATA_TOOL_NAME },\n ...(signal ? { signal } : {}),\n },\n {\n onText: () => {},\n },\n )\n\n const payload = result.toolCalls.find(tc => tc.name === DATA_TOOL_NAME)?.input\n if (payload === undefined)\n throw new Error('generateStructuredData: the model returned no structured payload')\n return payload\n}\n","/**\n * Inline (ghost-text) autocomplete for the prompt.\n *\n * Distinct from the trigger-based completion popover (`@` files, `/`\n * commands): providers here suggest plain text SUFFIX candidates to\n * append at the cursor — the first renders as dim ghost text, up/down\n * rotates through the rest, Tab accepts. No popover.\n *\n * Providers are queried in registration order with a shared AbortSignal;\n * the engine returns the first provider's non-empty candidate list.\n * Extensions add providers via `ctx.registerInlineAutocompleteProvider`\n * — the built-in path provider ({@link createPathAutocompleteProvider})\n * is always first.\n */\n\nimport { readdirSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { basename, dirname, isAbsolute, join, resolve } from 'node:path'\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface InlineAutocompleteContext {\n /** Full prompt buffer. */\n text: string\n /** Cursor offset into `text`. Suggestions complete the token ENDING here. */\n cursor: number\n /** Workspace directory relative paths resolve against. */\n cwd: string\n /** Aborted when the buffer changes before the provider resolves. */\n signal: AbortSignal\n}\n\nexport interface InlineAutocompleteProvider {\n /** Stable id — used for first-wins dedupe across extensions. */\n id: string\n /**\n * Per-provider deadline override (ms), capped at\n * {@link MAX_PROVIDER_DEADLINE_MS}. The default\n * ({@link PROVIDER_DEADLINE_MS}) suits instant sources like directory\n * listings; a provider backed by a slow source (e.g. a local LLM) can\n * extend its own budget — the wait is async (typing is never blocked,\n * an edit aborts the query) and only delays providers ordered AFTER\n * this one.\n */\n deadlineMs?: number\n /**\n * Return the ghost suffix (or an ordered list of candidate suffixes —\n * the host shows the first and lets the user rotate with up/down) to\n * append at the cursor, or null/''/[] for no suggestion. Must respect\n * its deadline budget — it runs per keystroke (debounced by the\n * host).\n */\n suggest: (ctx: InlineAutocompleteContext) => string | readonly string[] | null | Promise<string | readonly string[] | null>\n}\n\n// ---------------------------------------------------------------------------\n// Engine\n// ---------------------------------------------------------------------------\n\n/** Default per-provider deadline — a hung provider is treated as \"no suggestion\". */\nconst PROVIDER_DEADLINE_MS = 150\n/** Hard cap on a provider's `deadlineMs` — past this, waiting for a ghost reads as broken. */\nexport const MAX_PROVIDER_DEADLINE_MS = 2_000\n\n/**\n * Query `providers` in order; the first provider yielding a non-empty\n * candidate list wins (a bare string normalizes to a 1-element list).\n * Provider errors are swallowed (a broken extension provider must not\n * break typing), and each provider races its deadline —\n * {@link PROVIDER_DEADLINE_MS} by default, a provider's own `deadlineMs`\n * (capped at {@link MAX_PROVIDER_DEADLINE_MS}) when declared — so one\n * hung provider can't permanently suppress the ghosts of providers\n * behind it. Returns null when aborted or nothing matched.\n */\nexport async function resolveInlineSuggestion(\n providers: readonly InlineAutocompleteProvider[],\n ctx: InlineAutocompleteContext,\n deadlineMs = PROVIDER_DEADLINE_MS,\n // Injectable cap — the host always uses the real MAX_PROVIDER_DEADLINE_MS;\n // tests pass a small value to prove clamping without a wall-clock stall.\n maxDeadlineMs = MAX_PROVIDER_DEADLINE_MS,\n): Promise<{ suffixes: readonly string[], providerId: string } | null> {\n const TIMEOUT = Symbol('timeout')\n for (const provider of providers) {\n if (ctx.signal.aborted)\n return null\n try {\n let timer: ReturnType<typeof setTimeout> | undefined\n const budget = provider.deadlineMs !== undefined\n ? Math.min(Math.max(provider.deadlineMs, 1), maxDeadlineMs)\n : deadlineMs\n const result = await Promise.race([\n Promise.resolve(provider.suggest(ctx)),\n new Promise<typeof TIMEOUT>((r) => { timer = setTimeout(r, budget, TIMEOUT) }),\n ])\n if (timer !== undefined)\n clearTimeout(timer)\n if (result === TIMEOUT)\n continue\n if (ctx.signal.aborted)\n return null\n const suffixes = (typeof result === 'string' ? [result] : result ?? [])\n .filter(s => typeof s === 'string' && s.length > 0)\n if (suffixes.length > 0)\n return { suffixes, providerId: provider.id }\n }\n catch {\n // Isolation-first: skip broken providers.\n }\n }\n return null\n}\n\n// ---------------------------------------------------------------------------\n// Built-in path provider\n// ---------------------------------------------------------------------------\n\n/**\n * Extract the whitespace-delimited token that ends at `cursor`. Returns\n * null when the cursor sits right after whitespace (nothing to complete).\n */\nexport function tokenEndingAtCursor(text: string, cursor: number): string | null {\n const before = text.slice(0, cursor)\n const match = before.match(/\\S+$/)\n return match ? match[0] : null\n}\n\n/**\n * Does this token carry an explicit path shape (`/` separator or a `~`\n * prefix)? Path-shaped tokens complete from the first character after\n * the separator; bare tokens (`sr` → `c/`) still complete against the\n * cwd listing but need ≥2 chars first — see the provider.\n */\nfunction looksLikePath(token: string): boolean {\n return token.includes('/') || token.startsWith('~')\n}\n\n/**\n * Cap on candidates returned for an ambiguous fragment — rotation past\n * a dozen-odd entries stops being faster than typing another character.\n */\nconst MAX_PATH_CANDIDATES = 16\n\n/** Directory listing seam — injectable for tests. */\nexport interface PathProviderIo {\n readdir?: (dir: string) => { name: string, isDirectory: boolean }[]\n}\n\nfunction defaultReaddir(dir: string): { name: string, isDirectory: boolean }[] {\n return readdirSync(dir, { withFileTypes: true }).map(e => ({ name: e.name, isDirectory: e.isDirectory() }))\n}\n\n/**\n * Built-in provider: complete filesystem paths under `cwd` (or absolute /\n * `~` paths). Given the token `src/chat/comp`, lists `src/chat/` and\n * returns every matching continuation of `comp` as a candidate list —\n * directories first, alphabetical, `/`-terminated for directories so\n * typing can continue. The host ghosts the first candidate and rotates\n * through the rest on up/down.\n *\n * Bare tokens (no `/` yet) are matched against the cwd listing too, so\n * a directory ghost appears as soon as the first characters of `src`\n * are typed — but only from the second character on, so 1-letter prose\n * words don't ghost-spam.\n *\n * Dotfiles are only offered when the typed fragment itself starts with\n * `.` — the usual completion convention.\n */\nexport function createPathAutocompleteProvider(opts: {\n cwd?: () => string\n io?: PathProviderIo\n} = {}): InlineAutocompleteProvider {\n const readdir = opts.io?.readdir ?? defaultReaddir\n // Prefer the per-invocation `ctx.cwd` (host truth); fall back to the\n // factory-supplied getter, then `process.cwd()`.\n const fallbackCwd = opts.cwd ?? (() => process.cwd())\n return {\n id: 'paths',\n suggest(ctx) {\n const token = tokenEndingAtCursor(ctx.text, ctx.cursor)\n if (!token)\n return null\n // Bare tokens complete against the cwd listing (dirname('sr') is '.',\n // so the resolution below just works) but require ≥2 typed chars.\n if (!looksLikePath(token) && token.length < 2)\n return null\n // Resolve the directory being completed and the fragment typed so\n // far. A trailing `/` means \"complete entries of this directory\".\n const expanded = token.startsWith('~')\n ? join(homedir(), token.slice(1))\n : token\n const endsWithSep = expanded.endsWith('/')\n const dirPart = endsWithSep ? expanded : dirname(expanded)\n const fragment = endsWithSep ? '' : basename(expanded)\n const baseCwd = ctx.cwd || fallbackCwd()\n const dir = isAbsolute(dirPart) ? dirPart : resolve(baseCwd, dirPart)\n let entries: { name: string, isDirectory: boolean }[]\n try {\n entries = readdir(dir)\n }\n catch {\n return null\n }\n const matches = entries.filter(e =>\n e.name.startsWith(fragment)\n && (fragment.startsWith('.') || !e.name.startsWith('.')),\n )\n // Directories first (they're what you're navigating through), then\n // alphabetical within each group.\n matches.sort((a, b) =>\n a.isDirectory !== b.isDirectory\n ? (a.isDirectory ? -1 : 1)\n : a.name.localeCompare(b.name))\n const suffixes = matches\n .map(e => e.name.slice(fragment.length) + (e.isDirectory ? '/' : ''))\n .filter(s => s.length > 0)\n .slice(0, MAX_PATH_CANDIDATES)\n return suffixes.length > 0 ? suffixes : null\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Host-side ghost reconciliation\n// ---------------------------------------------------------------------------\n\nexport interface InlineGhost {\n suffixes: readonly string[]\n index: number\n text: string\n cursor: number\n}\n\n/**\n * Optimistically transform the current ghost across a buffer edit so it\n * stays visible while the debounced provider re-query is in flight —\n * without this, every keystroke inside a completable token blinks the\n * ghost (hide → 120 ms → reshow).\n *\n * Two edits are reconcilable (everything else returns null and waits for\n * the authoritative result):\n * - typing FORWARD along the suggestion (`bi` → `bin` with ghost\n * `n/`): candidates are filtered to those starting with the typed\n * chars and trimmed;\n * - deleting BACKWARD within the token (`bin` → `bi` with ghost `/`):\n * the deleted chars are re-prepended to every candidate. Guarded to\n * non-whitespace so crossing a token boundary drops the ghost.\n *\n * The patched ghost is provisional — the provider's answer replaces it\n * (and may add candidates the patch couldn't know about).\n */\nexport function patchInlineGhost(prev: InlineGhost | null, text: string, cursor: number): InlineGhost | null {\n if (!prev)\n return null\n if (text === prev.text && cursor === prev.cursor)\n return prev\n // The text after the caret must be untouched — edits there invalidate\n // the completion context entirely.\n if (text.slice(cursor) !== prev.text.slice(prev.cursor))\n return null\n if (cursor > prev.cursor) {\n if (text.slice(0, prev.cursor) !== prev.text.slice(0, prev.cursor))\n return null\n const typed = text.slice(prev.cursor, cursor)\n const shown = prev.suffixes[prev.index]\n const suffixes = prev.suffixes\n .filter(s => s.startsWith(typed) && s.length > typed.length)\n .map(s => s.slice(typed.length))\n if (suffixes.length === 0)\n return null\n // Keep the rotated-to candidate selected when it survives the filter.\n const shownTrimmed = shown.startsWith(typed) ? shown.slice(typed.length) : null\n const index = shownTrimmed !== null ? Math.max(0, suffixes.indexOf(shownTrimmed)) : 0\n return { suffixes, index, text, cursor }\n }\n if (prev.text.slice(0, cursor) !== text.slice(0, cursor))\n return null\n const deleted = prev.text.slice(cursor, prev.cursor)\n if (deleted.length === 0 || /\\s/.test(deleted))\n return null\n return { suffixes: prev.suffixes.map(s => deleted + s), index: prev.index, text, cursor }\n}\n","/**\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","/**\n * Metrics helper — wraps agent lifecycle hooks in caller-provided meters.\n *\n * Symmetric with {@link createTracingHooks} from `./tracing`. Where the\n * tracer answers \"what happened in this run?\" by producing spans, the\n * metrics helper answers \"how is the fleet behaving?\" by emitting\n * counters / histograms / up-down counters. Both are tracer-agnostic —\n * the caller supplies a `Meter` (OTel-API-shaped) and the helper plugs\n * the right hook on each metric.\n *\n * Instrument families:\n *\n * ### Histograms (distribution metrics)\n * - `gen_ai.client.operation.duration` (ms) — wall-clock per turn (`turn:before` → `turn:after`).\n * - `gen_ai.client.token.usage` — `input` / `output` token counts per turn (tagged by `gen_ai.token.type`).\n * - `gen_ai.client.time_to_first_token` (ms) — per-turn TTFT from `TurnUsage.timeToFirstTokenMs`.\n * - `gen_ai.tool.duration` (ms) — native tool execution time (`tool:before` → `tool:after`).\n * - `gen_ai.tool.output_bytes` — `tool:after.outputBytes`.\n * - `gen_ai.mcp.tool.duration` (ms) — MCP tool execution time.\n * - `gen_ai.mcp.bootstrap.duration` (ms) — MCP server bootstrap (already measured by harness).\n *\n * ### Counters (monotonic)\n * - `gen_ai.agent.runs` — `agent:start`.\n * - `gen_ai.agent.runs.completed` — `agent:done`.\n * - `gen_ai.agent.aborts` — `agent:abort`.\n * - `gen_ai.tool.calls` — `tool:dispatched` (tagged by `outcome`).\n * - `gen_ai.tool.errors` — `tool:error`.\n * - `gen_ai.mcp.tool.errors` — `mcp:tool:error`.\n * - `gen_ai.stream.errors` — `stream:error` (tagged by `status_code`).\n * - `gen_ai.validation.rejects` — `validation:reject`.\n * - `gen_ai.validation.coercions` — `validation:coerce`.\n * - `gen_ai.gate.blocks` — `tool:dispatched` outcome `gate-block`.\n * - `gen_ai.budget.exceeded` — `budget:exceeded`.\n * - `gen_ai.tool_budget.exceeded` — `tool-budget:exceeded` (tagged by `mode`).\n * - `gen_ai.pairing.repairs` — `pairing:repair` (tagged by `mode`).\n * - `gen_ai.oauth.refreshes` — `oauth:refresh`.\n * - `gen_ai.mcp.errors` — `mcp:error`.\n * - `gen_ai.mcp.auth.required` — `mcp:auth:required`.\n * - `gen_ai.cost_usd` — accumulated cost (added on `turn:after`).\n *\n * ### Up-down counter (gauge-shaped)\n * - `gen_ai.agent.runs.active` — incremented on `agent:start`, decremented on `agent:done`.\n *\n * The `Meter` shape mirrors `@opentelemetry/api`'s `Meter` exactly — passing\n * `metrics.getMeter('zidane')` works directly. Hosts using a different\n * metrics backend (StatsD, Prometheus client, custom in-memory aggregator)\n * implement the same three factory methods and the helper plugs in\n * unchanged.\n */\n\nimport type { Hookable } from 'hookable'\nimport type { AgentHooks } from './agent'\n\n// ---------------------------------------------------------------------------\n// OTel-shaped Meter interface\n// ---------------------------------------------------------------------------\n\nexport type MetricAttributes = Record<string, string | number | boolean | undefined>\n\nexport interface Counter {\n add: (value: number, attributes?: MetricAttributes) => void\n}\n\nexport interface Histogram {\n record: (value: number, attributes?: MetricAttributes) => void\n}\n\nexport interface UpDownCounter {\n add: (value: number, attributes?: MetricAttributes) => void\n}\n\nexport interface InstrumentOptions {\n description?: string\n unit?: string\n}\n\n/**\n * Minimal Meter interface — structurally identical to OTel's `Meter`.\n * Hosts passing `metrics.getMeter(name)` (from `@opentelemetry/api`)\n * satisfy this without adaptation.\n */\nexport interface Meter {\n createCounter: (name: string, options?: InstrumentOptions) => Counter\n createHistogram: (name: string, options?: InstrumentOptions) => Histogram\n createUpDownCounter: (name: string, options?: InstrumentOptions) => UpDownCounter\n}\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\nexport interface MetricsHooksOptions {\n meter: Meter\n /**\n * Optional prefix prepended to every instrument name. Default: no prefix\n * (instrument names follow OTel Gen AI semantic conventions verbatim,\n * which is the most-portable shape). Set to e.g. `'zidane.'` to\n * namespace inside a shared meter registry.\n */\n namespace?: string\n /**\n * Optional baseline attributes applied to every measurement. Typical\n * use: `{ service: 'tui', env: 'prod' }`. Per-event attributes win on\n * key collision.\n */\n baseAttributes?: MetricAttributes\n /**\n * Error sink for meter failures. The helper still swallows the throw\n * so a broken backend can't crash a run; this callback surfaces the\n * failure for ops dashboards.\n */\n onError?: (kind: string, err: unknown) => void\n}\n\nexport interface MetricsHookSet {\n install: (hooks: Hookable<AgentHooks>) => () => void\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction prefixed(prefix: string | undefined, name: string): string {\n return prefix ? `${prefix}${name}` : name\n}\n\n/**\n * Drop `undefined` entries before handing attributes to a metrics\n * backend. OTel's `@opentelemetry/api` rejects `undefined` attribute\n * values (and Prometheus / StatsD adapters silently mis-label them); the\n * helper's own API surface allows `undefined` for ergonomic call sites\n * (`{...(cond ? { foo } : {})}`-style spreads), so we strip here at the\n * boundary.\n */\nfunction mergeAttrs(\n base: MetricAttributes | undefined,\n extra: MetricAttributes,\n): Record<string, string | number | boolean> {\n const out: Record<string, string | number | boolean> = {}\n if (base) {\n for (const [k, v] of Object.entries(base)) {\n if (v !== undefined)\n out[k] = v\n }\n }\n for (const [k, v] of Object.entries(extra)) {\n if (v !== undefined)\n out[k] = v\n }\n return out\n}\n\n// ---------------------------------------------------------------------------\n// createMetricsHooks\n// ---------------------------------------------------------------------------\n\n/**\n * Build a set of metrics hook handlers that can be installed on an agent.\n *\n * @example OpenTelemetry\n * ```ts\n * import { metrics } from '@opentelemetry/api'\n * const meter = metrics.getMeter('zidane')\n * const m = createMetricsHooks({ meter, baseAttributes: { service: 'tui' } })\n * const uninstall = m.install(agent.hooks)\n * try { await agent.run({ prompt }) }\n * finally { uninstall() }\n * ```\n */\nexport function createMetricsHooks(options: MetricsHooksOptions): MetricsHookSet {\n const ns = options.namespace\n const base = options.baseAttributes\n const onError = options.onError ?? (() => {})\n\n const make = {\n counter: (name: string, opts?: InstrumentOptions) =>\n safeFactory(() => options.meter.createCounter(prefixed(ns, name), opts), name, onError),\n histogram: (name: string, opts?: InstrumentOptions) =>\n safeFactory(() => options.meter.createHistogram(prefixed(ns, name), opts), name, onError),\n upDown: (name: string, opts?: InstrumentOptions) =>\n safeFactory(() => options.meter.createUpDownCounter(prefixed(ns, name), opts), name, onError),\n }\n\n // Instruments — created once at install time.\n const turnDuration = make.histogram('gen_ai.client.operation.duration', { unit: 'ms', description: 'Per-turn LLM operation duration.' })\n const tokenUsage = make.histogram('gen_ai.client.token.usage', { unit: 'tokens', description: 'Per-turn token usage (tagged by gen_ai.token.type).' })\n const ttft = make.histogram('gen_ai.client.time_to_first_token', { unit: 'ms', description: 'Per-turn time to first token.' })\n const toolDuration = make.histogram('gen_ai.tool.duration', { unit: 'ms', description: 'Native tool execution wall clock.' })\n const toolOutput = make.histogram('gen_ai.tool.output_bytes', { unit: 'By', description: 'Native tool output bytes.' })\n const mcpToolDuration = make.histogram('gen_ai.mcp.tool.duration', { unit: 'ms', description: 'MCP tool execution wall clock.' })\n const mcpBootstrapDuration = make.histogram('gen_ai.mcp.bootstrap.duration', { unit: 'ms', description: 'MCP server bootstrap duration.' })\n\n const runsStarted = make.counter('gen_ai.agent.runs', { description: 'Runs started.' })\n const runsCompleted = make.counter('gen_ai.agent.runs.completed', { description: 'Runs completed.' })\n const aborts = make.counter('gen_ai.agent.aborts', { description: 'Run abort events.' })\n const toolCalls = make.counter('gen_ai.tool.calls', { description: 'Tool dispatches (tagged by outcome).' })\n const toolErrors = make.counter('gen_ai.tool.errors', { description: 'Native tool errors.' })\n const mcpToolErrors = make.counter('gen_ai.mcp.tool.errors', { description: 'MCP tool errors.' })\n const streamErrors = make.counter('gen_ai.stream.errors', { description: 'Provider stream errors.' })\n const validationRejects = make.counter('gen_ai.validation.rejects', { description: 'Tool input validation rejects.' })\n const validationCoercions = make.counter('gen_ai.validation.coercions', { description: 'Tool input auto-coercions.' })\n const gateBlocks = make.counter('gen_ai.gate.blocks', { description: 'Tool-gate refusals.' })\n const budgetExceeded = make.counter('gen_ai.budget.exceeded', { description: 'Per-turn byte budget exceedances.' })\n const toolBudgetExceeded = make.counter('gen_ai.tool_budget.exceeded', { description: 'Per-tool dispatch budget exceedances.' })\n const pairingRepairs = make.counter('gen_ai.pairing.repairs', { description: 'Tool-pairing repair counts.' })\n const oauthRefreshes = make.counter('gen_ai.oauth.refreshes', { description: 'Provider OAuth credential refreshes.' })\n const mcpErrors = make.counter('gen_ai.mcp.errors', { description: 'MCP server errors.' })\n const mcpAuthRequired = make.counter('gen_ai.mcp.auth.required', { description: 'MCP bootstrap blocked on missing auth.' })\n const costMeter = make.counter('gen_ai.cost_usd', { unit: 'USD', description: 'Cumulative cost across turns.' })\n\n const runsActive = make.upDown('gen_ai.agent.runs.active', { description: 'In-flight agent runs.' })\n\n return {\n install(hooks: Hookable<AgentHooks>): () => void {\n // Per-instrument book-keeping. Each map tracks instrument start times\n // keyed by the identifier that distinguishes concurrent in-flight\n // instances of that kind.\n const turnStart = new Map<string, number>() // turnId -> startedAt\n const toolStart = new Map<string, number>() // callId -> startedAt\n const mcpStart = new Map<string, number>() // callId -> startedAt\n\n const unregisters: Array<() => void> = []\n\n const record = <T extends { record: (v: number, a?: MetricAttributes) => void }>(\n instrument: T | undefined,\n value: number,\n attrs: MetricAttributes,\n kind: string,\n ): void => {\n if (!instrument)\n return\n try {\n instrument.record(value, mergeAttrs(base, attrs))\n }\n catch (err) {\n try {\n onError(kind, err)\n }\n catch { /* ignore */ }\n }\n }\n\n const add = <T extends { add: (v: number, a?: MetricAttributes) => void }>(\n instrument: T | undefined,\n value: number,\n attrs: MetricAttributes,\n kind: string,\n ): void => {\n if (!instrument)\n return\n try {\n instrument.add(value, mergeAttrs(base, attrs))\n }\n catch (err) {\n try {\n onError(kind, err)\n }\n catch { /* ignore */ }\n }\n }\n\n // ---- Agent lifecycle --------------------------------------------\n\n unregisters.push(hooks.hook('agent:start', (ctx) => {\n const attrs: MetricAttributes = {\n 'gen_ai.agent.depth': ctx.depth,\n ...(ctx.agentName ? { 'gen_ai.agent.name': ctx.agentName } : {}),\n }\n add(runsStarted, 1, attrs, 'runs')\n add(runsActive, 1, attrs, 'runs.active')\n }))\n\n unregisters.push(hooks.hook('agent:done', () => {\n add(runsCompleted, 1, {}, 'runs.completed')\n add(runsActive, -1, {}, 'runs.active')\n // Aborted turns skip `turn:after` / `tool:after` / `mcp:tool:after`,\n // stranding their start entries until uninstall — the run is over,\n // so flush them here.\n turnStart.clear()\n toolStart.clear()\n mcpStart.clear()\n }))\n\n unregisters.push(hooks.hook('agent:abort', () => {\n add(aborts, 1, {}, 'aborts')\n }))\n\n // ---- Turn / stream metrics -------------------------------------\n\n unregisters.push(hooks.hook('turn:before', (ctx) => {\n turnStart.set(ctx.turnId, Date.now())\n }))\n\n unregisters.push(hooks.hook('turn:after', (ctx) => {\n const started = turnStart.get(ctx.turnId)\n turnStart.delete(ctx.turnId)\n const tagsBase: MetricAttributes = {\n 'gen_ai.operation.name': 'chat',\n ...(ctx.usage.modelId ? { 'gen_ai.response.model': ctx.usage.modelId } : {}),\n ...(ctx.usage.finishReason ? { 'gen_ai.response.finish_reason': ctx.usage.finishReason } : {}),\n }\n if (typeof started === 'number')\n record(turnDuration, Date.now() - started, tagsBase, 'turn.duration')\n if (typeof ctx.usage.timeToFirstTokenMs === 'number')\n record(ttft, ctx.usage.timeToFirstTokenMs, tagsBase, 'ttft')\n\n record(tokenUsage, ctx.usage.input, { ...tagsBase, 'gen_ai.token.type': 'input' }, 'token.input')\n record(tokenUsage, ctx.usage.output, { ...tagsBase, 'gen_ai.token.type': 'output' }, 'token.output')\n if (typeof ctx.usage.cacheRead === 'number' && ctx.usage.cacheRead > 0)\n record(tokenUsage, ctx.usage.cacheRead, { ...tagsBase, 'gen_ai.token.type': 'cache_read' }, 'token.cache_read')\n if (typeof ctx.usage.cacheCreation === 'number' && ctx.usage.cacheCreation > 0)\n record(tokenUsage, ctx.usage.cacheCreation, { ...tagsBase, 'gen_ai.token.type': 'cache_creation' }, 'token.cache_creation')\n\n if (typeof ctx.usage.cost === 'number' && ctx.usage.cost > 0)\n add(costMeter, ctx.usage.cost, tagsBase, 'cost')\n }))\n\n unregisters.push(hooks.hook('stream:error', (ctx) => {\n add(streamErrors, 1, {\n 'http.response.status_code': ctx.statusCode,\n 'error.type': ctx.err instanceof Error ? ctx.err.name : 'unknown',\n }, 'stream.error')\n }))\n\n // ---- Native tool metrics ---------------------------------------\n\n unregisters.push(hooks.hook('tool:before', (ctx) => {\n toolStart.set(ctx.callId, Date.now())\n }))\n\n unregisters.push(hooks.hook('tool:after', (ctx) => {\n const started = toolStart.get(ctx.callId)\n toolStart.delete(ctx.callId)\n const tags: MetricAttributes = { 'gen_ai.tool.name': ctx.name }\n if (typeof started === 'number')\n record(toolDuration, Date.now() - started, tags, 'tool.duration')\n record(toolOutput, ctx.outputBytes, tags, 'tool.output_bytes')\n }))\n\n // NOTE: `tool:error` must NOT delete `toolStart[callId]` — on the\n // execute-error path the loop still fires `tool:after` afterwards\n // (with the `Tool error: …` payload), and that's where the errored\n // call's duration gets recorded + the entry cleaned up.\n unregisters.push(hooks.hook('tool:error', (ctx) => {\n add(toolErrors, 1, {\n 'gen_ai.tool.name': ctx.name,\n 'error.type': ctx.error.name,\n }, 'tool.errors')\n }))\n\n // Mid-flight per-call cancels close with `tool:cancelled` instead of\n // `tool:after` — clean up the start entry here or it leaks.\n unregisters.push(hooks.hook('tool:cancelled', (ctx) => {\n toolStart.delete(ctx.callId)\n }))\n\n unregisters.push(hooks.hook('tool:dispatched', (ctx) => {\n add(toolCalls, 1, {\n 'gen_ai.tool.name': ctx.name,\n 'outcome': ctx.outcome,\n }, 'tool.calls')\n if (ctx.outcome === 'gate-block') {\n add(gateBlocks, 1, {\n 'gen_ai.tool.name': ctx.name,\n ...(ctx.reason ? { reason: ctx.reason } : {}),\n }, 'gate.blocks')\n }\n // Unknown-tool dispatches fire `tool:before` (with `unknown: true`)\n // but never `tool:after` — drop the start entry so hallucinated\n // tool names can't grow the map unboundedly. `tool:dispatched` for\n // real executions fires BEFORE `tool:before`, so this only affects\n // the unknown path.\n if (ctx.outcome === 'unknown')\n toolStart.delete(ctx.callId)\n }))\n\n unregisters.push(hooks.hook('validation:reject', (ctx) => {\n add(validationRejects, 1, { 'gen_ai.tool.name': ctx.name }, 'validation.rejects')\n }))\n\n unregisters.push(hooks.hook('validation:coerce', (ctx) => {\n add(validationCoercions, 1, {\n 'gen_ai.tool.name': ctx.name,\n 'coercions': ctx.coercions.length,\n }, 'validation.coercions')\n }))\n\n unregisters.push(hooks.hook('budget:exceeded', (ctx) => {\n add(budgetExceeded, 1, {\n bytes: ctx.bytes,\n budget: ctx.budget,\n }, 'budget.exceeded')\n }))\n\n unregisters.push(hooks.hook('tool-budget:exceeded', (ctx) => {\n add(toolBudgetExceeded, 1, {\n 'gen_ai.tool.name': ctx.tool,\n 'mode': ctx.mode,\n 'count': ctx.count,\n 'max': ctx.max,\n }, 'tool_budget.exceeded')\n }))\n\n unregisters.push(hooks.hook('pairing:repair', (ctx) => {\n add(pairingRepairs, 1, { mode: ctx.mode }, 'pairing.repairs')\n }))\n\n unregisters.push(hooks.hook('oauth:refresh', (ctx) => {\n add(oauthRefreshes, 1, { provider: ctx.provider, source: ctx.source }, 'oauth.refreshes')\n }))\n\n // ---- MCP metrics -----------------------------------------------\n\n unregisters.push(hooks.hook('mcp:tool:before', (ctx) => {\n mcpStart.set(ctx.callId, Date.now())\n }))\n\n unregisters.push(hooks.hook('mcp:tool:after', (ctx) => {\n const started = mcpStart.get(ctx.callId)\n mcpStart.delete(ctx.callId)\n const tags: MetricAttributes = {\n 'gen_ai.mcp.server': ctx.server,\n 'gen_ai.tool.name': ctx.displayName,\n }\n if (typeof started === 'number')\n record(mcpToolDuration, Date.now() - started, tags, 'mcp.tool.duration')\n }))\n\n unregisters.push(hooks.hook('mcp:tool:error', (ctx) => {\n mcpStart.delete(ctx.callId)\n add(mcpToolErrors, 1, {\n 'gen_ai.mcp.server': ctx.server,\n 'gen_ai.tool.name': ctx.displayName,\n 'error.type': ctx.error.name,\n }, 'mcp.tool.errors')\n }))\n\n unregisters.push(hooks.hook('mcp:bootstrap:end', (ctx) => {\n record(mcpBootstrapDuration, ctx.durationMs, {\n 'gen_ai.mcp.server': ctx.name,\n 'ok': ctx.ok,\n }, 'mcp.bootstrap.duration')\n }))\n\n unregisters.push(hooks.hook('mcp:error', (ctx) => {\n add(mcpErrors, 1, {\n 'gen_ai.mcp.server': ctx.name,\n 'error.type': ctx.error.name,\n }, 'mcp.errors')\n }))\n\n unregisters.push(hooks.hook('mcp:auth:required', (ctx) => {\n add(mcpAuthRequired, 1, {\n 'gen_ai.mcp.server': ctx.name,\n 'transport': ctx.transport,\n 'reason': ctx.reason,\n }, 'mcp.auth.required')\n }))\n\n // -----------------------------------------------------------------\n // Disposal\n // -----------------------------------------------------------------\n\n let disposed = false\n return function uninstall() {\n if (disposed)\n return\n disposed = true\n for (const un of unregisters) {\n try {\n un()\n }\n catch { /* ignore */ }\n }\n turnStart.clear()\n toolStart.clear()\n mcpStart.clear()\n }\n },\n }\n}\n\n/**\n * Defensive factory wrapper — returns undefined when meter creation\n * throws. Lets a half-broken meter (one unsupported instrument) still\n * give partial metrics rather than collapsing the whole install.\n */\nfunction safeFactory<T>(\n factory: () => T,\n name: string,\n onError: (kind: string, err: unknown) => void,\n): T | undefined {\n try {\n return factory()\n }\n catch (err) {\n try {\n onError(`createInstrument:${name}`, err)\n }\n catch { /* ignore */ }\n return undefined\n }\n}\n","import type { AgentHookMap } from './agent'\nimport type { ToolOutcome, ToolResultContent } from './types'\n\ntype MaybePromise<T> = T | Promise<T>\n\nexport type SuccessfulMutationOutcome = Extract<ToolOutcome, 'created' | 'updated' | 'edited'>\n\nexport interface MutationValidationMutation {\n turnId: string\n callId: string\n tool: string\n displayName: string\n input: Record<string, unknown>\n outcome: SuccessfulMutationOutcome\n result: string | ToolResultContent[]\n}\n\nexport interface MutationValidationBatch {\n turnId: string\n mutations: readonly MutationValidationMutation[]\n}\n\nexport type MutationValidationResult\n = | { ok: true }\n | { ok: false, message?: string, errors?: readonly string[] }\n\nexport interface MutationValidationFailure {\n batch: MutationValidationBatch\n result: Extract<MutationValidationResult, { ok: false }>\n message: string\n failureCount: number\n}\n\nexport interface MutationValidationHooksOptions {\n validate: (batch: MutationValidationBatch) => MaybePromise<MutationValidationResult | void>\n /**\n * Called when validation fails and the run has not crossed\n * `maxFailuresPerRun`. Hosts typically call `agent.followUp(message)` here.\n */\n onFailure?: (failure: MutationValidationFailure) => MaybePromise<void>\n buildFailureMessage?: (failure: Omit<MutationValidationFailure, 'message'>) => string\n onError?: (error: unknown, batch: MutationValidationBatch) => MaybePromise<void>\n maxFailuresPerRun?: number\n}\n\nfunction defaultFailureMessage(failure: Omit<MutationValidationFailure, 'message'>): string {\n const changed = failure.batch.mutations\n .map(m => `${m.displayName} ${String(m.input.path ?? m.callId)}`)\n .join(', ')\n const details = failure.result.message\n ?? failure.result.errors?.join('\\n')\n ?? 'Validation failed after the last file mutation.'\n const seen = failure.failureCount > 1 ? ` (failure ${failure.failureCount})` : ''\n return `[Mutation validation failed${seen}] ${changed}\\n${details}\\nFix the validation errors with the smallest focused change.`\n}\n\nfunction isSuccessfulMutation(outcome: ToolOutcome | undefined): outcome is SuccessfulMutationOutcome {\n return outcome === 'created' || outcome === 'updated' || outcome === 'edited'\n}\n\n/**\n * Build host-side hooks for post-mutation validation without changing the\n * built-in edit/write tools. The helper only observes successful mutations\n * reported through `tool:after.outcome`; validation commands and recovery\n * prompts remain host policy.\n */\nexport function createMutationValidationHooks(options: MutationValidationHooksOptions): AgentHookMap {\n const pending: MutationValidationMutation[] = []\n let failureCount = 0\n const maxFailures = Math.max(1, Math.floor(options.maxFailuresPerRun ?? 2))\n\n return {\n 'tool:after': (ctx) => {\n if (!isSuccessfulMutation(ctx.outcome))\n return\n pending.push({\n turnId: ctx.turnId,\n callId: ctx.callId,\n tool: ctx.name,\n displayName: ctx.displayName,\n input: ctx.input,\n outcome: ctx.outcome,\n result: ctx.result,\n })\n },\n 'tool-results:after': async (ctx) => {\n if (pending.length === 0)\n return\n const mutations = pending.splice(0)\n const batch: MutationValidationBatch = {\n turnId: ctx.turnId,\n mutations,\n }\n let result: MutationValidationResult | void\n try {\n result = await options.validate(batch)\n }\n catch (err) {\n await options.onError?.(err, batch)\n return\n }\n if (!result || result.ok)\n return\n failureCount++\n if (failureCount > maxFailures)\n return\n const withoutMessage = { batch, result, failureCount }\n const message = options.buildFailureMessage?.(withoutMessage)\n ?? defaultFailureMessage(withoutMessage)\n await options.onFailure?.({ ...withoutMessage, message })\n },\n 'agent:done': () => {\n pending.length = 0\n // `agent:done` fires at the end of every run, so resetting here gives\n // `maxFailuresPerRun` true per-run semantics even when this hook map is\n // registered at agent level and reused across runs.\n failureCount = 0\n },\n }\n}\n","/**\n * Tracing helper — wraps agent lifecycle hooks in caller-provided spans.\n *\n * Zidane is hooks-native; rather than introducing a plugin abstraction just for\n * observability, we ship a small helper that turns an arbitrary `startSpan`\n * function into a bundle of hook handlers. Works with Sentry, OpenTelemetry,\n * Datadog — any tracer that can hand back an object with an `end()` method.\n *\n * Attribute conventions follow the Sentry AI Agents spec (which itself\n * tracks OpenTelemetry Gen AI semconv v1.36, with a few divergences). The\n * `conventions` option lets callers pick `'sentry'` (default — Sentry's\n * exact keys), `'otel'` (vendor-neutral OTel 1.36), or `'both'` (emit\n * every variant for maximum portability).\n *\n * Span coverage:\n *\n * - `invoke_agent <name?>` — root span, opens on `agent:start`, closes on `agent:done`. Sentry op `gen_ai.invoke_agent`.\n * - `chat <model?>` — per-turn LLM call, opens on `turn:before`, closes on `turn:after`. Sentry op `gen_ai.chat`.\n * - `execute_tool <name>` — native tool execution, opens on `tool:before`, closes on `tool:after` / `tool:error`. Sentry op `gen_ai.execute_tool`.\n * - `execute_tool <server>.<name>` — MCP tool variant, same op. Tagged with `gen_ai.tool.type: 'extension'`.\n * - `handoff to <task>` — subagent handoff (parent→child), opens on `spawn:before`, closes on `spawn:complete` / `spawn:error`. Sentry op `gen_ai.handoff`.\n * Also injects parent trace context into the spawn carrier when `propagator` is configured.\n * - `mcp.bootstrap <server>` — server bootstrap, opens on `mcp:bootstrap:start`, closes on `mcp:bootstrap:end`. Zidane-specific (no Sentry op equivalent).\n *\n * Span events (not spans — recorded on the active span via `addEvent`):\n *\n * - `gen_ai.gate.block` — a `tool:gate` listener refused a call.\n * - `gen_ai.validation.reject` — tool input failed schema validation.\n * - `gen_ai.validation.coerce` — tool input was auto-coerced to satisfy the schema.\n * - `gen_ai.budget.exceeded` — byte budget exceeded (`budget:exceeded`).\n * - `gen_ai.tool_budget.exceeded` — per-tool dispatch cap exceeded.\n * - `gen_ai.pairing.repair` — pre-send pairing repair fired.\n * - `gen_ai.stream.error` — provider stream errored (carries `statusCode` / `requestId`).\n * - `gen_ai.mcp.auth.required` — MCP server bootstrap blocked on missing OAuth tokens.\n * - `gen_ai.oauth.refresh` — provider OAuth credentials refreshed.\n *\n * Attribute naming follows the Sentry AI Agents conventions\n * (https://develop.sentry.dev/sdk/telemetry/traces/modules/ai-agents/) by\n * default — which closely track OTel Gen AI semconv v1.36 with a few\n * intentional differences (`usage.input_tokens.cached` instead of\n * `usage.cache_read_input_tokens`, `cost.total_tokens` instead of\n * `usage.cost_usd`, seconds instead of ms for `time_to_first_token`).\n * Set `conventions: 'otel'` to emit only the vendor-neutral OTel keys, or\n * `'both'` to emit everything for maximum portability. Legacy attribute\n * names (`inputTokens`, `toolName`, `displayName`, …) are also emitted by\n * default for back-compat; set `legacyAttributes: false` to drop them.\n *\n * On `agent:done` or `uninstall()` it closes any spans still open (defensive —\n * normally all tool/turn spans have matching end hooks, but an abort\n * mid-execution can leave dangling ones).\n *\n * Returns an `install(hooks)` helper that registers every handler at once and\n * yields an `uninstall()` function for cleanup. Safe across multiple runs.\n */\n\nimport type { Hookable } from 'hookable'\nimport type { AgentHooks } from './agent'\nimport type { SessionMessage } from './types'\n\n/** Minimal span shape — any tracer that provides these methods is compatible. */\nexport interface Span {\n /** Close the span. Called exactly once per span. */\n end: () => void\n /** Optional: attach additional attributes after the span is started (ignored if unsupported). */\n setAttributes?: (attrs: Record<string, unknown>) => void\n /**\n * Optional: record a structured event on the span. Used for non-span\n * occurrences (gate blocks, validation rejects, budget hits, pairing\n * repairs). Maps to OTel's `Span.addEvent` and Sentry's `addBreadcrumb`.\n * Tracers without an event surface should treat this as a no-op (the\n * helper falls back to `setAttributes` with an `event.*` key prefix).\n */\n addEvent?: (name: string, attrs?: Record<string, unknown>) => void\n}\n\n/**\n * Function that opens a span. Caller-provided so we stay tracer-agnostic.\n *\n * `parentContext` carries opaque trace metadata propagated from another\n * agent (typically a W3C `{ traceparent, tracestate }` carrier received on\n * `agent:start.tracingContext` after a parent agent's tracer injected it on\n * `spawn:before`). Implementations that integrate with OTel should extract\n * the carrier into a `Context` and use it as the parent when opening the\n * span; implementations that don't care about cross-agent linkage can\n * ignore the third argument entirely.\n */\nexport type StartSpan = (\n name: string,\n attrs?: Record<string, unknown>,\n parentContext?: Readonly<Record<string, string>>,\n) => Span\n\nexport type TracingConventions = 'sentry' | 'otel' | 'both'\n\nexport interface TracingHooksOptions {\n /** Tracer seam. Receives a span name + attributes; must return a `Span`. */\n startSpan: StartSpan\n /**\n * Optional attribute namespace. Prepended to every span name with a `/`\n * separator (e.g. `\"agent\"` → `agent/chat <model>`, `agent/execute_tool Bash`).\n *\n * Empty / undefined = no prefix.\n */\n namespace?: string\n /**\n * Which Gen-AI attribute conventions to emit.\n *\n * - `'sentry'` (default) — Sentry AI Agents conventions. Most common\n * choice — Sentry, Datadog AI, Honeycomb derived dashboards and\n * Langfuse all consume these.\n * - `'otel'` — vendor-neutral OpenTelemetry Gen AI semconv v1.36.\n * Diverges from Sentry on cache-token keys, cost keys, and TTFT unit.\n * - `'both'` — emit every variant. Doubles the per-span attribute\n * payload; pick when shipping to multiple backends with different\n * ingestion conventions.\n */\n conventions?: TracingConventions\n /**\n * Capture the actual prompt / completion text on spans\n * (`gen_ai.input.messages`, `gen_ai.output.messages`,\n * `gen_ai.system_instructions`, `gen_ai.tool.definitions`,\n * `gen_ai.tool.call.arguments`, `gen_ai.tool.call.result`).\n *\n * Defaults to `true` (required for the Sentry AI Agents conversation\n * viewer). Full prompts and tool arguments can carry secrets and PII;\n * captured content flows through `redact`, so pair the default with a\n * redactor when payloads may hold secrets, or set `false` to keep all\n * content-shaped data in-process.\n */\n captureMessageContent?: boolean\n /**\n * Emit legacy attribute keys (`inputTokens`, `outputTokens`, `toolName`,\n * `displayName`, `finishReason`, `modelId`) alongside the new\n * `gen_ai.*` semconv keys. Defaults to `true` so existing dashboards\n * keep working. Set `false` once you've migrated all consumers off the\n * legacy names.\n */\n legacyAttributes?: boolean\n /**\n * Error sink for tracer failures. Replaces the historic silent-swallow.\n * Called when `Span.end()`, `setAttributes()`, `addEvent()`, or\n * `startSpan()` throws — so degraded tracers stay visible. The helper\n * still does not let tracer errors propagate into the agent loop.\n *\n * `kind` identifies which step failed (`'startSpan'`, `'end'`,\n * `'setAttributes'`, `'addEvent'`, `'redact'`, `'getActiveTraceContext'`).\n * Default: no-op (silent).\n */\n onError?: (kind: string, err: unknown) => void\n /**\n * Optional callback the helper invokes on `spawn:before` (after opening\n * the spawn span) to read the active trace context. Whatever it returns\n * is written into `ctx.tracingContext` and forwarded to the child's\n * `agent.run({ tracingContext })`. Typical use:\n *\n * ```ts\n * getActiveTraceContext: () => {\n * const carrier: Record<string, string> = {}\n * propagation.inject(context.active(), carrier)\n * return carrier\n * }\n * ```\n *\n * Returning `undefined` or an empty object leaves the carrier empty\n * and the child runs with no parent context. The helper's own\n * `propagator` is preferred over this when both are set (the\n * propagator runs inside the helper; this callback is the escape\n * hatch for hosts wanting full control).\n */\n getActiveTraceContext?: () => Readonly<Record<string, string>> | undefined\n /**\n * Optional in-process span redactor. When set, every potentially\n * sensitive string (tool input, tool result, system prompt) flows\n * through this fn before landing on a span attribute / event. Returning\n * the input unchanged is a no-op.\n *\n * Composes with the harness-wide `tracing:redact` hook: if a host has\n * already registered a hook handler that mutates `ctx.redacted`, the\n * tracer reuses that pipeline; setting `redact` here registers an extra\n * step that runs only for the tracer's attributes. The hook is the\n * canonical path; `redact` is the convenience surface for tracer-only\n * setups.\n */\n redact?: (kind: string, value: string, meta?: Readonly<Record<string, unknown>>) => string\n}\n\n/** Value returned by {@link createTracingHooks} — install entrypoint. */\nexport interface TracingHookSet {\n /**\n * Attach every hook handler to the given agent hooks instance.\n *\n * @returns an `uninstall` function that detaches every handler and closes any\n * still-open spans. Safe to call multiple times.\n */\n install: (hooks: Hookable<AgentHooks>) => () => void\n}\n\n// ---------------------------------------------------------------------------\n// Gen AI semantic-convention attribute keys\n// ---------------------------------------------------------------------------\n\n/**\n * Stable keys we emit unconditionally — present in BOTH Sentry and OTel\n * conventions with identical semantics, so no `conventions` branching is\n * needed at the call site.\n */\nconst GEN_AI = {\n system: 'gen_ai.system',\n operationName: 'gen_ai.operation.name',\n requestModel: 'gen_ai.request.model',\n responseModel: 'gen_ai.response.model',\n responseFinishReasons: 'gen_ai.response.finish_reasons',\n responseId: 'gen_ai.response.id',\n responseStreaming: 'gen_ai.response.streaming',\n responseTokensPerSecond: 'gen_ai.response.tokens_per_second',\n // Sentry uses seconds; OTel uses ms. We emit one or both based on `conventions`.\n responseTimeToFirstTokenSeconds: 'gen_ai.response.time_to_first_token',\n responseTimeToFirstTokenMs: 'gen_ai.client.time_to_first_token',\n // Usage — Sentry expects input_tokens to INCLUDE cached, with subkeys for cache slices.\n usageInputTokens: 'gen_ai.usage.input_tokens',\n usageOutputTokens: 'gen_ai.usage.output_tokens',\n usageTotalTokens: 'gen_ai.usage.total_tokens',\n // Sentry-specific cache keys.\n usageInputTokensCached: 'gen_ai.usage.input_tokens.cached',\n usageInputTokensCacheWrite: 'gen_ai.usage.input_tokens.cache_write',\n usageOutputTokensReasoning: 'gen_ai.usage.output_tokens.reasoning',\n // OTel cache keys (older / vendor-neutral).\n usageCacheReadInputTokens: 'gen_ai.usage.cache_read_input_tokens',\n usageCacheCreationInputTokens: 'gen_ai.usage.cache_creation_input_tokens',\n usageReasoningTokens: 'gen_ai.usage.reasoning_tokens',\n // Cost — Sentry splits, OTel rolls up.\n costTotalTokens: 'gen_ai.cost.total_tokens',\n costInputTokens: 'gen_ai.cost.input_tokens',\n costOutputTokens: 'gen_ai.cost.output_tokens',\n usageCostUsd: 'gen_ai.usage.cost_usd',\n // Tool — Sentry uses `tool.call.arguments` / `tool.call.result`; OTel uses `tool.input` / `tool.output` (deprecated in Sentry).\n toolName: 'gen_ai.tool.name',\n toolDescription: 'gen_ai.tool.description',\n toolType: 'gen_ai.tool.type',\n toolCallId: 'gen_ai.tool.call.id',\n toolCallArguments: 'gen_ai.tool.call.arguments',\n toolCallResult: 'gen_ai.tool.call.result',\n toolMessage: 'gen_ai.tool.message',\n toolInputDeprecated: 'gen_ai.tool.input',\n toolOutputDeprecated: 'gen_ai.tool.output',\n // Request configuration.\n requestMaxTokens: 'gen_ai.request.max_tokens',\n requestTemperature: 'gen_ai.request.temperature',\n requestTopP: 'gen_ai.request.top_p',\n requestTopK: 'gen_ai.request.top_k',\n requestSeed: 'gen_ai.request.seed',\n requestFrequencyPenalty: 'gen_ai.request.frequency_penalty',\n requestPresencePenalty: 'gen_ai.request.presence_penalty',\n // Message content surfaces (Sentry conversation viewer).\n inputMessages: 'gen_ai.input.messages',\n outputMessages: 'gen_ai.output.messages',\n systemInstructions: 'gen_ai.system_instructions',\n toolDefinitions: 'gen_ai.tool.definitions',\n // Agent identity.\n agentName: 'gen_ai.agent.name',\n agentRunId: 'gen_ai.agent.run.id',\n agentParentRunId: 'gen_ai.agent.parent_run.id',\n agentDepth: 'gen_ai.agent.depth',\n pipelineName: 'gen_ai.pipeline.name',\n turn: 'gen_ai.agent.turn',\n // MCP-specific (zidane extension; no Sentry op equivalent).\n mcpServer: 'gen_ai.mcp.server',\n mcpToolCount: 'gen_ai.mcp.tool_count',\n} as const\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Compose namespace prefix once. */\nfunction prefixed(namespace: string | undefined, name: string): string {\n return namespace ? `${namespace}/${name}` : name\n}\n\n/** Drop undefined entries — tracers vary on how they render `undefined`. */\nfunction compact(attrs: Record<string, unknown>): Record<string, unknown> {\n const out: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(attrs)) {\n if (v !== undefined)\n out[k] = v\n }\n return out\n}\n\n/**\n * Convert a zidane `SessionMessage` (or `StreamOptions.messages` entry)\n * into Sentry's `{role, parts: [{type, content}]}` shape, JSON-stringify\n * the result. Returns `undefined` on serialization failure so the caller\n * skips the attribute rather than poisoning the span.\n *\n * Per Sentry footnote [7], binary blobs in image content are replaced\n * with `\"[Blob substitute]\"`. Tool calls become `{type: 'tool_call'}`\n * parts; tool results become `{type: 'tool_result'}` parts.\n */\ninterface SentryMessagePart {\n type: 'text' | 'tool_call' | 'tool_result' | 'reasoning'\n content?: string\n name?: string\n arguments?: string\n result?: string\n call_id?: string\n}\ninterface SentryMessage {\n role: 'user' | 'assistant' | 'system' | 'tool'\n parts: SentryMessagePart[]\n}\n\n/**\n * Wide-shape block record. `SessionContentBlock` is a discriminated\n * union — accepting the wide record lets us peek at the discriminator\n * without re-importing the entire union at this layer.\n */\ninterface AnyBlock { type: string, [k: string]: unknown }\n\nfunction blockToParts(block: AnyBlock): SentryMessagePart[] {\n switch (block.type) {\n case 'text':\n return [{ type: 'text', content: typeof block.text === 'string' ? block.text : '' }]\n case 'image':\n case 'audio':\n case 'video':\n case 'document':\n return [{ type: 'text', content: '[Blob substitute]' }]\n case 'tool_call':\n return [{\n type: 'tool_call',\n ...(typeof block.name === 'string' ? { name: block.name } : {}),\n ...(typeof block.id === 'string' ? { call_id: block.id } : {}),\n arguments: safeJson(block.input),\n }]\n case 'tool_result': {\n const output = block.output\n const text = typeof output === 'string'\n ? output\n : Array.isArray(output)\n ? (output as Array<{ type: string, text?: string }>)\n .map(b => (b.type === 'image' || b.type === 'audio' || b.type === 'video' || b.type === 'document') ? '[Blob substitute]' : (b.text ?? ''))\n .join('\\n')\n : ''\n return [{\n type: 'tool_result',\n ...(typeof block.callId === 'string' ? { call_id: block.callId } : {}),\n content: text,\n }]\n }\n case 'thinking':\n return [{ type: 'reasoning', content: typeof block.text === 'string' ? block.text : '' }]\n default:\n return []\n }\n}\n\nfunction messageToSentry(msg: { role: 'user' | 'assistant', content: readonly AnyBlock[] }): SentryMessage {\n const parts: SentryMessagePart[] = []\n for (const block of msg.content)\n parts.push(...blockToParts(block))\n return { role: msg.role, parts }\n}\n\nfunction safeJson(value: unknown): string {\n try {\n return JSON.stringify(value ?? null)\n }\n catch {\n return '\"[unserializable]\"'\n }\n}\n\n/**\n * Stringify an array of `SessionMessage`s into Sentry's `gen_ai.input.messages`\n * format. Each entry's role + content is mapped via `messageToSentry`.\n * Returns `undefined` when the input is empty or unserializable.\n */\nfunction stringifyMessages(messages: readonly SessionMessage[]): string | undefined {\n if (messages.length === 0)\n return undefined\n try {\n const sentry = messages.map(m => messageToSentry({\n role: m.role,\n content: m.content as readonly AnyBlock[],\n }))\n return JSON.stringify(sentry)\n }\n catch {\n return undefined\n }\n}\n\n/**\n * Stringify a `StreamOptions.tools` array into Sentry's\n * `gen_ai.tool.definitions` format: `[{name, description?, parameters?}, …]`.\n *\n * The input is `unknown[]` because `StreamOptions.tools` carries the\n * provider-formatted shape (Anthropic / OpenAI / OpenAI-compat all\n * differ). We probe the three names that overlap (`name`, `description`,\n * `inputSchema` / `parameters`) and degrade gracefully otherwise.\n */\nfunction stringifyToolDefs(tools: readonly unknown[] | undefined): string | undefined {\n if (!tools || tools.length === 0)\n return undefined\n try {\n return JSON.stringify(tools.map((raw) => {\n if (!raw || typeof raw !== 'object')\n return { name: 'anonymous' }\n const t = raw as Record<string, unknown>\n const name = typeof t.name === 'string' ? t.name : 'anonymous'\n const description = typeof t.description === 'string' ? t.description : undefined\n // OpenAI's wire format calls it `parameters`; Anthropic / ToolSpec\n // call it `input_schema` / `inputSchema`. Probe all three.\n const parameters = t.parameters ?? t.input_schema ?? t.inputSchema\n return {\n name,\n ...(description ? { description } : {}),\n ...(parameters !== undefined ? { parameters } : {}),\n }\n }))\n }\n catch {\n return undefined\n }\n}\n\n/**\n * Build the usage attribute set per the configured conventions. Sentry\n * expects `input_tokens` to include cached, with `input_tokens.cached`\n * + `input_tokens.cache_write` subkeys; OTel splits cache into\n * separate top-level keys. `'both'` emits everything.\n */\nfunction buildUsageAttrs(\n usage: {\n input: number\n output: number\n cacheRead?: number\n cacheCreation?: number\n thinking?: number\n cost?: number\n modelId?: string\n finishReason?: string\n timeToFirstTokenMs?: number\n },\n conventions: TracingConventions,\n): Record<string, unknown> {\n const cacheRead = usage.cacheRead ?? 0\n const cacheWrite = usage.cacheCreation ?? 0\n // Per Sentry: `input_tokens` includes cached. zidane reports `input` as\n // the uncached new-input slice, so the headline number is the sum.\n const inputInclCache = usage.input + cacheRead + cacheWrite\n const totalTokens = inputInclCache + usage.output\n const wantSentry = conventions === 'sentry' || conventions === 'both'\n const wantOtel = conventions === 'otel' || conventions === 'both'\n\n const out: Record<string, unknown> = {\n [GEN_AI.responseModel]: usage.modelId,\n [GEN_AI.responseFinishReasons]: usage.finishReason ? [usage.finishReason] : undefined,\n [GEN_AI.usageOutputTokens]: usage.output,\n [GEN_AI.usageTotalTokens]: totalTokens,\n }\n\n if (wantSentry) {\n out[GEN_AI.usageInputTokens] = inputInclCache\n if (cacheRead > 0)\n out[GEN_AI.usageInputTokensCached] = cacheRead\n if (cacheWrite > 0)\n out[GEN_AI.usageInputTokensCacheWrite] = cacheWrite\n if (typeof usage.thinking === 'number' && usage.thinking > 0)\n out[GEN_AI.usageOutputTokensReasoning] = usage.thinking\n if (typeof usage.cost === 'number')\n out[GEN_AI.costTotalTokens] = usage.cost\n if (typeof usage.timeToFirstTokenMs === 'number')\n out[GEN_AI.responseTimeToFirstTokenSeconds] = usage.timeToFirstTokenMs / 1000\n }\n if (wantOtel) {\n // OTel: input_tokens is the uncached slice; cache is separate.\n if (!wantSentry)\n out[GEN_AI.usageInputTokens] = usage.input\n if (cacheRead > 0)\n out[GEN_AI.usageCacheReadInputTokens] = cacheRead\n if (cacheWrite > 0)\n out[GEN_AI.usageCacheCreationInputTokens] = cacheWrite\n if (typeof usage.thinking === 'number' && usage.thinking > 0)\n out[GEN_AI.usageReasoningTokens] = usage.thinking\n if (typeof usage.cost === 'number')\n out[GEN_AI.usageCostUsd] = usage.cost\n if (typeof usage.timeToFirstTokenMs === 'number')\n out[GEN_AI.responseTimeToFirstTokenMs] = usage.timeToFirstTokenMs\n }\n return out\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Build a set of tracing hook handlers that can be installed on an agent.\n *\n * @example Sentry\n * ```ts\n * const tracing = createTracingHooks({\n * startSpan: (name, attrs) => Sentry.startInactiveSpan({ name, attributes: attrs }),\n * })\n * const uninstall = tracing.install(agent.hooks)\n * try { await agent.run({ prompt }) }\n * finally { uninstall() }\n * ```\n *\n * @example OpenTelemetry with parent-context propagation\n * ```ts\n * import { trace, context, propagation } from '@opentelemetry/api'\n *\n * const tracer = trace.getTracer('zidane')\n * const tracing = createTracingHooks({\n * startSpan: (name, attrs, parentCtx) => {\n * const ctx = parentCtx ? propagation.extract(context.active(), parentCtx) : context.active()\n * return tracer.startSpan(name, { attributes: attrs }, ctx)\n * },\n * getActiveTraceContext: () => {\n * const carrier: Record<string, string> = {}\n * propagation.inject(context.active(), carrier)\n * return carrier\n * },\n * })\n * ```\n */\nexport function createTracingHooks(options: TracingHooksOptions): TracingHookSet {\n const namespace = options.namespace\n const legacy = options.legacyAttributes ?? true\n const conventions = options.conventions ?? 'sentry'\n const wantSentry = conventions === 'sentry' || conventions === 'both'\n const captureContent = options.captureMessageContent !== false\n const onError = options.onError ?? (() => {})\n\n return {\n install(hooks: Hookable<AgentHooks>): () => void {\n // Span book-keeping. Each map keys by the identifier that's unique\n // across concurrent in-flight spans of that kind.\n const runSpans = new Map<string, Span>() // keyed by runId\n const turnSpans = new Map<string, Span>() // keyed by turnId\n const toolSpans = new Map<string, Span>() // keyed by callId\n const mcpSpans = new Map<string, Span>() // keyed by callId\n const spawnSpans = new Map<string, Span>() // keyed by spawn id\n const bootstrapSpans = new Map<string, Span>() // keyed by server name\n // Provider system identifier (`anthropic`, `openai`, …) captured at\n // `agent:start`. Stamped onto every chat / tool span belonging to\n // this run so Sentry's per-vendor breakdowns work. Reset on\n // `agent:done`.\n let activeSystem: string | undefined\n // Active turn book-keeping for tokens-per-second derivation.\n const turnTrack = new Map<string, { startedAt: number }>()\n // Active run span ref — used so non-span events (gate blocks,\n // budget hits, validation rejects, policy/pairing repairs) can\n // attach via `addEvent` on the in-flight run span. The run span is\n // the natural parent for cross-cutting policy events: it outlives\n // turn spans (which close before tool dispatch starts in the\n // current loop), and tool spans (which haven't opened on a\n // gate-block path).\n let activeRunSpan: Span | undefined\n\n function safeStartSpan(\n name: string,\n attrs: Record<string, unknown>,\n parentContext?: Readonly<Record<string, string>>,\n ): Span | undefined {\n try {\n return options.startSpan(prefixed(namespace, name), compact(attrs), parentContext)\n }\n catch (err) {\n try {\n onError('startSpan', err)\n }\n catch { /* ignore */ }\n return undefined\n }\n }\n\n function safeSetAttrs(span: Span | undefined, attrs: Record<string, unknown>): void {\n if (!span?.setAttributes)\n return\n try {\n span.setAttributes(compact(attrs))\n }\n catch (err) {\n try {\n onError('setAttributes', err)\n }\n catch { /* ignore */ }\n }\n }\n\n function safeEnd(map: Map<string, Span>, key: string): void {\n const span = map.get(key)\n if (!span)\n return\n try {\n span.end()\n }\n catch (err) {\n try {\n onError('end', err)\n }\n catch { /* ignore */ }\n }\n map.delete(key)\n }\n\n function endAll(map: Map<string, Span>): void {\n for (const [, span] of map) {\n try {\n span.end()\n }\n catch (err) {\n try {\n onError('end', err)\n }\n catch { /* ignore */ }\n }\n }\n map.clear()\n }\n\n function safeAddEvent(span: Span | undefined, name: string, attrs: Record<string, unknown>): void {\n if (!span)\n return\n if (typeof span.addEvent === 'function') {\n try {\n span.addEvent(name, compact(attrs))\n }\n catch (err) {\n try {\n onError('addEvent', err)\n }\n catch { /* ignore */ }\n }\n return\n }\n // Tracer doesn't support events — fall back to a single\n // `event.<name>.*` attribute so the data lands somewhere.\n safeSetAttrs(span, Object.fromEntries(\n Object.entries(compact(attrs)).map(([k, v]) => [`event.${name}.${k}`, v]),\n ))\n }\n\n function redact(kind: string, value: string, meta?: Readonly<Record<string, unknown>>): string {\n if (typeof options.redact !== 'function')\n return value\n try {\n return options.redact(kind, value, meta)\n }\n catch (err) {\n try {\n onError('redact', err)\n }\n catch { /* ignore */ }\n return value\n }\n }\n\n const unregisters: Array<() => void> = []\n\n // -----------------------------------------------------------------\n // agent.run (root)\n // -----------------------------------------------------------------\n\n unregisters.push(hooks.hook('agent:start', (ctx) => {\n activeSystem = ctx.providerName\n // Empty-string names produce `invoke_agent ` (trailing space).\n // Treat empty as \"no name\" so the span name reads cleanly.\n const displayName = ctx.agentName && ctx.agentName.length > 0\n ? ctx.agentName\n : 'agent'\n const span = safeStartSpan(\n `invoke_agent ${displayName}`,\n {\n [GEN_AI.operationName]: 'invoke_agent',\n [GEN_AI.system]: activeSystem,\n [GEN_AI.agentName]: ctx.agentName,\n [GEN_AI.agentRunId]: ctx.runId,\n [GEN_AI.agentParentRunId]: ctx.parentRunId,\n [GEN_AI.agentDepth]: ctx.depth,\n // Sentry-specific span op hint — backends that key off\n // `op` (Sentry Python/JS) read this directly; OTel\n // backends ignore unknown keys, so it's harmless.\n 'sentry.op': 'gen_ai.invoke_agent',\n ...(legacy\n ? {\n runId: ctx.runId,\n parentRunId: ctx.parentRunId,\n agentName: ctx.agentName,\n depth: ctx.depth,\n }\n : {}),\n },\n ctx.tracingContext,\n )\n if (span) {\n runSpans.set(ctx.runId, span)\n activeRunSpan = span\n }\n }))\n\n unregisters.push(hooks.hook('agent:done', (stats) => {\n // `agent:done` doesn't carry a runId on its payload (it's `AgentStats`).\n // The 99% case is a single in-flight run — close every open run span\n // and stamp the totals on each. Multi-run-concurrent users plug a\n // runId into stats via their own integration; for now we close all.\n const usageAttrs = buildUsageAttrs({\n input: stats.totalIn,\n output: stats.totalOut,\n cacheRead: stats.totalCacheRead,\n cacheCreation: stats.totalCacheCreation,\n cost: stats.cost,\n timeToFirstTokenMs: stats.timeTillFirstTokenMs,\n }, conventions)\n for (const [, span] of runSpans) {\n safeSetAttrs(span, {\n ...usageAttrs,\n [GEN_AI.turn]: stats.turns,\n 'gen_ai.agent.elapsed_ms': stats.elapsed,\n ...(legacy\n ? {\n totalIn: stats.totalIn,\n totalOut: stats.totalOut,\n totalCacheRead: stats.totalCacheRead,\n totalCacheCreation: stats.totalCacheCreation,\n cost: stats.cost,\n turns: stats.turns,\n elapsed: stats.elapsed,\n }\n : {}),\n })\n }\n endAll(runSpans)\n // Defensive — close anything else still hanging open.\n endAll(turnSpans)\n endAll(toolSpans)\n endAll(mcpSpans)\n endAll(spawnSpans)\n endAll(bootstrapSpans)\n activeRunSpan = undefined\n activeSystem = undefined\n turnTrack.clear()\n }))\n\n unregisters.push(hooks.hook('agent:abort', () => {\n for (const [, span] of runSpans)\n safeSetAttrs(span, { 'gen_ai.agent.status': 'aborted' })\n }))\n\n // -----------------------------------------------------------------\n // Turn span — OTel Gen AI `chat` operation\n // -----------------------------------------------------------------\n\n unregisters.push(hooks.hook('turn:before', (ctx) => {\n const requestedModel = ctx.options.model\n // Built-in Sentry/OTel attrs that don't depend on content capture.\n const baseAttrs: Record<string, unknown> = {\n [GEN_AI.operationName]: 'chat',\n [GEN_AI.system]: activeSystem,\n [GEN_AI.requestModel]: requestedModel,\n [GEN_AI.turn]: ctx.turn,\n [GEN_AI.responseStreaming]: true,\n [GEN_AI.requestMaxTokens]: ctx.options.maxTokens,\n 'sentry.op': 'gen_ai.chat',\n 'gen_ai.agent.turn_id': ctx.turnId,\n ...(legacy ? { turnId: ctx.turnId, turn: ctx.turn } : {}),\n }\n // Optionally surface conversation content — prompts, system\n // instructions, available tools. Each piece flows through the\n // redact seam so hosts can scrub secrets without losing the\n // structural attribute. Skipping content keeps the span cheap\n // and privacy-safe.\n if (captureContent) {\n const systemText = ctx.options.system\n if (systemText) {\n baseAttrs[GEN_AI.systemInstructions] = redact('system', systemText, {\n turnId: ctx.turnId,\n turn: ctx.turn,\n })\n }\n const inputMsgs = stringifyMessages(ctx.options.messages)\n if (inputMsgs) {\n baseAttrs[GEN_AI.inputMessages] = redact('prompt', inputMsgs, {\n turnId: ctx.turnId,\n turn: ctx.turn,\n })\n }\n const toolDefs = stringifyToolDefs(ctx.options.tools)\n if (toolDefs)\n baseAttrs[GEN_AI.toolDefinitions] = toolDefs\n }\n const span = safeStartSpan(\n `chat${requestedModel ? ` ${requestedModel}` : ''}`,\n baseAttrs,\n )\n if (span) {\n turnSpans.set(ctx.turnId, span)\n turnTrack.set(ctx.turnId, { startedAt: Date.now() })\n }\n }))\n\n unregisters.push(hooks.hook('stream:start', (ctx) => {\n safeAddEvent(turnSpans.get(ctx.turnId), 'gen_ai.stream.start', {\n startedAt: ctx.startedAt,\n })\n }))\n\n unregisters.push(hooks.hook('turn:after', (ctx) => {\n const span = turnSpans.get(ctx.turnId)\n const tracked = turnTrack.get(ctx.turnId)\n turnTrack.delete(ctx.turnId)\n // Tokens-per-second derivation. Use wall-clock duration since the\n // turn span opened, fall back to TTFT-adjusted slice when present.\n let tokensPerSecond: number | undefined\n if (tracked && ctx.usage.output > 0) {\n const elapsedMs = Date.now() - tracked.startedAt\n if (elapsedMs > 0)\n tokensPerSecond = (ctx.usage.output / elapsedMs) * 1000\n }\n const usageAttrs = buildUsageAttrs(ctx.usage, conventions)\n // Cumulative-through-this-turn totals. We compute `input_tokens`\n // here to mirror `gen_ai.usage.input_tokens`'s semantic on the\n // same span: it INCLUDES cached when Sentry conventions are in\n // play, matches the uncached slice when OTel-only. Without this\n // alignment the two keys (`usage.*` vs `cumulative.*`) would\n // silently differ in arithmetic.\n const cumInputTokens = wantSentry\n ? ctx.cumulativeUsage.input + ctx.cumulativeUsage.cacheRead + ctx.cumulativeUsage.cacheCreation\n : ctx.cumulativeUsage.input\n const attrs: Record<string, unknown> = {\n ...usageAttrs,\n [GEN_AI.responseTokensPerSecond]: tokensPerSecond,\n 'gen_ai.agent.cumulative.input_tokens': cumInputTokens,\n 'gen_ai.agent.cumulative.output_tokens': ctx.cumulativeUsage.output,\n 'gen_ai.agent.cumulative.total_tokens': cumInputTokens + ctx.cumulativeUsage.output,\n 'gen_ai.agent.cumulative.cache_read_input_tokens': ctx.cumulativeUsage.cacheRead,\n 'gen_ai.agent.cumulative.cache_creation_input_tokens': ctx.cumulativeUsage.cacheCreation,\n 'gen_ai.agent.cumulative.cost_usd': ctx.cumulativeUsage.cost,\n 'gen_ai.agent.cumulative.turns': ctx.cumulativeUsage.turns,\n ...(legacy\n ? {\n inputTokens: ctx.usage.input,\n outputTokens: ctx.usage.output,\n finishReason: ctx.usage.finishReason,\n modelId: ctx.usage.modelId,\n }\n : {}),\n }\n // Output messages — the assistant turn that just landed. Sentry's\n // conversation viewer keys off this; skipped when content capture\n // is off. `system` turns aren't serialized (Sentry's schema only\n // accepts user / assistant / system / tool — and we never emit\n // system role from the loop).\n if (captureContent && ctx.message && ctx.message.role !== 'system' && ctx.message.role !== 'command') {\n const outputMsgs = stringifyMessages([{\n role: ctx.message.role,\n content: ctx.message.content,\n }])\n if (outputMsgs)\n attrs[GEN_AI.outputMessages] = redact('stream-text', outputMsgs, { turnId: ctx.turnId, turn: ctx.turn })\n }\n safeSetAttrs(span, attrs)\n safeEnd(turnSpans, ctx.turnId)\n }))\n\n unregisters.push(hooks.hook('stream:error', (ctx) => {\n const span = turnSpans.get(ctx.turnId)\n const message = ctx.err instanceof Error ? ctx.err.message : String(ctx.err)\n safeSetAttrs(span, {\n 'error.type': ctx.err instanceof Error ? ctx.err.name : 'unknown',\n 'error.message': message,\n 'http.response.status_code': ctx.statusCode,\n 'gen_ai.request.id': ctx.requestId,\n })\n safeAddEvent(span, 'gen_ai.stream.error', {\n 'error.type': ctx.err instanceof Error ? ctx.err.name : 'unknown',\n 'error.message': message,\n 'http.response.status_code': ctx.statusCode,\n 'gen_ai.request.id': ctx.requestId,\n })\n }))\n\n // -----------------------------------------------------------------\n // Native tool span — OTel Gen AI `execute_tool` operation\n // -----------------------------------------------------------------\n\n unregisters.push(hooks.hook('tool:before', (ctx) => {\n const span = safeStartSpan(\n `execute_tool ${ctx.displayName}`,\n {\n [GEN_AI.operationName]: 'execute_tool',\n [GEN_AI.system]: activeSystem,\n [GEN_AI.toolName]: ctx.name,\n [GEN_AI.toolType]: 'function',\n [GEN_AI.toolCallId]: ctx.callId,\n 'sentry.op': 'gen_ai.execute_tool',\n 'gen_ai.tool.display_name': ctx.displayName,\n 'gen_ai.agent.turn_id': ctx.turnId,\n 'gen_ai.agent.depth': ctx.depth,\n 'gen_ai.agent.run.id': ctx.runId,\n 'gen_ai.agent.parent_run.id': ctx.parentRunId,\n ...(legacy\n ? {\n toolName: ctx.name,\n displayName: ctx.displayName,\n turnId: ctx.turnId,\n callId: ctx.callId,\n }\n : {}),\n },\n )\n if (span) {\n toolSpans.set(ctx.callId, span)\n if (captureContent) {\n // Attach (possibly redacted) input as an attribute. Sentry's\n // canonical key is `gen_ai.tool.call.arguments`; OTel's\n // older key is `gen_ai.tool.input` (now deprecated in\n // Sentry). Emit one or both based on `conventions`.\n try {\n const serialized = JSON.stringify(ctx.input)\n const redacted = redact('tool-input', serialized, {\n toolName: ctx.name,\n displayName: ctx.displayName,\n callId: ctx.callId,\n turnId: ctx.turnId,\n })\n const attrs: Record<string, unknown> = {}\n if (wantSentry)\n attrs[GEN_AI.toolCallArguments] = redacted\n if (conventions === 'otel' || conventions === 'both')\n attrs[GEN_AI.toolInputDeprecated] = redacted\n safeSetAttrs(span, attrs)\n }\n catch {\n // Non-serializable input (circular refs) — skip the attr;\n // the tool name and call id are still on the span.\n }\n }\n }\n }))\n\n unregisters.push(hooks.hook('tool:after', (ctx) => {\n const span = toolSpans.get(ctx.callId)\n safeSetAttrs(span, {\n 'gen_ai.tool.output_bytes': ctx.outputBytes,\n 'gen_ai.tool.coercions': ctx.coercions?.length ?? 0,\n })\n if (captureContent && typeof ctx.result === 'string') {\n const redacted = redact('tool-result', ctx.result, {\n toolName: ctx.name,\n callId: ctx.callId,\n })\n const attrs: Record<string, unknown> = {}\n if (wantSentry)\n attrs[GEN_AI.toolCallResult] = redacted\n if (conventions === 'otel' || conventions === 'both')\n attrs[GEN_AI.toolOutputDeprecated] = redacted\n safeSetAttrs(span, attrs)\n }\n safeEnd(toolSpans, ctx.callId)\n }))\n\n unregisters.push(hooks.hook('tool:error', (ctx) => {\n safeSetAttrs(toolSpans.get(ctx.callId), {\n 'error.type': ctx.error.name,\n 'error.message': ctx.error.message,\n })\n safeEnd(toolSpans, ctx.callId)\n }))\n\n // Tool gate / dispatch outcomes. Listen on `tool:dispatched` (fires\n // AFTER every `tool:gate` listener has had a chance to mutate\n // `block` / `result`) so the recorded `outcome` reflects the final\n // decision rather than the as-entered state. Attaches to the\n // in-flight run span; the chat span has already closed by tool\n // dispatch time in the current loop ordering, and no tool span has\n // opened for a `gate-block` / `unknown` / `invalid-input` path.\n unregisters.push(hooks.hook('tool:dispatched', (ctx) => {\n if (ctx.outcome === 'gate-block') {\n safeAddEvent(activeRunSpan, 'gen_ai.gate.block', {\n [GEN_AI.toolName]: ctx.name,\n [GEN_AI.toolCallId]: ctx.callId,\n reason: ctx.reason,\n })\n }\n else if (ctx.outcome === 'gate-substitute') {\n safeAddEvent(activeRunSpan, 'gen_ai.gate.substitute', {\n [GEN_AI.toolName]: ctx.name,\n [GEN_AI.toolCallId]: ctx.callId,\n })\n }\n else if (ctx.outcome === 'unknown') {\n safeAddEvent(activeRunSpan, 'gen_ai.tool.unknown', {\n [GEN_AI.toolName]: ctx.name,\n [GEN_AI.toolCallId]: ctx.callId,\n })\n }\n else if (ctx.outcome === 'invalid-input') {\n safeAddEvent(activeRunSpan, 'gen_ai.tool.invalid_input', {\n [GEN_AI.toolName]: ctx.name,\n [GEN_AI.toolCallId]: ctx.callId,\n })\n }\n }))\n\n unregisters.push(hooks.hook('validation:reject', (ctx) => {\n safeAddEvent(activeRunSpan, 'gen_ai.validation.reject', {\n [GEN_AI.toolName]: ctx.name,\n [GEN_AI.toolCallId]: ctx.callId,\n reason: ctx.reason,\n })\n }))\n\n unregisters.push(hooks.hook('validation:coerce', (ctx) => {\n safeAddEvent(activeRunSpan, 'gen_ai.validation.coerce', {\n [GEN_AI.toolName]: ctx.name,\n [GEN_AI.toolCallId]: ctx.callId,\n coercions: ctx.coercions,\n })\n }))\n\n unregisters.push(hooks.hook('budget:exceeded', (ctx) => {\n safeAddEvent(activeRunSpan, 'gen_ai.budget.exceeded', {\n turnId: ctx.turnId,\n bytes: ctx.bytes,\n budget: ctx.budget,\n })\n }))\n\n unregisters.push(hooks.hook('tool-budget:exceeded', (ctx) => {\n safeAddEvent(activeRunSpan, 'gen_ai.tool_budget.exceeded', {\n [GEN_AI.toolName]: ctx.tool,\n turnId: ctx.turnId,\n count: ctx.count,\n max: ctx.max,\n mode: ctx.mode,\n })\n }))\n\n unregisters.push(hooks.hook('pairing:repair', (ctx) => {\n safeAddEvent(activeRunSpan, 'gen_ai.pairing.repair', {\n mode: ctx.mode,\n callId: ctx.callId,\n turnId: ctx.turnId,\n })\n }))\n\n unregisters.push(hooks.hook('oauth:refresh', (ctx) => {\n safeAddEvent(activeRunSpan, 'gen_ai.oauth.refresh', {\n provider: ctx.provider,\n providerId: ctx.providerId,\n source: ctx.source,\n })\n }))\n\n // -----------------------------------------------------------------\n // MCP tool span — OTel Gen AI `execute_tool` operation, MCP variant\n // -----------------------------------------------------------------\n\n unregisters.push(hooks.hook('mcp:tool:before', (ctx) => {\n const span = safeStartSpan(\n `execute_tool ${ctx.server}.${ctx.tool}`,\n {\n [GEN_AI.operationName]: 'execute_tool',\n [GEN_AI.system]: activeSystem,\n [GEN_AI.toolName]: ctx.displayName,\n // MCP tools are Sentry's \"extension\" tool type — backed by an\n // out-of-process server rather than a local function.\n [GEN_AI.toolType]: 'extension',\n [GEN_AI.toolCallId]: ctx.callId,\n [GEN_AI.mcpServer]: ctx.server,\n 'sentry.op': 'gen_ai.execute_tool',\n 'gen_ai.mcp.tool': ctx.tool,\n 'gen_ai.agent.turn_id': ctx.turnId,\n 'gen_ai.agent.run.id': ctx.runId,\n ...(legacy\n ? {\n server: ctx.server,\n tool: ctx.tool,\n displayName: ctx.displayName,\n turnId: ctx.turnId,\n callId: ctx.callId,\n }\n : {}),\n },\n )\n if (span) {\n mcpSpans.set(ctx.callId, span)\n if (captureContent) {\n try {\n const redacted = redact('mcp-tool-input', JSON.stringify(ctx.input), {\n server: ctx.server,\n tool: ctx.tool,\n callId: ctx.callId,\n })\n const attrs: Record<string, unknown> = {}\n if (wantSentry)\n attrs[GEN_AI.toolCallArguments] = redacted\n if (conventions === 'otel' || conventions === 'both')\n attrs[GEN_AI.toolInputDeprecated] = redacted\n safeSetAttrs(span, attrs)\n }\n catch { /* ignore */ }\n }\n }\n }))\n\n unregisters.push(hooks.hook('mcp:tool:after', (ctx) => {\n const span = mcpSpans.get(ctx.callId)\n safeSetAttrs(span, { 'gen_ai.tool.output_bytes': ctx.outputBytes })\n if (captureContent && typeof ctx.result === 'string') {\n const redacted = redact('mcp-tool-result', ctx.result, {\n server: ctx.server,\n tool: ctx.tool,\n callId: ctx.callId,\n })\n const attrs: Record<string, unknown> = {}\n if (wantSentry)\n attrs[GEN_AI.toolCallResult] = redacted\n if (conventions === 'otel' || conventions === 'both')\n attrs[GEN_AI.toolOutputDeprecated] = redacted\n safeSetAttrs(span, attrs)\n }\n safeEnd(mcpSpans, ctx.callId)\n }))\n\n unregisters.push(hooks.hook('mcp:tool:error', (ctx) => {\n safeSetAttrs(mcpSpans.get(ctx.callId), {\n 'error.type': ctx.error.name,\n 'error.message': ctx.error.message,\n })\n safeEnd(mcpSpans, ctx.callId)\n }))\n\n // -----------------------------------------------------------------\n // MCP server bootstrap span\n // -----------------------------------------------------------------\n\n unregisters.push(hooks.hook('mcp:bootstrap:start', (ctx) => {\n const span = safeStartSpan(\n `mcp.bootstrap ${ctx.name}`,\n {\n [GEN_AI.operationName]: 'mcp.bootstrap',\n [GEN_AI.system]: activeSystem,\n [GEN_AI.mcpServer]: ctx.name,\n 'gen_ai.mcp.transport': ctx.transport,\n },\n )\n if (span)\n bootstrapSpans.set(ctx.name, span)\n }))\n\n unregisters.push(hooks.hook('mcp:bootstrap:end', (ctx) => {\n const span = bootstrapSpans.get(ctx.name)\n const ok = ctx.ok\n safeSetAttrs(span, {\n 'gen_ai.mcp.bootstrap.duration_ms': ctx.durationMs,\n 'gen_ai.mcp.bootstrap.ok': ok,\n ...(ok\n ? {\n [GEN_AI.mcpToolCount]: ctx.toolCount,\n 'gen_ai.mcp.lazy': ctx.lazy,\n 'gen_ai.mcp.cached': ctx.cached,\n }\n : {\n 'error.type': ctx.error.name,\n 'error.message': ctx.error.message,\n }),\n })\n safeEnd(bootstrapSpans, ctx.name)\n }))\n\n unregisters.push(hooks.hook('mcp:auth:required', (ctx) => {\n const span = bootstrapSpans.get(ctx.name)\n safeAddEvent(span ?? activeRunSpan, 'gen_ai.mcp.auth.required', {\n server: ctx.name,\n transport: ctx.transport,\n reason: ctx.reason,\n })\n }))\n\n unregisters.push(hooks.hook('mcp:error', (ctx) => {\n safeAddEvent(activeRunSpan, 'gen_ai.mcp.error', {\n 'server': ctx.name,\n 'error.type': ctx.error.name,\n 'error.message': ctx.error.message,\n })\n }))\n\n // -----------------------------------------------------------------\n // Spawn span — parent-side. Also injects trace context for the child.\n // -----------------------------------------------------------------\n\n unregisters.push(hooks.hook('spawn:before', (ctx) => {\n // Sentry: \"handoff from {from_agent} to {to_agent}\". We don't\n // always know the child agent's name at spawn time — fall back\n // to the truncated task as the destination identifier.\n const toAgent = ctx.task.length > 60 ? `${ctx.task.slice(0, 57)}…` : ctx.task\n const span = safeStartSpan(\n `handoff to ${toAgent}`,\n {\n [GEN_AI.operationName]: 'handoff',\n [GEN_AI.system]: activeSystem,\n 'sentry.op': 'gen_ai.handoff',\n 'gen_ai.agent.spawn.id': ctx.id,\n 'gen_ai.agent.spawn.task': ctx.task,\n 'gen_ai.agent.depth': ctx.depth,\n },\n )\n if (span)\n spawnSpans.set(ctx.id, span)\n\n // Inject parent trace context for the child agent. The spawn tool\n // passes whatever we write into `ctx.tracingContext` to the child's\n // `agent.run({ tracingContext })` — see `tools/spawn.ts`.\n if (typeof options.getActiveTraceContext === 'function' && ctx.tracingContext) {\n let carrier: Readonly<Record<string, string>> | undefined\n try {\n carrier = options.getActiveTraceContext()\n }\n catch (err) {\n try {\n onError('getActiveTraceContext', err)\n }\n catch { /* ignore */ }\n }\n if (carrier) {\n for (const [k, v] of Object.entries(carrier))\n ctx.tracingContext[k] = v\n }\n }\n }))\n\n unregisters.push(hooks.hook('spawn:complete', (ctx) => {\n const span = spawnSpans.get(ctx.id)\n const childUsage = buildUsageAttrs({\n input: ctx.stats.totalIn,\n output: ctx.stats.totalOut,\n cacheRead: ctx.stats.totalCacheRead,\n cacheCreation: ctx.stats.totalCacheCreation,\n cost: ctx.stats.cost,\n }, conventions)\n safeSetAttrs(span, {\n 'gen_ai.agent.spawn.status': ctx.status ?? 'completed',\n 'gen_ai.agent.spawn.depth': ctx.depth,\n ...childUsage,\n })\n safeEnd(spawnSpans, ctx.id)\n }))\n\n unregisters.push(hooks.hook('spawn:error', (ctx) => {\n safeSetAttrs(spawnSpans.get(ctx.id), {\n 'error.type': ctx.error.name,\n 'error.message': ctx.error.message,\n })\n safeEnd(spawnSpans, ctx.id)\n }))\n\n // -----------------------------------------------------------------\n // Disposal\n // -----------------------------------------------------------------\n\n let disposed = false\n return function uninstall() {\n if (disposed)\n return\n disposed = true\n for (const un of unregisters) {\n try {\n un()\n }\n catch { /* ignore */ }\n }\n endAll(runSpans)\n endAll(turnSpans)\n endAll(toolSpans)\n endAll(mcpSpans)\n endAll(spawnSpans)\n endAll(bootstrapSpans)\n activeRunSpan = undefined\n }\n },\n }\n}\n\n/**\n * OpenTelemetry Gen AI semantic-convention attribute keys used by the\n * built-in tracer. Exported so consumers integrating with raw OTel SDKs\n * (instead of going through `createTracingHooks`) can stay aligned with\n * the names the harness itself emits.\n */\nexport const GEN_AI_ATTRIBUTES = GEN_AI\n"],"mappings":"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"}