@asm-agent/coding-agent 0.8.8 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +2 -7
  3. package/dist/asm-agent-runtime/src/__pycache__/asm_cm_driver.cpython-313.pyc +0 -0
  4. package/dist/asm-agent-runtime/src/__pycache__/phase76_address_head.cpython-313.pyc +0 -0
  5. package/dist/asm-agent-runtime/src/rlm/__init__.py +1 -1
  6. package/dist/asm-agent-runtime/src/rlm/__pycache__/__init__.cpython-312.pyc +0 -0
  7. package/dist/asm-agent-runtime/src/rlm/__pycache__/__init__.cpython-313.pyc +0 -0
  8. package/dist/asm-agent-runtime/src/rlm/__pycache__/bash.cpython-312.pyc +0 -0
  9. package/dist/asm-agent-runtime/src/rlm/__pycache__/bash.cpython-313.pyc +0 -0
  10. package/dist/asm-agent-runtime/src/rlm/__pycache__/mcp.cpython-312.pyc +0 -0
  11. package/dist/asm-agent-runtime/src/rlm/__pycache__/mcp_base.cpython-312.pyc +0 -0
  12. package/dist/asm-agent-runtime/src/rlm/__pycache__/repl.cpython-312.pyc +0 -0
  13. package/dist/asm-agent-runtime/src/rlm/__pycache__/repl.cpython-313.pyc +0 -0
  14. package/dist/asm-agent-runtime/src/rlm/__pycache__/skill.cpython-313.pyc +0 -0
  15. package/dist/asm-agent-runtime/src/rlm/bash.py +94 -31
  16. package/dist/asm-agent-runtime/src/rlm/repl.md +3 -3
  17. package/dist/asm-agent-runtime/src/rlm/repl.py +46 -45
  18. package/dist/asm-agent-runtime/test/__pycache__/test_bash.cpython-313.pyc +0 -0
  19. package/dist/asm-agent-runtime/test/__pycache__/test_repl.cpython-313.pyc +0 -0
  20. package/dist/asm-agent-runtime/test/test_bash.py +74 -19
  21. package/dist/asm-agent-runtime/test/test_repl.py +67 -3
  22. package/dist/asm-agent-runtime/uv.lock +446 -155
  23. package/dist/bundle/{anthropic-OFECORE7.js → anthropic-ZOZNPFXS.js} +2 -2
  24. package/dist/bundle/{azure-openai-responses-AOVTLR6N.js → azure-openai-responses-TDHCU647.js} +2 -2
  25. package/dist/bundle/{bundled-modules-JQCJDEIY.js → bundled-modules-Q2YODC4B.js} +3 -3
  26. package/dist/bundle/{chunk-M67RGWDQ.js → chunk-AMKLNEAK.js} +25 -44
  27. package/dist/bundle/{chunk-7TB7CGPV.js → chunk-FPY7FFJS.js} +978 -1821
  28. package/dist/bundle/{chunk-JC2TT4MZ.js → chunk-G6SC4YYW.js} +4942 -1916
  29. package/dist/bundle/{chunk-A65OM3TZ.js → chunk-QLHAP3FT.js} +1 -1
  30. package/dist/bundle/{cli-main-FNHDQGQD.js → cli-main-277AG2BR.js} +3 -3
  31. package/dist/bundle/cli.js +1 -1
  32. package/dist/bundle/{google-RVJL6RIQ.js → google-Q3AJWUOY.js} +1 -1
  33. package/dist/bundle/{google-vertex-EZTRRRH6.js → google-vertex-2CSNOUBS.js} +1 -1
  34. package/dist/bundle/{main-IYXIIAJN.js → main-TNXWJXIU.js} +3 -3
  35. package/dist/bundle/{mistral-FSGKSMA7.js → mistral-XRYJGLLG.js} +1 -1
  36. package/dist/bundle/{openai-codex-responses-VM5Y564D.js → openai-codex-responses-C6BRO2BN.js} +2 -2
  37. package/dist/bundle/{openai-completions-X46FOQDN.js → openai-completions-DNXCK2JW.js} +3 -3
  38. package/dist/bundle/{openai-responses-SOQNEPBE.js → openai-responses-BEMWVIXE.js} +2 -2
  39. package/dist/config.d.ts +0 -1
  40. package/dist/config.d.ts.map +1 -1
  41. package/dist/config.js +0 -3
  42. package/dist/config.js.map +1 -1
  43. package/dist/core/agent-session-config.d.ts +1 -2
  44. package/dist/core/agent-session-config.d.ts.map +1 -1
  45. package/dist/core/agent-session-config.js +0 -3
  46. package/dist/core/agent-session-config.js.map +1 -1
  47. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  48. package/dist/core/agent-session-runtime.js +0 -8
  49. package/dist/core/agent-session-runtime.js.map +1 -1
  50. package/dist/core/agent-session-services.d.ts +0 -2
  51. package/dist/core/agent-session-services.d.ts.map +1 -1
  52. package/dist/core/agent-session-services.js +0 -22
  53. package/dist/core/agent-session-services.js.map +1 -1
  54. package/dist/core/agent-session.d.ts +8 -1
  55. package/dist/core/agent-session.d.ts.map +1 -1
  56. package/dist/core/agent-session.js +100 -22
  57. package/dist/core/agent-session.js.map +1 -1
  58. package/dist/core/kernel/bootstrap.d.ts.map +1 -1
  59. package/dist/core/kernel/bootstrap.js +1 -1
  60. package/dist/core/kernel/bootstrap.js.map +1 -1
  61. package/dist/core/kernel/repl-manager.d.ts +32 -8
  62. package/dist/core/kernel/repl-manager.d.ts.map +1 -1
  63. package/dist/core/kernel/repl-manager.js +401 -102
  64. package/dist/core/kernel/repl-manager.js.map +1 -1
  65. package/dist/core/kernel/shared.d.ts +11 -7
  66. package/dist/core/kernel/shared.d.ts.map +1 -1
  67. package/dist/core/kernel/shared.js +2 -5
  68. package/dist/core/kernel/shared.js.map +1 -1
  69. package/dist/core/mcp/mcp-manager.d.ts +4 -2
  70. package/dist/core/mcp/mcp-manager.d.ts.map +1 -1
  71. package/dist/core/mcp/mcp-manager.js +9 -7
  72. package/dist/core/mcp/mcp-manager.js.map +1 -1
  73. package/dist/core/prime-inference-auth.d.ts +0 -9
  74. package/dist/core/prime-inference-auth.d.ts.map +1 -1
  75. package/dist/core/prime-inference-auth.js +0 -50
  76. package/dist/core/prime-inference-auth.js.map +1 -1
  77. package/dist/core/provider-display-names.d.ts.map +1 -1
  78. package/dist/core/provider-display-names.js +0 -1
  79. package/dist/core/provider-display-names.js.map +1 -1
  80. package/dist/core/session-manager.d.ts +0 -4
  81. package/dist/core/session-manager.d.ts.map +1 -1
  82. package/dist/core/session-manager.js +0 -22
  83. package/dist/core/session-manager.js.map +1 -1
  84. package/dist/core/settings-manager.d.ts +4 -16
  85. package/dist/core/settings-manager.d.ts.map +1 -1
  86. package/dist/core/settings-manager.js +13 -43
  87. package/dist/core/settings-manager.js.map +1 -1
  88. package/dist/core/slash-commands.d.ts.map +1 -1
  89. package/dist/core/slash-commands.js +0 -5
  90. package/dist/core/slash-commands.js.map +1 -1
  91. package/dist/core/tools/acp-mcp.d.ts +6 -0
  92. package/dist/core/tools/acp-mcp.d.ts.map +1 -0
  93. package/dist/core/tools/acp-mcp.js +86 -0
  94. package/dist/core/tools/acp-mcp.js.map +1 -0
  95. package/dist/core/tools/index.d.ts +1 -0
  96. package/dist/core/tools/index.d.ts.map +1 -1
  97. package/dist/core/tools/index.js +1 -0
  98. package/dist/core/tools/index.js.map +1 -1
  99. package/dist/core/tools/ipython.d.ts.map +1 -1
  100. package/dist/core/tools/ipython.js +9 -4
  101. package/dist/core/tools/ipython.js.map +1 -1
  102. package/dist/main.d.ts.map +1 -1
  103. package/dist/main.js +2 -11
  104. package/dist/main.js.map +1 -1
  105. package/dist/modes/acp/acp-mode.d.ts.map +1 -1
  106. package/dist/modes/acp/acp-mode.js +1 -1
  107. package/dist/modes/acp/acp-mode.js.map +1 -1
  108. package/dist/modes/agent-connection/daemon-agent-connection.d.ts +0 -2
  109. package/dist/modes/agent-connection/daemon-agent-connection.d.ts.map +1 -1
  110. package/dist/modes/agent-connection/daemon-agent-connection.js +0 -2
  111. package/dist/modes/agent-connection/daemon-agent-connection.js.map +1 -1
  112. package/dist/modes/agents-view/agents-view-mode.d.ts.map +1 -1
  113. package/dist/modes/agents-view/agents-view-mode.js +0 -19
  114. package/dist/modes/agents-view/agents-view-mode.js.map +1 -1
  115. package/dist/modes/daemon/daemon-mode.d.ts +1 -10
  116. package/dist/modes/daemon/daemon-mode.d.ts.map +1 -1
  117. package/dist/modes/daemon/daemon-mode.js +134 -274
  118. package/dist/modes/daemon/daemon-mode.js.map +1 -1
  119. package/dist/modes/daemon/daemon-protocol.d.ts +11 -5
  120. package/dist/modes/daemon/daemon-protocol.d.ts.map +1 -1
  121. package/dist/modes/daemon/daemon-protocol.js +6 -8
  122. package/dist/modes/daemon/daemon-protocol.js.map +1 -1
  123. package/dist/modes/daemon/daemon-supervisor.d.ts +0 -3
  124. package/dist/modes/daemon/daemon-supervisor.d.ts.map +1 -1
  125. package/dist/modes/daemon/daemon-supervisor.js +18 -52
  126. package/dist/modes/daemon/daemon-supervisor.js.map +1 -1
  127. package/dist/modes/daemon/daemon-worker-protocol.d.ts +1 -6
  128. package/dist/modes/daemon/daemon-worker-protocol.d.ts.map +1 -1
  129. package/dist/modes/daemon/daemon-worker-protocol.js +0 -2
  130. package/dist/modes/daemon/daemon-worker-protocol.js.map +1 -1
  131. package/dist/modes/interactive/auth-flows.d.ts +0 -2
  132. package/dist/modes/interactive/auth-flows.d.ts.map +1 -1
  133. package/dist/modes/interactive/auth-flows.js +1 -101
  134. package/dist/modes/interactive/auth-flows.js.map +1 -1
  135. package/dist/modes/interactive/components/assistant-message.d.ts +6 -1
  136. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  137. package/dist/modes/interactive/components/assistant-message.js +13 -3
  138. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  139. package/dist/modes/interactive/components/footer.d.ts +1 -1
  140. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  141. package/dist/modes/interactive/components/footer.js +2 -2
  142. package/dist/modes/interactive/components/footer.js.map +1 -1
  143. package/dist/modes/interactive/components/mermaid.d.ts +12 -0
  144. package/dist/modes/interactive/components/mermaid.d.ts.map +1 -0
  145. package/dist/modes/interactive/components/mermaid.js +63 -0
  146. package/dist/modes/interactive/components/mermaid.js.map +1 -0
  147. package/dist/modes/interactive/components/settings-selector.d.ts +3 -1
  148. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  149. package/dist/modes/interactive/components/settings-selector.js +10 -0
  150. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  151. package/dist/modes/interactive/feature-hints.d.ts.map +1 -1
  152. package/dist/modes/interactive/feature-hints.js +0 -4
  153. package/dist/modes/interactive/feature-hints.js.map +1 -1
  154. package/dist/modes/interactive/interactive-mode.d.ts +1 -7
  155. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  156. package/dist/modes/interactive/interactive-mode.js +23 -279
  157. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  158. package/dist/utils/version-check.d.ts.map +1 -1
  159. package/dist/utils/version-check.js +1 -1
  160. package/dist/utils/version-check.js.map +1 -1
  161. package/docs/settings.md +1 -31
  162. package/docs/usage.md +1 -4
  163. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  164. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  165. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  166. package/examples/extensions/sandbox/package-lock.json +2 -2
  167. package/examples/extensions/sandbox/package.json +1 -1
  168. package/examples/extensions/with-deps/package-lock.json +2 -2
  169. package/examples/extensions/with-deps/package.json +1 -1
  170. package/package.json +6 -5
  171. package/dist/core/agent-traces.d.ts +0 -130
  172. package/dist/core/agent-traces.d.ts.map +0 -1
  173. package/dist/core/agent-traces.js +0 -746
  174. package/dist/core/agent-traces.js.map +0 -1
  175. package/dist/core/telemetry.d.ts +0 -92
  176. package/dist/core/telemetry.d.ts.map +0 -1
  177. package/dist/core/telemetry.js +0 -633
  178. package/dist/core/telemetry.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../src/core/kernel/shared.ts"],"names":[],"mappings":"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","sourcesContent":["import { registerSessionResourceCleanup } from \"@asm-agent/ai\";\nimport type { KernelBootstrapProgressHandler, KernelPythonSkill } from \"./bootstrap.js\";\nimport type { RestoreResult, SnapshotResult } from \"./state-snapshot.js\";\n\nexport const DEFAULT_MAX_OUTPUT_CHARS = 65536;\nexport const HOST_REQUEST_DISPOSE_TIMEOUT_MS = 5000;\nexport const KERNEL_SHUTDOWN_TIMEOUT_MS = 5000;\nexport const DEFAULT_SNAPSHOT_DEBOUNCE_MS = 1500;\n// Cap how long a graceful dispose waits on the final snapshot; the debounced\n// on-disk copy is the fallback if this is exceeded.\nexport const SNAPSHOT_DISPOSE_TIMEOUT_MS = 5000;\nexport const SNAPSHOT_EXECUTION_TIMEOUT_MS = 5000;\nexport const KERNEL_ABORT_GRACE_MS = 1000;\nexport const KERNEL_BUSY_REUSE_WAIT_MS = 5000;\nexport const KERNEL_BUSY_INTERRUPT_INTERVAL_MS = 500;\nexport const MAX_LATE_SENT_AGENT_MESSAGE_HANDLERS = 256;\nconst KERNEL_BUSY_AFTER_INTERRUPT_MESSAGE =\n\t\"The Python kernel is still running the previously interrupted cell. Wait and try again, or kill the kernel to start fresh.\";\n\nexport class KernelBusyAfterInterruptError extends Error {\n\tconstructor() {\n\t\tsuper(KERNEL_BUSY_AFTER_INTERRUPT_MESSAGE);\n\t\tthis.name = \"KernelBusyAfterInterruptError\";\n\t}\n}\n\n/**\n * Handles one typed request from Python code running in the kernel.\n * The returned record is sent back verbatim as the reply payload.\n *\n * The unary form is the dispatcher and registration contract; context-aware\n * handlers are declared separately below.\n */\nexport type HostRequestHandler = (payload: Record<string, unknown>) => Promise<Record<string, unknown>>;\n\n/**\n * Per-call authority supplied by the host-request dispatcher.\n * `requestId` is an opaque host-minted correlation token and `isCurrent()`\n * lets an implementation reject work after its authority is revoked.\n */\nexport interface HostRequestContext {\n\treadonly requestId: string;\n\treadonly generation: number;\n\treadonly signal: AbortSignal;\n\tisCurrent(): boolean;\n}\n\nconst hostRequestHandlerBrand = Symbol(\"hostRequestHandler\");\n\n/** A context-aware implementation that must receive dispatcher authority. */\nexport type HostRequestHandlerImplementation = (\n\tpayload: Record<string, unknown>,\n\tcontext: HostRequestContext,\n) => Promise<Record<string, unknown>>;\n\n/** A factory-minted, context-aware host-request handler capability. */\ntype HostRequestHandlerCapability = HostRequestHandlerImplementation & { readonly [hostRequestHandlerBrand]: true };\n\n/** Runtime provenance cannot be recreated by copying the nominal symbol property. */\nconst factoryCreatedHostRequestHandlers = new WeakSet<object>();\n\nfunction assertGenuineHostRequestContext(context: unknown): asserts context is HostRequestContext {\n\tif (\n\t\ttypeof context !== \"object\" ||\n\t\tcontext === null ||\n\t\ttypeof (context as HostRequestContext).requestId !== \"string\" ||\n\t\t!(context as HostRequestContext).requestId ||\n\t\t!Number.isSafeInteger((context as HostRequestContext).generation) ||\n\t\ttypeof (context as HostRequestContext).isCurrent !== \"function\" ||\n\t\ttypeof (context as HostRequestContext).signal !== \"object\" ||\n\t\t(context as HostRequestContext).signal === null ||\n\t\ttypeof (context as HostRequestContext).signal.aborted !== \"boolean\" ||\n\t\ttypeof (context as HostRequestContext).signal.addEventListener !== \"function\"\n\t) {\n\t\tthrow new Error(\"host request context is invalid\");\n\t}\n}\n\n/**\n * Creates a branded wrapper rather than mutating its implementation. Both its\n * generic shape and runtime arity reject unary callbacks before they can run.\n */\nexport function createHostRequestHandler<T extends HostRequestHandlerImplementation>(\n\timplementation: T,\n\t..._unaryRejection: Parameters<T> extends [unknown, unknown, ...unknown[]]\n\t\t? []\n\t\t: [\"host request handlers must accept payload and context\"]\n): HostRequestHandlerCapability {\n\tif (implementation.length < 2) throw new Error(\"host request handlers must accept payload and context\");\n\tconst handler = async (payload: Record<string, unknown>, context: HostRequestContext) => {\n\t\tassertGenuineHostRequestContext(context);\n\t\treturn implementation(payload, context);\n\t};\n\tfactoryCreatedHostRequestHandlers.add(handler);\n\treturn Object.defineProperty(handler, hostRequestHandlerBrand, { value: true }) as HostRequestHandlerCapability;\n}\n\n/** Reject copied-symbol and raw-function forgeries before they observe authenticated payloads. */\nexport function assertHostRequestHandler(value: unknown): asserts value is HostRequestHandlerCapability {\n\tif (\n\t\ttypeof value !== \"function\" ||\n\t\t(value as Partial<HostRequestHandlerCapability>)[hostRequestHandlerBrand] !== true ||\n\t\t!factoryCreatedHostRequestHandlers.has(value)\n\t) {\n\t\tthrow new Error(\"host request handler is not a dispatcher-created capability\");\n\t}\n}\n\n/** Host request handlers keyed by request type (e.g. \"rlm.run\", \"goal.complete\"). */\nexport type HostRequestHandlers = Record<string, HostRequestHandler>;\n\n/** Where and how to persist the kernel's user namespace so it survives resume. */\nexport interface KernelSnapshotConfig {\n\t/** Absolute path for the dill payload. */\n\tpath: string;\n\t/** Absolute path for the JSON manifest written alongside the payload. */\n\tmanifestPath: string;\n\t/** Maximum aggregate snapshot size. Default 256 MiB. */\n\tmaxBytes?: number;\n\t/** Maximum serialized size of one variable. Default 16 MiB. */\n\tmaxVariableBytes?: number;\n\t/** Debounce window for the auto-snapshot after a successful execution. Default 1500 ms. */\n\tdebounceMs?: number;\n}\n\nexport interface KernelManagerOptions {\n\t/** Python interpreter with the kernel runtime available. Defaults to the auto-bootstrapped kernel. */\n\tpython?: string;\n\tcwd?: string;\n\tenv?: Record<string, string>;\n\tsessionId?: string;\n\thostHandlers?: HostRequestHandlers;\n\tpythonSkills?: readonly KernelPythonSkill[];\n\t/** Persist/revive the user namespace across kernel restarts and session resume. */\n\tsnapshot?: KernelSnapshotConfig;\n}\n\nexport interface KernelStartOptions {\n\tonBootstrapProgress?: KernelBootstrapProgressHandler;\n\tsignal?: AbortSignal;\n}\n\nexport interface ExecuteOptions {\n\t/** Aborting interrupts the kernel out-of-band. */\n\tsignal?: AbortSignal;\n\tonStream?: (chunk: string, name: \"stdout\" | \"stderr\") => void;\n\tonLateSentAgentMessage?: (message: KernelSentAgentMessage) => void;\n\t/** Cap stdout / stderr / result at this many characters. Default 65536. */\n\tmaxOutputChars?: number;\n\t/** Synthetic host cell (snapshot/restore/list); excluded from lastCellCode attribution. */\n\tinternal?: boolean;\n}\n\n/** MIME tag the `edit` skill emits diff payloads under. */\nexport const DIFF_DISPLAY_MIME = \"application/vnd.asm-agent.diff+json\";\n\n/** MIME tag the `attach-image` skill emits media payloads under. */\nexport const ATTACHMENT_DISPLAY_MIME = \"application/vnd.asm-agent.attachment+json\";\n\n/** MIME tag the `agent-message` skill emits after sending a message. */\nexport const AGENT_MESSAGE_DISPLAY_MIME = \"application/vnd.asm-agent.agent-message+json\";\n\n/**\n * Hard ceiling on a single attachment's base64 payload, a defensive guard\n * against a runaway direct display emit. The `attach-image` skill caps\n * its own images well under this (see `_MAX_IMAGE_BYTES`), so a skill-produced\n * attachment is never dropped here — only a non-skill emit can hit this.\n */\nexport const MAX_ATTACHMENT_DATA_CHARS = 10_000_000;\n\n/** One file edit, captured from a {@link DIFF_DISPLAY_MIME} display payload. */\nexport interface KernelDiffDisplay {\n\tpath: string;\n\toldStr: string;\n\tnewStr: string;\n\t/** 1-based line where `oldStr` begins in the file, for absolute line numbers. */\n\tstartLine?: number;\n}\n\n/** One media attachment, captured from an {@link ATTACHMENT_DISPLAY_MIME} display payload. */\nexport interface KernelAttachment {\n\tmimeType: string;\n\t/** base64-encoded bytes. */\n\tdata: string;\n\t/** Source path, surfaced to the TUI renderer. */\n\tpath?: string;\n}\n\nexport interface KernelSentAgentMessage {\n\tid: string;\n\tmessage: string;\n\tdeliveryStatus: \"delivered\" | \"queued\";\n\treceiverRole?: \"parent\" | \"sibling\" | \"child\";\n\ttarget: {\n\t\tactiveSessionId: string;\n\t\tsessionId: string;\n\t\tsessionName?: string;\n\t};\n}\n\nexport interface ExecuteResult {\n\tstdout: string;\n\tstderr: string;\n\t/** Text of the cell's trailing expression value, if the cell produced one. */\n\tresult?: string;\n\t/** Diffs emitted via display events, in order. */\n\tdiffs?: KernelDiffDisplay[];\n\t/** Media attachments emitted via display events, in order. */\n\tattachments?: KernelAttachment[];\n\t/** Agent messages sent from this cell, in order. */\n\tsentAgentMessages?: KernelSentAgentMessage[];\n\t/** Output that arrived without this cell's id (user threads, other cells' leftovers, raw fd writes). */\n\tbackgroundOutput?: string;\n\tstatus: \"ok\" | \"error\" | \"aborted\";\n\terror?: { ename: string; evalue: string; traceback: string[] };\n\tdurationMs: number;\n}\n\n/** Parse a {@link DIFF_DISPLAY_MIME} payload, tolerating malformed input. */\nexport function parseDiffDisplay(payload: unknown): KernelDiffDisplay | undefined {\n\tif (!isRecord(payload)) {\n\t\treturn undefined;\n\t}\n\tconst { path, old_str: oldStr, new_str: newStr, start_line: startLine } = payload;\n\tif (typeof path !== \"string\" || typeof oldStr !== \"string\" || typeof newStr !== \"string\") {\n\t\treturn undefined;\n\t}\n\treturn { path, oldStr, newStr, startLine: typeof startLine === \"number\" ? startLine : undefined };\n}\n\n/**\n * Parse an {@link ATTACHMENT_DISPLAY_MIME} payload. Malformed payloads are\n * tolerantly ignored (`undefined`); a well-formed payload exceeding\n * {@link MAX_ATTACHMENT_DATA_CHARS} is reported as `\"oversized\"` so the caller\n * can fail the cell loudly rather than silently dropping the image.\n */\nexport function parseAttachmentDisplay(payload: unknown): KernelAttachment | \"oversized\" | undefined {\n\tif (!isRecord(payload)) {\n\t\treturn undefined;\n\t}\n\tconst { mime_type: mimeType, data, path } = payload;\n\tif (typeof mimeType !== \"string\" || typeof data !== \"string\") {\n\t\treturn undefined;\n\t}\n\tif (data.length > MAX_ATTACHMENT_DATA_CHARS) {\n\t\treturn \"oversized\";\n\t}\n\treturn { mimeType, data, path: typeof path === \"string\" ? path : undefined };\n}\n\nexport function parseSentAgentMessage(payload: unknown): KernelSentAgentMessage | undefined {\n\tif (!isRecord(payload) || !isRecord(payload.target)) {\n\t\treturn undefined;\n\t}\n\tconst { id, message, deliveryStatus, receiverRole, target } = payload;\n\tconst { activeSessionId, sessionId, sessionName } = target;\n\tif (\n\t\ttypeof id !== \"string\" ||\n\t\ttypeof message !== \"string\" ||\n\t\t(deliveryStatus !== \"delivered\" && deliveryStatus !== \"queued\") ||\n\t\ttypeof activeSessionId !== \"string\" ||\n\t\ttypeof sessionId !== \"string\"\n\t) {\n\t\treturn undefined;\n\t}\n\treturn {\n\t\tid,\n\t\tmessage,\n\t\tdeliveryStatus,\n\t\t...(receiverRole === \"parent\" || receiverRole === \"sibling\" || receiverRole === \"child\" ? { receiverRole } : {}),\n\t\ttarget: {\n\t\t\tactiveSessionId,\n\t\t\tsessionId,\n\t\t\t...(typeof sessionName === \"string\" ? { sessionName } : {}),\n\t\t},\n\t};\n}\n\nexport function createKernelStartupAbortError(): Error {\n\treturn new Error(\"Kernel startup aborted\");\n}\n\nexport function raceStartupWithAbort<T>(promise: Promise<T>, signal: AbortSignal | undefined): Promise<T> {\n\tif (!signal) {\n\t\treturn promise;\n\t}\n\tif (signal.aborted) {\n\t\treturn Promise.reject(createKernelStartupAbortError());\n\t}\n\treturn new Promise<T>((resolve, reject) => {\n\t\tlet settled = false;\n\t\tconst cleanup = () => signal.removeEventListener(\"abort\", abort);\n\t\tconst abort = () => {\n\t\t\tif (settled) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsettled = true;\n\t\t\tcleanup();\n\t\t\treject(createKernelStartupAbortError());\n\t\t};\n\t\tsignal.addEventListener(\"abort\", abort, { once: true });\n\t\tpromise.then(\n\t\t\t(value) => {\n\t\t\t\tif (settled) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tsettled = true;\n\t\t\t\tcleanup();\n\t\t\t\tresolve(value);\n\t\t\t},\n\t\t\t(error: unknown) => {\n\t\t\t\tif (settled) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tsettled = true;\n\t\t\t\tcleanup();\n\t\t\t\treject(error);\n\t\t\t},\n\t\t);\n\t});\n}\n\nexport interface Deferred<T> {\n\tpromise: Promise<T>;\n\tresolve: (value: T) => void;\n\treject: (error: Error) => void;\n}\n\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nexport function errorMessage(error: unknown): string {\n\treturn error instanceof Error ? error.message : String(error);\n}\n\nexport function createDeferred<T>(): Deferred<T> {\n\tlet resolve!: (value: T) => void;\n\tlet reject!: (error: Error) => void;\n\tconst promise = new Promise<T>((promiseResolve, promiseReject) => {\n\t\tresolve = promiseResolve;\n\t\treject = promiseReject;\n\t});\n\treturn { promise, resolve, reject };\n}\n\n/** Public surface every kernel client exposes to the provisioner and session layer. */\nexport interface KernelClient {\n\treadonly ownerSessionId: string | undefined;\n\treadonly isRunning: boolean;\n\tstart(options?: KernelStartOptions): Promise<void>;\n\texecute(code: string, opts?: ExecuteOptions): Promise<ExecuteResult>;\n\tshutdown(opts?: { snapshot?: boolean }): Promise<boolean>;\n\trestart(): Promise<void>;\n\tkill(): Promise<void>;\n\tdispose(): Promise<void>;\n\tdisposeSync(): void;\n\tsnapshotState(): Promise<SnapshotResult | null>;\n\tpruneOversizedVariables(): Promise<SnapshotResult | null>;\n\trestoreState(): Promise<RestoreResult | null>;\n\tlistNamespaceNames(signal?: AbortSignal): Promise<string[] | null>;\n}\n\n// One registry serves every client kind; two parallel registries would\n// double-install process signal handlers.\nexport const liveKernels = new Set<KernelClient>();\nlet signalHandlersInstalled = false;\n\nregisterSessionResourceCleanup((sessionId) => {\n\tfor (const k of liveKernels) {\n\t\tif (!sessionId || k.ownerSessionId === sessionId) {\n\t\t\tvoid k.dispose();\n\t\t}\n\t}\n});\n\nexport function installSignalHandlersOnce(): void {\n\tif (signalHandlersInstalled) return;\n\tsignalHandlersInstalled = true;\n\n\tconst asyncShutdown = async (): Promise<void> => {\n\t\t// These paths can await, so flush the namespace snapshot before tearing down.\n\t\tawait Promise.allSettled([...liveKernels].map((k) => k.shutdown({ snapshot: true })));\n\t};\n\n\t// `beforeExit` and signal handlers can await async cleanup. `exit`\n\t// can only do sync work (Node won't run pending microtasks past it),\n\t// so it falls back to `disposeSync()` which kills the child synchronously.\n\tprocess.on(\"beforeExit\", () => {\n\t\tvoid asyncShutdown();\n\t});\n\tprocess.on(\"SIGINT\", () => {\n\t\tvoid asyncShutdown().finally(() => process.exit(130));\n\t});\n\tprocess.on(\"SIGTERM\", () => {\n\t\tvoid asyncShutdown().finally(() => process.exit(143));\n\t});\n\tprocess.on(\"exit\", () => {\n\t\tfor (const k of liveKernels) k.disposeSync();\n\t});\n}\n"]}
1
+ {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../src/core/kernel/shared.ts"],"names":[],"mappings":"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","sourcesContent":["import { registerSessionResourceCleanup } from \"@asm-agent/ai\";\nimport type { KernelBootstrapProgressHandler, KernelPythonSkill } from \"./bootstrap.js\";\nimport type { RestoreResult, SnapshotResult } from \"./state-snapshot.js\";\n\nexport const DEFAULT_MAX_OUTPUT_CHARS = 65536;\nexport const HOST_REQUEST_SHUTDOWN_TIMEOUT_MS = 5000;\nexport const KERNEL_SHUTDOWN_TIMEOUT_MS = 5000;\nexport const DEFAULT_SNAPSHOT_DEBOUNCE_MS = 1500;\nexport const SNAPSHOT_EXECUTION_TIMEOUT_MS = 5000;\nexport const KERNEL_ABORT_GRACE_MS = 1000;\nexport const KERNEL_BUSY_REUSE_WAIT_MS = 5000;\nexport const KERNEL_BUSY_INTERRUPT_INTERVAL_MS = 500;\nexport const MAX_LATE_SENT_AGENT_MESSAGE_HANDLERS = 256;\nconst KERNEL_BUSY_AFTER_INTERRUPT_MESSAGE =\n\t\"The Python kernel is still running the previously interrupted cell. Wait and try again, or kill the kernel to start fresh.\";\n\nexport class KernelBusyAfterInterruptError extends Error {\n\tconstructor() {\n\t\tsuper(KERNEL_BUSY_AFTER_INTERRUPT_MESSAGE);\n\t\tthis.name = \"KernelBusyAfterInterruptError\";\n\t}\n}\n\n/**\n * Handles one typed request from Python code running in the kernel.\n * The returned record is delivered verbatim to the Python caller.\n *\n * The unary form is the dispatcher and registration contract; context-aware\n * handlers are declared separately below.\n */\nexport type HostRequestHandler = (payload: Record<string, unknown>) => Promise<Record<string, unknown>>;\n\n/**\n * Per-call authority supplied by the host-request dispatcher.\n * `requestId` is an opaque host-minted correlation token and `isCurrent()`\n * lets an implementation reject work after its authority is revoked.\n */\nexport interface HostRequestContext {\n\treadonly requestId: string;\n\treadonly generation: number;\n\treadonly signal: AbortSignal;\n\tisCurrent(): boolean;\n}\n\nconst hostRequestHandlerBrand = Symbol(\"hostRequestHandler\");\n\n/** A context-aware implementation that must receive dispatcher authority. */\nexport type HostRequestHandlerImplementation = (\n\tpayload: Record<string, unknown>,\n\tcontext: HostRequestContext,\n) => Promise<Record<string, unknown>>;\n\n/** A factory-minted, context-aware host-request handler capability. */\ntype HostRequestHandlerCapability = HostRequestHandlerImplementation & { readonly [hostRequestHandlerBrand]: true };\n\n/** Runtime provenance cannot be recreated by copying the nominal symbol property. */\nconst factoryCreatedHostRequestHandlers = new WeakSet<object>();\n\nfunction assertGenuineHostRequestContext(context: unknown): asserts context is HostRequestContext {\n\tif (\n\t\ttypeof context !== \"object\" ||\n\t\tcontext === null ||\n\t\ttypeof (context as HostRequestContext).requestId !== \"string\" ||\n\t\t!(context as HostRequestContext).requestId ||\n\t\t!Number.isSafeInteger((context as HostRequestContext).generation) ||\n\t\ttypeof (context as HostRequestContext).isCurrent !== \"function\" ||\n\t\ttypeof (context as HostRequestContext).signal !== \"object\" ||\n\t\t(context as HostRequestContext).signal === null ||\n\t\ttypeof (context as HostRequestContext).signal.aborted !== \"boolean\" ||\n\t\ttypeof (context as HostRequestContext).signal.addEventListener !== \"function\"\n\t) {\n\t\tthrow new Error(\"host request context is invalid\");\n\t}\n}\n\n/**\n * Creates a branded wrapper rather than mutating its implementation. Both its\n * generic shape and runtime arity reject unary callbacks before they can run.\n */\nexport function createHostRequestHandler<T extends HostRequestHandlerImplementation>(\n\timplementation: T,\n\t..._unaryRejection: Parameters<T> extends [unknown, unknown, ...unknown[]]\n\t\t? []\n\t\t: [\"host request handlers must accept payload and context\"]\n): HostRequestHandlerCapability {\n\tif (implementation.length < 2) throw new Error(\"host request handlers must accept payload and context\");\n\tconst handler = async (payload: Record<string, unknown>, context: HostRequestContext) => {\n\t\tassertGenuineHostRequestContext(context);\n\t\treturn implementation(payload, context);\n\t};\n\tfactoryCreatedHostRequestHandlers.add(handler);\n\treturn Object.defineProperty(handler, hostRequestHandlerBrand, { value: true }) as HostRequestHandlerCapability;\n}\n\n/** Reject copied-symbol and raw-function forgeries before they observe authenticated payloads. */\nexport function assertHostRequestHandler(value: unknown): asserts value is HostRequestHandlerCapability {\n\tif (\n\t\ttypeof value !== \"function\" ||\n\t\t(value as Partial<HostRequestHandlerCapability>)[hostRequestHandlerBrand] !== true ||\n\t\t!factoryCreatedHostRequestHandlers.has(value)\n\t) {\n\t\tthrow new Error(\"host request handler is not a dispatcher-created capability\");\n\t}\n}\n\n/** Host request handlers keyed by request type (e.g. \"rlm.run\", \"goal.complete\"). */\nexport type HostRequestHandlers = Record<string, HostRequestHandler>;\n\n/** Where and how to persist the kernel's user namespace so it survives resume. */\nexport interface KernelSnapshotConfig {\n\t/** Absolute path for the dill payload. */\n\tpath: string;\n\t/** Absolute path for the JSON manifest written alongside the payload. */\n\tmanifestPath: string;\n\t/** Maximum aggregate snapshot size. Default 256 MiB. */\n\tmaxBytes?: number;\n\t/** Maximum serialized size of one variable. Default 16 MiB. */\n\tmaxVariableBytes?: number;\n\t/** Debounce window for the auto-snapshot after a successful execution. Default 1500 ms. */\n\tdebounceMs?: number;\n}\n\nexport interface KernelManagerOptions {\n\t/** Python interpreter with the kernel runtime available. Defaults to the auto-bootstrapped kernel. */\n\tpython?: string;\n\tcwd?: string;\n\tenv?: Record<string, string>;\n\tsessionId?: string;\n\thostHandlers?: HostRequestHandlers;\n\tpythonSkills?: readonly KernelPythonSkill[];\n\t/** Persist/revive the user namespace across kernel restarts and session resume. */\n\tsnapshot?: KernelSnapshotConfig;\n\t/** Runtime bootstrap re-run on a protocol-repaired kernel so live handles (rlm, bash, skills) exist again. */\n\tbootstrapCode?: string;\n}\n\nexport interface KernelStartOptions {\n\tonBootstrapProgress?: KernelBootstrapProgressHandler;\n\tsignal?: AbortSignal;\n}\n\nexport interface ExecuteOptions {\n\t/** Aborting interrupts the kernel out-of-band. */\n\tsignal?: AbortSignal;\n\tonStream?: (chunk: string, name: \"stdout\" | \"stderr\") => void;\n\tonLateSentAgentMessage?: (message: KernelSentAgentMessage) => void;\n\t/** Cap stdout / stderr / result at this many characters. Default 65536. */\n\tmaxOutputChars?: number;\n\t/** Synthetic host cell (snapshot/restore/list); excluded from lastCellCode attribution. */\n\tinternal?: boolean;\n\t/** The protocol repair's own restore; exempt from waiting on the repair it belongs to. */\n\tprotocolRepair?: boolean;\n}\n\n/** MIME tag the `edit` skill emits diff payloads under. */\nexport const DIFF_DISPLAY_MIME = \"application/vnd.asm-agent.diff+json\";\n\n/** MIME tag the `attach-image` skill emits media payloads under. */\nexport const ATTACHMENT_DISPLAY_MIME = \"application/vnd.asm-agent.attachment+json\";\n\n/** MIME tag the `agent-message` skill emits after sending a message. */\nexport const AGENT_MESSAGE_DISPLAY_MIME = \"application/vnd.asm-agent.agent-message+json\";\n\n/**\n * Hard ceiling on a single attachment's base64 payload, a defensive guard\n * against a runaway direct display emit. The `attach-image` skill caps\n * its own images well under this (see `_MAX_IMAGE_BYTES`), so a skill-produced\n * attachment is never dropped here — only a non-skill emit can hit this.\n */\nexport const MAX_ATTACHMENT_DATA_CHARS = 10_000_000;\n\n/** One file edit, captured from a {@link DIFF_DISPLAY_MIME} display payload. */\nexport interface KernelDiffDisplay {\n\tpath: string;\n\toldStr: string;\n\tnewStr: string;\n\t/** 1-based line where `oldStr` begins in the file, for absolute line numbers. */\n\tstartLine?: number;\n}\n\n/** One media attachment, captured from an {@link ATTACHMENT_DISPLAY_MIME} display payload. */\nexport interface KernelAttachment {\n\tmimeType: string;\n\t/** base64-encoded bytes. */\n\tdata: string;\n\t/** Source path, surfaced to the TUI renderer. */\n\tpath?: string;\n}\n\nexport interface KernelSentAgentMessage {\n\tid: string;\n\tmessage: string;\n\tdeliveryStatus: \"delivered\" | \"queued\";\n\treceiverRole?: \"parent\" | \"sibling\" | \"child\";\n\ttarget: {\n\t\tactiveSessionId: string;\n\t\tsessionId: string;\n\t\tsessionName?: string;\n\t};\n}\n\nexport interface ExecuteResult {\n\tstdout: string;\n\tstderr: string;\n\t/** Text of the cell's trailing expression value, if the cell produced one. */\n\tresult?: string;\n\t/** Diffs emitted via display events, in order. */\n\tdiffs?: KernelDiffDisplay[];\n\t/** Media attachments emitted via display events, in order. */\n\tattachments?: KernelAttachment[];\n\t/** Agent messages sent from this cell, in order. */\n\tsentAgentMessages?: KernelSentAgentMessage[];\n\t/** Output that arrived without this cell's id (user threads, other cells' leftovers, raw fd writes). */\n\tbackgroundOutput?: string;\n\tstatus: \"ok\" | \"error\" | \"aborted\";\n\terror?: { ename: string; evalue: string; traceback: string[] };\n\tdurationMs: number;\n}\n\n/** Parse a {@link DIFF_DISPLAY_MIME} payload, tolerating malformed input. */\nexport function parseDiffDisplay(payload: unknown): KernelDiffDisplay | undefined {\n\tif (!isRecord(payload)) {\n\t\treturn undefined;\n\t}\n\tconst { path, old_str: oldStr, new_str: newStr, start_line: startLine } = payload;\n\tif (typeof path !== \"string\" || typeof oldStr !== \"string\" || typeof newStr !== \"string\") {\n\t\treturn undefined;\n\t}\n\treturn { path, oldStr, newStr, startLine: typeof startLine === \"number\" ? startLine : undefined };\n}\n\n/**\n * Parse an {@link ATTACHMENT_DISPLAY_MIME} payload. Malformed payloads are\n * tolerantly ignored (`undefined`); a well-formed payload exceeding\n * {@link MAX_ATTACHMENT_DATA_CHARS} is reported as `\"oversized\"` so the caller\n * can fail the cell loudly rather than silently dropping the image.\n */\nexport function parseAttachmentDisplay(payload: unknown): KernelAttachment | \"oversized\" | undefined {\n\tif (!isRecord(payload)) {\n\t\treturn undefined;\n\t}\n\tconst { mime_type: mimeType, data, path } = payload;\n\tif (typeof mimeType !== \"string\" || typeof data !== \"string\") {\n\t\treturn undefined;\n\t}\n\tif (data.length > MAX_ATTACHMENT_DATA_CHARS) {\n\t\treturn \"oversized\";\n\t}\n\treturn { mimeType, data, path: typeof path === \"string\" ? path : undefined };\n}\n\nexport function parseSentAgentMessage(payload: unknown): KernelSentAgentMessage | undefined {\n\tif (!isRecord(payload) || !isRecord(payload.target)) {\n\t\treturn undefined;\n\t}\n\tconst { id, message, deliveryStatus, receiverRole, target } = payload;\n\tconst { activeSessionId, sessionId, sessionName } = target;\n\tif (\n\t\ttypeof id !== \"string\" ||\n\t\ttypeof message !== \"string\" ||\n\t\t(deliveryStatus !== \"delivered\" && deliveryStatus !== \"queued\") ||\n\t\ttypeof activeSessionId !== \"string\" ||\n\t\ttypeof sessionId !== \"string\"\n\t) {\n\t\treturn undefined;\n\t}\n\treturn {\n\t\tid,\n\t\tmessage,\n\t\tdeliveryStatus,\n\t\t...(receiverRole === \"parent\" || receiverRole === \"sibling\" || receiverRole === \"child\" ? { receiverRole } : {}),\n\t\ttarget: {\n\t\t\tactiveSessionId,\n\t\t\tsessionId,\n\t\t\t...(typeof sessionName === \"string\" ? { sessionName } : {}),\n\t\t},\n\t};\n}\n\nexport function createKernelStartupAbortError(): Error {\n\treturn new Error(\"Kernel startup aborted\");\n}\n\nexport function raceStartupWithAbort<T>(promise: Promise<T>, signal: AbortSignal | undefined): Promise<T> {\n\tif (!signal) {\n\t\treturn promise;\n\t}\n\tif (signal.aborted) {\n\t\treturn Promise.reject(createKernelStartupAbortError());\n\t}\n\treturn new Promise<T>((resolve, reject) => {\n\t\tlet settled = false;\n\t\tconst cleanup = () => signal.removeEventListener(\"abort\", abort);\n\t\tconst abort = () => {\n\t\t\tif (settled) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsettled = true;\n\t\t\tcleanup();\n\t\t\treject(createKernelStartupAbortError());\n\t\t};\n\t\tsignal.addEventListener(\"abort\", abort, { once: true });\n\t\tpromise.then(\n\t\t\t(value) => {\n\t\t\t\tif (settled) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tsettled = true;\n\t\t\t\tcleanup();\n\t\t\t\tresolve(value);\n\t\t\t},\n\t\t\t(error: unknown) => {\n\t\t\t\tif (settled) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tsettled = true;\n\t\t\t\tcleanup();\n\t\t\t\treject(error);\n\t\t\t},\n\t\t);\n\t});\n}\n\nexport interface Deferred<T> {\n\tpromise: Promise<T>;\n\tresolve: (value: T) => void;\n\treject: (error: Error) => void;\n}\n\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nexport function errorMessage(error: unknown): string {\n\treturn error instanceof Error ? error.message : String(error);\n}\n\nexport function createDeferred<T>(): Deferred<T> {\n\tlet resolve!: (value: T) => void;\n\tlet reject!: (error: Error) => void;\n\tconst promise = new Promise<T>((promiseResolve, promiseReject) => {\n\t\tresolve = promiseResolve;\n\t\treject = promiseReject;\n\t});\n\treturn { promise, resolve, reject };\n}\n\nexport interface KernelShutdownOptions {\n\tsnapshot?: boolean;\n\tdrainHostRequests?: boolean;\n}\n\n/** Public surface every kernel client exposes to the provisioner and session layer. */\nexport interface KernelClient {\n\treadonly ownerSessionId: string | undefined;\n\treadonly isRunning: boolean;\n\tstart(options?: KernelStartOptions): Promise<void>;\n\texecute(code: string, opts?: ExecuteOptions): Promise<ExecuteResult>;\n\tshutdown(opts?: KernelShutdownOptions): Promise<boolean>;\n\trestart(): Promise<void>;\n\tkill(): Promise<void>;\n\tdisposeSync(): void;\n\tsnapshotState(): Promise<SnapshotResult | null>;\n\tpruneOversizedVariables(): Promise<SnapshotResult | null>;\n\trestoreState(): Promise<RestoreResult | null>;\n\tlistNamespaceNames(signal?: AbortSignal): Promise<string[] | null>;\n}\n\n// One registry serves every client kind; two parallel registries would\n// double-install process signal handlers.\nexport const liveKernels = new Set<KernelClient>();\nlet signalHandlersInstalled = false;\n\nregisterSessionResourceCleanup((sessionId) => {\n\tfor (const k of liveKernels) {\n\t\tif (!sessionId || k.ownerSessionId === sessionId) {\n\t\t\tvoid k.shutdown({ snapshot: true, drainHostRequests: true });\n\t\t}\n\t}\n});\n\nexport function installSignalHandlersOnce(): void {\n\tif (signalHandlersInstalled) return;\n\tsignalHandlersInstalled = true;\n\n\tconst asyncShutdown = async (): Promise<void> => {\n\t\t// These paths can await, so flush the namespace snapshot before tearing down.\n\t\tawait Promise.allSettled([...liveKernels].map((k) => k.shutdown({ snapshot: true })));\n\t};\n\n\t// `beforeExit` and signal handlers can await async cleanup. `exit`\n\t// can only do sync work (Node won't run pending microtasks past it),\n\t// so it falls back to `disposeSync()` which kills the child synchronously.\n\tprocess.on(\"beforeExit\", () => {\n\t\tvoid asyncShutdown();\n\t});\n\tprocess.on(\"SIGINT\", () => {\n\t\tvoid asyncShutdown().finally(() => process.exit(130));\n\t});\n\tprocess.on(\"SIGTERM\", () => {\n\t\tvoid asyncShutdown().finally(() => process.exit(143));\n\t});\n\tprocess.on(\"exit\", () => {\n\t\tfor (const k of liveKernels) k.disposeSync();\n\t});\n}\n"]}
@@ -1,11 +1,8 @@
1
1
  import { registerSessionResourceCleanup } from "@asm-agent/ai";
2
2
  export const DEFAULT_MAX_OUTPUT_CHARS = 65536;
3
- export const HOST_REQUEST_DISPOSE_TIMEOUT_MS = 5000;
3
+ export const HOST_REQUEST_SHUTDOWN_TIMEOUT_MS = 5000;
4
4
  export const KERNEL_SHUTDOWN_TIMEOUT_MS = 5000;
5
5
  export const DEFAULT_SNAPSHOT_DEBOUNCE_MS = 1500;
6
- // Cap how long a graceful dispose waits on the final snapshot; the debounced
7
- // on-disk copy is the fallback if this is exceeded.
8
- export const SNAPSHOT_DISPOSE_TIMEOUT_MS = 5000;
9
6
  export const SNAPSHOT_EXECUTION_TIMEOUT_MS = 5000;
10
7
  export const KERNEL_ABORT_GRACE_MS = 1000;
11
8
  export const KERNEL_BUSY_REUSE_WAIT_MS = 5000;
@@ -186,7 +183,7 @@ let signalHandlersInstalled = false;
186
183
  registerSessionResourceCleanup((sessionId) => {
187
184
  for (const k of liveKernels) {
188
185
  if (!sessionId || k.ownerSessionId === sessionId) {
189
- void k.dispose();
186
+ void k.shutdown({ snapshot: true, drainHostRequests: true });
190
187
  }
191
188
  }
192
189
  });
@@ -1 +1 @@
1
- {"version":3,"file":"shared.js","sourceRoot":"","sources":["../../../src/core/kernel/shared.ts"],"names":[],"mappings":"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","sourcesContent":["import { registerSessionResourceCleanup } from \"@asm-agent/ai\";\nimport type { KernelBootstrapProgressHandler, KernelPythonSkill } from \"./bootstrap.js\";\nimport type { RestoreResult, SnapshotResult } from \"./state-snapshot.js\";\n\nexport const DEFAULT_MAX_OUTPUT_CHARS = 65536;\nexport const HOST_REQUEST_DISPOSE_TIMEOUT_MS = 5000;\nexport const KERNEL_SHUTDOWN_TIMEOUT_MS = 5000;\nexport const DEFAULT_SNAPSHOT_DEBOUNCE_MS = 1500;\n// Cap how long a graceful dispose waits on the final snapshot; the debounced\n// on-disk copy is the fallback if this is exceeded.\nexport const SNAPSHOT_DISPOSE_TIMEOUT_MS = 5000;\nexport const SNAPSHOT_EXECUTION_TIMEOUT_MS = 5000;\nexport const KERNEL_ABORT_GRACE_MS = 1000;\nexport const KERNEL_BUSY_REUSE_WAIT_MS = 5000;\nexport const KERNEL_BUSY_INTERRUPT_INTERVAL_MS = 500;\nexport const MAX_LATE_SENT_AGENT_MESSAGE_HANDLERS = 256;\nconst KERNEL_BUSY_AFTER_INTERRUPT_MESSAGE =\n\t\"The Python kernel is still running the previously interrupted cell. Wait and try again, or kill the kernel to start fresh.\";\n\nexport class KernelBusyAfterInterruptError extends Error {\n\tconstructor() {\n\t\tsuper(KERNEL_BUSY_AFTER_INTERRUPT_MESSAGE);\n\t\tthis.name = \"KernelBusyAfterInterruptError\";\n\t}\n}\n\n/**\n * Handles one typed request from Python code running in the kernel.\n * The returned record is sent back verbatim as the reply payload.\n *\n * The unary form is the dispatcher and registration contract; context-aware\n * handlers are declared separately below.\n */\nexport type HostRequestHandler = (payload: Record<string, unknown>) => Promise<Record<string, unknown>>;\n\n/**\n * Per-call authority supplied by the host-request dispatcher.\n * `requestId` is an opaque host-minted correlation token and `isCurrent()`\n * lets an implementation reject work after its authority is revoked.\n */\nexport interface HostRequestContext {\n\treadonly requestId: string;\n\treadonly generation: number;\n\treadonly signal: AbortSignal;\n\tisCurrent(): boolean;\n}\n\nconst hostRequestHandlerBrand = Symbol(\"hostRequestHandler\");\n\n/** A context-aware implementation that must receive dispatcher authority. */\nexport type HostRequestHandlerImplementation = (\n\tpayload: Record<string, unknown>,\n\tcontext: HostRequestContext,\n) => Promise<Record<string, unknown>>;\n\n/** A factory-minted, context-aware host-request handler capability. */\ntype HostRequestHandlerCapability = HostRequestHandlerImplementation & { readonly [hostRequestHandlerBrand]: true };\n\n/** Runtime provenance cannot be recreated by copying the nominal symbol property. */\nconst factoryCreatedHostRequestHandlers = new WeakSet<object>();\n\nfunction assertGenuineHostRequestContext(context: unknown): asserts context is HostRequestContext {\n\tif (\n\t\ttypeof context !== \"object\" ||\n\t\tcontext === null ||\n\t\ttypeof (context as HostRequestContext).requestId !== \"string\" ||\n\t\t!(context as HostRequestContext).requestId ||\n\t\t!Number.isSafeInteger((context as HostRequestContext).generation) ||\n\t\ttypeof (context as HostRequestContext).isCurrent !== \"function\" ||\n\t\ttypeof (context as HostRequestContext).signal !== \"object\" ||\n\t\t(context as HostRequestContext).signal === null ||\n\t\ttypeof (context as HostRequestContext).signal.aborted !== \"boolean\" ||\n\t\ttypeof (context as HostRequestContext).signal.addEventListener !== \"function\"\n\t) {\n\t\tthrow new Error(\"host request context is invalid\");\n\t}\n}\n\n/**\n * Creates a branded wrapper rather than mutating its implementation. Both its\n * generic shape and runtime arity reject unary callbacks before they can run.\n */\nexport function createHostRequestHandler<T extends HostRequestHandlerImplementation>(\n\timplementation: T,\n\t..._unaryRejection: Parameters<T> extends [unknown, unknown, ...unknown[]]\n\t\t? []\n\t\t: [\"host request handlers must accept payload and context\"]\n): HostRequestHandlerCapability {\n\tif (implementation.length < 2) throw new Error(\"host request handlers must accept payload and context\");\n\tconst handler = async (payload: Record<string, unknown>, context: HostRequestContext) => {\n\t\tassertGenuineHostRequestContext(context);\n\t\treturn implementation(payload, context);\n\t};\n\tfactoryCreatedHostRequestHandlers.add(handler);\n\treturn Object.defineProperty(handler, hostRequestHandlerBrand, { value: true }) as HostRequestHandlerCapability;\n}\n\n/** Reject copied-symbol and raw-function forgeries before they observe authenticated payloads. */\nexport function assertHostRequestHandler(value: unknown): asserts value is HostRequestHandlerCapability {\n\tif (\n\t\ttypeof value !== \"function\" ||\n\t\t(value as Partial<HostRequestHandlerCapability>)[hostRequestHandlerBrand] !== true ||\n\t\t!factoryCreatedHostRequestHandlers.has(value)\n\t) {\n\t\tthrow new Error(\"host request handler is not a dispatcher-created capability\");\n\t}\n}\n\n/** Host request handlers keyed by request type (e.g. \"rlm.run\", \"goal.complete\"). */\nexport type HostRequestHandlers = Record<string, HostRequestHandler>;\n\n/** Where and how to persist the kernel's user namespace so it survives resume. */\nexport interface KernelSnapshotConfig {\n\t/** Absolute path for the dill payload. */\n\tpath: string;\n\t/** Absolute path for the JSON manifest written alongside the payload. */\n\tmanifestPath: string;\n\t/** Maximum aggregate snapshot size. Default 256 MiB. */\n\tmaxBytes?: number;\n\t/** Maximum serialized size of one variable. Default 16 MiB. */\n\tmaxVariableBytes?: number;\n\t/** Debounce window for the auto-snapshot after a successful execution. Default 1500 ms. */\n\tdebounceMs?: number;\n}\n\nexport interface KernelManagerOptions {\n\t/** Python interpreter with the kernel runtime available. Defaults to the auto-bootstrapped kernel. */\n\tpython?: string;\n\tcwd?: string;\n\tenv?: Record<string, string>;\n\tsessionId?: string;\n\thostHandlers?: HostRequestHandlers;\n\tpythonSkills?: readonly KernelPythonSkill[];\n\t/** Persist/revive the user namespace across kernel restarts and session resume. */\n\tsnapshot?: KernelSnapshotConfig;\n}\n\nexport interface KernelStartOptions {\n\tonBootstrapProgress?: KernelBootstrapProgressHandler;\n\tsignal?: AbortSignal;\n}\n\nexport interface ExecuteOptions {\n\t/** Aborting interrupts the kernel out-of-band. */\n\tsignal?: AbortSignal;\n\tonStream?: (chunk: string, name: \"stdout\" | \"stderr\") => void;\n\tonLateSentAgentMessage?: (message: KernelSentAgentMessage) => void;\n\t/** Cap stdout / stderr / result at this many characters. Default 65536. */\n\tmaxOutputChars?: number;\n\t/** Synthetic host cell (snapshot/restore/list); excluded from lastCellCode attribution. */\n\tinternal?: boolean;\n}\n\n/** MIME tag the `edit` skill emits diff payloads under. */\nexport const DIFF_DISPLAY_MIME = \"application/vnd.asm-agent.diff+json\";\n\n/** MIME tag the `attach-image` skill emits media payloads under. */\nexport const ATTACHMENT_DISPLAY_MIME = \"application/vnd.asm-agent.attachment+json\";\n\n/** MIME tag the `agent-message` skill emits after sending a message. */\nexport const AGENT_MESSAGE_DISPLAY_MIME = \"application/vnd.asm-agent.agent-message+json\";\n\n/**\n * Hard ceiling on a single attachment's base64 payload, a defensive guard\n * against a runaway direct display emit. The `attach-image` skill caps\n * its own images well under this (see `_MAX_IMAGE_BYTES`), so a skill-produced\n * attachment is never dropped here — only a non-skill emit can hit this.\n */\nexport const MAX_ATTACHMENT_DATA_CHARS = 10_000_000;\n\n/** One file edit, captured from a {@link DIFF_DISPLAY_MIME} display payload. */\nexport interface KernelDiffDisplay {\n\tpath: string;\n\toldStr: string;\n\tnewStr: string;\n\t/** 1-based line where `oldStr` begins in the file, for absolute line numbers. */\n\tstartLine?: number;\n}\n\n/** One media attachment, captured from an {@link ATTACHMENT_DISPLAY_MIME} display payload. */\nexport interface KernelAttachment {\n\tmimeType: string;\n\t/** base64-encoded bytes. */\n\tdata: string;\n\t/** Source path, surfaced to the TUI renderer. */\n\tpath?: string;\n}\n\nexport interface KernelSentAgentMessage {\n\tid: string;\n\tmessage: string;\n\tdeliveryStatus: \"delivered\" | \"queued\";\n\treceiverRole?: \"parent\" | \"sibling\" | \"child\";\n\ttarget: {\n\t\tactiveSessionId: string;\n\t\tsessionId: string;\n\t\tsessionName?: string;\n\t};\n}\n\nexport interface ExecuteResult {\n\tstdout: string;\n\tstderr: string;\n\t/** Text of the cell's trailing expression value, if the cell produced one. */\n\tresult?: string;\n\t/** Diffs emitted via display events, in order. */\n\tdiffs?: KernelDiffDisplay[];\n\t/** Media attachments emitted via display events, in order. */\n\tattachments?: KernelAttachment[];\n\t/** Agent messages sent from this cell, in order. */\n\tsentAgentMessages?: KernelSentAgentMessage[];\n\t/** Output that arrived without this cell's id (user threads, other cells' leftovers, raw fd writes). */\n\tbackgroundOutput?: string;\n\tstatus: \"ok\" | \"error\" | \"aborted\";\n\terror?: { ename: string; evalue: string; traceback: string[] };\n\tdurationMs: number;\n}\n\n/** Parse a {@link DIFF_DISPLAY_MIME} payload, tolerating malformed input. */\nexport function parseDiffDisplay(payload: unknown): KernelDiffDisplay | undefined {\n\tif (!isRecord(payload)) {\n\t\treturn undefined;\n\t}\n\tconst { path, old_str: oldStr, new_str: newStr, start_line: startLine } = payload;\n\tif (typeof path !== \"string\" || typeof oldStr !== \"string\" || typeof newStr !== \"string\") {\n\t\treturn undefined;\n\t}\n\treturn { path, oldStr, newStr, startLine: typeof startLine === \"number\" ? startLine : undefined };\n}\n\n/**\n * Parse an {@link ATTACHMENT_DISPLAY_MIME} payload. Malformed payloads are\n * tolerantly ignored (`undefined`); a well-formed payload exceeding\n * {@link MAX_ATTACHMENT_DATA_CHARS} is reported as `\"oversized\"` so the caller\n * can fail the cell loudly rather than silently dropping the image.\n */\nexport function parseAttachmentDisplay(payload: unknown): KernelAttachment | \"oversized\" | undefined {\n\tif (!isRecord(payload)) {\n\t\treturn undefined;\n\t}\n\tconst { mime_type: mimeType, data, path } = payload;\n\tif (typeof mimeType !== \"string\" || typeof data !== \"string\") {\n\t\treturn undefined;\n\t}\n\tif (data.length > MAX_ATTACHMENT_DATA_CHARS) {\n\t\treturn \"oversized\";\n\t}\n\treturn { mimeType, data, path: typeof path === \"string\" ? path : undefined };\n}\n\nexport function parseSentAgentMessage(payload: unknown): KernelSentAgentMessage | undefined {\n\tif (!isRecord(payload) || !isRecord(payload.target)) {\n\t\treturn undefined;\n\t}\n\tconst { id, message, deliveryStatus, receiverRole, target } = payload;\n\tconst { activeSessionId, sessionId, sessionName } = target;\n\tif (\n\t\ttypeof id !== \"string\" ||\n\t\ttypeof message !== \"string\" ||\n\t\t(deliveryStatus !== \"delivered\" && deliveryStatus !== \"queued\") ||\n\t\ttypeof activeSessionId !== \"string\" ||\n\t\ttypeof sessionId !== \"string\"\n\t) {\n\t\treturn undefined;\n\t}\n\treturn {\n\t\tid,\n\t\tmessage,\n\t\tdeliveryStatus,\n\t\t...(receiverRole === \"parent\" || receiverRole === \"sibling\" || receiverRole === \"child\" ? { receiverRole } : {}),\n\t\ttarget: {\n\t\t\tactiveSessionId,\n\t\t\tsessionId,\n\t\t\t...(typeof sessionName === \"string\" ? { sessionName } : {}),\n\t\t},\n\t};\n}\n\nexport function createKernelStartupAbortError(): Error {\n\treturn new Error(\"Kernel startup aborted\");\n}\n\nexport function raceStartupWithAbort<T>(promise: Promise<T>, signal: AbortSignal | undefined): Promise<T> {\n\tif (!signal) {\n\t\treturn promise;\n\t}\n\tif (signal.aborted) {\n\t\treturn Promise.reject(createKernelStartupAbortError());\n\t}\n\treturn new Promise<T>((resolve, reject) => {\n\t\tlet settled = false;\n\t\tconst cleanup = () => signal.removeEventListener(\"abort\", abort);\n\t\tconst abort = () => {\n\t\t\tif (settled) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsettled = true;\n\t\t\tcleanup();\n\t\t\treject(createKernelStartupAbortError());\n\t\t};\n\t\tsignal.addEventListener(\"abort\", abort, { once: true });\n\t\tpromise.then(\n\t\t\t(value) => {\n\t\t\t\tif (settled) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tsettled = true;\n\t\t\t\tcleanup();\n\t\t\t\tresolve(value);\n\t\t\t},\n\t\t\t(error: unknown) => {\n\t\t\t\tif (settled) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tsettled = true;\n\t\t\t\tcleanup();\n\t\t\t\treject(error);\n\t\t\t},\n\t\t);\n\t});\n}\n\nexport interface Deferred<T> {\n\tpromise: Promise<T>;\n\tresolve: (value: T) => void;\n\treject: (error: Error) => void;\n}\n\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nexport function errorMessage(error: unknown): string {\n\treturn error instanceof Error ? error.message : String(error);\n}\n\nexport function createDeferred<T>(): Deferred<T> {\n\tlet resolve!: (value: T) => void;\n\tlet reject!: (error: Error) => void;\n\tconst promise = new Promise<T>((promiseResolve, promiseReject) => {\n\t\tresolve = promiseResolve;\n\t\treject = promiseReject;\n\t});\n\treturn { promise, resolve, reject };\n}\n\n/** Public surface every kernel client exposes to the provisioner and session layer. */\nexport interface KernelClient {\n\treadonly ownerSessionId: string | undefined;\n\treadonly isRunning: boolean;\n\tstart(options?: KernelStartOptions): Promise<void>;\n\texecute(code: string, opts?: ExecuteOptions): Promise<ExecuteResult>;\n\tshutdown(opts?: { snapshot?: boolean }): Promise<boolean>;\n\trestart(): Promise<void>;\n\tkill(): Promise<void>;\n\tdispose(): Promise<void>;\n\tdisposeSync(): void;\n\tsnapshotState(): Promise<SnapshotResult | null>;\n\tpruneOversizedVariables(): Promise<SnapshotResult | null>;\n\trestoreState(): Promise<RestoreResult | null>;\n\tlistNamespaceNames(signal?: AbortSignal): Promise<string[] | null>;\n}\n\n// One registry serves every client kind; two parallel registries would\n// double-install process signal handlers.\nexport const liveKernels = new Set<KernelClient>();\nlet signalHandlersInstalled = false;\n\nregisterSessionResourceCleanup((sessionId) => {\n\tfor (const k of liveKernels) {\n\t\tif (!sessionId || k.ownerSessionId === sessionId) {\n\t\t\tvoid k.dispose();\n\t\t}\n\t}\n});\n\nexport function installSignalHandlersOnce(): void {\n\tif (signalHandlersInstalled) return;\n\tsignalHandlersInstalled = true;\n\n\tconst asyncShutdown = async (): Promise<void> => {\n\t\t// These paths can await, so flush the namespace snapshot before tearing down.\n\t\tawait Promise.allSettled([...liveKernels].map((k) => k.shutdown({ snapshot: true })));\n\t};\n\n\t// `beforeExit` and signal handlers can await async cleanup. `exit`\n\t// can only do sync work (Node won't run pending microtasks past it),\n\t// so it falls back to `disposeSync()` which kills the child synchronously.\n\tprocess.on(\"beforeExit\", () => {\n\t\tvoid asyncShutdown();\n\t});\n\tprocess.on(\"SIGINT\", () => {\n\t\tvoid asyncShutdown().finally(() => process.exit(130));\n\t});\n\tprocess.on(\"SIGTERM\", () => {\n\t\tvoid asyncShutdown().finally(() => process.exit(143));\n\t});\n\tprocess.on(\"exit\", () => {\n\t\tfor (const k of liveKernels) k.disposeSync();\n\t});\n}\n"]}
1
+ {"version":3,"file":"shared.js","sourceRoot":"","sources":["../../../src/core/kernel/shared.ts"],"names":[],"mappings":"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","sourcesContent":["import { registerSessionResourceCleanup } from \"@asm-agent/ai\";\nimport type { KernelBootstrapProgressHandler, KernelPythonSkill } from \"./bootstrap.js\";\nimport type { RestoreResult, SnapshotResult } from \"./state-snapshot.js\";\n\nexport const DEFAULT_MAX_OUTPUT_CHARS = 65536;\nexport const HOST_REQUEST_SHUTDOWN_TIMEOUT_MS = 5000;\nexport const KERNEL_SHUTDOWN_TIMEOUT_MS = 5000;\nexport const DEFAULT_SNAPSHOT_DEBOUNCE_MS = 1500;\nexport const SNAPSHOT_EXECUTION_TIMEOUT_MS = 5000;\nexport const KERNEL_ABORT_GRACE_MS = 1000;\nexport const KERNEL_BUSY_REUSE_WAIT_MS = 5000;\nexport const KERNEL_BUSY_INTERRUPT_INTERVAL_MS = 500;\nexport const MAX_LATE_SENT_AGENT_MESSAGE_HANDLERS = 256;\nconst KERNEL_BUSY_AFTER_INTERRUPT_MESSAGE =\n\t\"The Python kernel is still running the previously interrupted cell. Wait and try again, or kill the kernel to start fresh.\";\n\nexport class KernelBusyAfterInterruptError extends Error {\n\tconstructor() {\n\t\tsuper(KERNEL_BUSY_AFTER_INTERRUPT_MESSAGE);\n\t\tthis.name = \"KernelBusyAfterInterruptError\";\n\t}\n}\n\n/**\n * Handles one typed request from Python code running in the kernel.\n * The returned record is delivered verbatim to the Python caller.\n *\n * The unary form is the dispatcher and registration contract; context-aware\n * handlers are declared separately below.\n */\nexport type HostRequestHandler = (payload: Record<string, unknown>) => Promise<Record<string, unknown>>;\n\n/**\n * Per-call authority supplied by the host-request dispatcher.\n * `requestId` is an opaque host-minted correlation token and `isCurrent()`\n * lets an implementation reject work after its authority is revoked.\n */\nexport interface HostRequestContext {\n\treadonly requestId: string;\n\treadonly generation: number;\n\treadonly signal: AbortSignal;\n\tisCurrent(): boolean;\n}\n\nconst hostRequestHandlerBrand = Symbol(\"hostRequestHandler\");\n\n/** A context-aware implementation that must receive dispatcher authority. */\nexport type HostRequestHandlerImplementation = (\n\tpayload: Record<string, unknown>,\n\tcontext: HostRequestContext,\n) => Promise<Record<string, unknown>>;\n\n/** A factory-minted, context-aware host-request handler capability. */\ntype HostRequestHandlerCapability = HostRequestHandlerImplementation & { readonly [hostRequestHandlerBrand]: true };\n\n/** Runtime provenance cannot be recreated by copying the nominal symbol property. */\nconst factoryCreatedHostRequestHandlers = new WeakSet<object>();\n\nfunction assertGenuineHostRequestContext(context: unknown): asserts context is HostRequestContext {\n\tif (\n\t\ttypeof context !== \"object\" ||\n\t\tcontext === null ||\n\t\ttypeof (context as HostRequestContext).requestId !== \"string\" ||\n\t\t!(context as HostRequestContext).requestId ||\n\t\t!Number.isSafeInteger((context as HostRequestContext).generation) ||\n\t\ttypeof (context as HostRequestContext).isCurrent !== \"function\" ||\n\t\ttypeof (context as HostRequestContext).signal !== \"object\" ||\n\t\t(context as HostRequestContext).signal === null ||\n\t\ttypeof (context as HostRequestContext).signal.aborted !== \"boolean\" ||\n\t\ttypeof (context as HostRequestContext).signal.addEventListener !== \"function\"\n\t) {\n\t\tthrow new Error(\"host request context is invalid\");\n\t}\n}\n\n/**\n * Creates a branded wrapper rather than mutating its implementation. Both its\n * generic shape and runtime arity reject unary callbacks before they can run.\n */\nexport function createHostRequestHandler<T extends HostRequestHandlerImplementation>(\n\timplementation: T,\n\t..._unaryRejection: Parameters<T> extends [unknown, unknown, ...unknown[]]\n\t\t? []\n\t\t: [\"host request handlers must accept payload and context\"]\n): HostRequestHandlerCapability {\n\tif (implementation.length < 2) throw new Error(\"host request handlers must accept payload and context\");\n\tconst handler = async (payload: Record<string, unknown>, context: HostRequestContext) => {\n\t\tassertGenuineHostRequestContext(context);\n\t\treturn implementation(payload, context);\n\t};\n\tfactoryCreatedHostRequestHandlers.add(handler);\n\treturn Object.defineProperty(handler, hostRequestHandlerBrand, { value: true }) as HostRequestHandlerCapability;\n}\n\n/** Reject copied-symbol and raw-function forgeries before they observe authenticated payloads. */\nexport function assertHostRequestHandler(value: unknown): asserts value is HostRequestHandlerCapability {\n\tif (\n\t\ttypeof value !== \"function\" ||\n\t\t(value as Partial<HostRequestHandlerCapability>)[hostRequestHandlerBrand] !== true ||\n\t\t!factoryCreatedHostRequestHandlers.has(value)\n\t) {\n\t\tthrow new Error(\"host request handler is not a dispatcher-created capability\");\n\t}\n}\n\n/** Host request handlers keyed by request type (e.g. \"rlm.run\", \"goal.complete\"). */\nexport type HostRequestHandlers = Record<string, HostRequestHandler>;\n\n/** Where and how to persist the kernel's user namespace so it survives resume. */\nexport interface KernelSnapshotConfig {\n\t/** Absolute path for the dill payload. */\n\tpath: string;\n\t/** Absolute path for the JSON manifest written alongside the payload. */\n\tmanifestPath: string;\n\t/** Maximum aggregate snapshot size. Default 256 MiB. */\n\tmaxBytes?: number;\n\t/** Maximum serialized size of one variable. Default 16 MiB. */\n\tmaxVariableBytes?: number;\n\t/** Debounce window for the auto-snapshot after a successful execution. Default 1500 ms. */\n\tdebounceMs?: number;\n}\n\nexport interface KernelManagerOptions {\n\t/** Python interpreter with the kernel runtime available. Defaults to the auto-bootstrapped kernel. */\n\tpython?: string;\n\tcwd?: string;\n\tenv?: Record<string, string>;\n\tsessionId?: string;\n\thostHandlers?: HostRequestHandlers;\n\tpythonSkills?: readonly KernelPythonSkill[];\n\t/** Persist/revive the user namespace across kernel restarts and session resume. */\n\tsnapshot?: KernelSnapshotConfig;\n\t/** Runtime bootstrap re-run on a protocol-repaired kernel so live handles (rlm, bash, skills) exist again. */\n\tbootstrapCode?: string;\n}\n\nexport interface KernelStartOptions {\n\tonBootstrapProgress?: KernelBootstrapProgressHandler;\n\tsignal?: AbortSignal;\n}\n\nexport interface ExecuteOptions {\n\t/** Aborting interrupts the kernel out-of-band. */\n\tsignal?: AbortSignal;\n\tonStream?: (chunk: string, name: \"stdout\" | \"stderr\") => void;\n\tonLateSentAgentMessage?: (message: KernelSentAgentMessage) => void;\n\t/** Cap stdout / stderr / result at this many characters. Default 65536. */\n\tmaxOutputChars?: number;\n\t/** Synthetic host cell (snapshot/restore/list); excluded from lastCellCode attribution. */\n\tinternal?: boolean;\n\t/** The protocol repair's own restore; exempt from waiting on the repair it belongs to. */\n\tprotocolRepair?: boolean;\n}\n\n/** MIME tag the `edit` skill emits diff payloads under. */\nexport const DIFF_DISPLAY_MIME = \"application/vnd.asm-agent.diff+json\";\n\n/** MIME tag the `attach-image` skill emits media payloads under. */\nexport const ATTACHMENT_DISPLAY_MIME = \"application/vnd.asm-agent.attachment+json\";\n\n/** MIME tag the `agent-message` skill emits after sending a message. */\nexport const AGENT_MESSAGE_DISPLAY_MIME = \"application/vnd.asm-agent.agent-message+json\";\n\n/**\n * Hard ceiling on a single attachment's base64 payload, a defensive guard\n * against a runaway direct display emit. The `attach-image` skill caps\n * its own images well under this (see `_MAX_IMAGE_BYTES`), so a skill-produced\n * attachment is never dropped here — only a non-skill emit can hit this.\n */\nexport const MAX_ATTACHMENT_DATA_CHARS = 10_000_000;\n\n/** One file edit, captured from a {@link DIFF_DISPLAY_MIME} display payload. */\nexport interface KernelDiffDisplay {\n\tpath: string;\n\toldStr: string;\n\tnewStr: string;\n\t/** 1-based line where `oldStr` begins in the file, for absolute line numbers. */\n\tstartLine?: number;\n}\n\n/** One media attachment, captured from an {@link ATTACHMENT_DISPLAY_MIME} display payload. */\nexport interface KernelAttachment {\n\tmimeType: string;\n\t/** base64-encoded bytes. */\n\tdata: string;\n\t/** Source path, surfaced to the TUI renderer. */\n\tpath?: string;\n}\n\nexport interface KernelSentAgentMessage {\n\tid: string;\n\tmessage: string;\n\tdeliveryStatus: \"delivered\" | \"queued\";\n\treceiverRole?: \"parent\" | \"sibling\" | \"child\";\n\ttarget: {\n\t\tactiveSessionId: string;\n\t\tsessionId: string;\n\t\tsessionName?: string;\n\t};\n}\n\nexport interface ExecuteResult {\n\tstdout: string;\n\tstderr: string;\n\t/** Text of the cell's trailing expression value, if the cell produced one. */\n\tresult?: string;\n\t/** Diffs emitted via display events, in order. */\n\tdiffs?: KernelDiffDisplay[];\n\t/** Media attachments emitted via display events, in order. */\n\tattachments?: KernelAttachment[];\n\t/** Agent messages sent from this cell, in order. */\n\tsentAgentMessages?: KernelSentAgentMessage[];\n\t/** Output that arrived without this cell's id (user threads, other cells' leftovers, raw fd writes). */\n\tbackgroundOutput?: string;\n\tstatus: \"ok\" | \"error\" | \"aborted\";\n\terror?: { ename: string; evalue: string; traceback: string[] };\n\tdurationMs: number;\n}\n\n/** Parse a {@link DIFF_DISPLAY_MIME} payload, tolerating malformed input. */\nexport function parseDiffDisplay(payload: unknown): KernelDiffDisplay | undefined {\n\tif (!isRecord(payload)) {\n\t\treturn undefined;\n\t}\n\tconst { path, old_str: oldStr, new_str: newStr, start_line: startLine } = payload;\n\tif (typeof path !== \"string\" || typeof oldStr !== \"string\" || typeof newStr !== \"string\") {\n\t\treturn undefined;\n\t}\n\treturn { path, oldStr, newStr, startLine: typeof startLine === \"number\" ? startLine : undefined };\n}\n\n/**\n * Parse an {@link ATTACHMENT_DISPLAY_MIME} payload. Malformed payloads are\n * tolerantly ignored (`undefined`); a well-formed payload exceeding\n * {@link MAX_ATTACHMENT_DATA_CHARS} is reported as `\"oversized\"` so the caller\n * can fail the cell loudly rather than silently dropping the image.\n */\nexport function parseAttachmentDisplay(payload: unknown): KernelAttachment | \"oversized\" | undefined {\n\tif (!isRecord(payload)) {\n\t\treturn undefined;\n\t}\n\tconst { mime_type: mimeType, data, path } = payload;\n\tif (typeof mimeType !== \"string\" || typeof data !== \"string\") {\n\t\treturn undefined;\n\t}\n\tif (data.length > MAX_ATTACHMENT_DATA_CHARS) {\n\t\treturn \"oversized\";\n\t}\n\treturn { mimeType, data, path: typeof path === \"string\" ? path : undefined };\n}\n\nexport function parseSentAgentMessage(payload: unknown): KernelSentAgentMessage | undefined {\n\tif (!isRecord(payload) || !isRecord(payload.target)) {\n\t\treturn undefined;\n\t}\n\tconst { id, message, deliveryStatus, receiverRole, target } = payload;\n\tconst { activeSessionId, sessionId, sessionName } = target;\n\tif (\n\t\ttypeof id !== \"string\" ||\n\t\ttypeof message !== \"string\" ||\n\t\t(deliveryStatus !== \"delivered\" && deliveryStatus !== \"queued\") ||\n\t\ttypeof activeSessionId !== \"string\" ||\n\t\ttypeof sessionId !== \"string\"\n\t) {\n\t\treturn undefined;\n\t}\n\treturn {\n\t\tid,\n\t\tmessage,\n\t\tdeliveryStatus,\n\t\t...(receiverRole === \"parent\" || receiverRole === \"sibling\" || receiverRole === \"child\" ? { receiverRole } : {}),\n\t\ttarget: {\n\t\t\tactiveSessionId,\n\t\t\tsessionId,\n\t\t\t...(typeof sessionName === \"string\" ? { sessionName } : {}),\n\t\t},\n\t};\n}\n\nexport function createKernelStartupAbortError(): Error {\n\treturn new Error(\"Kernel startup aborted\");\n}\n\nexport function raceStartupWithAbort<T>(promise: Promise<T>, signal: AbortSignal | undefined): Promise<T> {\n\tif (!signal) {\n\t\treturn promise;\n\t}\n\tif (signal.aborted) {\n\t\treturn Promise.reject(createKernelStartupAbortError());\n\t}\n\treturn new Promise<T>((resolve, reject) => {\n\t\tlet settled = false;\n\t\tconst cleanup = () => signal.removeEventListener(\"abort\", abort);\n\t\tconst abort = () => {\n\t\t\tif (settled) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsettled = true;\n\t\t\tcleanup();\n\t\t\treject(createKernelStartupAbortError());\n\t\t};\n\t\tsignal.addEventListener(\"abort\", abort, { once: true });\n\t\tpromise.then(\n\t\t\t(value) => {\n\t\t\t\tif (settled) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tsettled = true;\n\t\t\t\tcleanup();\n\t\t\t\tresolve(value);\n\t\t\t},\n\t\t\t(error: unknown) => {\n\t\t\t\tif (settled) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tsettled = true;\n\t\t\t\tcleanup();\n\t\t\t\treject(error);\n\t\t\t},\n\t\t);\n\t});\n}\n\nexport interface Deferred<T> {\n\tpromise: Promise<T>;\n\tresolve: (value: T) => void;\n\treject: (error: Error) => void;\n}\n\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nexport function errorMessage(error: unknown): string {\n\treturn error instanceof Error ? error.message : String(error);\n}\n\nexport function createDeferred<T>(): Deferred<T> {\n\tlet resolve!: (value: T) => void;\n\tlet reject!: (error: Error) => void;\n\tconst promise = new Promise<T>((promiseResolve, promiseReject) => {\n\t\tresolve = promiseResolve;\n\t\treject = promiseReject;\n\t});\n\treturn { promise, resolve, reject };\n}\n\nexport interface KernelShutdownOptions {\n\tsnapshot?: boolean;\n\tdrainHostRequests?: boolean;\n}\n\n/** Public surface every kernel client exposes to the provisioner and session layer. */\nexport interface KernelClient {\n\treadonly ownerSessionId: string | undefined;\n\treadonly isRunning: boolean;\n\tstart(options?: KernelStartOptions): Promise<void>;\n\texecute(code: string, opts?: ExecuteOptions): Promise<ExecuteResult>;\n\tshutdown(opts?: KernelShutdownOptions): Promise<boolean>;\n\trestart(): Promise<void>;\n\tkill(): Promise<void>;\n\tdisposeSync(): void;\n\tsnapshotState(): Promise<SnapshotResult | null>;\n\tpruneOversizedVariables(): Promise<SnapshotResult | null>;\n\trestoreState(): Promise<RestoreResult | null>;\n\tlistNamespaceNames(signal?: AbortSignal): Promise<string[] | null>;\n}\n\n// One registry serves every client kind; two parallel registries would\n// double-install process signal handlers.\nexport const liveKernels = new Set<KernelClient>();\nlet signalHandlersInstalled = false;\n\nregisterSessionResourceCleanup((sessionId) => {\n\tfor (const k of liveKernels) {\n\t\tif (!sessionId || k.ownerSessionId === sessionId) {\n\t\t\tvoid k.shutdown({ snapshot: true, drainHostRequests: true });\n\t\t}\n\t}\n});\n\nexport function installSignalHandlersOnce(): void {\n\tif (signalHandlersInstalled) return;\n\tsignalHandlersInstalled = true;\n\n\tconst asyncShutdown = async (): Promise<void> => {\n\t\t// These paths can await, so flush the namespace snapshot before tearing down.\n\t\tawait Promise.allSettled([...liveKernels].map((k) => k.shutdown({ snapshot: true })));\n\t};\n\n\t// `beforeExit` and signal handlers can await async cleanup. `exit`\n\t// can only do sync work (Node won't run pending microtasks past it),\n\t// so it falls back to `disposeSync()` which kills the child synchronously.\n\tprocess.on(\"beforeExit\", () => {\n\t\tvoid asyncShutdown();\n\t});\n\tprocess.on(\"SIGINT\", () => {\n\t\tvoid asyncShutdown().finally(() => process.exit(130));\n\t});\n\tprocess.on(\"SIGTERM\", () => {\n\t\tvoid asyncShutdown().finally(() => process.exit(143));\n\t});\n\tprocess.on(\"exit\", () => {\n\t\tfor (const k of liveKernels) k.disposeSync();\n\t});\n}\n"]}
@@ -37,8 +37,10 @@ export declare class McpManager {
37
37
  getDisabledBuiltinSkillOverrides(): string[];
38
38
  /** Host-request handlers exposed to the kernel. */
39
39
  hostHandlers(): Record<string, (payload: Record<string, unknown>) => Promise<Record<string, unknown>>>;
40
- /** Enabled persistent and session-scoped servers available through the generic kernel API. */
41
- getEnabledGenericServers(): string[];
40
+ /** Session-scoped servers supplied by the active ACP client. */
41
+ getAcpServers(): AcpMcpServerConfig[];
42
+ /** Enabled user-declared servers available through the generic kernel API. */
43
+ getEnabledPersistentGenericServers(): string[];
42
44
  /** Status for the /mcp list command. */
43
45
  listStatus(): Array<{
44
46
  server: string;
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-manager.d.ts","sourceRoot":"","sources":["../../../src/core/mcp/mcp-manager.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAE7D,MAAM,WAAW,iBAAiB;IACjC,WAAW,EAAE,WAAW,CAAC;IACzB,qFAAmF;IACnF,cAAc,CAAC,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,SAAS,CAAC;IACnE,kFAAkF;IAClF,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/C;AAcD,qBAAa,UAAU;IACtB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;IAC1C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAoD;IACnF,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAoC;IAChE,OAAO,CAAC,YAAY,CAA0C;IAC9D,OAAO,CAAC,UAAU,CAAyC;IAC3D,OAAO,CAAC,UAAU,CAAC,CAAS;IAC5B,qFAAqF;IACrF,OAAO,CAAC,yBAAyB,CAAqB;IAEtD,YAAY,OAAO,EAAE,iBAAiB,EAMrC;IAED,+EAA+E;IAC/E,OAAO,IAAI,IAAI,CAGd;IAED,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAE7C;IAED,iBAAiB,CAAC,OAAO,EAAE,SAAS,kBAAkB,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAqBlF;IAED,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,mBAAmB;IAsB3B,OAAO,CAAC,iBAAiB;IAKzB;;;;OAIG;IACH,qBAAqB,IAAI,IAAI,CAuB5B;IAED,iFAAiF;IACjF,OAAO,CAAC,QAAQ;IAkBhB,gGAAgG;IAChG,gCAAgC,IAAI,MAAM,EAAE,CAS3C;IAED,mDAAmD;IACnD,YAAY,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAwCrG;IAED,8FAA8F;IAC9F,wBAAwB,IAAI,MAAM,EAAE,CAYnC;IAED,wCAAwC;IACxC,UAAU,IAAI,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC,CAO3F;CACD","sourcesContent":["// Host side of MCP integrations. The protocol itself runs Python-side in the kernel; the host\n// only registers OAuth providers, gates integration skills by auth, and serves mcp.* host-requests.\n\nimport {\n\tBUILTIN_MCP_CATALOG,\n\tcreateMcpOAuthProvider,\n\tgetCatalogEntry,\n\tregisterBuiltinMcpOAuthProviders,\n} from \"@asm-agent/ai/mcp\";\nimport { registerOAuthProvider, unregisterOAuthProvider } from \"@asm-agent/ai/oauth\";\nimport type { AuthStorage } from \"../auth-storage.js\";\nimport type { McpServerConfig } from \"../settings-manager.js\";\nimport type { AcpMcpServerConfig } from \"./acp-mcp-types.js\";\n\nexport interface McpManagerOptions {\n\tauthStorage: AuthStorage;\n\t/** Reads the current Settings.mcpServers (name → config). Re-read on refresh(). */\n\tgetUserServers?: () => Record<string, McpServerConfig> | undefined;\n\t/** Start an interactive host-side login for a server. Provided by the UI mode. */\n\tbeginLogin?: (server: string) => Promise<void>;\n}\n\n/** A resolved integration: a catalog/user entry plus its provider id. */\nconst GENERIC_SERVER_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;\n\ninterface ResolvedIntegration {\n\tserver: string;\n\tlabel: string;\n\tconfig: McpServerConfig;\n\tusesOAuth: boolean;\n\t/** True when this came from Settings.mcpServers (may override a catalog name). */\n\tuserDeclared?: boolean;\n}\n\nexport class McpManager {\n\tprivate readonly authStorage: AuthStorage;\n\tprivate readonly getUserServers: () => Record<string, McpServerConfig> | undefined;\n\tprivate readonly beginLogin?: (server: string) => Promise<void>;\n\tprivate integrations = new Map<string, ResolvedIntegration>();\n\tprivate acpServers = new Map<string, AcpMcpServerConfig>();\n\tprivate acpOwnerId?: string;\n\t/** Provider ids we registered for user servers, so refresh can drop removed ones. */\n\tprivate registeredUserProviderIds = new Set<string>();\n\n\tconstructor(options: McpManagerOptions) {\n\t\tthis.authStorage = options.authStorage;\n\t\tthis.getUserServers = options.getUserServers ?? (() => undefined);\n\t\tthis.beginLogin = options.beginLogin;\n\t\tthis.resolveIntegrations();\n\t\tthis.registerProviders();\n\t}\n\n\t/** Re-read settings and re-register providers; call after a session reload. */\n\trefresh(): void {\n\t\tthis.resolveIntegrations();\n\t\tthis.registerProviders();\n\t}\n\n\tcanReleaseAcpServers(ownerId: string): boolean {\n\t\treturn this.acpOwnerId === undefined || this.acpOwnerId === ownerId;\n\t}\n\n\treplaceAcpServers(servers: readonly AcpMcpServerConfig[], ownerId: string): boolean {\n\t\tif (!ownerId) throw new Error(\"ACP MCP owner id is required\");\n\t\tif (servers.length === 0 && this.acpOwnerId !== ownerId) return false;\n\t\tif (servers.length > 0 && this.acpOwnerId && this.acpOwnerId !== ownerId) {\n\t\t\tthrow new Error(\"ACP MCP configuration is owned by another client\");\n\t\t}\n\n\t\tconst next = new Map<string, AcpMcpServerConfig>();\n\t\tfor (const server of servers) {\n\t\t\tif (next.has(server.name)) throw new Error(`Duplicate ACP MCP server: ${server.name}`);\n\t\t\tnext.set(server.name, server);\n\t\t}\n\t\tconst unchanged =\n\t\t\tnext.size === this.acpServers.size &&\n\t\t\tArray.from(next).every(\n\t\t\t\t([name, config]) => JSON.stringify(this.acpServers.get(name)) === JSON.stringify(config),\n\t\t\t);\n\t\tif (unchanged) return false;\n\t\tthis.acpServers = next;\n\t\tthis.acpOwnerId = next.size > 0 ? ownerId : undefined;\n\t\treturn true;\n\t}\n\n\tprivate providerId(server: string): string {\n\t\treturn `mcp:${server}`;\n\t}\n\n\tprivate resolveIntegrations(): void {\n\t\tconst integrations = new Map<string, ResolvedIntegration>();\n\t\tfor (const entry of BUILTIN_MCP_CATALOG) {\n\t\t\tintegrations.set(entry.server, {\n\t\t\t\tserver: entry.server,\n\t\t\t\tlabel: entry.label,\n\t\t\t\tconfig: { type: \"http\", url: entry.url, oauth: true },\n\t\t\t\tusesOAuth: entry.oauth?.kind === \"oauth\",\n\t\t\t});\n\t\t}\n\t\tfor (const [server, config] of Object.entries(this.getUserServers() ?? {})) {\n\t\t\tintegrations.set(server, {\n\t\t\t\tserver,\n\t\t\t\tlabel: server,\n\t\t\t\tconfig,\n\t\t\t\tusesOAuth: config.type === \"http\" && config.oauth === true,\n\t\t\t\tuserDeclared: true,\n\t\t\t});\n\t\t}\n\t\tthis.integrations = integrations;\n\t}\n\n\tprivate registerProviders(): void {\n\t\tregisterBuiltinMcpOAuthProviders();\n\t\tthis.registerUserProviders();\n\t}\n\n\t/**\n\t * Register OAuth providers for user-declared (non-catalog) servers. Public so it\n\t * can run after ModelRegistry.refresh() resets the registry — otherwise custom\n\t * `mcp:<server>` providers vanish on every refresh (e.g. post-login).\n\t */\n\tregisterUserProviders(): void {\n\t\tconst current = new Set<string>();\n\t\tfor (const integration of this.integrations.values()) {\n\t\t\tif (!integration.userDeclared || integration.config.type !== \"http\" || getCatalogEntry(integration.server)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst id = this.providerId(integration.server);\n\t\t\tif (integration.usesOAuth) {\n\t\t\t\tcurrent.add(id);\n\t\t\t\tregisterOAuthProvider(\n\t\t\t\t\tcreateMcpOAuthProvider({\n\t\t\t\t\t\tserver: integration.server,\n\t\t\t\t\t\tlabel: integration.label,\n\t\t\t\t\t\turl: integration.config.url,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\t// Drop providers for user servers removed since the last registration.\n\t\tfor (const id of this.registeredUserProviderIds) {\n\t\t\tif (!current.has(id)) unregisterOAuthProvider(id);\n\t\t}\n\t\tthis.registeredUserProviderIds = current;\n\t}\n\n\t/** True when valid credentials exist for the integration (drives enablement). */\n\tprivate isAuthed(integration: ResolvedIntegration): boolean {\n\t\tif (integration.config.enabled === false) return false;\n\t\tif (integration.userDeclared && getCatalogEntry(integration.server)) return false;\n\t\tif (integration.config.type === \"stdio\") return true;\n\t\tconst { bearerTokenEnvVar } = integration.config;\n\t\tif (!integration.usesOAuth && !bearerTokenEnvVar) return true;\n\t\tif (bearerTokenEnvVar && process.env[bearerTokenEnvVar]?.trim()) {\n\t\t\treturn true;\n\t\t}\n\t\tconst cred = this.authStorage.get(this.providerId(integration.server));\n\t\tif (cred === undefined) return false;\n\t\t// Builtin URLs are code-constant; only user-declared endpoints can be retargeted, so only their\n\t\t// tokens must prove where they belong. Mismatched or unbound tokens require re-login.\n\t\tif (!integration.userDeclared) return true;\n\t\tconst endpoint = (cred as { endpoint?: string }).endpoint;\n\t\treturn typeof endpoint === \"string\" && endpoint === integration.config.url;\n\t}\n\n\t/** `-<server>/SKILL.md` overrides for every built-in integration the user isn't logged into. */\n\tgetDisabledBuiltinSkillOverrides(): string[] {\n\t\tconst overrides: string[] = [];\n\t\tfor (const entry of BUILTIN_MCP_CATALOG) {\n\t\t\tconst integration = this.integrations.get(entry.server);\n\t\t\tif (integration && !this.isAuthed(integration)) {\n\t\t\t\toverrides.push(`-${entry.server}/SKILL.md`);\n\t\t\t}\n\t\t}\n\t\treturn overrides;\n\t}\n\n\t/** Host-request handlers exposed to the kernel. */\n\thostHandlers(): Record<string, (payload: Record<string, unknown>) => Promise<Record<string, unknown>>> {\n\t\tconst handlers: Record<string, (payload: Record<string, unknown>) => Promise<Record<string, unknown>>> = {\n\t\t\t\"mcp.refresh\": async (payload) => {\n\t\t\t\tconst server = String(payload.server ?? \"\");\n\t\t\t\tif (!server) throw new Error(\"mcp.refresh requires a server\");\n\t\t\t\tif (this.acpServers.has(server)) throw new Error(`ACP MCP server ${server} does not use host OAuth`);\n\t\t\t\t// getApiKey refreshes + rewrites auth.json under lock; Python re-reads.\n\t\t\t\t// Surface failure (throw) instead of a false success so the kernel can\n\t\t\t\t// report a refresh error rather than a misleading \"not enabled\".\n\t\t\t\tconst key = await this.authStorage.getApiKey(this.providerId(server));\n\t\t\t\tif (!key) throw new Error(`Could not refresh credentials for ${server}`);\n\t\t\t\treturn {};\n\t\t\t},\n\t\t\t// Resolved config so the kernel skill connects to the same URL the host\n\t\t\t// registered/authenticated (honors a user's mcpServers `url` override).\n\t\t\t\"mcp.config\": async (payload) => {\n\t\t\t\tconst server = String(payload.server ?? \"\");\n\t\t\t\tif (!server) throw new Error(\"mcp.config requires a server\");\n\t\t\t\tconst acpServer = this.acpServers.get(server);\n\t\t\t\tif (acpServer) {\n\t\t\t\t\tconst { name: _name, ...config } = acpServer;\n\t\t\t\t\treturn { ...config, credentialSource: \"acp\" };\n\t\t\t\t}\n\t\t\t\tconst integration = this.integrations.get(server);\n\t\t\t\tif (!integration?.userDeclared || getCatalogEntry(server)) return {};\n\t\t\t\treturn { ...integration.config };\n\t\t\t},\n\t\t};\n\t\t// Only expose begin_login when an interactive login is actually wired, so the\n\t\t// kernel doesn't get a handler whose only behavior is to throw.\n\t\tconst beginLogin = this.beginLogin;\n\t\tif (beginLogin) {\n\t\t\thandlers[\"mcp.begin_login\"] = async (payload) => {\n\t\t\t\tconst server = String(payload.server ?? \"\");\n\t\t\t\tif (!server) throw new Error(\"mcp.begin_login requires a server\");\n\t\t\t\tawait beginLogin(server);\n\t\t\t\treturn {};\n\t\t\t};\n\t\t}\n\t\treturn handlers;\n\t}\n\n\t/** Enabled persistent and session-scoped servers available through the generic kernel API. */\n\tgetEnabledGenericServers(): string[] {\n\t\tconst servers = Array.from(this.integrations.values())\n\t\t\t.filter(\n\t\t\t\t(integration) =>\n\t\t\t\t\tintegration.userDeclared &&\n\t\t\t\t\tGENERIC_SERVER_NAME_PATTERN.test(integration.server) &&\n\t\t\t\t\t!getCatalogEntry(integration.server) &&\n\t\t\t\t\tthis.isAuthed(integration),\n\t\t\t)\n\t\t\t.map((integration) => integration.server);\n\t\tfor (const server of this.acpServers.keys()) servers.push(server);\n\t\treturn [...new Set(servers)].sort((left, right) => left.localeCompare(right));\n\t}\n\n\t/** Status for the /mcp list command. */\n\tlistStatus(): Array<{ server: string; label: string; enabled: boolean; usesOAuth: boolean }> {\n\t\treturn Array.from(this.integrations.values()).map((integration) => ({\n\t\t\tserver: integration.server,\n\t\t\tlabel: integration.label,\n\t\t\tenabled: this.isAuthed(integration),\n\t\t\tusesOAuth: integration.usesOAuth,\n\t\t}));\n\t}\n}\n"]}
1
+ {"version":3,"file":"mcp-manager.d.ts","sourceRoot":"","sources":["../../../src/core/mcp/mcp-manager.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAE7D,MAAM,WAAW,iBAAiB;IACjC,WAAW,EAAE,WAAW,CAAC;IACzB,qFAAmF;IACnF,cAAc,CAAC,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,SAAS,CAAC;IACnE,kFAAkF;IAClF,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/C;AAcD,qBAAa,UAAU;IACtB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;IAC1C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAoD;IACnF,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAoC;IAChE,OAAO,CAAC,YAAY,CAA0C;IAC9D,OAAO,CAAC,UAAU,CAAyC;IAC3D,OAAO,CAAC,UAAU,CAAC,CAAS;IAC5B,qFAAqF;IACrF,OAAO,CAAC,yBAAyB,CAAqB;IAEtD,YAAY,OAAO,EAAE,iBAAiB,EAMrC;IAED,+EAA+E;IAC/E,OAAO,IAAI,IAAI,CAGd;IAED,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAE7C;IAED,iBAAiB,CAAC,OAAO,EAAE,SAAS,kBAAkB,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAqBlF;IAED,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,mBAAmB;IAsB3B,OAAO,CAAC,iBAAiB;IAKzB;;;;OAIG;IACH,qBAAqB,IAAI,IAAI,CAuB5B;IAED,iFAAiF;IACjF,OAAO,CAAC,QAAQ;IAkBhB,gGAAgG;IAChG,gCAAgC,IAAI,MAAM,EAAE,CAS3C;IAED,mDAAmD;IACnD,YAAY,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAwCrG;IAED,gEAAgE;IAChE,aAAa,IAAI,kBAAkB,EAAE,CAEpC;IAED,8EAA8E;IAC9E,kCAAkC,IAAI,MAAM,EAAE,CAW7C;IAED,wCAAwC;IACxC,UAAU,IAAI,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC,CAO3F;CACD","sourcesContent":["// Host side of MCP integrations. The protocol itself runs Python-side in the kernel; the host\n// only registers OAuth providers, gates integration skills by auth, and serves mcp.* host-requests.\n\nimport {\n\tBUILTIN_MCP_CATALOG,\n\tcreateMcpOAuthProvider,\n\tgetCatalogEntry,\n\tregisterBuiltinMcpOAuthProviders,\n} from \"@asm-agent/ai/mcp\";\nimport { registerOAuthProvider, unregisterOAuthProvider } from \"@asm-agent/ai/oauth\";\nimport type { AuthStorage } from \"../auth-storage.js\";\nimport type { McpServerConfig } from \"../settings-manager.js\";\nimport type { AcpMcpServerConfig } from \"./acp-mcp-types.js\";\n\nexport interface McpManagerOptions {\n\tauthStorage: AuthStorage;\n\t/** Reads the current Settings.mcpServers (name → config). Re-read on refresh(). */\n\tgetUserServers?: () => Record<string, McpServerConfig> | undefined;\n\t/** Start an interactive host-side login for a server. Provided by the UI mode. */\n\tbeginLogin?: (server: string) => Promise<void>;\n}\n\n/** A resolved integration: a catalog/user entry plus its provider id. */\nconst GENERIC_SERVER_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;\n\ninterface ResolvedIntegration {\n\tserver: string;\n\tlabel: string;\n\tconfig: McpServerConfig;\n\tusesOAuth: boolean;\n\t/** True when this came from Settings.mcpServers (may override a catalog name). */\n\tuserDeclared?: boolean;\n}\n\nexport class McpManager {\n\tprivate readonly authStorage: AuthStorage;\n\tprivate readonly getUserServers: () => Record<string, McpServerConfig> | undefined;\n\tprivate readonly beginLogin?: (server: string) => Promise<void>;\n\tprivate integrations = new Map<string, ResolvedIntegration>();\n\tprivate acpServers = new Map<string, AcpMcpServerConfig>();\n\tprivate acpOwnerId?: string;\n\t/** Provider ids we registered for user servers, so refresh can drop removed ones. */\n\tprivate registeredUserProviderIds = new Set<string>();\n\n\tconstructor(options: McpManagerOptions) {\n\t\tthis.authStorage = options.authStorage;\n\t\tthis.getUserServers = options.getUserServers ?? (() => undefined);\n\t\tthis.beginLogin = options.beginLogin;\n\t\tthis.resolveIntegrations();\n\t\tthis.registerProviders();\n\t}\n\n\t/** Re-read settings and re-register providers; call after a session reload. */\n\trefresh(): void {\n\t\tthis.resolveIntegrations();\n\t\tthis.registerProviders();\n\t}\n\n\tcanReleaseAcpServers(ownerId: string): boolean {\n\t\treturn this.acpOwnerId === undefined || this.acpOwnerId === ownerId;\n\t}\n\n\treplaceAcpServers(servers: readonly AcpMcpServerConfig[], ownerId: string): boolean {\n\t\tif (!ownerId) throw new Error(\"ACP MCP owner id is required\");\n\t\tif (servers.length === 0 && this.acpOwnerId !== ownerId) return false;\n\t\tif (servers.length > 0 && this.acpOwnerId && this.acpOwnerId !== ownerId) {\n\t\t\tthrow new Error(\"ACP MCP configuration is owned by another client\");\n\t\t}\n\n\t\tconst next = new Map<string, AcpMcpServerConfig>();\n\t\tfor (const server of servers) {\n\t\t\tif (next.has(server.name)) throw new Error(`Duplicate ACP MCP server: ${server.name}`);\n\t\t\tnext.set(server.name, server);\n\t\t}\n\t\tconst unchanged =\n\t\t\tnext.size === this.acpServers.size &&\n\t\t\tArray.from(next).every(\n\t\t\t\t([name, config]) => JSON.stringify(this.acpServers.get(name)) === JSON.stringify(config),\n\t\t\t);\n\t\tif (unchanged) return false;\n\t\tthis.acpServers = next;\n\t\tthis.acpOwnerId = next.size > 0 ? ownerId : undefined;\n\t\treturn true;\n\t}\n\n\tprivate providerId(server: string): string {\n\t\treturn `mcp:${server}`;\n\t}\n\n\tprivate resolveIntegrations(): void {\n\t\tconst integrations = new Map<string, ResolvedIntegration>();\n\t\tfor (const entry of BUILTIN_MCP_CATALOG) {\n\t\t\tintegrations.set(entry.server, {\n\t\t\t\tserver: entry.server,\n\t\t\t\tlabel: entry.label,\n\t\t\t\tconfig: { type: \"http\", url: entry.url, oauth: true },\n\t\t\t\tusesOAuth: entry.oauth?.kind === \"oauth\",\n\t\t\t});\n\t\t}\n\t\tfor (const [server, config] of Object.entries(this.getUserServers() ?? {})) {\n\t\t\tintegrations.set(server, {\n\t\t\t\tserver,\n\t\t\t\tlabel: server,\n\t\t\t\tconfig,\n\t\t\t\tusesOAuth: config.type === \"http\" && config.oauth === true,\n\t\t\t\tuserDeclared: true,\n\t\t\t});\n\t\t}\n\t\tthis.integrations = integrations;\n\t}\n\n\tprivate registerProviders(): void {\n\t\tregisterBuiltinMcpOAuthProviders();\n\t\tthis.registerUserProviders();\n\t}\n\n\t/**\n\t * Register OAuth providers for user-declared (non-catalog) servers. Public so it\n\t * can run after ModelRegistry.refresh() resets the registry — otherwise custom\n\t * `mcp:<server>` providers vanish on every refresh (e.g. post-login).\n\t */\n\tregisterUserProviders(): void {\n\t\tconst current = new Set<string>();\n\t\tfor (const integration of this.integrations.values()) {\n\t\t\tif (!integration.userDeclared || integration.config.type !== \"http\" || getCatalogEntry(integration.server)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst id = this.providerId(integration.server);\n\t\t\tif (integration.usesOAuth) {\n\t\t\t\tcurrent.add(id);\n\t\t\t\tregisterOAuthProvider(\n\t\t\t\t\tcreateMcpOAuthProvider({\n\t\t\t\t\t\tserver: integration.server,\n\t\t\t\t\t\tlabel: integration.label,\n\t\t\t\t\t\turl: integration.config.url,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\t// Drop providers for user servers removed since the last registration.\n\t\tfor (const id of this.registeredUserProviderIds) {\n\t\t\tif (!current.has(id)) unregisterOAuthProvider(id);\n\t\t}\n\t\tthis.registeredUserProviderIds = current;\n\t}\n\n\t/** True when valid credentials exist for the integration (drives enablement). */\n\tprivate isAuthed(integration: ResolvedIntegration): boolean {\n\t\tif (integration.config.enabled === false) return false;\n\t\tif (integration.userDeclared && getCatalogEntry(integration.server)) return false;\n\t\tif (integration.config.type === \"stdio\") return true;\n\t\tconst { bearerTokenEnvVar } = integration.config;\n\t\tif (!integration.usesOAuth && !bearerTokenEnvVar) return true;\n\t\tif (bearerTokenEnvVar && process.env[bearerTokenEnvVar]?.trim()) {\n\t\t\treturn true;\n\t\t}\n\t\tconst cred = this.authStorage.get(this.providerId(integration.server));\n\t\tif (cred === undefined) return false;\n\t\t// Builtin URLs are code-constant; only user-declared endpoints can be retargeted, so only their\n\t\t// tokens must prove where they belong. Mismatched or unbound tokens require re-login.\n\t\tif (!integration.userDeclared) return true;\n\t\tconst endpoint = (cred as { endpoint?: string }).endpoint;\n\t\treturn typeof endpoint === \"string\" && endpoint === integration.config.url;\n\t}\n\n\t/** `-<server>/SKILL.md` overrides for every built-in integration the user isn't logged into. */\n\tgetDisabledBuiltinSkillOverrides(): string[] {\n\t\tconst overrides: string[] = [];\n\t\tfor (const entry of BUILTIN_MCP_CATALOG) {\n\t\t\tconst integration = this.integrations.get(entry.server);\n\t\t\tif (integration && !this.isAuthed(integration)) {\n\t\t\t\toverrides.push(`-${entry.server}/SKILL.md`);\n\t\t\t}\n\t\t}\n\t\treturn overrides;\n\t}\n\n\t/** Host-request handlers exposed to the kernel. */\n\thostHandlers(): Record<string, (payload: Record<string, unknown>) => Promise<Record<string, unknown>>> {\n\t\tconst handlers: Record<string, (payload: Record<string, unknown>) => Promise<Record<string, unknown>>> = {\n\t\t\t\"mcp.refresh\": async (payload) => {\n\t\t\t\tconst server = String(payload.server ?? \"\");\n\t\t\t\tif (!server) throw new Error(\"mcp.refresh requires a server\");\n\t\t\t\tif (this.acpServers.has(server)) throw new Error(`ACP MCP server ${server} does not use host OAuth`);\n\t\t\t\t// getApiKey refreshes + rewrites auth.json under lock; Python re-reads.\n\t\t\t\t// Surface failure (throw) instead of a false success so the kernel can\n\t\t\t\t// report a refresh error rather than a misleading \"not enabled\".\n\t\t\t\tconst key = await this.authStorage.getApiKey(this.providerId(server));\n\t\t\t\tif (!key) throw new Error(`Could not refresh credentials for ${server}`);\n\t\t\t\treturn {};\n\t\t\t},\n\t\t\t// Resolved config so the kernel skill connects to the same URL the host\n\t\t\t// registered/authenticated (honors a user's mcpServers `url` override).\n\t\t\t\"mcp.config\": async (payload) => {\n\t\t\t\tconst server = String(payload.server ?? \"\");\n\t\t\t\tif (!server) throw new Error(\"mcp.config requires a server\");\n\t\t\t\tconst acpServer = this.acpServers.get(server);\n\t\t\t\tif (acpServer) {\n\t\t\t\t\tconst { name: _name, ...config } = acpServer;\n\t\t\t\t\treturn { ...config, credentialSource: \"acp\" };\n\t\t\t\t}\n\t\t\t\tconst integration = this.integrations.get(server);\n\t\t\t\tif (!integration?.userDeclared || getCatalogEntry(server)) return {};\n\t\t\t\treturn { ...integration.config };\n\t\t\t},\n\t\t};\n\t\t// Only expose begin_login when an interactive login is actually wired, so the\n\t\t// kernel doesn't get a handler whose only behavior is to throw.\n\t\tconst beginLogin = this.beginLogin;\n\t\tif (beginLogin) {\n\t\t\thandlers[\"mcp.begin_login\"] = async (payload) => {\n\t\t\t\tconst server = String(payload.server ?? \"\");\n\t\t\t\tif (!server) throw new Error(\"mcp.begin_login requires a server\");\n\t\t\t\tawait beginLogin(server);\n\t\t\t\treturn {};\n\t\t\t};\n\t\t}\n\t\treturn handlers;\n\t}\n\n\t/** Session-scoped servers supplied by the active ACP client. */\n\tgetAcpServers(): AcpMcpServerConfig[] {\n\t\treturn [...this.acpServers.values()];\n\t}\n\n\t/** Enabled user-declared servers available through the generic kernel API. */\n\tgetEnabledPersistentGenericServers(): string[] {\n\t\treturn Array.from(this.integrations.values())\n\t\t\t.filter(\n\t\t\t\t(integration) =>\n\t\t\t\t\tintegration.userDeclared &&\n\t\t\t\t\tGENERIC_SERVER_NAME_PATTERN.test(integration.server) &&\n\t\t\t\t\t!getCatalogEntry(integration.server) &&\n\t\t\t\t\tthis.isAuthed(integration),\n\t\t\t)\n\t\t\t.map((integration) => integration.server)\n\t\t\t.sort((left, right) => left.localeCompare(right));\n\t}\n\n\t/** Status for the /mcp list command. */\n\tlistStatus(): Array<{ server: string; label: string; enabled: boolean; usesOAuth: boolean }> {\n\t\treturn Array.from(this.integrations.values()).map((integration) => ({\n\t\t\tserver: integration.server,\n\t\t\tlabel: integration.label,\n\t\t\tenabled: this.isAuthed(integration),\n\t\t\tusesOAuth: integration.usesOAuth,\n\t\t}));\n\t}\n}\n"]}
@@ -189,17 +189,19 @@ export class McpManager {
189
189
  }
190
190
  return handlers;
191
191
  }
192
- /** Enabled persistent and session-scoped servers available through the generic kernel API. */
193
- getEnabledGenericServers() {
194
- const servers = Array.from(this.integrations.values())
192
+ /** Session-scoped servers supplied by the active ACP client. */
193
+ getAcpServers() {
194
+ return [...this.acpServers.values()];
195
+ }
196
+ /** Enabled user-declared servers available through the generic kernel API. */
197
+ getEnabledPersistentGenericServers() {
198
+ return Array.from(this.integrations.values())
195
199
  .filter((integration) => integration.userDeclared &&
196
200
  GENERIC_SERVER_NAME_PATTERN.test(integration.server) &&
197
201
  !getCatalogEntry(integration.server) &&
198
202
  this.isAuthed(integration))
199
- .map((integration) => integration.server);
200
- for (const server of this.acpServers.keys())
201
- servers.push(server);
202
- return [...new Set(servers)].sort((left, right) => left.localeCompare(right));
203
+ .map((integration) => integration.server)
204
+ .sort((left, right) => left.localeCompare(right));
203
205
  }
204
206
  /** Status for the /mcp list command. */
205
207
  listStatus() {
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-manager.js","sourceRoot":"","sources":["../../../src/core/mcp/mcp-manager.ts"],"names":[],"mappings":"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","sourcesContent":["// Host side of MCP integrations. The protocol itself runs Python-side in the kernel; the host\n// only registers OAuth providers, gates integration skills by auth, and serves mcp.* host-requests.\n\nimport {\n\tBUILTIN_MCP_CATALOG,\n\tcreateMcpOAuthProvider,\n\tgetCatalogEntry,\n\tregisterBuiltinMcpOAuthProviders,\n} from \"@asm-agent/ai/mcp\";\nimport { registerOAuthProvider, unregisterOAuthProvider } from \"@asm-agent/ai/oauth\";\nimport type { AuthStorage } from \"../auth-storage.js\";\nimport type { McpServerConfig } from \"../settings-manager.js\";\nimport type { AcpMcpServerConfig } from \"./acp-mcp-types.js\";\n\nexport interface McpManagerOptions {\n\tauthStorage: AuthStorage;\n\t/** Reads the current Settings.mcpServers (name → config). Re-read on refresh(). */\n\tgetUserServers?: () => Record<string, McpServerConfig> | undefined;\n\t/** Start an interactive host-side login for a server. Provided by the UI mode. */\n\tbeginLogin?: (server: string) => Promise<void>;\n}\n\n/** A resolved integration: a catalog/user entry plus its provider id. */\nconst GENERIC_SERVER_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;\n\ninterface ResolvedIntegration {\n\tserver: string;\n\tlabel: string;\n\tconfig: McpServerConfig;\n\tusesOAuth: boolean;\n\t/** True when this came from Settings.mcpServers (may override a catalog name). */\n\tuserDeclared?: boolean;\n}\n\nexport class McpManager {\n\tprivate readonly authStorage: AuthStorage;\n\tprivate readonly getUserServers: () => Record<string, McpServerConfig> | undefined;\n\tprivate readonly beginLogin?: (server: string) => Promise<void>;\n\tprivate integrations = new Map<string, ResolvedIntegration>();\n\tprivate acpServers = new Map<string, AcpMcpServerConfig>();\n\tprivate acpOwnerId?: string;\n\t/** Provider ids we registered for user servers, so refresh can drop removed ones. */\n\tprivate registeredUserProviderIds = new Set<string>();\n\n\tconstructor(options: McpManagerOptions) {\n\t\tthis.authStorage = options.authStorage;\n\t\tthis.getUserServers = options.getUserServers ?? (() => undefined);\n\t\tthis.beginLogin = options.beginLogin;\n\t\tthis.resolveIntegrations();\n\t\tthis.registerProviders();\n\t}\n\n\t/** Re-read settings and re-register providers; call after a session reload. */\n\trefresh(): void {\n\t\tthis.resolveIntegrations();\n\t\tthis.registerProviders();\n\t}\n\n\tcanReleaseAcpServers(ownerId: string): boolean {\n\t\treturn this.acpOwnerId === undefined || this.acpOwnerId === ownerId;\n\t}\n\n\treplaceAcpServers(servers: readonly AcpMcpServerConfig[], ownerId: string): boolean {\n\t\tif (!ownerId) throw new Error(\"ACP MCP owner id is required\");\n\t\tif (servers.length === 0 && this.acpOwnerId !== ownerId) return false;\n\t\tif (servers.length > 0 && this.acpOwnerId && this.acpOwnerId !== ownerId) {\n\t\t\tthrow new Error(\"ACP MCP configuration is owned by another client\");\n\t\t}\n\n\t\tconst next = new Map<string, AcpMcpServerConfig>();\n\t\tfor (const server of servers) {\n\t\t\tif (next.has(server.name)) throw new Error(`Duplicate ACP MCP server: ${server.name}`);\n\t\t\tnext.set(server.name, server);\n\t\t}\n\t\tconst unchanged =\n\t\t\tnext.size === this.acpServers.size &&\n\t\t\tArray.from(next).every(\n\t\t\t\t([name, config]) => JSON.stringify(this.acpServers.get(name)) === JSON.stringify(config),\n\t\t\t);\n\t\tif (unchanged) return false;\n\t\tthis.acpServers = next;\n\t\tthis.acpOwnerId = next.size > 0 ? ownerId : undefined;\n\t\treturn true;\n\t}\n\n\tprivate providerId(server: string): string {\n\t\treturn `mcp:${server}`;\n\t}\n\n\tprivate resolveIntegrations(): void {\n\t\tconst integrations = new Map<string, ResolvedIntegration>();\n\t\tfor (const entry of BUILTIN_MCP_CATALOG) {\n\t\t\tintegrations.set(entry.server, {\n\t\t\t\tserver: entry.server,\n\t\t\t\tlabel: entry.label,\n\t\t\t\tconfig: { type: \"http\", url: entry.url, oauth: true },\n\t\t\t\tusesOAuth: entry.oauth?.kind === \"oauth\",\n\t\t\t});\n\t\t}\n\t\tfor (const [server, config] of Object.entries(this.getUserServers() ?? {})) {\n\t\t\tintegrations.set(server, {\n\t\t\t\tserver,\n\t\t\t\tlabel: server,\n\t\t\t\tconfig,\n\t\t\t\tusesOAuth: config.type === \"http\" && config.oauth === true,\n\t\t\t\tuserDeclared: true,\n\t\t\t});\n\t\t}\n\t\tthis.integrations = integrations;\n\t}\n\n\tprivate registerProviders(): void {\n\t\tregisterBuiltinMcpOAuthProviders();\n\t\tthis.registerUserProviders();\n\t}\n\n\t/**\n\t * Register OAuth providers for user-declared (non-catalog) servers. Public so it\n\t * can run after ModelRegistry.refresh() resets the registry — otherwise custom\n\t * `mcp:<server>` providers vanish on every refresh (e.g. post-login).\n\t */\n\tregisterUserProviders(): void {\n\t\tconst current = new Set<string>();\n\t\tfor (const integration of this.integrations.values()) {\n\t\t\tif (!integration.userDeclared || integration.config.type !== \"http\" || getCatalogEntry(integration.server)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst id = this.providerId(integration.server);\n\t\t\tif (integration.usesOAuth) {\n\t\t\t\tcurrent.add(id);\n\t\t\t\tregisterOAuthProvider(\n\t\t\t\t\tcreateMcpOAuthProvider({\n\t\t\t\t\t\tserver: integration.server,\n\t\t\t\t\t\tlabel: integration.label,\n\t\t\t\t\t\turl: integration.config.url,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\t// Drop providers for user servers removed since the last registration.\n\t\tfor (const id of this.registeredUserProviderIds) {\n\t\t\tif (!current.has(id)) unregisterOAuthProvider(id);\n\t\t}\n\t\tthis.registeredUserProviderIds = current;\n\t}\n\n\t/** True when valid credentials exist for the integration (drives enablement). */\n\tprivate isAuthed(integration: ResolvedIntegration): boolean {\n\t\tif (integration.config.enabled === false) return false;\n\t\tif (integration.userDeclared && getCatalogEntry(integration.server)) return false;\n\t\tif (integration.config.type === \"stdio\") return true;\n\t\tconst { bearerTokenEnvVar } = integration.config;\n\t\tif (!integration.usesOAuth && !bearerTokenEnvVar) return true;\n\t\tif (bearerTokenEnvVar && process.env[bearerTokenEnvVar]?.trim()) {\n\t\t\treturn true;\n\t\t}\n\t\tconst cred = this.authStorage.get(this.providerId(integration.server));\n\t\tif (cred === undefined) return false;\n\t\t// Builtin URLs are code-constant; only user-declared endpoints can be retargeted, so only their\n\t\t// tokens must prove where they belong. Mismatched or unbound tokens require re-login.\n\t\tif (!integration.userDeclared) return true;\n\t\tconst endpoint = (cred as { endpoint?: string }).endpoint;\n\t\treturn typeof endpoint === \"string\" && endpoint === integration.config.url;\n\t}\n\n\t/** `-<server>/SKILL.md` overrides for every built-in integration the user isn't logged into. */\n\tgetDisabledBuiltinSkillOverrides(): string[] {\n\t\tconst overrides: string[] = [];\n\t\tfor (const entry of BUILTIN_MCP_CATALOG) {\n\t\t\tconst integration = this.integrations.get(entry.server);\n\t\t\tif (integration && !this.isAuthed(integration)) {\n\t\t\t\toverrides.push(`-${entry.server}/SKILL.md`);\n\t\t\t}\n\t\t}\n\t\treturn overrides;\n\t}\n\n\t/** Host-request handlers exposed to the kernel. */\n\thostHandlers(): Record<string, (payload: Record<string, unknown>) => Promise<Record<string, unknown>>> {\n\t\tconst handlers: Record<string, (payload: Record<string, unknown>) => Promise<Record<string, unknown>>> = {\n\t\t\t\"mcp.refresh\": async (payload) => {\n\t\t\t\tconst server = String(payload.server ?? \"\");\n\t\t\t\tif (!server) throw new Error(\"mcp.refresh requires a server\");\n\t\t\t\tif (this.acpServers.has(server)) throw new Error(`ACP MCP server ${server} does not use host OAuth`);\n\t\t\t\t// getApiKey refreshes + rewrites auth.json under lock; Python re-reads.\n\t\t\t\t// Surface failure (throw) instead of a false success so the kernel can\n\t\t\t\t// report a refresh error rather than a misleading \"not enabled\".\n\t\t\t\tconst key = await this.authStorage.getApiKey(this.providerId(server));\n\t\t\t\tif (!key) throw new Error(`Could not refresh credentials for ${server}`);\n\t\t\t\treturn {};\n\t\t\t},\n\t\t\t// Resolved config so the kernel skill connects to the same URL the host\n\t\t\t// registered/authenticated (honors a user's mcpServers `url` override).\n\t\t\t\"mcp.config\": async (payload) => {\n\t\t\t\tconst server = String(payload.server ?? \"\");\n\t\t\t\tif (!server) throw new Error(\"mcp.config requires a server\");\n\t\t\t\tconst acpServer = this.acpServers.get(server);\n\t\t\t\tif (acpServer) {\n\t\t\t\t\tconst { name: _name, ...config } = acpServer;\n\t\t\t\t\treturn { ...config, credentialSource: \"acp\" };\n\t\t\t\t}\n\t\t\t\tconst integration = this.integrations.get(server);\n\t\t\t\tif (!integration?.userDeclared || getCatalogEntry(server)) return {};\n\t\t\t\treturn { ...integration.config };\n\t\t\t},\n\t\t};\n\t\t// Only expose begin_login when an interactive login is actually wired, so the\n\t\t// kernel doesn't get a handler whose only behavior is to throw.\n\t\tconst beginLogin = this.beginLogin;\n\t\tif (beginLogin) {\n\t\t\thandlers[\"mcp.begin_login\"] = async (payload) => {\n\t\t\t\tconst server = String(payload.server ?? \"\");\n\t\t\t\tif (!server) throw new Error(\"mcp.begin_login requires a server\");\n\t\t\t\tawait beginLogin(server);\n\t\t\t\treturn {};\n\t\t\t};\n\t\t}\n\t\treturn handlers;\n\t}\n\n\t/** Enabled persistent and session-scoped servers available through the generic kernel API. */\n\tgetEnabledGenericServers(): string[] {\n\t\tconst servers = Array.from(this.integrations.values())\n\t\t\t.filter(\n\t\t\t\t(integration) =>\n\t\t\t\t\tintegration.userDeclared &&\n\t\t\t\t\tGENERIC_SERVER_NAME_PATTERN.test(integration.server) &&\n\t\t\t\t\t!getCatalogEntry(integration.server) &&\n\t\t\t\t\tthis.isAuthed(integration),\n\t\t\t)\n\t\t\t.map((integration) => integration.server);\n\t\tfor (const server of this.acpServers.keys()) servers.push(server);\n\t\treturn [...new Set(servers)].sort((left, right) => left.localeCompare(right));\n\t}\n\n\t/** Status for the /mcp list command. */\n\tlistStatus(): Array<{ server: string; label: string; enabled: boolean; usesOAuth: boolean }> {\n\t\treturn Array.from(this.integrations.values()).map((integration) => ({\n\t\t\tserver: integration.server,\n\t\t\tlabel: integration.label,\n\t\t\tenabled: this.isAuthed(integration),\n\t\t\tusesOAuth: integration.usesOAuth,\n\t\t}));\n\t}\n}\n"]}
1
+ {"version":3,"file":"mcp-manager.js","sourceRoot":"","sources":["../../../src/core/mcp/mcp-manager.ts"],"names":[],"mappings":"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","sourcesContent":["// Host side of MCP integrations. The protocol itself runs Python-side in the kernel; the host\n// only registers OAuth providers, gates integration skills by auth, and serves mcp.* host-requests.\n\nimport {\n\tBUILTIN_MCP_CATALOG,\n\tcreateMcpOAuthProvider,\n\tgetCatalogEntry,\n\tregisterBuiltinMcpOAuthProviders,\n} from \"@asm-agent/ai/mcp\";\nimport { registerOAuthProvider, unregisterOAuthProvider } from \"@asm-agent/ai/oauth\";\nimport type { AuthStorage } from \"../auth-storage.js\";\nimport type { McpServerConfig } from \"../settings-manager.js\";\nimport type { AcpMcpServerConfig } from \"./acp-mcp-types.js\";\n\nexport interface McpManagerOptions {\n\tauthStorage: AuthStorage;\n\t/** Reads the current Settings.mcpServers (name → config). Re-read on refresh(). */\n\tgetUserServers?: () => Record<string, McpServerConfig> | undefined;\n\t/** Start an interactive host-side login for a server. Provided by the UI mode. */\n\tbeginLogin?: (server: string) => Promise<void>;\n}\n\n/** A resolved integration: a catalog/user entry plus its provider id. */\nconst GENERIC_SERVER_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;\n\ninterface ResolvedIntegration {\n\tserver: string;\n\tlabel: string;\n\tconfig: McpServerConfig;\n\tusesOAuth: boolean;\n\t/** True when this came from Settings.mcpServers (may override a catalog name). */\n\tuserDeclared?: boolean;\n}\n\nexport class McpManager {\n\tprivate readonly authStorage: AuthStorage;\n\tprivate readonly getUserServers: () => Record<string, McpServerConfig> | undefined;\n\tprivate readonly beginLogin?: (server: string) => Promise<void>;\n\tprivate integrations = new Map<string, ResolvedIntegration>();\n\tprivate acpServers = new Map<string, AcpMcpServerConfig>();\n\tprivate acpOwnerId?: string;\n\t/** Provider ids we registered for user servers, so refresh can drop removed ones. */\n\tprivate registeredUserProviderIds = new Set<string>();\n\n\tconstructor(options: McpManagerOptions) {\n\t\tthis.authStorage = options.authStorage;\n\t\tthis.getUserServers = options.getUserServers ?? (() => undefined);\n\t\tthis.beginLogin = options.beginLogin;\n\t\tthis.resolveIntegrations();\n\t\tthis.registerProviders();\n\t}\n\n\t/** Re-read settings and re-register providers; call after a session reload. */\n\trefresh(): void {\n\t\tthis.resolveIntegrations();\n\t\tthis.registerProviders();\n\t}\n\n\tcanReleaseAcpServers(ownerId: string): boolean {\n\t\treturn this.acpOwnerId === undefined || this.acpOwnerId === ownerId;\n\t}\n\n\treplaceAcpServers(servers: readonly AcpMcpServerConfig[], ownerId: string): boolean {\n\t\tif (!ownerId) throw new Error(\"ACP MCP owner id is required\");\n\t\tif (servers.length === 0 && this.acpOwnerId !== ownerId) return false;\n\t\tif (servers.length > 0 && this.acpOwnerId && this.acpOwnerId !== ownerId) {\n\t\t\tthrow new Error(\"ACP MCP configuration is owned by another client\");\n\t\t}\n\n\t\tconst next = new Map<string, AcpMcpServerConfig>();\n\t\tfor (const server of servers) {\n\t\t\tif (next.has(server.name)) throw new Error(`Duplicate ACP MCP server: ${server.name}`);\n\t\t\tnext.set(server.name, server);\n\t\t}\n\t\tconst unchanged =\n\t\t\tnext.size === this.acpServers.size &&\n\t\t\tArray.from(next).every(\n\t\t\t\t([name, config]) => JSON.stringify(this.acpServers.get(name)) === JSON.stringify(config),\n\t\t\t);\n\t\tif (unchanged) return false;\n\t\tthis.acpServers = next;\n\t\tthis.acpOwnerId = next.size > 0 ? ownerId : undefined;\n\t\treturn true;\n\t}\n\n\tprivate providerId(server: string): string {\n\t\treturn `mcp:${server}`;\n\t}\n\n\tprivate resolveIntegrations(): void {\n\t\tconst integrations = new Map<string, ResolvedIntegration>();\n\t\tfor (const entry of BUILTIN_MCP_CATALOG) {\n\t\t\tintegrations.set(entry.server, {\n\t\t\t\tserver: entry.server,\n\t\t\t\tlabel: entry.label,\n\t\t\t\tconfig: { type: \"http\", url: entry.url, oauth: true },\n\t\t\t\tusesOAuth: entry.oauth?.kind === \"oauth\",\n\t\t\t});\n\t\t}\n\t\tfor (const [server, config] of Object.entries(this.getUserServers() ?? {})) {\n\t\t\tintegrations.set(server, {\n\t\t\t\tserver,\n\t\t\t\tlabel: server,\n\t\t\t\tconfig,\n\t\t\t\tusesOAuth: config.type === \"http\" && config.oauth === true,\n\t\t\t\tuserDeclared: true,\n\t\t\t});\n\t\t}\n\t\tthis.integrations = integrations;\n\t}\n\n\tprivate registerProviders(): void {\n\t\tregisterBuiltinMcpOAuthProviders();\n\t\tthis.registerUserProviders();\n\t}\n\n\t/**\n\t * Register OAuth providers for user-declared (non-catalog) servers. Public so it\n\t * can run after ModelRegistry.refresh() resets the registry — otherwise custom\n\t * `mcp:<server>` providers vanish on every refresh (e.g. post-login).\n\t */\n\tregisterUserProviders(): void {\n\t\tconst current = new Set<string>();\n\t\tfor (const integration of this.integrations.values()) {\n\t\t\tif (!integration.userDeclared || integration.config.type !== \"http\" || getCatalogEntry(integration.server)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst id = this.providerId(integration.server);\n\t\t\tif (integration.usesOAuth) {\n\t\t\t\tcurrent.add(id);\n\t\t\t\tregisterOAuthProvider(\n\t\t\t\t\tcreateMcpOAuthProvider({\n\t\t\t\t\t\tserver: integration.server,\n\t\t\t\t\t\tlabel: integration.label,\n\t\t\t\t\t\turl: integration.config.url,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\t// Drop providers for user servers removed since the last registration.\n\t\tfor (const id of this.registeredUserProviderIds) {\n\t\t\tif (!current.has(id)) unregisterOAuthProvider(id);\n\t\t}\n\t\tthis.registeredUserProviderIds = current;\n\t}\n\n\t/** True when valid credentials exist for the integration (drives enablement). */\n\tprivate isAuthed(integration: ResolvedIntegration): boolean {\n\t\tif (integration.config.enabled === false) return false;\n\t\tif (integration.userDeclared && getCatalogEntry(integration.server)) return false;\n\t\tif (integration.config.type === \"stdio\") return true;\n\t\tconst { bearerTokenEnvVar } = integration.config;\n\t\tif (!integration.usesOAuth && !bearerTokenEnvVar) return true;\n\t\tif (bearerTokenEnvVar && process.env[bearerTokenEnvVar]?.trim()) {\n\t\t\treturn true;\n\t\t}\n\t\tconst cred = this.authStorage.get(this.providerId(integration.server));\n\t\tif (cred === undefined) return false;\n\t\t// Builtin URLs are code-constant; only user-declared endpoints can be retargeted, so only their\n\t\t// tokens must prove where they belong. Mismatched or unbound tokens require re-login.\n\t\tif (!integration.userDeclared) return true;\n\t\tconst endpoint = (cred as { endpoint?: string }).endpoint;\n\t\treturn typeof endpoint === \"string\" && endpoint === integration.config.url;\n\t}\n\n\t/** `-<server>/SKILL.md` overrides for every built-in integration the user isn't logged into. */\n\tgetDisabledBuiltinSkillOverrides(): string[] {\n\t\tconst overrides: string[] = [];\n\t\tfor (const entry of BUILTIN_MCP_CATALOG) {\n\t\t\tconst integration = this.integrations.get(entry.server);\n\t\t\tif (integration && !this.isAuthed(integration)) {\n\t\t\t\toverrides.push(`-${entry.server}/SKILL.md`);\n\t\t\t}\n\t\t}\n\t\treturn overrides;\n\t}\n\n\t/** Host-request handlers exposed to the kernel. */\n\thostHandlers(): Record<string, (payload: Record<string, unknown>) => Promise<Record<string, unknown>>> {\n\t\tconst handlers: Record<string, (payload: Record<string, unknown>) => Promise<Record<string, unknown>>> = {\n\t\t\t\"mcp.refresh\": async (payload) => {\n\t\t\t\tconst server = String(payload.server ?? \"\");\n\t\t\t\tif (!server) throw new Error(\"mcp.refresh requires a server\");\n\t\t\t\tif (this.acpServers.has(server)) throw new Error(`ACP MCP server ${server} does not use host OAuth`);\n\t\t\t\t// getApiKey refreshes + rewrites auth.json under lock; Python re-reads.\n\t\t\t\t// Surface failure (throw) instead of a false success so the kernel can\n\t\t\t\t// report a refresh error rather than a misleading \"not enabled\".\n\t\t\t\tconst key = await this.authStorage.getApiKey(this.providerId(server));\n\t\t\t\tif (!key) throw new Error(`Could not refresh credentials for ${server}`);\n\t\t\t\treturn {};\n\t\t\t},\n\t\t\t// Resolved config so the kernel skill connects to the same URL the host\n\t\t\t// registered/authenticated (honors a user's mcpServers `url` override).\n\t\t\t\"mcp.config\": async (payload) => {\n\t\t\t\tconst server = String(payload.server ?? \"\");\n\t\t\t\tif (!server) throw new Error(\"mcp.config requires a server\");\n\t\t\t\tconst acpServer = this.acpServers.get(server);\n\t\t\t\tif (acpServer) {\n\t\t\t\t\tconst { name: _name, ...config } = acpServer;\n\t\t\t\t\treturn { ...config, credentialSource: \"acp\" };\n\t\t\t\t}\n\t\t\t\tconst integration = this.integrations.get(server);\n\t\t\t\tif (!integration?.userDeclared || getCatalogEntry(server)) return {};\n\t\t\t\treturn { ...integration.config };\n\t\t\t},\n\t\t};\n\t\t// Only expose begin_login when an interactive login is actually wired, so the\n\t\t// kernel doesn't get a handler whose only behavior is to throw.\n\t\tconst beginLogin = this.beginLogin;\n\t\tif (beginLogin) {\n\t\t\thandlers[\"mcp.begin_login\"] = async (payload) => {\n\t\t\t\tconst server = String(payload.server ?? \"\");\n\t\t\t\tif (!server) throw new Error(\"mcp.begin_login requires a server\");\n\t\t\t\tawait beginLogin(server);\n\t\t\t\treturn {};\n\t\t\t};\n\t\t}\n\t\treturn handlers;\n\t}\n\n\t/** Session-scoped servers supplied by the active ACP client. */\n\tgetAcpServers(): AcpMcpServerConfig[] {\n\t\treturn [...this.acpServers.values()];\n\t}\n\n\t/** Enabled user-declared servers available through the generic kernel API. */\n\tgetEnabledPersistentGenericServers(): string[] {\n\t\treturn Array.from(this.integrations.values())\n\t\t\t.filter(\n\t\t\t\t(integration) =>\n\t\t\t\t\tintegration.userDeclared &&\n\t\t\t\t\tGENERIC_SERVER_NAME_PATTERN.test(integration.server) &&\n\t\t\t\t\t!getCatalogEntry(integration.server) &&\n\t\t\t\t\tthis.isAuthed(integration),\n\t\t\t)\n\t\t\t.map((integration) => integration.server)\n\t\t\t.sort((left, right) => left.localeCompare(right));\n\t}\n\n\t/** Status for the /mcp list command. */\n\tlistStatus(): Array<{ server: string; label: string; enabled: boolean; usesOAuth: boolean }> {\n\t\treturn Array.from(this.integrations.values()).map((integration) => ({\n\t\t\tserver: integration.server,\n\t\t\tlabel: integration.label,\n\t\t\tenabled: this.isAuthed(integration),\n\t\t\tusesOAuth: integration.usesOAuth,\n\t\t}));\n\t}\n}\n"]}
@@ -1,8 +1,6 @@
1
1
  import type { OAuthAuthInfo } from "@asm-agent/ai";
2
2
  export declare const PRIME_INFERENCE_PROVIDER_ID = "prime-inference";
3
3
  export declare const PRIME_INFERENCE_PROVIDER_NAME = "Prime Inference";
4
- export declare const ASM_AGENT_TRACES_PROVIDER_ID = "asm-agent-traces";
5
- export declare const ASM_AGENT_TRACES_PROVIDER_NAME = "ASM Agent Traces";
6
4
  export type PrimeInferenceAuthSource = "prime-cli" | "browser";
7
5
  export type PrimeInferenceLoginResult = {
8
6
  apiKey: string;
@@ -49,7 +47,6 @@ export declare function loadPrimeCliConfig(configPath?: string): PrimeCliConfig;
49
47
  export declare function savePrimeCliApiKey(apiKey: string, configPath?: string): PrimeCliConfig;
50
48
  export declare function clearPrimeCliCredentials(configPath?: string): PrimeCliConfig;
51
49
  export declare function savePrimeCliTeamSelection(team: PrimeTeam | null, configPath?: string): PrimeCliConfig;
52
- export declare function resolveAsmAgentTracesBaseUrl(baseUrl?: string): string;
53
50
  export declare function fetchPrimeTeams(apiKey: string, baseUrl: string, options?: {
54
51
  fetchFn?: typeof fetch;
55
52
  requestTimeoutMs?: number;
@@ -60,11 +57,5 @@ export declare function checkPrimeInferenceAccess(apiKey: string, baseUrl: strin
60
57
  requestTimeoutMs?: number;
61
58
  signal?: AbortSignal;
62
59
  }): Promise<PrimeInferenceAccessResult>;
63
- export declare function checkAsmAgentTracesAccess(apiKey: string, baseUrl: string, options?: {
64
- fetchFn?: typeof fetch;
65
- requestTimeoutMs?: number;
66
- signal?: AbortSignal;
67
- }): Promise<PrimeInferenceAccessResult>;
68
60
  export declare function loginPrimeInference(callbacks: PrimeInferenceLoginCallbacks, options?: PrimeInferenceLoginOptions): Promise<PrimeInferenceLoginResult>;
69
- export declare function loginAsmAgentTraces(callbacks: PrimeInferenceLoginCallbacks, options?: PrimeInferenceLoginOptions): Promise<PrimeInferenceLoginResult>;
70
61
  //# sourceMappingURL=prime-inference-auth.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"prime-inference-auth.d.ts","sourceRoot":"","sources":["../../src/core/prime-inference-auth.ts"],"names":[],"mappings":"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","sourcesContent":["import { Buffer } from \"node:buffer\";\nimport { constants, generateKeyPairSync, privateDecrypt } from \"node:crypto\";\nimport {\n\tchmodSync,\n\tcloseSync,\n\texistsSync,\n\tmkdirSync,\n\topenSync,\n\treadFileSync,\n\trenameSync,\n\trmSync,\n\twriteFileSync,\n} from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { basename, dirname, join } from \"node:path\";\nimport type { OAuthAuthInfo } from \"@asm-agent/ai\";\n\nexport const PRIME_INFERENCE_PROVIDER_ID = \"prime-inference\";\nexport const PRIME_INFERENCE_PROVIDER_NAME = \"Prime Inference\";\nexport const ASM_AGENT_TRACES_PROVIDER_ID = \"asm-agent-traces\";\nexport const ASM_AGENT_TRACES_PROVIDER_NAME = \"ASM Agent Traces\";\n\nconst DEFAULT_PRIME_API_BASE_URL = \"https://api.primeintellect.ai\";\nconst DEFAULT_PRIME_FRONTEND_URL = \"https://app.primeintellect.ai\";\nconst DEFAULT_PRIME_INFERENCE_URL = \"https://api.pinference.ai/api/v1\";\nconst DEFAULT_REQUEST_TIMEOUT_MS = 30_000;\nconst DEFAULT_POLL_INTERVAL_MS = 5_000;\n\nexport type PrimeInferenceAuthSource = \"prime-cli\" | \"browser\";\n\nexport type PrimeInferenceLoginResult = {\n\tapiKey: string;\n\tsource: PrimeInferenceAuthSource;\n};\n\nexport type PrimeCliConfig = {\n\tapiKey?: string;\n\tbaseUrl: string;\n\tfrontendUrl: string;\n\tinferenceUrl: string;\n\tpath: string;\n\tteamId?: string;\n\tteamName?: string;\n\tteamRole?: string;\n\tteamIdFromEnv: boolean;\n};\n\nexport type PrimeInferenceLoginCallbacks = {\n\tonAuth: (info: OAuthAuthInfo) => void;\n\tonProgress?: (message: string) => void;\n\tsignal?: AbortSignal;\n};\n\nexport type PrimeInferenceLoginOptions = {\n\tconfigPath?: string;\n\tfetchFn?: typeof fetch;\n\tpollIntervalMs?: number;\n\trequestTimeoutMs?: number;\n};\n\ntype PrimeChallengeConfig = {\n\tbaseUrl: string;\n\tfrontendUrl: string;\n};\n\ntype PrimeChallengeResponse = {\n\tchallenge: string;\n\tstatusAuthToken: string;\n};\n\nexport type PrimeInferenceAccessResult =\n\t| { ok: true }\n\t| {\n\t\t\tok: false;\n\t\t\tstatus?: number;\n\t\t\tmessage: string;\n\t };\n\ntype PrimeAccessScope = \"inference\" | \"agent_traces\";\n\nexport type PrimeTeam = {\n\tteamId: string;\n\tname: string;\n\tslug?: string;\n\trole?: string;\n\tcreatedAt?: string;\n};\n\nfunction defaultPrimeCliConfigPath(): string {\n\treturn join(homedir(), \".prime\", \"config.json\");\n}\n\nexport function getPrimeCliConfigPath(configPath?: string): string {\n\treturn configPath ?? defaultPrimeCliConfigPath();\n}\n\nfunction normalizeBaseUrl(value: string | undefined): string {\n\treturn (value?.trim() || DEFAULT_PRIME_API_BASE_URL).replace(/\\/+$/, \"\").replace(/\\/api\\/v1$/, \"\");\n}\n\nfunction normalizeUrl(value: string | undefined, fallback: string): string {\n\treturn (value || fallback).trim().replace(/\\/+$/, \"\");\n}\n\nfunction stringField(data: Record<string, unknown>, key: string): string | undefined {\n\tconst value = data[key];\n\treturn typeof value === \"string\" && value.trim() ? value.trim() : undefined;\n}\n\nfunction stringEnv(name: string): string | undefined {\n\tconst value = process.env[name];\n\treturn typeof value === \"string\" && value.trim() ? value.trim() : undefined;\n}\n\nfunction numberField(data: Record<string, unknown>, key: string): number | undefined {\n\tconst value = data[key];\n\treturn typeof value === \"number\" && Number.isFinite(value) ? value : undefined;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction readPrimeCliConfigData(configPath: string): Record<string, unknown> {\n\tlet data: Record<string, unknown> = {};\n\tif (existsSync(configPath)) {\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(readFileSync(configPath, \"utf-8\")) as unknown;\n\t\t\tif (isRecord(parsed)) {\n\t\t\t\tdata = parsed;\n\t\t\t}\n\t\t} catch {\n\t\t\tdata = {};\n\t\t}\n\t}\n\treturn data;\n}\n\nfunction writePrimeCliConfigData(configPath: string, data: Record<string, unknown>): void {\n\tconst dir = dirname(configPath);\n\tif (!existsSync(dir)) {\n\t\tmkdirSync(dir, { recursive: true, mode: 0o700 });\n\t}\n\tconst tempPath = join(\n\t\tdir,\n\t\t`.${basename(configPath)}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`,\n\t);\n\tlet fd: number | undefined = openSync(tempPath, \"wx\", 0o600);\n\ttry {\n\t\twriteFileSync(fd, `${JSON.stringify(data, null, 2)}\\n`, \"utf-8\");\n\t\tcloseSync(fd);\n\t\tfd = undefined;\n\t\tchmodSync(tempPath, 0o600);\n\t\trenameSync(tempPath, configPath);\n\t\tchmodSync(configPath, 0o600);\n\t} finally {\n\t\tif (fd !== undefined) {\n\t\t\tcloseSync(fd);\n\t\t}\n\t\tif (existsSync(tempPath)) {\n\t\t\trmSync(tempPath, { force: true });\n\t\t}\n\t}\n}\n\nfunction clearPrimeTeamFields(data: Record<string, unknown>): void {\n\tdelete data.team_id;\n\tdelete data.team_name;\n\tdelete data.team_role;\n}\n\nexport function loadPrimeCliConfig(configPath: string = defaultPrimeCliConfigPath()): PrimeCliConfig {\n\tconst data = readPrimeCliConfigData(configPath);\n\tconst teamIdFromEnv = stringEnv(\"PRIME_TEAM_ID\");\n\tconst teamId = teamIdFromEnv ?? stringField(data, \"team_id\");\n\n\tconst config: PrimeCliConfig = {\n\t\tbaseUrl: normalizeBaseUrl(stringField(data, \"base_url\")),\n\t\tfrontendUrl: normalizeUrl(stringField(data, \"frontend_url\"), DEFAULT_PRIME_FRONTEND_URL),\n\t\tinferenceUrl: normalizeUrl(stringField(data, \"inference_url\"), DEFAULT_PRIME_INFERENCE_URL),\n\t\tpath: configPath,\n\t\tteamIdFromEnv: teamIdFromEnv !== undefined,\n\t};\n\tconst apiKey = stringField(data, \"api_key\");\n\tif (apiKey) {\n\t\tconfig.apiKey = apiKey;\n\t}\n\tif (teamId) {\n\t\tconfig.teamId = teamId;\n\t}\n\tif (!teamIdFromEnv) {\n\t\tconst teamName = stringField(data, \"team_name\");\n\t\tconst teamRole = stringField(data, \"team_role\");\n\t\tif (teamName) {\n\t\t\tconfig.teamName = teamName;\n\t\t}\n\t\tif (teamRole) {\n\t\t\tconfig.teamRole = teamRole;\n\t\t}\n\t}\n\treturn config;\n}\n\nexport function savePrimeCliApiKey(apiKey: string, configPath: string = defaultPrimeCliConfigPath()): PrimeCliConfig {\n\tconst data = readPrimeCliConfigData(configPath);\n\tdata.api_key = apiKey;\n\tclearPrimeTeamFields(data);\n\twritePrimeCliConfigData(configPath, data);\n\treturn loadPrimeCliConfig(configPath);\n}\n\nexport function clearPrimeCliCredentials(configPath: string = defaultPrimeCliConfigPath()): PrimeCliConfig {\n\tconst data = readPrimeCliConfigData(configPath);\n\tdelete data.api_key;\n\tclearPrimeTeamFields(data);\n\twritePrimeCliConfigData(configPath, data);\n\treturn loadPrimeCliConfig(configPath);\n}\n\nexport function savePrimeCliTeamSelection(\n\tteam: PrimeTeam | null,\n\tconfigPath: string = defaultPrimeCliConfigPath(),\n): PrimeCliConfig {\n\tconst data = readPrimeCliConfigData(configPath);\n\tif (team) {\n\t\tdata.team_id = team.teamId;\n\t\tdata.team_name = team.name;\n\t\tif (team.role) {\n\t\t\tdata.team_role = team.role;\n\t\t} else {\n\t\t\tdelete data.team_role;\n\t\t}\n\t} else {\n\t\tclearPrimeTeamFields(data);\n\t}\n\twritePrimeCliConfigData(configPath, data);\n\treturn loadPrimeCliConfig(configPath);\n}\n\nexport function resolveAsmAgentTracesBaseUrl(baseUrl?: string): string {\n\treturn normalizeBaseUrl(baseUrl ?? stringEnv(\"ASM_AGENT_TRACES_BASE_URL\"));\n}\n\nfunction resolveAsmAgentTracesChallengeConfig(config: PrimeCliConfig): PrimeChallengeConfig {\n\treturn {\n\t\tbaseUrl: resolveAsmAgentTracesBaseUrl(),\n\t\tfrontendUrl: stringEnv(\"ASM_AGENT_TRACES_BASE_URL\") ? config.frontendUrl : DEFAULT_PRIME_FRONTEND_URL,\n\t};\n}\n\nfunction throwIfCancelled(signal?: AbortSignal): void {\n\tif (signal?.aborted) {\n\t\tthrow new Error(\"Login cancelled\");\n\t}\n}\n\nfunction abortableSleep(ms: number, signal?: AbortSignal): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tif (signal?.aborted) {\n\t\t\treject(new Error(\"Login cancelled\"));\n\t\t\treturn;\n\t\t}\n\n\t\tlet timeout: NodeJS.Timeout;\n\t\tconst onAbort = () => {\n\t\t\tclearTimeout(timeout);\n\t\t\treject(new Error(\"Login cancelled\"));\n\t\t};\n\t\ttimeout = setTimeout(() => {\n\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\tresolve();\n\t\t}, ms);\n\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t});\n}\n\nasync function fetchWithTimeout(\n\tfetchFn: typeof fetch,\n\turl: string | URL,\n\tinit: RequestInit,\n\ttimeoutMs: number,\n\tsignal?: AbortSignal,\n): Promise<Response> {\n\tthrowIfCancelled(signal);\n\n\tconst controller = new AbortController();\n\tconst timeout = setTimeout(() => controller.abort(), timeoutMs);\n\tconst onAbort = () => controller.abort();\n\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\ttry {\n\t\treturn await fetchFn(url, { ...init, signal: controller.signal });\n\t} catch (error) {\n\t\tif (signal?.aborted) {\n\t\t\tthrow new Error(\"Login cancelled\");\n\t\t}\n\t\tif (controller.signal.aborted) {\n\t\t\tthrow new Error(\"Prime Inference request timed out\");\n\t\t}\n\t\tthrow error;\n\t} finally {\n\t\tclearTimeout(timeout);\n\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t}\n}\n\nasync function readResponseMessage(response: Response): Promise<string> {\n\tconst text = await response.text().catch(() => \"\");\n\tif (!text.trim()) {\n\t\treturn response.statusText || \"Unknown error\";\n\t}\n\n\ttry {\n\t\tconst parsed = JSON.parse(text) as unknown;\n\t\tif (isRecord(parsed)) {\n\t\t\tconst error = parsed.error;\n\t\t\tif (isRecord(error)) {\n\t\t\t\tconst message = stringField(error, \"message\");\n\t\t\t\tif (message) return message;\n\t\t\t}\n\t\t\tconst detail = stringField(parsed, \"detail\");\n\t\t\tif (detail) return detail;\n\t\t\tconst message = stringField(parsed, \"message\");\n\t\t\tif (message) return message;\n\t\t}\n\t} catch {\n\t\t// Fall back to raw text.\n\t}\n\n\treturn text.trim();\n}\n\nasync function readJsonObject(response: Response, context: string): Promise<Record<string, unknown>> {\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = (await response.json()) as unknown;\n\t} catch {\n\t\tthrow new Error(`${context} returned an invalid response`);\n\t}\n\tif (!isRecord(parsed)) {\n\t\tthrow new Error(`${context} returned an invalid response`);\n\t}\n\treturn parsed;\n}\n\nfunction parsePrimeTeam(value: unknown): PrimeTeam | undefined {\n\tif (!isRecord(value)) {\n\t\treturn undefined;\n\t}\n\tconst teamId = stringField(value, \"teamId\");\n\tif (!teamId) {\n\t\treturn undefined;\n\t}\n\n\tconst team: PrimeTeam = {\n\t\tteamId,\n\t\tname: stringField(value, \"name\") ?? \"Unknown\",\n\t};\n\tconst slug = stringField(value, \"slug\");\n\tconst role = stringField(value, \"role\");\n\tconst createdAt = stringField(value, \"createdAt\");\n\tif (slug) {\n\t\tteam.slug = slug;\n\t}\n\tif (role) {\n\t\tteam.role = role;\n\t}\n\tif (createdAt) {\n\t\tteam.createdAt = createdAt;\n\t}\n\treturn team;\n}\n\nexport async function fetchPrimeTeams(\n\tapiKey: string,\n\tbaseUrl: string,\n\toptions: {\n\t\tfetchFn?: typeof fetch;\n\t\trequestTimeoutMs?: number;\n\t\tsignal?: AbortSignal;\n\t} = {},\n): Promise<PrimeTeam[]> {\n\tconst fetchFn = options.fetchFn ?? fetch;\n\tconst requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n\tconst teams: PrimeTeam[] = [];\n\tlet offset = 0;\n\tconst limit = 100;\n\n\twhile (true) {\n\t\tconst url = new URL(`${normalizeBaseUrl(baseUrl)}/api/v1/user/teams`);\n\t\turl.searchParams.set(\"offset\", String(offset));\n\t\turl.searchParams.set(\"limit\", String(limit));\n\t\tconst response = await fetchWithTimeout(\n\t\t\tfetchFn,\n\t\t\turl,\n\t\t\t{\n\t\t\t\tmethod: \"GET\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${apiKey}`,\n\t\t\t\t\tAccept: \"application/json\",\n\t\t\t\t},\n\t\t\t},\n\t\t\trequestTimeoutMs,\n\t\t\toptions.signal,\n\t\t);\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(`Failed to fetch Prime teams: ${await readResponseMessage(response)}`);\n\t\t}\n\n\t\tconst data = await readJsonObject(response, \"Prime teams\");\n\t\tconst batch = data.data;\n\t\tif (!Array.isArray(batch)) {\n\t\t\tthrow new Error(\"Prime teams response missing team data\");\n\t\t}\n\n\t\tfor (const item of batch) {\n\t\t\tconst team = parsePrimeTeam(item);\n\t\t\tif (team) {\n\t\t\t\tteams.push(team);\n\t\t\t}\n\t\t}\n\n\t\tconst totalCount = numberField(data, \"total_count\") ?? teams.length;\n\t\tif (batch.length === 0 || teams.length >= totalCount) {\n\t\t\tbreak;\n\t\t}\n\t\toffset += limit;\n\t}\n\n\treturn teams;\n}\n\nfunction createPrimeChallengeKeypair(): { privateKey: string; publicKey: string } {\n\tconst { privateKey, publicKey } = generateKeyPairSync(\"rsa\", {\n\t\tmodulusLength: 2048,\n\t\tpublicExponent: 0x10001,\n\t\tpublicKeyEncoding: {\n\t\t\ttype: \"spki\",\n\t\t\tformat: \"pem\",\n\t\t},\n\t\tprivateKeyEncoding: {\n\t\t\ttype: \"pkcs8\",\n\t\t\tformat: \"pem\",\n\t\t},\n\t});\n\treturn { privateKey, publicKey };\n}\n\nfunction decryptPrimeChallengeResult(privateKey: string, encryptedResult: string): string {\n\tconst decrypted = privateDecrypt(\n\t\t{\n\t\t\tkey: privateKey,\n\t\t\tpadding: constants.RSA_PKCS1_OAEP_PADDING,\n\t\t\toaepHash: \"sha256\",\n\t\t},\n\t\tBuffer.from(encryptedResult, \"base64\"),\n\t);\n\treturn decrypted.toString(\"utf-8\");\n}\n\nasync function generatePrimeChallenge(\n\tconfig: PrimeChallengeConfig,\n\tpublicKey: string,\n\tfetchFn: typeof fetch,\n\ttimeoutMs: number,\n\tsignal?: AbortSignal,\n): Promise<PrimeChallengeResponse> {\n\tconst response = await fetchWithTimeout(\n\t\tfetchFn,\n\t\t`${config.baseUrl}/api/v1/auth_challenge/generate`,\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\tbody: JSON.stringify({ encryptionPublicKey: publicKey }),\n\t\t},\n\t\ttimeoutMs,\n\t\tsignal,\n\t);\n\n\tif (!response.ok) {\n\t\tthrow new Error(`Failed to generate Prime login challenge: ${await readResponseMessage(response)}`);\n\t}\n\n\tconst data = await readJsonObject(response, \"Prime login challenge\");\n\tconst challenge = stringField(data, \"challenge\");\n\tconst statusAuthToken = stringField(data, \"status_auth_token\");\n\tif (!challenge || !statusAuthToken) {\n\t\tthrow new Error(\"Prime login challenge response missing required fields\");\n\t}\n\n\treturn { challenge, statusAuthToken };\n}\n\nasync function pollPrimeChallengeResult(\n\tconfig: PrimeChallengeConfig,\n\tchallenge: PrimeChallengeResponse,\n\tprivateKey: string,\n\tfetchFn: typeof fetch,\n\ttimeoutMs: number,\n\tpollIntervalMs: number,\n\tsignal?: AbortSignal,\n): Promise<string> {\n\twhile (true) {\n\t\tthrowIfCancelled(signal);\n\n\t\tconst statusUrl = new URL(`${config.baseUrl}/api/v1/auth_challenge/status`);\n\t\tstatusUrl.searchParams.set(\"challenge\", challenge.challenge);\n\t\tconst response = await fetchWithTimeout(\n\t\t\tfetchFn,\n\t\t\tstatusUrl,\n\t\t\t{\n\t\t\t\tmethod: \"GET\",\n\t\t\t\theaders: { Authorization: `Bearer ${challenge.statusAuthToken}` },\n\t\t\t},\n\t\t\ttimeoutMs,\n\t\t\tsignal,\n\t\t);\n\n\t\tif (response.status === 404) {\n\t\t\tthrow new Error(\"Prime login challenge expired\");\n\t\t}\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(`Failed to check Prime login status: ${await readResponseMessage(response)}`);\n\t\t}\n\n\t\tconst data = await readJsonObject(response, \"Prime login status\");\n\t\tconst encryptedResult = stringField(data, \"result\");\n\t\tif (encryptedResult) {\n\t\t\treturn decryptPrimeChallengeResult(privateKey, encryptedResult);\n\t\t}\n\n\t\tawait abortableSleep(pollIntervalMs, signal);\n\t}\n}\n\nasync function runPrimeBrowserLogin(\n\tconfig: PrimeChallengeConfig,\n\tcallbacks: PrimeInferenceLoginCallbacks,\n\tfetchFn: typeof fetch,\n\ttimeoutMs: number,\n\tpollIntervalMs: number,\n\tscope?: PrimeAccessScope,\n): Promise<string> {\n\tconst { privateKey, publicKey } = createPrimeChallengeKeypair();\n\tconst challenge = await generatePrimeChallenge(config, publicKey, fetchFn, timeoutMs, callbacks.signal);\n\tconst url = new URL(`${config.frontendUrl}/dashboard/tokens/challenge`);\n\turl.searchParams.set(\"code\", challenge.challenge);\n\tif (scope) {\n\t\turl.searchParams.set(\"scope\", scope);\n\t}\n\tcallbacks.onAuth({ url: url.toString(), instructions: `Code: ${challenge.challenge}` });\n\treturn pollPrimeChallengeResult(config, challenge, privateKey, fetchFn, timeoutMs, pollIntervalMs, callbacks.signal);\n}\n\nasync function checkPrimeScopeAccess(\n\tapiKey: string,\n\tbaseUrl: string,\n\tscopeName: PrimeAccessScope,\n\tscopeLabel: string,\n\toptions: {\n\t\tfetchFn?: typeof fetch;\n\t\trequestTimeoutMs?: number;\n\t\tsignal?: AbortSignal;\n\t} = {},\n): Promise<PrimeInferenceAccessResult> {\n\tconst fetchFn = options.fetchFn ?? fetch;\n\tconst requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n\tconst url = `${normalizeBaseUrl(baseUrl)}/api/v1/user/whoami`;\n\tconst response = await fetchWithTimeout(\n\t\tfetchFn,\n\t\turl,\n\t\t{\n\t\t\tmethod: \"GET\",\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${apiKey}`,\n\t\t\t\tAccept: \"application/json\",\n\t\t\t},\n\t\t},\n\t\trequestTimeoutMs,\n\t\toptions.signal,\n\t);\n\n\tif (!response.ok) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\tstatus: response.status,\n\t\t\tmessage: await readResponseMessage(response),\n\t\t};\n\t}\n\n\tconst data = await readJsonObject(response, \"Prime whoami\");\n\tconst user = data.data;\n\tif (!isRecord(user)) {\n\t\treturn { ok: false, message: \"Prime whoami response missing user data\" };\n\t}\n\n\tconst scope = user.scope;\n\tif (!isRecord(scope)) {\n\t\treturn { ok: false, message: \"Prime token is missing permission scope data\" };\n\t}\n\n\tconst scopedPermission = scope[scopeName];\n\tif (!isRecord(scopedPermission)) {\n\t\treturn { ok: false, message: `Prime token is missing ${scopeLabel} permissions` };\n\t}\n\n\tif (scopedPermission.write === true) {\n\t\treturn { ok: true };\n\t}\n\n\treturn { ok: false, message: `Prime token does not have ${scopeLabel} write permission` };\n}\n\nexport async function checkPrimeInferenceAccess(\n\tapiKey: string,\n\tbaseUrl: string,\n\toptions: {\n\t\tfetchFn?: typeof fetch;\n\t\trequestTimeoutMs?: number;\n\t\tsignal?: AbortSignal;\n\t} = {},\n): Promise<PrimeInferenceAccessResult> {\n\treturn checkPrimeScopeAccess(apiKey, baseUrl, \"inference\", \"inference\", options);\n}\n\nexport async function checkAsmAgentTracesAccess(\n\tapiKey: string,\n\tbaseUrl: string,\n\toptions: {\n\t\tfetchFn?: typeof fetch;\n\t\trequestTimeoutMs?: number;\n\t\tsignal?: AbortSignal;\n\t} = {},\n): Promise<PrimeInferenceAccessResult> {\n\treturn checkPrimeScopeAccess(apiKey, baseUrl, \"agent_traces\", \"agent trace\", options);\n}\n\nfunction formatAccessFailure(result: Exclude<PrimeInferenceAccessResult, { ok: true }>): string {\n\tconst status = result.status === undefined ? \"\" : `HTTP ${result.status}: `;\n\treturn `${status}${result.message}`;\n}\n\nexport async function loginPrimeInference(\n\tcallbacks: PrimeInferenceLoginCallbacks,\n\toptions: PrimeInferenceLoginOptions = {},\n): Promise<PrimeInferenceLoginResult> {\n\tconst config = loadPrimeCliConfig(options.configPath);\n\tconst fetchFn = options.fetchFn ?? fetch;\n\tconst requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n\tconst pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n\n\tif (config.apiKey) {\n\t\tcallbacks.onProgress?.(\"Checking existing Prime CLI credentials...\");\n\t\tconst access = await checkPrimeInferenceAccess(config.apiKey, config.baseUrl, {\n\t\t\tfetchFn,\n\t\t\trequestTimeoutMs,\n\t\t\tsignal: callbacks.signal,\n\t\t});\n\t\tif (access.ok) {\n\t\t\tthrowIfCancelled(callbacks.signal);\n\t\t\treturn { apiKey: config.apiKey, source: \"prime-cli\" };\n\t\t}\n\t\tcallbacks.onProgress?.(\n\t\t\t`Existing Prime CLI key cannot access Prime Inference (${formatAccessFailure(access)}). Starting browser login...`,\n\t\t);\n\t} else {\n\t\tcallbacks.onProgress?.(\"No Prime CLI API key found. Starting browser login...\");\n\t}\n\n\tconst apiKey = await runPrimeBrowserLogin(config, callbacks, fetchFn, requestTimeoutMs, pollIntervalMs);\n\tthrowIfCancelled(callbacks.signal);\n\tcallbacks.onProgress?.(\"Checking Prime Inference access...\");\n\tconst access = await checkPrimeInferenceAccess(apiKey, config.baseUrl, {\n\t\tfetchFn,\n\t\trequestTimeoutMs,\n\t\tsignal: callbacks.signal,\n\t});\n\tif (!access.ok) {\n\t\tthrow new Error(`Prime API key does not have Prime Inference access (${formatAccessFailure(access)})`);\n\t}\n\n\tthrowIfCancelled(callbacks.signal);\n\treturn { apiKey, source: \"browser\" };\n}\n\nexport async function loginAsmAgentTraces(\n\tcallbacks: PrimeInferenceLoginCallbacks,\n\toptions: PrimeInferenceLoginOptions = {},\n): Promise<PrimeInferenceLoginResult> {\n\tconst config = loadPrimeCliConfig(options.configPath);\n\tconst traceConfig = resolveAsmAgentTracesChallengeConfig(config);\n\tconst fetchFn = options.fetchFn ?? fetch;\n\tconst requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n\tconst pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n\n\tif (config.apiKey) {\n\t\tcallbacks.onProgress?.(\"Checking existing Prime CLI credentials...\");\n\t\tconst access = await checkAsmAgentTracesAccess(config.apiKey, traceConfig.baseUrl, {\n\t\t\tfetchFn,\n\t\t\trequestTimeoutMs,\n\t\t\tsignal: callbacks.signal,\n\t\t});\n\t\tif (access.ok) {\n\t\t\tthrowIfCancelled(callbacks.signal);\n\t\t\treturn { apiKey: config.apiKey, source: \"prime-cli\" };\n\t\t}\n\t\tcallbacks.onProgress?.(\n\t\t\t`Existing Prime CLI key cannot upload ASM Agent traces (${formatAccessFailure(access)}). Starting browser login...`,\n\t\t);\n\t} else {\n\t\tcallbacks.onProgress?.(\"No Prime CLI API key found. Starting browser login...\");\n\t}\n\n\tconst apiKey = await runPrimeBrowserLogin(\n\t\ttraceConfig,\n\t\tcallbacks,\n\t\tfetchFn,\n\t\trequestTimeoutMs,\n\t\tpollIntervalMs,\n\t\t\"agent_traces\",\n\t);\n\tthrowIfCancelled(callbacks.signal);\n\tcallbacks.onProgress?.(\"Checking ASM Agent trace access...\");\n\tconst access = await checkAsmAgentTracesAccess(apiKey, traceConfig.baseUrl, {\n\t\tfetchFn,\n\t\trequestTimeoutMs,\n\t\tsignal: callbacks.signal,\n\t});\n\tif (!access.ok) {\n\t\tthrow new Error(`Prime API key does not have ASM Agent trace access (${formatAccessFailure(access)})`);\n\t}\n\n\tthrowIfCancelled(callbacks.signal);\n\treturn { apiKey, source: \"browser\" };\n}\n"]}
1
+ {"version":3,"file":"prime-inference-auth.d.ts","sourceRoot":"","sources":["../../src/core/prime-inference-auth.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,eAAO,MAAM,2BAA2B,oBAAoB,CAAC;AAC7D,eAAO,MAAM,6BAA6B,oBAAoB,CAAC;AAQ/D,MAAM,MAAM,wBAAwB,GAAG,WAAW,GAAG,SAAS,CAAC;AAE/D,MAAM,MAAM,yBAAyB,GAAG;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,wBAAwB,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG;IAC1C,MAAM,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;IACtC,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAYF,MAAM,MAAM,0BAA0B,GACnC;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GACZ;IACA,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACf,CAAC;AAIL,MAAM,MAAM,SAAS,GAAG;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAMF,wBAAgB,qBAAqB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAEjE;AA6ED,wBAAgB,kBAAkB,CAAC,UAAU,GAAE,MAAoC,GAAG,cAAc,CA8BnG;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,GAAE,MAAoC,GAAG,cAAc,CAMnH;AAED,wBAAgB,wBAAwB,CAAC,UAAU,GAAE,MAAoC,GAAG,cAAc,CAMzG;AAED,wBAAgB,yBAAyB,CACxC,IAAI,EAAE,SAAS,GAAG,IAAI,EACtB,UAAU,GAAE,MAAoC,GAC9C,cAAc,CAehB;AA6HD,wBAAsB,eAAe,CACpC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IACR,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,WAAW,CAAC;CAChB,GACJ,OAAO,CAAC,SAAS,EAAE,CAAC,CAkDtB;AAuLD,wBAAsB,yBAAyB,CAC9C,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IACR,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,WAAW,CAAC;CAChB,GACJ,OAAO,CAAC,0BAA0B,CAAC,CAErC;AAOD,wBAAsB,mBAAmB,CACxC,SAAS,EAAE,4BAA4B,EACvC,OAAO,GAAE,0BAA+B,GACtC,OAAO,CAAC,yBAAyB,CAAC,CAsCpC","sourcesContent":["import { Buffer } from \"node:buffer\";\nimport { constants, generateKeyPairSync, privateDecrypt } from \"node:crypto\";\nimport {\n\tchmodSync,\n\tcloseSync,\n\texistsSync,\n\tmkdirSync,\n\topenSync,\n\treadFileSync,\n\trenameSync,\n\trmSync,\n\twriteFileSync,\n} from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { basename, dirname, join } from \"node:path\";\nimport type { OAuthAuthInfo } from \"@asm-agent/ai\";\n\nexport const PRIME_INFERENCE_PROVIDER_ID = \"prime-inference\";\nexport const PRIME_INFERENCE_PROVIDER_NAME = \"Prime Inference\";\n\nconst DEFAULT_PRIME_API_BASE_URL = \"https://api.primeintellect.ai\";\nconst DEFAULT_PRIME_FRONTEND_URL = \"https://app.primeintellect.ai\";\nconst DEFAULT_PRIME_INFERENCE_URL = \"https://api.pinference.ai/api/v1\";\nconst DEFAULT_REQUEST_TIMEOUT_MS = 30_000;\nconst DEFAULT_POLL_INTERVAL_MS = 5_000;\n\nexport type PrimeInferenceAuthSource = \"prime-cli\" | \"browser\";\n\nexport type PrimeInferenceLoginResult = {\n\tapiKey: string;\n\tsource: PrimeInferenceAuthSource;\n};\n\nexport type PrimeCliConfig = {\n\tapiKey?: string;\n\tbaseUrl: string;\n\tfrontendUrl: string;\n\tinferenceUrl: string;\n\tpath: string;\n\tteamId?: string;\n\tteamName?: string;\n\tteamRole?: string;\n\tteamIdFromEnv: boolean;\n};\n\nexport type PrimeInferenceLoginCallbacks = {\n\tonAuth: (info: OAuthAuthInfo) => void;\n\tonProgress?: (message: string) => void;\n\tsignal?: AbortSignal;\n};\n\nexport type PrimeInferenceLoginOptions = {\n\tconfigPath?: string;\n\tfetchFn?: typeof fetch;\n\tpollIntervalMs?: number;\n\trequestTimeoutMs?: number;\n};\n\ntype PrimeChallengeConfig = {\n\tbaseUrl: string;\n\tfrontendUrl: string;\n};\n\ntype PrimeChallengeResponse = {\n\tchallenge: string;\n\tstatusAuthToken: string;\n};\n\nexport type PrimeInferenceAccessResult =\n\t| { ok: true }\n\t| {\n\t\t\tok: false;\n\t\t\tstatus?: number;\n\t\t\tmessage: string;\n\t };\n\ntype PrimeAccessScope = \"inference\";\n\nexport type PrimeTeam = {\n\tteamId: string;\n\tname: string;\n\tslug?: string;\n\trole?: string;\n\tcreatedAt?: string;\n};\n\nfunction defaultPrimeCliConfigPath(): string {\n\treturn join(homedir(), \".prime\", \"config.json\");\n}\n\nexport function getPrimeCliConfigPath(configPath?: string): string {\n\treturn configPath ?? defaultPrimeCliConfigPath();\n}\n\nfunction normalizeBaseUrl(value: string | undefined): string {\n\treturn (value?.trim() || DEFAULT_PRIME_API_BASE_URL).replace(/\\/+$/, \"\").replace(/\\/api\\/v1$/, \"\");\n}\n\nfunction normalizeUrl(value: string | undefined, fallback: string): string {\n\treturn (value || fallback).trim().replace(/\\/+$/, \"\");\n}\n\nfunction stringField(data: Record<string, unknown>, key: string): string | undefined {\n\tconst value = data[key];\n\treturn typeof value === \"string\" && value.trim() ? value.trim() : undefined;\n}\n\nfunction stringEnv(name: string): string | undefined {\n\tconst value = process.env[name];\n\treturn typeof value === \"string\" && value.trim() ? value.trim() : undefined;\n}\n\nfunction numberField(data: Record<string, unknown>, key: string): number | undefined {\n\tconst value = data[key];\n\treturn typeof value === \"number\" && Number.isFinite(value) ? value : undefined;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction readPrimeCliConfigData(configPath: string): Record<string, unknown> {\n\tlet data: Record<string, unknown> = {};\n\tif (existsSync(configPath)) {\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(readFileSync(configPath, \"utf-8\")) as unknown;\n\t\t\tif (isRecord(parsed)) {\n\t\t\t\tdata = parsed;\n\t\t\t}\n\t\t} catch {\n\t\t\tdata = {};\n\t\t}\n\t}\n\treturn data;\n}\n\nfunction writePrimeCliConfigData(configPath: string, data: Record<string, unknown>): void {\n\tconst dir = dirname(configPath);\n\tif (!existsSync(dir)) {\n\t\tmkdirSync(dir, { recursive: true, mode: 0o700 });\n\t}\n\tconst tempPath = join(\n\t\tdir,\n\t\t`.${basename(configPath)}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`,\n\t);\n\tlet fd: number | undefined = openSync(tempPath, \"wx\", 0o600);\n\ttry {\n\t\twriteFileSync(fd, `${JSON.stringify(data, null, 2)}\\n`, \"utf-8\");\n\t\tcloseSync(fd);\n\t\tfd = undefined;\n\t\tchmodSync(tempPath, 0o600);\n\t\trenameSync(tempPath, configPath);\n\t\tchmodSync(configPath, 0o600);\n\t} finally {\n\t\tif (fd !== undefined) {\n\t\t\tcloseSync(fd);\n\t\t}\n\t\tif (existsSync(tempPath)) {\n\t\t\trmSync(tempPath, { force: true });\n\t\t}\n\t}\n}\n\nfunction clearPrimeTeamFields(data: Record<string, unknown>): void {\n\tdelete data.team_id;\n\tdelete data.team_name;\n\tdelete data.team_role;\n}\n\nexport function loadPrimeCliConfig(configPath: string = defaultPrimeCliConfigPath()): PrimeCliConfig {\n\tconst data = readPrimeCliConfigData(configPath);\n\tconst teamIdFromEnv = stringEnv(\"PRIME_TEAM_ID\");\n\tconst teamId = teamIdFromEnv ?? stringField(data, \"team_id\");\n\n\tconst config: PrimeCliConfig = {\n\t\tbaseUrl: normalizeBaseUrl(stringField(data, \"base_url\")),\n\t\tfrontendUrl: normalizeUrl(stringField(data, \"frontend_url\"), DEFAULT_PRIME_FRONTEND_URL),\n\t\tinferenceUrl: normalizeUrl(stringField(data, \"inference_url\"), DEFAULT_PRIME_INFERENCE_URL),\n\t\tpath: configPath,\n\t\tteamIdFromEnv: teamIdFromEnv !== undefined,\n\t};\n\tconst apiKey = stringField(data, \"api_key\");\n\tif (apiKey) {\n\t\tconfig.apiKey = apiKey;\n\t}\n\tif (teamId) {\n\t\tconfig.teamId = teamId;\n\t}\n\tif (!teamIdFromEnv) {\n\t\tconst teamName = stringField(data, \"team_name\");\n\t\tconst teamRole = stringField(data, \"team_role\");\n\t\tif (teamName) {\n\t\t\tconfig.teamName = teamName;\n\t\t}\n\t\tif (teamRole) {\n\t\t\tconfig.teamRole = teamRole;\n\t\t}\n\t}\n\treturn config;\n}\n\nexport function savePrimeCliApiKey(apiKey: string, configPath: string = defaultPrimeCliConfigPath()): PrimeCliConfig {\n\tconst data = readPrimeCliConfigData(configPath);\n\tdata.api_key = apiKey;\n\tclearPrimeTeamFields(data);\n\twritePrimeCliConfigData(configPath, data);\n\treturn loadPrimeCliConfig(configPath);\n}\n\nexport function clearPrimeCliCredentials(configPath: string = defaultPrimeCliConfigPath()): PrimeCliConfig {\n\tconst data = readPrimeCliConfigData(configPath);\n\tdelete data.api_key;\n\tclearPrimeTeamFields(data);\n\twritePrimeCliConfigData(configPath, data);\n\treturn loadPrimeCliConfig(configPath);\n}\n\nexport function savePrimeCliTeamSelection(\n\tteam: PrimeTeam | null,\n\tconfigPath: string = defaultPrimeCliConfigPath(),\n): PrimeCliConfig {\n\tconst data = readPrimeCliConfigData(configPath);\n\tif (team) {\n\t\tdata.team_id = team.teamId;\n\t\tdata.team_name = team.name;\n\t\tif (team.role) {\n\t\t\tdata.team_role = team.role;\n\t\t} else {\n\t\t\tdelete data.team_role;\n\t\t}\n\t} else {\n\t\tclearPrimeTeamFields(data);\n\t}\n\twritePrimeCliConfigData(configPath, data);\n\treturn loadPrimeCliConfig(configPath);\n}\n\nfunction throwIfCancelled(signal?: AbortSignal): void {\n\tif (signal?.aborted) {\n\t\tthrow new Error(\"Login cancelled\");\n\t}\n}\n\nfunction abortableSleep(ms: number, signal?: AbortSignal): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tif (signal?.aborted) {\n\t\t\treject(new Error(\"Login cancelled\"));\n\t\t\treturn;\n\t\t}\n\n\t\tlet timeout: NodeJS.Timeout;\n\t\tconst onAbort = () => {\n\t\t\tclearTimeout(timeout);\n\t\t\treject(new Error(\"Login cancelled\"));\n\t\t};\n\t\ttimeout = setTimeout(() => {\n\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\tresolve();\n\t\t}, ms);\n\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t});\n}\n\nasync function fetchWithTimeout(\n\tfetchFn: typeof fetch,\n\turl: string | URL,\n\tinit: RequestInit,\n\ttimeoutMs: number,\n\tsignal?: AbortSignal,\n): Promise<Response> {\n\tthrowIfCancelled(signal);\n\n\tconst controller = new AbortController();\n\tconst timeout = setTimeout(() => controller.abort(), timeoutMs);\n\tconst onAbort = () => controller.abort();\n\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\ttry {\n\t\treturn await fetchFn(url, { ...init, signal: controller.signal });\n\t} catch (error) {\n\t\tif (signal?.aborted) {\n\t\t\tthrow new Error(\"Login cancelled\");\n\t\t}\n\t\tif (controller.signal.aborted) {\n\t\t\tthrow new Error(\"Prime Inference request timed out\");\n\t\t}\n\t\tthrow error;\n\t} finally {\n\t\tclearTimeout(timeout);\n\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t}\n}\n\nasync function readResponseMessage(response: Response): Promise<string> {\n\tconst text = await response.text().catch(() => \"\");\n\tif (!text.trim()) {\n\t\treturn response.statusText || \"Unknown error\";\n\t}\n\n\ttry {\n\t\tconst parsed = JSON.parse(text) as unknown;\n\t\tif (isRecord(parsed)) {\n\t\t\tconst error = parsed.error;\n\t\t\tif (isRecord(error)) {\n\t\t\t\tconst message = stringField(error, \"message\");\n\t\t\t\tif (message) return message;\n\t\t\t}\n\t\t\tconst detail = stringField(parsed, \"detail\");\n\t\t\tif (detail) return detail;\n\t\t\tconst message = stringField(parsed, \"message\");\n\t\t\tif (message) return message;\n\t\t}\n\t} catch {\n\t\t// Fall back to raw text.\n\t}\n\n\treturn text.trim();\n}\n\nasync function readJsonObject(response: Response, context: string): Promise<Record<string, unknown>> {\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = (await response.json()) as unknown;\n\t} catch {\n\t\tthrow new Error(`${context} returned an invalid response`);\n\t}\n\tif (!isRecord(parsed)) {\n\t\tthrow new Error(`${context} returned an invalid response`);\n\t}\n\treturn parsed;\n}\n\nfunction parsePrimeTeam(value: unknown): PrimeTeam | undefined {\n\tif (!isRecord(value)) {\n\t\treturn undefined;\n\t}\n\tconst teamId = stringField(value, \"teamId\");\n\tif (!teamId) {\n\t\treturn undefined;\n\t}\n\n\tconst team: PrimeTeam = {\n\t\tteamId,\n\t\tname: stringField(value, \"name\") ?? \"Unknown\",\n\t};\n\tconst slug = stringField(value, \"slug\");\n\tconst role = stringField(value, \"role\");\n\tconst createdAt = stringField(value, \"createdAt\");\n\tif (slug) {\n\t\tteam.slug = slug;\n\t}\n\tif (role) {\n\t\tteam.role = role;\n\t}\n\tif (createdAt) {\n\t\tteam.createdAt = createdAt;\n\t}\n\treturn team;\n}\n\nexport async function fetchPrimeTeams(\n\tapiKey: string,\n\tbaseUrl: string,\n\toptions: {\n\t\tfetchFn?: typeof fetch;\n\t\trequestTimeoutMs?: number;\n\t\tsignal?: AbortSignal;\n\t} = {},\n): Promise<PrimeTeam[]> {\n\tconst fetchFn = options.fetchFn ?? fetch;\n\tconst requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n\tconst teams: PrimeTeam[] = [];\n\tlet offset = 0;\n\tconst limit = 100;\n\n\twhile (true) {\n\t\tconst url = new URL(`${normalizeBaseUrl(baseUrl)}/api/v1/user/teams`);\n\t\turl.searchParams.set(\"offset\", String(offset));\n\t\turl.searchParams.set(\"limit\", String(limit));\n\t\tconst response = await fetchWithTimeout(\n\t\t\tfetchFn,\n\t\t\turl,\n\t\t\t{\n\t\t\t\tmethod: \"GET\",\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${apiKey}`,\n\t\t\t\t\tAccept: \"application/json\",\n\t\t\t\t},\n\t\t\t},\n\t\t\trequestTimeoutMs,\n\t\t\toptions.signal,\n\t\t);\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(`Failed to fetch Prime teams: ${await readResponseMessage(response)}`);\n\t\t}\n\n\t\tconst data = await readJsonObject(response, \"Prime teams\");\n\t\tconst batch = data.data;\n\t\tif (!Array.isArray(batch)) {\n\t\t\tthrow new Error(\"Prime teams response missing team data\");\n\t\t}\n\n\t\tfor (const item of batch) {\n\t\t\tconst team = parsePrimeTeam(item);\n\t\t\tif (team) {\n\t\t\t\tteams.push(team);\n\t\t\t}\n\t\t}\n\n\t\tconst totalCount = numberField(data, \"total_count\") ?? teams.length;\n\t\tif (batch.length === 0 || teams.length >= totalCount) {\n\t\t\tbreak;\n\t\t}\n\t\toffset += limit;\n\t}\n\n\treturn teams;\n}\n\nfunction createPrimeChallengeKeypair(): { privateKey: string; publicKey: string } {\n\tconst { privateKey, publicKey } = generateKeyPairSync(\"rsa\", {\n\t\tmodulusLength: 2048,\n\t\tpublicExponent: 0x10001,\n\t\tpublicKeyEncoding: {\n\t\t\ttype: \"spki\",\n\t\t\tformat: \"pem\",\n\t\t},\n\t\tprivateKeyEncoding: {\n\t\t\ttype: \"pkcs8\",\n\t\t\tformat: \"pem\",\n\t\t},\n\t});\n\treturn { privateKey, publicKey };\n}\n\nfunction decryptPrimeChallengeResult(privateKey: string, encryptedResult: string): string {\n\tconst decrypted = privateDecrypt(\n\t\t{\n\t\t\tkey: privateKey,\n\t\t\tpadding: constants.RSA_PKCS1_OAEP_PADDING,\n\t\t\toaepHash: \"sha256\",\n\t\t},\n\t\tBuffer.from(encryptedResult, \"base64\"),\n\t);\n\treturn decrypted.toString(\"utf-8\");\n}\n\nasync function generatePrimeChallenge(\n\tconfig: PrimeChallengeConfig,\n\tpublicKey: string,\n\tfetchFn: typeof fetch,\n\ttimeoutMs: number,\n\tsignal?: AbortSignal,\n): Promise<PrimeChallengeResponse> {\n\tconst response = await fetchWithTimeout(\n\t\tfetchFn,\n\t\t`${config.baseUrl}/api/v1/auth_challenge/generate`,\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\tbody: JSON.stringify({ encryptionPublicKey: publicKey }),\n\t\t},\n\t\ttimeoutMs,\n\t\tsignal,\n\t);\n\n\tif (!response.ok) {\n\t\tthrow new Error(`Failed to generate Prime login challenge: ${await readResponseMessage(response)}`);\n\t}\n\n\tconst data = await readJsonObject(response, \"Prime login challenge\");\n\tconst challenge = stringField(data, \"challenge\");\n\tconst statusAuthToken = stringField(data, \"status_auth_token\");\n\tif (!challenge || !statusAuthToken) {\n\t\tthrow new Error(\"Prime login challenge response missing required fields\");\n\t}\n\n\treturn { challenge, statusAuthToken };\n}\n\nasync function pollPrimeChallengeResult(\n\tconfig: PrimeChallengeConfig,\n\tchallenge: PrimeChallengeResponse,\n\tprivateKey: string,\n\tfetchFn: typeof fetch,\n\ttimeoutMs: number,\n\tpollIntervalMs: number,\n\tsignal?: AbortSignal,\n): Promise<string> {\n\twhile (true) {\n\t\tthrowIfCancelled(signal);\n\n\t\tconst statusUrl = new URL(`${config.baseUrl}/api/v1/auth_challenge/status`);\n\t\tstatusUrl.searchParams.set(\"challenge\", challenge.challenge);\n\t\tconst response = await fetchWithTimeout(\n\t\t\tfetchFn,\n\t\t\tstatusUrl,\n\t\t\t{\n\t\t\t\tmethod: \"GET\",\n\t\t\t\theaders: { Authorization: `Bearer ${challenge.statusAuthToken}` },\n\t\t\t},\n\t\t\ttimeoutMs,\n\t\t\tsignal,\n\t\t);\n\n\t\tif (response.status === 404) {\n\t\t\tthrow new Error(\"Prime login challenge expired\");\n\t\t}\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(`Failed to check Prime login status: ${await readResponseMessage(response)}`);\n\t\t}\n\n\t\tconst data = await readJsonObject(response, \"Prime login status\");\n\t\tconst encryptedResult = stringField(data, \"result\");\n\t\tif (encryptedResult) {\n\t\t\treturn decryptPrimeChallengeResult(privateKey, encryptedResult);\n\t\t}\n\n\t\tawait abortableSleep(pollIntervalMs, signal);\n\t}\n}\n\nasync function runPrimeBrowserLogin(\n\tconfig: PrimeChallengeConfig,\n\tcallbacks: PrimeInferenceLoginCallbacks,\n\tfetchFn: typeof fetch,\n\ttimeoutMs: number,\n\tpollIntervalMs: number,\n\tscope?: PrimeAccessScope,\n): Promise<string> {\n\tconst { privateKey, publicKey } = createPrimeChallengeKeypair();\n\tconst challenge = await generatePrimeChallenge(config, publicKey, fetchFn, timeoutMs, callbacks.signal);\n\tconst url = new URL(`${config.frontendUrl}/dashboard/tokens/challenge`);\n\turl.searchParams.set(\"code\", challenge.challenge);\n\tif (scope) {\n\t\turl.searchParams.set(\"scope\", scope);\n\t}\n\tcallbacks.onAuth({ url: url.toString(), instructions: `Code: ${challenge.challenge}` });\n\treturn pollPrimeChallengeResult(config, challenge, privateKey, fetchFn, timeoutMs, pollIntervalMs, callbacks.signal);\n}\n\nasync function checkPrimeScopeAccess(\n\tapiKey: string,\n\tbaseUrl: string,\n\tscopeName: PrimeAccessScope,\n\tscopeLabel: string,\n\toptions: {\n\t\tfetchFn?: typeof fetch;\n\t\trequestTimeoutMs?: number;\n\t\tsignal?: AbortSignal;\n\t} = {},\n): Promise<PrimeInferenceAccessResult> {\n\tconst fetchFn = options.fetchFn ?? fetch;\n\tconst requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n\tconst url = `${normalizeBaseUrl(baseUrl)}/api/v1/user/whoami`;\n\tconst response = await fetchWithTimeout(\n\t\tfetchFn,\n\t\turl,\n\t\t{\n\t\t\tmethod: \"GET\",\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${apiKey}`,\n\t\t\t\tAccept: \"application/json\",\n\t\t\t},\n\t\t},\n\t\trequestTimeoutMs,\n\t\toptions.signal,\n\t);\n\n\tif (!response.ok) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\tstatus: response.status,\n\t\t\tmessage: await readResponseMessage(response),\n\t\t};\n\t}\n\n\tconst data = await readJsonObject(response, \"Prime whoami\");\n\tconst user = data.data;\n\tif (!isRecord(user)) {\n\t\treturn { ok: false, message: \"Prime whoami response missing user data\" };\n\t}\n\n\tconst scope = user.scope;\n\tif (!isRecord(scope)) {\n\t\treturn { ok: false, message: \"Prime token is missing permission scope data\" };\n\t}\n\n\tconst scopedPermission = scope[scopeName];\n\tif (!isRecord(scopedPermission)) {\n\t\treturn { ok: false, message: `Prime token is missing ${scopeLabel} permissions` };\n\t}\n\n\tif (scopedPermission.write === true) {\n\t\treturn { ok: true };\n\t}\n\n\treturn { ok: false, message: `Prime token does not have ${scopeLabel} write permission` };\n}\n\nexport async function checkPrimeInferenceAccess(\n\tapiKey: string,\n\tbaseUrl: string,\n\toptions: {\n\t\tfetchFn?: typeof fetch;\n\t\trequestTimeoutMs?: number;\n\t\tsignal?: AbortSignal;\n\t} = {},\n): Promise<PrimeInferenceAccessResult> {\n\treturn checkPrimeScopeAccess(apiKey, baseUrl, \"inference\", \"inference\", options);\n}\n\nfunction formatAccessFailure(result: Exclude<PrimeInferenceAccessResult, { ok: true }>): string {\n\tconst status = result.status === undefined ? \"\" : `HTTP ${result.status}: `;\n\treturn `${status}${result.message}`;\n}\n\nexport async function loginPrimeInference(\n\tcallbacks: PrimeInferenceLoginCallbacks,\n\toptions: PrimeInferenceLoginOptions = {},\n): Promise<PrimeInferenceLoginResult> {\n\tconst config = loadPrimeCliConfig(options.configPath);\n\tconst fetchFn = options.fetchFn ?? fetch;\n\tconst requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n\tconst pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n\n\tif (config.apiKey) {\n\t\tcallbacks.onProgress?.(\"Checking existing Prime CLI credentials...\");\n\t\tconst access = await checkPrimeInferenceAccess(config.apiKey, config.baseUrl, {\n\t\t\tfetchFn,\n\t\t\trequestTimeoutMs,\n\t\t\tsignal: callbacks.signal,\n\t\t});\n\t\tif (access.ok) {\n\t\t\tthrowIfCancelled(callbacks.signal);\n\t\t\treturn { apiKey: config.apiKey, source: \"prime-cli\" };\n\t\t}\n\t\tcallbacks.onProgress?.(\n\t\t\t`Existing Prime CLI key cannot access Prime Inference (${formatAccessFailure(access)}). Starting browser login...`,\n\t\t);\n\t} else {\n\t\tcallbacks.onProgress?.(\"No Prime CLI API key found. Starting browser login...\");\n\t}\n\n\tconst apiKey = await runPrimeBrowserLogin(config, callbacks, fetchFn, requestTimeoutMs, pollIntervalMs);\n\tthrowIfCancelled(callbacks.signal);\n\tcallbacks.onProgress?.(\"Checking Prime Inference access...\");\n\tconst access = await checkPrimeInferenceAccess(apiKey, config.baseUrl, {\n\t\tfetchFn,\n\t\trequestTimeoutMs,\n\t\tsignal: callbacks.signal,\n\t});\n\tif (!access.ok) {\n\t\tthrow new Error(`Prime API key does not have Prime Inference access (${formatAccessFailure(access)})`);\n\t}\n\n\tthrowIfCancelled(callbacks.signal);\n\treturn { apiKey, source: \"browser\" };\n}\n"]}