zidane 7.0.25 → 7.0.26

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 (89) hide show
  1. package/dist/{acp-BhbXWwj_.js → acp-C5O2YBst.js} +21 -3
  2. package/dist/acp-C5O2YBst.js.map +1 -0
  3. package/dist/acp-cli.js +4 -4
  4. package/dist/acp.d.ts +1 -1
  5. package/dist/acp.js +1 -1
  6. package/dist/{agent-LC2c1ue-.js → agent-BjI9-PlG.js} +9 -1
  7. package/dist/{agent-LC2c1ue-.js.map → agent-BjI9-PlG.js.map} +1 -1
  8. package/dist/agent.d.ts +1 -1
  9. package/dist/agent.js +1 -1
  10. package/dist/{anthropic-BSbDiW0J.js → anthropic-CUM2CjXS.js} +508 -299
  11. package/dist/anthropic-CUM2CjXS.js.map +1 -0
  12. package/dist/{approvals-Deg8JhMI.js → approvals-xpcwa2UM.js} +35 -4
  13. package/dist/approvals-xpcwa2UM.js.map +1 -0
  14. package/dist/{auth-Nq-tSMb9.js → auth-BhbRmx2I.js} +5 -5
  15. package/dist/{auth-Nq-tSMb9.js.map → auth-BhbRmx2I.js.map} +1 -1
  16. package/dist/chat/pure.d.ts +3 -3
  17. package/dist/chat.d.ts +5 -5
  18. package/dist/chat.js +3 -3
  19. package/dist/contexts/daytona.d.ts +1 -1
  20. package/dist/contexts/e2b.d.ts +1 -1
  21. package/dist/daemon-cli.js +1 -1
  22. package/dist/eval.d.ts +1 -1
  23. package/dist/extensions.d.ts +1 -1
  24. package/dist/extensions.js +1 -1
  25. package/dist/headless.d.ts +1 -1
  26. package/dist/headless.js +1 -1
  27. package/dist/{index-C4mY0oKE.d.ts → index-C0y2tSgk.d.ts} +26 -2
  28. package/dist/index-C0y2tSgk.d.ts.map +1 -0
  29. package/dist/{index-DQn4tuAA.d.ts → index-Co4Fddtj.d.ts} +12 -5
  30. package/dist/index-Co4Fddtj.d.ts.map +1 -0
  31. package/dist/index.d.ts +1 -1
  32. package/dist/index.js +4 -4
  33. package/dist/{ipc-BVqRR-42.js → ipc-BKe8SqXv.js} +2 -2
  34. package/dist/{ipc-BVqRR-42.js.map → ipc-BKe8SqXv.js.map} +1 -1
  35. package/dist/ipc.d.ts +5 -5
  36. package/dist/ipc.js +3 -3
  37. package/dist/mcp.d.ts +1 -1
  38. package/dist/{oauth-CmtlmCzi.js → oauth-B6F43fti.js} +16 -4
  39. package/dist/{oauth-CmtlmCzi.js.map → oauth-B6F43fti.js.map} +1 -1
  40. package/dist/output/stream-json.d.ts +1 -1
  41. package/dist/output/terminal.d.ts +1 -1
  42. package/dist/plugins.d.ts +1 -1
  43. package/dist/presets.d.ts +1 -1
  44. package/dist/presets.js +1 -1
  45. package/dist/providers/anthropic.d.ts +1 -1
  46. package/dist/providers/anthropic.js +1 -1
  47. package/dist/providers/arcee.d.ts +1 -1
  48. package/dist/providers/baseten.d.ts +1 -1
  49. package/dist/providers/cerebras.d.ts +1 -1
  50. package/dist/providers/local.d.ts +1 -1
  51. package/dist/providers/openai-compat.d.ts +1 -1
  52. package/dist/providers/openai.d.ts +1 -1
  53. package/dist/providers/openai.js +1 -1
  54. package/dist/providers/openrouter.d.ts +1 -1
  55. package/dist/providers/xai.d.ts +1 -1
  56. package/dist/providers/xai.js +1 -1
  57. package/dist/{providers-ZucqgwU7.js → providers-DPygcIX3.js} +2 -2
  58. package/dist/{providers-ZucqgwU7.js.map → providers-DPygcIX3.js.map} +1 -1
  59. package/dist/providers.d.ts +1 -1
  60. package/dist/providers.js +3 -3
  61. package/dist/restate.d.ts +1 -1
  62. package/dist/restate.js +1 -1
  63. package/dist/{server-qsKLtfXY.js → server-CGe2GGjg.js} +5 -5
  64. package/dist/{server-qsKLtfXY.js.map → server-CGe2GGjg.js.map} +1 -1
  65. package/dist/session/sqlite.d.ts +1 -1
  66. package/dist/session.d.ts +1 -1
  67. package/dist/skills.d.ts +1 -1
  68. package/dist/{tool-formatters-GZHxP824.d.ts → tool-formatters-bbgUM3zI.d.ts} +2 -2
  69. package/dist/{tool-formatters-GZHxP824.d.ts.map → tool-formatters-bbgUM3zI.d.ts.map} +1 -1
  70. package/dist/tools/fetch-url.d.ts +1 -1
  71. package/dist/tools/web-search.d.ts +1 -1
  72. package/dist/tools.d.ts +1 -1
  73. package/dist/tools.js +1 -1
  74. package/dist/{transcript-anchors-CY50AXdF.d.ts → transcript-anchors-BHyrChoO.d.ts} +3 -2
  75. package/dist/transcript-anchors-BHyrChoO.d.ts.map +1 -0
  76. package/dist/tui.d.ts +3 -3
  77. package/dist/tui.d.ts.map +1 -1
  78. package/dist/tui.js +56 -44
  79. package/dist/tui.js.map +1 -1
  80. package/dist/{turn-operations-CAmNETlu.d.ts → turn-operations-ZP5-2OWi.d.ts} +3 -3
  81. package/dist/{turn-operations-CAmNETlu.d.ts.map → turn-operations-ZP5-2OWi.d.ts.map} +1 -1
  82. package/dist/types.d.ts +1 -1
  83. package/package.json +1 -1
  84. package/dist/acp-BhbXWwj_.js.map +0 -1
  85. package/dist/anthropic-BSbDiW0J.js.map +0 -1
  86. package/dist/approvals-Deg8JhMI.js.map +0 -1
  87. package/dist/index-C4mY0oKE.d.ts.map +0 -1
  88. package/dist/index-DQn4tuAA.d.ts.map +0 -1
  89. package/dist/transcript-anchors-CY50AXdF.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { E as getModelCatalog, b as sanitizeToolSpecs, f as mapOAIFinishReason, j as setModelCatalog, l as classifyOpenAICompatError, m as openaiCompat, n as OpenAICompatHttpError, y as sanitizeToolSchema } from "./openai-compat-cIeLSED_.js";
2
2
  import { c as toolOutputBudgetByteLength, l as toolOutputByteLength, u as toolResultToText } from "./types-CWgENVLY.js";
3
- import { $ as installExtensionFromSpec, $n as defaultRepeatGuardTracked, A as selectFilesFromSession, An as resolveExtensionConfig, At as createSkillsRunScriptTool, B as buildUpToCompactPrompt, Bt as formatModelLine, C as resolveLegacySessionDir, D as resolveTasksDir, Dn as coerceExtensionConfigInput, Dt as isSpawnTool, E as resolveSessionDir, En as clampExtensionConfigNumber, Et as createSpawnTool, F as TRAILER, Fn as createExtensionBus, G as summaryToTurn, Gn as renderArtifactRow, Gt as glob, H as anchorPreviewFor, I as buildCompactPrompt, J as CompactPromptTooLongError, Jn as AUTO_COMPACT_MIN_GROWTH_FRACTION, Jt as artifacts_default, K as truncateHeadForPtlRetry, Kn as resolveArtifactKind, Kt as edit, L as buildFromCompactPrompt, Ln as BUILTIN_ARTIFACT_KINDS, M as compactConversation, Mn as sanitizeExtensionConfigActionPatch, N as BASE_INSTRUCTIONS, Nn as toWireConfigSchema, Nt as createShellTool, O as buildPostCompactAttachments, On as isValidExtensionConfigItem, P as NO_TOOLS_PREAMBLE, Pn as EXTENSION_BUS_MAX_PAYLOAD_BYTES, Q as fetchExtensionSource, Qn as defaultRepeatGuardNormalize, R as buildFullCompactPrompt, Rn as artifactKindLabel, Rt as multiEdit, S as resolveLegacyPersistDir, Sn as scanExtensionDirectories, T as resolvePersistDir, Tn as defineExtension, U as sliceForCompaction, Ut as createInteractionTool, V as ANCHOR_PREVIEW_MAX_CHARS, Vt as matchModelEntries, W as stripImagesFromTurns, Wn as refreshArtifact, Wt as grep, X as createExtensionPromptBridge, Xn as effectiveContextWindow, Y as EMPTY_EXTENSION_REGISTRY, Yn as OUTPUT_RESERVE_TOKENS, Z as ExtensionInstallError, Zn as shouldAutoCompact, _ as buildPersistedStub, _n as defaultExtensionScanPaths, _t as createWorkspaceSurface, a as stagedToolState, ar as hydrateReadStateFromSessionAsync, at as createExtensionHud, b as migrateSessionArtifactDirs, bn as isExtensionDefinition, bt as basicTools, c as INTERRUPT_MESSAGE_FOR_TOOL_USE, ct as buildExtensionRegistry, d as TOOL_USE_SKIPPED_MESSAGE, dt as filterExtensionRegistry, er as normalizeShellCommand, et as listExtensionCandidates, f as applyCompactSummaryCutoff, g as PERSISTENCE_PREVIEW_BYTES, gn as ENTRY_CANDIDATES, gt as createExtensionHostRuntimeBridge, h as PERSISTED_STUB_PREFIX, hn as registerExtensionHostModules, ht as createArtifactsSurface, i as isToolStateOps, ir as hydrateReadStateFromSession, it as updateInstalledExtension, j as selectRecentFiles, jn as resolveExtensionConfigValue, jt as createSkillsReadTool, k as selectFilesFromReadState, kn as isValidExtensionConfigValue, kt as createSkillsUseTool, l as SHELL_CASCADE_CANCEL_MESSAGE, lt as composeExtensionPreset, m as renderStaleReadNotice, mt as partitionExtensionCatalog, n as DEFAULT_MAX_VALUE_BYTES, nr as getReadState, nt as readExtensionInstallProvenance, o as normalizeProviders, or as readStateKey, ot as discoverAndBuildExtensionRegistry, p as collectNewlyStaleReadPaths, pt as mergeContributions, q as CompactInvalidInputError, qn as createArtifactDetectHook, qt as validateToolArgs, r as createToolStateController, rr as hashContent, rt as removeInstalledExtension, s as resolveModelTargets, sr as resolveReadStateMap, st as buildExtensionPreset, t as createAgent, tr as stableStringify, tt as parseExtensionInstallSource, u as TOOL_USE_CANCELLED_MESSAGE, ut as disposeExtensionRegistry, v as cleanupPersistedSession, vn as discoverExtensions, vt as composePresets, w as resolveMcpWarningsDir, wt as waitTask, xn as parseExtensionScanPathEnv, xt as basic_default, y as maybePersistToolResult, yn as inlineLoadedExtension, yt as definePreset, z as buildTailCompactPrompt, zt as createModelSearchTool } from "./agent-LC2c1ue-.js";
3
+ import { $ as installExtensionFromSpec, $n as defaultRepeatGuardTracked, A as selectFilesFromSession, An as resolveExtensionConfig, At as createSkillsRunScriptTool, B as buildUpToCompactPrompt, Bt as formatModelLine, C as resolveLegacySessionDir, D as resolveTasksDir, Dn as coerceExtensionConfigInput, Dt as isSpawnTool, E as resolveSessionDir, En as clampExtensionConfigNumber, Et as createSpawnTool, F as TRAILER, Fn as createExtensionBus, G as summaryToTurn, Gn as renderArtifactRow, Gt as glob, H as anchorPreviewFor, I as buildCompactPrompt, J as CompactPromptTooLongError, Jn as AUTO_COMPACT_MIN_GROWTH_FRACTION, Jt as artifacts_default, K as truncateHeadForPtlRetry, Kn as resolveArtifactKind, Kt as edit, L as buildFromCompactPrompt, Ln as BUILTIN_ARTIFACT_KINDS, M as compactConversation, Mn as sanitizeExtensionConfigActionPatch, N as BASE_INSTRUCTIONS, Nn as toWireConfigSchema, Nt as createShellTool, O as buildPostCompactAttachments, On as isValidExtensionConfigItem, P as NO_TOOLS_PREAMBLE, Pn as EXTENSION_BUS_MAX_PAYLOAD_BYTES, Q as fetchExtensionSource, Qn as defaultRepeatGuardNormalize, R as buildFullCompactPrompt, Rn as artifactKindLabel, Rt as multiEdit, S as resolveLegacyPersistDir, Sn as scanExtensionDirectories, T as resolvePersistDir, Tn as defineExtension, U as sliceForCompaction, Ut as createInteractionTool, V as ANCHOR_PREVIEW_MAX_CHARS, Vt as matchModelEntries, W as stripImagesFromTurns, Wn as refreshArtifact, Wt as grep, X as createExtensionPromptBridge, Xn as effectiveContextWindow, Y as EMPTY_EXTENSION_REGISTRY, Yn as OUTPUT_RESERVE_TOKENS, Z as ExtensionInstallError, Zn as shouldAutoCompact, _ as buildPersistedStub, _n as defaultExtensionScanPaths, _t as createWorkspaceSurface, a as stagedToolState, ar as hydrateReadStateFromSessionAsync, at as createExtensionHud, b as migrateSessionArtifactDirs, bn as isExtensionDefinition, bt as basicTools, c as INTERRUPT_MESSAGE_FOR_TOOL_USE, ct as buildExtensionRegistry, d as TOOL_USE_SKIPPED_MESSAGE, dt as filterExtensionRegistry, er as normalizeShellCommand, et as listExtensionCandidates, f as applyCompactSummaryCutoff, g as PERSISTENCE_PREVIEW_BYTES, gn as ENTRY_CANDIDATES, gt as createExtensionHostRuntimeBridge, h as PERSISTED_STUB_PREFIX, hn as registerExtensionHostModules, ht as createArtifactsSurface, i as isToolStateOps, ir as hydrateReadStateFromSession, it as updateInstalledExtension, j as selectRecentFiles, jn as resolveExtensionConfigValue, jt as createSkillsReadTool, k as selectFilesFromReadState, kn as isValidExtensionConfigValue, kt as createSkillsUseTool, l as SHELL_CASCADE_CANCEL_MESSAGE, lt as composeExtensionPreset, m as renderStaleReadNotice, mt as partitionExtensionCatalog, n as DEFAULT_MAX_VALUE_BYTES, nr as getReadState, nt as readExtensionInstallProvenance, o as normalizeProviders, or as readStateKey, ot as discoverAndBuildExtensionRegistry, p as collectNewlyStaleReadPaths, pt as mergeContributions, q as CompactInvalidInputError, qn as createArtifactDetectHook, qt as validateToolArgs, r as createToolStateController, rr as hashContent, rt as removeInstalledExtension, s as resolveModelTargets, sr as resolveReadStateMap, st as buildExtensionPreset, t as createAgent, tr as stableStringify, tt as parseExtensionInstallSource, u as TOOL_USE_CANCELLED_MESSAGE, ut as disposeExtensionRegistry, v as cleanupPersistedSession, vn as discoverExtensions, vt as composePresets, w as resolveMcpWarningsDir, wt as waitTask, xn as parseExtensionScanPathEnv, xt as basic_default, y as maybePersistToolResult, yn as inlineLoadedExtension, yt as definePreset, z as buildTailCompactPrompt, zt as createModelSearchTool } from "./agent-BjI9-PlG.js";
4
4
  import { A as zodToJsonSchema, F as parseCommandInvocation, I as localReliabilityBehavior, M as commandBlocksToContent, N as commandSteps, O as isStandardSchema, P as executeCommand, S as resolveInlineSuggestion, _ as resolveExtensionDocsDir, a as loginMcpServer, b as createPathAutocompleteProvider, c as SKILL_EDIT_METADATA_KEY, d as buildZidaneExtensionSkill, f as buildZidaneSkillEditSkill, g as readSkillEditMarker, h as extensionScaffold, i as createMetricsHooks, j as commandBlockToEvent, k as resolveSchemaInput, l as ZIDANE_EXTENSION_SKILL_NAME, m as extensionDirForCwd, n as createTracingHooks, o as startOAuthCallback, p as editSessionMetadata, r as createMutationValidationHooks, s as EXTENSION_EDIT_METADATA_KEY, t as GEN_AI_ATTRIBUTES, u as ZIDANE_SKILL_EDIT_SKILL_NAME, v as withZidaneExtensionSkill, w as generateStructuredData, x as patchInlineGhost, y as withZidaneSkillEditSkill } from "./src-DzzhE2YO.js";
5
5
  import { n as estimateTokens, r as utf8ByteLength, t as BYTES_PER_TOKEN } from "./utils-ngQzYzZD.js";
6
6
  import { r as createProcessContext, t as resolveDetachedTasksCapability } from "./contexts-BSFQVclW.js";
@@ -23,12 +23,12 @@ import { s as resolveCompletionSubmit } from "./completion-core-BRo86R_L.js";
23
23
  import { t as createRunSummaryCollector } from "./run-summary-BU_NgwUT.js";
24
24
  import { headlessEventToJsonl, runHeadless, transcriptToOpenAIMessages } from "./headless.js";
25
25
  import { EFFICIENCY_METRICS, EvalMetricError, artifactPath, buildEvalRunSummary, buildRegisteredEvals, buildTrajectory, clearRegisteredEvals, computeEvalTagScores, createEvalAgent, createEvalRunReporter, createReusableExecutionContext, defineEval, defineMetrics, efficiencyMetricValues, emitEfficiencyMetrics, fileContains, fileContentQuality, fileExists, fileExistsOneOf, finalizeEvalMetrics, formatEvalCaseSummary, formatEvalRunSummary, formatTrajectoryLine, functionalityMetric, llmJudge, normalizeMetric, registerEvalTests, relativeArtifactPath, runEvalCase, statusCompleted } from "./eval.js";
26
- import { n as anthropic, r as applyAnthropicCacheBreakpoints } from "./anthropic-BSbDiW0J.js";
27
- import { a as readOAuthCredentials, d as getOAuthApiKey, f as getOAuthProvider, h as registerOAuthProvider, i as isOAuthAccessToken, l as writeOAuthCredentials, m as refreshOpenAICodexToken, n as credentialsFromParams, o as registerCustomOAuthProvider, p as refreshAnthropicToken, r as extractRuntimeCredentials, s as resolveOAuthApiKey, t as credentialsFilePath } from "./oauth-CmtlmCzi.js";
26
+ import { n as anthropic, r as applyAnthropicCacheBreakpoints } from "./anthropic-CUM2CjXS.js";
27
+ import { a as readOAuthCredentials, d as getOAuthApiKey, f as getOAuthProvider, h as registerOAuthProvider, i as isOAuthAccessToken, l as writeOAuthCredentials, m as refreshOpenAICodexToken, n as credentialsFromParams, o as registerCustomOAuthProvider, p as refreshAnthropicToken, r as extractRuntimeCredentials, s as resolveOAuthApiKey, t as credentialsFilePath } from "./oauth-B6F43fti.js";
28
28
  import { arcee } from "./providers/arcee.js";
29
29
  import { baseten } from "./providers/baseten.js";
30
30
  import { cerebras } from "./providers/cerebras.js";
31
- import { r as mistral } from "./providers-ZucqgwU7.js";
31
+ import { r as mistral } from "./providers-DPygcIX3.js";
32
32
  import { local } from "./providers/local.js";
33
33
  import { openai } from "./providers/openai.js";
34
34
  import { openrouter } from "./providers/openrouter.js";
@@ -1,5 +1,5 @@
1
1
  import { R as findGitRoot } from "./skills-PJyiSRCp.js";
2
- import { d as defaultSocketPath, o as connectOrSpawnDaemon } from "./server-qsKLtfXY.js";
2
+ import { d as defaultSocketPath, o as connectOrSpawnDaemon } from "./server-CGe2GGjg.js";
3
3
  import process from "node:process";
4
4
  import { createInterface } from "node:readline";
5
5
  //#region src/ipc/attach.ts
@@ -328,4 +328,4 @@ function clipLines(text, max) {
328
328
  //#endregion
329
329
  export { runAttach as t };
330
330
 
331
- //# sourceMappingURL=ipc-BVqRR-42.js.map
331
+ //# sourceMappingURL=ipc-BKe8SqXv.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ipc-BVqRR-42.js","names":[],"sources":["../src/ipc/attach.ts"],"sourcesContent":["/**\n * `zidane attach` — a thin terminal client over the daemon protocol.\n *\n * Deliberately minimal (readline, no TUI framework): it exists to prove and\n * exercise the multi-window contract — several attach clients plus the GUI\n * can watch the SAME session converge live, answer each other's approvals,\n * and steer the same run. The full OpenTUI front-end migrates onto\n * `connectIpcClient` separately.\n *\n * Everything here is presentation: all state lives in the daemon.\n */\nimport type { InteractionRequest, StreamEvent } from '../chat/index'\nimport type { AnswerValue } from '../chat/interactions'\nimport type { IpcClient } from './client'\nimport type { EnsureDaemonOptions } from './daemon/lifecycle'\nimport type { HostSignal, SessionRow, WireApprovalRequest } from './protocol'\nimport process from 'node:process'\nimport { createInterface } from 'node:readline'\nimport { findGitRoot } from '../chat/index'\nimport { connectOrSpawnDaemon } from './client'\nimport { defaultSocketPath } from './daemon/lifecycle'\n\nconst DIM = '\\u001B[2m'\nconst BOLD = '\\u001B[1m'\nconst CYAN = '\\u001B[36m'\nconst YELLOW = '\\u001B[33m'\nconst RED = '\\u001B[31m'\nconst RESET = '\\u001B[0m'\n\nexport interface AttachOptions {\n socketPath?: string\n /** Attach to this session id (prefix match against the list). */\n sessionId?: string\n /** Create a fresh session instead of picking. */\n createNew?: boolean\n cwd?: string\n appVersion?: string\n /** Spawn-on-demand command when no daemon is listening. */\n spawnCommand?: string[]\n /** Injectable streams for tests. */\n stdin?: NodeJS.ReadableStream\n stdout?: NodeJS.WritableStream\n}\n\ninterface PendingAsk {\n resolve: (line: string) => void\n}\n\nexport async function runAttach(options: AttachOptions = {}): Promise<void> {\n const out = options.stdout ?? process.stdout\n const write = (text: string): void => void out.write(text)\n const cwd = options.cwd ?? process.cwd()\n\n const ensureOptions: EnsureDaemonOptions = {\n socketPath: options.socketPath ?? defaultSocketPath(),\n ...(options.spawnCommand ? { spawnCommand: options.spawnCommand } : {}),\n }\n /*\n * Library-safe failure: `runAttach` is exported from `zidane/ipc`, so a\n * dropped daemon REJECTS (the CLI wrapper owns the exit code) instead\n * of killing an embedder's process from inside the library.\n */\n let failRun: ((error: Error) => void) | null = null\n\n // Drain-and-respawn on version mismatch — a stale daemon self-heals\n // instead of hard-failing the window.\n const client = await connectOrSpawnDaemon({\n ...ensureOptions,\n ...(options.appVersion ? { appVersion: options.appVersion } : {}),\n onClose: () => {\n write(`\\n${RED}daemon connection lost${RESET}\\n`)\n failRun?.(new Error('daemon connection lost'))\n },\n })\n\n write(`${DIM}connected to zidane daemon (pid ${client.hello.pid}, v${client.hello.appVersion})${RESET}\\n`)\n\n const rl = createInterface({\n input: (options.stdin ?? process.stdin) as NodeJS.ReadableStream,\n output: out as NodeJS.WritableStream,\n prompt: `${BOLD}> ${RESET}`,\n })\n\n /*\n * Line routing: normally a line is a prompt for the model, but while an\n * approval / interaction / picker question is pending, the next line\n * answers it instead. One slot suffices — questions are asked serially.\n * Registered BEFORE the session picker so `ask` works during picking.\n */\n let pendingAsk: PendingAsk | null = null\n let submitLine: ((line: string) => void) | null = null\n /** Prompt lines typed before the input loop is armed (mid-boot) queue up. */\n const earlyLines: string[] = []\n\n rl.on('line', (line) => {\n if (pendingAsk) {\n const slot = pendingAsk\n pendingAsk = null\n slot.resolve(line)\n return\n }\n if (submitLine)\n submitLine(line)\n else\n earlyLines.push(line)\n })\n\n const ask = (prompt: string): Promise<string> => {\n write(prompt)\n return new Promise<string>((resolve) => {\n pendingAsk = { resolve }\n })\n }\n\n /*\n * HITL prompts share the single `pendingAsk` slot, so approvals /\n * interactions MUST be asked one at a time — the startup rehydrate loops\n * and live signals can otherwise fire several handlers at once, each\n * overwriting the previous resolver and leaving those prompts hung\n * forever (unanswerable from this window). Chain every handler on the\n * previous one so they run strictly sequentially.\n */\n let hitlQueue: Promise<void> = Promise.resolve()\n\n /*\n * De-dup + resolution tracking for HITL prompts. A request can arrive\n * both via `*:listPending` backfill AND a live `*:request` signal, and\n * another window can answer it while it's still queued here. We track\n * every id we've queued (so we never prompt twice) and every id the host\n * reported resolved (so a queued-but-not-yet-prompted request is skipped\n * instead of blocking the queue on a no-op stdin answer).\n */\n const queuedHitlIds = new Set<string>()\n const resolvedHitlIds = new Set<string>()\n\n const enqueueHitl = (requestId: string, task: () => Promise<void>): void => {\n if (queuedHitlIds.has(requestId) || resolvedHitlIds.has(requestId))\n return\n queuedHitlIds.add(requestId)\n hitlQueue = hitlQueue.then(async () => {\n // Another window may have answered it while it waited in the queue.\n if (resolvedHitlIds.has(requestId))\n return\n await task()\n }, () => undefined)\n }\n\n const session = await pickSession(client, { ask, write, cwd, ...options })\n if (!session) {\n rl.close()\n client.close()\n return\n }\n\n write(`${BOLD}session${RESET} ${session.id} ${DIM}(${session.title})${RESET}\\n`)\n write(`${DIM}tools run in ${session.cwd} — /interrupt to stop a run, /exit to detach (run keeps going)${RESET}\\n\\n`)\n\n /*\n * Subscribe BEFORE replaying the transcript / snapshotting run state so\n * stream events emitted in that window aren't permanently lost from this\n * client's view. Signals that arrive during replay buffer here and drain\n * once the replay completes.\n */\n const bufferedSignals: HostSignal[] = []\n let signalSink: (signal: HostSignal) => void = s => bufferedSignals.push(s)\n const unsubscribe = client.onSignal(s => signalSink(s))\n\n await replayTranscript(client, session.id, write)\n\n let running = await client.request('chat.isRunning', { sessionId: session.id })\n if (running)\n write(`${DIM}a run is in flight — attaching to the live stream${RESET}\\n`)\n\n /* ----------------------------------------------------------------------\n * Signal rendering\n * -------------------------------------------------------------------- */\n\n let lastEventKind: StreamEvent['kind'] | null = null\n\n /**\n * The host broadcasts every window's user message as a `user-prompt`\n * event — render it so this window sees prompts posted elsewhere. Our\n * OWN just-sent messages are skipped once each (readline already echoed\n * them). A multiset (text → count) handles rapid identical sends and\n * never suppresses another window's matching message beyond our own\n * outstanding count.\n */\n const ownEchoes = new Map<string, number>()\n\n const renderEvent = (event: StreamEvent): void => {\n switch (event.kind) {\n case 'user-prompt': {\n const outstanding = ownEchoes.get(event.text) ?? 0\n if (outstanding > 0) {\n if (outstanding === 1)\n ownEchoes.delete(event.text)\n else\n ownEchoes.set(event.text, outstanding - 1)\n break\n }\n write(`\\n${BOLD}> ${event.text}${RESET}\\n`)\n break\n }\n case 'markdown':\n if (lastEventKind !== 'markdown' && lastEventKind !== null)\n write('\\n')\n write(event.text)\n break\n case 'thinking':\n // Streamed reasoning stays out of the transcript — show a pulse once.\n if (lastEventKind !== 'thinking')\n write(`${DIM}(thinking…)${RESET}\\n`)\n break\n case 'tool':\n write(`\\n${CYAN}▸ ${event.text}${RESET}\\n`)\n break\n case 'tool-result':\n write(`${DIM}${clipLines(event.text, 3)}${RESET}\\n`)\n break\n case 'task-notification':\n case 'info':\n write(`${DIM}${event.text}${RESET}\\n`)\n break\n case 'error':\n write(`${RED}${event.text}${RESET}\\n`)\n break\n default:\n break\n }\n lastEventKind = event.kind\n }\n\n const handleApproval = async (request: WireApprovalRequest): Promise<void> => {\n write(`\\n${YELLOW}approval needed${RESET} — ${BOLD}${request.tool}${RESET}\\n`)\n write(`${DIM}${clipLines(JSON.stringify(request.input, null, 2), 12)}${RESET}\\n`)\n const answer = await ask(`${YELLOW}[y]es once / [s]ession / [a]lways (safelist) / [n]o: ${RESET}`)\n const decision\n = answer.trim().toLowerCase() === 'y'\n ? 'accept-once'\n : answer.trim().toLowerCase() === 's'\n ? 'accept-session'\n : answer.trim().toLowerCase() === 'a'\n ? 'accept-safelist'\n : 'deny'\n await client.request('approvals.resolve', { requestId: request.id, decision })\n }\n\n const handleInteraction = async (request: InteractionRequest): Promise<void> => {\n if (request.kind === 'plan') {\n write(`\\n${YELLOW}plan:${RESET} ${BOLD}${request.payload.title}${RESET}\\n${request.payload.plan}\\n`)\n const answer = await ask(`${YELLOW}[a]pprove / [r]eject / re[v]ise (with comment): ${RESET}`)\n const c = answer.trim().toLowerCase()\n if (c === 'v') {\n const comment = await ask('comment: ')\n await client.request('interactions.respond', {\n requestId: request.id,\n response: { kind: 'plan', decision: 'revise', comment },\n })\n return\n }\n await client.request('interactions.respond', {\n requestId: request.id,\n response: { kind: 'plan', decision: c === 'a' ? 'approve' : 'reject' },\n })\n return\n }\n\n if (request.payload.intro)\n write(`\\n${request.payload.intro}\\n`)\n const answers: Record<string, AnswerValue> = {}\n for (const question of request.payload.questions) {\n if (question.type === 'confirm') {\n const answer = await ask(`${YELLOW}${question.prompt}${RESET} [y/n]: `)\n answers[question.id] = answer.trim().toLowerCase().startsWith('y')\n continue\n }\n if (question.type === 'select') {\n write(`${YELLOW}${question.prompt}${RESET}\\n`)\n question.choices.forEach((choice, i) => write(` ${i + 1}. ${choice.label}\\n`))\n const answer = await ask(question.multi ? 'pick (comma-separated numbers): ' : 'pick a number: ')\n const picks = answer\n .split(',')\n .map(part => question.choices[Number.parseInt(part.trim(), 10) - 1]?.id)\n .filter((id): id is string => typeof id === 'string')\n answers[question.id] = question.multi ? picks : (picks[0] ?? '')\n continue\n }\n answers[question.id] = await ask(`${YELLOW}${question.prompt}${RESET}: `)\n }\n await client.request('interactions.respond', {\n requestId: request.id,\n response: { kind: 'question', answers },\n })\n }\n\n const onSignal = (signal: HostSignal): void => {\n // Converge on resolution: mark the id answered so a queued-but-not-yet-\n // prompted request is skipped instead of blocking on stdin, and record\n // it so late backfill can't re-raise it.\n if (signal.kind === 'approval:resolved' || signal.kind === 'interaction:resolved') {\n resolvedHitlIds.add(signal.requestId)\n return\n }\n\n if (signal.kind === 'approval:request') {\n // Only surface approvals for the attached session — other windows'\n // sessions raise their own prompts in their own windows.\n if (signal.request.sessionId === session.id)\n enqueueHitl(signal.request.id, () => handleApproval(signal.request))\n return\n }\n\n if ('sessionId' in signal && signal.sessionId !== session.id)\n return\n\n switch (signal.kind) {\n case 'run:start':\n running = true\n lastEventKind = null\n break\n case 'run:end':\n running = false\n write(signal.ok ? '\\n' : `\\n${RED}run failed: ${signal.error ?? 'unknown error'}${RESET}\\n`)\n if (!pendingAsk)\n rl.prompt()\n break\n case 'event':\n renderEvent(signal.event)\n break\n case 'compacting:start':\n write(`${DIM}compacting…${RESET}\\n`)\n break\n case 'interaction:request':\n enqueueHitl(signal.request.id, () => handleInteraction(signal.request))\n break\n default:\n break\n }\n }\n\n // Surface HITL state left pending before this window attached. Done\n // BEFORE draining buffered signals so a live `*:request` that arrived\n // during backfill de-dups against the same id (via `queuedHitlIds`).\n for (const request of await client.request('approvals.listPending', {})) {\n if (request.sessionId === session.id)\n enqueueHitl(request.id, () => handleApproval(request))\n }\n for (const pending of await client.request('interactions.list', { sessionId: session.id })) {\n if (pending.live)\n enqueueHitl(pending.request.id, () => handleInteraction(pending.request))\n }\n\n // Drain signals buffered during replay/backfill, then hand live signals\n // straight to `onSignal`.\n signalSink = onSignal\n for (const buffered of bufferedSignals.splice(0))\n onSignal(buffered)\n\n /* ----------------------------------------------------------------------\n * Input loop\n * -------------------------------------------------------------------- */\n\n const detach = (): void => {\n unsubscribe()\n rl.close()\n client.close()\n write(`\\n${DIM}detached — the session keeps running in the daemon${RESET}\\n`)\n }\n\n rl.on('SIGINT', () => {\n if (running) {\n // Fire-and-forget: a dead daemon rejects the request, and an\n // uncaught rejection would crash the attach client.\n client.request('chat.interrupt', { sessionId: session.id }).catch(() => undefined)\n write(`\\n${DIM}interrupt sent${RESET}\\n`)\n return\n }\n detach()\n })\n\n await new Promise<void>((resolve, reject) => {\n failRun = (error) => {\n unsubscribe()\n rl.close()\n client.close()\n reject(error)\n }\n rl.on('close', () => resolve())\n\n const handleLine = (line: string): void => {\n const text = line.trim()\n if (text.length === 0) {\n rl.prompt()\n return\n }\n if (text === '/exit' || text === '/quit') {\n detach()\n resolve()\n return\n }\n if (text === '/interrupt') {\n client.request('chat.interrupt', { sessionId: session.id }).catch(() => undefined)\n return\n }\n\n ownEchoes.set(text, (ownEchoes.get(text) ?? 0) + 1)\n void client.request('chat.pushQueued', { sessionId: session.id, message: text }).then(({ delivered }) => {\n if (delivered === 'steer')\n write(`${DIM}(steered into the live run)${RESET}\\n`)\n }).catch((err) => {\n // Send failed: the host never broadcast the prompt, so release the\n // pending echo suppression to avoid eating a later identical one.\n const outstanding = ownEchoes.get(text) ?? 0\n if (outstanding <= 1)\n ownEchoes.delete(text)\n else\n ownEchoes.set(text, outstanding - 1)\n write(`${RED}${err instanceof Error ? err.message : String(err)}${RESET}\\n`)\n rl.prompt()\n })\n }\n\n submitLine = handleLine\n while (earlyLines.length > 0)\n handleLine(earlyLines.shift()!)\n\n if (!running)\n rl.prompt()\n })\n}\n\n/* --------------------------------------------------------------------------\n * Session picking\n * ------------------------------------------------------------------------ */\n\nasync function pickSession(\n client: IpcClient,\n ctx: {\n ask: (prompt: string) => Promise<string>\n write: (text: string) => void\n cwd: string\n sessionId?: string\n createNew?: boolean\n },\n): Promise<SessionRow | null> {\n const projectRoot = findGitRoot(ctx.cwd) ?? ctx.cwd\n\n if (ctx.createNew)\n return client.request('sessions.create', { cwd: ctx.cwd })\n\n const rows = await client.request('sessions.list', { projectRoot, archive: 'active' })\n\n if (ctx.sessionId) {\n const match = rows.find(r => r.id === ctx.sessionId || r.id.startsWith(ctx.sessionId!))\n if (match)\n return match\n // Not in this project — try the full list before giving up.\n const all = await client.request('sessions.list', {})\n const global = all.find(r => r.id === ctx.sessionId || r.id.startsWith(ctx.sessionId!))\n if (global)\n return global\n ctx.write(`${RED}no session matching \"${ctx.sessionId}\"${RESET}\\n`)\n return null\n }\n\n if (rows.length === 0)\n return client.request('sessions.create', { cwd: ctx.cwd })\n\n ctx.write(`${BOLD}sessions in ${projectRoot}:${RESET}\\n`)\n rows.slice(0, 15).forEach((row, i) => {\n const status = row.status === 'running' ? ` ${YELLOW}● running${RESET}` : ''\n ctx.write(` ${i + 1}. ${row.title} ${DIM}(${row.id.slice(0, 8)})${RESET}${status}\\n`)\n })\n\n const answer = await ctx.ask('pick a number, or [n]ew: ')\n if (answer.trim().toLowerCase() === 'n')\n return client.request('sessions.create', { cwd: ctx.cwd })\n\n const picked = rows[Number.parseInt(answer.trim(), 10) - 1]\n if (!picked) {\n ctx.write(`${RED}invalid pick${RESET}\\n`)\n return null\n }\n return picked\n}\n\n/* --------------------------------------------------------------------------\n * Transcript replay\n * ------------------------------------------------------------------------ */\n\nconst REPLAY_EVENT_CAP = 60\n\nasync function replayTranscript(\n client: IpcClient,\n sessionId: string,\n write: (text: string) => void,\n): Promise<void> {\n const events = await client.request('sessions.loadEvents', { sessionId })\n if (events.length === 0)\n return\n\n const tail = events.slice(-REPLAY_EVENT_CAP)\n if (events.length > tail.length)\n write(`${DIM}… ${events.length - tail.length} earlier events omitted${RESET}\\n`)\n\n for (const event of tail) {\n if (event.kind === 'user-prompt')\n write(`\\n${BOLD}> ${event.text}${RESET}\\n`)\n else if (event.kind === 'markdown')\n write(`${event.text}\\n`)\n else if (event.kind === 'tool')\n write(`${CYAN}▸ ${event.text}${RESET}\\n`)\n }\n write('\\n')\n}\n\nfunction clipLines(text: string, max: number): string {\n const lines = text.split('\\n')\n if (lines.length <= max)\n return text\n return `${lines.slice(0, max).join('\\n')}\\n${DIM}… ${lines.length - max} more lines${RESET}`\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ipc-BKe8SqXv.js","names":[],"sources":["../src/ipc/attach.ts"],"sourcesContent":["/**\n * `zidane attach` — a thin terminal client over the daemon protocol.\n *\n * Deliberately minimal (readline, no TUI framework): it exists to prove and\n * exercise the multi-window contract — several attach clients plus the GUI\n * can watch the SAME session converge live, answer each other's approvals,\n * and steer the same run. The full OpenTUI front-end migrates onto\n * `connectIpcClient` separately.\n *\n * Everything here is presentation: all state lives in the daemon.\n */\nimport type { InteractionRequest, StreamEvent } from '../chat/index'\nimport type { AnswerValue } from '../chat/interactions'\nimport type { IpcClient } from './client'\nimport type { EnsureDaemonOptions } from './daemon/lifecycle'\nimport type { HostSignal, SessionRow, WireApprovalRequest } from './protocol'\nimport process from 'node:process'\nimport { createInterface } from 'node:readline'\nimport { findGitRoot } from '../chat/index'\nimport { connectOrSpawnDaemon } from './client'\nimport { defaultSocketPath } from './daemon/lifecycle'\n\nconst DIM = '\\u001B[2m'\nconst BOLD = '\\u001B[1m'\nconst CYAN = '\\u001B[36m'\nconst YELLOW = '\\u001B[33m'\nconst RED = '\\u001B[31m'\nconst RESET = '\\u001B[0m'\n\nexport interface AttachOptions {\n socketPath?: string\n /** Attach to this session id (prefix match against the list). */\n sessionId?: string\n /** Create a fresh session instead of picking. */\n createNew?: boolean\n cwd?: string\n appVersion?: string\n /** Spawn-on-demand command when no daemon is listening. */\n spawnCommand?: string[]\n /** Injectable streams for tests. */\n stdin?: NodeJS.ReadableStream\n stdout?: NodeJS.WritableStream\n}\n\ninterface PendingAsk {\n resolve: (line: string) => void\n}\n\nexport async function runAttach(options: AttachOptions = {}): Promise<void> {\n const out = options.stdout ?? process.stdout\n const write = (text: string): void => void out.write(text)\n const cwd = options.cwd ?? process.cwd()\n\n const ensureOptions: EnsureDaemonOptions = {\n socketPath: options.socketPath ?? defaultSocketPath(),\n ...(options.spawnCommand ? { spawnCommand: options.spawnCommand } : {}),\n }\n /*\n * Library-safe failure: `runAttach` is exported from `zidane/ipc`, so a\n * dropped daemon REJECTS (the CLI wrapper owns the exit code) instead\n * of killing an embedder's process from inside the library.\n */\n let failRun: ((error: Error) => void) | null = null\n\n // Drain-and-respawn on version mismatch — a stale daemon self-heals\n // instead of hard-failing the window.\n const client = await connectOrSpawnDaemon({\n ...ensureOptions,\n ...(options.appVersion ? { appVersion: options.appVersion } : {}),\n onClose: () => {\n write(`\\n${RED}daemon connection lost${RESET}\\n`)\n failRun?.(new Error('daemon connection lost'))\n },\n })\n\n write(`${DIM}connected to zidane daemon (pid ${client.hello.pid}, v${client.hello.appVersion})${RESET}\\n`)\n\n const rl = createInterface({\n input: (options.stdin ?? process.stdin) as NodeJS.ReadableStream,\n output: out as NodeJS.WritableStream,\n prompt: `${BOLD}> ${RESET}`,\n })\n\n /*\n * Line routing: normally a line is a prompt for the model, but while an\n * approval / interaction / picker question is pending, the next line\n * answers it instead. One slot suffices — questions are asked serially.\n * Registered BEFORE the session picker so `ask` works during picking.\n */\n let pendingAsk: PendingAsk | null = null\n let submitLine: ((line: string) => void) | null = null\n /** Prompt lines typed before the input loop is armed (mid-boot) queue up. */\n const earlyLines: string[] = []\n\n rl.on('line', (line) => {\n if (pendingAsk) {\n const slot = pendingAsk\n pendingAsk = null\n slot.resolve(line)\n return\n }\n if (submitLine)\n submitLine(line)\n else\n earlyLines.push(line)\n })\n\n const ask = (prompt: string): Promise<string> => {\n write(prompt)\n return new Promise<string>((resolve) => {\n pendingAsk = { resolve }\n })\n }\n\n /*\n * HITL prompts share the single `pendingAsk` slot, so approvals /\n * interactions MUST be asked one at a time — the startup rehydrate loops\n * and live signals can otherwise fire several handlers at once, each\n * overwriting the previous resolver and leaving those prompts hung\n * forever (unanswerable from this window). Chain every handler on the\n * previous one so they run strictly sequentially.\n */\n let hitlQueue: Promise<void> = Promise.resolve()\n\n /*\n * De-dup + resolution tracking for HITL prompts. A request can arrive\n * both via `*:listPending` backfill AND a live `*:request` signal, and\n * another window can answer it while it's still queued here. We track\n * every id we've queued (so we never prompt twice) and every id the host\n * reported resolved (so a queued-but-not-yet-prompted request is skipped\n * instead of blocking the queue on a no-op stdin answer).\n */\n const queuedHitlIds = new Set<string>()\n const resolvedHitlIds = new Set<string>()\n\n const enqueueHitl = (requestId: string, task: () => Promise<void>): void => {\n if (queuedHitlIds.has(requestId) || resolvedHitlIds.has(requestId))\n return\n queuedHitlIds.add(requestId)\n hitlQueue = hitlQueue.then(async () => {\n // Another window may have answered it while it waited in the queue.\n if (resolvedHitlIds.has(requestId))\n return\n await task()\n }, () => undefined)\n }\n\n const session = await pickSession(client, { ask, write, cwd, ...options })\n if (!session) {\n rl.close()\n client.close()\n return\n }\n\n write(`${BOLD}session${RESET} ${session.id} ${DIM}(${session.title})${RESET}\\n`)\n write(`${DIM}tools run in ${session.cwd} — /interrupt to stop a run, /exit to detach (run keeps going)${RESET}\\n\\n`)\n\n /*\n * Subscribe BEFORE replaying the transcript / snapshotting run state so\n * stream events emitted in that window aren't permanently lost from this\n * client's view. Signals that arrive during replay buffer here and drain\n * once the replay completes.\n */\n const bufferedSignals: HostSignal[] = []\n let signalSink: (signal: HostSignal) => void = s => bufferedSignals.push(s)\n const unsubscribe = client.onSignal(s => signalSink(s))\n\n await replayTranscript(client, session.id, write)\n\n let running = await client.request('chat.isRunning', { sessionId: session.id })\n if (running)\n write(`${DIM}a run is in flight — attaching to the live stream${RESET}\\n`)\n\n /* ----------------------------------------------------------------------\n * Signal rendering\n * -------------------------------------------------------------------- */\n\n let lastEventKind: StreamEvent['kind'] | null = null\n\n /**\n * The host broadcasts every window's user message as a `user-prompt`\n * event — render it so this window sees prompts posted elsewhere. Our\n * OWN just-sent messages are skipped once each (readline already echoed\n * them). A multiset (text → count) handles rapid identical sends and\n * never suppresses another window's matching message beyond our own\n * outstanding count.\n */\n const ownEchoes = new Map<string, number>()\n\n const renderEvent = (event: StreamEvent): void => {\n switch (event.kind) {\n case 'user-prompt': {\n const outstanding = ownEchoes.get(event.text) ?? 0\n if (outstanding > 0) {\n if (outstanding === 1)\n ownEchoes.delete(event.text)\n else\n ownEchoes.set(event.text, outstanding - 1)\n break\n }\n write(`\\n${BOLD}> ${event.text}${RESET}\\n`)\n break\n }\n case 'markdown':\n if (lastEventKind !== 'markdown' && lastEventKind !== null)\n write('\\n')\n write(event.text)\n break\n case 'thinking':\n // Streamed reasoning stays out of the transcript — show a pulse once.\n if (lastEventKind !== 'thinking')\n write(`${DIM}(thinking…)${RESET}\\n`)\n break\n case 'tool':\n write(`\\n${CYAN}▸ ${event.text}${RESET}\\n`)\n break\n case 'tool-result':\n write(`${DIM}${clipLines(event.text, 3)}${RESET}\\n`)\n break\n case 'task-notification':\n case 'info':\n write(`${DIM}${event.text}${RESET}\\n`)\n break\n case 'error':\n write(`${RED}${event.text}${RESET}\\n`)\n break\n default:\n break\n }\n lastEventKind = event.kind\n }\n\n const handleApproval = async (request: WireApprovalRequest): Promise<void> => {\n write(`\\n${YELLOW}approval needed${RESET} — ${BOLD}${request.tool}${RESET}\\n`)\n write(`${DIM}${clipLines(JSON.stringify(request.input, null, 2), 12)}${RESET}\\n`)\n const answer = await ask(`${YELLOW}[y]es once / [s]ession / [a]lways (safelist) / [n]o: ${RESET}`)\n const decision\n = answer.trim().toLowerCase() === 'y'\n ? 'accept-once'\n : answer.trim().toLowerCase() === 's'\n ? 'accept-session'\n : answer.trim().toLowerCase() === 'a'\n ? 'accept-safelist'\n : 'deny'\n await client.request('approvals.resolve', { requestId: request.id, decision })\n }\n\n const handleInteraction = async (request: InteractionRequest): Promise<void> => {\n if (request.kind === 'plan') {\n write(`\\n${YELLOW}plan:${RESET} ${BOLD}${request.payload.title}${RESET}\\n${request.payload.plan}\\n`)\n const answer = await ask(`${YELLOW}[a]pprove / [r]eject / re[v]ise (with comment): ${RESET}`)\n const c = answer.trim().toLowerCase()\n if (c === 'v') {\n const comment = await ask('comment: ')\n await client.request('interactions.respond', {\n requestId: request.id,\n response: { kind: 'plan', decision: 'revise', comment },\n })\n return\n }\n await client.request('interactions.respond', {\n requestId: request.id,\n response: { kind: 'plan', decision: c === 'a' ? 'approve' : 'reject' },\n })\n return\n }\n\n if (request.payload.intro)\n write(`\\n${request.payload.intro}\\n`)\n const answers: Record<string, AnswerValue> = {}\n for (const question of request.payload.questions) {\n if (question.type === 'confirm') {\n const answer = await ask(`${YELLOW}${question.prompt}${RESET} [y/n]: `)\n answers[question.id] = answer.trim().toLowerCase().startsWith('y')\n continue\n }\n if (question.type === 'select') {\n write(`${YELLOW}${question.prompt}${RESET}\\n`)\n question.choices.forEach((choice, i) => write(` ${i + 1}. ${choice.label}\\n`))\n const answer = await ask(question.multi ? 'pick (comma-separated numbers): ' : 'pick a number: ')\n const picks = answer\n .split(',')\n .map(part => question.choices[Number.parseInt(part.trim(), 10) - 1]?.id)\n .filter((id): id is string => typeof id === 'string')\n answers[question.id] = question.multi ? picks : (picks[0] ?? '')\n continue\n }\n answers[question.id] = await ask(`${YELLOW}${question.prompt}${RESET}: `)\n }\n await client.request('interactions.respond', {\n requestId: request.id,\n response: { kind: 'question', answers },\n })\n }\n\n const onSignal = (signal: HostSignal): void => {\n // Converge on resolution: mark the id answered so a queued-but-not-yet-\n // prompted request is skipped instead of blocking on stdin, and record\n // it so late backfill can't re-raise it.\n if (signal.kind === 'approval:resolved' || signal.kind === 'interaction:resolved') {\n resolvedHitlIds.add(signal.requestId)\n return\n }\n\n if (signal.kind === 'approval:request') {\n // Only surface approvals for the attached session — other windows'\n // sessions raise their own prompts in their own windows.\n if (signal.request.sessionId === session.id)\n enqueueHitl(signal.request.id, () => handleApproval(signal.request))\n return\n }\n\n if ('sessionId' in signal && signal.sessionId !== session.id)\n return\n\n switch (signal.kind) {\n case 'run:start':\n running = true\n lastEventKind = null\n break\n case 'run:end':\n running = false\n write(signal.ok ? '\\n' : `\\n${RED}run failed: ${signal.error ?? 'unknown error'}${RESET}\\n`)\n if (!pendingAsk)\n rl.prompt()\n break\n case 'event':\n renderEvent(signal.event)\n break\n case 'compacting:start':\n write(`${DIM}compacting…${RESET}\\n`)\n break\n case 'interaction:request':\n enqueueHitl(signal.request.id, () => handleInteraction(signal.request))\n break\n default:\n break\n }\n }\n\n // Surface HITL state left pending before this window attached. Done\n // BEFORE draining buffered signals so a live `*:request` that arrived\n // during backfill de-dups against the same id (via `queuedHitlIds`).\n for (const request of await client.request('approvals.listPending', {})) {\n if (request.sessionId === session.id)\n enqueueHitl(request.id, () => handleApproval(request))\n }\n for (const pending of await client.request('interactions.list', { sessionId: session.id })) {\n if (pending.live)\n enqueueHitl(pending.request.id, () => handleInteraction(pending.request))\n }\n\n // Drain signals buffered during replay/backfill, then hand live signals\n // straight to `onSignal`.\n signalSink = onSignal\n for (const buffered of bufferedSignals.splice(0))\n onSignal(buffered)\n\n /* ----------------------------------------------------------------------\n * Input loop\n * -------------------------------------------------------------------- */\n\n const detach = (): void => {\n unsubscribe()\n rl.close()\n client.close()\n write(`\\n${DIM}detached — the session keeps running in the daemon${RESET}\\n`)\n }\n\n rl.on('SIGINT', () => {\n if (running) {\n // Fire-and-forget: a dead daemon rejects the request, and an\n // uncaught rejection would crash the attach client.\n client.request('chat.interrupt', { sessionId: session.id }).catch(() => undefined)\n write(`\\n${DIM}interrupt sent${RESET}\\n`)\n return\n }\n detach()\n })\n\n await new Promise<void>((resolve, reject) => {\n failRun = (error) => {\n unsubscribe()\n rl.close()\n client.close()\n reject(error)\n }\n rl.on('close', () => resolve())\n\n const handleLine = (line: string): void => {\n const text = line.trim()\n if (text.length === 0) {\n rl.prompt()\n return\n }\n if (text === '/exit' || text === '/quit') {\n detach()\n resolve()\n return\n }\n if (text === '/interrupt') {\n client.request('chat.interrupt', { sessionId: session.id }).catch(() => undefined)\n return\n }\n\n ownEchoes.set(text, (ownEchoes.get(text) ?? 0) + 1)\n void client.request('chat.pushQueued', { sessionId: session.id, message: text }).then(({ delivered }) => {\n if (delivered === 'steer')\n write(`${DIM}(steered into the live run)${RESET}\\n`)\n }).catch((err) => {\n // Send failed: the host never broadcast the prompt, so release the\n // pending echo suppression to avoid eating a later identical one.\n const outstanding = ownEchoes.get(text) ?? 0\n if (outstanding <= 1)\n ownEchoes.delete(text)\n else\n ownEchoes.set(text, outstanding - 1)\n write(`${RED}${err instanceof Error ? err.message : String(err)}${RESET}\\n`)\n rl.prompt()\n })\n }\n\n submitLine = handleLine\n while (earlyLines.length > 0)\n handleLine(earlyLines.shift()!)\n\n if (!running)\n rl.prompt()\n })\n}\n\n/* --------------------------------------------------------------------------\n * Session picking\n * ------------------------------------------------------------------------ */\n\nasync function pickSession(\n client: IpcClient,\n ctx: {\n ask: (prompt: string) => Promise<string>\n write: (text: string) => void\n cwd: string\n sessionId?: string\n createNew?: boolean\n },\n): Promise<SessionRow | null> {\n const projectRoot = findGitRoot(ctx.cwd) ?? ctx.cwd\n\n if (ctx.createNew)\n return client.request('sessions.create', { cwd: ctx.cwd })\n\n const rows = await client.request('sessions.list', { projectRoot, archive: 'active' })\n\n if (ctx.sessionId) {\n const match = rows.find(r => r.id === ctx.sessionId || r.id.startsWith(ctx.sessionId!))\n if (match)\n return match\n // Not in this project — try the full list before giving up.\n const all = await client.request('sessions.list', {})\n const global = all.find(r => r.id === ctx.sessionId || r.id.startsWith(ctx.sessionId!))\n if (global)\n return global\n ctx.write(`${RED}no session matching \"${ctx.sessionId}\"${RESET}\\n`)\n return null\n }\n\n if (rows.length === 0)\n return client.request('sessions.create', { cwd: ctx.cwd })\n\n ctx.write(`${BOLD}sessions in ${projectRoot}:${RESET}\\n`)\n rows.slice(0, 15).forEach((row, i) => {\n const status = row.status === 'running' ? ` ${YELLOW}● running${RESET}` : ''\n ctx.write(` ${i + 1}. ${row.title} ${DIM}(${row.id.slice(0, 8)})${RESET}${status}\\n`)\n })\n\n const answer = await ctx.ask('pick a number, or [n]ew: ')\n if (answer.trim().toLowerCase() === 'n')\n return client.request('sessions.create', { cwd: ctx.cwd })\n\n const picked = rows[Number.parseInt(answer.trim(), 10) - 1]\n if (!picked) {\n ctx.write(`${RED}invalid pick${RESET}\\n`)\n return null\n }\n return picked\n}\n\n/* --------------------------------------------------------------------------\n * Transcript replay\n * ------------------------------------------------------------------------ */\n\nconst REPLAY_EVENT_CAP = 60\n\nasync function replayTranscript(\n client: IpcClient,\n sessionId: string,\n write: (text: string) => void,\n): Promise<void> {\n const events = await client.request('sessions.loadEvents', { sessionId })\n if (events.length === 0)\n return\n\n const tail = events.slice(-REPLAY_EVENT_CAP)\n if (events.length > tail.length)\n write(`${DIM}… ${events.length - tail.length} earlier events omitted${RESET}\\n`)\n\n for (const event of tail) {\n if (event.kind === 'user-prompt')\n write(`\\n${BOLD}> ${event.text}${RESET}\\n`)\n else if (event.kind === 'markdown')\n write(`${event.text}\\n`)\n else if (event.kind === 'tool')\n write(`${CYAN}▸ ${event.text}${RESET}\\n`)\n }\n write('\\n')\n}\n\nfunction clipLines(text: string, max: number): string {\n const lines = text.split('\\n')\n if (lines.length <= max)\n return text\n return `${lines.slice(0, max).join('\\n')}\\n${DIM}… ${lines.length - max} more lines${RESET}`\n}\n"],"mappings":"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"}
package/dist/ipc.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { Ga as ExtensionBus, Jd as ProviderKey, Ju as StreamEvent, Wp as ContextBreakdown, Yg as SessionTurn, kf as InteractionResponse, qv as SessionArtifact, r_ as ThinkingLevel, wg as JsonValue } from "./index-C4mY0oKE.js";
2
- import { An as ChatHostEnvironment, Bn as QueuedItem, Cn as PendingAnnotation, Dn as ApprovalRequestInput, En as ApprovalHub, Fn as HostSettings, Hn as SendOptions, In as HostSignal, Kn as TodosSnapshot, Ln as PendingInteraction, Mn as ApprovalOriginator, Nn as CommandArgSuggestionSummary, On as createApprovalHub, Pn as CommandSummary, Rn as PromptAttachment, Sn as EditOutcomeLedger, Tn as runCompaction, Un as SerializedPreview, Vn as RunningWorkItem, Wn as SessionPresence, _n as CreateWorkerParams, bn as InteractionHub, dn as readFileSafe, fn as resolveCwdPath, gn as createChatHost, hn as ContextSegmentsReport, jn as PendingApprovalsStore, kn as createMemoryApprovalsStore, mn as ChatHost, pn as serializePreview, qn as WireApprovalRequest, un as extractEditPayloadForGate, v as SystemMdFile, vn as WorkerRow, wn as CompactionEntry, xn as createInteractionHub, yn as WorkerSummary } from "./index-DQn4tuAA.js";
3
- import { O as ResolvedConfig } from "./transcript-anchors-CY50AXdF.js";
4
- import { p as TodoItem } from "./turn-operations-CAmNETlu.js";
5
- import { T as SessionHistoryMatch, V as ApprovalDecision, w as SearchTurnResult } from "./tool-formatters-GZHxP824.js";
1
+ import { Ga as ExtensionBus, Jd as ProviderKey, Ju as StreamEvent, Wp as ContextBreakdown, Yg as SessionTurn, kf as InteractionResponse, qv as SessionArtifact, r_ as ThinkingLevel, wg as JsonValue } from "./index-C0y2tSgk.js";
2
+ import { An as ChatHostEnvironment, Bn as QueuedItem, Cn as PendingAnnotation, Dn as ApprovalRequestInput, En as ApprovalHub, Fn as HostSettings, Hn as SendOptions, In as HostSignal, Kn as TodosSnapshot, Ln as PendingInteraction, Mn as ApprovalOriginator, Nn as CommandArgSuggestionSummary, On as createApprovalHub, Pn as CommandSummary, Rn as PromptAttachment, Sn as EditOutcomeLedger, Tn as runCompaction, Un as SerializedPreview, Vn as RunningWorkItem, Wn as SessionPresence, _n as CreateWorkerParams, bn as InteractionHub, dn as readFileSafe, fn as resolveCwdPath, gn as createChatHost, hn as ContextSegmentsReport, jn as PendingApprovalsStore, kn as createMemoryApprovalsStore, mn as ChatHost, pn as serializePreview, qn as WireApprovalRequest, un as extractEditPayloadForGate, v as SystemMdFile, vn as WorkerRow, wn as CompactionEntry, xn as createInteractionHub, yn as WorkerSummary } from "./index-Co4Fddtj.js";
3
+ import { O as ResolvedConfig } from "./transcript-anchors-BHyrChoO.js";
4
+ import { p as TodoItem } from "./turn-operations-ZP5-2OWi.js";
5
+ import { T as SessionHistoryMatch, V as ApprovalDecision, w as SearchTurnResult } from "./tool-formatters-bbgUM3zI.js";
6
6
 
7
7
  //#region src/ipc/attach.d.ts
8
8
  interface AttachOptions {
package/dist/ipc.js CHANGED
@@ -1,4 +1,4 @@
1
- import { $t as runCompaction, Bt as readFileSafe, Ht as serializePreview, It as createChatHost, Qt as EditOutcomeLedger, Rt as createInteractionHub, Vt as resolveCwdPath, n as createMemoryApprovalsStore, t as createApprovalHub, zt as extractEditPayloadForGate } from "./approvals-Deg8JhMI.js";
2
- import { a as connectIpcClient, c as IpcErrorCode, d as defaultSocketPath, f as ensureDaemonRunning, i as IpcVersionMismatchError, l as IPC_BUILD_ID, m as selfDaemonSpawnCommand, n as createDaemonEnvironment, o as connectOrSpawnDaemon, p as probeSocket, r as createFileApprovalsStore, s as IPC_PROTOCOL_VERSION, t as startIpcDaemon, u as daemonSocketName } from "./server-qsKLtfXY.js";
3
- import { t as runAttach } from "./ipc-BVqRR-42.js";
1
+ import { $t as EditOutcomeLedger, Bt as extractEditPayloadForGate, Ht as resolveCwdPath, It as createChatHost, Ut as serializePreview, Vt as readFileSafe, en as runCompaction, n as createMemoryApprovalsStore, t as createApprovalHub, zt as createInteractionHub } from "./approvals-xpcwa2UM.js";
2
+ import { a as connectIpcClient, c as IpcErrorCode, d as defaultSocketPath, f as ensureDaemonRunning, i as IpcVersionMismatchError, l as IPC_BUILD_ID, m as selfDaemonSpawnCommand, n as createDaemonEnvironment, o as connectOrSpawnDaemon, p as probeSocket, r as createFileApprovalsStore, s as IPC_PROTOCOL_VERSION, t as startIpcDaemon, u as daemonSocketName } from "./server-CGe2GGjg.js";
3
+ import { t as runAttach } from "./ipc-BKe8SqXv.js";
4
4
  export { EditOutcomeLedger, IPC_BUILD_ID, IPC_PROTOCOL_VERSION, IpcErrorCode, IpcVersionMismatchError, connectIpcClient, connectOrSpawnDaemon, createApprovalHub, createChatHost, createDaemonEnvironment, createFileApprovalsStore, createInteractionHub, createMemoryApprovalsStore, daemonSocketName, defaultSocketPath, ensureDaemonRunning, extractEditPayloadForGate, probeSocket, readFileSafe, resolveCwdPath, runAttach, runCompaction, selfDaemonSpawnCommand, serializePreview, startIpcDaemon };
package/dist/mcp.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { Dg as McpToolSchema, Tg as McpServerConfig, _y as originPinnedFetch, ay as createStderrLineEmitter, cy as expandMcpConfigEnv, dy as normalizeMcpServers, ey as ConnectMcpServersOptions, fy as resolveTransportConfig, gy as isAllowedRemoteUrl, hy as PinnedFetch, iy as connectMcpServers, ly as nonOwningMcpView, my as wrapDiscoveredMcpTools, ny as McpExecutionBinding, oy as deriveMcpToolMeta, py as resultToString, ry as attachStderrWarnPump, sy as expandEnvPlaceholders, ty as McpConnection, uy as normalizeMcpBlocks } from "./index-C4mY0oKE.js";
1
+ import { Dg as McpToolSchema, Tg as McpServerConfig, _y as originPinnedFetch, ay as createStderrLineEmitter, cy as expandMcpConfigEnv, dy as normalizeMcpServers, ey as ConnectMcpServersOptions, fy as resolveTransportConfig, gy as isAllowedRemoteUrl, hy as PinnedFetch, iy as connectMcpServers, ly as nonOwningMcpView, my as wrapDiscoveredMcpTools, ny as McpExecutionBinding, oy as deriveMcpToolMeta, py as resultToString, ry as attachStderrWarnPump, sy as expandEnvPlaceholders, ty as McpConnection, uy as normalizeMcpBlocks } from "./index-C0y2tSgk.js";
2
2
  export { ConnectMcpServersOptions, McpConnection, McpExecutionBinding, type McpServerConfig, type McpToolSchema, type PinnedFetch, attachStderrWarnPump, connectMcpServers, createStderrLineEmitter, deriveMcpToolMeta, expandEnvPlaceholders, expandMcpConfigEnv, isAllowedRemoteUrl, nonOwningMcpView, normalizeMcpBlocks, normalizeMcpServers, originPinnedFetch, resolveTransportConfig, resultToString, wrapDiscoveredMcpTools };
@@ -428,15 +428,25 @@ function credentialsFromParams(params, extraKeys = []) {
428
428
  };
429
429
  }
430
430
  async function resolveOAuthApiKey(options, callbacks) {
431
- if (typeof options.params?.apiKey === "string") return options.params.apiKey;
431
+ if (typeof options.params?.apiKey === "string") {
432
+ if (isOAuthAccessToken(options.params.apiKey)) callbacks?.onOAuthStage?.("credentials");
433
+ return options.params.apiKey;
434
+ }
432
435
  const paramsCredentials = credentialsFromParams(options.params, options.extraCredentialKeys);
433
- if (!paramsCredentials && typeof options.params?.access === "string") return options.params.access;
436
+ if (!paramsCredentials && typeof options.params?.access === "string") {
437
+ callbacks?.onOAuthStage?.("credentials");
438
+ return options.params.access;
439
+ }
434
440
  const path = resolve(credentialsFilePath());
435
441
  const usingDefaultStore = !options.readCredentials && !options.writeCredentials;
436
442
  const read = options.readCredentials ?? (() => readOAuthCredentialsAt(path));
437
443
  const write = options.writeCredentials ?? ((patch) => withCredentialsLock(path, () => writeCredentialPatch(path, patch)));
438
444
  const envValue = options.envKey ? process.env[options.envKey] : void 0;
439
- if (!paramsCredentials && envValue && (!isOAuthAccessToken(envValue) || !read()[options.providerId])) return envValue;
445
+ if (!paramsCredentials && envValue && (!isOAuthAccessToken(envValue) || !read()[options.providerId])) {
446
+ if (isOAuthAccessToken(envValue)) callbacks?.onOAuthStage?.("credentials");
447
+ return envValue;
448
+ }
449
+ callbacks?.onOAuthStage?.("credentials");
440
450
  const source = paramsCredentials ? "params" : "file";
441
451
  const initial = paramsCredentials ?? read()[options.providerId];
442
452
  const store = usingDefaultStore ? path : [identity(options.readCredentials ?? readOAuthCredentials), identity(options.writeCredentials ?? writeOAuthCredentials)];
@@ -450,6 +460,7 @@ async function resolveOAuthApiKey(options, callbacks) {
450
460
  identity(options.getOAuthApiKey ?? getOAuthApiKey)
451
461
  ]);
452
462
  let task = refreshLocks.get(key);
463
+ if (task) callbacks?.onOAuthStage?.("shared-refresh");
453
464
  if (!task) {
454
465
  task = run();
455
466
  refreshLocks.set(key, task);
@@ -470,6 +481,7 @@ async function resolveOAuthApiKey(options, callbacks) {
470
481
  let result;
471
482
  try {
472
483
  const stillRejected = options.rejectedAccess === void 0 || current.access === options.rejectedAccess;
484
+ if (current.expires <= Date.now() || options.forceRefresh && stillRejected) callbacks?.onOAuthStage?.("refresh");
473
485
  result = await (options.getOAuthApiKey ?? getOAuthApiKey)(options.providerId, { [options.providerId]: current }, { force: options.forceRefresh === true && stillRejected });
474
486
  } catch (err) {
475
487
  throw new Error(options.refreshError(errorMessage(err)), { cause: err });
@@ -509,4 +521,4 @@ async function resolveOAuthApiKey(options, callbacks) {
509
521
  //#endregion
510
522
  export { readOAuthCredentials as a, withCredentialsLock as c, getOAuthApiKey as d, getOAuthProvider as f, registerOAuthProvider as h, isOAuthAccessToken as i, writeOAuthCredentials as l, refreshOpenAICodexToken as m, credentialsFromParams as n, registerCustomOAuthProvider as o, refreshAnthropicToken as p, extractRuntimeCredentials as r, resolveOAuthApiKey as s, credentialsFilePath as t, ensureBundledOAuthFlows as u };
511
523
 
512
- //# sourceMappingURL=oauth-CmtlmCzi.js.map
524
+ //# sourceMappingURL=oauth-B6F43fti.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"oauth-CmtlmCzi.js","names":[],"sources":["../src/providers/oauth-registry.ts","../src/providers/oauth.ts"],"sourcesContent":["/**\n * Local OAuth provider registry.\n *\n * pi-ai 0.80.8 removed its global OAuth registry (`registerOAuthProvider`,\n * `getOAuthApiKey`, `OAuthProviderInterface`) and the public low-level\n * refresh helpers in favor of canonical `Provider.auth.oauth` objects\n * resolved through a `Models` collection + `CredentialStore`. zidane's\n * credential architecture (shared `.credentials.json`, cross-process refresh\n * locks in `./oauth.ts`, themed login flows in `src/chat/oauth-page/`)\n * predates that and owns storage itself, so this module reproduces the\n * removed registry surface locally with the same semantics.\n *\n * Nothing protocol-level is duplicated here: token refresh for the built-in\n * providers delegates to pi-ai's canonical `auth.oauth.refresh` flows via the\n * public `providers/*` subpaths (lazily imported, so processes that never\n * authenticate don't load provider code).\n */\n\nimport type { OAuthAuth } from '@earendil-works/pi-ai'\nimport type { OAuthCredentials, OAuthLoginCallbacks } from '@earendil-works/pi-ai/oauth'\nimport { errorMessage } from '../errors'\n\n/**\n * Provider-owned OAuth flow. Mirrors the interface pi-ai exported from\n * `@earendil-works/pi-ai/oauth` up to 0.80.6 — kept as a local type so\n * zidane's descriptor seam (`ProviderDescriptor.oauthProvider`) and provider\n * extensions (e.g. `extensions/bolt`) stay source-compatible across the\n * upstream removal.\n */\nexport interface OAuthProviderInterface {\n readonly id: string\n readonly name: string\n\n /** Run the login flow, return credentials to persist. */\n login: (callbacks: OAuthLoginCallbacks) => Promise<OAuthCredentials>\n\n /** Whether login uses a local callback server and supports manual code input. */\n usesCallbackServer?: boolean\n\n /** Refresh expired credentials, return updated credentials to persist. */\n refreshToken: (credentials: OAuthCredentials) => Promise<OAuthCredentials>\n\n /** Convert credentials to an API key string for the provider. */\n getApiKey: (credentials: OAuthCredentials) => string\n}\n\n/**\n * Run a canonical pi-ai `OAuthAuth.refresh` from a bare refresh token.\n * pi-ai's built-in refresh implementations only read `credential.refresh`,\n * so the placeholder access/expires fields are never observed.\n *\n * The canonical result is an `OAuthCredential` tagged `type: 'oauth'`. Strip\n * the tag before returning: refresh results get merged into stored\n * `.credentials.json` entries and `onOAuthRefresh` hook payloads (see\n * `resolveCredentialSource` in `./oauth.ts`), whose documented shape carries\n * zidane's own `kind` tag — letting pi-ai's `type` leak through would\n * silently drift the on-disk shape.\n */\nasync function refreshViaCanonicalFlow(\n providerId: string,\n oauth: OAuthAuth | undefined,\n refreshToken: string,\n): Promise<OAuthCredentials> {\n if (!oauth)\n throw new Error(`pi-ai provider ${providerId} exposes no OAuth flow`)\n // pi-ai 0.84 made `signal` a required second argument. These refreshers are\n // called from token-expiry paths that carry no cancellation of their own, so\n // a never-aborting signal keeps the pre-0.84 behavior exactly — same idiom as\n // `completion-core.ts`.\n const { type: _type, ...credentials }\n = await oauth.refresh(\n { type: 'oauth', access: '', refresh: refreshToken, expires: 0 },\n new AbortController().signal,\n )\n return credentials\n}\n\n/**\n * pi-ai ≥ 0.85 loads each OAuth flow module through a VARIABLE specifier\n * (`auth/oauth/load.js`: `import(rewrite(\"./anthropic.ts\"))`) precisely so\n * bundlers cannot follow it. Fine under plain Node/Bun where the file sits\n * next to `load.js`; fatal inside anything bundled — the compiled `zidane`\n * binary, the packaged Electron main — where the refresh path died with\n * `Cannot find module './anthropic.js'` and the user was told to re-run auth\n * for a token that was perfectly renewable.\n *\n * pi-ai ships the escape hatch for exactly this: `bun-oauth` statically\n * imports every flow and registers them with the loader. We call it lazily,\n * once, right before the first refresh — a static specifier the bundler DOES\n * follow — so every host (TUI binary, GUI, daemon, SDK) refreshes the same\n * way. Idempotent; failure to register falls through to the loader's own\n * behavior (works unbundled, so nothing gets worse).\n */\nlet bundledFlowsReady: Promise<void> | null = null\nexport function ensureBundledOAuthFlows(): Promise<void> {\n bundledFlowsReady ??= import('@earendil-works/pi-ai/bun-oauth')\n .then(({ registerBunOAuthFlows }) => {\n registerBunOAuthFlows()\n })\n .catch(() => {\n bundledFlowsReady = null\n })\n return bundledFlowsReady\n}\n\n/**\n * Refresh an Anthropic (Claude Pro/Max) OAuth token. Drop-in replacement for\n * the `refreshAnthropicToken` pi-ai exported until 0.80.6; delegates to the\n * canonical flow on `anthropicProvider().auth.oauth`.\n */\nexport async function refreshAnthropicToken(refreshToken: string): Promise<OAuthCredentials> {\n await ensureBundledOAuthFlows()\n const { anthropicProvider } = await import('@earendil-works/pi-ai/providers/anthropic')\n return refreshViaCanonicalFlow('anthropic', anthropicProvider().auth.oauth, refreshToken)\n}\n\n/**\n * Refresh an OpenAI Codex (ChatGPT) OAuth token. Drop-in replacement for the\n * removed `refreshOpenAICodexToken`; the canonical flow still derives\n * `accountId` from the refreshed access token's JWT claims, so the returned\n * credentials keep the extra field older callers persisted.\n */\nexport async function refreshOpenAICodexToken(refreshToken: string): Promise<OAuthCredentials> {\n await ensureBundledOAuthFlows()\n const { openaiCodexProvider } = await import('@earendil-works/pi-ai/providers/openai-codex')\n return refreshViaCanonicalFlow('openai-codex', openaiCodexProvider().auth.oauth, refreshToken)\n}\n\n/**\n * Built-in registry seeds. Every id whose stored credential can expire has to\n * be seeded here, because zidane's lazy token refresh (`getOAuthApiKey` inside\n * `resolveOAuthApiKey`) resolves through the registry — an unseeded id is not a\n * degraded path but a dead end, with no way to renew the session. That is wider\n * than the Anthropic / OpenAI Codex pair pi-ai's removed registry pre-seeded:\n * flows zidane implements itself (xAI) need seeding just as much.\n *\n * Login is intentionally unsupported here: interactive login always goes\n * through `ProviderDescriptor.oauthProvider` (the themed flows in\n * `src/chat/oauth-page/`), never through the registry.\n */\nfunction builtinProvider(\n id: string,\n name: string,\n refreshToken: (credentials: OAuthCredentials) => Promise<OAuthCredentials>,\n): OAuthProviderInterface {\n return {\n id,\n name,\n login: async () => {\n throw new Error(`OAuth login for ${id} must run through a provider descriptor flow, not the registry`)\n },\n refreshToken,\n getApiKey: credentials => credentials.access,\n }\n}\n\n/**\n * Seed id for the xAI Grok OAuth flow. Duplicated from\n * `XAI_OAUTH_PROVIDER_ID` in `../chat/oauth-page/xai` rather than imported:\n * that module pulls in `node:http` and the PKCE helpers at module scope, and\n * this registry is loaded by every consumer that resolves a credential. The\n * ids are pinned together by a test, so the duplication cannot drift.\n */\nconst XAI_SEED_ID = 'xai-oauth'\n\n/**\n * Refresh an xAI Grok token. Imported lazily so seeding the registry does not\n * drag the login flow's `node:http` callback server into every consumer —\n * and so the static import edge back to this module stays type-only, leaving\n * no runtime cycle.\n *\n * Unlike the Anthropic / Codex seeds, this receives the FULL credential rather\n * than a bare refresh token: xAI stores its resolved `token_endpoint` (and the\n * cached discovery doc) alongside the tokens, and dropping them would force a\n * fresh OIDC discovery round-trip on every refresh — and discard the validated\n * endpoint that `refreshXaiToken` re-checks before posting the refresh token\n * to it.\n */\nasync function refreshXaiTokenSeed(credentials: OAuthCredentials): Promise<OAuthCredentials> {\n const { refreshXaiToken } = await import('../chat/oauth-page/xai')\n return refreshXaiToken(credentials)\n}\n\n/**\n * Seed id for the Cursor OAuth flow. Duplicated from\n * `CURSOR_OAUTH_PROVIDER_ID` for the same reason as {@link XAI_SEED_ID}, and\n * pinned to it by a test.\n */\nconst CURSOR_SEED_ID = 'cursor'\n\n/**\n * Refresh a Cursor token. Lazy for the same reason as the xAI seed: the login\n * module owns a callback server, and seeding must not drag it into every\n * consumer that merely resolves a credential.\n */\nasync function refreshCursorTokenSeed(credentials: OAuthCredentials): Promise<OAuthCredentials> {\n const { refreshCursorToken } = await import('../chat/oauth-page/cursor')\n return refreshCursorToken(credentials)\n}\n\n/**\n * The registry map is anchored on a process-wide `globalThis` slot rather\n * than module state. The pi-ai registry this replaces lived in pi-ai's own\n * module, so every copy of zidane code in a process shared one map — and two\n * copies is a real configuration, not a hypothetical: a host running from\n * `src` while a disk-loaded extension resolves `'zidane'` to `dist` through\n * ordinary node_modules resolution (see `import()` in\n * `src/extensions/discovery.ts`). The extension registers its flow at setup;\n * `resolveOAuthApiKey` looks it up only once a stored token expires — with\n * per-module maps that fails as a deferred \"Unknown OAuth provider\", which is\n * nasty to debug. `Symbol.for` restores the process-wide sharing. Whichever\n * copy touches the slot first seeds the built-ins, so a `dist` consumer can end\n * up refreshing through the `src` copy's closure (and vice versa). That is\n * harmless because the seeds are STATELESS refresh delegations — they close over\n * no module state and no configuration, so the two copies compute the same\n * result from the same stored credential. It is that property, not where the\n * refresh happens to be implemented, that makes first-copy-wins safe: it holds\n * equally for the seeds that delegate to pi-ai's canonical flows and for the\n * xAI one implemented in `../chat/oauth-page/xai`.\n */\n/**\n * Exported so tests that register a throwaway provider can remove it from the\n * same slot rather than re-deriving the key with a copied string: the registry\n * exposes no unregister, and a `Symbol.for` looked up from a duplicated literal\n * would silently stop matching if this key were ever renamed, leaving the test\n * provider resident in shared state. Deliberately kept out of the package's\n * public surface — `./index.ts` re-exports this module by explicit name list.\n */\nexport const REGISTRY_SLOT = Symbol.for('zidane.oauth-provider-registry')\n\nfunction oauthProviderRegistry(): Map<string, OAuthProviderInterface> {\n const slots = globalThis as { [key: symbol]: Map<string, OAuthProviderInterface> | undefined }\n slots[REGISTRY_SLOT] ??= new Map(\n [\n builtinProvider('anthropic', 'Anthropic (Claude Pro/Max)', c => refreshAnthropicToken(c.refresh)),\n builtinProvider('openai-codex', 'OpenAI (ChatGPT Plus/Pro)', c => refreshOpenAICodexToken(c.refresh)),\n // xAI is NOT built into pi-ai, so unlike the two above there is no\n // canonical flow to delegate to — `../chat/oauth-page/xai` implements it.\n // It is seeded here anyway because seeding is what makes refresh work for\n // consumers that never import `chat/providers.ts` or `auth.ts`, whose\n // module-scope `registerXaiOAuthProvider()` call was previously the ONLY\n // thing that registered it. A library consumer resolving a stored\n // credential through `getOAuthApiKey` got `Unknown OAuth provider:\n // xai-oauth` and had no way to renew a session — a dead end rather than a\n // degraded path. The interactive login half still comes from\n // `registerXaiOAuthProvider(renderPage)`, which replaces this seed.\n builtinProvider(XAI_SEED_ID, 'xAI Grok (SuperGrok / X Premium+)', refreshXaiTokenSeed),\n // Cursor has the identical shape and had the identical defect: its flow\n // lives in `../chat/oauth-page/cursor` and was registered only as a\n // module-scope side effect of importing `chat/providers.ts` / `auth.ts`.\n builtinProvider(CURSOR_SEED_ID, 'Cursor', refreshCursorTokenSeed),\n ].map(provider => [provider.id, provider]),\n )\n return slots[REGISTRY_SLOT]\n}\n\n/** Register (or replace) an OAuth provider by id. */\nexport function registerOAuthProvider(provider: OAuthProviderInterface): void {\n oauthProviderRegistry().set(provider.id, provider)\n}\n\n/** Get a registered OAuth provider by id. */\nexport function getOAuthProvider(id: string): OAuthProviderInterface | undefined {\n return oauthProviderRegistry().get(id)\n}\n\n/**\n * Get an API key for a provider from OAuth credentials, refreshing expired\n * tokens through the registered flow. Same contract as the pi-ai original\n * (`null` when no credentials are stored for the provider, throws on unknown\n * provider or refresh failure), with two deliberate additions:\n *\n * - refresh failures keep the underlying reason — the original discarded it,\n * leaving invalid_grant (needs re-login) indistinguishable from a transient\n * network error.\n * - `options.force` rotates regardless of the local expiry, for 401 recovery\n * when the server revoked the grant before our stored timestamp said so.\n * Expressed as a flag rather than by handing this function a credential\n * with a doctored `expires`: callers persist whatever comes back, so a\n * fabricated expiry that a provider echoes through would land in the\n * credential store. See `resolveCredentialSource` in `./oauth.ts`.\n */\nexport async function getOAuthApiKey(\n providerId: string,\n credentials: Record<string, OAuthCredentials>,\n options?: { force?: boolean },\n): Promise<{ newCredentials: OAuthCredentials, apiKey: string } | null> {\n const provider = oauthProviderRegistry().get(providerId)\n if (!provider)\n throw new Error(`Unknown OAuth provider: ${providerId}`)\n\n let creds = credentials[providerId]\n if (!creds)\n return null\n\n if (options?.force || Date.now() >= creds.expires) {\n try {\n creds = await provider.refreshToken(creds)\n }\n catch (err) {\n throw new Error(`Failed to refresh OAuth token for ${providerId}: ${errorMessage(err)}`, { cause: err })\n }\n }\n\n return { newCredentials: creds, apiKey: provider.getApiKey(creds) }\n}\n","import type { OAuthCredentials } from '@earendil-works/pi-ai/oauth'\nimport type { StreamCallbacks } from '.'\nimport type { OAuthRefreshHookContext } from '../types'\nimport { Buffer } from 'node:buffer'\nimport { randomUUID } from 'node:crypto'\nimport { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { dirname, resolve } from 'node:path'\nimport { isDeepStrictEqual } from 'node:util'\nimport { writeFileAtomic } from '../atomic-write'\nimport { errorMessage } from '../errors'\nimport { getOAuthApiKey, registerOAuthProvider } from './oauth-registry'\n\ntype GetOAuthApiKey = typeof getOAuthApiKey\n\n/**\n * Resolve the creds-file path at call time rather than module-load time, so\n * env changes between import and first OAuth use (test suites, multi-project\n * CLIs) are honored.\n *\n * Order:\n * 1. `ZIDANE_CREDENTIALS_PATH` — explicit override. The TUI sets this at\n * launch to point at `<storageDir>/credentials.json` so the harness\n * providers find the same file the TUI manages.\n * 2. `~/.credentials.json` — home-anchored default.\n *\n * The historic `cwd/.credentials.json` fallback was deliberately dropped:\n * launching zidane inside an untrusted checkout would let that repo plant a\n * `.credentials.json` and become the credential source (and receive rotated\n * refresh tokens on the next refresh persist). Same trust model as the TUI's\n * legacy-file migration in `chat/credentials.ts`, which only honors the\n * home-anchored location. The only \"trusted\" cwd under that model is `$HOME`\n * itself — where the cwd-relative path resolves to `~/.credentials.json`\n * anyway, so anchoring at home preserves the legitimate legacy case.\n */\nexport function credentialsFilePath(): string {\n return process.env.ZIDANE_CREDENTIALS_PATH ?? resolve(homedir(), '.credentials.json')\n}\n\n/**\n * POSIX mode for credentials on disk. Read+write for the owner only — refresh\n * tokens and API keys leak if the file is world-readable on a shared box.\n * Ignored on Windows.\n */\nconst CREDENTIALS_FILE_MODE = 0o600\n\ninterface RefreshResult {\n apiKey: string\n event?: OAuthRefreshHookContext\n}\n\nconst refreshLocks = new Map<string, Promise<RefreshResult>>()\nconst storeIds = new WeakMap<object, number>()\nlet nextStoreId = 0\nfunction identity(value: object): number {\n let id = storeIds.get(value)\n if (id === undefined) {\n id = ++nextStoreId\n storeIds.set(value, id)\n }\n return id\n}\n\n/**\n * Register a custom OAuth flow with the local registry (`./oauth-registry`)\n * so lazy refresh through {@link resolveOAuthApiKey} can resolve `providerId`.\n *\n * Public because provider extensions need it: without registration,\n * `getOAuthApiKey('<id>', …)` throws `Unknown OAuth provider` the first time a\n * stored token expires. Idempotent per id (the registry is a keyed map). Kept\n * async for API stability — it awaited a dynamic pi-ai import before pi-ai\n * 0.80.8 removed its global registry and this moved in-repo.\n */\nexport async function registerCustomOAuthProvider(\n provider: import('./oauth-registry').OAuthProviderInterface,\n): Promise<void> {\n registerOAuthProvider(provider)\n}\n\nexport interface OAuthParams {\n apiKey?: string\n access?: string\n refresh?: string\n expires?: number\n}\n\n/**\n * Pull a complete `{ access, refresh, expires }` triple out of provider params\n * when (and only when) all three OAuth fields are present and well-typed.\n * Returns `undefined` for the static-api-key path. Shared by the OAuth-capable\n * providers so the runtime-credential guard can't drift between them.\n */\n/**\n * Whether a credential string is an OAuth *access token* rather than a real,\n * static API key. Anthropic OAuth tokens are prefixed `sk-ant-oat`; OpenAI\n * Codex OAuth access tokens are JWTs (`eyJ...`). Used to decide whether an\n * env-provided value can be refreshed (OAuth) or must be sent verbatim (API key).\n */\nexport function isOAuthAccessToken(value: string): boolean {\n return value.startsWith('sk-ant-oat') || isJwtLikeAccessToken(value)\n}\n\nfunction isJwtLikeAccessToken(value: string): boolean {\n const parts = value.split('.')\n if (parts.length !== 3 || !parts[0]?.startsWith('eyJ'))\n return false\n try {\n const header = JSON.parse(Buffer.from(parts[0], 'base64url').toString('utf8')) as unknown\n return Boolean(header && typeof header === 'object' && 'alg' in header)\n }\n catch {\n return false\n }\n}\n\nexport function extractRuntimeCredentials(\n params?: OAuthParams,\n): { access: string, refresh: string, expires: number } | undefined {\n if (\n typeof params?.access === 'string'\n && typeof params.refresh === 'string'\n && typeof params.expires === 'number'\n ) {\n return { access: params.access, refresh: params.refresh, expires: params.expires }\n }\n return undefined\n}\n\nexport interface ResolveOAuthApiKeyOptions<TParams extends OAuthParams = OAuthParams> {\n provider: string\n providerId: string\n params?: TParams\n envKey?: string\n extraCredentialKeys?: (keyof TParams & string)[]\n missingError: string\n refreshError: (reason: string) => string\n readCredentials?: () => Record<string, OAuthCredentials | undefined>\n /** Synchronous provider-local patch writer; injected stores own cross-process coordination. */\n writeCredentials?: (credentials: Record<string, OAuthCredentials | undefined>) => void\n getOAuthApiKey?: GetOAuthApiKey\n /**\n * Force a refresh even when the credential's local expiry is still in the\n * future (for one-shot 401 recovery). Reaches the refresh helper as an\n * explicit flag, so no credential with a fabricated expiry ever exists to\n * leak into the store — see {@link getOAuthApiKey}.\n */\n forceRefresh?: boolean\n /** Access token rejected by the server; a different token found under lock is reused instead of rotated again. */\n rejectedAccess?: string\n}\n\n/**\n * Read the shared credentials file.\n *\n * Returns `{}` when the file is missing OR when the file exists but is corrupted\n * / not valid JSON. Corrupt-file tolerance is important: the refresh-then-persist\n * path below writes atomically, but older builds wrote in place and could have\n * truncated the file on a crash. Treating corruption as \"no stored creds\" lets\n * users re-auth without manually deleting the file.\n *\n * Supports two on-disk shapes:\n * 1. **Legacy** (untagged): `{ \"anthropic\": { access, refresh, expires, ... } }`\n * — written by older `bun run auth`.\n * 2. **New** (kind-tagged): `{ \"anthropic\": { kind: 'oauth', access, refresh, expires, ... } }`\n * — written by the TUI. Entries with `kind: 'apikey'` are skipped — those\n * reach providers via env vars set at TUI launch (see `applyApiKeyEnv`).\n */\nexport function readOAuthCredentials(): Record<string, OAuthCredentials | undefined> {\n return readOAuthCredentialsAt(credentialsFilePath())\n}\n\nfunction readOAuthCredentialsAt(path: string): Record<string, OAuthCredentials | undefined> {\n if (!existsSync(path))\n return {}\n\n try {\n const raw = readFileSync(path, 'utf-8')\n const parsed = JSON.parse(raw)\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))\n return {}\n const result: Record<string, OAuthCredentials | undefined> = {}\n for (const [key, value] of Object.entries(parsed)) {\n if (!value || typeof value !== 'object' || Array.isArray(value))\n continue\n const v = value as Record<string, unknown>\n // New schema: explicit `kind` tag.\n if (v.kind === 'apikey')\n continue\n // Both new (`kind: 'oauth'`) and legacy (untagged) entries have `access`;\n // the harmless `kind` field is allowed by `OAuthCredentials`'s\n // `[key: string]: unknown` index signature. Validate the required\n // fields explicitly so the assignment doesn't need a double-cast.\n if (\n typeof v.access === 'string'\n && typeof v.refresh === 'string'\n && typeof v.expires === 'number'\n ) {\n result[key] = { ...v, access: v.access, refresh: v.refresh, expires: v.expires }\n }\n }\n return result\n }\n catch {\n return {}\n }\n}\n\n/** Never steal a lock based on age: a suspended holder may still own it. */\nfunction tryAcquireLock(lockPath: string): (() => void) | undefined {\n const owner = `${process.pid}:${randomUUID()}`\n try {\n writeFileSync(lockPath, owner, { flag: 'wx', mode: CREDENTIALS_FILE_MODE })\n }\n catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'EEXIST')\n return undefined\n throw err\n }\n return () => {\n try {\n if (readFileSync(lockPath, 'utf8') === owner)\n unlinkSync(lockPath)\n }\n catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT')\n throw err\n }\n }\n}\n\n/** Shared by refresh persistence and interactive credential mutations. */\nexport function withCredentialsLock<T>(path: string, fn: () => T): T {\n mkdirSync(dirname(path), { recursive: true, mode: 0o700 })\n const deadline = Date.now() + 2000\n let release = tryAcquireLock(`${path}.lock`)\n while (!release) {\n if (Date.now() >= deadline)\n throw new Error('Timed out waiting for credentials lock')\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 20)\n release = tryAcquireLock(`${path}.lock`)\n }\n try {\n return fn()\n }\n finally {\n release()\n }\n}\n\nasync function acquireRefreshLock(lockPath: string): Promise<() => void> {\n mkdirSync(dirname(lockPath), { recursive: true, mode: 0o700 })\n const deadline = Date.now() + 30_000\n while (true) {\n const release = tryAcquireLock(lockPath)\n if (release)\n return release\n if (Date.now() >= deadline)\n throw new Error('Timed out waiting for OAuth refresh lock')\n await new Promise(resolve => setTimeout(resolve, 50))\n }\n}\n\nfunction readRawCredentials(path: string): Record<string, unknown> {\n try {\n const parsed = JSON.parse(readFileSync(path, 'utf8'))\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed))\n return parsed\n }\n catch { /* Missing or corrupt file. */ }\n return {}\n}\n\nfunction writeCredentialPatch(path: string, credentials: Record<string, unknown>): void {\n const merged = { ...readRawCredentials(path), ...credentials }\n for (const [key, value] of Object.entries(merged)) {\n if (value === undefined)\n delete merged[key]\n }\n writeFileAtomic(path, JSON.stringify(merged, null, 2), { mode: CREDENTIALS_FILE_MODE })\n}\n\n/** Incoming entries are a provider-local patch, not a read-time snapshot. */\nexport function writeOAuthCredentials(credentials: Record<string, OAuthCredentials | undefined>) {\n const path = credentialsFilePath()\n withCredentialsLock(path, () => writeCredentialPatch(path, credentials))\n}\n\nexport function credentialsFromParams<TParams extends OAuthParams>(params?: TParams, extraKeys: (keyof TParams & string)[] = []): OAuthCredentials | undefined {\n if (typeof params?.access !== 'string' || typeof params.refresh !== 'string' || typeof params.expires !== 'number')\n return undefined\n\n const extras = Object.fromEntries(\n extraKeys\n .map(key => [key, params[key]])\n .filter(([, value]) => value !== undefined),\n )\n\n return {\n access: params.access,\n refresh: params.refresh,\n expires: params.expires,\n ...extras,\n }\n}\n\nexport async function resolveOAuthApiKey<TParams extends OAuthParams>(\n options: ResolveOAuthApiKeyOptions<TParams>,\n callbacks?: Pick<StreamCallbacks, 'onOAuthRefresh'>,\n): Promise<string> {\n if (typeof options.params?.apiKey === 'string')\n return options.params.apiKey\n const paramsCredentials = credentialsFromParams(options.params, options.extraCredentialKeys)\n if (!paramsCredentials && typeof options.params?.access === 'string')\n return options.params.access\n\n // Capture the store before awaiting: environment changes cannot redirect writes.\n const path = resolve(credentialsFilePath())\n const usingDefaultStore = !options.readCredentials && !options.writeCredentials\n const read = options.readCredentials ?? (() => readOAuthCredentialsAt(path))\n const write = options.writeCredentials ?? (patch => withCredentialsLock(path, () => writeCredentialPatch(path, patch)))\n const envValue = options.envKey ? process.env[options.envKey] : undefined\n if (!paramsCredentials && envValue && (!isOAuthAccessToken(envValue) || !read()[options.providerId]))\n return envValue\n\n const source = paramsCredentials ? 'params' : 'file'\n const initial = paramsCredentials ?? read()[options.providerId]\n const store = usingDefaultStore ? path : [identity(options.readCredentials ?? readOAuthCredentials), identity(options.writeCredentials ?? writeOAuthCredentials)]\n const key = JSON.stringify([source, options.providerId, source === 'file' ? store : null, initial, options.forceRefresh === true, options.rejectedAccess, identity(options.getOAuthApiKey ?? getOAuthApiKey)])\n let task = refreshLocks.get(key)\n if (!task) {\n task = run()\n refreshLocks.set(key, task)\n // Delete before callbacks run; a failing subscriber must not poison peers.\n void task.finally(() => refreshLocks.delete(key)).catch(() => {})\n }\n const result = await task\n if (result.event) {\n await callbacks?.onOAuthRefresh?.({\n ...structuredClone(result.event),\n provider: options.provider,\n })\n }\n return result.apiKey\n\n async function run(): Promise<RefreshResult> {\n const release = source === 'file' && usingDefaultStore\n ? await acquireRefreshLock(`${path}.refresh-${encodeURIComponent(options.providerId)}.lock`)\n : undefined\n try {\n const current = paramsCredentials ?? read()[options.providerId]\n if (!current)\n throw new Error(options.missingError)\n const generation = structuredClone(current)\n let result: Awaited<ReturnType<GetOAuthApiKey>>\n try {\n const stillRejected = options.rejectedAccess === undefined || current.access === options.rejectedAccess\n result = await (options.getOAuthApiKey ?? getOAuthApiKey)(\n options.providerId,\n { [options.providerId]: current },\n { force: options.forceRefresh === true && stillRejected },\n )\n }\n catch (err) {\n throw new Error(options.refreshError(errorMessage(err)), { cause: err })\n }\n if (!result)\n throw new Error(options.refreshError(options.missingError))\n const refreshed = { ...generation, ...result.newCredentials }\n if (source === 'file') {\n const persist = () => {\n if (!isDeepStrictEqual(read()[options.providerId], generation))\n throw new Error('OAuth credentials changed during refresh; retry with current credentials')\n if (result.newCredentials !== current) {\n const patch = { [options.providerId]: refreshed }\n if (usingDefaultStore)\n writeCredentialPatch(path, patch)\n else\n write(patch)\n }\n }\n if (usingDefaultStore)\n withCredentialsLock(path, persist)\n else\n persist()\n }\n return {\n apiKey: result.apiKey,\n ...(result.newCredentials !== current\n ? { event: {\n provider: options.provider,\n providerId: options.providerId,\n source,\n previousCredentials: generation,\n credentials: refreshed,\n } }\n : {}),\n }\n }\n finally {\n release?.()\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"oauth-B6F43fti.js","names":[],"sources":["../src/providers/oauth-registry.ts","../src/providers/oauth.ts"],"sourcesContent":["/**\n * Local OAuth provider registry.\n *\n * pi-ai 0.80.8 removed its global OAuth registry (`registerOAuthProvider`,\n * `getOAuthApiKey`, `OAuthProviderInterface`) and the public low-level\n * refresh helpers in favor of canonical `Provider.auth.oauth` objects\n * resolved through a `Models` collection + `CredentialStore`. zidane's\n * credential architecture (shared `.credentials.json`, cross-process refresh\n * locks in `./oauth.ts`, themed login flows in `src/chat/oauth-page/`)\n * predates that and owns storage itself, so this module reproduces the\n * removed registry surface locally with the same semantics.\n *\n * Nothing protocol-level is duplicated here: token refresh for the built-in\n * providers delegates to pi-ai's canonical `auth.oauth.refresh` flows via the\n * public `providers/*` subpaths (lazily imported, so processes that never\n * authenticate don't load provider code).\n */\n\nimport type { OAuthAuth } from '@earendil-works/pi-ai'\nimport type { OAuthCredentials, OAuthLoginCallbacks } from '@earendil-works/pi-ai/oauth'\nimport { errorMessage } from '../errors'\n\n/**\n * Provider-owned OAuth flow. Mirrors the interface pi-ai exported from\n * `@earendil-works/pi-ai/oauth` up to 0.80.6 — kept as a local type so\n * zidane's descriptor seam (`ProviderDescriptor.oauthProvider`) and provider\n * extensions (e.g. `extensions/bolt`) stay source-compatible across the\n * upstream removal.\n */\nexport interface OAuthProviderInterface {\n readonly id: string\n readonly name: string\n\n /** Run the login flow, return credentials to persist. */\n login: (callbacks: OAuthLoginCallbacks) => Promise<OAuthCredentials>\n\n /** Whether login uses a local callback server and supports manual code input. */\n usesCallbackServer?: boolean\n\n /** Refresh expired credentials, return updated credentials to persist. */\n refreshToken: (credentials: OAuthCredentials) => Promise<OAuthCredentials>\n\n /** Convert credentials to an API key string for the provider. */\n getApiKey: (credentials: OAuthCredentials) => string\n}\n\n/**\n * Run a canonical pi-ai `OAuthAuth.refresh` from a bare refresh token.\n * pi-ai's built-in refresh implementations only read `credential.refresh`,\n * so the placeholder access/expires fields are never observed.\n *\n * The canonical result is an `OAuthCredential` tagged `type: 'oauth'`. Strip\n * the tag before returning: refresh results get merged into stored\n * `.credentials.json` entries and `onOAuthRefresh` hook payloads (see\n * `resolveCredentialSource` in `./oauth.ts`), whose documented shape carries\n * zidane's own `kind` tag — letting pi-ai's `type` leak through would\n * silently drift the on-disk shape.\n */\nasync function refreshViaCanonicalFlow(\n providerId: string,\n oauth: OAuthAuth | undefined,\n refreshToken: string,\n): Promise<OAuthCredentials> {\n if (!oauth)\n throw new Error(`pi-ai provider ${providerId} exposes no OAuth flow`)\n // pi-ai 0.84 made `signal` a required second argument. These refreshers are\n // called from token-expiry paths that carry no cancellation of their own, so\n // a never-aborting signal keeps the pre-0.84 behavior exactly — same idiom as\n // `completion-core.ts`.\n const { type: _type, ...credentials }\n = await oauth.refresh(\n { type: 'oauth', access: '', refresh: refreshToken, expires: 0 },\n new AbortController().signal,\n )\n return credentials\n}\n\n/**\n * pi-ai ≥ 0.85 loads each OAuth flow module through a VARIABLE specifier\n * (`auth/oauth/load.js`: `import(rewrite(\"./anthropic.ts\"))`) precisely so\n * bundlers cannot follow it. Fine under plain Node/Bun where the file sits\n * next to `load.js`; fatal inside anything bundled — the compiled `zidane`\n * binary, the packaged Electron main — where the refresh path died with\n * `Cannot find module './anthropic.js'` and the user was told to re-run auth\n * for a token that was perfectly renewable.\n *\n * pi-ai ships the escape hatch for exactly this: `bun-oauth` statically\n * imports every flow and registers them with the loader. We call it lazily,\n * once, right before the first refresh — a static specifier the bundler DOES\n * follow — so every host (TUI binary, GUI, daemon, SDK) refreshes the same\n * way. Idempotent; failure to register falls through to the loader's own\n * behavior (works unbundled, so nothing gets worse).\n */\nlet bundledFlowsReady: Promise<void> | null = null\nexport function ensureBundledOAuthFlows(): Promise<void> {\n bundledFlowsReady ??= import('@earendil-works/pi-ai/bun-oauth')\n .then(({ registerBunOAuthFlows }) => {\n registerBunOAuthFlows()\n })\n .catch(() => {\n bundledFlowsReady = null\n })\n return bundledFlowsReady\n}\n\n/**\n * Refresh an Anthropic (Claude Pro/Max) OAuth token. Drop-in replacement for\n * the `refreshAnthropicToken` pi-ai exported until 0.80.6; delegates to the\n * canonical flow on `anthropicProvider().auth.oauth`.\n */\nexport async function refreshAnthropicToken(refreshToken: string): Promise<OAuthCredentials> {\n await ensureBundledOAuthFlows()\n const { anthropicProvider } = await import('@earendil-works/pi-ai/providers/anthropic')\n return refreshViaCanonicalFlow('anthropic', anthropicProvider().auth.oauth, refreshToken)\n}\n\n/**\n * Refresh an OpenAI Codex (ChatGPT) OAuth token. Drop-in replacement for the\n * removed `refreshOpenAICodexToken`; the canonical flow still derives\n * `accountId` from the refreshed access token's JWT claims, so the returned\n * credentials keep the extra field older callers persisted.\n */\nexport async function refreshOpenAICodexToken(refreshToken: string): Promise<OAuthCredentials> {\n await ensureBundledOAuthFlows()\n const { openaiCodexProvider } = await import('@earendil-works/pi-ai/providers/openai-codex')\n return refreshViaCanonicalFlow('openai-codex', openaiCodexProvider().auth.oauth, refreshToken)\n}\n\n/**\n * Built-in registry seeds. Every id whose stored credential can expire has to\n * be seeded here, because zidane's lazy token refresh (`getOAuthApiKey` inside\n * `resolveOAuthApiKey`) resolves through the registry — an unseeded id is not a\n * degraded path but a dead end, with no way to renew the session. That is wider\n * than the Anthropic / OpenAI Codex pair pi-ai's removed registry pre-seeded:\n * flows zidane implements itself (xAI) need seeding just as much.\n *\n * Login is intentionally unsupported here: interactive login always goes\n * through `ProviderDescriptor.oauthProvider` (the themed flows in\n * `src/chat/oauth-page/`), never through the registry.\n */\nfunction builtinProvider(\n id: string,\n name: string,\n refreshToken: (credentials: OAuthCredentials) => Promise<OAuthCredentials>,\n): OAuthProviderInterface {\n return {\n id,\n name,\n login: async () => {\n throw new Error(`OAuth login for ${id} must run through a provider descriptor flow, not the registry`)\n },\n refreshToken,\n getApiKey: credentials => credentials.access,\n }\n}\n\n/**\n * Seed id for the xAI Grok OAuth flow. Duplicated from\n * `XAI_OAUTH_PROVIDER_ID` in `../chat/oauth-page/xai` rather than imported:\n * that module pulls in `node:http` and the PKCE helpers at module scope, and\n * this registry is loaded by every consumer that resolves a credential. The\n * ids are pinned together by a test, so the duplication cannot drift.\n */\nconst XAI_SEED_ID = 'xai-oauth'\n\n/**\n * Refresh an xAI Grok token. Imported lazily so seeding the registry does not\n * drag the login flow's `node:http` callback server into every consumer —\n * and so the static import edge back to this module stays type-only, leaving\n * no runtime cycle.\n *\n * Unlike the Anthropic / Codex seeds, this receives the FULL credential rather\n * than a bare refresh token: xAI stores its resolved `token_endpoint` (and the\n * cached discovery doc) alongside the tokens, and dropping them would force a\n * fresh OIDC discovery round-trip on every refresh — and discard the validated\n * endpoint that `refreshXaiToken` re-checks before posting the refresh token\n * to it.\n */\nasync function refreshXaiTokenSeed(credentials: OAuthCredentials): Promise<OAuthCredentials> {\n const { refreshXaiToken } = await import('../chat/oauth-page/xai')\n return refreshXaiToken(credentials)\n}\n\n/**\n * Seed id for the Cursor OAuth flow. Duplicated from\n * `CURSOR_OAUTH_PROVIDER_ID` for the same reason as {@link XAI_SEED_ID}, and\n * pinned to it by a test.\n */\nconst CURSOR_SEED_ID = 'cursor'\n\n/**\n * Refresh a Cursor token. Lazy for the same reason as the xAI seed: the login\n * module owns a callback server, and seeding must not drag it into every\n * consumer that merely resolves a credential.\n */\nasync function refreshCursorTokenSeed(credentials: OAuthCredentials): Promise<OAuthCredentials> {\n const { refreshCursorToken } = await import('../chat/oauth-page/cursor')\n return refreshCursorToken(credentials)\n}\n\n/**\n * The registry map is anchored on a process-wide `globalThis` slot rather\n * than module state. The pi-ai registry this replaces lived in pi-ai's own\n * module, so every copy of zidane code in a process shared one map — and two\n * copies is a real configuration, not a hypothetical: a host running from\n * `src` while a disk-loaded extension resolves `'zidane'` to `dist` through\n * ordinary node_modules resolution (see `import()` in\n * `src/extensions/discovery.ts`). The extension registers its flow at setup;\n * `resolveOAuthApiKey` looks it up only once a stored token expires — with\n * per-module maps that fails as a deferred \"Unknown OAuth provider\", which is\n * nasty to debug. `Symbol.for` restores the process-wide sharing. Whichever\n * copy touches the slot first seeds the built-ins, so a `dist` consumer can end\n * up refreshing through the `src` copy's closure (and vice versa). That is\n * harmless because the seeds are STATELESS refresh delegations — they close over\n * no module state and no configuration, so the two copies compute the same\n * result from the same stored credential. It is that property, not where the\n * refresh happens to be implemented, that makes first-copy-wins safe: it holds\n * equally for the seeds that delegate to pi-ai's canonical flows and for the\n * xAI one implemented in `../chat/oauth-page/xai`.\n */\n/**\n * Exported so tests that register a throwaway provider can remove it from the\n * same slot rather than re-deriving the key with a copied string: the registry\n * exposes no unregister, and a `Symbol.for` looked up from a duplicated literal\n * would silently stop matching if this key were ever renamed, leaving the test\n * provider resident in shared state. Deliberately kept out of the package's\n * public surface — `./index.ts` re-exports this module by explicit name list.\n */\nexport const REGISTRY_SLOT = Symbol.for('zidane.oauth-provider-registry')\n\nfunction oauthProviderRegistry(): Map<string, OAuthProviderInterface> {\n const slots = globalThis as { [key: symbol]: Map<string, OAuthProviderInterface> | undefined }\n slots[REGISTRY_SLOT] ??= new Map(\n [\n builtinProvider('anthropic', 'Anthropic (Claude Pro/Max)', c => refreshAnthropicToken(c.refresh)),\n builtinProvider('openai-codex', 'OpenAI (ChatGPT Plus/Pro)', c => refreshOpenAICodexToken(c.refresh)),\n // xAI is NOT built into pi-ai, so unlike the two above there is no\n // canonical flow to delegate to — `../chat/oauth-page/xai` implements it.\n // It is seeded here anyway because seeding is what makes refresh work for\n // consumers that never import `chat/providers.ts` or `auth.ts`, whose\n // module-scope `registerXaiOAuthProvider()` call was previously the ONLY\n // thing that registered it. A library consumer resolving a stored\n // credential through `getOAuthApiKey` got `Unknown OAuth provider:\n // xai-oauth` and had no way to renew a session — a dead end rather than a\n // degraded path. The interactive login half still comes from\n // `registerXaiOAuthProvider(renderPage)`, which replaces this seed.\n builtinProvider(XAI_SEED_ID, 'xAI Grok (SuperGrok / X Premium+)', refreshXaiTokenSeed),\n // Cursor has the identical shape and had the identical defect: its flow\n // lives in `../chat/oauth-page/cursor` and was registered only as a\n // module-scope side effect of importing `chat/providers.ts` / `auth.ts`.\n builtinProvider(CURSOR_SEED_ID, 'Cursor', refreshCursorTokenSeed),\n ].map(provider => [provider.id, provider]),\n )\n return slots[REGISTRY_SLOT]\n}\n\n/** Register (or replace) an OAuth provider by id. */\nexport function registerOAuthProvider(provider: OAuthProviderInterface): void {\n oauthProviderRegistry().set(provider.id, provider)\n}\n\n/** Get a registered OAuth provider by id. */\nexport function getOAuthProvider(id: string): OAuthProviderInterface | undefined {\n return oauthProviderRegistry().get(id)\n}\n\n/**\n * Get an API key for a provider from OAuth credentials, refreshing expired\n * tokens through the registered flow. Same contract as the pi-ai original\n * (`null` when no credentials are stored for the provider, throws on unknown\n * provider or refresh failure), with two deliberate additions:\n *\n * - refresh failures keep the underlying reason — the original discarded it,\n * leaving invalid_grant (needs re-login) indistinguishable from a transient\n * network error.\n * - `options.force` rotates regardless of the local expiry, for 401 recovery\n * when the server revoked the grant before our stored timestamp said so.\n * Expressed as a flag rather than by handing this function a credential\n * with a doctored `expires`: callers persist whatever comes back, so a\n * fabricated expiry that a provider echoes through would land in the\n * credential store. See `resolveCredentialSource` in `./oauth.ts`.\n */\nexport async function getOAuthApiKey(\n providerId: string,\n credentials: Record<string, OAuthCredentials>,\n options?: { force?: boolean },\n): Promise<{ newCredentials: OAuthCredentials, apiKey: string } | null> {\n const provider = oauthProviderRegistry().get(providerId)\n if (!provider)\n throw new Error(`Unknown OAuth provider: ${providerId}`)\n\n let creds = credentials[providerId]\n if (!creds)\n return null\n\n if (options?.force || Date.now() >= creds.expires) {\n try {\n creds = await provider.refreshToken(creds)\n }\n catch (err) {\n throw new Error(`Failed to refresh OAuth token for ${providerId}: ${errorMessage(err)}`, { cause: err })\n }\n }\n\n return { newCredentials: creds, apiKey: provider.getApiKey(creds) }\n}\n","import type { OAuthCredentials } from '@earendil-works/pi-ai/oauth'\nimport type { StreamCallbacks } from '.'\nimport type { OAuthRefreshHookContext } from '../types'\nimport { Buffer } from 'node:buffer'\nimport { randomUUID } from 'node:crypto'\nimport { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { dirname, resolve } from 'node:path'\nimport { isDeepStrictEqual } from 'node:util'\nimport { writeFileAtomic } from '../atomic-write'\nimport { errorMessage } from '../errors'\nimport { getOAuthApiKey, registerOAuthProvider } from './oauth-registry'\n\ntype GetOAuthApiKey = typeof getOAuthApiKey\n\n/**\n * Resolve the creds-file path at call time rather than module-load time, so\n * env changes between import and first OAuth use (test suites, multi-project\n * CLIs) are honored.\n *\n * Order:\n * 1. `ZIDANE_CREDENTIALS_PATH` — explicit override. The TUI sets this at\n * launch to point at `<storageDir>/credentials.json` so the harness\n * providers find the same file the TUI manages.\n * 2. `~/.credentials.json` — home-anchored default.\n *\n * The historic `cwd/.credentials.json` fallback was deliberately dropped:\n * launching zidane inside an untrusted checkout would let that repo plant a\n * `.credentials.json` and become the credential source (and receive rotated\n * refresh tokens on the next refresh persist). Same trust model as the TUI's\n * legacy-file migration in `chat/credentials.ts`, which only honors the\n * home-anchored location. The only \"trusted\" cwd under that model is `$HOME`\n * itself — where the cwd-relative path resolves to `~/.credentials.json`\n * anyway, so anchoring at home preserves the legitimate legacy case.\n */\nexport function credentialsFilePath(): string {\n return process.env.ZIDANE_CREDENTIALS_PATH ?? resolve(homedir(), '.credentials.json')\n}\n\n/**\n * POSIX mode for credentials on disk. Read+write for the owner only — refresh\n * tokens and API keys leak if the file is world-readable on a shared box.\n * Ignored on Windows.\n */\nconst CREDENTIALS_FILE_MODE = 0o600\n\ninterface RefreshResult {\n apiKey: string\n event?: OAuthRefreshHookContext\n}\n\nconst refreshLocks = new Map<string, Promise<RefreshResult>>()\nconst storeIds = new WeakMap<object, number>()\nlet nextStoreId = 0\nfunction identity(value: object): number {\n let id = storeIds.get(value)\n if (id === undefined) {\n id = ++nextStoreId\n storeIds.set(value, id)\n }\n return id\n}\n\n/**\n * Register a custom OAuth flow with the local registry (`./oauth-registry`)\n * so lazy refresh through {@link resolveOAuthApiKey} can resolve `providerId`.\n *\n * Public because provider extensions need it: without registration,\n * `getOAuthApiKey('<id>', …)` throws `Unknown OAuth provider` the first time a\n * stored token expires. Idempotent per id (the registry is a keyed map). Kept\n * async for API stability — it awaited a dynamic pi-ai import before pi-ai\n * 0.80.8 removed its global registry and this moved in-repo.\n */\nexport async function registerCustomOAuthProvider(\n provider: import('./oauth-registry').OAuthProviderInterface,\n): Promise<void> {\n registerOAuthProvider(provider)\n}\n\nexport interface OAuthParams {\n apiKey?: string\n access?: string\n refresh?: string\n expires?: number\n}\n\n/**\n * Pull a complete `{ access, refresh, expires }` triple out of provider params\n * when (and only when) all three OAuth fields are present and well-typed.\n * Returns `undefined` for the static-api-key path. Shared by the OAuth-capable\n * providers so the runtime-credential guard can't drift between them.\n */\n/**\n * Whether a credential string is an OAuth *access token* rather than a real,\n * static API key. Anthropic OAuth tokens are prefixed `sk-ant-oat`; OpenAI\n * Codex OAuth access tokens are JWTs (`eyJ...`). Used to decide whether an\n * env-provided value can be refreshed (OAuth) or must be sent verbatim (API key).\n */\nexport function isOAuthAccessToken(value: string): boolean {\n return value.startsWith('sk-ant-oat') || isJwtLikeAccessToken(value)\n}\n\nfunction isJwtLikeAccessToken(value: string): boolean {\n const parts = value.split('.')\n if (parts.length !== 3 || !parts[0]?.startsWith('eyJ'))\n return false\n try {\n const header = JSON.parse(Buffer.from(parts[0], 'base64url').toString('utf8')) as unknown\n return Boolean(header && typeof header === 'object' && 'alg' in header)\n }\n catch {\n return false\n }\n}\n\nexport function extractRuntimeCredentials(\n params?: OAuthParams,\n): { access: string, refresh: string, expires: number } | undefined {\n if (\n typeof params?.access === 'string'\n && typeof params.refresh === 'string'\n && typeof params.expires === 'number'\n ) {\n return { access: params.access, refresh: params.refresh, expires: params.expires }\n }\n return undefined\n}\n\nexport interface ResolveOAuthApiKeyOptions<TParams extends OAuthParams = OAuthParams> {\n provider: string\n providerId: string\n params?: TParams\n envKey?: string\n extraCredentialKeys?: (keyof TParams & string)[]\n missingError: string\n refreshError: (reason: string) => string\n readCredentials?: () => Record<string, OAuthCredentials | undefined>\n /** Synchronous provider-local patch writer; injected stores own cross-process coordination. */\n writeCredentials?: (credentials: Record<string, OAuthCredentials | undefined>) => void\n getOAuthApiKey?: GetOAuthApiKey\n /**\n * Force a refresh even when the credential's local expiry is still in the\n * future (for one-shot 401 recovery). Reaches the refresh helper as an\n * explicit flag, so no credential with a fabricated expiry ever exists to\n * leak into the store — see {@link getOAuthApiKey}.\n */\n forceRefresh?: boolean\n /** Access token rejected by the server; a different token found under lock is reused instead of rotated again. */\n rejectedAccess?: string\n}\n\n/**\n * Read the shared credentials file.\n *\n * Returns `{}` when the file is missing OR when the file exists but is corrupted\n * / not valid JSON. Corrupt-file tolerance is important: the refresh-then-persist\n * path below writes atomically, but older builds wrote in place and could have\n * truncated the file on a crash. Treating corruption as \"no stored creds\" lets\n * users re-auth without manually deleting the file.\n *\n * Supports two on-disk shapes:\n * 1. **Legacy** (untagged): `{ \"anthropic\": { access, refresh, expires, ... } }`\n * — written by older `bun run auth`.\n * 2. **New** (kind-tagged): `{ \"anthropic\": { kind: 'oauth', access, refresh, expires, ... } }`\n * — written by the TUI. Entries with `kind: 'apikey'` are skipped — those\n * reach providers via env vars set at TUI launch (see `applyApiKeyEnv`).\n */\nexport function readOAuthCredentials(): Record<string, OAuthCredentials | undefined> {\n return readOAuthCredentialsAt(credentialsFilePath())\n}\n\nfunction readOAuthCredentialsAt(path: string): Record<string, OAuthCredentials | undefined> {\n if (!existsSync(path))\n return {}\n\n try {\n const raw = readFileSync(path, 'utf-8')\n const parsed = JSON.parse(raw)\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))\n return {}\n const result: Record<string, OAuthCredentials | undefined> = {}\n for (const [key, value] of Object.entries(parsed)) {\n if (!value || typeof value !== 'object' || Array.isArray(value))\n continue\n const v = value as Record<string, unknown>\n // New schema: explicit `kind` tag.\n if (v.kind === 'apikey')\n continue\n // Both new (`kind: 'oauth'`) and legacy (untagged) entries have `access`;\n // the harmless `kind` field is allowed by `OAuthCredentials`'s\n // `[key: string]: unknown` index signature. Validate the required\n // fields explicitly so the assignment doesn't need a double-cast.\n if (\n typeof v.access === 'string'\n && typeof v.refresh === 'string'\n && typeof v.expires === 'number'\n ) {\n result[key] = { ...v, access: v.access, refresh: v.refresh, expires: v.expires }\n }\n }\n return result\n }\n catch {\n return {}\n }\n}\n\n/** Never steal a lock based on age: a suspended holder may still own it. */\nfunction tryAcquireLock(lockPath: string): (() => void) | undefined {\n const owner = `${process.pid}:${randomUUID()}`\n try {\n writeFileSync(lockPath, owner, { flag: 'wx', mode: CREDENTIALS_FILE_MODE })\n }\n catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'EEXIST')\n return undefined\n throw err\n }\n return () => {\n try {\n if (readFileSync(lockPath, 'utf8') === owner)\n unlinkSync(lockPath)\n }\n catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT')\n throw err\n }\n }\n}\n\n/** Shared by refresh persistence and interactive credential mutations. */\nexport function withCredentialsLock<T>(path: string, fn: () => T): T {\n mkdirSync(dirname(path), { recursive: true, mode: 0o700 })\n const deadline = Date.now() + 2000\n let release = tryAcquireLock(`${path}.lock`)\n while (!release) {\n if (Date.now() >= deadline)\n throw new Error('Timed out waiting for credentials lock')\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 20)\n release = tryAcquireLock(`${path}.lock`)\n }\n try {\n return fn()\n }\n finally {\n release()\n }\n}\n\nasync function acquireRefreshLock(lockPath: string): Promise<() => void> {\n mkdirSync(dirname(lockPath), { recursive: true, mode: 0o700 })\n const deadline = Date.now() + 30_000\n while (true) {\n const release = tryAcquireLock(lockPath)\n if (release)\n return release\n if (Date.now() >= deadline)\n throw new Error('Timed out waiting for OAuth refresh lock')\n await new Promise(resolve => setTimeout(resolve, 50))\n }\n}\n\nfunction readRawCredentials(path: string): Record<string, unknown> {\n try {\n const parsed = JSON.parse(readFileSync(path, 'utf8'))\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed))\n return parsed\n }\n catch { /* Missing or corrupt file. */ }\n return {}\n}\n\nfunction writeCredentialPatch(path: string, credentials: Record<string, unknown>): void {\n const merged = { ...readRawCredentials(path), ...credentials }\n for (const [key, value] of Object.entries(merged)) {\n if (value === undefined)\n delete merged[key]\n }\n writeFileAtomic(path, JSON.stringify(merged, null, 2), { mode: CREDENTIALS_FILE_MODE })\n}\n\n/** Incoming entries are a provider-local patch, not a read-time snapshot. */\nexport function writeOAuthCredentials(credentials: Record<string, OAuthCredentials | undefined>) {\n const path = credentialsFilePath()\n withCredentialsLock(path, () => writeCredentialPatch(path, credentials))\n}\n\nexport function credentialsFromParams<TParams extends OAuthParams>(params?: TParams, extraKeys: (keyof TParams & string)[] = []): OAuthCredentials | undefined {\n if (typeof params?.access !== 'string' || typeof params.refresh !== 'string' || typeof params.expires !== 'number')\n return undefined\n\n const extras = Object.fromEntries(\n extraKeys\n .map(key => [key, params[key]])\n .filter(([, value]) => value !== undefined),\n )\n\n return {\n access: params.access,\n refresh: params.refresh,\n expires: params.expires,\n ...extras,\n }\n}\n\nexport async function resolveOAuthApiKey<TParams extends OAuthParams>(\n options: ResolveOAuthApiKeyOptions<TParams>,\n callbacks?: Pick<StreamCallbacks, 'onOAuthRefresh'> & { onOAuthStage?: (stage: 'credentials' | 'refresh' | 'shared-refresh') => void },\n): Promise<string> {\n if (typeof options.params?.apiKey === 'string') {\n if (isOAuthAccessToken(options.params.apiKey))\n callbacks?.onOAuthStage?.('credentials')\n return options.params.apiKey\n }\n const paramsCredentials = credentialsFromParams(options.params, options.extraCredentialKeys)\n if (!paramsCredentials && typeof options.params?.access === 'string') {\n callbacks?.onOAuthStage?.('credentials')\n return options.params.access\n }\n\n // Capture the store before awaiting: environment changes cannot redirect writes.\n const path = resolve(credentialsFilePath())\n const usingDefaultStore = !options.readCredentials && !options.writeCredentials\n const read = options.readCredentials ?? (() => readOAuthCredentialsAt(path))\n const write = options.writeCredentials ?? (patch => withCredentialsLock(path, () => writeCredentialPatch(path, patch)))\n const envValue = options.envKey ? process.env[options.envKey] : undefined\n if (!paramsCredentials && envValue && (!isOAuthAccessToken(envValue) || !read()[options.providerId])) {\n if (isOAuthAccessToken(envValue))\n callbacks?.onOAuthStage?.('credentials')\n return envValue\n }\n\n callbacks?.onOAuthStage?.('credentials')\n const source = paramsCredentials ? 'params' : 'file'\n const initial = paramsCredentials ?? read()[options.providerId]\n const store = usingDefaultStore ? path : [identity(options.readCredentials ?? readOAuthCredentials), identity(options.writeCredentials ?? writeOAuthCredentials)]\n const key = JSON.stringify([source, options.providerId, source === 'file' ? store : null, initial, options.forceRefresh === true, options.rejectedAccess, identity(options.getOAuthApiKey ?? getOAuthApiKey)])\n let task = refreshLocks.get(key)\n if (task)\n callbacks?.onOAuthStage?.('shared-refresh')\n if (!task) {\n task = run()\n refreshLocks.set(key, task)\n // Delete before callbacks run; a failing subscriber must not poison peers.\n void task.finally(() => refreshLocks.delete(key)).catch(() => {})\n }\n const result = await task\n if (result.event) {\n await callbacks?.onOAuthRefresh?.({\n ...structuredClone(result.event),\n provider: options.provider,\n })\n }\n return result.apiKey\n\n async function run(): Promise<RefreshResult> {\n const release = source === 'file' && usingDefaultStore\n ? await acquireRefreshLock(`${path}.refresh-${encodeURIComponent(options.providerId)}.lock`)\n : undefined\n try {\n const current = paramsCredentials ?? read()[options.providerId]\n if (!current)\n throw new Error(options.missingError)\n const generation = structuredClone(current)\n let result: Awaited<ReturnType<GetOAuthApiKey>>\n try {\n const stillRejected = options.rejectedAccess === undefined || current.access === options.rejectedAccess\n if (current.expires <= Date.now() || (options.forceRefresh && stillRejected))\n callbacks?.onOAuthStage?.('refresh')\n result = await (options.getOAuthApiKey ?? getOAuthApiKey)(\n options.providerId,\n { [options.providerId]: current },\n { force: options.forceRefresh === true && stillRejected },\n )\n }\n catch (err) {\n throw new Error(options.refreshError(errorMessage(err)), { cause: err })\n }\n if (!result)\n throw new Error(options.refreshError(options.missingError))\n const refreshed = { ...generation, ...result.newCredentials }\n if (source === 'file') {\n const persist = () => {\n if (!isDeepStrictEqual(read()[options.providerId], generation))\n throw new Error('OAuth credentials changed during refresh; retry with current credentials')\n if (result.newCredentials !== current) {\n const patch = { [options.providerId]: refreshed }\n if (usingDefaultStore)\n writeCredentialPatch(path, patch)\n else\n write(patch)\n }\n }\n if (usingDefaultStore)\n withCredentialsLock(path, persist)\n else\n persist()\n }\n return {\n apiKey: result.apiKey,\n ...(result.newCredentials !== current\n ? { event: {\n provider: options.provider,\n providerId: options.providerId,\n source,\n previousCredentials: generation,\n credentials: refreshed,\n } }\n : {}),\n }\n }\n finally {\n release?.()\n }\n }\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { $n as HeadlessResult, Jn as FormattedHeadlessTurnEvent, Qn as HeadlessOutputFormat, Xn as HeadlessEvent, qi as Agent, qn as FormattedHeadlessResult } from "../index-C4mY0oKE.js";
1
+ import { $n as HeadlessResult, Jn as FormattedHeadlessTurnEvent, Qn as HeadlessOutputFormat, Xn as HeadlessEvent, qi as Agent, qn as FormattedHeadlessResult } from "../index-C0y2tSgk.js";
2
2
 
3
3
  //#region src/output/stream-json.d.ts
4
4
  type StreamJsonOutputFormat = HeadlessOutputFormat;
@@ -1,4 +1,4 @@
1
- import { Yi as AgentHooks, qi as Agent, t as Preset } from "../index-C4mY0oKE.js";
1
+ import { Yi as AgentHooks, qi as Agent, t as Preset } from "../index-C0y2tSgk.js";
2
2
 
3
3
  //#region src/output/terminal.d.ts
4
4
  interface TerminalOutputOptions {
package/dist/plugins.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { Ac as PLUGIN_MANIFEST_FILE, Bc as PluginSchemaKind, Dc as ManifestValidation, Ec as expandPluginVars, Fc as LoadedPlugin, Hc as agentPluginsSchemaId, Ic as PluginAuthor, Lc as PluginComponentStatus, Mc as loadAgentPlugin, Nc as AgentPluginsVersion, Oc as parsePluginManifest, Pc as LoadAgentPluginOptions, Rc as PluginDiagnostic, Tc as PLUGIN_MCP_FILE, Uc as isSupportedAgentPluginsVersion, Vc as SUPPORTED_AGENT_PLUGINS_VERSIONS, Wc as parseSchemaId, jc as defaultPluginDataRoot, kc as validatePluginManifest, wc as PLUGIN_SKILLS_DIR, zc as PluginManifest } from "./index-C4mY0oKE.js";
1
+ import { Ac as PLUGIN_MANIFEST_FILE, Bc as PluginSchemaKind, Dc as ManifestValidation, Ec as expandPluginVars, Fc as LoadedPlugin, Hc as agentPluginsSchemaId, Ic as PluginAuthor, Lc as PluginComponentStatus, Mc as loadAgentPlugin, Nc as AgentPluginsVersion, Oc as parsePluginManifest, Pc as LoadAgentPluginOptions, Rc as PluginDiagnostic, Tc as PLUGIN_MCP_FILE, Uc as isSupportedAgentPluginsVersion, Vc as SUPPORTED_AGENT_PLUGINS_VERSIONS, Wc as parseSchemaId, jc as defaultPluginDataRoot, kc as validatePluginManifest, wc as PLUGIN_SKILLS_DIR, zc as PluginManifest } from "./index-C0y2tSgk.js";
2
2
  export { type AgentPluginsVersion, type LoadAgentPluginOptions, type LoadedPlugin, type ManifestValidation, PLUGIN_MANIFEST_FILE, PLUGIN_MCP_FILE, PLUGIN_SKILLS_DIR, type PluginAuthor, type PluginComponentStatus, type PluginDiagnostic, type PluginManifest, type PluginSchemaKind, SUPPORTED_AGENT_PLUGINS_VERSIONS, agentPluginsSchemaId, defaultPluginDataRoot, expandPluginVars, isSupportedAgentPluginsVersion, loadAgentPlugin, parsePluginManifest, parseSchemaId, validatePluginManifest };
package/dist/presets.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { Ji as AgentHookMap, Ki as _default, a as basicTools, i as _default$1, n as composePresets, r as definePreset, t as Preset } from "./index-C4mY0oKE.js";
1
+ import { Ji as AgentHookMap, Ki as _default, a as basicTools, i as _default$1, n as composePresets, r as definePreset, t as Preset } from "./index-C0y2tSgk.js";
2
2
  export { type AgentHookMap, Preset, _default as artifacts, _default$1 as basic, basicTools, composePresets, definePreset };
package/dist/presets.js CHANGED
@@ -1,2 +1,2 @@
1
- import { Jt as artifacts_default, bt as basicTools, vt as composePresets, xt as basic_default, yt as definePreset } from "./agent-LC2c1ue-.js";
1
+ import { Jt as artifacts_default, bt as basicTools, vt as composePresets, xt as basic_default, yt as definePreset } from "./agent-BjI9-PlG.js";
2
2
  export { artifacts_default as artifacts, basic_default as basic, basicTools, composePresets, definePreset };
@@ -1,2 +1,2 @@
1
- import { Ah as anthropic, Bh as resolveAnthropicMaxOutput, Dh as AnthropicParams, Eh as AnthropicContextManagement, Fh as credentialIdentity, Ih as defaultAnthropicMaxTokens, Lh as planAnthropicThinking, Mh as buildAnthropicClientOptions, Nh as cacheWriteTtlMultiplier, Oh as AnthropicThinkingPlan, Ph as classifyAnthropicError, Rh as resolveAnthropicAuthShape, Th as AnthropicBedrockConfig, jh as applyAnthropicCacheBreakpoints, kh as EXTENDED_CACHE_TTL_BETA, zh as resolveAnthropicBetas } from "../index-C4mY0oKE.js";
1
+ import { Ah as anthropic, Bh as resolveAnthropicMaxOutput, Dh as AnthropicParams, Eh as AnthropicContextManagement, Fh as credentialIdentity, Ih as defaultAnthropicMaxTokens, Lh as planAnthropicThinking, Mh as buildAnthropicClientOptions, Nh as cacheWriteTtlMultiplier, Oh as AnthropicThinkingPlan, Ph as classifyAnthropicError, Rh as resolveAnthropicAuthShape, Th as AnthropicBedrockConfig, jh as applyAnthropicCacheBreakpoints, kh as EXTENDED_CACHE_TTL_BETA, zh as resolveAnthropicBetas } from "../index-C0y2tSgk.js";
2
2
  export { AnthropicBedrockConfig, AnthropicContextManagement, AnthropicParams, AnthropicThinkingPlan, EXTENDED_CACHE_TTL_BETA, anthropic, applyAnthropicCacheBreakpoints, buildAnthropicClientOptions, cacheWriteTtlMultiplier, classifyAnthropicError, credentialIdentity, defaultAnthropicMaxTokens, planAnthropicThinking, resolveAnthropicAuthShape, resolveAnthropicBetas, resolveAnthropicMaxOutput };
@@ -1,2 +1,2 @@
1
- import { a as cacheWriteTtlMultiplier, c as defaultAnthropicMaxTokens, d as resolveAnthropicBetas, f as resolveAnthropicMaxOutput, i as buildAnthropicClientOptions, l as planAnthropicThinking, n as anthropic, o as classifyAnthropicError, r as applyAnthropicCacheBreakpoints, s as credentialIdentity, t as EXTENDED_CACHE_TTL_BETA, u as resolveAnthropicAuthShape } from "../anthropic-BSbDiW0J.js";
1
+ import { a as cacheWriteTtlMultiplier, c as defaultAnthropicMaxTokens, d as resolveAnthropicBetas, f as resolveAnthropicMaxOutput, i as buildAnthropicClientOptions, l as planAnthropicThinking, n as anthropic, o as classifyAnthropicError, r as applyAnthropicCacheBreakpoints, s as credentialIdentity, t as EXTENDED_CACHE_TTL_BETA, u as resolveAnthropicAuthShape } from "../anthropic-CUM2CjXS.js";
2
2
  export { EXTENDED_CACHE_TTL_BETA, anthropic, applyAnthropicCacheBreakpoints, buildAnthropicClientOptions, cacheWriteTtlMultiplier, classifyAnthropicError, credentialIdentity, defaultAnthropicMaxTokens, planAnthropicThinking, resolveAnthropicAuthShape, resolveAnthropicBetas, resolveAnthropicMaxOutput };
@@ -1,2 +1,2 @@
1
- import { Ch as ArceeParams, wh as arcee } from "../index-C4mY0oKE.js";
1
+ import { Ch as ArceeParams, wh as arcee } from "../index-C0y2tSgk.js";
2
2
  export { ArceeParams, arcee };
@@ -1,2 +1,2 @@
1
- import { Sh as planBasetenReasoning, bh as BasetenParams, xh as baseten } from "../index-C4mY0oKE.js";
1
+ import { Sh as planBasetenReasoning, bh as BasetenParams, xh as baseten } from "../index-C0y2tSgk.js";
2
2
  export { BasetenParams, baseten, planBasetenReasoning };
@@ -1,2 +1,2 @@
1
- import { vh as CerebrasParams, yh as cerebras } from "../index-C4mY0oKE.js";
1
+ import { vh as CerebrasParams, yh as cerebras } from "../index-C0y2tSgk.js";
2
2
  export { CerebrasParams, cerebras };
@@ -1,2 +1,2 @@
1
- import { hh as local, mh as LocalParams } from "../index-C4mY0oKE.js";
1
+ import { hh as local, mh as LocalParams } from "../index-C0y2tSgk.js";
2
2
  export { LocalParams, local };
@@ -1,2 +1,2 @@
1
- import { $m as formatTools, Bm as OAIMessage, Gm as OpenAICompatStreamError, Hm as OpenAICompatAuthHeader, Jm as applyOAIToolCacheBreakpoint, Km as TOOL_RESULTS_TAG, Qm as consumeSSE, Um as OpenAICompatHttpError, Vm as OAITool, Wm as OpenAICompatParams, Xm as buildAssistantContent, Ym as assistantMessage, Zm as classifyOpenAICompatError, ah as userMessage, eh as mapOAIFinishReason, ih as toolResultsMessage, nh as openaiCompat, qm as applyOAICacheBreakpoints, rh as toOAIMessages, th as openAICompatPromptMessage, zm as ASSISTANT_TOOL_CALLS_TAG } from "../index-C4mY0oKE.js";
1
+ import { $m as formatTools, Bm as OAIMessage, Gm as OpenAICompatStreamError, Hm as OpenAICompatAuthHeader, Jm as applyOAIToolCacheBreakpoint, Km as TOOL_RESULTS_TAG, Qm as consumeSSE, Um as OpenAICompatHttpError, Vm as OAITool, Wm as OpenAICompatParams, Xm as buildAssistantContent, Ym as assistantMessage, Zm as classifyOpenAICompatError, ah as userMessage, eh as mapOAIFinishReason, ih as toolResultsMessage, nh as openaiCompat, qm as applyOAICacheBreakpoints, rh as toOAIMessages, th as openAICompatPromptMessage, zm as ASSISTANT_TOOL_CALLS_TAG } from "../index-C0y2tSgk.js";
2
2
  export { ASSISTANT_TOOL_CALLS_TAG, OAIMessage, OAITool, OpenAICompatAuthHeader, OpenAICompatHttpError, OpenAICompatParams, OpenAICompatStreamError, TOOL_RESULTS_TAG, applyOAICacheBreakpoints, applyOAIToolCacheBreakpoint, assistantMessage, buildAssistantContent, classifyOpenAICompatError, consumeSSE, formatTools, mapOAIFinishReason, openAICompatPromptMessage, openaiCompat, toOAIMessages, toolResultsMessage, userMessage };
@@ -1,2 +1,2 @@
1
- import { ch as mapCodexStopReason, lh as openai, oh as OpenAIParams, sh as classifyOpenAIError, uh as toPiMessages } from "../index-C4mY0oKE.js";
1
+ import { ch as mapCodexStopReason, lh as openai, oh as OpenAIParams, sh as classifyOpenAIError, uh as toPiMessages } from "../index-C0y2tSgk.js";
2
2
  export { OpenAIParams, classifyOpenAIError, mapCodexStopReason, openai, toPiMessages };
@@ -3,7 +3,7 @@ import { a as documentBlockMarker, r as assertResolvedMediaBlock } from "../type
3
3
  import { f as isRetryableHttpStatus, h as matchesToolPairingError, l as classifyErrorPrelude, m as matchesContextExceeded } from "../errors-DKiMrOKq.js";
4
4
  import { renderSystemForWire } from "../prompt.js";
5
5
  import { f as reasoningWireAction, n as SYNTHETIC_TOOL_RESULT_PLACEHOLDER } from "../messages-aORUmIKe.js";
6
- import { r as extractRuntimeCredentials, s as resolveOAuthApiKey } from "../oauth-CmtlmCzi.js";
6
+ import { r as extractRuntimeCredentials, s as resolveOAuthApiKey } from "../oauth-B6F43fti.js";
7
7
  import { stream } from "@earendil-works/pi-ai/api/openai-codex-responses";
8
8
  import { stream as stream$1 } from "@earendil-works/pi-ai/api/openai-responses";
9
9
  import { getBuiltinModel } from "@earendil-works/pi-ai/providers/all";
@@ -1,2 +1,2 @@
1
- import { Im as OpenRouterParams, Lm as normalizeOpenRouterModelId, Rm as openrouter } from "../index-C4mY0oKE.js";
1
+ import { Im as OpenRouterParams, Lm as normalizeOpenRouterModelId, Rm as openrouter } from "../index-C0y2tSgk.js";
2
2
  export { OpenRouterParams, normalizeOpenRouterModelId, openrouter };
@@ -1,2 +1,2 @@
1
- import { Am as xai, km as XaiParams } from "../index-C4mY0oKE.js";
1
+ import { Am as xai, km as XaiParams } from "../index-C0y2tSgk.js";
2
2
  export { XaiParams, xai };