indusagi 0.13.6 → 0.13.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +7 -1
  3. package/dist/cli.js +114 -14
  4. package/dist/index.js +1618 -51
  5. package/dist/llmgateway.js +114 -14
  6. package/dist/mcp.js +812 -3
  7. package/dist/runtime.js +114 -14
  8. package/dist/sarvam-mcp.js +960 -0
  9. package/dist/shell-app.js +116 -14
  10. package/dist/smithy.js +114 -14
  11. package/dist/swarm.js +114 -14
  12. package/dist/tracing.js +37 -0
  13. package/dist/types/connectors-sarvam/connect.d.ts +14 -0
  14. package/dist/types/connectors-sarvam/connectors-sarvam.test.d.ts +14 -0
  15. package/dist/types/connectors-sarvam/index.d.ts +14 -0
  16. package/dist/types/connectors-sarvam/toolbox.d.ts +19 -0
  17. package/dist/types/connectors-sarvam/types.d.ts +49 -0
  18. package/dist/types/connectors-zoho/connect.d.ts +22 -0
  19. package/dist/types/connectors-zoho/connectors-zoho.test.d.ts +10 -0
  20. package/dist/types/connectors-zoho/index.d.ts +13 -0
  21. package/dist/types/connectors-zoho/toolbox.d.ts +19 -0
  22. package/dist/types/connectors-zoho/types.d.ts +56 -0
  23. package/dist/types/facade/mcp-core/client.d.ts +32 -0
  24. package/dist/types/facade/mcp-core/index.d.ts +4 -0
  25. package/dist/types/facade/mcp-core/oauth/flow.d.ts +72 -0
  26. package/dist/types/facade/mcp-core/oauth/index.d.ts +18 -0
  27. package/dist/types/facade/mcp-core/oauth/oauth.test.d.ts +8 -0
  28. package/dist/types/facade/mcp-core/oauth/provider.d.ts +51 -0
  29. package/dist/types/facade/mcp-core/oauth/token-store.d.ts +31 -0
  30. package/dist/types/facade/mcp-core/oauth/types.d.ts +96 -0
  31. package/dist/types/facade/mcp-core/toolbox-bridge.d.ts +46 -0
  32. package/dist/types/facade/mcp-core/toolbox-bridge.test.d.ts +8 -0
  33. package/dist/types/index.d.ts +2 -0
  34. package/dist/types/llmgateway/contract/model-card.d.ts +1 -1
  35. package/dist/types/llmgateway/credentials/secrets.d.ts +2 -0
  36. package/dist/zoho.js +1451 -0
  37. package/package.json +11 -4
  38. package/dist/knowledge/guides/authoring-an-agent.md +0 -53
  39. package/dist/knowledge/guides/choosing-tools.md +0 -49
  40. package/dist/knowledge/guides/model-selection.md +0 -51
  41. package/dist/knowledge/guides/writing-system-prompts.md +0 -53
  42. package/dist/knowledge/index.ts +0 -19
  43. package/dist/knowledge/loader.ts +0 -200
  44. package/dist/knowledge/manifest.json +0 -29
package/dist/index.js CHANGED
@@ -1,4 +1,10 @@
1
1
  var __defProp = Object.defineProperty;
2
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
+ }) : x)(function(x) {
5
+ if (typeof require !== "undefined") return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
7
+ });
2
8
  var __export = (target, all) => {
3
9
  for (var name in all)
4
10
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -261,6 +267,85 @@ var MODEL_CARDS = [
261
267
  cacheReadPerMTok: 0.15
262
268
  }
263
269
  },
270
+ // ── MiniMax (Anthropic-compatible Messages API) ─────────────────────────
271
+ // Wire: same dialect as Claude (`anthropic-messages`) at api.minimax.io.
272
+ // Auth: MINIMAX_API_KEY. Ids match facade/models.generated.ts.
273
+ {
274
+ id: "MiniMax-M2",
275
+ provider: "minimax",
276
+ api: "anthropic-messages",
277
+ displayName: "MiniMax M2",
278
+ baseUrl: "https://api.minimax.io/anthropic",
279
+ contextWindow: 204800,
280
+ maxOutputTokens: 131072,
281
+ modalities: ["text"],
282
+ reasoning: true,
283
+ cost: {
284
+ inputPerMTok: 0.3,
285
+ outputPerMTok: 1.2
286
+ }
287
+ },
288
+ {
289
+ id: "MiniMax-M2.1",
290
+ provider: "minimax",
291
+ api: "anthropic-messages",
292
+ displayName: "MiniMax M2.1",
293
+ baseUrl: "https://api.minimax.io/anthropic",
294
+ contextWindow: 204800,
295
+ maxOutputTokens: 131072,
296
+ modalities: ["text"],
297
+ reasoning: true,
298
+ cost: {
299
+ inputPerMTok: 0.3,
300
+ outputPerMTok: 1.2
301
+ }
302
+ },
303
+ {
304
+ id: "MiniMax-M2.5",
305
+ provider: "minimax",
306
+ api: "anthropic-messages",
307
+ displayName: "MiniMax M2.5",
308
+ baseUrl: "https://api.minimax.io/anthropic",
309
+ contextWindow: 204800,
310
+ maxOutputTokens: 131072,
311
+ modalities: ["text"],
312
+ reasoning: true,
313
+ cost: {
314
+ inputPerMTok: 0.3,
315
+ outputPerMTok: 1.2
316
+ }
317
+ },
318
+ {
319
+ id: "MiniMax-M2.7",
320
+ provider: "minimax",
321
+ api: "anthropic-messages",
322
+ displayName: "MiniMax M2.7",
323
+ baseUrl: "https://api.minimax.io/anthropic",
324
+ contextWindow: 204800,
325
+ maxOutputTokens: 131072,
326
+ modalities: ["text"],
327
+ reasoning: true,
328
+ cost: {
329
+ inputPerMTok: 0.3,
330
+ outputPerMTok: 1.2
331
+ }
332
+ },
333
+ {
334
+ id: "MiniMax-M3",
335
+ provider: "minimax",
336
+ api: "anthropic-messages",
337
+ displayName: "MiniMax M3",
338
+ baseUrl: "https://api.minimax.io/anthropic",
339
+ contextWindow: 1048576,
340
+ maxOutputTokens: 512e3,
341
+ modalities: ["text", "image"],
342
+ reasoning: true,
343
+ cost: {
344
+ inputPerMTok: 0.6,
345
+ outputPerMTok: 2.4,
346
+ cacheReadPerMTok: 0.12
347
+ }
348
+ },
264
349
  // ── Ollama (local; pricing is zero — your own hardware) ─────────────────
265
350
  {
266
351
  id: "llama3.1:8b",
@@ -423,6 +508,19 @@ var SECRET_TABLE = {
423
508
  envVars: ["MOONSHOT_API_KEY", "KIMI_API_KEY"],
424
509
  hint: "Moonshot AI (Kimi) API key."
425
510
  },
511
+ minimax: {
512
+ provider: "minimax",
513
+ scheme: "api-key",
514
+ envVars: ["MINIMAX_API_KEY"],
515
+ hint: "MiniMax global API key (api.minimax.io Anthropic-compatible)."
516
+ },
517
+ "minimax-cn": {
518
+ provider: "minimax-cn",
519
+ scheme: "api-key",
520
+ // Same key often works for both; CN-specific override first when set.
521
+ envVars: ["MINIMAX_CN_API_KEY", "MINIMAX_API_KEY"],
522
+ hint: "MiniMax China API key (api.minimaxi.com Anthropic-compatible)."
523
+ },
426
524
  ollama: {
427
525
  provider: "ollama",
428
526
  scheme: "none",
@@ -1263,7 +1361,7 @@ function endpointFor(model) {
1263
1361
  const base = (model.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
1264
1362
  return `${base}/v1/messages`;
1265
1363
  }
1266
- async function postMessages(url, apiKey, body, signal) {
1364
+ async function postMessages(url, apiKey, body, signal, provider = "anthropic") {
1267
1365
  let response;
1268
1366
  try {
1269
1367
  response = await fetch(url, {
@@ -1278,24 +1376,24 @@ async function postMessages(url, apiKey, body, signal) {
1278
1376
  });
1279
1377
  } catch (cause) {
1280
1378
  if (signal?.aborted) {
1281
- throw gatewayError("aborted", "request canceled before Anthropic responded", {
1282
- provider: "anthropic",
1379
+ throw gatewayError("aborted", `request canceled before ${provider} responded`, {
1380
+ provider,
1283
1381
  cause
1284
1382
  });
1285
1383
  }
1286
- throw gatewayError("transport", "network call to Anthropic failed", { provider: "anthropic", cause });
1384
+ throw gatewayError("transport", `network call to ${provider} failed`, { provider, cause });
1287
1385
  }
1288
1386
  if (!response.ok) {
1289
1387
  const detail = await readErrorBody(response);
1290
1388
  const kind = response.status === 401 || response.status === 403 ? "auth" : response.status === 429 ? "rate_limit" : "http";
1291
- throw gatewayError(kind, `Anthropic returned HTTP ${response.status}${detail}`, {
1292
- provider: "anthropic",
1389
+ throw gatewayError(kind, `${provider} returned HTTP ${response.status}${detail}`, {
1390
+ provider,
1293
1391
  status: response.status
1294
1392
  });
1295
1393
  }
1296
1394
  if (response.body === null) {
1297
- throw gatewayError("transport", "Anthropic response arrived with no body", {
1298
- provider: "anthropic",
1395
+ throw gatewayError("transport", `${provider} response arrived with no body`, {
1396
+ provider,
1299
1397
  status: response.status
1300
1398
  });
1301
1399
  }
@@ -1317,11 +1415,13 @@ function asGatewayError(cause) {
1317
1415
  }
1318
1416
  function createanthropicConnector(deps2) {
1319
1417
  const api = "anthropic-messages";
1320
- async function requireKey(options) {
1321
- const key = await deps2.resolveSecret("anthropic", options.apiKey);
1418
+ async function requireKey(model, options) {
1419
+ const provider = model.provider;
1420
+ const key = await deps2.resolveSecret(provider, options.apiKey);
1322
1421
  if (key === void 0 || key === "") {
1323
- throw gatewayError("auth", "no Anthropic API key found (set ANTHROPIC_API_KEY or pass options.apiKey)", {
1324
- provider: "anthropic"
1422
+ const hint = provider === "minimax" || provider === "minimax-cn" ? "set MINIMAX_API_KEY (or MINIMAX_CN_API_KEY for CN) or pass options.apiKey" : "set ANTHROPIC_API_KEY or pass options.apiKey";
1423
+ throw gatewayError("auth", `no API key found for provider "${provider}" (${hint})`, {
1424
+ provider
1325
1425
  });
1326
1426
  }
1327
1427
  return key;
@@ -1330,10 +1430,16 @@ function createanthropicConnector(deps2) {
1330
1430
  return channelOf(async function* run2() {
1331
1431
  const collected = [];
1332
1432
  try {
1333
- const apiKey = await requireKey(options);
1433
+ const apiKey = await requireKey(model, options);
1334
1434
  const body = toAnthropicRequest(conversation, model, options);
1335
1435
  body.stream = true;
1336
- const response = await postMessages(endpointFor(model), apiKey, body, options.signal);
1436
+ const response = await postMessages(
1437
+ endpointFor(model),
1438
+ apiKey,
1439
+ body,
1440
+ options.signal,
1441
+ model.provider
1442
+ );
1337
1443
  const wire = response.body;
1338
1444
  const state = { toolIds: /* @__PURE__ */ new Map(), usage: { inputTokens: 0, outputTokens: 0 } };
1339
1445
  for await (const event of sseEvents(wire, options.signal)) {
@@ -8595,6 +8701,1426 @@ function createComposioGateway(opts) {
8595
8701
  return createSaasGateway(createComposioBackend({ apiKey: opts.apiKey }));
8596
8702
  }
8597
8703
 
8704
+ // src/connectors-zoho/index.ts
8705
+ var connectors_zoho_exports = {};
8706
+ __export(connectors_zoho_exports, {
8707
+ connectZohoMcp: () => connectZohoMcp,
8708
+ zohoToolBoxFromClient: () => zohoToolBoxFromClient
8709
+ });
8710
+
8711
+ // src/facade/mcp-core/client.ts
8712
+ import { Client as Client2 } from "@modelcontextprotocol/sdk/client/index.js";
8713
+ import { StdioClientTransport as StdioClientTransport2 } from "@modelcontextprotocol/sdk/client/stdio.js";
8714
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
8715
+ import { SSEClientTransport as SSEClientTransport2 } from "@modelcontextprotocol/sdk/client/sse.js";
8716
+ import {
8717
+ UnauthorizedError
8718
+ } from "@modelcontextprotocol/sdk/client/auth.js";
8719
+ import {
8720
+ ListRootsRequestSchema,
8721
+ ElicitRequestSchema,
8722
+ CreateMessageRequestSchema,
8723
+ PingRequestSchema,
8724
+ ToolListChangedNotificationSchema,
8725
+ ResourceListChangedNotificationSchema,
8726
+ ResourceUpdatedNotificationSchema,
8727
+ PromptListChangedNotificationSchema,
8728
+ ProgressNotificationSchema,
8729
+ LoggingMessageNotificationSchema,
8730
+ CancelledNotificationSchema
8731
+ } from "@modelcontextprotocol/sdk/types.js";
8732
+
8733
+ // src/facade/mcp-core/errors.ts
8734
+ var MCPError = class extends Error {
8735
+ /** Error code */
8736
+ code;
8737
+ /** Additional error details */
8738
+ details;
8739
+ /** Server name where error occurred */
8740
+ serverName;
8741
+ /** Tool name if error occurred during tool execution */
8742
+ toolName;
8743
+ constructor(message, code, details, options) {
8744
+ super(message, options?.cause ? { cause: options.cause } : void 0);
8745
+ this.name = "MCPError";
8746
+ this.code = code;
8747
+ this.details = details;
8748
+ this.serverName = options?.serverName;
8749
+ this.toolName = options?.toolName;
8750
+ }
8751
+ /**
8752
+ * Convert error to JSON for logging/serialization.
8753
+ */
8754
+ toJSON() {
8755
+ return {
8756
+ name: this.name,
8757
+ message: this.message,
8758
+ code: this.code,
8759
+ details: this.details,
8760
+ serverName: this.serverName,
8761
+ toolName: this.toolName
8762
+ };
8763
+ }
8764
+ /**
8765
+ * Create a string representation of the error.
8766
+ */
8767
+ toString() {
8768
+ let str2 = `${this.name} [${this.code}]: ${this.message}`;
8769
+ if (this.serverName) str2 += ` (server: ${this.serverName})`;
8770
+ if (this.toolName) str2 += ` (tool: ${this.toolName})`;
8771
+ return str2;
8772
+ }
8773
+ };
8774
+ function createServerError(message, serverName, details) {
8775
+ return new MCPError(message, "SERVER_ERROR" /* SERVER_ERROR */, details, { serverName });
8776
+ }
8777
+
8778
+ // src/facade/mcp-core/client.ts
8779
+ var DEFAULT_REQUEST_TIMEOUT = 6e4;
8780
+ var DEFAULT_CONNECT_TIMEOUT = 3e4;
8781
+ var MCPClient = class {
8782
+ constructor(options) {
8783
+ this.options = options;
8784
+ this.serverName = options.name;
8785
+ this.config = options.config;
8786
+ this.timeout = options.timeout ?? DEFAULT_REQUEST_TIMEOUT;
8787
+ this.logHandler = options.logger;
8788
+ this.enableServerLogs = options.enableServerLogs ?? true;
8789
+ this.enableProgressTracking = options.enableProgressTracking ?? false;
8790
+ this._roots = options.roots ?? [];
8791
+ }
8792
+ options;
8793
+ client;
8794
+ transport;
8795
+ isConnected = false;
8796
+ connectionPromise = null;
8797
+ serverCapabilities;
8798
+ logHandler;
8799
+ enableServerLogs;
8800
+ enableProgressTracking;
8801
+ _roots;
8802
+ // --- Host-overridable handlers (Core of #13) --------------------------------
8803
+ // Server-initiated REQUESTS are answered by request handlers registered on the
8804
+ // SDK client; the handlers delegate to these host-supplied callbacks when set,
8805
+ // and fall back to sensible defaults otherwise.
8806
+ elicitationHandler;
8807
+ samplingHandler;
8808
+ // Server-initiated NOTIFICATIONS are dispatched to these (so the host can, for
8809
+ // example, re-list tools when a `tools/list_changed` arrives).
8810
+ resourceUpdatedHandler;
8811
+ resourceListChangedHandler;
8812
+ toolListChangedHandler;
8813
+ promptListChangedHandler;
8814
+ progressHandler;
8815
+ /** Server name */
8816
+ serverName;
8817
+ /** Server config */
8818
+ config;
8819
+ /** Request timeout */
8820
+ timeout;
8821
+ // ========================================================================
8822
+ // Connection Lifecycle
8823
+ // ========================================================================
8824
+ /**
8825
+ * Connect to the MCP server.
8826
+ * Safe to call multiple times - returns existing connection if already connected.
8827
+ */
8828
+ async connect() {
8829
+ if (this.isConnected) return;
8830
+ if (this.connectionPromise) {
8831
+ return this.connectionPromise;
8832
+ }
8833
+ this.connectionPromise = this.doConnect();
8834
+ try {
8835
+ await this.connectionPromise;
8836
+ } finally {
8837
+ this.connectionPromise = null;
8838
+ }
8839
+ }
8840
+ async doConnect() {
8841
+ const transport = this.createTransport(this.config);
8842
+ this.transport = transport;
8843
+ const client = new Client2(
8844
+ { name: "indusagi-coding-agent", version: "0.13.0" },
8845
+ {
8846
+ capabilities: {
8847
+ roots: { listChanged: true },
8848
+ elicitation: {},
8849
+ sampling: {}
8850
+ }
8851
+ }
8852
+ );
8853
+ this.client = client;
8854
+ this.registerServerHandlers(client);
8855
+ const prevOnClose = transport.onclose;
8856
+ transport.onclose = () => {
8857
+ this.isConnected = false;
8858
+ this.log("debug", "Transport closed");
8859
+ prevOnClose?.();
8860
+ };
8861
+ try {
8862
+ await client.connect(transport, { timeout: DEFAULT_CONNECT_TIMEOUT });
8863
+ } catch (err) {
8864
+ const recovered = await this.tryFinishOAuthAndReconnect(client, transport, err);
8865
+ if (!recovered) {
8866
+ throw this.wrapConnectError(err);
8867
+ }
8868
+ }
8869
+ this.serverCapabilities = client.getServerCapabilities();
8870
+ this.isConnected = true;
8871
+ this.log("info", `Connected to MCP server`);
8872
+ }
8873
+ /**
8874
+ * If `err` is an OAuth challenge and we have a provider + finishAuth-capable
8875
+ * transport, wait for the browser code, exchange tokens, then reconnect on a
8876
+ * **fresh** transport (the first transport is already `start()`ed and cannot
8877
+ * be reused — see StreamableHTTPClientTransport.start).
8878
+ *
8879
+ * Matches the official SDK CLI example (`simpleOAuthClient`):
8880
+ * finishAuth(code) → new StreamableHTTPClientTransport → client.connect again
8881
+ *
8882
+ * Client.connect already called `close()` on init failure, so the Client is
8883
+ * free to attach a new transport.
8884
+ */
8885
+ async tryFinishOAuthAndReconnect(client, transport, err) {
8886
+ if (!isAuthRequiredError(err)) return false;
8887
+ const provider = this.options.authProvider;
8888
+ if (!provider) return false;
8889
+ const finishAuth = transport.finishAuth;
8890
+ const waitForCode = provider.waitForAuthorizationCode;
8891
+ if (typeof finishAuth !== "function" || typeof waitForCode !== "function") {
8892
+ this.log(
8893
+ "error",
8894
+ "MCP server requires authentication but OAuth finish path is incomplete (need authProvider.waitForAuthorizationCode + transport.finishAuth)."
8895
+ );
8896
+ return false;
8897
+ }
8898
+ this.log("info", "MCP server requires OAuth \u2014 complete login in the browser\u2026");
8899
+ try {
8900
+ const code = await waitForCode.call(provider);
8901
+ this.log("info", "OAuth authorization code received \u2014 exchanging tokens\u2026");
8902
+ await finishAuth.call(transport, code);
8903
+ try {
8904
+ await transport.close?.();
8905
+ } catch {
8906
+ }
8907
+ const fresh = this.createTransport(this.config);
8908
+ this.transport = fresh;
8909
+ const prevOnClose = fresh.onclose;
8910
+ fresh.onclose = () => {
8911
+ this.isConnected = false;
8912
+ this.log("debug", "Transport closed");
8913
+ prevOnClose?.();
8914
+ };
8915
+ this.log("info", "Reconnecting MCP session with OAuth tokens\u2026");
8916
+ await client.connect(fresh, { timeout: DEFAULT_CONNECT_TIMEOUT });
8917
+ return true;
8918
+ } catch (oauthErr) {
8919
+ this.log(
8920
+ "error",
8921
+ `OAuth finish failed: ${oauthErr instanceof Error ? oauthErr.message : String(oauthErr)}`
8922
+ );
8923
+ throw this.wrapConnectError(oauthErr);
8924
+ }
8925
+ }
8926
+ /** Turn raw SDK / network errors into a clearer MCPError for callers. */
8927
+ wrapConnectError(err) {
8928
+ if (err instanceof MCPError) return err;
8929
+ const message = err instanceof Error ? err.message : String(err);
8930
+ if (isAuthRequiredError(err)) {
8931
+ return new MCPError(
8932
+ `Authentication required for MCP server "${this.serverName}". Pass auth: "auto" (default) to run the OAuth browser flow, or set a Bearer token via headers. Original: ${message}`,
8933
+ "CONNECTION_FAILED" /* CONNECTION_FAILED */,
8934
+ err,
8935
+ { serverName: this.serverName, cause: err instanceof Error ? err : void 0 }
8936
+ );
8937
+ }
8938
+ return new MCPError(
8939
+ `Failed to connect to MCP server "${this.serverName}": ${message}`,
8940
+ "CONNECTION_FAILED" /* CONNECTION_FAILED */,
8941
+ err,
8942
+ { serverName: this.serverName, cause: err instanceof Error ? err : void 0 }
8943
+ );
8944
+ }
8945
+ /**
8946
+ * Build the SDK transport for the configured server.
8947
+ *
8948
+ * - stdio: `StdioClientTransport` (inherits the parent env, merges config.env).
8949
+ * - http: `StreamableHTTPClientTransport` (POST + server-push SSE channel,
8950
+ * reconnect + session-expiry handled by the SDK), unless the config
8951
+ * asks for the legacy `SSEClientTransport`.
8952
+ */
8953
+ createTransport(config) {
8954
+ if ("command" in config) {
8955
+ return new StdioClientTransport2({
8956
+ command: config.command,
8957
+ args: config.args ?? [],
8958
+ env: { ...sanitizeEnv(process.env), ...config.env ?? {} },
8959
+ cwd: config.cwd,
8960
+ stderr: "pipe"
8961
+ });
8962
+ }
8963
+ if ("url" in config) {
8964
+ const requestInit = config.headers ? { headers: config.headers } : void 0;
8965
+ if (config.transport === "sse") {
8966
+ return new SSEClientTransport2(config.url, {
8967
+ requestInit,
8968
+ ...this.options.authProvider ? { authProvider: this.options.authProvider } : {}
8969
+ });
8970
+ }
8971
+ return new StreamableHTTPClientTransport(config.url, {
8972
+ requestInit,
8973
+ ...this.options.authProvider ? { authProvider: this.options.authProvider } : {}
8974
+ });
8975
+ }
8976
+ throw new MCPError(
8977
+ "Server configuration must include either a command or a url",
8978
+ "CONFIG_ERROR" /* CONFIG_ERROR */,
8979
+ void 0,
8980
+ { serverName: this.serverName }
8981
+ );
8982
+ }
8983
+ /**
8984
+ * Register handlers for server-initiated REQUESTS and NOTIFICATIONS.
8985
+ *
8986
+ * REQUESTS (have both `.method` AND `.id`) must be ANSWERED with a JSON-RPC
8987
+ * RESPONSE carrying the matching id — the SDK does this automatically for the
8988
+ * value a request handler returns (or rejects). The previous hand-rolled
8989
+ * transport routed these into the notification path, so they were never
8990
+ * answered (bug #13).
8991
+ *
8992
+ * Defaults:
8993
+ * - ping → {}
8994
+ * - roots/list → the configured roots
8995
+ * - elicitation/create→ decline ({action:"cancel"}) unless a host handler is set
8996
+ * - sampling → reject "Method not found" unless a host handler is set
8997
+ */
8998
+ registerServerHandlers(client) {
8999
+ client.setRequestHandler(PingRequestSchema, async () => ({}));
9000
+ client.setRequestHandler(ListRootsRequestSchema, async () => ({
9001
+ roots: this._roots.map((r) => ({ uri: r.uri, name: r.name }))
9002
+ }));
9003
+ client.setRequestHandler(ElicitRequestSchema, async (request) => {
9004
+ if (!this.elicitationHandler) {
9005
+ return { action: "cancel" };
9006
+ }
9007
+ const params = request.params;
9008
+ const elicitRequest = {
9009
+ message: params.message,
9010
+ requestedSchema: params.requestedSchema ?? {}
9011
+ };
9012
+ const result = await this.elicitationHandler(elicitRequest);
9013
+ return mapToSdkElicitResult(result);
9014
+ });
9015
+ client.setRequestHandler(CreateMessageRequestSchema, async (request) => {
9016
+ if (!this.samplingHandler) {
9017
+ throw new MCPError(
9018
+ "Method not found: sampling/createMessage",
9019
+ "SERVER_ERROR" /* SERVER_ERROR */,
9020
+ { method: "sampling/createMessage" },
9021
+ { serverName: this.serverName }
9022
+ );
9023
+ }
9024
+ return await this.samplingHandler(request.params);
9025
+ });
9026
+ client.setNotificationHandler(ToolListChangedNotificationSchema, () => {
9027
+ this.log("debug", "Tool list changed");
9028
+ this.toolListChangedHandler?.();
9029
+ });
9030
+ client.setNotificationHandler(ResourceListChangedNotificationSchema, () => {
9031
+ this.log("debug", "Resource list changed");
9032
+ this.resourceListChangedHandler?.();
9033
+ });
9034
+ client.setNotificationHandler(ResourceUpdatedNotificationSchema, (n) => {
9035
+ const uri = n.params?.uri;
9036
+ this.log("debug", `Resource updated: ${uri}`);
9037
+ if (uri) this.resourceUpdatedHandler?.({ uri });
9038
+ });
9039
+ client.setNotificationHandler(PromptListChangedNotificationSchema, () => {
9040
+ this.log("debug", "Prompt list changed");
9041
+ this.promptListChangedHandler?.();
9042
+ });
9043
+ client.setNotificationHandler(ProgressNotificationSchema, (n) => {
9044
+ const params = n.params;
9045
+ this.log("debug", `Progress: ${JSON.stringify(params)}`);
9046
+ if (this.progressHandler && params) {
9047
+ const notification = {
9048
+ progressToken: params.progressToken,
9049
+ progress: params.progress,
9050
+ total: params.total,
9051
+ message: params.message
9052
+ };
9053
+ this.progressHandler(notification);
9054
+ }
9055
+ });
9056
+ client.setNotificationHandler(LoggingMessageNotificationSchema, (n) => {
9057
+ if (this.enableServerLogs && n.params) {
9058
+ const { level, ...rest } = n.params;
9059
+ this.log(level || "info", "[SERVER]", rest);
9060
+ }
9061
+ });
9062
+ client.setNotificationHandler(CancelledNotificationSchema, (n) => {
9063
+ this.log("debug", `Request cancelled: ${JSON.stringify(n.params)}`);
9064
+ });
9065
+ }
9066
+ /**
9067
+ * Disconnect from the MCP server.
9068
+ *
9069
+ * The SDK's `client.close()` closes the transport and rejects any pending
9070
+ * requests — a clean teardown for both stdio (graceful subprocess shutdown)
9071
+ * and HTTP (close the push channel / end the session).
9072
+ */
9073
+ async disconnect() {
9074
+ if (!this.client) {
9075
+ this.log("debug", "Disconnect called but not connected");
9076
+ this.isConnected = false;
9077
+ return;
9078
+ }
9079
+ this.log("debug", "Disconnecting from MCP server");
9080
+ try {
9081
+ await this.client.close();
9082
+ } catch (error) {
9083
+ this.log("error", `Error during disconnect: ${error}`);
9084
+ } finally {
9085
+ this.client = void 0;
9086
+ this.transport = void 0;
9087
+ this.isConnected = false;
9088
+ this.log("debug", "Successfully disconnected");
9089
+ }
9090
+ }
9091
+ /**
9092
+ * Whether the client is connected.
9093
+ */
9094
+ get connected() {
9095
+ return this.isConnected;
9096
+ }
9097
+ // ========================================================================
9098
+ // Tool Operations
9099
+ // ========================================================================
9100
+ /**
9101
+ * List all tools available from the server.
9102
+ */
9103
+ async listTools() {
9104
+ this.ensureConnected();
9105
+ const result = await this.client.listTools(void 0, {
9106
+ timeout: this.timeout
9107
+ });
9108
+ return result.tools;
9109
+ }
9110
+ /**
9111
+ * Call a tool on the server.
9112
+ */
9113
+ async callTool(name, args) {
9114
+ this.ensureConnected();
9115
+ this.log("debug", `Calling tool: ${name}`);
9116
+ try {
9117
+ const result = await this.client.callTool(
9118
+ { name, arguments: args },
9119
+ void 0,
9120
+ { timeout: this.timeout }
9121
+ );
9122
+ this.log("debug", `Tool ${name} executed successfully`);
9123
+ return result;
9124
+ } catch (error) {
9125
+ this.log("error", `Tool ${name} failed: ${error}`);
9126
+ throw this.wrapError(error);
9127
+ }
9128
+ }
9129
+ // ========================================================================
9130
+ // Resource Operations
9131
+ // ========================================================================
9132
+ /**
9133
+ * List all resources available from the server.
9134
+ */
9135
+ async listResources() {
9136
+ this.ensureConnected();
9137
+ if (!this.serverCapabilities?.resources) {
9138
+ return [];
9139
+ }
9140
+ const result = await this.client.listResources(void 0, {
9141
+ timeout: this.timeout
9142
+ });
9143
+ return result.resources;
9144
+ }
9145
+ /**
9146
+ * Read a resource from the server.
9147
+ */
9148
+ async readResource(uri) {
9149
+ this.ensureConnected();
9150
+ return await this.client.readResource({ uri }, { timeout: this.timeout });
9151
+ }
9152
+ /**
9153
+ * Subscribe to resource updates.
9154
+ */
9155
+ async subscribeResource(uri) {
9156
+ this.ensureConnected();
9157
+ await this.client.subscribeResource({ uri }, { timeout: this.timeout });
9158
+ }
9159
+ /**
9160
+ * Unsubscribe from resource updates.
9161
+ */
9162
+ async unsubscribeResource(uri) {
9163
+ this.ensureConnected();
9164
+ await this.client.unsubscribeResource({ uri }, { timeout: this.timeout });
9165
+ }
9166
+ // ========================================================================
9167
+ // Prompt Operations
9168
+ // ========================================================================
9169
+ /**
9170
+ * List all prompts available from the server.
9171
+ */
9172
+ async listPrompts() {
9173
+ this.ensureConnected();
9174
+ if (!this.serverCapabilities?.prompts) {
9175
+ return [];
9176
+ }
9177
+ const result = await this.client.listPrompts(void 0, {
9178
+ timeout: this.timeout
9179
+ });
9180
+ return result.prompts;
9181
+ }
9182
+ /**
9183
+ * Get a prompt from the server.
9184
+ */
9185
+ async getPrompt(name, args) {
9186
+ this.ensureConnected();
9187
+ return await this.client.getPrompt(
9188
+ { name, arguments: args },
9189
+ { timeout: this.timeout }
9190
+ );
9191
+ }
9192
+ // ========================================================================
9193
+ // Roots Operations
9194
+ // ========================================================================
9195
+ /**
9196
+ * Get the configured roots.
9197
+ */
9198
+ get roots() {
9199
+ return [...this._roots];
9200
+ }
9201
+ /**
9202
+ * Update the roots and notify the server.
9203
+ */
9204
+ async setRoots(roots) {
9205
+ this.log("debug", `Updating roots to ${roots.length} entries`);
9206
+ this._roots = [...roots];
9207
+ if (this.isConnected && this.client) {
9208
+ try {
9209
+ await this.client.sendRootsListChanged();
9210
+ } catch (error) {
9211
+ this.log("debug", `Failed to send roots/list_changed: ${error}`);
9212
+ }
9213
+ }
9214
+ }
9215
+ // ========================================================================
9216
+ // Handler Registration
9217
+ //
9218
+ // Setters STORE the host handler (and, for notifications, the SDK handler
9219
+ // registered in `registerServerHandlers` delegates to the stored callback).
9220
+ // This is the host-overridable layer at the core of bug #13.
9221
+ // ========================================================================
9222
+ /**
9223
+ * Set a handler for resource updated notifications.
9224
+ */
9225
+ setResourceUpdatedHandler(handler) {
9226
+ this.resourceUpdatedHandler = handler;
9227
+ this.log("debug", "Resource updated handler registered");
9228
+ }
9229
+ /**
9230
+ * Set a handler for resource list changed notifications.
9231
+ */
9232
+ setResourceListChangedHandler(handler) {
9233
+ this.resourceListChangedHandler = handler;
9234
+ this.log("debug", "Resource list changed handler registered");
9235
+ }
9236
+ /**
9237
+ * Set a handler for tool list changed notifications.
9238
+ */
9239
+ setToolListChangedHandler(handler) {
9240
+ this.toolListChangedHandler = handler;
9241
+ this.log("debug", "Tool list changed handler registered");
9242
+ }
9243
+ /**
9244
+ * Set a handler for prompt list changed notifications.
9245
+ */
9246
+ setPromptListChangedHandler(handler) {
9247
+ this.promptListChangedHandler = handler;
9248
+ this.log("debug", "Prompt list changed handler registered");
9249
+ }
9250
+ /**
9251
+ * Set a handler for elicitation requests. When set, the server's
9252
+ * `elicitation/create` REQUEST is answered with the host's decision; when
9253
+ * unset the request is declined ({action:"cancel"}).
9254
+ */
9255
+ setElicitationHandler(handler) {
9256
+ this.elicitationHandler = handler;
9257
+ this.log("debug", "Elicitation handler registered");
9258
+ }
9259
+ /**
9260
+ * Set a handler for sampling (`sampling/createMessage`) requests. When set,
9261
+ * the server request is answered with the host's result; when unset the
9262
+ * request is rejected with "Method not found".
9263
+ */
9264
+ setSamplingHandler(handler) {
9265
+ this.samplingHandler = handler;
9266
+ this.log("debug", "Sampling handler registered");
9267
+ }
9268
+ /**
9269
+ * Set a handler for progress notifications.
9270
+ */
9271
+ setProgressHandler(handler) {
9272
+ this.progressHandler = handler;
9273
+ this.log("debug", "Progress handler registered");
9274
+ }
9275
+ // ========================================================================
9276
+ // Private Methods
9277
+ // ========================================================================
9278
+ ensureConnected() {
9279
+ if (!this.isConnected || !this.client) {
9280
+ throw new MCPError(
9281
+ "Not connected to MCP server",
9282
+ "NOT_CONNECTED" /* NOT_CONNECTED */,
9283
+ void 0,
9284
+ { serverName: this.serverName }
9285
+ );
9286
+ }
9287
+ }
9288
+ /**
9289
+ * Normalize an SDK/transport error into an MCPError. Session-expiry style
9290
+ * failures (404 session / -32001) are surfaced as SESSION_ERROR so callers
9291
+ * (the pool) can decide to reconnect; the SDK's StreamableHTTP transport
9292
+ * already attempts a bounded reconnect re-using the session id before this.
9293
+ */
9294
+ wrapError(error) {
9295
+ if (error instanceof MCPError) return error;
9296
+ const message = error instanceof Error ? error.message : String(error);
9297
+ const code = error.code;
9298
+ if (code === -32001 || /\b404\b|session/i.test(message)) {
9299
+ return new MCPError(message, "SESSION_ERROR" /* SESSION_ERROR */, { code }, {
9300
+ serverName: this.serverName
9301
+ });
9302
+ }
9303
+ return new MCPError(message, "SERVER_ERROR" /* SERVER_ERROR */, { code }, {
9304
+ serverName: this.serverName
9305
+ });
9306
+ }
9307
+ log(level, message, details) {
9308
+ const msg = `[${this.serverName}] ${message}`;
9309
+ if (this.logHandler) {
9310
+ this.logHandler({
9311
+ level,
9312
+ message: msg,
9313
+ timestamp: /* @__PURE__ */ new Date(),
9314
+ serverName: this.serverName,
9315
+ details
9316
+ });
9317
+ } else {
9318
+ if (process.env.INDUSAGI_DEBUG || level === "error") {
9319
+ const prefix = `[MCP:${level.toUpperCase()}]`;
9320
+ if (details) {
9321
+ if (level === "error") {
9322
+ console.error(prefix, msg, details);
9323
+ } else {
9324
+ console.log(prefix, msg, details);
9325
+ }
9326
+ } else {
9327
+ if (level === "error") {
9328
+ console.error(prefix, msg);
9329
+ } else {
9330
+ console.log(prefix, msg);
9331
+ }
9332
+ }
9333
+ }
9334
+ }
9335
+ }
9336
+ };
9337
+ function mapToSdkElicitResult(result) {
9338
+ if (result.action === "accept") {
9339
+ return {
9340
+ action: "accept",
9341
+ content: result.content ?? {}
9342
+ };
9343
+ }
9344
+ return { action: result.action };
9345
+ }
9346
+ function isAuthRequiredError(err) {
9347
+ if (err == null) return false;
9348
+ if (err instanceof UnauthorizedError) return true;
9349
+ if (typeof err === "object") {
9350
+ const code = err.code;
9351
+ if (code === 401 || code === "401") return true;
9352
+ }
9353
+ if (err instanceof Error) {
9354
+ const msg = err.message;
9355
+ if (/unauthorized/i.test(msg)) return true;
9356
+ if (/authentication required/i.test(msg)) return true;
9357
+ if (/auth required/i.test(msg)) return true;
9358
+ if (err.name === "UnauthorizedError") return true;
9359
+ }
9360
+ return false;
9361
+ }
9362
+ function sanitizeEnv(env) {
9363
+ const out = {};
9364
+ for (const [k, v] of Object.entries(env)) {
9365
+ if (typeof v === "string") out[k] = v;
9366
+ }
9367
+ return out;
9368
+ }
9369
+
9370
+ // src/facade/mcp-core/oauth/flow.ts
9371
+ import { createServer } from "node:http";
9372
+ var CALLBACK_TIMEOUT_MS = 5 * 60 * 1e3;
9373
+ var SUCCESS_HTML = '<!doctype html><html><body style="font-family:system-ui;padding:2rem"><h1>Authorization complete</h1><p>You can close this tab and return to your terminal.</p></body></html>';
9374
+ var ERROR_HTML = (msg) => `<!doctype html><html><body style="font-family:system-ui;padding:2rem"><h1>Authorization failed</h1><pre>${escapeHtml(msg)}</pre></body></html>`;
9375
+ function escapeHtml(s) {
9376
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
9377
+ }
9378
+ async function startOAuthCallbackServer(options) {
9379
+ const timeoutMs = options?.timeoutMs ?? CALLBACK_TIMEOUT_MS;
9380
+ const logger = options?.logger;
9381
+ let settled2 = false;
9382
+ let timer;
9383
+ let resolveCode;
9384
+ let rejectCode;
9385
+ const codePromise = new Promise((res, rej) => {
9386
+ resolveCode = res;
9387
+ rejectCode = rej;
9388
+ });
9389
+ codePromise.catch(() => {
9390
+ });
9391
+ let expectedState;
9392
+ const clearTimer = () => {
9393
+ if (timer !== void 0) {
9394
+ clearTimeout(timer);
9395
+ timer = void 0;
9396
+ }
9397
+ };
9398
+ const settleReject = (err) => {
9399
+ if (settled2) return;
9400
+ settled2 = true;
9401
+ clearTimer();
9402
+ try {
9403
+ server.close();
9404
+ } catch {
9405
+ }
9406
+ rejectCode(err);
9407
+ };
9408
+ const settleResolve = (code) => {
9409
+ if (settled2) return;
9410
+ settled2 = true;
9411
+ clearTimer();
9412
+ resolveCode(code);
9413
+ setTimeout(() => {
9414
+ try {
9415
+ server.close();
9416
+ } catch {
9417
+ }
9418
+ }, 50);
9419
+ };
9420
+ const server = createServer((req, res) => {
9421
+ if (settled2) {
9422
+ res.statusCode = 410;
9423
+ res.end("already used");
9424
+ return;
9425
+ }
9426
+ handleRequest(
9427
+ req,
9428
+ res,
9429
+ expectedState,
9430
+ (code) => settleResolve(code),
9431
+ (err) => settleReject(err),
9432
+ logger
9433
+ );
9434
+ });
9435
+ await new Promise((resolveListen, rejectListen) => {
9436
+ server.once("error", rejectListen);
9437
+ server.listen(0, "127.0.0.1", () => resolveListen());
9438
+ });
9439
+ const address = server.address();
9440
+ if (!address) {
9441
+ server.close();
9442
+ throw new Error("oauth-callback: server bound to no address");
9443
+ }
9444
+ const port = address.port;
9445
+ const redirectUri = `http://127.0.0.1:${port}/callback`;
9446
+ const close = () => {
9447
+ if (!settled2) {
9448
+ settleReject(new Error("oauth-callback: closed before browser login completed"));
9449
+ } else {
9450
+ clearTimer();
9451
+ try {
9452
+ server.close();
9453
+ } catch {
9454
+ }
9455
+ }
9456
+ };
9457
+ return {
9458
+ redirectUri,
9459
+ port,
9460
+ waitForCode(state) {
9461
+ expectedState = state;
9462
+ logger?.debug(`oauth-callback: waiting for browser at ${redirectUri}`);
9463
+ if (!settled2 && timer === void 0) {
9464
+ timer = setTimeout(() => {
9465
+ settleReject(
9466
+ new Error(
9467
+ `oauth-callback: timed out after ${timeoutMs}ms waiting for browser login at ${redirectUri}`
9468
+ )
9469
+ );
9470
+ }, timeoutMs);
9471
+ timer.unref();
9472
+ }
9473
+ return codePromise;
9474
+ },
9475
+ close
9476
+ };
9477
+ }
9478
+ function handleRequest(req, res, expectedState, onCode, onError, logger) {
9479
+ const url = new URL(req.url ?? "/", "http://127.0.0.1");
9480
+ if (url.pathname !== "/callback") {
9481
+ if (url.pathname === "/favicon.ico") {
9482
+ res.statusCode = 404;
9483
+ res.end();
9484
+ return;
9485
+ }
9486
+ res.statusCode = 404;
9487
+ res.end("not found");
9488
+ return;
9489
+ }
9490
+ const errorParam = url.searchParams.get("error");
9491
+ if (errorParam) {
9492
+ const desc = url.searchParams.get("error_description") ?? "";
9493
+ logger?.warn(`oauth-callback: server returned error ${errorParam} ${desc}`);
9494
+ res.statusCode = 400;
9495
+ res.setHeader("content-type", "text/html; charset=utf-8");
9496
+ res.end(ERROR_HTML(`${errorParam}${desc ? ": " + desc : ""}`));
9497
+ onError(new Error(`OAuth provider returned error: ${errorParam}${desc ? " \u2014 " + desc : ""}`));
9498
+ return;
9499
+ }
9500
+ const code = url.searchParams.get("code");
9501
+ const state = url.searchParams.get("state");
9502
+ if (!code) {
9503
+ res.statusCode = 400;
9504
+ res.setHeader("content-type", "text/html; charset=utf-8");
9505
+ res.end(ERROR_HTML("missing code"));
9506
+ return;
9507
+ }
9508
+ if (expectedState && state && state !== expectedState) {
9509
+ res.statusCode = 400;
9510
+ res.setHeader("content-type", "text/html; charset=utf-8");
9511
+ res.end(ERROR_HTML("state mismatch \u2014 possible CSRF"));
9512
+ onError(new Error("OAuth callback state did not match \u2014 refusing to exchange code"));
9513
+ return;
9514
+ }
9515
+ res.statusCode = 200;
9516
+ res.setHeader("content-type", "text/html; charset=utf-8");
9517
+ res.end(SUCCESS_HTML);
9518
+ logger?.debug(`oauth-callback: received authorization code (${code.length} chars)`);
9519
+ onCode(code);
9520
+ }
9521
+ async function openBrowser(url) {
9522
+ try {
9523
+ const mod = await import("node:child_process").catch(() => null);
9524
+ if (!mod) return false;
9525
+ const cmd = platformOpenCommand(url);
9526
+ if (!cmd) return false;
9527
+ const child = mod.spawn(cmd.cmd, cmd.args, { detached: true, stdio: "ignore" });
9528
+ child.unref();
9529
+ return true;
9530
+ } catch {
9531
+ return false;
9532
+ }
9533
+ }
9534
+ function platformOpenCommand(url) {
9535
+ switch (process.platform) {
9536
+ case "darwin":
9537
+ return { cmd: "open", args: [url] };
9538
+ case "win32":
9539
+ return { cmd: "rundll32", args: ["url.dll,FileProtocolHandler", url] };
9540
+ default:
9541
+ return { cmd: "xdg-open", args: [url] };
9542
+ }
9543
+ }
9544
+
9545
+ // src/facade/mcp-core/oauth/token-store.ts
9546
+ import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
9547
+ import { dirname, join as join2 } from "node:path";
9548
+ function defaultCredentialsDir() {
9549
+ const xdg = process.env.XDG_CONFIG_HOME;
9550
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
9551
+ if (xdg && xdg.length > 0) return join2(xdg, "indusagi", "credentials", "mcp");
9552
+ if (home.length > 0) return join2(home, ".config", "indusagi", "credentials", "mcp");
9553
+ return join2(process.cwd(), ".indusagi", "credentials", "mcp");
9554
+ }
9555
+ function resolveCredentialsDir(override) {
9556
+ return override && override.length > 0 ? override : defaultCredentialsDir();
9557
+ }
9558
+ function credentialsPath(credentialsDir, id) {
9559
+ const safe = id.replace(/[^a-zA-Z0-9._-]/g, "-");
9560
+ return join2(credentialsDir, `${safe}.json`);
9561
+ }
9562
+ function readMcpOAuthState(credentialsDir, id) {
9563
+ const path2 = credentialsPath(credentialsDir, id);
9564
+ if (!existsSync(path2)) return void 0;
9565
+ let raw;
9566
+ try {
9567
+ raw = readFileSync(path2, "utf8");
9568
+ } catch (e) {
9569
+ return void 0;
9570
+ }
9571
+ let parsed;
9572
+ try {
9573
+ parsed = JSON.parse(raw);
9574
+ } catch (e) {
9575
+ return void 0;
9576
+ }
9577
+ if (typeof parsed !== "object" || parsed === null) return void 0;
9578
+ return { id, updatedAt: Date.now(), ...parsed };
9579
+ }
9580
+ function writeMcpOAuthState(credentialsDir, state) {
9581
+ const path2 = credentialsPath(credentialsDir, state.id);
9582
+ mkdirSync(dirname(path2), { recursive: true });
9583
+ const tmp = `${path2}.tmp-${process.pid}-${Date.now()}`;
9584
+ const json = JSON.stringify({ ...state, updatedAt: Date.now() }, null, 2);
9585
+ writeFileSync(tmp, json, { mode: 384 });
9586
+ renameSync(tmp, path2);
9587
+ try {
9588
+ chmodSync(path2, 384);
9589
+ } catch {
9590
+ }
9591
+ }
9592
+ function clearMcpOAuthState(credentialsDir, id) {
9593
+ const path2 = credentialsPath(credentialsDir, id);
9594
+ try {
9595
+ const fs2 = __require("node:fs");
9596
+ fs2.rmSync(path2, { force: true });
9597
+ } catch {
9598
+ }
9599
+ }
9600
+
9601
+ // src/facade/mcp-core/oauth/provider.ts
9602
+ function randomState() {
9603
+ const bytes = new Uint8Array(16);
9604
+ crypto.getRandomValues(bytes);
9605
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
9606
+ }
9607
+ function toSdkTokens(t) {
9608
+ return {
9609
+ access_token: t.access_token,
9610
+ refresh_token: t.refresh_token,
9611
+ scope: t.scope,
9612
+ token_type: t.token_type ?? "Bearer",
9613
+ expires_in: t.expires_at !== void 0 ? Math.max(0, Math.floor((t.expires_at - Date.now()) / 1e3)) : void 0
9614
+ };
9615
+ }
9616
+ function toSdkClientInfo(c) {
9617
+ return {
9618
+ client_id: c.client_id,
9619
+ client_secret: c.client_secret,
9620
+ client_id_issued_at: c.client_id_issued_at,
9621
+ client_secret_expires_at: c.client_secret_expires_at
9622
+ };
9623
+ }
9624
+ async function createMcpOAuthProvider(config) {
9625
+ const credentialsDir = resolveCredentialsDir(config.credentialsDir);
9626
+ const openBrowserEnabled = config.openBrowser ?? Boolean(process.env.CI !== "true" && process.stdout.isTTY);
9627
+ let callback;
9628
+ let redirectUrl;
9629
+ if (config.redirectUrl) {
9630
+ redirectUrl = config.redirectUrl;
9631
+ } else {
9632
+ callback = await startOAuthCallbackServer({
9633
+ logger: config.logger ? { debug: config.logger.debug, warn: config.logger.warn } : void 0
9634
+ });
9635
+ redirectUrl = callback.redirectUri;
9636
+ }
9637
+ const clientMetadata = {
9638
+ client_name: config.clientMetadata?.client_name ?? "indusagi-mcp-client",
9639
+ client_uri: config.clientMetadata?.client_uri,
9640
+ logo_uri: config.clientMetadata?.logo_uri,
9641
+ contacts: config.clientMetadata?.contacts ? [...config.clientMetadata.contacts] : void 0,
9642
+ tos_uri: config.clientMetadata?.tos_uri,
9643
+ policy_uri: config.clientMetadata?.policy_uri,
9644
+ redirect_uris: config.clientMetadata?.redirect_uris ? [...config.clientMetadata.redirect_uris] : [redirectUrl],
9645
+ token_endpoint_auth_method: config.clientMetadata?.token_endpoint_auth_method ?? "none",
9646
+ grant_types: config.clientMetadata?.grant_types ? [...config.clientMetadata.grant_types] : ["authorization_code", "refresh_token"],
9647
+ response_types: config.clientMetadata?.response_types ? [...config.clientMetadata.response_types] : ["code"],
9648
+ scope: config.clientMetadata?.scope
9649
+ };
9650
+ let lastAuthUrl;
9651
+ let lastState;
9652
+ let pendingCode;
9653
+ const provider = {
9654
+ id: config.id,
9655
+ credentialsDir,
9656
+ redirectUrl,
9657
+ get clientMetadata() {
9658
+ return clientMetadata;
9659
+ },
9660
+ state() {
9661
+ const s = randomState();
9662
+ lastState = s;
9663
+ return s;
9664
+ },
9665
+ clientInformation() {
9666
+ const state = readMcpOAuthState(credentialsDir, config.id);
9667
+ return state?.client ? toSdkClientInfo(state.client) : void 0;
9668
+ },
9669
+ saveClientInformation(client) {
9670
+ const state = readMcpOAuthState(credentialsDir, config.id) ?? {
9671
+ id: config.id,
9672
+ updatedAt: Date.now()
9673
+ };
9674
+ writeMcpOAuthState(credentialsDir, {
9675
+ ...state,
9676
+ client: {
9677
+ client_id: client.client_id,
9678
+ client_secret: client.client_secret,
9679
+ client_id_issued_at: client.client_id_issued_at,
9680
+ client_secret_expires_at: client.client_secret_expires_at
9681
+ }
9682
+ });
9683
+ },
9684
+ tokens() {
9685
+ const state = readMcpOAuthState(credentialsDir, config.id);
9686
+ return state?.tokens ? toSdkTokens(state.tokens) : void 0;
9687
+ },
9688
+ saveTokens(tokens) {
9689
+ const state = readMcpOAuthState(credentialsDir, config.id) ?? {
9690
+ id: config.id,
9691
+ updatedAt: Date.now()
9692
+ };
9693
+ writeMcpOAuthState(credentialsDir, {
9694
+ ...state,
9695
+ tokens: {
9696
+ access_token: tokens.access_token,
9697
+ refresh_token: tokens.refresh_token,
9698
+ scope: tokens.scope,
9699
+ token_type: tokens.token_type,
9700
+ expires_at: tokens.expires_in !== void 0 ? Date.now() + Math.trunc(tokens.expires_in) * 1e3 : void 0
9701
+ }
9702
+ });
9703
+ },
9704
+ async redirectToAuthorization(authorizationUrl) {
9705
+ const url = authorizationUrl.toString();
9706
+ lastAuthUrl = url;
9707
+ try {
9708
+ lastState = new URL(url).searchParams.get("state") ?? lastState;
9709
+ } catch {
9710
+ }
9711
+ const prompt = `oauth: open this URL in your browser to authorize Zoho MCP:
9712
+ ${url}`;
9713
+ config.logger?.info?.(prompt);
9714
+ config.logger?.debug?.(prompt);
9715
+ console.error(`
9716
+ [zoho oauth] Authorize in browser:
9717
+ ${url}
9718
+ `);
9719
+ let opened = false;
9720
+ if (openBrowserEnabled) opened = await openBrowser(url);
9721
+ if (!opened) {
9722
+ const tip = "oauth: could not auto-open browser. Visit the URL above manually.";
9723
+ config.logger?.info?.(tip);
9724
+ config.logger?.debug?.(tip);
9725
+ console.error(`[zoho oauth] ${tip}`);
9726
+ } else {
9727
+ config.logger?.debug?.("oauth: browser open launched");
9728
+ }
9729
+ },
9730
+ saveCodeVerifier(verifier) {
9731
+ const state = readMcpOAuthState(credentialsDir, config.id) ?? {
9732
+ id: config.id,
9733
+ updatedAt: Date.now()
9734
+ };
9735
+ writeMcpOAuthState(credentialsDir, { ...state, codeVerifier: verifier });
9736
+ },
9737
+ codeVerifier() {
9738
+ const state = readMcpOAuthState(credentialsDir, config.id);
9739
+ const verifier = state?.codeVerifier;
9740
+ if (!verifier) throw new Error("No PKCE code verifier saved");
9741
+ return verifier;
9742
+ },
9743
+ invalidateCredentials(scope) {
9744
+ if (scope === "all") {
9745
+ clearMcpOAuthState(credentialsDir, config.id);
9746
+ return;
9747
+ }
9748
+ const state = readMcpOAuthState(credentialsDir, config.id);
9749
+ if (!state) return;
9750
+ const next = { ...state };
9751
+ if (scope === "tokens" || scope === "verifier") next.tokens = void 0;
9752
+ if (scope === "verifier") next.codeVerifier = void 0;
9753
+ if (scope === "discovery") {
9754
+ next.resourceMetadata = void 0;
9755
+ next.authServerMetadata = void 0;
9756
+ }
9757
+ writeMcpOAuthState(credentialsDir, next);
9758
+ },
9759
+ async waitForAuthorizationCode() {
9760
+ if (pendingCode) return pendingCode;
9761
+ if (!callback) {
9762
+ throw new Error(
9763
+ "oauth: no local callback server (custom redirectUrl set). Pass the authorization code via finishAuth yourself."
9764
+ );
9765
+ }
9766
+ config.logger?.info("oauth: waiting for browser authorization\u2026");
9767
+ console.error("[zoho oauth] Waiting for browser authorization\u2026");
9768
+ const code = await callback.waitForCode(lastState);
9769
+ pendingCode = code;
9770
+ return code;
9771
+ },
9772
+ dispose() {
9773
+ callback?.close();
9774
+ callback = void 0;
9775
+ },
9776
+ lastAuthorizationUrl() {
9777
+ return lastAuthUrl;
9778
+ }
9779
+ };
9780
+ return provider;
9781
+ }
9782
+
9783
+ // src/facade/mcp-core/oauth/index.ts
9784
+ import {
9785
+ auth,
9786
+ discoverOAuthServerInfo,
9787
+ startAuthorization,
9788
+ exchangeAuthorization,
9789
+ refreshAuthorization,
9790
+ registerClient,
9791
+ extractWWWAuthenticateParams
9792
+ } from "@modelcontextprotocol/sdk/client/auth.js";
9793
+
9794
+ // src/facade/mcp-core/toolbox-bridge.ts
9795
+ var NOOP_FS_NOT_SUPPORTED = "not supported in MCP-wrapped tools";
9796
+ var noopFs = {
9797
+ readFile: () => {
9798
+ throw new MCPError(NOOP_FS_NOT_SUPPORTED, "CONFIG_ERROR");
9799
+ },
9800
+ writeFile: () => {
9801
+ throw new MCPError(NOOP_FS_NOT_SUPPORTED, "CONFIG_ERROR");
9802
+ },
9803
+ stat: () => {
9804
+ throw new MCPError(NOOP_FS_NOT_SUPPORTED, "CONFIG_ERROR");
9805
+ },
9806
+ readdir: () => {
9807
+ throw new MCPError(NOOP_FS_NOT_SUPPORTED, "CONFIG_ERROR");
9808
+ },
9809
+ mkdir: () => {
9810
+ throw new MCPError(NOOP_FS_NOT_SUPPORTED, "CONFIG_ERROR");
9811
+ },
9812
+ rm: () => {
9813
+ throw new MCPError(NOOP_FS_NOT_SUPPORTED, "CONFIG_ERROR");
9814
+ },
9815
+ exists: () => {
9816
+ throw new MCPError(NOOP_FS_NOT_SUPPORTED, "CONFIG_ERROR");
9817
+ }
9818
+ };
9819
+ var noopShell = {
9820
+ exec: () => {
9821
+ throw new MCPError(NOOP_FS_NOT_SUPPORTED, "CONFIG_ERROR");
9822
+ },
9823
+ spawn: () => {
9824
+ throw new MCPError(NOOP_FS_NOT_SUPPORTED, "CONFIG_ERROR");
9825
+ }
9826
+ };
9827
+ function makeToolContext(signal) {
9828
+ return {
9829
+ cwd: process.cwd(),
9830
+ fs: noopFs,
9831
+ shell: noopShell,
9832
+ signal,
9833
+ budget: { kind: "tail", maxBytes: 5e4 }
9834
+ };
9835
+ }
9836
+ function describeThrow2(err) {
9837
+ if (err instanceof Error) return err.message;
9838
+ if (typeof err === "string") return err;
9839
+ try {
9840
+ return JSON.stringify(err);
9841
+ } catch {
9842
+ return String(err);
9843
+ }
9844
+ }
9845
+ function projectMcpResult(result, logger) {
9846
+ const blocks = [];
9847
+ if (result.content && result.content.length > 0) {
9848
+ for (const block of result.content) {
9849
+ if (block.type === "text") {
9850
+ blocks.push({ kind: "text", text: block.text });
9851
+ } else if (block.type === "image") {
9852
+ const len = typeof block.data === "string" ? block.data.length : 0;
9853
+ blocks.push({ kind: "text", text: `[image ${block.mimeType}, ${len} chars base64]` });
9854
+ logger?.debug(`mcp bridge: image block collapsed to text mention (${block.mimeType})`);
9855
+ } else if (block.type === "resource") {
9856
+ blocks.push({ kind: "text", text: `[resource ${block.resource.uri}]` });
9857
+ } else {
9858
+ blocks.push({ kind: "text", text: "[unknown content block]" });
9859
+ }
9860
+ }
9861
+ }
9862
+ if (result.structuredContent !== void 0) {
9863
+ blocks.push({ kind: "json", value: result.structuredContent });
9864
+ }
9865
+ const content = blocks.length > 0 ? blocks : [{ kind: "text", text: "(no content)" }];
9866
+ return {
9867
+ content,
9868
+ ...result.isError === true ? { isError: true } : {}
9869
+ };
9870
+ }
9871
+ function makeRunner(client, remoteName, logger) {
9872
+ return async (input, ctx) => {
9873
+ if (ctx.signal.aborted) {
9874
+ return {
9875
+ content: [{ kind: "text", text: "Cancelled before tool could start." }],
9876
+ isError: true
9877
+ };
9878
+ }
9879
+ try {
9880
+ const args = input ?? {};
9881
+ const result = await client.callTool(remoteName, args);
9882
+ return projectMcpResult(result, logger);
9883
+ } catch (err) {
9884
+ const wrapped = err instanceof MCPError ? err : createServerError(describeThrow2(err), client.serverName);
9885
+ logger?.error(`mcp bridge: tool "${remoteName}" failed: ${wrapped.message}`, wrapped);
9886
+ return {
9887
+ content: [{ kind: "text", text: `MCP tool failed: ${wrapped.message}` }],
9888
+ isError: true
9889
+ };
9890
+ }
9891
+ };
9892
+ }
9893
+ async function mcpClientToToolBox(client, options = {}) {
9894
+ const prefix = options.namePrefix ?? "mcp";
9895
+ const logger = options.logger;
9896
+ const tools = await client.listTools();
9897
+ const filtered = typeof options.filter === "function" ? tools.filter((t) => options.filter(t)) : tools;
9898
+ const byName = /* @__PURE__ */ new Map();
9899
+ for (const tool of filtered) {
9900
+ const remoteName = tool.name;
9901
+ const prefixed = `${prefix}__${remoteName}`;
9902
+ const description = tool.description && tool.description.length > 0 ? tool.description : `<tool "${remoteName}" from MCP server "${client.serverName}">`;
9903
+ const parameters = tool.inputSchema;
9904
+ const def = defineTool({
9905
+ name: prefixed,
9906
+ description,
9907
+ parameters,
9908
+ run: makeRunner(client, remoteName, logger)
9909
+ });
9910
+ byName.set(prefixed, def);
9911
+ }
9912
+ const runner = {
9913
+ async run(call, signal) {
9914
+ const def = byName.get(call.name);
9915
+ if (!def) {
9916
+ return {
9917
+ id: call.id,
9918
+ output: `No tool "${call.name}" on MCP server "${client.serverName}".`,
9919
+ isError: true
9920
+ };
9921
+ }
9922
+ return def.invoke(call, makeToolContext(signal));
9923
+ }
9924
+ };
9925
+ return {
9926
+ descriptors() {
9927
+ return Array.from(byName.values()).map((d) => d.descriptor());
9928
+ },
9929
+ runner
9930
+ };
9931
+ }
9932
+
9933
+ // src/connectors-zoho/connect.ts
9934
+ async function connectZohoMcp(options) {
9935
+ const id = options.id ?? "zoho";
9936
+ const authMode = options.auth ?? "auto";
9937
+ let parsedUrl;
9938
+ try {
9939
+ parsedUrl = new URL(options.url);
9940
+ } catch {
9941
+ throw new Error(`connectZohoMcp: invalid url "${options.url}"`);
9942
+ }
9943
+ if (parsedUrl.protocol !== "https:" && parsedUrl.protocol !== "http:") {
9944
+ throw new Error(`connectZohoMcp: url must be http(s), got "${parsedUrl.protocol}"`);
9945
+ }
9946
+ let oauthProvider;
9947
+ if (authMode === "auto" || authMode === "oauth") {
9948
+ oauthProvider = await createMcpOAuthProvider({
9949
+ id,
9950
+ credentialsDir: options.tokenStorePath,
9951
+ redirectUrl: options.redirectUrl,
9952
+ // Default true outside CI so TUI sessions always get a browser redirect.
9953
+ openBrowser: options.openBrowser ?? (process.env.CI !== "true" && process.env.ZOHO_OPEN_BROWSER !== "0"),
9954
+ clientMetadata: {
9955
+ client_name: "indusagi-zoho-agent",
9956
+ scope: options.scopes?.join(" ")
9957
+ },
9958
+ logger: options.logger ? {
9959
+ debug: options.logger.debug,
9960
+ info: (msg) => {
9961
+ options.logger?.debug(msg);
9962
+ console.error(`[zoho] ${msg}`);
9963
+ },
9964
+ warn: (msg) => options.logger?.debug(msg),
9965
+ error: options.logger.error
9966
+ } : {
9967
+ debug: (msg) => {
9968
+ if (process.env.INDUSAGI_DEBUG) console.error(`[zoho debug] ${msg}`);
9969
+ },
9970
+ info: (msg) => console.error(`[zoho] ${msg}`),
9971
+ warn: (msg) => console.error(`[zoho] ${msg}`),
9972
+ error: (msg) => console.error(`[zoho error] ${msg}`)
9973
+ }
9974
+ });
9975
+ }
9976
+ const client = new MCPClient({
9977
+ name: id,
9978
+ config: {
9979
+ url: parsedUrl,
9980
+ headers: options.headers ? { ...options.headers } : void 0,
9981
+ ...options.useSse ? { transport: "sse" } : {}
9982
+ },
9983
+ timeout: options.timeoutMs ?? 3e4,
9984
+ ...oauthProvider ? {
9985
+ authProvider: oauthProvider
9986
+ } : {}
9987
+ });
9988
+ try {
9989
+ await client.connect();
9990
+ } catch (e) {
9991
+ oauthProvider?.dispose();
9992
+ const msg = e instanceof Error ? e.message : String(e);
9993
+ if (/authentication required|unauthorized|401/i.test(msg)) {
9994
+ throw new Error(
9995
+ `Zoho MCP authentication failed for "${id}".
9996
+ \u2022 Ensure ZOHO_MCP_URL is the full Connect URL from https://mcp.zoho.com
9997
+ \u2022 Complete the browser OAuth login when prompted
9998
+ \u2022 Or set auth: "none" and pass Authorization headers if you use a pre-authorized URL
9999
+ Original: ${msg}`,
10000
+ { cause: e instanceof Error ? e : void 0 }
10001
+ );
10002
+ }
10003
+ throw e;
10004
+ }
10005
+ try {
10006
+ const toolBox2 = await mcpClientToToolBox(client, {
10007
+ namePrefix: id,
10008
+ logger: options.logger
10009
+ });
10010
+ const tools = await client.listTools();
10011
+ const prefixed = tools.map((t) => `${id}__${t.name}`);
10012
+ try {
10013
+ oauthProvider?.dispose();
10014
+ } catch {
10015
+ }
10016
+ let closed = false;
10017
+ return {
10018
+ id,
10019
+ toolBox: toolBox2,
10020
+ toolNames: prefixed,
10021
+ client,
10022
+ async close() {
10023
+ if (closed) return;
10024
+ closed = true;
10025
+ try {
10026
+ await client.disconnect();
10027
+ } catch (err) {
10028
+ options.logger?.error(`zoho close failed: ${err.message}`, err);
10029
+ } finally {
10030
+ try {
10031
+ oauthProvider?.dispose();
10032
+ } catch {
10033
+ }
10034
+ }
10035
+ }
10036
+ };
10037
+ } catch (e) {
10038
+ oauthProvider?.dispose();
10039
+ try {
10040
+ await client.disconnect();
10041
+ } catch {
10042
+ }
10043
+ throw e;
10044
+ }
10045
+ }
10046
+
10047
+ // src/connectors-zoho/toolbox.ts
10048
+ async function zohoToolBoxFromClient(client, options) {
10049
+ return mcpClientToToolBox(client, {
10050
+ ...options,
10051
+ namePrefix: options?.id ?? "zoho"
10052
+ });
10053
+ }
10054
+
10055
+ // src/connectors-sarvam/index.ts
10056
+ var connectors_sarvam_exports = {};
10057
+ __export(connectors_sarvam_exports, {
10058
+ connectSarvamMcp: () => connectSarvamMcp,
10059
+ sarvamToolBoxFromClient: () => sarvamToolBoxFromClient
10060
+ });
10061
+
10062
+ // src/connectors-sarvam/connect.ts
10063
+ function resolveHome(p) {
10064
+ if (!p.startsWith("~")) return p;
10065
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
10066
+ return home.length > 0 ? p.replace(/^~/, home) : p;
10067
+ }
10068
+ async function connectSarvamMcp(options) {
10069
+ const id = options.id ?? "sarvam";
10070
+ const apiKey = (options.apiKey?.trim() || process.env.SARVAM_API_KEY?.trim() || "").trim();
10071
+ if (!apiKey) {
10072
+ throw new Error(
10073
+ "connectSarvamMcp: SARVAM_API_KEY missing (pass apiKey or set the env var)."
10074
+ );
10075
+ }
10076
+ const baseUrl = options.baseUrl ?? process.env.SARVAM_API_BASE_URL ?? "https://api.sarvam.ai";
10077
+ const basePath = resolveHome(options.basePath ?? process.env.SARVAM_MCP_BASE_PATH ?? "~/Desktop");
10078
+ const command = options.command ?? "uvx";
10079
+ const args = [...options.args ?? ["sarvam-mcp"]];
10080
+ const env = {
10081
+ ...options.env ? { ...options.env } : {},
10082
+ SARVAM_API_KEY: apiKey,
10083
+ SARVAM_API_BASE_URL: baseUrl,
10084
+ SARVAM_MCP_BASE_PATH: basePath
10085
+ };
10086
+ const client = new MCPClient({
10087
+ name: id,
10088
+ config: { command, args, env },
10089
+ timeout: options.timeoutMs ?? 6e4
10090
+ });
10091
+ await client.connect();
10092
+ const toolBox2 = await mcpClientToToolBox(client, {
10093
+ namePrefix: id,
10094
+ logger: options.logger
10095
+ });
10096
+ const tools = await client.listTools();
10097
+ const prefixed = tools.map((t) => `${id}__${t.name}`);
10098
+ let closed = false;
10099
+ return {
10100
+ id,
10101
+ toolBox: toolBox2,
10102
+ toolNames: prefixed,
10103
+ client,
10104
+ async close() {
10105
+ if (closed) return;
10106
+ closed = true;
10107
+ try {
10108
+ await client.disconnect();
10109
+ } catch (e) {
10110
+ options.logger?.error(`sarvam close failed: ${e.message}`, e);
10111
+ }
10112
+ }
10113
+ };
10114
+ }
10115
+
10116
+ // src/connectors-sarvam/toolbox.ts
10117
+ async function sarvamToolBoxFromClient(client, options) {
10118
+ return mcpClientToToolBox(client, {
10119
+ ...options,
10120
+ namePrefix: options?.id ?? "sarvam"
10121
+ });
10122
+ }
10123
+
8598
10124
  // src/swarm/index.ts
8599
10125
  var swarm_exports = {};
8600
10126
  __export(swarm_exports, {
@@ -8624,14 +10150,14 @@ __export(swarm_exports, {
8624
10150
  });
8625
10151
 
8626
10152
  // src/swarm/coordinator.ts
8627
- import { join as join4 } from "node:path";
10153
+ import { join as join5 } from "node:path";
8628
10154
 
8629
10155
  // src/swarm/roster/manifest.ts
8630
10156
  import { z as z2 } from "zod";
8631
10157
 
8632
10158
  // src/swarm/kernel/json-cell.ts
8633
10159
  import { mkdir as mkdir2, readFile as readFile2, rename, rm, stat, writeFile } from "node:fs/promises";
8634
- import { dirname, join as join2 } from "node:path";
10160
+ import { dirname as dirname2, join as join3 } from "node:path";
8635
10161
  import { z } from "zod";
8636
10162
 
8637
10163
  // src/swarm/kernel/faults.ts
@@ -8702,7 +10228,7 @@ var JsonCell = class _JsonCell {
8702
10228
  parsed.error
8703
10229
  );
8704
10230
  }
8705
- await mkdir2(dirname(path2), { recursive: true });
10231
+ await mkdir2(dirname2(path2), { recursive: true });
8706
10232
  const opts = { ...GUARD_DEFAULTS, ...guard };
8707
10233
  return new _JsonCell(path2, schema, parsed.data, opts);
8708
10234
  }
@@ -8813,7 +10339,7 @@ var JsonCell = class _JsonCell {
8813
10339
  try {
8814
10340
  await mkdir2(this.lockDir);
8815
10341
  await writeFile(
8816
- join2(this.lockDir, "owner.json"),
10342
+ join3(this.lockDir, "owner.json"),
8817
10343
  JSON.stringify({
8818
10344
  pid: process.pid,
8819
10345
  takenAt: Date.now(),
@@ -8851,7 +10377,7 @@ var JsonCell = class _JsonCell {
8851
10377
  const now = Date.now();
8852
10378
  try {
8853
10379
  const marker = lockMarkerSchema.safeParse(
8854
- JSON.parse(await readFile2(join2(this.lockDir, "owner.json"), "utf8"))
10380
+ JSON.parse(await readFile2(join3(this.lockDir, "owner.json"), "utf8"))
8855
10381
  );
8856
10382
  if (marker.success) return now - marker.data.takenAt > staleMs;
8857
10383
  } catch {
@@ -8877,7 +10403,7 @@ var JsonCell = class _JsonCell {
8877
10403
 
8878
10404
  // src/swarm/kernel/jsonl-log.ts
8879
10405
  import { appendFile as appendFile2, mkdir as mkdir3, readFile as readFile3 } from "node:fs/promises";
8880
- import { dirname as dirname2 } from "node:path";
10406
+ import { dirname as dirname3 } from "node:path";
8881
10407
  function hasCode2(err, code) {
8882
10408
  return typeof err === "object" && err !== null && "code" in err && err.code === code;
8883
10409
  }
@@ -8891,7 +10417,7 @@ var JsonlLog = class _JsonlLog {
8891
10417
  }
8892
10418
  /** Open (or adopt) the log at `path`, ensuring its parent directory exists. */
8893
10419
  static async open(path2, schema) {
8894
- await mkdir3(dirname2(path2), { recursive: true });
10420
+ await mkdir3(dirname3(path2), { recursive: true });
8895
10421
  return new _JsonlLog(path2, schema);
8896
10422
  }
8897
10423
  /**
@@ -9536,7 +11062,7 @@ function isTicket(value) {
9536
11062
  }
9537
11063
 
9538
11064
  // src/swarm/postbox/channel.ts
9539
- import { join as join3 } from "node:path";
11065
+ import { join as join4 } from "node:path";
9540
11066
  import { z as z5 } from "zod";
9541
11067
 
9542
11068
  // src/swarm/postbox/codecs.ts
@@ -9650,10 +11176,10 @@ var cursorSchema = z5.object({
9650
11176
  seen: z5.number().int().nonnegative()
9651
11177
  });
9652
11178
  function transcriptPath(root) {
9653
- return join3(root, "transcript.jsonl");
11179
+ return join4(root, "transcript.jsonl");
9654
11180
  }
9655
11181
  function cursorPath(root, readerId) {
9656
- return join3(root, "cursors", `${encodeReaderId(readerId)}.json`);
11182
+ return join4(root, "cursors", `${encodeReaderId(readerId)}.json`);
9657
11183
  }
9658
11184
  function encodeReaderId(readerId) {
9659
11185
  return encodeURIComponent(readerId);
@@ -10083,14 +11609,14 @@ var Crew = class _Crew {
10083
11609
  */
10084
11610
  static async open(opts) {
10085
11611
  const now = opts.now ?? Date.now;
10086
- const manifest = await CrewManifest.open(join4(opts.dir, "manifest.json"));
11612
+ const manifest = await CrewManifest.open(join5(opts.dir, "manifest.json"));
10087
11613
  await manifest.create().catch((err) => {
10088
11614
  if (SwarmFault.is(err, "conflict")) return;
10089
11615
  throw err;
10090
11616
  });
10091
- const board = await TicketBoard.open(join4(opts.dir, "board.json"), now);
10092
- const channel = await Channel.open(join4(opts.dir, "channel"));
10093
- const activity = await ActivityLog.open(join4(opts.dir, "activity.jsonl"), now);
11617
+ const board = await TicketBoard.open(join5(opts.dir, "board.json"), now);
11618
+ const channel = await Channel.open(join5(opts.dir, "channel"));
11619
+ const activity = await ActivityLog.open(join5(opts.dir, "activity.jsonl"), now);
10094
11620
  const workspace = opts.baseRepo !== void 0 ? new Workspace() : null;
10095
11621
  const spawnAgent = opts.spawnAgent ?? defaultSpawnAgent;
10096
11622
  return new _Crew(
@@ -10659,7 +12185,7 @@ function toAgentConfig(blueprint, cwd) {
10659
12185
 
10660
12186
  // src/smithy/knowledge/loader.ts
10661
12187
  import { readFile as readFile4 } from "node:fs/promises";
10662
- import { dirname as dirname3, isAbsolute, join as join5, resolve } from "node:path";
12188
+ import { dirname as dirname4, isAbsolute, join as join6, resolve } from "node:path";
10663
12189
  import { fileURLToPath } from "node:url";
10664
12190
  var KnowledgeError = class extends Error {
10665
12191
  constructor(message) {
@@ -10668,21 +12194,21 @@ var KnowledgeError = class extends Error {
10668
12194
  }
10669
12195
  };
10670
12196
  function defaultDir() {
10671
- return dirname3(fileURLToPath(import.meta.url));
12197
+ return dirname4(fileURLToPath(import.meta.url));
10672
12198
  }
10673
12199
  async function loadKnowledge(dir) {
10674
12200
  const root = dir ? resolve(dir) : defaultDir();
10675
12201
  const manifest = await readManifest(root);
10676
12202
  const guides = [];
10677
12203
  for (const entry of manifest.guides) {
10678
- const path2 = isAbsolute(entry.file) ? entry.file : join5(root, entry.file);
12204
+ const path2 = isAbsolute(entry.file) ? entry.file : join6(root, entry.file);
10679
12205
  const body = await readGuideBody(path2, entry.id);
10680
12206
  guides.push({ ...entry, body });
10681
12207
  }
10682
12208
  return freezePack(manifest.version, guides);
10683
12209
  }
10684
12210
  async function readManifest(root) {
10685
- const path2 = join5(root, "manifest.json");
12211
+ const path2 = join6(root, "manifest.json");
10686
12212
  let raw;
10687
12213
  try {
10688
12214
  raw = await readFile4(path2, "utf8");
@@ -11487,6 +13013,43 @@ var DEFAULT_SECRET_PATTERNS = Object.freeze([
11487
13013
  label: "private-key-block-value",
11488
13014
  // a PEM-armoured private key body
11489
13015
  value: /-----BEGIN(?:[A-Z ]+)?PRIVATE KEY-----/
13016
+ },
13017
+ // -----------------------------------------------------------------------
13018
+ // MCP / Zoho MCP additions.
13019
+ // -----------------------------------------------------------------------
13020
+ {
13021
+ label: "mcp-url-key-name",
13022
+ // mcp_url, mcpUrl, mcp_url_secret, MCP_URL, ZOHO_MCP_URL, ZOHO_MCP_TOKEN, etc.
13023
+ key: /(?:mcp[_-]?url|zoho[_-]?mcp|mcp[_-]?token)/i
13024
+ },
13025
+ {
13026
+ label: "pkce-verifier-key-name",
13027
+ // code_verifier, codeVerifier, pkce_verifier, etc.
13028
+ key: /(?:code[_-]?verifier|pkce[_-]?verifier)/i
13029
+ },
13030
+ {
13031
+ label: "mcp-server-url-value",
13032
+ // Zoho MCP URLs — the host is enough to make the URL sensitive.
13033
+ value: /\bhttps?:\/\/(?:[A-Za-z0-9-]+\.)*mcp\.zoho\.(?:com|in|eu|net|com\.au|jp)\b[^\s"'<>]*/i
13034
+ },
13035
+ // -----------------------------------------------------------------------
13036
+ // Sarvam MCP additions.
13037
+ // -----------------------------------------------------------------------
13038
+ {
13039
+ label: "sarvam-api-key-key-name",
13040
+ // SARVAM_API_KEY, sarvamApiKey, sarvam_api_key
13041
+ key: /sarvam[_-]?api[_-]?key/i
13042
+ },
13043
+ {
13044
+ label: "sarvam-api-key-value",
13045
+ // dashboard-issued key shape if Sarvam ever uses a prefix; fallback so
13046
+ // SARVAM_API_KEY=<anything> gets redacted even if no key pattern matched.
13047
+ value: /SARVAM_API_KEY\s*=\s*[^\s"'<>]+/i
13048
+ },
13049
+ {
13050
+ label: "api-subscription-key-header",
13051
+ // Sarvam's REST auth header name.
13052
+ value: /\bapi-subscription-key\s*[:=]\s*[A-Za-z0-9._~+/-]{8,}=*\b/i
11490
13053
  }
11491
13054
  ]);
11492
13055
  function isContainer(value) {
@@ -11986,8 +13549,8 @@ __export(shell_app_exports, {
11986
13549
 
11987
13550
  // src/shell-app/cli.ts
11988
13551
  import { fileURLToPath as fileURLToPath3 } from "node:url";
11989
- import { readFileSync, realpathSync } from "node:fs";
11990
- import { dirname as dirname6, join as join8 } from "node:path";
13552
+ import { readFileSync as readFileSync2, realpathSync } from "node:fs";
13553
+ import { dirname as dirname7, join as join9 } from "node:path";
11991
13554
 
11992
13555
  // src/shell-app/invocation/flags.ts
11993
13556
  var FLAG_SPECS = [
@@ -12225,7 +13788,7 @@ async function runStages(initial2, stages) {
12225
13788
 
12226
13789
  // src/shell-app/config/locator.ts
12227
13790
  import { homedir } from "node:os";
12228
- import { join as join6 } from "node:path";
13791
+ import { join as join7 } from "node:path";
12229
13792
  var BRAND = {
12230
13793
  name: "indusagi",
12231
13794
  configDir: ".indusagi",
@@ -12240,7 +13803,7 @@ var NodeLocator = class {
12240
13803
  }
12241
13804
  /** Join segments under the profile directory. */
12242
13805
  under(...segments) {
12243
- return join6(this.home, this.brand.configDir, ...segments);
13806
+ return join7(this.home, this.brand.configDir, ...segments);
12244
13807
  }
12245
13808
  profileDir() {
12246
13809
  return this.under();
@@ -12249,7 +13812,7 @@ var NodeLocator = class {
12249
13812
  return this.under("settings.json");
12250
13813
  }
12251
13814
  projectSettingsPath(cwd) {
12252
- return join6(cwd, this.brand.configDir, "settings.json");
13815
+ return join7(cwd, this.brand.configDir, "settings.json");
12253
13816
  }
12254
13817
  authPath() {
12255
13818
  return this.under("auth.json");
@@ -12419,7 +13982,7 @@ function resolveModelId(settings, invocationModel) {
12419
13982
 
12420
13983
  // src/shell-app/upgrade/upgrades.ts
12421
13984
  import { mkdir as mkdir4, readFile as readFile6, writeFile as writeFile2 } from "node:fs/promises";
12422
- import { dirname as dirname4 } from "node:path";
13985
+ import { dirname as dirname5 } from "node:path";
12423
13986
  var UPGRADES = [
12424
13987
  {
12425
13988
  id: "ensure-profile-dir",
@@ -12455,7 +14018,7 @@ async function readApplied(path2) {
12455
14018
  return new Set(parsed.filter((entry) => typeof entry === "string"));
12456
14019
  }
12457
14020
  async function writeApplied(path2, applied) {
12458
- await mkdir4(dirname4(path2), { recursive: true });
14021
+ await mkdir4(dirname5(path2), { recursive: true });
12459
14022
  const ids = [...applied].sort();
12460
14023
  await writeFile2(path2, `${JSON.stringify(ids, null, 2)}
12461
14024
  `, "utf8");
@@ -15943,7 +17506,7 @@ function envName(suffix, brand = BRAND2) {
15943
17506
 
15944
17507
  // src/shell-app/locate/locator.ts
15945
17508
  import { mkdir as mkdir5 } from "node:fs/promises";
15946
- import { dirname as dirname5, isAbsolute as isAbsolute2, join as join7, resolve as resolve2 } from "node:path";
17509
+ import { dirname as dirname6, isAbsolute as isAbsolute2, join as join8, resolve as resolve2 } from "node:path";
15947
17510
  import { homedir as homedir3 } from "node:os";
15948
17511
  var Locator = class {
15949
17512
  home;
@@ -15993,12 +17556,12 @@ var Locator = class {
15993
17556
  * is computed relative to this.
15994
17557
  */
15995
17558
  profileDir() {
15996
- return join7(this.home, this.brand.profileDirName);
17559
+ return join8(this.home, this.brand.profileDirName);
15997
17560
  }
15998
17561
  // --- Settings ----------------------------------------------------------
15999
17562
  /** The global settings file inside the profile (e.g. `~/.indusagi/settings.json`). */
16000
17563
  settingsPath() {
16001
- return join7(this.profileDir(), this.brand.settingsFileName);
17564
+ return join8(this.profileDir(), this.brand.settingsFileName);
16002
17565
  }
16003
17566
  /**
16004
17567
  * The per-project settings file for a given working directory.
@@ -16012,20 +17575,20 @@ var Locator = class {
16012
17575
  */
16013
17576
  projectSettingsPath(cwd = this.cwd) {
16014
17577
  const root = isAbsolute2(cwd) ? cwd : resolve2(this.cwd, cwd);
16015
- return join7(root, this.brand.projectDirName, this.brand.projectSettingsFileName);
17578
+ return join8(root, this.brand.projectDirName, this.brand.projectSettingsFileName);
16016
17579
  }
16017
17580
  // --- Stores & directories ---------------------------------------------
16018
17581
  /** Directory holding persisted conversation sessions (e.g. `~/.indusagi/sessions`). */
16019
17582
  sessionsDir() {
16020
- return join7(this.profileDir(), this.brand.sessionsDirName);
17583
+ return join8(this.profileDir(), this.brand.sessionsDirName);
16021
17584
  }
16022
17585
  /** The credential/auth store file inside the profile (e.g. `~/.indusagi/auth.json`). */
16023
17586
  authStorePath() {
16024
- return join7(this.profileDir(), this.brand.authStoreFileName);
17587
+ return join8(this.profileDir(), this.brand.authStoreFileName);
16025
17588
  }
16026
17589
  /** Directory holding diagnostic and crash logs (e.g. `~/.indusagi/logs`). */
16027
17590
  logsDir() {
16028
- return join7(this.profileDir(), this.brand.logsDirName);
17591
+ return join8(this.profileDir(), this.brand.logsDirName);
16029
17592
  }
16030
17593
  // --- I/O helpers (the only members that touch disk) -------------------
16031
17594
  /**
@@ -16055,7 +17618,7 @@ var Locator = class {
16055
17618
  * never fails on a missing directory.
16056
17619
  */
16057
17620
  async ensureParentOf(filePath) {
16058
- await this.ensureDir(dirname5(filePath));
17621
+ await this.ensureDir(dirname6(filePath));
16059
17622
  return filePath;
16060
17623
  }
16061
17624
  /**
@@ -16132,11 +17695,11 @@ function renderUsage() {
16132
17695
  function resolveVersion() {
16133
17696
  const FALLBACK = "0.0.0";
16134
17697
  try {
16135
- const here = dirname6(fileURLToPath3(import.meta.url));
17698
+ const here = dirname7(fileURLToPath3(import.meta.url));
16136
17699
  for (const up of ["..", "../..", "../../.."]) {
16137
- const candidate = join8(here, up, "package.json");
17700
+ const candidate = join9(here, up, "package.json");
16138
17701
  try {
16139
- const raw = readFileSync(candidate, "utf8");
17702
+ const raw = readFileSync2(candidate, "utf8");
16140
17703
  const parsed = JSON.parse(raw);
16141
17704
  if (typeof parsed.version === "string" && parsed.version.length > 0) {
16142
17705
  return parsed.version;
@@ -16570,6 +18133,8 @@ var KNOWN_PROVIDERS = [
16570
18133
  "azure",
16571
18134
  "nvidia",
16572
18135
  "kimi",
18136
+ "minimax",
18137
+ "minimax-cn",
16573
18138
  "ollama",
16574
18139
  "mock"
16575
18140
  ];
@@ -16637,8 +18202,10 @@ export {
16637
18202
  interop_exports as interop,
16638
18203
  runtime_exports as runtime,
16639
18204
  connectors_saas_exports as saas,
18205
+ connectors_sarvam_exports as sarvamMcp,
16640
18206
  shell_app_exports as shell,
16641
18207
  smithy_exports as smithy,
16642
18208
  swarm_exports as swarm,
16643
- tracing_exports as tracing
18209
+ tracing_exports as tracing,
18210
+ connectors_zoho_exports as zoho
16644
18211
  };