@poncho-ai/harness 0.34.0 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -397,10 +397,11 @@ var estimateTotalTokens = (systemPrompt, messages, toolDefinitionsJson) => {
397
397
  return sum + 200;
398
398
  }, 0);
399
399
  }
400
+ let tokens = Math.ceil(chars / 4 * OVERHEAD_MULTIPLIER);
400
401
  if (toolDefinitionsJson) {
401
- chars += toolDefinitionsJson.length;
402
+ tokens += Math.ceil(toolDefinitionsJson.length / 6);
402
403
  }
403
- return Math.ceil(chars / 4 * OVERHEAD_MULTIPLIER);
404
+ return tokens;
404
405
  };
405
406
  var findSafeSplitPoint = (messages, keepRecentMessages) => {
406
407
  const candidateIdx = messages.length - keepRecentMessages;
@@ -1449,6 +1450,112 @@ Available memory tools:
1449
1450
  - \`memory_main_write\` \u2014 overwrite the entire memory document
1450
1451
  - \`memory_main_edit\` \u2014 edit memory via exact string replacement (\`old_str\` / \`new_str\`)
1451
1452
  - \`conversation_recall\` \u2014 search past conversations
1453
+
1454
+ ## Multi-Tenancy
1455
+
1456
+ Deploy one agent and let many users (tenants) use it, each with fully isolated data. Multi-tenancy is opt-in \u2014 it activates automatically when a valid tenant JWT is received. Existing single-user deployments work unchanged.
1457
+
1458
+ ### How it works
1459
+
1460
+ 1. **Builder creates a JWT** for each tenant, signed with \`PONCHO_AUTH_TOKEN\` (HS256).
1461
+ 2. **Tenant accesses the agent** using \`?token=<jwt>\` in the web UI URL, or \`Authorization: Bearer <jwt>\` for API calls.
1462
+ 3. **All data is scoped** \u2014 conversations, memory, reminders, and todos are isolated per tenant.
1463
+
1464
+ ### Creating tenant tokens
1465
+
1466
+ Using the CLI (for development/testing):
1467
+
1468
+ \`\`\`bash
1469
+ poncho auth create-token --tenant acme-corp --ttl 24h
1470
+ \`\`\`
1471
+
1472
+ Using the client SDK (for your backend):
1473
+
1474
+ \`\`\`typescript
1475
+ import { createTenantToken } from "@poncho-ai/client";
1476
+
1477
+ const token = await createTenantToken({
1478
+ signingKey: process.env.PONCHO_AUTH_TOKEN,
1479
+ tenantId: "acme-corp",
1480
+ expiresIn: "1h",
1481
+ metadata: { plan: "pro" }, // optional, stored in JWT \`meta\` claim
1482
+ });
1483
+
1484
+ // Give this URL to the tenant:
1485
+ const url = \`https://my-agent.example.com/?token=\${token}\`;
1486
+ \`\`\`
1487
+
1488
+ Or use any HS256 JWT library in any language \u2014 set \`sub\` to the tenant ID.
1489
+
1490
+ ### Tenant-scoped API access
1491
+
1492
+ \`\`\`typescript
1493
+ import { AgentClient, createTenantToken } from "@poncho-ai/client";
1494
+
1495
+ const token = await createTenantToken({
1496
+ signingKey: process.env.PONCHO_AUTH_TOKEN,
1497
+ tenantId: "acme-corp",
1498
+ expiresIn: "1h",
1499
+ });
1500
+
1501
+ const client = new AgentClient({
1502
+ url: "https://my-agent.example.com",
1503
+ token, // tenant-scoped access
1504
+ });
1505
+
1506
+ const conversations = await client.listConversations(); // only acme-corp's
1507
+ \`\`\`
1508
+
1509
+ ### Per-tenant secrets
1510
+
1511
+ Tenants can provide their own API keys for MCP integrations. Declare which env vars are tenant-managed in \`poncho.config.js\`:
1512
+
1513
+ \`\`\`javascript
1514
+ export default {
1515
+ tenantSecrets: {
1516
+ LINEAR_API_KEY: "Linear API Key",
1517
+ GITHUB_TOKEN: "GitHub Token",
1518
+ },
1519
+ mcp: [
1520
+ { url: "https://mcp.linear.app/sse", auth: { type: "bearer", tokenEnv: "LINEAR_API_KEY" } },
1521
+ ],
1522
+ };
1523
+ \`\`\`
1524
+
1525
+ When a tenant sets their \`LINEAR_API_KEY\` through the web UI settings panel (or API), MCP tool calls for that tenant use their key. If the tenant hasn't set it, the agent falls back to \`process.env.LINEAR_API_KEY\`.
1526
+
1527
+ Builders can also set secrets for tenants via CLI:
1528
+
1529
+ \`\`\`bash
1530
+ poncho secrets set --tenant acme-corp LINEAR_API_KEY lk_acme_123
1531
+ poncho secrets list --tenant acme-corp
1532
+ poncho secrets delete --tenant acme-corp LINEAR_API_KEY
1533
+ \`\`\`
1534
+
1535
+ Secrets are encrypted at rest (AES-256-GCM) using a key derived from \`PONCHO_AUTH_TOKEN\`.
1536
+
1537
+ ### Auth model
1538
+
1539
+ | Access type | Token | Scope |
1540
+ |---|---|---|
1541
+ | **Builder** | \`Authorization: Bearer <PONCHO_AUTH_TOKEN>\` | Full admin access |
1542
+ | **Tenant** | \`Authorization: Bearer <JWT>\` or \`?token=<JWT>\` | Scoped to one tenant |
1543
+ | **Anonymous** | No token (when \`auth.required\` is false) | Legacy single-user mode |
1544
+
1545
+ ### What's isolated per tenant
1546
+
1547
+ - Conversations and message history
1548
+ - Persistent memory
1549
+ - Reminders
1550
+ - Todos
1551
+ - Secrets (MCP auth tokens)
1552
+ - Subagent conversations
1553
+
1554
+ ### Limitations
1555
+
1556
+ - **No tenant registry**: builders can't enumerate all tenants. Cron jobs and reminders run in agent scope, not per-tenant.
1557
+ - **No token revocation**: use short TTLs (1h recommended). Stateless JWTs can't be individually revoked.
1558
+ - **\`PONCHO_AUTH_TOKEN\` is immutable**: rotating it invalidates all tenant JWTs and encrypted secrets.
1452
1559
  `,
1453
1560
  "configuration": `# Configuration & Security
1454
1561
 
@@ -1531,6 +1638,14 @@ export default {
1531
1638
  // When auth.required is true:
1532
1639
  // - Web UI: users enter the passphrase (value of PONCHO_AUTH_TOKEN env var)
1533
1640
  // - API: clients include Authorization: Bearer <token> header
1641
+ // - Tenants: use JWT tokens (see Multi-Tenancy in docs/features.md)
1642
+
1643
+ // Per-tenant secrets (optional). Tenants can set their own values for these
1644
+ // env vars via the web UI settings panel or API. Used for MCP auth tokens.
1645
+ // tenantSecrets: {
1646
+ // LINEAR_API_KEY: 'Linear API Key',
1647
+ // GITHUB_TOKEN: 'GitHub Token',
1648
+ // },
1534
1649
 
1535
1650
  // Model provider API key env var overrides (optional)
1536
1651
  providers: {
@@ -2101,8 +2216,8 @@ var ponchoDocsTool = defineTool({
2101
2216
 
2102
2217
  // src/harness.ts
2103
2218
  import { randomUUID as randomUUID3 } from "crypto";
2104
- import { readFile as readFile10 } from "fs/promises";
2105
- import { resolve as resolve12 } from "path";
2219
+ import { readFile as readFile11 } from "fs/promises";
2220
+ import { resolve as resolve13 } from "path";
2106
2221
  import { defineTool as defineTool8, getTextContent as getTextContent2 } from "@poncho-ai/sdk";
2107
2222
 
2108
2223
  // src/upload-store.ts
@@ -2615,20 +2730,25 @@ var InMemoryMemoryStore = class {
2615
2730
  var FileMainMemoryStore = class {
2616
2731
  workingDir;
2617
2732
  filePath = "";
2733
+ customRelPath;
2618
2734
  ttlMs;
2619
2735
  loaded = false;
2620
2736
  writing = Promise.resolve();
2621
2737
  mainMemory = { ...DEFAULT_MAIN_MEMORY };
2622
- constructor(workingDir, ttlSeconds) {
2738
+ constructor(workingDir, ttlSeconds, customRelPath) {
2623
2739
  this.workingDir = workingDir;
2624
2740
  this.ttlMs = typeof ttlSeconds === "number" ? ttlSeconds * 1e3 : void 0;
2741
+ this.customRelPath = customRelPath;
2625
2742
  }
2626
2743
  async ensureFilePath() {
2627
2744
  if (this.filePath) {
2628
2745
  return;
2629
2746
  }
2630
2747
  const identity = await ensureAgentIdentity(this.workingDir);
2631
- this.filePath = resolve6(getAgentStoreDirectory(identity), LOCAL_MEMORY_FILE);
2748
+ this.filePath = resolve6(
2749
+ getAgentStoreDirectory(identity),
2750
+ this.customRelPath ?? LOCAL_MEMORY_FILE
2751
+ );
2632
2752
  }
2633
2753
  isExpired(updatedAt) {
2634
2754
  return typeof this.ttlMs === "number" && Date.now() - updatedAt > this.ttlMs;
@@ -2724,7 +2844,15 @@ var createMemoryStore = (agentId, config, options) => {
2724
2844
  const provider = config?.provider ?? "local";
2725
2845
  const ttl = config?.ttl;
2726
2846
  const workingDir = options?.workingDir ?? process.cwd();
2847
+ const tenantId = options?.tenantId;
2727
2848
  if (provider === "local") {
2849
+ if (tenantId) {
2850
+ return new FileMainMemoryStore(
2851
+ workingDir,
2852
+ ttl,
2853
+ `tenants/${slugifyStorageComponent(tenantId)}/${LOCAL_MEMORY_FILE}`
2854
+ );
2855
+ }
2728
2856
  return new FileMainMemoryStore(workingDir, ttl);
2729
2857
  }
2730
2858
  if (provider === "memory") {
@@ -2732,7 +2860,8 @@ var createMemoryStore = (agentId, config, options) => {
2732
2860
  }
2733
2861
  const kv = createRawKVStore(config);
2734
2862
  if (kv) {
2735
- const storageKey = `poncho:${STORAGE_SCHEMA_VERSION}:${slugifyStorageComponent(agentId)}:memory:main`;
2863
+ const base = `poncho:${STORAGE_SCHEMA_VERSION}:${slugifyStorageComponent(agentId)}`;
2864
+ const storageKey = tenantId ? `${base}:t:${slugifyStorageComponent(tenantId)}:memory:main` : `${base}:memory:main`;
2736
2865
  return new KVBackedMemoryStore(kv, storageKey, ttl);
2737
2866
  }
2738
2867
  return new InMemoryMemoryStore(ttl);
@@ -2767,6 +2896,7 @@ var buildRecallSnippet = (content, query, maxChars = 360) => {
2767
2896
  return content.slice(start, end);
2768
2897
  };
2769
2898
  var createMemoryTools = (store, options) => {
2899
+ const resolveStore = typeof store === "function" ? store : () => store;
2770
2900
  const maxRecallConversations = Math.max(1, options?.maxRecallConversations ?? 20);
2771
2901
  return [
2772
2902
  defineTool2({
@@ -2777,8 +2907,8 @@ var createMemoryTools = (store, options) => {
2777
2907
  properties: {},
2778
2908
  additionalProperties: false
2779
2909
  },
2780
- handler: async () => {
2781
- const memory = await store.getMainMemory();
2910
+ handler: async (_input, context) => {
2911
+ const memory = await resolveStore(context).getMainMemory();
2782
2912
  return { memory };
2783
2913
  }
2784
2914
  }),
@@ -2796,12 +2926,12 @@ var createMemoryTools = (store, options) => {
2796
2926
  required: ["content"],
2797
2927
  additionalProperties: false
2798
2928
  },
2799
- handler: async (input) => {
2929
+ handler: async (input, context) => {
2800
2930
  const content = typeof input.content === "string" ? input.content.trim() : "";
2801
2931
  if (!content) {
2802
2932
  throw new Error("content is required");
2803
2933
  }
2804
- const memory = await store.updateMainMemory({ content });
2934
+ const memory = await resolveStore(context).updateMainMemory({ content });
2805
2935
  return { ok: true, memory };
2806
2936
  }
2807
2937
  }),
@@ -2823,13 +2953,13 @@ var createMemoryTools = (store, options) => {
2823
2953
  required: ["old_str", "new_str"],
2824
2954
  additionalProperties: false
2825
2955
  },
2826
- handler: async (input) => {
2956
+ handler: async (input, context) => {
2827
2957
  const oldStr = typeof input.old_str === "string" ? input.old_str : "";
2828
2958
  const newStr = typeof input.new_str === "string" ? input.new_str : "";
2829
2959
  if (!oldStr) {
2830
2960
  throw new Error("old_str must not be empty.");
2831
2961
  }
2832
- const current = await store.getMainMemory();
2962
+ const current = await resolveStore(context).getMainMemory();
2833
2963
  const content = current.content;
2834
2964
  const first = content.indexOf(oldStr);
2835
2965
  if (first === -1) {
@@ -2844,7 +2974,7 @@ var createMemoryTools = (store, options) => {
2844
2974
  );
2845
2975
  }
2846
2976
  const newContent = content.slice(0, first) + newStr + content.slice(first + oldStr.length);
2847
- const memory = await store.updateMainMemory({ content: newContent });
2977
+ const memory = await resolveStore(context).updateMainMemory({ content: newContent });
2848
2978
  return { ok: true, memory };
2849
2979
  }
2850
2980
  }),
@@ -3207,7 +3337,8 @@ var InMemoryReminderStore = class {
3207
3337
  status: "pending",
3208
3338
  createdAt: Date.now(),
3209
3339
  conversationId: input.conversationId,
3210
- ownerId: input.ownerId
3340
+ ownerId: input.ownerId,
3341
+ tenantId: input.tenantId
3211
3342
  };
3212
3343
  this.reminders = pruneStale(this.reminders);
3213
3344
  this.reminders.push(reminder);
@@ -3266,7 +3397,8 @@ var FileReminderStore = class {
3266
3397
  status: "pending",
3267
3398
  createdAt: Date.now(),
3268
3399
  conversationId: input.conversationId,
3269
- ownerId: input.ownerId
3400
+ ownerId: input.ownerId,
3401
+ tenantId: input.tenantId
3270
3402
  };
3271
3403
  let reminders = await this.readAll();
3272
3404
  reminders = pruneStale(reminders);
@@ -3392,6 +3524,171 @@ var createReminderStore = (agentId, config, options) => {
3392
3524
  return new InMemoryReminderStore();
3393
3525
  };
3394
3526
 
3527
+ // src/secrets-store.ts
3528
+ import { createCipheriv, createDecipheriv, randomBytes, createHash as createHash3 } from "crypto";
3529
+ import { mkdir as mkdir6, readFile as readFile8, writeFile as writeFile7 } from "fs/promises";
3530
+ import { dirname as dirname5, resolve as resolve9 } from "path";
3531
+ function deriveKey(signingKey) {
3532
+ return createHash3("sha256").update("poncho-secrets-v1:" + signingKey).digest();
3533
+ }
3534
+ function encrypt(plaintext, key) {
3535
+ const iv = randomBytes(12);
3536
+ const cipher = createCipheriv("aes-256-gcm", key, iv);
3537
+ const ct = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
3538
+ const tag = cipher.getAuthTag();
3539
+ return {
3540
+ iv: iv.toString("base64"),
3541
+ ct: ct.toString("base64"),
3542
+ tag: tag.toString("base64")
3543
+ };
3544
+ }
3545
+ function decrypt(blob, key) {
3546
+ const decipher = createDecipheriv(
3547
+ "aes-256-gcm",
3548
+ key,
3549
+ Buffer.from(blob.iv, "base64")
3550
+ );
3551
+ decipher.setAuthTag(Buffer.from(blob.tag, "base64"));
3552
+ return Buffer.concat([
3553
+ decipher.update(Buffer.from(blob.ct, "base64")),
3554
+ decipher.final()
3555
+ ]).toString("utf8");
3556
+ }
3557
+ var FileSecretsStore = class {
3558
+ workingDir;
3559
+ encKey;
3560
+ constructor(workingDir, signingKey) {
3561
+ this.workingDir = workingDir;
3562
+ this.encKey = deriveKey(signingKey);
3563
+ }
3564
+ async filePath(tenantId) {
3565
+ const identity = await ensureAgentIdentity(this.workingDir);
3566
+ const dir = resolve9(
3567
+ getAgentStoreDirectory(identity),
3568
+ "tenants",
3569
+ slugifyStorageComponent(tenantId)
3570
+ );
3571
+ return resolve9(dir, "secrets.json");
3572
+ }
3573
+ async readAll(tenantId) {
3574
+ try {
3575
+ const raw = await readFile8(await this.filePath(tenantId), "utf8");
3576
+ return JSON.parse(raw);
3577
+ } catch {
3578
+ return {};
3579
+ }
3580
+ }
3581
+ async writeAll(tenantId, data) {
3582
+ const fp = await this.filePath(tenantId);
3583
+ await mkdir6(dirname5(fp), { recursive: true });
3584
+ await writeFile7(fp, JSON.stringify(data, null, 2), "utf8");
3585
+ }
3586
+ async get(tenantId) {
3587
+ const data = await this.readAll(tenantId);
3588
+ const result = {};
3589
+ for (const [k, blob] of Object.entries(data)) {
3590
+ try {
3591
+ result[k] = decrypt(blob, this.encKey);
3592
+ } catch {
3593
+ }
3594
+ }
3595
+ return result;
3596
+ }
3597
+ async set(tenantId, key, value) {
3598
+ const data = await this.readAll(tenantId);
3599
+ data[key] = encrypt(value, this.encKey);
3600
+ await this.writeAll(tenantId, data);
3601
+ }
3602
+ async delete(tenantId, key) {
3603
+ const data = await this.readAll(tenantId);
3604
+ delete data[key];
3605
+ await this.writeAll(tenantId, data);
3606
+ }
3607
+ async list(tenantId) {
3608
+ const data = await this.readAll(tenantId);
3609
+ return Object.keys(data);
3610
+ }
3611
+ };
3612
+ var KVSecretsStore = class {
3613
+ kv;
3614
+ baseKey;
3615
+ encKey;
3616
+ ttl;
3617
+ constructor(kv, baseKey, signingKey, ttl) {
3618
+ this.kv = kv;
3619
+ this.baseKey = baseKey;
3620
+ this.encKey = deriveKey(signingKey);
3621
+ this.ttl = ttl;
3622
+ }
3623
+ kvKey(tenantId) {
3624
+ return `${this.baseKey}:t:${slugifyStorageComponent(tenantId)}:secrets`;
3625
+ }
3626
+ async readAll(tenantId) {
3627
+ try {
3628
+ const raw = await this.kv.get(this.kvKey(tenantId));
3629
+ if (!raw) return {};
3630
+ return JSON.parse(raw);
3631
+ } catch {
3632
+ return {};
3633
+ }
3634
+ }
3635
+ async writeAll(tenantId, data) {
3636
+ const key = this.kvKey(tenantId);
3637
+ const value = JSON.stringify(data);
3638
+ if (this.ttl) {
3639
+ await this.kv.setWithTtl(key, value, this.ttl);
3640
+ } else {
3641
+ await this.kv.set(key, value);
3642
+ }
3643
+ }
3644
+ async get(tenantId) {
3645
+ const data = await this.readAll(tenantId);
3646
+ const result = {};
3647
+ for (const [k, blob] of Object.entries(data)) {
3648
+ try {
3649
+ result[k] = decrypt(blob, this.encKey);
3650
+ } catch {
3651
+ }
3652
+ }
3653
+ return result;
3654
+ }
3655
+ async set(tenantId, key, value) {
3656
+ const data = await this.readAll(tenantId);
3657
+ data[key] = encrypt(value, this.encKey);
3658
+ await this.writeAll(tenantId, data);
3659
+ }
3660
+ async delete(tenantId, key) {
3661
+ const data = await this.readAll(tenantId);
3662
+ delete data[key];
3663
+ await this.writeAll(tenantId, data);
3664
+ }
3665
+ async list(tenantId) {
3666
+ const data = await this.readAll(tenantId);
3667
+ return Object.keys(data);
3668
+ }
3669
+ };
3670
+ var createSecretsStore = (agentId, signingKey, config, options) => {
3671
+ const provider = config?.provider ?? "local";
3672
+ const ttl = typeof config?.ttl === "number" ? config.ttl : void 0;
3673
+ const workingDir = options?.workingDir ?? process.cwd();
3674
+ if (provider === "local" || provider === "memory") {
3675
+ return new FileSecretsStore(workingDir, signingKey);
3676
+ }
3677
+ const kv = createRawKVStore(config);
3678
+ if (kv) {
3679
+ const baseKey = `poncho:${STORAGE_SCHEMA_VERSION}:${slugifyStorageComponent(agentId)}`;
3680
+ return new KVSecretsStore(kv, baseKey, signingKey, ttl);
3681
+ }
3682
+ return new FileSecretsStore(workingDir, signingKey);
3683
+ };
3684
+ async function resolveEnv(secretsStore, tenantId, envName) {
3685
+ if (tenantId && secretsStore) {
3686
+ const secrets = await secretsStore.get(tenantId);
3687
+ if (secrets[envName]) return secrets[envName];
3688
+ }
3689
+ return process.env[envName];
3690
+ }
3691
+
3395
3692
  // src/reminder-tools.ts
3396
3693
  import { defineTool as defineTool4 } from "@poncho-ai/sdk";
3397
3694
  var VALID_STATUSES2 = ["pending", "cancelled"];
@@ -3464,7 +3761,8 @@ var createReminderTools = (store) => [
3464
3761
  task,
3465
3762
  scheduledAt,
3466
3763
  timezone,
3467
- conversationId
3764
+ conversationId,
3765
+ tenantId: context.tenantId
3468
3766
  });
3469
3767
  return {
3470
3768
  ok: true,
@@ -3492,8 +3790,11 @@ var createReminderTools = (store) => [
3492
3790
  },
3493
3791
  additionalProperties: false
3494
3792
  },
3495
- handler: async (input) => {
3793
+ handler: async (input, context) => {
3496
3794
  let reminders = await store.list();
3795
+ if (context.tenantId) {
3796
+ reminders = reminders.filter((r) => r.tenantId === context.tenantId);
3797
+ }
3497
3798
  const status = typeof input.status === "string" ? input.status : void 0;
3498
3799
  if (status && VALID_STATUSES2.includes(status)) {
3499
3800
  reminders = reminders.filter((r) => r.status === status);
@@ -3525,9 +3826,16 @@ var createReminderTools = (store) => [
3525
3826
  required: ["id"],
3526
3827
  additionalProperties: false
3527
3828
  },
3528
- handler: async (input) => {
3829
+ handler: async (input, context) => {
3529
3830
  const id = typeof input.id === "string" ? input.id.trim() : "";
3530
3831
  if (!id) throw new Error("id is required");
3832
+ if (context.tenantId) {
3833
+ const all = await store.list();
3834
+ const target = all.find((r) => r.id === id);
3835
+ if (target && target.tenantId !== context.tenantId) {
3836
+ throw new Error("Reminder not found");
3837
+ }
3838
+ }
3531
3839
  const cancelled = await store.cancel(id);
3532
3840
  return {
3533
3841
  ok: true,
@@ -3750,6 +4058,14 @@ var LocalMcpBridge = class {
3750
4058
  toolCatalog = /* @__PURE__ */ new Map();
3751
4059
  unavailableServers = /* @__PURE__ */ new Map();
3752
4060
  authFailedServers = /* @__PURE__ */ new Set();
4061
+ envResolver;
4062
+ /**
4063
+ * Set a resolver for per-tenant env vars (e.g. MCP auth tokens).
4064
+ * Called by the harness after creating the secrets store.
4065
+ */
4066
+ setEnvResolver(resolver) {
4067
+ this.envResolver = resolver;
4068
+ }
3753
4069
  constructor(config) {
3754
4070
  this.remoteServers = (config?.mcp ?? []).filter(
3755
4071
  (entry) => typeof entry.url === "string"
@@ -3789,8 +4105,11 @@ var LocalMcpBridge = class {
3789
4105
  }
3790
4106
  console.info(`[poncho][mcp] ${line}`);
3791
4107
  }
4108
+ /** Set of servers where discovery was deferred (no default token, has env resolver). */
4109
+ deferredDiscoveryServers = /* @__PURE__ */ new Set();
3792
4110
  async discoverTools() {
3793
4111
  this.toolCatalog.clear();
4112
+ this.deferredDiscoveryServers.clear();
3794
4113
  for (const remoteServer of this.remoteServers) {
3795
4114
  const name = this.getServerName(remoteServer);
3796
4115
  if (this.unavailableServers.has(name)) {
@@ -3811,6 +4130,11 @@ var LocalMcpBridge = class {
3811
4130
  } catch (error) {
3812
4131
  const message = error instanceof Error ? error.message : String(error);
3813
4132
  if (error instanceof McpHttpError && error.status === 401) {
4133
+ if (this.envResolver && remoteServer.auth?.tokenEnv) {
4134
+ this.deferredDiscoveryServers.add(name);
4135
+ this.log("info", "catalog.deferred", { server: name, reason: "auth_deferred_to_tenant" });
4136
+ continue;
4137
+ }
3814
4138
  this.authFailedServers.add(name);
3815
4139
  this.log("warn", "auth.failed", {
3816
4140
  server: name,
@@ -3823,6 +4147,57 @@ var LocalMcpBridge = class {
3823
4147
  }
3824
4148
  }
3825
4149
  }
4150
+ /**
4151
+ * Run deferred discovery and return ToolDefinitions for all newly discovered tools.
4152
+ * Call this during run() so the tools are available to the model immediately.
4153
+ */
4154
+ async discoverAndLoadDeferred(tenantId) {
4155
+ if (this.deferredDiscoveryServers.size === 0) return [];
4156
+ for (const server of this.remoteServers) {
4157
+ await this.tryDeferredDiscovery(server, tenantId);
4158
+ }
4159
+ const tools = [];
4160
+ for (const server of this.remoteServers) {
4161
+ const name = this.getServerName(server);
4162
+ if (!server.auth?.tokenEnv) continue;
4163
+ const discovered = this.toolCatalog.get(name);
4164
+ if (!discovered || discovered.length === 0) continue;
4165
+ const client = this.rpcClients.get(name);
4166
+ if (!client) continue;
4167
+ tools.push(...this.toToolDefinitions(name, discovered, client, server));
4168
+ }
4169
+ return tools;
4170
+ }
4171
+ async tryDeferredDiscovery(server, tenantId) {
4172
+ const name = this.getServerName(server);
4173
+ if (!this.deferredDiscoveryServers.has(name)) return;
4174
+ if (this.toolCatalog.has(name)) return;
4175
+ const tokenEnv = server.auth?.tokenEnv;
4176
+ if (!tokenEnv || !this.envResolver) return;
4177
+ const token = await this.envResolver(tenantId, tokenEnv);
4178
+ if (!token) return;
4179
+ try {
4180
+ const probeClient = new StreamableHttpMcpRpcClient(
4181
+ server.url,
4182
+ server.timeoutMs ?? 1e4,
4183
+ token,
4184
+ server.headers
4185
+ );
4186
+ const discovered = await probeClient.listTools();
4187
+ this.toolCatalog.set(name, discovered);
4188
+ this.deferredDiscoveryServers.delete(name);
4189
+ this.log("info", "catalog.loaded", {
4190
+ server: name,
4191
+ discoveredCount: discovered.length,
4192
+ via: "deferred_tenant_discovery"
4193
+ });
4194
+ } catch (error) {
4195
+ this.log("warn", "catalog.deferred_failed", {
4196
+ server: name,
4197
+ error: error instanceof Error ? error.message : String(error)
4198
+ });
4199
+ }
4200
+ }
3826
4201
  async startLocalServers() {
3827
4202
  this.unavailableServers.clear();
3828
4203
  for (const server of this.remoteServers) {
@@ -3831,15 +4206,21 @@ var LocalMcpBridge = class {
3831
4206
  if (tokenEnv) {
3832
4207
  const token = process.env[tokenEnv];
3833
4208
  if (!token || token.trim().length === 0) {
3834
- this.unavailableServers.set(
3835
- name,
3836
- `Missing bearer token value from env var ${tokenEnv}`
3837
- );
3838
- this.log("warn", "auth.token_missing", {
4209
+ if (!this.envResolver) {
4210
+ this.unavailableServers.set(
4211
+ name,
4212
+ `Missing bearer token value from env var ${tokenEnv}`
4213
+ );
4214
+ this.log("warn", "auth.token_missing", {
4215
+ server: name,
4216
+ tokenEnv
4217
+ });
4218
+ continue;
4219
+ }
4220
+ this.log("info", "auth.token_deferred", {
3839
4221
  server: name,
3840
4222
  tokenEnv
3841
4223
  });
3842
- continue;
3843
4224
  }
3844
4225
  }
3845
4226
  this.rpcClients.set(
@@ -3903,6 +4284,29 @@ var LocalMcpBridge = class {
3903
4284
  }
3904
4285
  return output.sort();
3905
4286
  }
4287
+ hasDeferredServers() {
4288
+ return this.deferredDiscoveryServers.size > 0;
4289
+ }
4290
+ /**
4291
+ * Return ToolDefinitions for catalog tools not already registered in the dispatcher.
4292
+ */
4293
+ getUnregisteredTools(registeredNames) {
4294
+ const tools = [];
4295
+ for (const server of this.remoteServers) {
4296
+ const name = this.getServerName(server);
4297
+ const discovered = this.toolCatalog.get(name);
4298
+ if (!discovered || discovered.length === 0) continue;
4299
+ const client = this.rpcClients.get(name);
4300
+ if (!client) continue;
4301
+ const unregistered = discovered.filter(
4302
+ (d) => !registeredNames.has(`${name}/${d.name}`)
4303
+ );
4304
+ if (unregistered.length > 0) {
4305
+ tools.push(...this.toToolDefinitions(name, unregistered, client, server));
4306
+ }
4307
+ }
4308
+ return tools;
4309
+ }
3906
4310
  async loadTools(requestedPatterns) {
3907
4311
  for (const [index, pattern] of requestedPatterns.entries()) {
3908
4312
  validateMcpPattern(pattern, `requestedPatterns[${index}]`);
@@ -3934,7 +4338,7 @@ var LocalMcpBridge = class {
3934
4338
  const selectedDescriptors = discovered.filter(
3935
4339
  (descriptor) => selectedRawNames.has(descriptor.name)
3936
4340
  );
3937
- tools.push(...this.toToolDefinitions(serverName, selectedDescriptors, client));
4341
+ tools.push(...this.toToolDefinitions(serverName, selectedDescriptors, client, server));
3938
4342
  }
3939
4343
  this.log("info", "tools.selected", {
3940
4344
  requestedPatternCount: requestedPatterns.length,
@@ -3944,7 +4348,7 @@ var LocalMcpBridge = class {
3944
4348
  });
3945
4349
  return tools;
3946
4350
  }
3947
- toToolDefinitions(serverName, tools, client) {
4351
+ toToolDefinitions(serverName, tools, client, server) {
3948
4352
  return tools.map((tool) => ({
3949
4353
  name: `${serverName}/${tool.name}`,
3950
4354
  description: tool.description ?? `MCP tool ${tool.name} from ${serverName}`,
@@ -3952,9 +4356,23 @@ var LocalMcpBridge = class {
3952
4356
  type: "object",
3953
4357
  properties: {}
3954
4358
  },
3955
- handler: async (input) => {
4359
+ handler: async (input, context) => {
3956
4360
  try {
3957
- return await client.callTool(tool.name, input);
4361
+ const tokenEnv = server?.auth?.tokenEnv;
4362
+ let callClient = client;
4363
+ if (tokenEnv && this.envResolver && context?.tenantId) {
4364
+ const tenantToken = await this.envResolver(context.tenantId, tokenEnv);
4365
+ const defaultToken = process.env[tokenEnv];
4366
+ if (tenantToken && tenantToken !== defaultToken) {
4367
+ callClient = new StreamableHttpMcpRpcClient(
4368
+ server.url,
4369
+ server.timeoutMs ?? 1e4,
4370
+ tenantToken,
4371
+ server.headers
4372
+ );
4373
+ }
4374
+ }
4375
+ return await callClient.callTool(tool.name, input);
3958
4376
  } catch (error) {
3959
4377
  if (error instanceof McpHttpError && error.status === 401) {
3960
4378
  this.authFailedServers.add(serverName);
@@ -3985,8 +4403,8 @@ import { createAnthropic } from "@ai-sdk/anthropic";
3985
4403
 
3986
4404
  // src/openai-codex-auth.ts
3987
4405
  import { homedir as homedir3 } from "os";
3988
- import { dirname as dirname5, resolve as resolve9 } from "path";
3989
- import { mkdir as mkdir6, readFile as readFile8, chmod, writeFile as writeFile7, rm as rm3 } from "fs/promises";
4406
+ import { dirname as dirname6, resolve as resolve10 } from "path";
4407
+ import { mkdir as mkdir7, readFile as readFile9, chmod, writeFile as writeFile8, rm as rm3 } from "fs/promises";
3990
4408
  var OPENAI_CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
3991
4409
  var OPENAI_AUTH_ISSUER = "https://auth.openai.com";
3992
4410
  var REFRESH_TOKEN_GRACE_MS = 5 * 60 * 1e3;
@@ -4018,14 +4436,14 @@ var getOpenAICodexAuthFilePath = (config) => {
4018
4436
  const env = defaultedConfig(config);
4019
4437
  const fromEnv = process.env[env.authFilePathEnv];
4020
4438
  if (typeof fromEnv === "string" && fromEnv.trim().length > 0) {
4021
- return resolve9(fromEnv);
4439
+ return resolve10(fromEnv);
4022
4440
  }
4023
- return resolve9(homedir3(), ".poncho", "auth", "openai-codex.json");
4441
+ return resolve10(homedir3(), ".poncho", "auth", "openai-codex.json");
4024
4442
  };
4025
4443
  var readOpenAICodexSession = async (config) => {
4026
4444
  const filePath = getOpenAICodexAuthFilePath(config);
4027
4445
  try {
4028
- const content = await readFile8(filePath, "utf8");
4446
+ const content = await readFile9(filePath, "utf8");
4029
4447
  const parsed = JSON.parse(content);
4030
4448
  if (typeof parsed.refreshToken !== "string" || parsed.refreshToken.length === 0) {
4031
4449
  return void 0;
@@ -4042,12 +4460,12 @@ var readOpenAICodexSession = async (config) => {
4042
4460
  };
4043
4461
  var writeOpenAICodexSession = async (session, config) => {
4044
4462
  const filePath = getOpenAICodexAuthFilePath(config);
4045
- await mkdir6(dirname5(filePath), { recursive: true });
4463
+ await mkdir7(dirname6(filePath), { recursive: true });
4046
4464
  const payload = {
4047
4465
  ...session,
4048
4466
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
4049
4467
  };
4050
- await writeFile7(filePath, `${JSON.stringify(payload, null, 2)}
4468
+ await writeFile8(filePath, `${JSON.stringify(payload, null, 2)}
4051
4469
  `, "utf8");
4052
4470
  await chmod(filePath, 384);
4053
4471
  };
@@ -4360,8 +4778,8 @@ var createModelProvider = (provider, config) => {
4360
4778
  };
4361
4779
 
4362
4780
  // src/skill-context.ts
4363
- import { readFile as readFile9, readdir as readdir2, stat } from "fs/promises";
4364
- import { dirname as dirname6, resolve as resolve10, normalize } from "path";
4781
+ import { readFile as readFile10, readdir as readdir2, stat } from "fs/promises";
4782
+ import { dirname as dirname7, resolve as resolve11, normalize } from "path";
4365
4783
  import YAML3 from "yaml";
4366
4784
  var DEFAULT_SKILL_DIRS = ["skills"];
4367
4785
  var resolveSkillDirs = (workingDir, extraPaths) => {
@@ -4373,7 +4791,7 @@ var resolveSkillDirs = (workingDir, extraPaths) => {
4373
4791
  }
4374
4792
  }
4375
4793
  }
4376
- return dirs.map((d) => resolve10(workingDir, d));
4794
+ return dirs.map((d) => resolve11(workingDir, d));
4377
4795
  };
4378
4796
  var FRONTMATTER_PATTERN3 = /^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/;
4379
4797
  var asRecord2 = (value) => typeof value === "object" && value !== null ? value : {};
@@ -4442,7 +4860,7 @@ var collectSkillManifests = async (directory) => {
4442
4860
  const entries = await readdir2(directory, { withFileTypes: true });
4443
4861
  const files = [];
4444
4862
  for (const entry of entries) {
4445
- const fullPath = resolve10(directory, entry.name);
4863
+ const fullPath = resolve11(directory, entry.name);
4446
4864
  let isDir = entry.isDirectory();
4447
4865
  let isFile = entry.isFile();
4448
4866
  if (entry.isSymbolicLink()) {
@@ -4477,13 +4895,13 @@ var loadSkillMetadata = async (workingDir, extraSkillPaths) => {
4477
4895
  const seen = /* @__PURE__ */ new Set();
4478
4896
  for (const manifest of allManifests) {
4479
4897
  try {
4480
- const content = await readFile9(manifest, "utf8");
4898
+ const content = await readFile10(manifest, "utf8");
4481
4899
  const parsed = parseSkillFrontmatter(content);
4482
4900
  if (parsed && !seen.has(parsed.name)) {
4483
4901
  seen.add(parsed.name);
4484
4902
  skills.push({
4485
4903
  ...parsed,
4486
- skillDir: dirname6(manifest),
4904
+ skillDir: dirname7(manifest),
4487
4905
  skillPath: manifest
4488
4906
  });
4489
4907
  }
@@ -4522,7 +4940,7 @@ ${xmlSkills}
4522
4940
  };
4523
4941
  var escapeXml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
4524
4942
  var loadSkillInstructions = async (skill) => {
4525
- const content = await readFile9(skill.skillPath, "utf8");
4943
+ const content = await readFile10(skill.skillPath, "utf8");
4526
4944
  const match = content.match(FRONTMATTER_PATTERN3);
4527
4945
  return match ? match[2].trim() : content.trim();
4528
4946
  };
@@ -4531,11 +4949,11 @@ var readSkillResource = async (skill, relativePath) => {
4531
4949
  if (normalized.startsWith("..") || normalized.startsWith("/")) {
4532
4950
  throw new Error("Path must be relative and within the skill directory");
4533
4951
  }
4534
- const fullPath = resolve10(skill.skillDir, normalized);
4952
+ const fullPath = resolve11(skill.skillDir, normalized);
4535
4953
  if (!fullPath.startsWith(skill.skillDir)) {
4536
4954
  throw new Error("Path escapes the skill directory");
4537
4955
  }
4538
- return await readFile9(fullPath, "utf8");
4956
+ return await readFile10(fullPath, "utf8");
4539
4957
  };
4540
4958
  var MAX_INSTRUCTIONS_PER_SKILL = 1200;
4541
4959
  var loadSkillContext = async (workingDir) => {
@@ -4654,7 +5072,7 @@ function convertSchema(schema) {
4654
5072
  // src/skill-tools.ts
4655
5073
  import { defineTool as defineTool5 } from "@poncho-ai/sdk";
4656
5074
  import { access as access2, readdir as readdir3, stat as stat2 } from "fs/promises";
4657
- import { extname, normalize as normalize2, resolve as resolve11, sep as sep2 } from "path";
5075
+ import { extname, normalize as normalize2, resolve as resolve12, sep as sep2 } from "path";
4658
5076
  import { pathToFileURL } from "url";
4659
5077
  import { createJiti as createJiti2 } from "jiti";
4660
5078
  var createSkillTools = (skills, options) => {
@@ -4912,7 +5330,7 @@ var collectScriptFiles = async (directory) => {
4912
5330
  if (entry.name === "node_modules") {
4913
5331
  continue;
4914
5332
  }
4915
- const fullPath = resolve11(directory, entry.name);
5333
+ const fullPath = resolve12(directory, entry.name);
4916
5334
  let isDir = entry.isDirectory();
4917
5335
  let isFile = entry.isFile();
4918
5336
  if (entry.isSymbolicLink()) {
@@ -4951,8 +5369,8 @@ var normalizeScriptPolicyPath = (relativePath) => {
4951
5369
  };
4952
5370
  var resolveScriptPath = (baseDir, relativePath, containmentDir) => {
4953
5371
  const normalized = normalizeScriptPolicyPath(relativePath);
4954
- const fullPath = resolve11(baseDir, normalized);
4955
- const boundary = resolve11(containmentDir ?? baseDir);
5372
+ const fullPath = resolve12(baseDir, normalized);
5373
+ const boundary = resolve12(containmentDir ?? baseDir);
4956
5374
  if (!fullPath.startsWith(`${boundary}${sep2}`) && fullPath !== boundary) {
4957
5375
  throw new Error("Script path must stay inside the allowed directory");
4958
5376
  }
@@ -5058,8 +5476,8 @@ function applyRateLimitCooldown(retryAfterHeader) {
5058
5476
  async function runWithSearchThrottle(fn) {
5059
5477
  const previous = searchQueue;
5060
5478
  let release;
5061
- searchQueue = new Promise((resolve14) => {
5062
- release = resolve14;
5479
+ searchQueue = new Promise((resolve15) => {
5480
+ release = resolve15;
5063
5481
  });
5064
5482
  await previous.catch(() => {
5065
5483
  });
@@ -5265,7 +5683,8 @@ var createSubagentTools = (manager) => [
5265
5683
  const { subagentId } = await manager.spawn({
5266
5684
  task: task.trim(),
5267
5685
  parentConversationId: conversationId,
5268
- ownerId
5686
+ ownerId,
5687
+ tenantId: context.tenantId
5269
5688
  });
5270
5689
  return { subagentId, status: "running" };
5271
5690
  }
@@ -5350,8 +5769,12 @@ import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
5350
5769
 
5351
5770
  // src/telemetry.ts
5352
5771
  var MAX_FIELD_LENGTH = 200;
5772
+ var OMIT_FROM_LOG = /* @__PURE__ */ new Set(["continuationMessages", "_harnessMessages", "messages", "compactedHistory"]);
5353
5773
  function sanitizeEventForLog(event) {
5354
- return JSON.stringify(event, (_key, value) => {
5774
+ return JSON.stringify(event, (key, value) => {
5775
+ if (OMIT_FROM_LOG.has(key) && Array.isArray(value)) {
5776
+ return `[${value.length} messages]`;
5777
+ }
5355
5778
  if (typeof value === "string" && value.length > MAX_FIELD_LENGTH) {
5356
5779
  return `${value.slice(0, 80)}...[${value.length} chars]`;
5357
5780
  }
@@ -6001,8 +6424,11 @@ var AgentHarness = class _AgentHarness {
6001
6424
  uploadStore;
6002
6425
  skillContextWindow = "";
6003
6426
  memoryStore;
6427
+ tenantMemoryStores = /* @__PURE__ */ new Map();
6428
+ memoryConfig;
6004
6429
  todoStore;
6005
6430
  reminderStore;
6431
+ secretsStore;
6006
6432
  loadedConfig;
6007
6433
  loadedSkills = [];
6008
6434
  skillFingerprint = "";
@@ -6287,6 +6713,28 @@ var AgentHarness = class _AgentHarness {
6287
6713
  if (!this.todoStore) return [];
6288
6714
  return this.todoStore.get(conversationId);
6289
6715
  }
6716
+ /**
6717
+ * Get a memory store, optionally scoped to a tenant.
6718
+ * Returns the default (agent-wide) store when tenantId is null/undefined.
6719
+ */
6720
+ getMemoryStore(tenantId) {
6721
+ if (!this.memoryConfig?.enabled) return void 0;
6722
+ if (!tenantId) return this.memoryStore;
6723
+ let store = this.tenantMemoryStores.get(tenantId);
6724
+ if (!store) {
6725
+ const agentId = this.parsedAgent?.frontmatter.id ?? this.parsedAgent?.frontmatter.name ?? "unknown";
6726
+ store = createMemoryStore(agentId, this.memoryConfig, {
6727
+ workingDir: this.workingDir,
6728
+ tenantId
6729
+ });
6730
+ this.tenantMemoryStores.set(tenantId, store);
6731
+ if (this.tenantMemoryStores.size > 100) {
6732
+ const oldest = this.tenantMemoryStores.keys().next().value;
6733
+ if (oldest) this.tenantMemoryStores.delete(oldest);
6734
+ }
6735
+ }
6736
+ return store;
6737
+ }
6290
6738
  listActiveSkills() {
6291
6739
  return [...this.activeSkillNames].sort();
6292
6740
  }
@@ -6517,8 +6965,8 @@ var AgentHarness = class _AgentHarness {
6517
6965
  return false;
6518
6966
  }
6519
6967
  try {
6520
- const agentFilePath = resolve12(this.workingDir, "AGENT.md");
6521
- const rawContent = await readFile10(agentFilePath, "utf8");
6968
+ const agentFilePath = resolve13(this.workingDir, "AGENT.md");
6969
+ const rawContent = await readFile11(agentFilePath, "utf8");
6522
6970
  if (rawContent === this.agentFileFingerprint) {
6523
6971
  return false;
6524
6972
  }
@@ -6587,8 +7035,8 @@ var AgentHarness = class _AgentHarness {
6587
7035
  }
6588
7036
  }
6589
7037
  async initialize() {
6590
- const agentFilePath = resolve12(this.workingDir, "AGENT.md");
6591
- const agentRawContent = await readFile10(agentFilePath, "utf8");
7038
+ const agentFilePath = resolve13(this.workingDir, "AGENT.md");
7039
+ const agentRawContent = await readFile11(agentFilePath, "utf8");
6592
7040
  this.parsedAgent = parseAgentMarkdown(agentRawContent);
6593
7041
  this.agentFileFingerprint = agentRawContent;
6594
7042
  const identity = await ensureAgentIdentity(this.workingDir);
@@ -6612,6 +7060,7 @@ var AgentHarness = class _AgentHarness {
6612
7060
  this.skillFingerprint = this.buildSkillFingerprint(skillMetadata);
6613
7061
  this.registerSkillTools(skillMetadata);
6614
7062
  const agentId = this.parsedAgent.frontmatter.id ?? this.parsedAgent.frontmatter.name;
7063
+ this.memoryConfig = memoryConfig ?? void 0;
6615
7064
  if (memoryConfig?.enabled) {
6616
7065
  this.memoryStore = createMemoryStore(
6617
7066
  agentId,
@@ -6619,9 +7068,10 @@ var AgentHarness = class _AgentHarness {
6619
7068
  { workingDir: this.workingDir }
6620
7069
  );
6621
7070
  this.dispatcher.registerMany(
6622
- createMemoryTools(this.memoryStore, {
6623
- maxRecallConversations: memoryConfig.maxRecallConversations
6624
- })
7071
+ createMemoryTools(
7072
+ (ctx) => this.getMemoryStore(ctx.tenantId) ?? this.memoryStore,
7073
+ { maxRecallConversations: memoryConfig.maxRecallConversations }
7074
+ )
6625
7075
  );
6626
7076
  }
6627
7077
  const stateConfig = resolveStateConfig(config);
@@ -6646,6 +7096,14 @@ var AgentHarness = class _AgentHarness {
6646
7096
  );
6647
7097
  });
6648
7098
  }
7099
+ const authTokenEnv = config?.auth?.tokenEnv ?? "PONCHO_AUTH_TOKEN";
7100
+ const authToken = process.env[authTokenEnv];
7101
+ if (authToken) {
7102
+ this.secretsStore = createSecretsStore(agentId, authToken, stateConfig, { workingDir: this.workingDir });
7103
+ bridge.setEnvResolver(async (tenantId, envName) => {
7104
+ return resolveEnv(this.secretsStore, tenantId, envName);
7105
+ });
7106
+ }
6649
7107
  await bridge.startLocalServers();
6650
7108
  await bridge.discoverTools();
6651
7109
  await this.refreshMcpTools("initialize");
@@ -6679,14 +7137,14 @@ var AgentHarness = class _AgentHarness {
6679
7137
  const filePath = pathResolve(stateDir, `${sessionId}.json`);
6680
7138
  return {
6681
7139
  async save(json) {
6682
- const { mkdir: mkdir8, writeFile: writeFile9 } = await import("fs/promises");
6683
- await mkdir8(stateDir, { recursive: true });
6684
- await writeFile9(filePath, json, "utf8");
7140
+ const { mkdir: mkdir9, writeFile: writeFile10 } = await import("fs/promises");
7141
+ await mkdir9(stateDir, { recursive: true });
7142
+ await writeFile10(filePath, json, "utf8");
6685
7143
  },
6686
7144
  async load() {
6687
- const { readFile: readFile12 } = await import("fs/promises");
7145
+ const { readFile: readFile13 } = await import("fs/promises");
6688
7146
  try {
6689
- return await readFile12(filePath, "utf8");
7147
+ return await readFile13(filePath, "utf8");
6690
7148
  } catch {
6691
7149
  return void 0;
6692
7150
  }
@@ -6752,7 +7210,7 @@ var AgentHarness = class _AgentHarness {
6752
7210
  let browserMod;
6753
7211
  try {
6754
7212
  const { existsSync } = await import("fs");
6755
- const { join, dirname: dirname8 } = await import("path");
7213
+ const { join, dirname: dirname9 } = await import("path");
6756
7214
  const { pathToFileURL: pathToFileURL2 } = await import("url");
6757
7215
  let searchDir = this.workingDir;
6758
7216
  let entryPath;
@@ -6762,7 +7220,7 @@ var AgentHarness = class _AgentHarness {
6762
7220
  entryPath = candidate;
6763
7221
  break;
6764
7222
  }
6765
- const parent = dirname8(searchDir);
7223
+ const parent = dirname9(searchDir);
6766
7224
  if (parent === searchDir) break;
6767
7225
  searchDir = parent;
6768
7226
  }
@@ -6838,7 +7296,8 @@ var AgentHarness = class _AgentHarness {
6838
7296
  kind: SpanKind.INTERNAL,
6839
7297
  attributes: {
6840
7298
  "gen_ai.operation.name": "invoke_agent",
6841
- ...input.conversationId ? { "gen_ai.conversation.id": input.conversationId } : {}
7299
+ ...input.conversationId ? { "gen_ai.conversation.id": input.conversationId } : {},
7300
+ ...input.tenantId ? { "tenant.id": input.tenantId } : {}
6842
7301
  }
6843
7302
  });
6844
7303
  const spanContext = trace.setSpan(otelContext.active(), rootSpan);
@@ -6884,10 +7343,18 @@ var AgentHarness = class _AgentHarness {
6884
7343
  if (!this.parsedAgent) {
6885
7344
  await this.initialize();
6886
7345
  }
6887
- const memoryPromise = this.memoryStore ? this.memoryStore.getMainMemory() : void 0;
7346
+ const activeMemoryStore = this.getMemoryStore(input.tenantId);
7347
+ const memoryPromise = activeMemoryStore ? activeMemoryStore.getMainMemory() : void 0;
6888
7348
  const todosPromise = this.todoStore ? this.todoStore.get(input.conversationId ?? "__default__") : void 0;
6889
7349
  await this.refreshAgentIfChanged();
6890
7350
  await this.refreshSkillsIfChanged();
7351
+ if (input.tenantId && this.mcpBridge?.hasDeferredServers()) {
7352
+ const newTools = await this.mcpBridge.discoverAndLoadDeferred(input.tenantId);
7353
+ for (const tool of newTools) {
7354
+ this.dispatcher.register(tool);
7355
+ this.registeredMcpToolNames.add(tool.name);
7356
+ }
7357
+ }
6891
7358
  let agent = this.parsedAgent;
6892
7359
  const runId = `run_${randomUUID3()}`;
6893
7360
  const start = now();
@@ -7289,19 +7756,24 @@ ${textContent}` };
7289
7756
  };
7290
7757
  }
7291
7758
  let resolvedData;
7292
- if (part.data.startsWith(PONCHO_UPLOAD_SCHEME) && this.uploadStore) {
7293
- const buf = await this.uploadStore.get(part.data);
7294
- resolvedData = buf.toString("base64");
7295
- } else if (part.data.startsWith("https://") || part.data.startsWith("http://")) {
7296
- if (this.uploadStore) {
7759
+ try {
7760
+ if (part.data.startsWith(PONCHO_UPLOAD_SCHEME) && this.uploadStore) {
7297
7761
  const buf = await this.uploadStore.get(part.data);
7298
7762
  resolvedData = buf.toString("base64");
7763
+ } else if (part.data.startsWith("https://") || part.data.startsWith("http://")) {
7764
+ if (this.uploadStore) {
7765
+ const buf = await this.uploadStore.get(part.data);
7766
+ resolvedData = buf.toString("base64");
7767
+ } else {
7768
+ const resp = await fetch(part.data);
7769
+ resolvedData = Buffer.from(await resp.arrayBuffer()).toString("base64");
7770
+ }
7299
7771
  } else {
7300
- const resp = await fetch(part.data);
7301
- resolvedData = Buffer.from(await resp.arrayBuffer()).toString("base64");
7772
+ resolvedData = part.data;
7302
7773
  }
7303
- } else {
7304
- resolvedData = part.data;
7774
+ } catch {
7775
+ const label = part.filename ?? part.mediaType;
7776
+ return { type: "text", text: `[Attached file: ${label} \u2014 file is no longer available]` };
7305
7777
  }
7306
7778
  if (isSupportedImage) {
7307
7779
  return {
@@ -7330,8 +7802,8 @@ ${textContent}` };
7330
7802
  const compactionConfig = resolveCompactionConfig(agent.frontmatter.compaction);
7331
7803
  if (compactionConfig.enabled && (step === 1 || step % COMPACTION_CHECK_INTERVAL_STEPS === 0)) {
7332
7804
  const estimated = estimateTotalTokens(systemPrompt, messages, toolDefsJsonForEstimate);
7333
- const lastReportedInput = totalInputTokens > 0 ? totalInputTokens : 0;
7334
- const effectiveTokens = Math.max(estimated, lastReportedInput);
7805
+ const lastReportedContext = latestContextTokens > 0 ? latestContextTokens + toolOutputEstimateSinceModel : 0;
7806
+ const effectiveTokens = Math.max(estimated, lastReportedContext);
7335
7807
  if (effectiveTokens > compactionConfig.trigger * contextWindow) {
7336
7808
  yield pushEvent({ type: "compaction:started", estimatedTokens: effectiveTokens });
7337
7809
  const compactResult = await compactMessages(
@@ -7439,8 +7911,8 @@ ${textContent}` };
7439
7911
  let timer;
7440
7912
  nextPart = await Promise.race([
7441
7913
  fullStreamIterator.next(),
7442
- new Promise((resolve14) => {
7443
- timer = setTimeout(() => resolve14(null), effectiveTimeout);
7914
+ new Promise((resolve15) => {
7915
+ timer = setTimeout(() => resolve15(null), effectiveTimeout);
7444
7916
  })
7445
7917
  ]);
7446
7918
  clearTimeout(timer);
@@ -7658,7 +8130,8 @@ ${textContent}` };
7658
8130
  workingDir: this.workingDir,
7659
8131
  parameters: input.parameters ?? {},
7660
8132
  abortSignal: input.abortSignal,
7661
- conversationId: input.conversationId
8133
+ conversationId: input.conversationId,
8134
+ tenantId: input.tenantId
7662
8135
  };
7663
8136
  const toolResultsForModel = [];
7664
8137
  const richToolResults = [];
@@ -7760,7 +8233,7 @@ ${textContent}` };
7760
8233
  const raced = await Promise.race([
7761
8234
  this.dispatcher.executeBatch(approvedCalls, toolContext),
7762
8235
  new Promise(
7763
- (resolve14) => setTimeout(() => resolve14(TOOL_DEADLINE_SENTINEL), toolDeadlineRemainingMs)
8236
+ (resolve15) => setTimeout(() => resolve15(TOOL_DEADLINE_SENTINEL), toolDeadlineRemainingMs)
7764
8237
  )
7765
8238
  ]);
7766
8239
  if (raced === TOOL_DEADLINE_SENTINEL) {
@@ -8133,8 +8606,8 @@ ${this.skillFingerprint}`;
8133
8606
 
8134
8607
  // src/state.ts
8135
8608
  import { randomUUID as randomUUID4 } from "crypto";
8136
- import { mkdir as mkdir7, readFile as readFile11, readdir as readdir4, rename as rename4, rm as rm4, writeFile as writeFile8 } from "fs/promises";
8137
- import { dirname as dirname7, resolve as resolve13 } from "path";
8609
+ import { mkdir as mkdir8, readFile as readFile12, readdir as readdir4, rename as rename4, rm as rm4, writeFile as writeFile9 } from "fs/promises";
8610
+ import { dirname as dirname8, resolve as resolve14 } from "path";
8138
8611
  var DEFAULT_OWNER = "local-owner";
8139
8612
  var LOCAL_STATE_FILE = "state.json";
8140
8613
  var CONVERSATIONS_DIRECTORY = "conversations";
@@ -8150,9 +8623,9 @@ var toStoreIdentity = async ({
8150
8623
  return { name: ensured.name, id: agentId };
8151
8624
  };
8152
8625
  var writeJsonAtomic4 = async (filePath, payload) => {
8153
- await mkdir7(dirname7(filePath), { recursive: true });
8626
+ await mkdir8(dirname8(filePath), { recursive: true });
8154
8627
  const tmpPath = `${filePath}.tmp`;
8155
- await writeFile8(tmpPath, JSON.stringify(payload, null, 2), "utf8");
8628
+ await writeFile9(tmpPath, JSON.stringify(payload, null, 2), "utf8");
8156
8629
  await rename4(tmpPath, filePath);
8157
8630
  };
8158
8631
  var formatUtcTimestamp = (value) => new Date(value).toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
@@ -8248,18 +8721,19 @@ var InMemoryConversationStore = class {
8248
8721
  }
8249
8722
  }
8250
8723
  }
8251
- async list(ownerId) {
8724
+ async list(ownerId, tenantId) {
8252
8725
  this.purgeExpired();
8253
- return Array.from(this.conversations.values()).filter((conversation) => !ownerId || conversation.ownerId === ownerId).sort((a, b) => b.updatedAt - a.updatedAt);
8726
+ return Array.from(this.conversations.values()).filter((conversation) => !ownerId || conversation.ownerId === ownerId).filter((c) => tenantId === void 0 || c.tenantId === tenantId).sort((a, b) => b.updatedAt - a.updatedAt);
8254
8727
  }
8255
- async listSummaries(ownerId) {
8728
+ async listSummaries(ownerId, tenantId) {
8256
8729
  this.purgeExpired();
8257
- return Array.from(this.conversations.values()).filter((c) => !ownerId || c.ownerId === ownerId).sort((a, b) => b.updatedAt - a.updatedAt).map((c) => ({
8730
+ return Array.from(this.conversations.values()).filter((c) => !ownerId || c.ownerId === ownerId).filter((c) => tenantId === void 0 || c.tenantId === tenantId).sort((a, b) => b.updatedAt - a.updatedAt).map((c) => ({
8258
8731
  conversationId: c.conversationId,
8259
8732
  title: c.title,
8260
8733
  updatedAt: c.updatedAt,
8261
8734
  createdAt: c.createdAt,
8262
8735
  ownerId: c.ownerId,
8736
+ tenantId: c.tenantId,
8263
8737
  parentConversationId: c.parentConversationId,
8264
8738
  messageCount: c.messages.length,
8265
8739
  hasPendingApprovals: Array.isArray(c.pendingApprovals) && c.pendingApprovals.length > 0,
@@ -8270,14 +8744,14 @@ var InMemoryConversationStore = class {
8270
8744
  this.purgeExpired();
8271
8745
  return this.conversations.get(conversationId);
8272
8746
  }
8273
- async create(ownerId = DEFAULT_OWNER, title) {
8747
+ async create(ownerId = DEFAULT_OWNER, title, tenantId = null) {
8274
8748
  const now2 = Date.now();
8275
8749
  const conversation = {
8276
8750
  conversationId: globalThis.crypto?.randomUUID?.() ?? `${now2}-${Math.random()}`,
8277
8751
  title: normalizeTitle(title),
8278
8752
  messages: [],
8279
8753
  ownerId,
8280
- tenantId: null,
8754
+ tenantId,
8281
8755
  createdAt: now2,
8282
8756
  updatedAt: now2
8283
8757
  };
@@ -8341,8 +8815,8 @@ var FileConversationStore = class {
8341
8815
  agentId: this.agentId
8342
8816
  });
8343
8817
  const agentDir = getAgentStoreDirectory(identity);
8344
- const conversationsDir = resolve13(agentDir, CONVERSATIONS_DIRECTORY);
8345
- const indexPath = resolve13(conversationsDir, LOCAL_CONVERSATION_INDEX_FILE);
8818
+ const conversationsDir = resolve14(agentDir, CONVERSATIONS_DIRECTORY);
8819
+ const indexPath = resolve14(conversationsDir, LOCAL_CONVERSATION_INDEX_FILE);
8346
8820
  this.paths = { conversationsDir, indexPath };
8347
8821
  return this.paths;
8348
8822
  }
@@ -8356,9 +8830,9 @@ var FileConversationStore = class {
8356
8830
  }
8357
8831
  async readConversationFile(fileName) {
8358
8832
  const { conversationsDir } = await this.resolvePaths();
8359
- const filePath = resolve13(conversationsDir, fileName);
8833
+ const filePath = resolve14(conversationsDir, fileName);
8360
8834
  try {
8361
- const raw = await readFile11(filePath, "utf8");
8835
+ const raw = await readFile12(filePath, "utf8");
8362
8836
  return JSON.parse(raw);
8363
8837
  } catch {
8364
8838
  return void 0;
@@ -8383,6 +8857,7 @@ var FileConversationStore = class {
8383
8857
  updatedAt: conversation.updatedAt,
8384
8858
  createdAt: conversation.createdAt,
8385
8859
  ownerId: conversation.ownerId,
8860
+ tenantId: conversation.tenantId,
8386
8861
  fileName: entry.name,
8387
8862
  parentConversationId: conversation.parentConversationId,
8388
8863
  messageCount: conversation.messages.length,
@@ -8404,7 +8879,7 @@ var FileConversationStore = class {
8404
8879
  this.loaded = true;
8405
8880
  const { indexPath } = await this.resolvePaths();
8406
8881
  try {
8407
- const raw = await readFile11(indexPath, "utf8");
8882
+ const raw = await readFile12(indexPath, "utf8");
8408
8883
  const parsed = JSON.parse(raw);
8409
8884
  for (const conversation of parsed.conversations ?? []) {
8410
8885
  this.conversations.set(conversation.conversationId, conversation);
@@ -8427,7 +8902,7 @@ var FileConversationStore = class {
8427
8902
  const { conversationsDir } = await this.resolvePaths();
8428
8903
  const existing = this.conversations.get(conversation.conversationId);
8429
8904
  const fileName = existing?.fileName ?? this.resolveConversationFileName(conversation);
8430
- const filePath = resolve13(conversationsDir, fileName);
8905
+ const filePath = resolve14(conversationsDir, fileName);
8431
8906
  this.writing = this.writing.then(async () => {
8432
8907
  await writeJsonAtomic4(filePath, conversation);
8433
8908
  this.conversations.set(conversation.conversationId, {
@@ -8436,6 +8911,7 @@ var FileConversationStore = class {
8436
8911
  updatedAt: conversation.updatedAt,
8437
8912
  createdAt: conversation.createdAt,
8438
8913
  ownerId: conversation.ownerId,
8914
+ tenantId: conversation.tenantId,
8439
8915
  fileName,
8440
8916
  parentConversationId: conversation.parentConversationId,
8441
8917
  messageCount: conversation.messages.length,
@@ -8446,9 +8922,9 @@ var FileConversationStore = class {
8446
8922
  });
8447
8923
  await this.writing;
8448
8924
  }
8449
- async list(ownerId) {
8925
+ async list(ownerId, tenantId) {
8450
8926
  await this.ensureLoaded();
8451
- const summaries = Array.from(this.conversations.values()).filter((conversation) => !ownerId || conversation.ownerId === ownerId).sort((a, b) => b.updatedAt - a.updatedAt);
8927
+ const summaries = Array.from(this.conversations.values()).filter((conversation) => !ownerId || conversation.ownerId === ownerId).filter((c) => tenantId === void 0 || (c.tenantId ?? null) === tenantId).sort((a, b) => b.updatedAt - a.updatedAt);
8452
8928
  const conversations = [];
8453
8929
  for (const summary of summaries) {
8454
8930
  const loaded = await this.readConversationFile(summary.fileName);
@@ -8458,14 +8934,15 @@ var FileConversationStore = class {
8458
8934
  }
8459
8935
  return conversations;
8460
8936
  }
8461
- async listSummaries(ownerId) {
8937
+ async listSummaries(ownerId, tenantId) {
8462
8938
  await this.ensureLoaded();
8463
- return Array.from(this.conversations.values()).filter((c) => !ownerId || c.ownerId === ownerId).sort((a, b) => b.updatedAt - a.updatedAt).map((c) => ({
8939
+ return Array.from(this.conversations.values()).filter((c) => !ownerId || c.ownerId === ownerId).filter((c) => tenantId === void 0 || (c.tenantId ?? null) === tenantId).sort((a, b) => b.updatedAt - a.updatedAt).map((c) => ({
8464
8940
  conversationId: c.conversationId,
8465
8941
  title: c.title,
8466
8942
  updatedAt: c.updatedAt,
8467
8943
  createdAt: c.createdAt,
8468
8944
  ownerId: c.ownerId,
8945
+ tenantId: c.tenantId,
8469
8946
  parentConversationId: c.parentConversationId,
8470
8947
  messageCount: c.messageCount,
8471
8948
  hasPendingApprovals: c.hasPendingApprovals,
@@ -8480,7 +8957,7 @@ var FileConversationStore = class {
8480
8957
  }
8481
8958
  return await this.readConversationFile(summary.fileName);
8482
8959
  }
8483
- async create(ownerId = DEFAULT_OWNER, title) {
8960
+ async create(ownerId = DEFAULT_OWNER, title, tenantId = null) {
8484
8961
  await this.ensureLoaded();
8485
8962
  const now2 = Date.now();
8486
8963
  const conversation = {
@@ -8488,7 +8965,7 @@ var FileConversationStore = class {
8488
8965
  title: normalizeTitle(title),
8489
8966
  messages: [],
8490
8967
  ownerId,
8491
- tenantId: null,
8968
+ tenantId,
8492
8969
  createdAt: now2,
8493
8970
  updatedAt: now2
8494
8971
  };
@@ -8525,7 +9002,7 @@ var FileConversationStore = class {
8525
9002
  if (removed) {
8526
9003
  this.writing = this.writing.then(async () => {
8527
9004
  if (existing) {
8528
- await rm4(resolve13(conversationsDir, existing.fileName), { force: true });
9005
+ await rm4(resolve14(conversationsDir, existing.fileName), { force: true });
8529
9006
  }
8530
9007
  await this.writeIndex();
8531
9008
  });
@@ -8547,7 +9024,7 @@ var FileConversationStore = class {
8547
9024
  const summary = this.conversations.get(conversationId);
8548
9025
  if (!summary) return void 0;
8549
9026
  const { conversationsDir } = await this.resolvePaths();
8550
- const filePath = resolve13(conversationsDir, summary.fileName);
9027
+ const filePath = resolve14(conversationsDir, summary.fileName);
8551
9028
  let result;
8552
9029
  this.writing = this.writing.then(async () => {
8553
9030
  const conv = await this.readConversationFile(summary.fileName);
@@ -8586,7 +9063,7 @@ var FileStateStore = class {
8586
9063
  workingDir: this.workingDir,
8587
9064
  agentId: this.agentId
8588
9065
  });
8589
- this.filePath = resolve13(getAgentStoreDirectory(identity), LOCAL_STATE_FILE);
9066
+ this.filePath = resolve14(getAgentStoreDirectory(identity), LOCAL_STATE_FILE);
8590
9067
  }
8591
9068
  isExpired(state) {
8592
9069
  return typeof this.ttlMs === "number" && Date.now() - state.updatedAt > this.ttlMs;
@@ -8598,7 +9075,7 @@ var FileStateStore = class {
8598
9075
  }
8599
9076
  this.loaded = true;
8600
9077
  try {
8601
- const raw = await readFile11(this.filePath, "utf8");
9078
+ const raw = await readFile12(this.filePath, "utf8");
8602
9079
  const parsed = JSON.parse(raw);
8603
9080
  for (const state of parsed.states ?? []) {
8604
9081
  this.states.set(state.runId, state);
@@ -8731,10 +9208,10 @@ var KeyValueConversationStoreBase = class {
8731
9208
  return void 0;
8732
9209
  }
8733
9210
  }
8734
- async list(ownerId) {
9211
+ async list(ownerId, tenantId) {
8735
9212
  const kv = await this.client();
8736
9213
  if (!kv) {
8737
- return await this.memoryFallback.list(ownerId);
9214
+ return await this.memoryFallback.list(ownerId, tenantId);
8738
9215
  }
8739
9216
  if (!ownerId) {
8740
9217
  return [];
@@ -8747,16 +9224,19 @@ var KeyValueConversationStoreBase = class {
8747
9224
  for (const raw of rawValues) {
8748
9225
  if (!raw) continue;
8749
9226
  try {
8750
- conversations.push(JSON.parse(raw));
9227
+ const conv = JSON.parse(raw);
9228
+ if (tenantId === void 0 || conv.tenantId === tenantId) {
9229
+ conversations.push(conv);
9230
+ }
8751
9231
  } catch {
8752
9232
  }
8753
9233
  }
8754
9234
  return conversations.sort((a, b) => b.updatedAt - a.updatedAt);
8755
9235
  }
8756
- async listSummaries(ownerId) {
9236
+ async listSummaries(ownerId, tenantId) {
8757
9237
  const kv = await this.client();
8758
9238
  if (!kv) {
8759
- return await this.memoryFallback.listSummaries(ownerId);
9239
+ return await this.memoryFallback.listSummaries(ownerId, tenantId);
8760
9240
  }
8761
9241
  if (!ownerId) {
8762
9242
  return [];
@@ -8770,13 +9250,14 @@ var KeyValueConversationStoreBase = class {
8770
9250
  if (!raw) continue;
8771
9251
  try {
8772
9252
  const meta = JSON.parse(raw);
8773
- if (meta.ownerId === ownerId) {
9253
+ if (meta.ownerId === ownerId && (tenantId === void 0 || (meta.tenantId ?? null) === tenantId)) {
8774
9254
  summaries.push({
8775
9255
  conversationId: meta.conversationId,
8776
9256
  title: meta.title,
8777
9257
  updatedAt: meta.updatedAt,
8778
9258
  createdAt: meta.createdAt,
8779
9259
  ownerId: meta.ownerId,
9260
+ tenantId: meta.tenantId,
8780
9261
  parentConversationId: meta.parentConversationId,
8781
9262
  messageCount: meta.messageCount,
8782
9263
  hasPendingApprovals: meta.hasPendingApprovals,
@@ -8803,14 +9284,14 @@ var KeyValueConversationStoreBase = class {
8803
9284
  return void 0;
8804
9285
  }
8805
9286
  }
8806
- async create(ownerId = DEFAULT_OWNER, title) {
9287
+ async create(ownerId = DEFAULT_OWNER, title, tenantId = null) {
8807
9288
  const now2 = Date.now();
8808
9289
  const conversation = {
8809
9290
  conversationId: globalThis.crypto?.randomUUID?.() ?? `${now2}-${Math.random()}`,
8810
9291
  title: normalizeTitle(title),
8811
9292
  messages: [],
8812
9293
  ownerId,
8813
- tenantId: null,
9294
+ tenantId,
8814
9295
  createdAt: now2,
8815
9296
  updatedAt: now2
8816
9297
  };
@@ -8839,6 +9320,7 @@ var KeyValueConversationStoreBase = class {
8839
9320
  updatedAt: nextConversation.updatedAt,
8840
9321
  createdAt: nextConversation.createdAt,
8841
9322
  ownerId: nextConversation.ownerId,
9323
+ tenantId: nextConversation.tenantId,
8842
9324
  parentConversationId: nextConversation.parentConversationId,
8843
9325
  messageCount: nextConversation.messages.length,
8844
9326
  hasPendingApprovals: Array.isArray(nextConversation.pendingApprovals) && nextConversation.pendingApprovals.length > 0,
@@ -9298,6 +9780,32 @@ var createConversationStore = (config, options) => {
9298
9780
  return new InMemoryConversationStore(ttl);
9299
9781
  };
9300
9782
 
9783
+ // src/tenant-token.ts
9784
+ import { jwtVerify } from "jose";
9785
+ async function verifyTenantToken(signingKey, token) {
9786
+ try {
9787
+ const secret = new TextEncoder().encode(signingKey);
9788
+ const { payload } = await jwtVerify(token, secret, {
9789
+ algorithms: ["HS256"]
9790
+ });
9791
+ const tenantId = payload.sub;
9792
+ if (!tenantId || typeof tenantId !== "string") {
9793
+ return void 0;
9794
+ }
9795
+ const metadata = extractMetadata(payload);
9796
+ return { tenantId, metadata };
9797
+ } catch {
9798
+ return void 0;
9799
+ }
9800
+ }
9801
+ function extractMetadata(payload) {
9802
+ const meta = payload.meta;
9803
+ if (meta && typeof meta === "object" && !Array.isArray(meta)) {
9804
+ return meta;
9805
+ }
9806
+ return void 0;
9807
+ }
9808
+
9301
9809
  // src/index.ts
9302
9810
  import { defineTool as defineTool9 } from "@poncho-ai/sdk";
9303
9811
  export {
@@ -9328,6 +9836,7 @@ export {
9328
9836
  createReminderStore,
9329
9837
  createReminderTools,
9330
9838
  createSearchTools,
9839
+ createSecretsStore,
9331
9840
  createSkillTools,
9332
9841
  createStateStore,
9333
9842
  createSubagentTools,
@@ -9362,10 +9871,12 @@ export {
9362
9871
  renderAgentPrompt,
9363
9872
  resolveAgentIdentity,
9364
9873
  resolveCompactionConfig,
9874
+ resolveEnv,
9365
9875
  resolveMemoryConfig,
9366
9876
  resolveSkillDirs,
9367
9877
  resolveStateConfig,
9368
9878
  slugifyStorageComponent,
9369
9879
  startOpenAICodexDeviceAuth,
9880
+ verifyTenantToken,
9370
9881
  writeOpenAICodexSession
9371
9882
  };