@caplets/core 0.27.0 → 0.28.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
- import { $ as resolveExposure, $t as mergeCapabilities, A as nativeCapletPromptGuidance, An as isJSONRPCRequest, At as runOAuthFlow, B as CodeModeSessionManager, Bn as safeParse, Bt as defaultConfigBaseDir, C as resolveHostedCloudRemote, Cn as ReadResourceRequestSchema, Ct as validateCapletFile, D as isLoopbackHost, Dn as assertCompleteRequestResourceTemplate, Dt as markdownStructuredContent, E as controlUrlForBase, En as assertCompleteRequestPrompt, Et as markdownCallToolResultContent, Fn as getSchemaDescription, Ft as readTokenBundle, G as CodeModeJournalStore, Gt as resolveProjectCapletsRoot, H as diagnoseCodeModeTypeScript, Ht as defaultStateBaseDir, I as codeModeRunInputSchema, In as isSchemaOptional, It as DEFAULT_AUTH_DIR, J as codeModeDeclarationHash, Jt as serializeMessage, K as CodeModeLogStore, Kt as resolveProjectConfigPath, L as codeModeRunParamsSchema, Ln as isZ4Schema, M as nativeCapletToolName, Mn as getLiteralValue, Mt as startOAuthFlow, Nn as getObjectShape, Nt as deleteTokenBundle, O as parseServerBaseUrl, On as isInitializeRequest, Ot as refreshOAuthTokenBundle, Pn as getParseErrorMessage, Pt as isTokenBundleExpired, Q as CapletsEngine, Qt as Protocol, R as emptyCodeModeRunMeta, Rn as normalizeObjectSchema, Rt as DEFAULT_OBSERVED_OUTPUT_SHAPE_CACHE_DIR, S as resolveCapletsRemote, Sn as McpError, St as discoverCapletFiles, T as appendBasePath, Tn as SetLevelRequestSchema, Tt as hasRenderableStructuredContent, U as createCodeModeCapletsApi, Ut as resolveCapletsRoot, V as QuickJsCodeModeSandbox, Vn as safeParseAsync, Vt as defaultConfigPath, W as listCodeModeCallableCaplets, Wt as resolveConfigPath, X as generateCodeModeRunToolDescription, Xt as assertToolsCallTaskCapability, Y as generateCodeModeDeclarations, Yt as assertClientRequestTaskCapability, Z as minifyCodeModeDeclarationText, Zt as AjvJsonSchemaValidator, _ as CapletsCloudClient, _n as ListResourceTemplatesRequestSchema, _t as FileVaultStore, a as CloudAuthStore, an as CreateMessageResultWithToolsSchema, at as ServerRegistry, b as isCapletsCloudUrl, bn as ListToolsRequestSchema, bt as decryptVaultValue, c as redactedCloudAuthStatus, cn as ElicitResultSchema, ct as loadConfig, d as projectBindingError, dn as GetPromptRequestSchema, dt as loadLocalOverlayConfigWithSources, en as toJsonSchemaCompat, et as decodeDirectResourceUri, f as projectBindingRecovery, fn as InitializeRequestSchema, ft as loadProjectConfig, g as buildProjectSyncManifest, gn as ListPromptsRequestSchema, gt as vaultStoreForAuthDir, hn as LATEST_PROTOCOL_VERSION, ht as vaultResolverForAuthDir, i as createRemoteProfileStore, in as CreateMessageResultSchema, it as handleServerTool, j as nativeCapletToolDescription, jn as isJSONRPCResultResponse, jt as startGenericOAuthFlow, k as resolveCapletsServer, kn as isJSONRPCErrorResponse, kt as runGenericOAuthFlow, l as PROJECT_BINDING_ERROR_CODES, ln as EmptyResultSchema, lt as loadConfigWithSources, mn as JSONRPCMessageSchema, mt as vaultBootstrapResolver, n as resolveRemoteSelection, nn as CallToolResultSchema, nt as findProjectRoot, o as cloudAuthPath, on as CreateTaskResultSchema, ot as capabilityDescription, p as CloudAuthClient, pn as InitializedNotificationSchema, pt as parseConfig, q as redactCodeModeLogText, qt as ReadBuffer, r as cloudCredentialsFromRemoteProfile, rn as CompleteRequestSchema, rt as fingerprintProjectRoot, s as migrateCredentials, st as GoogleDiscoveryManager, t as createNativeCapletsService, tn as CallToolRequestSchema, tt as directResourceUriMatchesTemplate, u as ProjectBindingError, un as ErrorCode, ut as loadGlobalConfig, vn as ListResourcesRequestSchema, vt as VAULT_MAX_VALUE_BYTES, w as resolveRemoteMode, wn as SUPPORTED_PROTOCOL_VERSIONS, wt as loadCapletFilesFromMap, x as normalizeRemoteProfileHostUrl, xn as LoggingLevelSchema, xt as encryptVaultValue, y as hostedCloudWorkspaceFromRemoteUrl, yn as ListRootsResultSchema, yt as validateVaultKeyName, z as runCodeMode, zn as objectFromShape, zt as defaultCacheBaseDir } from "./service-BGGiZLHa.js";
1
+ import { $ as redactCodeModeLogText, $n as getParseErrorMessage, $t as readTokenBundle, A as resolveRemoteMode, An as GetPromptRequestSchema, At as loadProjectConfig, Bn as LoggingLevelSchema, Bt as discoverCapletFiles, C as CapletsCloudClient, Cn as CreateMessageResultSchema, Ct as ServerRegistry, D as normalizeRemoteProfileHostUrl, Dn as ElicitResultSchema, Dt as loadConfigWithSources, E as isCapletsCloudUrl, Et as loadConfig, F as resolveCapletsServer, Fn as ListPromptsRequestSchema, Ft as FileVaultStore, G as runCodeMode, Gn as assertCompleteRequestPrompt, Gt as markdownStructuredContent, H as codeModeRunInputSchema, Hn as ReadResourceRequestSchema, Ht as loadCapletFilesFromMap, I as nativeCapletPromptGuidance, In as ListResourceTemplatesRequestSchema, It as VAULT_MAX_VALUE_BYTES, J as diagnoseCodeModeTypeScript, Jn as isJSONRPCErrorResponse, Jt as runOAuthFlow, K as CodeModeSessionManager, Kn as assertCompleteRequestResourceTemplate, Kt as refreshOAuthTokenBundle, L as nativeCapletToolDescription, Ln as ListResourcesRequestSchema, Lt as validateVaultKeyName, M as controlUrlForBase, Mn as InitializedNotificationSchema, Mt as vaultBootstrapResolver, N as isLoopbackHost, Nn as JSONRPCMessageSchema, Nt as vaultResolverForAuthDir, O as resolveCapletsRemote, On as EmptyResultSchema, Ot as loadGlobalConfig, P as parseServerBaseUrl, Pn as LATEST_PROTOCOL_VERSION, Pt as vaultStoreForAuthDir, Q as CodeModeLogStore, Qn as getObjectShape, Qt as isTokenBundleExpired, R as nativeCapletToolName, Rn as ListRootsResultSchema, Rt as decryptVaultValue, S as buildProjectSyncManifest, Sn as CompleteRequestSchema, St as handleServerTool, T as hostedCloudWorkspaceFromRemoteUrl, Tn as CreateTaskResultSchema, Tt as GoogleDiscoveryManager, U as codeModeRunParamsSchema, Un as SUPPORTED_PROTOCOL_VERSIONS, Ut as hasRenderableStructuredContent, Vn as McpError, Vt as validateCapletFile, W as emptyCodeModeRunMeta, Wn as SetLevelRequestSchema, Wt as markdownCallToolResultContent, X as listCodeModeCallableCaplets, Xn as isJSONRPCResultResponse, Xt as startOAuthFlow, Y as createCodeModeCapletsApi, Yn as isJSONRPCRequest, Yt as startGenericOAuthFlow, Z as CodeModeJournalStore, Zn as getLiteralValue, Zt as deleteTokenBundle, _ as attachErrorResponse, _n as Protocol, _t as rotateTelemetryIdentity, a as CloudAuthStore, an as defaultConfigPath, ar as safeParse, at as version, b as invokeAttachExport, bn as CallToolRequestSchema, bt as findProjectRoot, c as redactedCloudAuthStatus, cn as resolveCapletsRoot, ct as buildProductTelemetryEvent, d as projectBindingError, dn as resolveProjectConfigPath, dt as maybePrintTelemetryNotice, en as DEFAULT_AUTH_DIR, er as getSchemaDescription, et as codeModeDeclarationHash, f as projectBindingRecovery, fn as ReadBuffer, ft as resolveTelemetryState, g as CAPLETS_ATTACH_SESSION_HEADER, gn as AjvJsonSchemaValidator, gt as readTelemetryNotice, hn as assertToolsCallTaskCapability, ht as readTelemetryIdentity, i as createRemoteProfileStore, in as defaultConfigBaseDir, ir as objectFromShape, it as CapletsEngine, j as appendBasePath, jn as InitializeRequestSchema, jt as parseConfig, k as resolveHostedCloudRemote, kn as ErrorCode, kt as loadLocalOverlayConfigWithSources, l as PROJECT_BINDING_ERROR_CODES, ln as resolveConfigPath, lt as buildReliabilityTelemetryEvent, mn as assertClientRequestTaskCapability, mt as readTelemetryDeliveryHealth, n as resolveRemoteSelection, nn as DEFAULT_OBSERVED_OUTPUT_SHAPE_CACHE_DIR, nr as isZ4Schema, nt as generateCodeModeRunToolDescription, o as cloudAuthPath, on as defaultStateBaseDir, or as safeParseAsync, ot as createTelemetryDispatcher, p as CloudAuthClient, pn as serializeMessage, pt as deleteTelemetryIdentity, q as QuickJsCodeModeSandbox, qn as isInitializeRequest, qt as runGenericOAuthFlow, r as cloudCredentialsFromRemoteProfile, rn as defaultCacheBaseDir, rr as normalizeObjectSchema, rt as minifyCodeModeDeclarationText, s as migrateCredentials, sn as defaultTelemetryStateDir, st as TelemetryDebugSink, t as createNativeCapletsService, tr as isSchemaOptional, tt as generateCodeModeDeclarations, u as ProjectBindingError, un as resolveProjectCapletsRoot, ut as durationBucket, v as buildAttachProjection, vn as mergeCapabilities, vt as resolveExposure, wn as CreateMessageResultWithToolsSchema, wt as capabilityDescription, x as invokeNativeAttachExport, xn as CallToolResultSchema, xt as fingerprintProjectRoot, y as buildNativeAttachProjection, yn as toJsonSchemaCompat, yt as decodeDirectResourceUri, zn as ListToolsRequestSchema, zt as encryptVaultValue } from "./service-B6YvNthO.js";
2
2
  import { _ as record, b as unknown, d as literal, m as object, n as ZodOptional, o as array, p as number, r as _enum, s as boolean, v as string, x as url } from "./schemas-BoqMu4MG.js";
3
3
  import { a as SERVER_ID_PATTERN, d as CapletsError, m as toSafeError, p as redactSecrets$1, u as CAPLETS_ERROR_CODES } from "./validation-CWzd2gtn.js";
4
- import { generatedToolInputJsonSchemaForCaplet, generatedToolInputSchema, generatedToolInputSchemaForCaplet } from "./generated-tool-input-schema.js";
5
- import { f as observedOutputShapeKey, g as stableJsonStringify, h as schemaHash, i as observeOutputShape, u as FileObservedOutputShapeStore } from "./observed-output-shapes-DuP7mJQf.js";
6
- import { a as formatCapletList, c as resolveCliConfigPaths, l as cliCommands$1, n as completionScript, o as formatConfigPaths, s as listCaplets, t as completeCliWords, u as completionShells } from "./completion-1wDjwHkC.js";
4
+ import { generatedToolInputSchema, generatedToolInputSchemaForCaplet } from "./generated-tool-input-schema.js";
5
+ import { f as observedOutputShapeKey, i as observeOutputShape, u as FileObservedOutputShapeStore } from "./observed-output-shapes-DuP7mJQf.js";
6
+ import { a as formatCapletList, c as resolveCliConfigPaths, l as cliCommands$1, n as completionScript, o as formatConfigPaths, s as listCaplets, t as completeCliWords, u as completionShells } from "./completion-D9253pYs.js";
7
7
  import { n as normalizeCapletSourcePath, t as FilesystemCapletSource } from "./filesystem-Kkg32TOJ.js";
8
8
  import { parseConfig as parseConfig$1 } from "./config-runtime.js";
9
9
  import fs, { accessSync, chmodSync, closeSync, constants, copyFileSync, cpSync, existsSync, fstatSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, statSync, watch, writeFileSync, writeSync } from "node:fs";
@@ -13,7 +13,7 @@ import process$1, { stdin, stdout } from "node:process";
13
13
  import { Readable, Writable } from "node:stream";
14
14
  import { STATUS_CODES, createServer } from "node:http";
15
15
  import { createHash, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
16
- import { homedir, tmpdir, userInfo } from "node:os";
16
+ import { arch, homedir, platform, tmpdir, userInfo } from "node:os";
17
17
  import { Buffer as Buffer$1 } from "node:buffer";
18
18
  import { EventEmitter } from "node:events";
19
19
  import { promisify, stripVTControlCharacters } from "node:util";
@@ -1552,9 +1552,6 @@ const EMPTY_COMPLETION_RESULT = { completion: {
1552
1552
  hasMore: false
1553
1553
  } };
1554
1554
  //#endregion
1555
- //#region package.json
1556
- var version = "0.27.0";
1557
- //#endregion
1558
1555
  //#region src/serve/session.ts
1559
1556
  var CapletsMcpSession = class {
1560
1557
  engine;
@@ -1634,7 +1631,7 @@ var CapletsMcpSession = class {
1634
1631
  downstreamName: entry.downstreamName,
1635
1632
  exposure: "direct"
1636
1633
  } },
1637
- callback: async (request) => this.engine.executeDirectTool(entry.caplet.server, entry.downstreamName, isRecord$4(request) ? request : {}),
1634
+ callback: async (request) => this.engine.executeDirectTool(entry.caplet.server, entry.downstreamName, isRecord$3(request) ? request : {}),
1638
1635
  enabled: true
1639
1636
  })
1640
1637
  });
@@ -1672,6 +1669,7 @@ var CapletsMcpSession = class {
1672
1669
  }, async (request) => this.handleCodeModeRunTool(request));
1673
1670
  }
1674
1671
  async handleCodeModeRunTool(request) {
1672
+ const started = Date.now();
1675
1673
  const parsed = codeModeRunInputSchema.safeParse(request);
1676
1674
  const envelope = parsed.success ? await runCodeMode({
1677
1675
  code: parsed.data.code,
@@ -1697,6 +1695,10 @@ var CapletsMcpSession = class {
1697
1695
  },
1698
1696
  meta: emptyCodeModeRunMeta()
1699
1697
  };
1698
+ this.engine.captureCodeModeOutcome(envelope, {
1699
+ started,
1700
+ ...parsed.success && parsed.data.timeoutMs !== void 0 ? { timeoutMs: parsed.data.timeoutMs } : {}
1701
+ }).catch(() => void 0);
1700
1702
  return {
1701
1703
  content: [{
1702
1704
  type: "text",
@@ -1725,7 +1727,7 @@ var CapletsMcpSession = class {
1725
1727
  downstreamName: entry.downstreamName,
1726
1728
  exposure: "direct"
1727
1729
  } }
1728
- }, async (request) => this.engine.executeDirectTool(entry.caplet.server, entry.downstreamName, isRecord$4(request) ? request : {}));
1730
+ }, async (request) => this.engine.executeDirectTool(entry.caplet.server, entry.downstreamName, isRecord$3(request) ? request : {}));
1729
1731
  }
1730
1732
  registerDirectResource(entry) {
1731
1733
  if (!this.server.registerResource) throw new Error("MCP server does not support resource registration");
@@ -1744,11 +1746,11 @@ var CapletsMcpSession = class {
1744
1746
  title: entry.prompt.name,
1745
1747
  ...entry.prompt.description ? { description: entry.prompt.description } : {},
1746
1748
  argsSchema: promptArgsSchema(entry.prompt.arguments)
1747
- }, async (args) => await this.engine.getDirectPrompt(entry.caplet.server, entry.downstreamName, isRecord$4(args) ? stringifyRecord$1(args) : {}));
1749
+ }, async (args) => await this.engine.getDirectPrompt(entry.caplet.server, entry.downstreamName, isRecord$3(args) ? stringifyRecord(args) : {}));
1748
1750
  }
1749
1751
  async directResourceResult(serverId, downstreamUri) {
1750
1752
  const result = await this.engine.readDirectResource(serverId, downstreamUri);
1751
- if (isRecord$4(result) && "contents" in result) return result;
1753
+ if (isRecord$3(result) && "contents" in result) return result;
1752
1754
  return { contents: [{
1753
1755
  uri: downstreamUri,
1754
1756
  mimeType: "application/json",
@@ -1819,10 +1821,10 @@ function promptArgsSchema(args) {
1819
1821
  for (const arg of args ?? []) shape[arg.name] = string().optional();
1820
1822
  return shape;
1821
1823
  }
1822
- function stringifyRecord$1(value) {
1824
+ function stringifyRecord(value) {
1823
1825
  return Object.fromEntries(Object.entries(value).map(([key, nested]) => [key, nested === void 0 ? "" : String(nested)]));
1824
1826
  }
1825
- function isRecord$4(value) {
1827
+ function isRecord$3(value) {
1826
1828
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1827
1829
  }
1828
1830
  function staticExposureSnapshot(config, caplets) {
@@ -5524,7 +5526,12 @@ async function runCodeModeCli(options) {
5524
5526
  mode: "local",
5525
5527
  ...options.configPath ? { configPath: options.configPath } : {},
5526
5528
  ...options.projectConfigPath ? { projectConfigPath: options.projectConfigPath } : {},
5527
- ...options.authDir ? { authDir: options.authDir } : {}
5529
+ ...options.authDir ? { authDir: options.authDir } : {},
5530
+ telemetryEnv: options.env,
5531
+ telemetryStateDir: options.telemetryStateDir ?? defaultTelemetryStateDir(options.env),
5532
+ telemetrySurface: "code_mode",
5533
+ telemetryVisibility: "visible",
5534
+ telemetryRuntimeMode: runtimeScope(options.env) === "local" ? "local" : "unknown"
5528
5535
  });
5529
5536
  try {
5530
5537
  if (options.sessionId !== void 0) {
@@ -5547,12 +5554,18 @@ async function runCodeModeCli(options) {
5547
5554
  options.setExitCode(1);
5548
5555
  return;
5549
5556
  }
5557
+ const code = await readCodeModeCliCode(options);
5558
+ const started = Date.now();
5550
5559
  const result = await runCodeMode({
5551
- code: await readCodeModeCliCode(options),
5552
- service: service.codeModeService?.() ?? service,
5560
+ code,
5553
5561
  ...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs },
5562
+ service: service.codeModeService?.() ?? service,
5554
5563
  runtimeScope: "cli-one-shot"
5555
5564
  });
5565
+ await service.captureCodeModeOutcome?.(result, {
5566
+ started,
5567
+ ...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs }
5568
+ }).catch(() => void 0);
5556
5569
  if (options.json) options.writeOut(`${JSON.stringify(result, null, 2)}\n`);
5557
5570
  else if (result.ok) options.writeOut(`${formatHumanValue(result.value)}\n`);
5558
5571
  else {
@@ -5587,7 +5600,8 @@ async function codeModeTypesCli(options) {
5587
5600
  const engine = new CapletsEngine({
5588
5601
  ...options.configPath ? { configPath: options.configPath } : {},
5589
5602
  ...options.projectConfigPath ? { projectConfigPath: options.projectConfigPath } : {},
5590
- ...options.authDir ? { authDir: options.authDir } : {}
5603
+ ...options.authDir ? { authDir: options.authDir } : {},
5604
+ telemetryStateDir: options.telemetryStateDir ?? defaultTelemetryStateDir(options.env)
5591
5605
  });
5592
5606
  try {
5593
5607
  const caplets = listCodeModeCallableCaplets$1(engine);
@@ -6919,14 +6933,24 @@ const HTTP_ONLY_OPTIONS = [
6919
6933
  "port",
6920
6934
  "path",
6921
6935
  "remoteStatePath",
6936
+ "upstreamUrl",
6922
6937
  "allowUnauthenticatedHttp",
6923
6938
  "trustProxy"
6924
6939
  ];
6940
+ const HTTP_ONLY_OPTION_FLAGS = {
6941
+ host: "--host",
6942
+ port: "--port",
6943
+ path: "--path",
6944
+ remoteStatePath: "--remote-state-path",
6945
+ upstreamUrl: "--upstream-url",
6946
+ allowUnauthenticatedHttp: "--allow-unauthenticated-http",
6947
+ trustProxy: "--trust-proxy"
6948
+ };
6925
6949
  function resolveServeOptions(raw, env = process.env) {
6926
6950
  const transport = parseTransport(raw.transport ?? "stdio");
6927
6951
  if (transport === "stdio") {
6928
6952
  const invalid = HTTP_ONLY_OPTIONS.filter((key) => raw[key] !== void 0);
6929
- if (invalid.length > 0) throw new CapletsError("REQUEST_INVALID", `${invalid.map((key) => `--${key}`).join(", ")} ${invalid.length === 1 ? "is" : "are"} only valid with --transport http`);
6953
+ if (invalid.length > 0) throw new CapletsError("REQUEST_INVALID", `${invalid.map((key) => HTTP_ONLY_OPTION_FLAGS[key]).join(", ")} ${invalid.length === 1 ? "is" : "are"} only valid with --transport http`);
6930
6954
  return { transport };
6931
6955
  }
6932
6956
  const serverUrl = env.CAPLETS_SERVER_URL ? parseServeServerUrl(nonEmpty$2(env.CAPLETS_SERVER_URL, "CAPLETS_SERVER_URL")) : void 0;
@@ -6934,6 +6958,13 @@ function resolveServeOptions(raw, env = process.env) {
6934
6958
  const port = parsePort(raw.port ?? (serverUrl?.port ? Number(serverUrl.port) : 5387));
6935
6959
  const path = normalizeHttpPath(raw.path ?? serverUrl?.pathname ?? "/");
6936
6960
  const remoteCredentialStateDir = nonEmpty$2(raw.remoteStatePath, "--remote-state-path") ?? nonEmpty$2(env.CAPLETS_REMOTE_SERVER_STATE_DIR, "CAPLETS_REMOTE_SERVER_STATE_DIR") ?? join(DEFAULT_AUTH_DIR, "remote-server");
6961
+ const upstreamUrl = nonEmpty$2(raw.upstreamUrl, "--upstream-url");
6962
+ if (upstreamUrl) rejectSelfReferentialUpstream(upstreamUrl, {
6963
+ ...serverUrl ? { origin: serverUrl.origin } : {},
6964
+ host,
6965
+ port,
6966
+ path
6967
+ });
6937
6968
  const loopback = isLoopbackHost(host);
6938
6969
  const auth = raw.allowUnauthenticatedHttp === true ? { type: "development_unauthenticated" } : { type: "remote_credentials" };
6939
6970
  return {
@@ -6944,6 +6975,7 @@ function resolveServeOptions(raw, env = process.env) {
6944
6975
  ...serverUrl ? { publicOrigin: serverUrl.origin } : {},
6945
6976
  auth,
6946
6977
  ...auth.type === "remote_credentials" ? { remoteCredentialStateDir } : {},
6978
+ ...upstreamUrl ? { upstreamUrl } : {},
6947
6979
  allowUnauthenticatedHttp: raw.allowUnauthenticatedHttp === true,
6948
6980
  warnUnauthenticatedNetwork: !loopback && auth.type === "development_unauthenticated",
6949
6981
  loopback,
@@ -6975,6 +7007,42 @@ function normalizeHttpPath(value) {
6975
7007
  function serverUrlHost(url) {
6976
7008
  return url?.hostname.replace(/^\[(.*)\]$/u, "$1");
6977
7009
  }
7010
+ function rejectSelfReferentialUpstream(upstreamUrl, local) {
7011
+ if (sameServerBase(parseServerBaseUrl(upstreamUrl), localServeBaseUrl(local))) throw new CapletsError("REQUEST_INVALID", "--upstream-url must not point back to this runtime.");
7012
+ }
7013
+ function localServeBaseUrl(local) {
7014
+ const origin = local.origin ?? `http://${formatHost$3(local.host)}:${local.port}`;
7015
+ const url = new URL(origin);
7016
+ url.pathname = local.path;
7017
+ url.search = "";
7018
+ url.hash = "";
7019
+ return url;
7020
+ }
7021
+ function sameServerBase(left, right) {
7022
+ return left.protocol === right.protocol && sameHost(left.hostname, right.hostname) && effectivePort(left) === effectivePort(right) && normalizePath(left.pathname) === normalizePath(right.pathname);
7023
+ }
7024
+ function sameHost(left, right) {
7025
+ if (left === right) return true;
7026
+ const normalizedLeft = normalizeLoopbackHost(left);
7027
+ const normalizedRight = normalizeLoopbackHost(right);
7028
+ return normalizedLeft !== void 0 && normalizedLeft === normalizedRight;
7029
+ }
7030
+ function normalizeLoopbackHost(host) {
7031
+ const normalized = host.toLowerCase().replace(/^\[(.*)\]$/u, "$1");
7032
+ if (normalized === "localhost" || normalized === "::1") return "loopback";
7033
+ if (normalized === "0.0.0.0" || normalized === "::") return "loopback";
7034
+ if (/^127(?:\.\d{1,3}){3}$/u.test(normalized)) return "loopback";
7035
+ }
7036
+ function effectivePort(url) {
7037
+ if (url.port) return url.port;
7038
+ return url.protocol === "https:" ? "443" : "80";
7039
+ }
7040
+ function normalizePath(path) {
7041
+ return path === "/" ? "/" : path.replace(/\/+$/u, "");
7042
+ }
7043
+ function formatHost$3(host) {
7044
+ return host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
7045
+ }
6978
7046
  function nonEmpty$2(value, label) {
6979
7047
  if (value === void 0) return;
6980
7048
  const trimmed = value.trim();
@@ -7064,6 +7132,7 @@ function daemonServeArgs(options) {
7064
7132
  options.path
7065
7133
  ];
7066
7134
  if (options.auth.type === "remote_credentials" && options.remoteCredentialStateDir) args.push("--remote-state-path", options.remoteCredentialStateDir);
7135
+ if (options.upstreamUrl) args.push("--upstream-url", options.upstreamUrl);
7067
7136
  if (options.allowUnauthenticatedHttp) args.push("--allow-unauthenticated-http");
7068
7137
  if (options.trustProxy) args.push("--trust-proxy");
7069
7138
  return args;
@@ -10481,291 +10550,6 @@ var logger = (fn = console.log) => {
10481
10550
  };
10482
10551
  };
10483
10552
  //#endregion
10484
- //#region src/attach/api.ts
10485
- async function buildAttachProjection(engine) {
10486
- const snapshot = await engine.exposureSnapshot();
10487
- const partial = sortAttachProjectionInput({
10488
- caplets: snapshot.progressiveCaplets.map(progressiveCapletExport),
10489
- tools: snapshot.directTools.map(toolExport),
10490
- resources: snapshot.directResources.map(resourceExport),
10491
- resourceTemplates: snapshot.directResourceTemplates.map(resourceTemplateExport),
10492
- prompts: snapshot.directPrompts.map(promptExport),
10493
- completions: completionExports(snapshot),
10494
- codeModeCaplets: snapshot.codeModeCaplets.map(codeModeCapletExport),
10495
- diagnostics: snapshot.hiddenCaplets.map((hidden) => ({
10496
- code: `ATTACH_CAPLET_${hidden.reason.toUpperCase()}`,
10497
- message: `Caplet ${hidden.capletId} is not exported: ${hidden.reason}.`,
10498
- capletId: hidden.capletId,
10499
- ...hidden.error ? { details: hidden.error } : {}
10500
- }))
10501
- });
10502
- const revision = revisionFor(partial);
10503
- const manifest = {
10504
- version: 1,
10505
- revision,
10506
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
10507
- ...withRevisionExportIds(revision, partial)
10508
- };
10509
- return {
10510
- manifest,
10511
- routes: routesFor(manifest)
10512
- };
10513
- }
10514
- async function invokeAttachExport(engine, projection, request) {
10515
- if (request.revision !== projection.manifest.revision) throw new CapletsError("ATTACH_MANIFEST_STALE", "Attach manifest revision is stale.");
10516
- const route = projection.routes.get(request.exportId);
10517
- if (!route || route.kind !== request.kind) throw new CapletsError("ATTACH_EXPORT_NOT_FOUND", "Attach export was not found.");
10518
- if (route.kind === "caplet") return await engine.execute(route.capletId, request.input);
10519
- if (route.kind === "tool") return await engine.executeDirectTool(route.capletId, route.downstreamName, isRecord$3(request.input) ? request.input : {});
10520
- if (route.kind === "resource") return await engine.readDirectResource(route.capletId, route.downstreamUri);
10521
- if (route.kind === "resourceTemplate") {
10522
- const uri = isRecord$3(request.input) && typeof request.input.uri === "string" ? request.input.uri : void 0;
10523
- if (!uri) throw new CapletsError("REQUEST_INVALID", "Attach resource template invoke requires input.uri.");
10524
- const downstreamUri = downstreamResourceUri(route.capletId, uri);
10525
- if (!directResourceUriMatchesTemplate(downstreamUri, route.downstreamUriTemplate)) throw new CapletsError("ATTACH_EXPORT_NOT_FOUND", "Attach resource URI does not match the exported resource template.");
10526
- return await engine.readDirectResource(route.capletId, downstreamUri);
10527
- }
10528
- if (route.kind === "prompt") return await engine.getDirectPrompt(route.capletId, route.downstreamName, isRecord$3(request.input) ? stringifyRecord(request.input) : {});
10529
- if (route.kind === "completion") return await engine.execute(route.capletId, {
10530
- ...normalizeCompletionInput(projection.manifest, route.capletId, request.input),
10531
- operation: "complete"
10532
- });
10533
- throw new CapletsError("REQUEST_INVALID", "Attach export kind is not invokable via /v1/attach/invoke.");
10534
- }
10535
- function attachErrorResponse(error) {
10536
- const safe = toSafeError(error, "INTERNAL_ERROR");
10537
- return {
10538
- status: safe.code === "ATTACH_MANIFEST_STALE" ? 409 : safe.code === "ATTACH_EXPORT_NOT_FOUND" ? 404 : safe.code === "REQUEST_INVALID" ? 400 : 500,
10539
- body: {
10540
- ok: false,
10541
- error: safe
10542
- }
10543
- };
10544
- }
10545
- function progressiveCapletExport(entry) {
10546
- const inputSchema = generatedToolInputJsonSchemaForCaplet(entry.caplet);
10547
- return {
10548
- stableId: `progressive:${entry.caplet.server}`,
10549
- kind: "caplet",
10550
- name: entry.caplet.server,
10551
- title: entry.caplet.name,
10552
- description: entry.caplet.description,
10553
- inputSchema,
10554
- schemaHash: schemaHash(inputSchema),
10555
- capletId: entry.caplet.server,
10556
- shadowing: shadowingPolicy(entry.caplet)
10557
- };
10558
- }
10559
- function codeModeCapletExport(entry) {
10560
- return {
10561
- stableId: `code_mode:${entry.caplet.server}`,
10562
- kind: "caplet",
10563
- name: entry.caplet.name,
10564
- title: entry.caplet.name,
10565
- description: entry.caplet.description,
10566
- schemaHash: null,
10567
- capletId: entry.caplet.server,
10568
- shadowing: shadowingPolicy(entry.caplet)
10569
- };
10570
- }
10571
- function toolExport(entry) {
10572
- return {
10573
- stableId: `tool:${entry.caplet.server}:${entry.downstreamName}`,
10574
- kind: "tool",
10575
- name: entry.name,
10576
- downstreamName: entry.downstreamName,
10577
- title: entry.tool.name,
10578
- description: entry.tool.description,
10579
- inputSchema: entry.tool.inputSchema,
10580
- outputSchema: entry.tool.outputSchema,
10581
- annotations: entry.tool.annotations,
10582
- schemaHash: schemaHash({
10583
- input: entry.tool.inputSchema,
10584
- output: entry.tool.outputSchema
10585
- }),
10586
- capletId: entry.caplet.server,
10587
- shadowing: shadowingPolicy(entry.caplet)
10588
- };
10589
- }
10590
- function resourceExport(entry) {
10591
- return {
10592
- stableId: `resource:${entry.caplet.server}:${entry.downstreamUri}`,
10593
- kind: "resource",
10594
- uri: entry.uri,
10595
- downstreamUri: entry.downstreamUri,
10596
- title: entry.resource.name,
10597
- description: entry.resource.description,
10598
- ...entry.resource.mimeType ? { mimeType: entry.resource.mimeType } : {},
10599
- ...typeof entry.resource.size === "number" ? { size: entry.resource.size } : {},
10600
- schemaHash: null,
10601
- capletId: entry.caplet.server,
10602
- shadowing: shadowingPolicy(entry.caplet)
10603
- };
10604
- }
10605
- function resourceTemplateExport(entry) {
10606
- return {
10607
- stableId: `resourceTemplate:${entry.caplet.server}:${entry.downstreamUriTemplate}`,
10608
- kind: "resourceTemplate",
10609
- uriTemplate: entry.uriTemplate,
10610
- downstreamUriTemplate: entry.downstreamUriTemplate,
10611
- title: entry.resourceTemplate.name,
10612
- description: entry.resourceTemplate.description,
10613
- ...entry.resourceTemplate.mimeType ? { mimeType: entry.resourceTemplate.mimeType } : {},
10614
- schemaHash: null,
10615
- capletId: entry.caplet.server,
10616
- shadowing: shadowingPolicy(entry.caplet)
10617
- };
10618
- }
10619
- function promptExport(entry) {
10620
- const inputSchema = { arguments: entry.prompt.arguments ?? [] };
10621
- return {
10622
- stableId: `prompt:${entry.caplet.server}:${entry.downstreamName}`,
10623
- kind: "prompt",
10624
- name: entry.name,
10625
- downstreamName: entry.downstreamName,
10626
- title: entry.prompt.name,
10627
- description: entry.prompt.description,
10628
- inputSchema,
10629
- schemaHash: schemaHash(inputSchema),
10630
- capletId: entry.caplet.server,
10631
- shadowing: shadowingPolicy(entry.caplet)
10632
- };
10633
- }
10634
- function completionExports(snapshot) {
10635
- return [...new Map([...snapshot.directPrompts, ...snapshot.directResourceTemplates].map((entry) => [entry.caplet.server, entry.caplet])).entries()].sort(([left], [right]) => left.localeCompare(right)).map(([capletId, caplet]) => ({
10636
- stableId: `completion:${capletId}`,
10637
- kind: "completion",
10638
- name: `${capletId}:complete`,
10639
- title: "Complete",
10640
- description: `MCP completion for ${capletId}.`,
10641
- schemaHash: null,
10642
- capletId,
10643
- shadowing: shadowingPolicy(caplet)
10644
- }));
10645
- }
10646
- function shadowingPolicy(caplet) {
10647
- return caplet.shadowing ?? "forbid";
10648
- }
10649
- function sortAttachProjectionInput(partial) {
10650
- return {
10651
- caplets: sortByStableId(partial.caplets),
10652
- tools: sortByStableId(partial.tools),
10653
- resources: sortByStableId(partial.resources),
10654
- resourceTemplates: sortByStableId(partial.resourceTemplates),
10655
- prompts: sortByStableId(partial.prompts),
10656
- completions: sortByStableId(partial.completions),
10657
- codeModeCaplets: sortByStableId(partial.codeModeCaplets),
10658
- diagnostics: [...partial.diagnostics].sort((left, right) => diagnosticSortKey(left).localeCompare(diagnosticSortKey(right)))
10659
- };
10660
- }
10661
- function sortByStableId(entries) {
10662
- return [...entries].sort((left, right) => left.stableId.localeCompare(right.stableId));
10663
- }
10664
- function diagnosticSortKey(diagnostic) {
10665
- return stableJsonStringify({
10666
- code: diagnostic.code,
10667
- capletId: diagnostic.capletId ?? "",
10668
- message: diagnostic.message
10669
- });
10670
- }
10671
- function revisionFor(value) {
10672
- return `sha256:${createHash("sha256").update(stableJsonStringify(value)).digest("hex")}`;
10673
- }
10674
- function withRevisionExportIds(revision, partial) {
10675
- return {
10676
- ...partial,
10677
- caplets: partial.caplets.map((entry) => withExportId(revision, entry)),
10678
- tools: partial.tools.map((entry) => withExportId(revision, entry)),
10679
- resources: partial.resources.map((entry) => withExportId(revision, entry)),
10680
- resourceTemplates: partial.resourceTemplates.map((entry) => withExportId(revision, entry)),
10681
- prompts: partial.prompts.map((entry) => withExportId(revision, entry)),
10682
- completions: partial.completions.map((entry) => withExportId(revision, entry)),
10683
- codeModeCaplets: partial.codeModeCaplets.map((entry) => withExportId(revision, entry))
10684
- };
10685
- }
10686
- function withExportId(revision, entry) {
10687
- return {
10688
- ...entry,
10689
- exportId: `${revision}:${entry.stableId}`
10690
- };
10691
- }
10692
- function routesFor(manifest) {
10693
- const routes = /* @__PURE__ */ new Map();
10694
- for (const entry of manifest.caplets) routes.set(entry.exportId, {
10695
- kind: "caplet",
10696
- capletId: entry.capletId
10697
- });
10698
- for (const entry of manifest.tools) routes.set(entry.exportId, {
10699
- kind: "tool",
10700
- capletId: entry.capletId,
10701
- downstreamName: entry.downstreamName
10702
- });
10703
- for (const entry of manifest.resources) routes.set(entry.exportId, {
10704
- kind: "resource",
10705
- capletId: entry.capletId,
10706
- downstreamUri: entry.downstreamUri
10707
- });
10708
- for (const entry of manifest.resourceTemplates) routes.set(entry.exportId, {
10709
- kind: "resourceTemplate",
10710
- capletId: entry.capletId,
10711
- downstreamUriTemplate: entry.downstreamUriTemplate
10712
- });
10713
- for (const entry of manifest.prompts) routes.set(entry.exportId, {
10714
- kind: "prompt",
10715
- capletId: entry.capletId,
10716
- downstreamName: entry.downstreamName
10717
- });
10718
- for (const entry of manifest.completions) routes.set(entry.exportId, {
10719
- kind: "completion",
10720
- capletId: entry.capletId
10721
- });
10722
- for (const entry of manifest.codeModeCaplets) routes.set(entry.exportId, {
10723
- kind: "caplet",
10724
- capletId: entry.capletId
10725
- });
10726
- return routes;
10727
- }
10728
- function normalizeCompletionInput(manifest, capletId, input) {
10729
- if (!isRecord$3(input)) return {};
10730
- const ref = input.ref;
10731
- if (!isRecord$3(ref)) return input;
10732
- if (ref.type === "prompt" && typeof ref.name === "string") {
10733
- const prompt = manifest.prompts.find((entry) => entry.capletId === capletId && (entry.name === ref.name || entry.downstreamName === ref.name));
10734
- if (!prompt) return input;
10735
- return {
10736
- ...input,
10737
- ref: {
10738
- ...ref,
10739
- name: prompt.downstreamName
10740
- }
10741
- };
10742
- }
10743
- if (ref.type === "resourceTemplate" && typeof ref.uri === "string") {
10744
- const resourceTemplate = manifest.resourceTemplates.find((entry) => entry.capletId === capletId && (entry.uriTemplate === ref.uri || entry.downstreamUriTemplate === ref.uri));
10745
- if (!resourceTemplate) return input;
10746
- return {
10747
- ...input,
10748
- ref: {
10749
- ...ref,
10750
- uri: resourceTemplate.downstreamUriTemplate
10751
- }
10752
- };
10753
- }
10754
- return input;
10755
- }
10756
- function isRecord$3(value) {
10757
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
10758
- }
10759
- function stringifyRecord(record) {
10760
- return Object.fromEntries(Object.entries(record).map(([key, value]) => [key, typeof value === "string" ? value : JSON.stringify(value)]));
10761
- }
10762
- function downstreamResourceUri(capletId, uri) {
10763
- if (!uri.startsWith("caplets://")) return uri;
10764
- const decoded = decodeDirectResourceUri(uri);
10765
- if (decoded.capletId !== capletId) throw new CapletsError("ATTACH_EXPORT_NOT_FOUND", "Attach resource template URI belongs to a different Caplet.");
10766
- return decoded.downstreamUri;
10767
- }
10768
- //#endregion
10769
10553
  //#region src/cli/install.ts
10770
10554
  function installCaplets(repo, options = {}) {
10771
10555
  const source = resolveInstallSource(repo);
@@ -12109,14 +11893,24 @@ function parseSupersededRefreshTokens(value) {
12109
11893
  }
12110
11894
  //#endregion
12111
11895
  //#region src/serve/http.ts
11896
+ const CAPLETS_STACK_CHAIN_HEADER = "caplets-stack-chain";
11897
+ const ATTACH_SESSION_IDLE_TIMEOUT_MS = 10 * 6e4;
11898
+ const ATTACH_SESSION_PRUNE_INTERVAL_MS = 6e4;
12112
11899
  function createHttpServeApp(options, engine, io = {}) {
12113
11900
  const app = new Hono();
12114
11901
  const sessions = /* @__PURE__ */ new Map();
11902
+ const attachSessions = /* @__PURE__ */ new Map();
11903
+ const defaultAttachSessions = /* @__PURE__ */ new Map();
11904
+ const defaultAttachSessionPromises = /* @__PURE__ */ new Map();
12115
11905
  const attachEventStreams = /* @__PURE__ */ new Set();
11906
+ const attachSessionPruneTimer = setInterval(() => pruneIdleAttachSessions(), ATTACH_SESSION_PRUNE_INTERVAL_MS);
11907
+ attachSessionPruneTimer.unref?.();
12116
11908
  const writeErr = io.writeErr ?? process.stderr.write.bind(process.stderr);
12117
11909
  const paths = servicePaths(options.path);
11910
+ const stackIdentity = httpStackIdentity(options);
12118
11911
  const authFlowStore = io.authFlowStore ?? new RemoteAuthFlowStore();
12119
11912
  const exposeAttach = io.exposeAttach ?? true;
11913
+ const exposeAttachSessions = exposeAttach && Boolean(io.attachSessionFactory);
12120
11914
  const remoteCredentialStore = remoteCredentialStoreForOptions(options, io.remoteCredentialStore);
12121
11915
  if (options.auth.type === "remote_credentials" && options.trustProxy === true && options.publicOrigin === void 0) throw new CapletsError("REQUEST_INVALID", "Remote credential auth with --trust-proxy requires CAPLETS_SERVER_URL.");
12122
11916
  const protectedRouteAuth = routeAuth(options, remoteCredentialStore, paths.base);
@@ -12130,14 +11924,20 @@ function createHttpServeApp(options, engine, io = {}) {
12130
11924
  name: "caplets",
12131
11925
  transport: "http",
12132
11926
  base: paths.base,
12133
- versions: [versionDiscovery(paths, exposeAttach, remote)],
11927
+ versions: [versionDiscovery(paths, {
11928
+ exposeAttach,
11929
+ exposeAttachSessions
11930
+ }, remote)],
12134
11931
  auth: { type: options.auth.type },
12135
11932
  ...remote ? { remote } : {}
12136
11933
  });
12137
11934
  });
12138
11935
  app.get(paths.version, (c) => {
12139
11936
  const remote = remoteCredentialStore ? remoteHostMetadata(c.req.url, paths.base, options, (name) => c.req.header(name)) : void 0;
12140
- return c.json(versionDiscovery(paths, exposeAttach, remote));
11937
+ return c.json(versionDiscovery(paths, {
11938
+ exposeAttach,
11939
+ exposeAttachSessions
11940
+ }, remote));
12141
11941
  });
12142
11942
  app.get(paths.health, (c) => c.json({ status: "ok" }));
12143
11943
  if (remoteCredentialStore) {
@@ -12272,14 +12072,68 @@ function createHttpServeApp(options, engine, io = {}) {
12272
12072
  return session.transport.handleRequest(c);
12273
12073
  });
12274
12074
  if (exposeAttach) {
12075
+ if (io.attachSessionFactory) {
12076
+ app.post(paths.attachSessions, attachHostProtection, protectedRouteAuth, async (c) => {
12077
+ try {
12078
+ const metadata = parseAttachSessionMetadata(await parseJsonObject(c.req.json(), "Attach session request"), { allowProjectContext: allowAttachSessionProjectContext(options, c.req.url, (name) => c.req.header(name)) });
12079
+ const context = attachSessionContext(c.req.header(CAPLETS_STACK_CHAIN_HEADER));
12080
+ const sessionId = randomUUID();
12081
+ const session = await io.attachSessionFactory(metadata, context);
12082
+ attachSessions.set(sessionId, {
12083
+ session,
12084
+ lastUsedAt: Date.now()
12085
+ });
12086
+ pruneIdleAttachSessions();
12087
+ return c.json({ sessionId }, 201);
12088
+ } catch (error) {
12089
+ const response = attachErrorResponse(error);
12090
+ return c.json(response.body, response.status);
12091
+ }
12092
+ });
12093
+ app.delete(routePath(paths.attachSessions, ":sessionId"), attachHostProtection, protectedRouteAuth, async (c) => {
12094
+ const sessionId = c.req.param("sessionId");
12095
+ if (!sessionId) return c.json({
12096
+ ok: false,
12097
+ error: { code: "REQUEST_INVALID" }
12098
+ }, 400);
12099
+ const record = attachSessions.get(sessionId);
12100
+ attachSessions.delete(sessionId);
12101
+ await record?.session.close();
12102
+ return c.json({ ok: true });
12103
+ });
12104
+ }
12275
12105
  app.get(paths.attachManifest, attachHostProtection, protectedRouteAuth, async (c) => {
12276
- const attachProjection = await buildAttachProjection(engine);
12277
- return c.json(attachProjection.manifest);
12106
+ try {
12107
+ const attachSessionId = c.req.header(CAPLETS_ATTACH_SESSION_HEADER);
12108
+ const attachSession = attachSessionId ? attachSessionForRequest(attachSessionId) : await fallbackAttachSession(attachSessionContext(c.req.header(CAPLETS_STACK_CHAIN_HEADER)));
12109
+ if (attachSession) return c.json(await attachSession.manifest());
12110
+ const attachProjection = await buildAttachProjection(engine);
12111
+ return c.json(attachProjection.manifest);
12112
+ } catch (error) {
12113
+ const response = attachErrorResponse(error);
12114
+ return c.json(response.body, response.status);
12115
+ }
12116
+ });
12117
+ app.get(paths.attachEvents, attachHostProtection, protectedRouteAuth, async (c) => {
12118
+ try {
12119
+ const attachSessionId = c.req.header(CAPLETS_ATTACH_SESSION_HEADER);
12120
+ return attachEventsResponse(attachEventSource(engine, attachSessionId ? attachSessionForRequest(attachSessionId) : await fallbackAttachSession(attachSessionContext(c.req.header(CAPLETS_STACK_CHAIN_HEADER)))), attachEventStreams, { onActivity: () => {
12121
+ if (attachSessionId) touchAttachSession(attachSessionId);
12122
+ } });
12123
+ } catch (error) {
12124
+ const response = attachErrorResponse(error);
12125
+ return c.json(response.body, response.status);
12126
+ }
12278
12127
  });
12279
- app.get(paths.attachEvents, attachHostProtection, protectedRouteAuth, () => attachEventsResponse(engine, attachEventStreams));
12280
12128
  app.post(paths.attachInvoke, attachHostProtection, protectedRouteAuth, async (c) => {
12281
12129
  try {
12282
12130
  const request = await parseAttachInvokeRequest(c.req.json());
12131
+ const attachSessionId = c.req.header(CAPLETS_ATTACH_SESSION_HEADER);
12132
+ const attachSession = attachSessionId ? attachSessionForRequest(attachSessionId) : await fallbackAttachSession(attachSessionContext(c.req.header(CAPLETS_STACK_CHAIN_HEADER)));
12133
+ if (attachSession) return c.json({
12134
+ ok: true,
12135
+ data: await attachSession.invoke(request)
12136
+ });
12283
12137
  const result = await invokeAttachExport(engine, await buildAttachProjection(engine), request);
12284
12138
  return c.json({
12285
12139
  ok: true,
@@ -12291,6 +12145,52 @@ function createHttpServeApp(options, engine, io = {}) {
12291
12145
  }
12292
12146
  });
12293
12147
  }
12148
+ function attachSessionForRequest(sessionId) {
12149
+ pruneIdleAttachSessions();
12150
+ if (!sessionId) return void 0;
12151
+ const record = attachSessions.get(sessionId);
12152
+ if (!record) throw new CapletsError("REQUEST_INVALID", "Attach session was not found.");
12153
+ record.lastUsedAt = Date.now();
12154
+ return record.session;
12155
+ }
12156
+ async function fallbackAttachSession(context) {
12157
+ if (!io.defaultAttachSessionFactory) return void 0;
12158
+ const key = context.stackChain.join("\0");
12159
+ const existing = defaultAttachSessions.get(key);
12160
+ if (existing) return existing;
12161
+ let pending = defaultAttachSessionPromises.get(key);
12162
+ if (!pending) {
12163
+ pending = Promise.resolve(io.defaultAttachSessionFactory({}, context)).then((session) => {
12164
+ defaultAttachSessions.set(key, session);
12165
+ defaultAttachSessionPromises.delete(key);
12166
+ return session;
12167
+ }, (error) => {
12168
+ defaultAttachSessionPromises.delete(key);
12169
+ throw error;
12170
+ });
12171
+ defaultAttachSessionPromises.set(key, pending);
12172
+ }
12173
+ return await pending;
12174
+ }
12175
+ function attachSessionContext(header) {
12176
+ const incoming = stackChainFromHeader(header);
12177
+ if (incoming.includes(stackIdentity)) throw new CapletsError("REQUEST_INVALID", "Stacked runtime upstream cycle detected.");
12178
+ return { stackChain: [...incoming, stackIdentity] };
12179
+ }
12180
+ function touchAttachSession(sessionId) {
12181
+ const record = attachSessions.get(sessionId);
12182
+ if (record) record.lastUsedAt = Date.now();
12183
+ }
12184
+ function pruneIdleAttachSessions() {
12185
+ const expiresBefore = Date.now() - ATTACH_SESSION_IDLE_TIMEOUT_MS;
12186
+ for (const [sessionId, record] of attachSessions) {
12187
+ if (record.lastUsedAt >= expiresBefore) continue;
12188
+ attachSessions.delete(sessionId);
12189
+ record.session.close().catch((error) => {
12190
+ writeErr(`Could not close idle attach session: ${errorMessage$1(error)}\n`);
12191
+ });
12192
+ }
12193
+ }
12294
12194
  app.post(paths.control, protectedRouteAuth, async (c) => {
12295
12195
  let request;
12296
12196
  try {
@@ -12314,31 +12214,9 @@ function createHttpServeApp(options, engine, io = {}) {
12314
12214
  bindingId: c.req.param("bindingId"),
12315
12215
  state: "not_attached"
12316
12216
  }));
12317
- app.post(routePath(paths.projectBindings, "sessions"), protectedRouteAuth, (c) => {
12318
- const bindingId = randomUUID();
12319
- return c.json({
12320
- binding: {
12321
- bindingId,
12322
- state: "attaching",
12323
- syncState: "pending"
12324
- },
12325
- sessionId: randomUUID()
12326
- }, 201);
12327
- });
12328
- app.post(routePath(paths.projectBindings, ":bindingId/heartbeat"), protectedRouteAuth, (c) => c.json({
12329
- ok: true,
12330
- binding: {
12331
- bindingId: c.req.param("bindingId"),
12332
- state: "ready"
12333
- }
12334
- }));
12335
- app.delete(routePath(paths.projectBindings, ":bindingId/session"), protectedRouteAuth, (c) => c.json({
12336
- ok: true,
12337
- binding: {
12338
- bindingId: c.req.param("bindingId"),
12339
- state: "ended"
12340
- }
12341
- }));
12217
+ app.post(routePath(paths.projectBindings, "sessions"), protectedRouteAuth, (c) => c.json(projectBindingUnsupported(), 501));
12218
+ app.post(routePath(paths.projectBindings, ":bindingId/heartbeat"), protectedRouteAuth, (c) => c.json(projectBindingUnsupported(c.req.param("bindingId")), 501));
12219
+ app.delete(routePath(paths.projectBindings, ":bindingId/session"), protectedRouteAuth, (c) => c.json(projectBindingUnsupported(c.req.param("bindingId")), 501));
12342
12220
  app.get(routePath(paths.control, "auth/callback/:flowId"), async (c) => {
12343
12221
  const flowId = c.req.param("flowId");
12344
12222
  const result = await dispatchRemoteCliRequest({
@@ -12353,11 +12231,17 @@ function createHttpServeApp(options, engine, io = {}) {
12353
12231
  });
12354
12232
  app.notFound((c) => c.json({ error: "not_found" }, 404));
12355
12233
  app.closeCapletsSessions = async () => {
12234
+ clearInterval(attachSessionPruneTimer);
12356
12235
  for (const stream of attachEventStreams) stream.close();
12357
12236
  await Promise.allSettled([...sessions.values()].map(async (session) => {
12358
12237
  await session.server.close();
12359
12238
  }));
12360
12239
  sessions.clear();
12240
+ await Promise.allSettled([...attachSessions.values()].map((record) => record.session.close()));
12241
+ attachSessions.clear();
12242
+ await Promise.allSettled([...defaultAttachSessions.values()].map((session) => session.close()));
12243
+ defaultAttachSessions.clear();
12244
+ defaultAttachSessionPromises.clear();
12361
12245
  };
12362
12246
  if (options.warnUnauthenticatedNetwork) writeErr(`Warning: Caplets MCP HTTP server is listening on ${options.host} without authentication.\n`);
12363
12247
  return app;
@@ -12390,9 +12274,36 @@ function remoteCredentialSourceHint(trustProxy, header) {
12390
12274
  const sourceHint = firstForwardedValue(header("x-forwarded-for")) ?? firstForwardedValue(header("x-real-ip")) ?? firstForwardedValue(header("cf-connecting-ip"));
12391
12275
  return sourceHint ? { sourceHint } : {};
12392
12276
  }
12277
+ function projectBindingUnsupported(bindingId) {
12278
+ return {
12279
+ ok: false,
12280
+ error: {
12281
+ code: "UNSUPPORTED_CAPABILITY",
12282
+ message: "Self-hosted Project Binding sessions are not implemented by this runtime."
12283
+ },
12284
+ ...bindingId ? { binding: {
12285
+ bindingId,
12286
+ state: "not_attached"
12287
+ } } : {}
12288
+ };
12289
+ }
12393
12290
  function firstForwardedValue(value) {
12394
12291
  return value?.split(",", 1)[0]?.trim() || void 0;
12395
12292
  }
12293
+ function errorMessage$1(error) {
12294
+ return error instanceof Error ? error.message : String(error);
12295
+ }
12296
+ function httpStackIdentity(options) {
12297
+ const origin = options.publicOrigin ?? `http://${formatHost$2(options.host)}:${options.port}`;
12298
+ const url = new URL(origin);
12299
+ url.pathname = options.path;
12300
+ url.search = "";
12301
+ url.hash = "";
12302
+ return url.toString();
12303
+ }
12304
+ function stackChainFromHeader(header) {
12305
+ return (header ?? "").split(",").map((value) => value.trim()).filter((value) => value.length > 0);
12306
+ }
12396
12307
  function remoteHostMetadata(requestUrl, basePath, options, header) {
12397
12308
  const audience = remoteCredentialHostUrl(requestUrl, basePath, options.publicOrigin, options.trustProxy, header);
12398
12309
  return {
@@ -12400,7 +12311,9 @@ function remoteHostMetadata(requestUrl, basePath, options, header) {
12400
12311
  audience
12401
12312
  };
12402
12313
  }
12403
- function versionDiscovery(paths, exposeAttach = true, remote) {
12314
+ function versionDiscovery(paths, options = {}, remote) {
12315
+ const exposeAttach = options.exposeAttach ?? true;
12316
+ const exposeAttachSessions = options.exposeAttachSessions ?? false;
12404
12317
  return {
12405
12318
  version: 1,
12406
12319
  path: paths.version,
@@ -12409,6 +12322,7 @@ function versionDiscovery(paths, exposeAttach = true, remote) {
12409
12322
  mcp: paths.mcp,
12410
12323
  admin: paths.control,
12411
12324
  ...exposeAttach ? {
12325
+ ...exposeAttachSessions ? { attachSessions: paths.attachSessions } : {},
12412
12326
  attachManifest: paths.attachManifest,
12413
12327
  attachEvents: paths.attachEvents,
12414
12328
  attachInvoke: paths.attachInvoke
@@ -12438,10 +12352,82 @@ async function parseAttachInvokeRequest(input) {
12438
12352
  function isAttachExportKind(value) {
12439
12353
  return value === "caplet" || value === "tool" || value === "resource" || value === "resourceTemplate" || value === "prompt" || value === "completion";
12440
12354
  }
12441
- function attachEventsResponse(engine, activeStreams) {
12355
+ function parseAttachSessionMetadata(input, options) {
12356
+ const rawProjectRoot = optionalStringField(input, "projectRoot");
12357
+ const rawProjectConfigPath = optionalStringField(input, "projectConfigPath");
12358
+ if (!options.allowProjectContext && (rawProjectRoot || rawProjectConfigPath)) throw new CapletsError("REQUEST_INVALID", "Attach session project context is only accepted by loopback runtimes.");
12359
+ const projectRoot = canonicalProjectRoot(rawProjectRoot);
12360
+ const projectConfigPath = canonicalProjectConfigPath(rawProjectConfigPath, projectRoot);
12361
+ return {
12362
+ ...projectRoot ? { projectRoot } : {},
12363
+ ...projectConfigPath ? { projectConfigPath } : {}
12364
+ };
12365
+ }
12366
+ function canonicalProjectRoot(projectRoot) {
12367
+ if (!projectRoot) return void 0;
12368
+ try {
12369
+ const canonical = realpathSync(projectRoot);
12370
+ if (!statSync(canonical).isDirectory()) throw new Error("projectRoot is not a directory.");
12371
+ return canonical;
12372
+ } catch (error) {
12373
+ throw new CapletsError("REQUEST_INVALID", "projectRoot must be an existing directory.", error);
12374
+ }
12375
+ }
12376
+ function canonicalProjectConfigPath(projectConfigPath, projectRoot) {
12377
+ if (!projectRoot) {
12378
+ if (!projectConfigPath) return void 0;
12379
+ throw new CapletsError("REQUEST_INVALID", "projectConfigPath requires projectRoot.");
12380
+ }
12381
+ const expectedProjectConfigPath = resolve(projectRoot, ".caplets", "config.json");
12382
+ const lexicalConfigPath = projectConfigPath === void 0 ? expectedProjectConfigPath : isAbsolute(projectConfigPath) ? projectConfigPath : resolve(projectRoot, projectConfigPath);
12383
+ if (resolve(projectConfigPath === void 0 ? expectedProjectConfigPath : canonicalizeExistingParentPath(lexicalConfigPath)) !== expectedProjectConfigPath) throw new CapletsError("REQUEST_INVALID", "projectConfigPath must be <projectRoot>/.caplets/config.json.");
12384
+ if (!existsSync(expectedProjectConfigPath)) return expectedProjectConfigPath;
12385
+ const expected = realpathSync(expectedProjectConfigPath);
12386
+ if (!pathIsInside(expected, projectRoot)) throw new CapletsError("REQUEST_INVALID", "projectConfigPath must resolve inside projectRoot.");
12387
+ return expected;
12388
+ }
12389
+ function canonicalizeExistingParentPath(path) {
12390
+ const parent = dirname(path);
12391
+ try {
12392
+ return resolve(realpathSync(parent), path.slice(parent.length + 1));
12393
+ } catch {
12394
+ return resolve(path);
12395
+ }
12396
+ }
12397
+ function pathIsInside(candidate, root) {
12398
+ const rel = relative(root, candidate);
12399
+ return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
12400
+ }
12401
+ function allowAttachSessionProjectContext(options, requestUrl, header) {
12402
+ if (!options.loopback) return false;
12403
+ return isLoopbackHost(attachRequestHost(options, requestUrl, header));
12404
+ }
12405
+ function attachRequestHost(options, requestUrl, header) {
12406
+ const fallback = new URL(requestUrl).host;
12407
+ const host = (options.trustProxy ? firstForwardedValue(header("x-forwarded-host")) : void 0) ?? header("host") ?? fallback;
12408
+ try {
12409
+ return new URL(`http://${host}`).hostname;
12410
+ } catch {
12411
+ return host.split(":")[0] ?? host;
12412
+ }
12413
+ }
12414
+ function attachEventSource(engine, session) {
12415
+ if (session) return {
12416
+ manifestRevision: async () => (await session.manifest()).revision,
12417
+ onManifestChanged: (listener) => session.onManifestChanged(listener)
12418
+ };
12419
+ return {
12420
+ manifestRevision: async () => (await buildAttachProjection(engine)).manifest.revision,
12421
+ onManifestChanged: (listener) => engine.onReload(() => {
12422
+ listener();
12423
+ })
12424
+ };
12425
+ }
12426
+ function attachEventsResponse(source, activeStreams, options = {}) {
12442
12427
  const encoder = new TextEncoder();
12443
12428
  let unsubscribe = () => void 0;
12444
12429
  let activeStream;
12430
+ let keepAliveTimer;
12445
12431
  let closed = false;
12446
12432
  const readable = new ReadableStream({
12447
12433
  start(controller) {
@@ -12449,17 +12435,30 @@ function attachEventsResponse(engine, activeStreams) {
12449
12435
  if (closed) return;
12450
12436
  closed = true;
12451
12437
  unsubscribe();
12438
+ if (keepAliveTimer) clearInterval(keepAliveTimer);
12452
12439
  if (activeStream) activeStreams.delete(activeStream);
12453
12440
  try {
12454
12441
  controller.close();
12455
12442
  } catch {}
12456
12443
  } };
12457
12444
  activeStreams.add(activeStream);
12445
+ options.onActivity?.();
12458
12446
  controller.enqueue(encoder.encode(": connected\n\n"));
12459
- unsubscribe = engine.onReload(() => {
12460
- buildAttachProjection(engine).then((projection) => {
12447
+ keepAliveTimer = setInterval(() => {
12448
+ if (closed) return;
12449
+ options.onActivity?.();
12450
+ try {
12451
+ controller.enqueue(encoder.encode(": keepalive\n\n"));
12452
+ } catch {
12453
+ activeStream?.close();
12454
+ }
12455
+ }, 3e4);
12456
+ keepAliveTimer.unref?.();
12457
+ unsubscribe = source.onManifestChanged(() => {
12458
+ options.onActivity?.();
12459
+ source.manifestRevision().then((revision) => {
12461
12460
  if (closed) return;
12462
- controller.enqueue(encoder.encode(`event: manifest_changed\ndata: ${JSON.stringify({ revision: projection.manifest.revision })}\n\n`));
12461
+ controller.enqueue(encoder.encode(`event: manifest_changed\ndata: ${JSON.stringify({ revision })}\n\n`));
12463
12462
  }).catch(() => void 0);
12464
12463
  });
12465
12464
  },
@@ -12489,7 +12488,7 @@ async function serveHttp(options, engineOptions = {}, writeErr = (value) => proc
12489
12488
  }
12490
12489
  });
12491
12490
  const paths = servicePaths(options.path);
12492
- const origin = `http://${formatHost$1(options.host)}:${options.port}`;
12491
+ const origin = `http://${formatHost$2(options.host)}:${options.port}`;
12493
12492
  const baseUrl = `${origin}${paths.base === "/" ? "" : paths.base}`;
12494
12493
  installHttpSignalHandlers(serve({
12495
12494
  fetch: app.fetch,
@@ -12504,20 +12503,26 @@ async function serveHttp(options, engineOptions = {}, writeErr = (value) => proc
12504
12503
  writeErr(`Auth: ${authDescription(options)}\n`);
12505
12504
  }), app, engine, writeErr);
12506
12505
  }
12507
- async function serveHttpWithSessionFactory(options, createSession, writeErr = (value) => process.stderr.write(value)) {
12508
- const resolvedEngineOptions = { exposeLocalArtifactPaths: false };
12506
+ async function serveHttpWithSessionFactory(options, createSession, writeErr = (value) => process.stderr.write(value), io = {}, engineOptions = {}) {
12507
+ const resolvedEngineOptions = {
12508
+ exposeLocalArtifactPaths: false,
12509
+ vaultRecoveryTarget: "remote",
12510
+ ...engineOptions
12511
+ };
12509
12512
  const engine = new CapletsEngine(resolvedEngineOptions);
12510
12513
  const app = createHttpServeApp(options, engine, {
12511
12514
  writeErr,
12512
- exposeAttach: false,
12515
+ exposeAttach: io.exposeAttach ?? false,
12513
12516
  sessionFactory: createSession,
12517
+ ...io.attachSessionFactory ? { attachSessionFactory: io.attachSessionFactory } : {},
12518
+ ...io.defaultAttachSessionFactory ? { defaultAttachSessionFactory: io.defaultAttachSessionFactory } : {},
12514
12519
  control: {
12515
12520
  ...resolvedEngineOptions,
12516
- projectCapletsRoot: resolveProjectCapletsRoot()
12521
+ projectCapletsRoot: projectCapletsRootForEngineOptions(resolvedEngineOptions)
12517
12522
  }
12518
12523
  });
12519
12524
  const paths = servicePaths(options.path);
12520
- const origin = `http://${formatHost$1(options.host)}:${options.port}`;
12525
+ const origin = `http://${formatHost$2(options.host)}:${options.port}`;
12521
12526
  const baseUrl = `${origin}${paths.base === "/" ? "" : paths.base}`;
12522
12527
  installHttpSignalHandlers(serve({
12523
12528
  fetch: app.fetch,
@@ -12549,6 +12554,7 @@ function servicePaths(base) {
12549
12554
  version,
12550
12555
  mcp: routePath(version, "mcp"),
12551
12556
  control: routePath(version, "admin"),
12557
+ attachSessions: routePath(attach, "sessions"),
12552
12558
  attachManifest: routePath(attach, "manifest"),
12553
12559
  attachEvents: routePath(attach, "events"),
12554
12560
  attachInvoke: routePath(attach, "invoke"),
@@ -12692,7 +12698,7 @@ function installHttpSignalHandlers(server, app, engine, writeErr) {
12692
12698
  function closeAllServerConnections(server) {
12693
12699
  server.closeAllConnections?.call(server);
12694
12700
  }
12695
- function formatHost$1(host) {
12701
+ function formatHost$2(host) {
12696
12702
  return host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
12697
12703
  }
12698
12704
  //#endregion
@@ -12823,11 +12829,11 @@ async function allocateLoopbackPort() {
12823
12829
  if (!address || typeof address === "string") throw new CapletsError("SERVER_UNAVAILABLE", "Could not allocate a validation port.");
12824
12830
  return address.port;
12825
12831
  }
12826
- function formatHost(host) {
12832
+ function formatHost$1(host) {
12827
12833
  return host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
12828
12834
  }
12829
12835
  function healthUrl(config, port = config.serve.port) {
12830
- return `http://${formatHost(config.serve.host)}:${port}${servicePaths(config.serve.path).health}`;
12836
+ return `http://${formatHost$1(config.serve.host)}:${port}${servicePaths(config.serve.path).health}`;
12831
12837
  }
12832
12838
  function validationSpawnCommand(config) {
12833
12839
  const planned = serviceCommand(config);
@@ -13289,6 +13295,7 @@ function mergeServeOptions(existing, install) {
13289
13295
  ...install.port !== void 0 ? { port: install.port } : existing?.serve.port ? { port: existing.serve.port } : {},
13290
13296
  ...install.path !== void 0 ? { path: install.path } : existing?.serve.path ? { path: existing.serve.path } : {},
13291
13297
  ...install.remoteStatePath !== void 0 ? { remoteStatePath: install.remoteStatePath } : existing?.serve.remoteCredentialStateDir ? { remoteStatePath: existing.serve.remoteCredentialStateDir } : {},
13298
+ ...install.upstreamUrl !== void 0 ? { upstreamUrl: install.upstreamUrl } : existing?.serve.upstreamUrl ? { upstreamUrl: existing.serve.upstreamUrl } : {},
13292
13299
  ...install.allowUnauthenticatedHttp !== void 0 ? { allowUnauthenticatedHttp: install.allowUnauthenticatedHttp } : existing ? { allowUnauthenticatedHttp: existing.serve.allowUnauthenticatedHttp } : {},
13293
13300
  ...install.trustProxy !== void 0 ? { trustProxy: install.trustProxy } : existing ? { trustProxy: existing.serve.trustProxy } : {},
13294
13301
  ...existing && existing.serve.auth.type === "development_unauthenticated" && install.allowUnauthenticatedHttp === void 0 ? { preserveUnauthenticatedAuth: true } : {}
@@ -14708,6 +14715,7 @@ function createAttachNativeService(options, io) {
14708
14715
  return createNativeCapletsService({
14709
14716
  mode: options.selection.kind === "hosted_cloud" ? "cloud" : "remote",
14710
14717
  configPath: options.configPath,
14718
+ projectRoot: options.projectRoot,
14711
14719
  projectConfigPath: options.projectConfigPath,
14712
14720
  ...options.authDir ? { authDir: options.authDir } : {},
14713
14721
  remote: {
@@ -15325,15 +15333,95 @@ async function serveResolvedCaplets(resolved, engineOptions = {}, writeErr) {
15325
15333
  });
15326
15334
  return;
15327
15335
  }
15336
+ if (resolved.upstreamUrl) {
15337
+ await serveHttpWithUpstream(resolved, resolved.upstreamUrl, engineOptions, writeErr);
15338
+ return;
15339
+ }
15328
15340
  await serveHttp(resolved, {
15329
15341
  ...engineOptions,
15330
15342
  ...writeErr ? { writeErr } : {}
15331
15343
  }, writeErr);
15332
15344
  }
15345
+ async function serveHttpWithUpstream(resolved, upstreamUrl, engineOptions, writeErr) {
15346
+ const stackChain = [serveStackIdentity(resolved)];
15347
+ await serveHttpWithSessionFactory(resolved, async () => new NativeCapletsMcpSession(await createReloadedUpstreamService(upstreamUrl, engineOptions, writeErr, {}, stackChain)), writeErr, {
15348
+ exposeAttach: true,
15349
+ defaultAttachSessionFactory: async (_metadata, context) => nativeAttachSession(await createReloadedUpstreamService(upstreamUrl, engineOptions, writeErr, {}, context.stackChain)),
15350
+ attachSessionFactory: async (metadata, context) => {
15351
+ return nativeAttachSession(await createReloadedUpstreamService(upstreamUrl, engineOptions, writeErr, metadata, context.stackChain));
15352
+ }
15353
+ }, engineOptions);
15354
+ }
15355
+ async function createReloadedUpstreamService(upstreamUrl, engineOptions, writeErr, metadata = {}, stackChain = []) {
15356
+ const service = createUpstreamNativeService(upstreamUrl, engineOptions, writeErr, metadata, stackChain);
15357
+ try {
15358
+ await service.reload();
15359
+ return service;
15360
+ } catch (error) {
15361
+ await service.close().catch(() => void 0);
15362
+ throw error;
15363
+ }
15364
+ }
15365
+ function createUpstreamNativeService(upstreamUrl, engineOptions, writeErr, metadata = {}, stackChain = []) {
15366
+ return createNativeCapletsService({
15367
+ ...engineOptions,
15368
+ ...metadata.projectRoot ? { projectRoot: metadata.projectRoot } : {},
15369
+ ...metadata.projectConfigPath ? { projectConfigPath: metadata.projectConfigPath } : {},
15370
+ mode: isCapletsCloudUrl(upstreamUrl) ? "cloud" : "remote",
15371
+ remote: {
15372
+ url: upstreamUrl,
15373
+ ...stackChain.length > 0 ? { requestHeaders: { [CAPLETS_STACK_CHAIN_HEADER]: stackChain.join(",") } } : {}
15374
+ },
15375
+ ...writeErr ? { writeErr } : {}
15376
+ });
15377
+ }
15378
+ function serveStackIdentity(options) {
15379
+ const origin = options.publicOrigin ?? `http://${formatHost(options.host)}:${options.port}`;
15380
+ const url = new URL(origin);
15381
+ url.pathname = options.path;
15382
+ url.search = "";
15383
+ url.hash = "";
15384
+ return url.toString();
15385
+ }
15386
+ function formatHost(host) {
15387
+ return host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
15388
+ }
15389
+ function nativeAttachSession(service) {
15390
+ let cachedProjection;
15391
+ const getProjection = async () => {
15392
+ cachedProjection ??= await buildNativeAttachProjection(service);
15393
+ return cachedProjection;
15394
+ };
15395
+ const unsubscribe = service.onToolsChanged(() => {
15396
+ cachedProjection = void 0;
15397
+ });
15398
+ return {
15399
+ manifest: async () => (await getProjection()).manifest,
15400
+ invoke: async (request) => await invokeNativeAttachExport(service, await getProjection(), request),
15401
+ onManifestChanged: (listener) => service.onToolsChanged(() => listener()),
15402
+ close: async () => {
15403
+ unsubscribe();
15404
+ await service.close();
15405
+ }
15406
+ };
15407
+ }
15333
15408
  //#endregion
15334
15409
  //#region src/cli.ts
15335
15410
  async function runCli(args, io = {}) {
15336
- const program = createProgram(io);
15411
+ let observedExitCode = 0;
15412
+ const wrappedIo = {
15413
+ ...io,
15414
+ setExitCode: (code) => {
15415
+ observedExitCode = code;
15416
+ if (io.setExitCode) io.setExitCode(code);
15417
+ else process.exitCode = code;
15418
+ }
15419
+ };
15420
+ const program = createProgram(wrappedIo);
15421
+ const trackedCommand = telemetryCommandFamilyFromArgs(args);
15422
+ const startedAt = Date.now();
15423
+ const telemetryContext = telemetryContextForIo(wrappedIo);
15424
+ const dispatcher = createTelemetryDispatcher({ stateDir: telemetryContext.stateDir });
15337
15425
  try {
15338
15426
  if (args.length === 0) {
15339
15427
  program.outputHelp();
@@ -15344,12 +15432,35 @@ async function runCli(args, io = {}) {
15344
15432
  "caplets",
15345
15433
  ...args
15346
15434
  ]);
15435
+ if (trackedCommand) await captureCliTelemetry(telemetryContext, {
15436
+ debugSink: wrappedIo.telemetryDebugSink,
15437
+ dispatcher,
15438
+ commandFamily: trackedCommand.commandFamily,
15439
+ surface: trackedCommand.surface,
15440
+ outcome: observedExitCode === 0 ? "success" : "failure",
15441
+ startedAt
15442
+ });
15347
15443
  } catch (error) {
15444
+ let normalizedError = error;
15445
+ let captureProductEvent = true;
15348
15446
  if (error instanceof CommanderError) {
15349
15447
  if (error.code === "commander.helpDisplayed" || error.code === "commander.version" || error.message === "(outputHelp)") return;
15350
- throw new CapletsError("REQUEST_INVALID", error.message);
15351
- }
15352
- throw error;
15448
+ normalizedError = new CapletsError("REQUEST_INVALID", error.message);
15449
+ captureProductEvent = false;
15450
+ }
15451
+ if (trackedCommand) await captureCliTelemetry(telemetryContext, {
15452
+ debugSink: wrappedIo.telemetryDebugSink,
15453
+ dispatcher,
15454
+ commandFamily: trackedCommand.commandFamily,
15455
+ surface: trackedCommand.surface,
15456
+ outcome: "failure",
15457
+ startedAt,
15458
+ error: normalizedError,
15459
+ productEvent: captureProductEvent
15460
+ }).catch(() => void 0);
15461
+ throw normalizedError;
15462
+ } finally {
15463
+ await dispatcher.shutdown();
15353
15464
  }
15354
15465
  }
15355
15466
  function normalizeCompletionWords(words) {
@@ -15359,7 +15470,7 @@ function addJsonOption(command) {
15359
15470
  return command.option("--json", "print JSON output");
15360
15471
  }
15361
15472
  function addDaemonInstallOptions(command) {
15362
- return addJsonOption(command).option("--host <host>", "HTTP bind host").option("--port <port>", "HTTP bind port").option("--path <path>", "HTTP service base path").option("--remote-state-path <path>", "server-owned remote credential state directory").option("--allow-unauthenticated-http", "allow unauthenticated HTTP serving on non-loopback hosts").option("--trust-proxy", "trust X-Forwarded-* headers from a reverse proxy").option("--reset", "rebuild daemon configuration from defaults").option("--env <KEY=VALUE>", "set an environment variable for the service", collectValues, []).option("--unset-env <KEY>", "remove an environment variable from the service", collectValues, []).option("--inherit-env", "run the service through the user's shell environment").option("--no-inherit-env", "disable shell environment inheritance").option("--dry-run", "preview actions without writing files or registering a service").option("--no-validate", "skip temporary service command validation").option("--start", "start the service after install").option("--restart", "restart the service after install").option("--no-restart", "do not restart a running service after updating config");
15473
+ return addJsonOption(command).option("--host <host>", "HTTP bind host").option("--port <port>", "HTTP bind port").option("--path <path>", "HTTP service base path").option("--remote-state-path <path>", "server-owned remote credential state directory").option("--upstream-url <url>", "upstream Caplets runtime URL to compose with this HTTP service").option("--allow-unauthenticated-http", "allow unauthenticated HTTP serving on non-loopback hosts").option("--trust-proxy", "trust X-Forwarded-* headers from a reverse proxy").option("--reset", "rebuild daemon configuration from defaults").option("--env <KEY=VALUE>", "set an environment variable for the service", collectValues, []).option("--unset-env <KEY>", "remove an environment variable from the service", collectValues, []).option("--inherit-env", "run the service through the user's shell environment").option("--no-inherit-env", "disable shell environment inheritance").option("--dry-run", "preview actions without writing files or registering a service").option("--no-validate", "skip temporary service command validation").option("--start", "start the service after install").option("--restart", "restart the service after install").option("--no-restart", "do not restart a running service after updating config");
15363
15474
  }
15364
15475
  function addServeMigrationCommand(parent, name, replacement) {
15365
15476
  parent.command(name, { hidden: true }).allowUnknownOption(true).action(() => {
@@ -15370,6 +15481,249 @@ function collectValues(value, previous) {
15370
15481
  return [...previous, value];
15371
15482
  }
15372
15483
  const HIDDEN_INPUT_PROMPT_LABELS = { vaultValue: "Value: " };
15484
+ function telemetryContextForIo(io) {
15485
+ const env = io.env ?? process.env;
15486
+ return {
15487
+ env,
15488
+ configPath: envConfigPath(env),
15489
+ projectConfigPath: envProjectConfigPath(env),
15490
+ stateDir: io.telemetryStateDir ?? defaultTelemetryStateDir(env),
15491
+ stderrIsTTY: io.stderrIsTTY ?? process.stderr.isTTY === true,
15492
+ writeErr: io.writeErr ?? ((value) => process.stderr.write(value))
15493
+ };
15494
+ }
15495
+ function telemetryCommandFamilyFromArgs(args) {
15496
+ const command = args[0];
15497
+ if (command === void 0 || command === "--help" || command === "-h" || command === "--version" || command === "-V" || command === cliCommands$1.telemetry || command === cliCommands$1.completion || command === cliCommands$1.completeHidden) return;
15498
+ if (command === cliCommands$1.serve) return {
15499
+ commandFamily: "serve",
15500
+ surface: "serve"
15501
+ };
15502
+ if (command === cliCommands$1.attach) return {
15503
+ commandFamily: "attach",
15504
+ surface: "attach"
15505
+ };
15506
+ if (command === cliCommands$1.daemon) return {
15507
+ commandFamily: "daemon",
15508
+ surface: "daemon"
15509
+ };
15510
+ if (command === cliCommands$1.codeMode) return {
15511
+ commandFamily: "code_mode",
15512
+ surface: "code_mode"
15513
+ };
15514
+ if (command === cliCommands$1.setup) return {
15515
+ commandFamily: "setup",
15516
+ surface: "cli"
15517
+ };
15518
+ if (command === cliCommands$1.init) return {
15519
+ commandFamily: "init",
15520
+ surface: "cli"
15521
+ };
15522
+ if (command === cliCommands$1.install) return {
15523
+ commandFamily: "install",
15524
+ surface: "cli"
15525
+ };
15526
+ if (command === cliCommands$1.add) return {
15527
+ commandFamily: "add",
15528
+ surface: "cli"
15529
+ };
15530
+ if (command === cliCommands$1.doctor) return {
15531
+ commandFamily: "doctor",
15532
+ surface: "cli"
15533
+ };
15534
+ if (command === cliCommands$1.auth) return {
15535
+ commandFamily: "auth",
15536
+ surface: "cli"
15537
+ };
15538
+ if (command === cliCommands$1.remote || command === cliCommands$1.cloud) return {
15539
+ commandFamily: "remote",
15540
+ surface: "cli"
15541
+ };
15542
+ if (command === cliCommands$1.inspect) return {
15543
+ commandFamily: "inspect",
15544
+ surface: "cli"
15545
+ };
15546
+ if (command === cliCommands$1.checkBackend) return {
15547
+ commandFamily: "check",
15548
+ surface: "cli"
15549
+ };
15550
+ if (command === cliCommands$1.listTools || command === cliCommands$1.searchTools || command === cliCommands$1.getTool || command === cliCommands$1.callTool) return {
15551
+ commandFamily: "tools",
15552
+ surface: "cli"
15553
+ };
15554
+ if (command === cliCommands$1.listResources || command === cliCommands$1.searchResources || command === cliCommands$1.listResourceTemplates || command === cliCommands$1.readResource) return {
15555
+ commandFamily: "resources",
15556
+ surface: "cli"
15557
+ };
15558
+ if (command === cliCommands$1.listPrompts || command === cliCommands$1.searchPrompts || command === cliCommands$1.getPrompt) return {
15559
+ commandFamily: "prompts",
15560
+ surface: "cli"
15561
+ };
15562
+ if (command === cliCommands$1.complete) return {
15563
+ commandFamily: "complete",
15564
+ surface: "cli"
15565
+ };
15566
+ return {
15567
+ commandFamily: "unknown",
15568
+ surface: "cli"
15569
+ };
15570
+ }
15571
+ function telemetryConfigForCli(context) {
15572
+ try {
15573
+ return loadConfigWithSources(context.configPath, context.projectConfigPath, { vaultResolver: vaultBootstrapResolver }).config;
15574
+ } catch (error) {
15575
+ if (error instanceof CapletsError && error.code !== "CONFIG_INVALID") return {};
15576
+ return telemetryOnlyConfigForCli(resolveConfigPath(context.configPath)) ?? { telemetry: false };
15577
+ }
15578
+ }
15579
+ function telemetryOnlyConfigForCli(path) {
15580
+ try {
15581
+ const config = readUserConfigObject(path);
15582
+ return typeof config.telemetry === "boolean" ? { telemetry: config.telemetry } : void 0;
15583
+ } catch {
15584
+ return;
15585
+ }
15586
+ }
15587
+ function maybePrintCliTelemetryNotice(context, surface) {
15588
+ const state = resolveTelemetryState({
15589
+ config: telemetryConfigForCli(context),
15590
+ env: context.env,
15591
+ stateDir: context.stateDir,
15592
+ surface,
15593
+ visibility: "visible",
15594
+ allowWithoutNotice: true,
15595
+ createIdentity: false
15596
+ });
15597
+ if (state.status !== "enabled" || state.notice.shown) return;
15598
+ maybePrintTelemetryNotice({
15599
+ stateDir: context.stateDir,
15600
+ surface,
15601
+ stderrIsTTY: context.stderrIsTTY,
15602
+ writeErr: context.writeErr
15603
+ });
15604
+ }
15605
+ async function captureCliTelemetry(context, options) {
15606
+ try {
15607
+ maybePrintCliTelemetryNotice(context, options.surface ?? "cli");
15608
+ } catch {}
15609
+ const state = resolveTelemetryState({
15610
+ config: telemetryConfigForCli(context),
15611
+ env: context.env,
15612
+ stateDir: context.stateDir,
15613
+ surface: options.surface ?? "cli",
15614
+ visibility: "visible",
15615
+ debug: context.env.CAPLETS_TELEMETRY_DEBUG === "1"
15616
+ });
15617
+ if (state.status !== "enabled" && state.status !== "debug") return;
15618
+ const identity = state.status === "debug" ? readTelemetryIdentity({
15619
+ stateDir: context.stateDir,
15620
+ create: false
15621
+ }) : state.identity ?? readTelemetryIdentity({
15622
+ stateDir: context.stateDir,
15623
+ create: true
15624
+ });
15625
+ if (options.productEvent !== false) {
15626
+ const product = buildProductTelemetryEvent({
15627
+ name: "caplets_cli_command",
15628
+ distinctId: identity.id,
15629
+ properties: {
15630
+ package: "@caplets/core",
15631
+ version,
15632
+ surface: options.surface ?? "cli",
15633
+ runtime_mode: runtimeModeForEnv(context.env),
15634
+ execution_context: state.executionContext,
15635
+ command_family: options.commandFamily,
15636
+ outcome: options.outcome,
15637
+ duration_bucket: durationBucket(Date.now() - options.startedAt)
15638
+ }
15639
+ });
15640
+ if (state.status === "debug") options.debugSink?.capture("debug", product);
15641
+ else await (options.dispatcher ?? createTelemetryDispatcher({ stateDir: context.stateDir })).capture(state, product);
15642
+ }
15643
+ if (options.outcome !== "failure") return;
15644
+ if (options.error === void 0) return;
15645
+ const reliability = buildReliabilityTelemetryEvent({
15646
+ name: "caplets_reliability_error",
15647
+ properties: {
15648
+ package: "@caplets/core",
15649
+ version,
15650
+ surface: options.surface ?? "cli",
15651
+ runtime_mode: runtimeModeForEnv(context.env),
15652
+ command_family: options.commandFamily,
15653
+ error_code: errorCodeForTelemetry(options.error),
15654
+ diagnostic_category: diagnosticCategoryForError(options.error),
15655
+ os_family: platform(),
15656
+ arch: arch(),
15657
+ node_major: Number(process.versions.node.split(".")[0] ?? 0)
15658
+ }
15659
+ });
15660
+ if (state.status === "debug") {
15661
+ options.debugSink?.capture("debug", reliability);
15662
+ return;
15663
+ }
15664
+ await (options.dispatcher ?? createTelemetryDispatcher({ stateDir: context.stateDir })).capture(state, reliability);
15665
+ }
15666
+ function readUserConfigObject(path) {
15667
+ if (!existsSync(path)) return {};
15668
+ try {
15669
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
15670
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
15671
+ } catch (error) {
15672
+ throw new CapletsError("CONFIG_INVALID", `Caplets config at ${path} is not valid JSON`, error);
15673
+ }
15674
+ }
15675
+ function runtimeModeForEnv(env) {
15676
+ const mode = env.CAPLETS_MODE;
15677
+ return mode === "remote" || mode === "cloud" || mode === "local" ? mode : "unknown";
15678
+ }
15679
+ function errorCodeForTelemetry(error) {
15680
+ if (error instanceof CapletsError) return error.code;
15681
+ return "UNKNOWN";
15682
+ }
15683
+ function diagnosticCategoryForError(error) {
15684
+ if (!(error instanceof CapletsError)) return "unknown";
15685
+ if (error.code.startsWith("CONFIG")) return "config";
15686
+ if (error.code.startsWith("AUTH")) return "auth";
15687
+ if (error.code.includes("NETWORK") || error.code.includes("UNAVAILABLE")) return "network";
15688
+ if (error.code.includes("VALID") || error.code.includes("REQUEST")) return "validation";
15689
+ return "runtime";
15690
+ }
15691
+ function writeTelemetryConfig(path, enabled) {
15692
+ const config = {
15693
+ ...readUserConfigObject(path),
15694
+ $schema: "https://caplets.dev/config.schema.json",
15695
+ telemetry: enabled
15696
+ };
15697
+ mkdirSync(dirname(path), { recursive: true });
15698
+ writeFileSync(path, `${JSON.stringify(config, null, 2)}\n`, { mode: 384 });
15699
+ }
15700
+ function formatTelemetryStatus(context) {
15701
+ const config = telemetryConfigForCli(context);
15702
+ const state = resolveTelemetryState({
15703
+ config,
15704
+ env: context.env,
15705
+ stateDir: context.stateDir,
15706
+ surface: "cli",
15707
+ visibility: "visible",
15708
+ createIdentity: false
15709
+ });
15710
+ const notice = readTelemetryNotice({ stateDir: context.stateDir });
15711
+ const identity = readTelemetryIdentity({
15712
+ stateDir: context.stateDir,
15713
+ create: false
15714
+ });
15715
+ const health = readTelemetryDeliveryHealth({ stateDir: context.stateDir });
15716
+ return `${[
15717
+ `Telemetry: ${state.status}`,
15718
+ `Decision: ${state.decider}`,
15719
+ `Config: ${config.telemetry === false ? "disabled" : "enabled"}`,
15720
+ `Environment: ${context.env.CAPLETS_DISABLE_TELEMETRY === "1" ? "disabled" : "enabled"}`,
15721
+ `Notice shown: ${notice.shown ? `yes (${notice.surface})` : "no"}`,
15722
+ `Anonymous ID: ${identity.kind === "stable" ? "present" : "not stored"}`,
15723
+ `Delivery health: ${Object.keys(health).length === 0 ? "none" : JSON.stringify(health)}`,
15724
+ "Disable with CAPLETS_DISABLE_TELEMETRY=1 or `caplets telemetry disable`."
15725
+ ].join("\n")}\n`;
15726
+ }
15373
15727
  function remoteProfileStore(authDir, env) {
15374
15728
  return createRemoteProfileStore({
15375
15729
  authDir,
@@ -15380,6 +15734,21 @@ function attachRemoteUrlFromArgs(positionalUrl, legacyRemoteUrl) {
15380
15734
  if (positionalUrl && legacyRemoteUrl && positionalUrl !== legacyRemoteUrl) throw new CapletsError("REQUEST_INVALID", "Pass either attach URL or --remote-url, not both. Use caplets attach <url> for new configs.");
15381
15735
  return positionalUrl ?? legacyRemoteUrl;
15382
15736
  }
15737
+ function rejectAttachHttpServeFlags(options) {
15738
+ const invalid = [
15739
+ options.transport !== void 0 ? "--transport" : void 0,
15740
+ options.host !== void 0 ? "--host" : void 0,
15741
+ options.port !== void 0 ? "--port" : void 0,
15742
+ options.path !== void 0 ? "--path" : void 0,
15743
+ options.allowUnauthenticatedHttp === true ? "--allow-unauthenticated-http" : void 0,
15744
+ options.trustProxy === true ? "--trust-proxy" : void 0
15745
+ ].filter((value) => value !== void 0);
15746
+ if (invalid.length === 0) return;
15747
+ throw new CapletsError("REQUEST_INVALID", `caplets attach is stdio-only; ${invalid.join(", ")} ${invalid.length === 1 ? "is" : "are"} no longer supported. Use caplets serve --transport http --upstream-url <url> to start an HTTP stacked runtime.`);
15748
+ }
15749
+ function hiddenOption(flags, description) {
15750
+ return new Option(flags, description).hideHelp();
15751
+ }
15383
15752
  function remoteServerCredentialStore(statePath, env) {
15384
15753
  return new RemoteServerCredentialStore({ dir: statePath ?? env.CAPLETS_REMOTE_SERVER_STATE_DIR ?? join(DEFAULT_AUTH_DIR, "remote-server") });
15385
15754
  }
@@ -15405,6 +15774,7 @@ function daemonInstallOptions(options) {
15405
15774
  ...options.port !== void 0 ? { port: options.port } : {},
15406
15775
  ...options.path !== void 0 ? { path: options.path } : {},
15407
15776
  ...options.remoteStatePath !== void 0 ? { remoteStatePath: options.remoteStatePath } : {},
15777
+ ...options.upstreamUrl !== void 0 ? { upstreamUrl: options.upstreamUrl } : {},
15408
15778
  ...options.allowUnauthenticatedHttp !== void 0 ? { allowUnauthenticatedHttp: options.allowUnauthenticatedHttp } : {},
15409
15779
  ...options.trustProxy !== void 0 ? { trustProxy: options.trustProxy } : {},
15410
15780
  ...options.reset !== void 0 ? { reset: options.reset } : {},
@@ -15801,9 +16171,33 @@ function createProgram(io = {}) {
15801
16171
  const writeErr = io.writeErr ?? ((value) => process.stderr.write(value));
15802
16172
  const env = io.env ?? process.env;
15803
16173
  const currentConfigPath = () => envConfigPath(env);
16174
+ const telemetryContext = () => ({
16175
+ env,
16176
+ configPath: currentConfigPath(),
16177
+ projectConfigPath: envProjectConfigPath(env),
16178
+ stateDir: io.telemetryStateDir ?? defaultTelemetryStateDir(env),
16179
+ stderrIsTTY: io.stderrIsTTY ?? process.stderr.isTTY === true,
16180
+ writeErr
16181
+ });
16182
+ const printTelemetryNotice = (surface) => {
16183
+ try {
16184
+ maybePrintCliTelemetryNotice(telemetryContext(), surface);
16185
+ } catch {}
16186
+ };
15804
16187
  const setExitCode = io.setExitCode ?? ((code) => {
15805
16188
  process.exitCode = code;
15806
16189
  });
16190
+ const executeOperationIo = (format) => ({
16191
+ writeOut,
16192
+ writeErr,
16193
+ setExitCode,
16194
+ authDir: io.authDir,
16195
+ env,
16196
+ remote: remoteClientForCli(io),
16197
+ format,
16198
+ telemetryStateDir: telemetryContext().stateDir,
16199
+ telemetryDebugSink: io.telemetryDebugSink
16200
+ });
15807
16201
  const program = new Command();
15808
16202
  program.name("caplets").description("Progressive-disclosure gateway for MCP servers.").version(io.version ?? version).exitOverride().configureOutput({
15809
16203
  writeOut,
@@ -15847,7 +16241,51 @@ function createProgram(io = {}) {
15847
16241
  }
15848
16242
  if (suggestions.length > 0) writeOut(`${suggestions.join("\n")}\n`);
15849
16243
  });
16244
+ const telemetry = program.command(cliCommands$1.telemetry).description("Inspect and control anonymous Caplets telemetry.");
16245
+ telemetry.command("status").description("Show anonymous telemetry status.").action(() => {
16246
+ writeOut(formatTelemetryStatus(telemetryContext()));
16247
+ });
16248
+ telemetry.command("enable").description("Enable anonymous telemetry in the user config.").action(() => {
16249
+ const path = resolveConfigPath(currentConfigPath());
16250
+ writeTelemetryConfig(path, true);
16251
+ writeOut(`Enabled anonymous telemetry in ${path}.\n`);
16252
+ if (env.CAPLETS_DISABLE_TELEMETRY === "1") writeOut("CAPLETS_DISABLE_TELEMETRY=1 still disables telemetry for this process.\n");
16253
+ });
16254
+ telemetry.command("disable").description("Disable anonymous telemetry in the user config.").action(() => {
16255
+ const path = resolveConfigPath(currentConfigPath());
16256
+ writeTelemetryConfig(path, false);
16257
+ writeOut(`Disabled anonymous telemetry in ${path}.\n`);
16258
+ });
16259
+ telemetry.command("delete-id").description("Delete the local anonymous telemetry ID.").action(() => {
16260
+ deleteTelemetryIdentity({ stateDir: telemetryContext().stateDir });
16261
+ writeOut("Deleted the local anonymous telemetry ID. This does not delete provider-side historical anonymous events; provider retention controls historical data.\n");
16262
+ });
16263
+ telemetry.command("rotate-id").description("Rotate the local anonymous telemetry ID.").action(() => {
16264
+ rotateTelemetryIdentity({ stateDir: telemetryContext().stateDir });
16265
+ writeOut("Rotated the local anonymous telemetry ID. This does not delete provider-side historical anonymous events; provider retention controls historical data.\n");
16266
+ });
16267
+ telemetry.command("debug").description("Run a Caplets command with local telemetry debug output.").allowUnknownOption(true).argument("[args...]", "Caplets command and arguments after --").action(async (args) => {
16268
+ const nestedArgs = args[0] === "--" ? args.slice(1) : args;
16269
+ const sink = new TelemetryDebugSink();
16270
+ try {
16271
+ if (nestedArgs.length > 0) await runCli(nestedArgs, {
16272
+ ...io,
16273
+ env: {
16274
+ ...env,
16275
+ CAPLETS_TELEMETRY_DEBUG: "1"
16276
+ },
16277
+ telemetryDebugSink: sink,
16278
+ writeOut,
16279
+ writeErr
16280
+ });
16281
+ } finally {
16282
+ writeOut(`${JSON.stringify({ telemetryDebug: sink.toJSON() }, null, 2)}\n`);
16283
+ }
16284
+ });
15850
16285
  program.command(cliCommands$1.codeMode).description("Run, inspect, and debug Caplets Code Mode.").argument("[code]", "inline TypeScript code to run").option("--file <path>", "read TypeScript code from a file relative to the current directory").option("--session-id <id>", "optional Code Mode session identifier").option("--recover <ref>", "recover a prior Code Mode REPL session when supported").option("--timeout-ms <ms>", "execution timeout in milliseconds", parsePositiveInteger).option("--json", "print the structured run envelope").action(async (code, options) => {
16286
+ try {
16287
+ maybePrintCliTelemetryNotice(telemetryContext(), "code_mode");
16288
+ } catch {}
15851
16289
  if (code === "repl" && options.file === void 0) {
15852
16290
  await runCodeModeReplCli({
15853
16291
  env,
@@ -15881,6 +16319,7 @@ function createProgram(io = {}) {
15881
16319
  ...currentConfigPath() ? { configPath: currentConfigPath() } : {},
15882
16320
  projectConfigPath: envProjectConfigPath(env),
15883
16321
  ...io.authDir ? { authDir: io.authDir } : {},
16322
+ telemetryStateDir: telemetryContext().stateDir,
15884
16323
  ...code === void 0 ? {} : { inlineCode: code },
15885
16324
  ...options.file === void 0 ? {} : { file: options.file },
15886
16325
  ...options.sessionId === void 0 ? {} : { sessionId: options.sessionId },
@@ -15897,16 +16336,23 @@ function createProgram(io = {}) {
15897
16336
  ...currentConfigPath() ? { configPath: currentConfigPath() } : {},
15898
16337
  projectConfigPath: envProjectConfigPath(env),
15899
16338
  ...io.authDir ? { authDir: io.authDir } : {},
16339
+ telemetryStateDir: telemetryContext().stateDir,
15900
16340
  ...options.json === void 0 && parentOptions.json === void 0 ? {} : { json: options.json ?? parentOptions.json },
15901
16341
  writeOut
15902
16342
  });
15903
16343
  });
15904
- const serve = program.command(cliCommands$1.serve).description("Serve configured Caplets as an MCP server.").option("--transport <transport>", "server transport: stdio or http").option("--host <host>", "HTTP bind host").option("--port <port>", "HTTP bind port").option("--path <path>", "HTTP service base path").option("--remote-state-path <path>", "server-owned remote credential state directory").option("--allow-unauthenticated-http", "allow unauthenticated HTTP serving on non-loopback hosts").option("--trust-proxy", "trust X-Forwarded-* headers from a reverse proxy").action(async (options) => {
16344
+ const serve = program.command(cliCommands$1.serve).description("Serve configured Caplets as an MCP server.").option("--transport <transport>", "server transport: stdio or http").option("--host <host>", "HTTP bind host").option("--port <port>", "HTTP bind port").option("--path <path>", "HTTP service base path").option("--remote-state-path <path>", "server-owned remote credential state directory").option("--upstream-url <url>", "upstream Caplets runtime URL to compose with this HTTP service").option("--allow-unauthenticated-http", "allow unauthenticated HTTP serving on non-loopback hosts").option("--trust-proxy", "trust X-Forwarded-* headers from a reverse proxy").action(async (options) => {
16345
+ printTelemetryNotice("serve");
15905
16346
  const resolved = resolveServeOptions(options);
15906
16347
  const configPath = currentConfigPath();
15907
16348
  await (io.serve ?? ((serveOptions) => serveResolvedCaplets(serveOptions, {
15908
16349
  ...configPath ? { configPath } : {},
15909
- ...io.authDir ? { authDir: io.authDir } : {}
16350
+ ...io.authDir ? { authDir: io.authDir } : {},
16351
+ telemetryEnv: env,
16352
+ telemetryStateDir: io.telemetryStateDir ?? defaultTelemetryStateDir(env),
16353
+ telemetrySurface: "serve",
16354
+ telemetryVisibility: "visible",
16355
+ telemetryRuntimeMode: runtimeModeForEnv(env)
15910
16356
  }, writeErr)))(resolved);
15911
16357
  });
15912
16358
  const daemonOptions = () => ({
@@ -15923,6 +16369,7 @@ function createProgram(io = {}) {
15923
16369
  })) addServeMigrationCommand(serve, name, replacement);
15924
16370
  const daemon = program.command(cliCommands$1.daemon).description("Install and manage the default Caplets daemon.");
15925
16371
  addDaemonInstallOptions(daemon.command("install").description("Install or update the default Caplets daemon.")).action(async (options) => {
16372
+ printTelemetryNotice("daemon");
15926
16373
  const prompt = options.json ? void 0 : createSetupPromptHandle(io, writeOut);
15927
16374
  try {
15928
16375
  const result = await installDaemon(daemonInstallOptions(options), {
@@ -15946,6 +16393,7 @@ function createProgram(io = {}) {
15946
16393
  }
15947
16394
  });
15948
16395
  addJsonOption(daemon.command("uninstall").description("Uninstall the default Caplets daemon.").option("--purge", "remove daemon config, state, and logs").option("--dry-run", "preview uninstall actions without mutation")).action(async (options) => {
16396
+ printTelemetryNotice("daemon");
15949
16397
  const result = await uninstallDaemon({
15950
16398
  purge: options.purge === true,
15951
16399
  dryRun: options.dryRun === true
@@ -15957,6 +16405,7 @@ function createProgram(io = {}) {
15957
16405
  writeOut(result.dryRun ? "Would uninstall Caplets daemon.\n" : "Uninstalled Caplets daemon.\n");
15958
16406
  });
15959
16407
  addJsonOption(daemon.command("start").description("Start the default Caplets daemon.")).action(async (options) => {
16408
+ printTelemetryNotice("daemon");
15960
16409
  const result = await startDaemon(daemonOptions());
15961
16410
  if (options.json) {
15962
16411
  writeOut(`${JSON.stringify(result, null, 2)}\n`);
@@ -15965,6 +16414,7 @@ function createProgram(io = {}) {
15965
16414
  writeOut(result.action === "restart" ? "Restarted Caplets daemon.\n" : "Started Caplets daemon.\n");
15966
16415
  });
15967
16416
  addJsonOption(daemon.command("restart").description("Restart the default Caplets daemon.")).action(async (options) => {
16417
+ printTelemetryNotice("daemon");
15968
16418
  const result = await restartDaemon(daemonOptions());
15969
16419
  if (options.json) {
15970
16420
  writeOut(`${JSON.stringify(result, null, 2)}\n`);
@@ -15973,6 +16423,7 @@ function createProgram(io = {}) {
15973
16423
  writeOut("Restarted Caplets daemon.\n");
15974
16424
  });
15975
16425
  addJsonOption(daemon.command("stop").description("Stop the default Caplets daemon.")).action(async (options) => {
16426
+ printTelemetryNotice("daemon");
15976
16427
  const result = await stopDaemon(daemonOptions());
15977
16428
  if (options.json) {
15978
16429
  writeOut(`${JSON.stringify(result, null, 2)}\n`);
@@ -15981,6 +16432,7 @@ function createProgram(io = {}) {
15981
16432
  writeOut("Stopped Caplets daemon.\n");
15982
16433
  });
15983
16434
  addJsonOption(daemon.command("status").description("Show the default Caplets daemon status.")).action(async (options) => {
16435
+ printTelemetryNotice("daemon");
15984
16436
  const status = await daemonStatus(daemonOptions());
15985
16437
  if (options.json) {
15986
16438
  writeOut(`${JSON.stringify(status, null, 2)}\n`);
@@ -15989,6 +16441,7 @@ function createProgram(io = {}) {
15989
16441
  writeOut(formatDaemonStatus(status));
15990
16442
  });
15991
16443
  addJsonOption(daemon.command("logs").description("Show Caplets daemon logs.").option("--follow", "follow appended log lines").option("--tail <lines>", "show the last number of lines").option("--stream <stream>", "log stream: stdout, stderr, or all")).action(async (options) => {
16444
+ printTelemetryNotice("daemon");
15992
16445
  const tail = options.tail === void 0 ? 10 : parseNonNegativeInteger(options.tail, "--tail");
15993
16446
  const stream = parseLogStream(options.stream);
15994
16447
  if (options.follow) {
@@ -16023,8 +16476,10 @@ function createProgram(io = {}) {
16023
16476
  }
16024
16477
  for (const entry of result.entries) writeOut(stream === "all" ? `[${entry.stream}] ${entry.line}\n` : `${entry.line}\n`);
16025
16478
  });
16026
- program.command(cliCommands$1.attach).description("Start a remote-backed Caplets MCP server.").argument("[url]", "remote Caplets service base URL").option("--transport <transport>", "server transport: stdio or http").option("--host <host>", "HTTP bind host").option("--port <port>", "HTTP bind port").option("--path <path>", "HTTP service base path").addOption(new Option("--remote-url <url>", "legacy alias for the remote Caplets service base URL").hideHelp()).option("--workspace <workspace>", "hosted Cloud workspace ID or slug").option("--allow-unauthenticated-http", "allow unauthenticated HTTP serving on non-loopback hosts").option("--trust-proxy", "trust X-Forwarded-* headers from a reverse proxy").option("--json", "print JSON status events").option("--verbose", "print detailed attach diagnostics").option("--once", "validate Project Binding once and exit").option("--project-root <path>", "test-only project root override").action(async (url, options) => {
16479
+ program.command(cliCommands$1.attach).description("Start a remote-backed Caplets MCP server.").argument("[url]", "remote Caplets service base URL").addOption(hiddenOption("--transport <transport>", "server transport: stdio or http")).addOption(hiddenOption("--host <host>", "HTTP bind host")).addOption(hiddenOption("--port <port>", "HTTP bind port")).addOption(hiddenOption("--path <path>", "HTTP service base path")).addOption(new Option("--remote-url <url>", "legacy alias for the remote Caplets service base URL").hideHelp()).option("--workspace <workspace>", "hosted Cloud workspace ID or slug").addOption(hiddenOption("--allow-unauthenticated-http", "allow unauthenticated HTTP serving on non-loopback hosts")).addOption(hiddenOption("--trust-proxy", "trust X-Forwarded-* headers from a reverse proxy")).option("--json", "print JSON status events").option("--verbose", "print detailed attach diagnostics").option("--once", "validate Project Binding once and exit").option("--project-root <path>", "test-only project root override").action(async (url, options) => {
16480
+ printTelemetryNotice("attach");
16027
16481
  try {
16482
+ rejectAttachHttpServeFlags(options);
16028
16483
  const remoteUrl = attachRemoteUrlFromArgs(url, options.remoteUrl);
16029
16484
  const attachOptions = {
16030
16485
  ...options,
@@ -16396,6 +16851,7 @@ function createProgram(io = {}) {
16396
16851
  writeOut(`Created ${localMutationTargetLabel(target, io)}Caplets config at ${path}\n`);
16397
16852
  });
16398
16853
  program.command(cliCommands$1.setup).description("Install or configure an agent integration for Caplets.").argument("[integration]", "integration: codex, claude-code, opencode, pi, or mcp-client").option("--remote", "configure for a remote Caplets server").option("--remote-url <url>", "remote Caplets service base URL").option("--server-url <url>", "remote Caplets service base URL").option("--output <path>", "config path to write for generic MCP setup").option("--dry-run", "print actions without running commands or writing files").option("--yes", "approve Caplet setup commands for the exact current content hash").option("--target <target>", "Caplet setup target: local, remote, or cloud", parseSetupTarget).option("--format <format>", "output format: plain or json", parseSetupFormat).action(async (integration, options) => {
16854
+ printTelemetryNotice("cli");
16399
16855
  const setupOptions = {
16400
16856
  ...options,
16401
16857
  env
@@ -16584,6 +17040,7 @@ function createProgram(io = {}) {
16584
17040
  writeOut(formatCapletList(rows, options.format ?? "plain"));
16585
17041
  });
16586
17042
  program.command(cliCommands$1.install).description("Install Caplets from a repo's caplets directory.").argument("<repo>", "local repo path, Git URL, or GitHub owner/repo").argument("[caplets...]", "optional Caplet IDs to install").option("--project", "install to the project Caplets root").option("-g, --global", "install to the user Caplets root").option("--remote", "install through remote control").option("--force", "overwrite installed Caplets").action(async (repo, capletIds, options) => {
17043
+ printTelemetryNotice("cli");
16587
17044
  const target = parseMutationTarget(options);
16588
17045
  if (target === "remote") {
16589
17046
  const result = await requireRemoteClientForTarget(io).request("install", {
@@ -16703,37 +17160,13 @@ function createProgram(io = {}) {
16703
17160
  writeAddResult(writeOut, `${localMutationTargetLabel(target, io)}HTTP`, result);
16704
17161
  });
16705
17162
  program.command(cliCommands$1.inspect).description("Print a configured Caplet card.").argument("<caplet>", "configured Caplet ID").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => {
16706
- await executeOperation(caplet, { operation: "inspect" }, {
16707
- writeOut,
16708
- writeErr,
16709
- setExitCode,
16710
- authDir: io.authDir,
16711
- env,
16712
- remote: remoteClientForCli(io),
16713
- format: options.format
16714
- });
17163
+ await executeOperation(caplet, { operation: "inspect" }, executeOperationIo(options.format));
16715
17164
  });
16716
17165
  program.command(cliCommands$1.checkBackend).description("Check backend availability for a configured Caplet.").argument("<caplet>", "configured Caplet ID").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => {
16717
- await executeOperation(caplet, { operation: "check" }, {
16718
- writeOut,
16719
- writeErr,
16720
- setExitCode,
16721
- authDir: io.authDir,
16722
- env,
16723
- remote: remoteClientForCli(io),
16724
- format: options.format
16725
- });
17166
+ await executeOperation(caplet, { operation: "check" }, executeOperationIo(options.format));
16726
17167
  });
16727
17168
  program.command(cliCommands$1.listTools).description("List downstream tools for a configured Caplet.").argument("<caplet>", "configured Caplet ID").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => {
16728
- await executeOperation(caplet, { operation: "tools" }, {
16729
- writeOut,
16730
- writeErr,
16731
- setExitCode,
16732
- authDir: io.authDir,
16733
- env,
16734
- remote: remoteClientForCli(io),
16735
- format: options.format
16736
- });
17169
+ await executeOperation(caplet, { operation: "tools" }, executeOperationIo(options.format));
16737
17170
  });
16738
17171
  program.command(cliCommands$1.searchTools).description("Search downstream tools for a configured Caplet.").argument("<caplet>", "configured Caplet ID").argument("<query>", "search query").option("--limit <n>", "maximum number of tools to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, query, options) => {
16739
17172
  await executeOperation(caplet, options.limit === void 0 ? {
@@ -16743,30 +17176,14 @@ function createProgram(io = {}) {
16743
17176
  operation: "search_tools",
16744
17177
  query,
16745
17178
  limit: options.limit
16746
- }, {
16747
- writeOut,
16748
- writeErr,
16749
- setExitCode,
16750
- authDir: io.authDir,
16751
- env,
16752
- remote: remoteClientForCli(io),
16753
- format: options.format
16754
- });
17179
+ }, executeOperationIo(options.format));
16755
17180
  });
16756
17181
  program.command(cliCommands$1.getTool).description("Print one downstream tool schema.").argument("<caplet-or-target>", "Caplet ID or qualified <caplet.tool> target").argument("[tool]", "downstream tool name when caplet is provided separately").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (capletOrTarget, toolArgument, options) => {
16757
17182
  const { caplet, tool } = parseQualifiedTarget(capletOrTarget, toolArgument);
16758
17183
  await executeOperation(caplet, {
16759
17184
  operation: "describe_tool",
16760
17185
  name: tool
16761
- }, {
16762
- writeOut,
16763
- writeErr,
16764
- setExitCode,
16765
- authDir: io.authDir,
16766
- env,
16767
- remote: remoteClientForCli(io),
16768
- format: options.format
16769
- });
17186
+ }, executeOperationIo(options.format));
16770
17187
  });
16771
17188
  program.command(cliCommands$1.callTool).description("Call one downstream tool.").argument("<caplet-or-target>", "Caplet ID or qualified <caplet.tool> target").argument("[tool]", "downstream tool name when caplet is provided separately").option("--args <json-object>", "JSON object of downstream tool arguments").option("--field <path>", "project a field from structured output", collect, []).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (capletOrTarget, toolArgument, options) => {
16772
17189
  const { caplet, tool } = parseQualifiedTarget(capletOrTarget, toolArgument);
@@ -16775,28 +17192,12 @@ function createProgram(io = {}) {
16775
17192
  name: tool,
16776
17193
  args: parseCallToolArgs(options.args),
16777
17194
  ...options.field && options.field.length > 0 ? { fields: options.field } : {}
16778
- }, {
16779
- writeOut,
16780
- writeErr,
16781
- setExitCode,
16782
- authDir: io.authDir,
16783
- env,
16784
- remote: remoteClientForCli(io),
16785
- format: options.format
16786
- });
17195
+ }, executeOperationIo(options.format));
16787
17196
  });
16788
17197
  program.command(cliCommands$1.listResources).description("List MCP resources for a configured MCP Caplet.").argument("<caplet>").option("--limit <n>", "maximum number of resources to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => executeOperation(caplet, options.limit === void 0 ? { operation: "resources" } : {
16789
17198
  operation: "resources",
16790
17199
  limit: options.limit
16791
- }, {
16792
- writeOut,
16793
- writeErr,
16794
- setExitCode,
16795
- authDir: io.authDir,
16796
- env,
16797
- remote: remoteClientForCli(io),
16798
- format: options.format
16799
- }));
17200
+ }, executeOperationIo(options.format)));
16800
17201
  program.command(cliCommands$1.searchResources).description("Search MCP resources and resource templates for a configured MCP Caplet.").argument("<caplet>").argument("<query>").option("--limit <n>", "maximum number of matches to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, query, options) => executeOperation(caplet, options.limit === void 0 ? {
16801
17202
  operation: "search_resources",
16802
17203
  query
@@ -16804,51 +17205,19 @@ function createProgram(io = {}) {
16804
17205
  operation: "search_resources",
16805
17206
  query,
16806
17207
  limit: options.limit
16807
- }, {
16808
- writeOut,
16809
- writeErr,
16810
- setExitCode,
16811
- authDir: io.authDir,
16812
- env,
16813
- remote: remoteClientForCli(io),
16814
- format: options.format
16815
- }));
17208
+ }, executeOperationIo(options.format)));
16816
17209
  program.command(cliCommands$1.listResourceTemplates).description("List MCP resource templates for a configured MCP Caplet.").argument("<caplet>").option("--limit <n>", "maximum number of templates to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => executeOperation(caplet, options.limit === void 0 ? { operation: "resource_templates" } : {
16817
17210
  operation: "resource_templates",
16818
17211
  limit: options.limit
16819
- }, {
16820
- writeOut,
16821
- writeErr,
16822
- setExitCode,
16823
- authDir: io.authDir,
16824
- env,
16825
- remote: remoteClientForCli(io),
16826
- format: options.format
16827
- }));
17212
+ }, executeOperationIo(options.format)));
16828
17213
  program.command(cliCommands$1.readResource).description("Read one MCP resource by URI.").argument("<caplet>").argument("<uri>").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, uri, options) => executeOperation(caplet, {
16829
17214
  operation: "read_resource",
16830
17215
  uri
16831
- }, {
16832
- writeOut,
16833
- writeErr,
16834
- setExitCode,
16835
- authDir: io.authDir,
16836
- env,
16837
- remote: remoteClientForCli(io),
16838
- format: options.format
16839
- }));
17216
+ }, executeOperationIo(options.format)));
16840
17217
  program.command(cliCommands$1.listPrompts).description("List MCP prompts for a configured MCP Caplet.").argument("<caplet>").option("--limit <n>", "maximum number of prompts to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => executeOperation(caplet, options.limit === void 0 ? { operation: "prompts" } : {
16841
17218
  operation: "prompts",
16842
17219
  limit: options.limit
16843
- }, {
16844
- writeOut,
16845
- writeErr,
16846
- setExitCode,
16847
- authDir: io.authDir,
16848
- env,
16849
- remote: remoteClientForCli(io),
16850
- format: options.format
16851
- }));
17220
+ }, executeOperationIo(options.format)));
16852
17221
  program.command(cliCommands$1.searchPrompts).description("Search MCP prompts for a configured MCP Caplet.").argument("<caplet>").argument("<query>").option("--limit <n>", "maximum number of prompts to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, query, options) => executeOperation(caplet, options.limit === void 0 ? {
16853
17222
  operation: "search_prompts",
16854
17223
  query
@@ -16856,30 +17225,14 @@ function createProgram(io = {}) {
16856
17225
  operation: "search_prompts",
16857
17226
  query,
16858
17227
  limit: options.limit
16859
- }, {
16860
- writeOut,
16861
- writeErr,
16862
- setExitCode,
16863
- authDir: io.authDir,
16864
- env,
16865
- remote: remoteClientForCli(io),
16866
- format: options.format
16867
- }));
17228
+ }, executeOperationIo(options.format)));
16868
17229
  program.command(cliCommands$1.getPrompt).description("Get one MCP prompt by name.").argument("<caplet-or-target>", "MCP Caplet ID or qualified <caplet.prompt> target").argument("[prompt]", "prompt name when caplet is provided separately").option("--args <json-object>", "JSON object of prompt arguments").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (capletOrTarget, promptArgument, options) => {
16869
17230
  const { caplet, tool: prompt } = parseQualifiedTarget(capletOrTarget, promptArgument);
16870
17231
  await executeOperation(caplet, {
16871
17232
  operation: "get_prompt",
16872
17233
  name: prompt,
16873
17234
  args: parseJsonObjectOption(options.args, "get-prompt --args")
16874
- }, {
16875
- writeOut,
16876
- writeErr,
16877
- setExitCode,
16878
- authDir: io.authDir,
16879
- env,
16880
- remote: remoteClientForCli(io),
16881
- format: options.format
16882
- });
17235
+ }, executeOperationIo(options.format));
16883
17236
  });
16884
17237
  program.command(cliCommands$1.complete).description("Complete an MCP prompt or resource-template argument.").argument("<caplet>").requiredOption("--argument <name>", "argument name").option("--value <value>", "argument prefix", "").option("--prompt <name>", "prompt name to complete").option("--resource-template <uri-template>", "resource template URI to complete").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => executeOperation(caplet, {
16885
17238
  operation: "complete",
@@ -16888,15 +17241,7 @@ function createProgram(io = {}) {
16888
17241
  name: options.argument,
16889
17242
  value: options.value
16890
17243
  }
16891
- }, {
16892
- writeOut,
16893
- writeErr,
16894
- setExitCode,
16895
- authDir: io.authDir,
16896
- env,
16897
- remote: remoteClientForCli(io),
16898
- format: options.format
16899
- }));
17244
+ }, executeOperationIo(options.format)));
16900
17245
  const config = program.command(cliCommands$1.config).description("Inspect Caplets config locations.");
16901
17246
  config.command("path").description("Print the effective user config path.").action(() => {
16902
17247
  writeOut(`${resolveConfigPath(currentConfigPath())}\n`);
@@ -17521,6 +17866,12 @@ async function executeLocalOperation(caplet, request, io, config) {
17521
17866
  ...io.authDir ? { authDir: io.authDir } : {},
17522
17867
  watch: false,
17523
17868
  writeErr: io.writeErr,
17869
+ telemetryEnv: io.env ?? process.env,
17870
+ telemetryStateDir: io.telemetryStateDir ?? defaultTelemetryStateDir(io.env ?? process.env),
17871
+ telemetrySurface: "cli",
17872
+ telemetryVisibility: "visible",
17873
+ telemetryRuntimeMode: runtimeModeForEnv(io.env ?? process.env),
17874
+ telemetryDebugSink: io.telemetryDebugSink,
17524
17875
  ...config ? { configLoader: () => config } : {}
17525
17876
  });
17526
17877
  try {