@poncho-ai/harness 0.34.1 → 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/.turbo/turbo-build.log +11 -11
- package/.turbo/turbo-lint.log +6 -0
- package/.turbo/turbo-test.log +11931 -0
- package/CHANGELOG.md +19 -0
- package/dist/index.d.ts +77 -9
- package/dist/index.js +610 -105
- package/package.json +3 -2
- package/src/config.ts +6 -0
- package/src/harness.ts +61 -5
- package/src/index.ts +2 -0
- package/src/mcp.ts +140 -9
- package/src/memory.ts +33 -13
- package/src/reminder-store.ts +6 -0
- package/src/reminder-tools.ts +15 -2
- package/src/secrets-store.ts +252 -0
- package/src/state.ts +41 -19
- package/src/subagent-manager.ts +1 -0
- package/src/subagent-tools.ts +1 -0
- package/src/telemetry.ts +5 -1
- package/src/tenant-token.ts +42 -0
package/dist/index.js
CHANGED
|
@@ -1450,6 +1450,112 @@ Available memory tools:
|
|
|
1450
1450
|
- \`memory_main_write\` \u2014 overwrite the entire memory document
|
|
1451
1451
|
- \`memory_main_edit\` \u2014 edit memory via exact string replacement (\`old_str\` / \`new_str\`)
|
|
1452
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.
|
|
1453
1559
|
`,
|
|
1454
1560
|
"configuration": `# Configuration & Security
|
|
1455
1561
|
|
|
@@ -1532,6 +1638,14 @@ export default {
|
|
|
1532
1638
|
// When auth.required is true:
|
|
1533
1639
|
// - Web UI: users enter the passphrase (value of PONCHO_AUTH_TOKEN env var)
|
|
1534
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
|
+
// },
|
|
1535
1649
|
|
|
1536
1650
|
// Model provider API key env var overrides (optional)
|
|
1537
1651
|
providers: {
|
|
@@ -2102,8 +2216,8 @@ var ponchoDocsTool = defineTool({
|
|
|
2102
2216
|
|
|
2103
2217
|
// src/harness.ts
|
|
2104
2218
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
2105
|
-
import { readFile as
|
|
2106
|
-
import { resolve as
|
|
2219
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
2220
|
+
import { resolve as resolve13 } from "path";
|
|
2107
2221
|
import { defineTool as defineTool8, getTextContent as getTextContent2 } from "@poncho-ai/sdk";
|
|
2108
2222
|
|
|
2109
2223
|
// src/upload-store.ts
|
|
@@ -2616,20 +2730,25 @@ var InMemoryMemoryStore = class {
|
|
|
2616
2730
|
var FileMainMemoryStore = class {
|
|
2617
2731
|
workingDir;
|
|
2618
2732
|
filePath = "";
|
|
2733
|
+
customRelPath;
|
|
2619
2734
|
ttlMs;
|
|
2620
2735
|
loaded = false;
|
|
2621
2736
|
writing = Promise.resolve();
|
|
2622
2737
|
mainMemory = { ...DEFAULT_MAIN_MEMORY };
|
|
2623
|
-
constructor(workingDir, ttlSeconds) {
|
|
2738
|
+
constructor(workingDir, ttlSeconds, customRelPath) {
|
|
2624
2739
|
this.workingDir = workingDir;
|
|
2625
2740
|
this.ttlMs = typeof ttlSeconds === "number" ? ttlSeconds * 1e3 : void 0;
|
|
2741
|
+
this.customRelPath = customRelPath;
|
|
2626
2742
|
}
|
|
2627
2743
|
async ensureFilePath() {
|
|
2628
2744
|
if (this.filePath) {
|
|
2629
2745
|
return;
|
|
2630
2746
|
}
|
|
2631
2747
|
const identity = await ensureAgentIdentity(this.workingDir);
|
|
2632
|
-
this.filePath = resolve6(
|
|
2748
|
+
this.filePath = resolve6(
|
|
2749
|
+
getAgentStoreDirectory(identity),
|
|
2750
|
+
this.customRelPath ?? LOCAL_MEMORY_FILE
|
|
2751
|
+
);
|
|
2633
2752
|
}
|
|
2634
2753
|
isExpired(updatedAt) {
|
|
2635
2754
|
return typeof this.ttlMs === "number" && Date.now() - updatedAt > this.ttlMs;
|
|
@@ -2725,7 +2844,15 @@ var createMemoryStore = (agentId, config, options) => {
|
|
|
2725
2844
|
const provider = config?.provider ?? "local";
|
|
2726
2845
|
const ttl = config?.ttl;
|
|
2727
2846
|
const workingDir = options?.workingDir ?? process.cwd();
|
|
2847
|
+
const tenantId = options?.tenantId;
|
|
2728
2848
|
if (provider === "local") {
|
|
2849
|
+
if (tenantId) {
|
|
2850
|
+
return new FileMainMemoryStore(
|
|
2851
|
+
workingDir,
|
|
2852
|
+
ttl,
|
|
2853
|
+
`tenants/${slugifyStorageComponent(tenantId)}/${LOCAL_MEMORY_FILE}`
|
|
2854
|
+
);
|
|
2855
|
+
}
|
|
2729
2856
|
return new FileMainMemoryStore(workingDir, ttl);
|
|
2730
2857
|
}
|
|
2731
2858
|
if (provider === "memory") {
|
|
@@ -2733,7 +2860,8 @@ var createMemoryStore = (agentId, config, options) => {
|
|
|
2733
2860
|
}
|
|
2734
2861
|
const kv = createRawKVStore(config);
|
|
2735
2862
|
if (kv) {
|
|
2736
|
-
const
|
|
2863
|
+
const base = `poncho:${STORAGE_SCHEMA_VERSION}:${slugifyStorageComponent(agentId)}`;
|
|
2864
|
+
const storageKey = tenantId ? `${base}:t:${slugifyStorageComponent(tenantId)}:memory:main` : `${base}:memory:main`;
|
|
2737
2865
|
return new KVBackedMemoryStore(kv, storageKey, ttl);
|
|
2738
2866
|
}
|
|
2739
2867
|
return new InMemoryMemoryStore(ttl);
|
|
@@ -2768,6 +2896,7 @@ var buildRecallSnippet = (content, query, maxChars = 360) => {
|
|
|
2768
2896
|
return content.slice(start, end);
|
|
2769
2897
|
};
|
|
2770
2898
|
var createMemoryTools = (store, options) => {
|
|
2899
|
+
const resolveStore = typeof store === "function" ? store : () => store;
|
|
2771
2900
|
const maxRecallConversations = Math.max(1, options?.maxRecallConversations ?? 20);
|
|
2772
2901
|
return [
|
|
2773
2902
|
defineTool2({
|
|
@@ -2778,8 +2907,8 @@ var createMemoryTools = (store, options) => {
|
|
|
2778
2907
|
properties: {},
|
|
2779
2908
|
additionalProperties: false
|
|
2780
2909
|
},
|
|
2781
|
-
handler: async () => {
|
|
2782
|
-
const memory = await
|
|
2910
|
+
handler: async (_input, context) => {
|
|
2911
|
+
const memory = await resolveStore(context).getMainMemory();
|
|
2783
2912
|
return { memory };
|
|
2784
2913
|
}
|
|
2785
2914
|
}),
|
|
@@ -2797,12 +2926,12 @@ var createMemoryTools = (store, options) => {
|
|
|
2797
2926
|
required: ["content"],
|
|
2798
2927
|
additionalProperties: false
|
|
2799
2928
|
},
|
|
2800
|
-
handler: async (input) => {
|
|
2929
|
+
handler: async (input, context) => {
|
|
2801
2930
|
const content = typeof input.content === "string" ? input.content.trim() : "";
|
|
2802
2931
|
if (!content) {
|
|
2803
2932
|
throw new Error("content is required");
|
|
2804
2933
|
}
|
|
2805
|
-
const memory = await
|
|
2934
|
+
const memory = await resolveStore(context).updateMainMemory({ content });
|
|
2806
2935
|
return { ok: true, memory };
|
|
2807
2936
|
}
|
|
2808
2937
|
}),
|
|
@@ -2824,13 +2953,13 @@ var createMemoryTools = (store, options) => {
|
|
|
2824
2953
|
required: ["old_str", "new_str"],
|
|
2825
2954
|
additionalProperties: false
|
|
2826
2955
|
},
|
|
2827
|
-
handler: async (input) => {
|
|
2956
|
+
handler: async (input, context) => {
|
|
2828
2957
|
const oldStr = typeof input.old_str === "string" ? input.old_str : "";
|
|
2829
2958
|
const newStr = typeof input.new_str === "string" ? input.new_str : "";
|
|
2830
2959
|
if (!oldStr) {
|
|
2831
2960
|
throw new Error("old_str must not be empty.");
|
|
2832
2961
|
}
|
|
2833
|
-
const current = await
|
|
2962
|
+
const current = await resolveStore(context).getMainMemory();
|
|
2834
2963
|
const content = current.content;
|
|
2835
2964
|
const first = content.indexOf(oldStr);
|
|
2836
2965
|
if (first === -1) {
|
|
@@ -2845,7 +2974,7 @@ var createMemoryTools = (store, options) => {
|
|
|
2845
2974
|
);
|
|
2846
2975
|
}
|
|
2847
2976
|
const newContent = content.slice(0, first) + newStr + content.slice(first + oldStr.length);
|
|
2848
|
-
const memory = await
|
|
2977
|
+
const memory = await resolveStore(context).updateMainMemory({ content: newContent });
|
|
2849
2978
|
return { ok: true, memory };
|
|
2850
2979
|
}
|
|
2851
2980
|
}),
|
|
@@ -3208,7 +3337,8 @@ var InMemoryReminderStore = class {
|
|
|
3208
3337
|
status: "pending",
|
|
3209
3338
|
createdAt: Date.now(),
|
|
3210
3339
|
conversationId: input.conversationId,
|
|
3211
|
-
ownerId: input.ownerId
|
|
3340
|
+
ownerId: input.ownerId,
|
|
3341
|
+
tenantId: input.tenantId
|
|
3212
3342
|
};
|
|
3213
3343
|
this.reminders = pruneStale(this.reminders);
|
|
3214
3344
|
this.reminders.push(reminder);
|
|
@@ -3267,7 +3397,8 @@ var FileReminderStore = class {
|
|
|
3267
3397
|
status: "pending",
|
|
3268
3398
|
createdAt: Date.now(),
|
|
3269
3399
|
conversationId: input.conversationId,
|
|
3270
|
-
ownerId: input.ownerId
|
|
3400
|
+
ownerId: input.ownerId,
|
|
3401
|
+
tenantId: input.tenantId
|
|
3271
3402
|
};
|
|
3272
3403
|
let reminders = await this.readAll();
|
|
3273
3404
|
reminders = pruneStale(reminders);
|
|
@@ -3393,6 +3524,171 @@ var createReminderStore = (agentId, config, options) => {
|
|
|
3393
3524
|
return new InMemoryReminderStore();
|
|
3394
3525
|
};
|
|
3395
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
|
+
|
|
3396
3692
|
// src/reminder-tools.ts
|
|
3397
3693
|
import { defineTool as defineTool4 } from "@poncho-ai/sdk";
|
|
3398
3694
|
var VALID_STATUSES2 = ["pending", "cancelled"];
|
|
@@ -3465,7 +3761,8 @@ var createReminderTools = (store) => [
|
|
|
3465
3761
|
task,
|
|
3466
3762
|
scheduledAt,
|
|
3467
3763
|
timezone,
|
|
3468
|
-
conversationId
|
|
3764
|
+
conversationId,
|
|
3765
|
+
tenantId: context.tenantId
|
|
3469
3766
|
});
|
|
3470
3767
|
return {
|
|
3471
3768
|
ok: true,
|
|
@@ -3493,8 +3790,11 @@ var createReminderTools = (store) => [
|
|
|
3493
3790
|
},
|
|
3494
3791
|
additionalProperties: false
|
|
3495
3792
|
},
|
|
3496
|
-
handler: async (input) => {
|
|
3793
|
+
handler: async (input, context) => {
|
|
3497
3794
|
let reminders = await store.list();
|
|
3795
|
+
if (context.tenantId) {
|
|
3796
|
+
reminders = reminders.filter((r) => r.tenantId === context.tenantId);
|
|
3797
|
+
}
|
|
3498
3798
|
const status = typeof input.status === "string" ? input.status : void 0;
|
|
3499
3799
|
if (status && VALID_STATUSES2.includes(status)) {
|
|
3500
3800
|
reminders = reminders.filter((r) => r.status === status);
|
|
@@ -3526,9 +3826,16 @@ var createReminderTools = (store) => [
|
|
|
3526
3826
|
required: ["id"],
|
|
3527
3827
|
additionalProperties: false
|
|
3528
3828
|
},
|
|
3529
|
-
handler: async (input) => {
|
|
3829
|
+
handler: async (input, context) => {
|
|
3530
3830
|
const id = typeof input.id === "string" ? input.id.trim() : "";
|
|
3531
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
|
+
}
|
|
3532
3839
|
const cancelled = await store.cancel(id);
|
|
3533
3840
|
return {
|
|
3534
3841
|
ok: true,
|
|
@@ -3751,6 +4058,14 @@ var LocalMcpBridge = class {
|
|
|
3751
4058
|
toolCatalog = /* @__PURE__ */ new Map();
|
|
3752
4059
|
unavailableServers = /* @__PURE__ */ new Map();
|
|
3753
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
|
+
}
|
|
3754
4069
|
constructor(config) {
|
|
3755
4070
|
this.remoteServers = (config?.mcp ?? []).filter(
|
|
3756
4071
|
(entry) => typeof entry.url === "string"
|
|
@@ -3790,8 +4105,11 @@ var LocalMcpBridge = class {
|
|
|
3790
4105
|
}
|
|
3791
4106
|
console.info(`[poncho][mcp] ${line}`);
|
|
3792
4107
|
}
|
|
4108
|
+
/** Set of servers where discovery was deferred (no default token, has env resolver). */
|
|
4109
|
+
deferredDiscoveryServers = /* @__PURE__ */ new Set();
|
|
3793
4110
|
async discoverTools() {
|
|
3794
4111
|
this.toolCatalog.clear();
|
|
4112
|
+
this.deferredDiscoveryServers.clear();
|
|
3795
4113
|
for (const remoteServer of this.remoteServers) {
|
|
3796
4114
|
const name = this.getServerName(remoteServer);
|
|
3797
4115
|
if (this.unavailableServers.has(name)) {
|
|
@@ -3812,6 +4130,11 @@ var LocalMcpBridge = class {
|
|
|
3812
4130
|
} catch (error) {
|
|
3813
4131
|
const message = error instanceof Error ? error.message : String(error);
|
|
3814
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
|
+
}
|
|
3815
4138
|
this.authFailedServers.add(name);
|
|
3816
4139
|
this.log("warn", "auth.failed", {
|
|
3817
4140
|
server: name,
|
|
@@ -3824,6 +4147,57 @@ var LocalMcpBridge = class {
|
|
|
3824
4147
|
}
|
|
3825
4148
|
}
|
|
3826
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
|
+
}
|
|
3827
4201
|
async startLocalServers() {
|
|
3828
4202
|
this.unavailableServers.clear();
|
|
3829
4203
|
for (const server of this.remoteServers) {
|
|
@@ -3832,15 +4206,21 @@ var LocalMcpBridge = class {
|
|
|
3832
4206
|
if (tokenEnv) {
|
|
3833
4207
|
const token = process.env[tokenEnv];
|
|
3834
4208
|
if (!token || token.trim().length === 0) {
|
|
3835
|
-
this.
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
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", {
|
|
3840
4221
|
server: name,
|
|
3841
4222
|
tokenEnv
|
|
3842
4223
|
});
|
|
3843
|
-
continue;
|
|
3844
4224
|
}
|
|
3845
4225
|
}
|
|
3846
4226
|
this.rpcClients.set(
|
|
@@ -3904,6 +4284,29 @@ var LocalMcpBridge = class {
|
|
|
3904
4284
|
}
|
|
3905
4285
|
return output.sort();
|
|
3906
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
|
+
}
|
|
3907
4310
|
async loadTools(requestedPatterns) {
|
|
3908
4311
|
for (const [index, pattern] of requestedPatterns.entries()) {
|
|
3909
4312
|
validateMcpPattern(pattern, `requestedPatterns[${index}]`);
|
|
@@ -3935,7 +4338,7 @@ var LocalMcpBridge = class {
|
|
|
3935
4338
|
const selectedDescriptors = discovered.filter(
|
|
3936
4339
|
(descriptor) => selectedRawNames.has(descriptor.name)
|
|
3937
4340
|
);
|
|
3938
|
-
tools.push(...this.toToolDefinitions(serverName, selectedDescriptors, client));
|
|
4341
|
+
tools.push(...this.toToolDefinitions(serverName, selectedDescriptors, client, server));
|
|
3939
4342
|
}
|
|
3940
4343
|
this.log("info", "tools.selected", {
|
|
3941
4344
|
requestedPatternCount: requestedPatterns.length,
|
|
@@ -3945,7 +4348,7 @@ var LocalMcpBridge = class {
|
|
|
3945
4348
|
});
|
|
3946
4349
|
return tools;
|
|
3947
4350
|
}
|
|
3948
|
-
toToolDefinitions(serverName, tools, client) {
|
|
4351
|
+
toToolDefinitions(serverName, tools, client, server) {
|
|
3949
4352
|
return tools.map((tool) => ({
|
|
3950
4353
|
name: `${serverName}/${tool.name}`,
|
|
3951
4354
|
description: tool.description ?? `MCP tool ${tool.name} from ${serverName}`,
|
|
@@ -3953,9 +4356,23 @@ var LocalMcpBridge = class {
|
|
|
3953
4356
|
type: "object",
|
|
3954
4357
|
properties: {}
|
|
3955
4358
|
},
|
|
3956
|
-
handler: async (input) => {
|
|
4359
|
+
handler: async (input, context) => {
|
|
3957
4360
|
try {
|
|
3958
|
-
|
|
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);
|
|
3959
4376
|
} catch (error) {
|
|
3960
4377
|
if (error instanceof McpHttpError && error.status === 401) {
|
|
3961
4378
|
this.authFailedServers.add(serverName);
|
|
@@ -3986,8 +4403,8 @@ import { createAnthropic } from "@ai-sdk/anthropic";
|
|
|
3986
4403
|
|
|
3987
4404
|
// src/openai-codex-auth.ts
|
|
3988
4405
|
import { homedir as homedir3 } from "os";
|
|
3989
|
-
import { dirname as
|
|
3990
|
-
import { mkdir as
|
|
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";
|
|
3991
4408
|
var OPENAI_CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
3992
4409
|
var OPENAI_AUTH_ISSUER = "https://auth.openai.com";
|
|
3993
4410
|
var REFRESH_TOKEN_GRACE_MS = 5 * 60 * 1e3;
|
|
@@ -4019,14 +4436,14 @@ var getOpenAICodexAuthFilePath = (config) => {
|
|
|
4019
4436
|
const env = defaultedConfig(config);
|
|
4020
4437
|
const fromEnv = process.env[env.authFilePathEnv];
|
|
4021
4438
|
if (typeof fromEnv === "string" && fromEnv.trim().length > 0) {
|
|
4022
|
-
return
|
|
4439
|
+
return resolve10(fromEnv);
|
|
4023
4440
|
}
|
|
4024
|
-
return
|
|
4441
|
+
return resolve10(homedir3(), ".poncho", "auth", "openai-codex.json");
|
|
4025
4442
|
};
|
|
4026
4443
|
var readOpenAICodexSession = async (config) => {
|
|
4027
4444
|
const filePath = getOpenAICodexAuthFilePath(config);
|
|
4028
4445
|
try {
|
|
4029
|
-
const content = await
|
|
4446
|
+
const content = await readFile9(filePath, "utf8");
|
|
4030
4447
|
const parsed = JSON.parse(content);
|
|
4031
4448
|
if (typeof parsed.refreshToken !== "string" || parsed.refreshToken.length === 0) {
|
|
4032
4449
|
return void 0;
|
|
@@ -4043,12 +4460,12 @@ var readOpenAICodexSession = async (config) => {
|
|
|
4043
4460
|
};
|
|
4044
4461
|
var writeOpenAICodexSession = async (session, config) => {
|
|
4045
4462
|
const filePath = getOpenAICodexAuthFilePath(config);
|
|
4046
|
-
await
|
|
4463
|
+
await mkdir7(dirname6(filePath), { recursive: true });
|
|
4047
4464
|
const payload = {
|
|
4048
4465
|
...session,
|
|
4049
4466
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4050
4467
|
};
|
|
4051
|
-
await
|
|
4468
|
+
await writeFile8(filePath, `${JSON.stringify(payload, null, 2)}
|
|
4052
4469
|
`, "utf8");
|
|
4053
4470
|
await chmod(filePath, 384);
|
|
4054
4471
|
};
|
|
@@ -4361,8 +4778,8 @@ var createModelProvider = (provider, config) => {
|
|
|
4361
4778
|
};
|
|
4362
4779
|
|
|
4363
4780
|
// src/skill-context.ts
|
|
4364
|
-
import { readFile as
|
|
4365
|
-
import { dirname as
|
|
4781
|
+
import { readFile as readFile10, readdir as readdir2, stat } from "fs/promises";
|
|
4782
|
+
import { dirname as dirname7, resolve as resolve11, normalize } from "path";
|
|
4366
4783
|
import YAML3 from "yaml";
|
|
4367
4784
|
var DEFAULT_SKILL_DIRS = ["skills"];
|
|
4368
4785
|
var resolveSkillDirs = (workingDir, extraPaths) => {
|
|
@@ -4374,7 +4791,7 @@ var resolveSkillDirs = (workingDir, extraPaths) => {
|
|
|
4374
4791
|
}
|
|
4375
4792
|
}
|
|
4376
4793
|
}
|
|
4377
|
-
return dirs.map((d) =>
|
|
4794
|
+
return dirs.map((d) => resolve11(workingDir, d));
|
|
4378
4795
|
};
|
|
4379
4796
|
var FRONTMATTER_PATTERN3 = /^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/;
|
|
4380
4797
|
var asRecord2 = (value) => typeof value === "object" && value !== null ? value : {};
|
|
@@ -4443,7 +4860,7 @@ var collectSkillManifests = async (directory) => {
|
|
|
4443
4860
|
const entries = await readdir2(directory, { withFileTypes: true });
|
|
4444
4861
|
const files = [];
|
|
4445
4862
|
for (const entry of entries) {
|
|
4446
|
-
const fullPath =
|
|
4863
|
+
const fullPath = resolve11(directory, entry.name);
|
|
4447
4864
|
let isDir = entry.isDirectory();
|
|
4448
4865
|
let isFile = entry.isFile();
|
|
4449
4866
|
if (entry.isSymbolicLink()) {
|
|
@@ -4478,13 +4895,13 @@ var loadSkillMetadata = async (workingDir, extraSkillPaths) => {
|
|
|
4478
4895
|
const seen = /* @__PURE__ */ new Set();
|
|
4479
4896
|
for (const manifest of allManifests) {
|
|
4480
4897
|
try {
|
|
4481
|
-
const content = await
|
|
4898
|
+
const content = await readFile10(manifest, "utf8");
|
|
4482
4899
|
const parsed = parseSkillFrontmatter(content);
|
|
4483
4900
|
if (parsed && !seen.has(parsed.name)) {
|
|
4484
4901
|
seen.add(parsed.name);
|
|
4485
4902
|
skills.push({
|
|
4486
4903
|
...parsed,
|
|
4487
|
-
skillDir:
|
|
4904
|
+
skillDir: dirname7(manifest),
|
|
4488
4905
|
skillPath: manifest
|
|
4489
4906
|
});
|
|
4490
4907
|
}
|
|
@@ -4523,7 +4940,7 @@ ${xmlSkills}
|
|
|
4523
4940
|
};
|
|
4524
4941
|
var escapeXml = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
4525
4942
|
var loadSkillInstructions = async (skill) => {
|
|
4526
|
-
const content = await
|
|
4943
|
+
const content = await readFile10(skill.skillPath, "utf8");
|
|
4527
4944
|
const match = content.match(FRONTMATTER_PATTERN3);
|
|
4528
4945
|
return match ? match[2].trim() : content.trim();
|
|
4529
4946
|
};
|
|
@@ -4532,11 +4949,11 @@ var readSkillResource = async (skill, relativePath) => {
|
|
|
4532
4949
|
if (normalized.startsWith("..") || normalized.startsWith("/")) {
|
|
4533
4950
|
throw new Error("Path must be relative and within the skill directory");
|
|
4534
4951
|
}
|
|
4535
|
-
const fullPath =
|
|
4952
|
+
const fullPath = resolve11(skill.skillDir, normalized);
|
|
4536
4953
|
if (!fullPath.startsWith(skill.skillDir)) {
|
|
4537
4954
|
throw new Error("Path escapes the skill directory");
|
|
4538
4955
|
}
|
|
4539
|
-
return await
|
|
4956
|
+
return await readFile10(fullPath, "utf8");
|
|
4540
4957
|
};
|
|
4541
4958
|
var MAX_INSTRUCTIONS_PER_SKILL = 1200;
|
|
4542
4959
|
var loadSkillContext = async (workingDir) => {
|
|
@@ -4655,7 +5072,7 @@ function convertSchema(schema) {
|
|
|
4655
5072
|
// src/skill-tools.ts
|
|
4656
5073
|
import { defineTool as defineTool5 } from "@poncho-ai/sdk";
|
|
4657
5074
|
import { access as access2, readdir as readdir3, stat as stat2 } from "fs/promises";
|
|
4658
|
-
import { extname, normalize as normalize2, resolve as
|
|
5075
|
+
import { extname, normalize as normalize2, resolve as resolve12, sep as sep2 } from "path";
|
|
4659
5076
|
import { pathToFileURL } from "url";
|
|
4660
5077
|
import { createJiti as createJiti2 } from "jiti";
|
|
4661
5078
|
var createSkillTools = (skills, options) => {
|
|
@@ -4913,7 +5330,7 @@ var collectScriptFiles = async (directory) => {
|
|
|
4913
5330
|
if (entry.name === "node_modules") {
|
|
4914
5331
|
continue;
|
|
4915
5332
|
}
|
|
4916
|
-
const fullPath =
|
|
5333
|
+
const fullPath = resolve12(directory, entry.name);
|
|
4917
5334
|
let isDir = entry.isDirectory();
|
|
4918
5335
|
let isFile = entry.isFile();
|
|
4919
5336
|
if (entry.isSymbolicLink()) {
|
|
@@ -4952,8 +5369,8 @@ var normalizeScriptPolicyPath = (relativePath) => {
|
|
|
4952
5369
|
};
|
|
4953
5370
|
var resolveScriptPath = (baseDir, relativePath, containmentDir) => {
|
|
4954
5371
|
const normalized = normalizeScriptPolicyPath(relativePath);
|
|
4955
|
-
const fullPath =
|
|
4956
|
-
const boundary =
|
|
5372
|
+
const fullPath = resolve12(baseDir, normalized);
|
|
5373
|
+
const boundary = resolve12(containmentDir ?? baseDir);
|
|
4957
5374
|
if (!fullPath.startsWith(`${boundary}${sep2}`) && fullPath !== boundary) {
|
|
4958
5375
|
throw new Error("Script path must stay inside the allowed directory");
|
|
4959
5376
|
}
|
|
@@ -5059,8 +5476,8 @@ function applyRateLimitCooldown(retryAfterHeader) {
|
|
|
5059
5476
|
async function runWithSearchThrottle(fn) {
|
|
5060
5477
|
const previous = searchQueue;
|
|
5061
5478
|
let release;
|
|
5062
|
-
searchQueue = new Promise((
|
|
5063
|
-
release =
|
|
5479
|
+
searchQueue = new Promise((resolve15) => {
|
|
5480
|
+
release = resolve15;
|
|
5064
5481
|
});
|
|
5065
5482
|
await previous.catch(() => {
|
|
5066
5483
|
});
|
|
@@ -5266,7 +5683,8 @@ var createSubagentTools = (manager) => [
|
|
|
5266
5683
|
const { subagentId } = await manager.spawn({
|
|
5267
5684
|
task: task.trim(),
|
|
5268
5685
|
parentConversationId: conversationId,
|
|
5269
|
-
ownerId
|
|
5686
|
+
ownerId,
|
|
5687
|
+
tenantId: context.tenantId
|
|
5270
5688
|
});
|
|
5271
5689
|
return { subagentId, status: "running" };
|
|
5272
5690
|
}
|
|
@@ -5351,8 +5769,12 @@ import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
|
5351
5769
|
|
|
5352
5770
|
// src/telemetry.ts
|
|
5353
5771
|
var MAX_FIELD_LENGTH = 200;
|
|
5772
|
+
var OMIT_FROM_LOG = /* @__PURE__ */ new Set(["continuationMessages", "_harnessMessages", "messages", "compactedHistory"]);
|
|
5354
5773
|
function sanitizeEventForLog(event) {
|
|
5355
|
-
return JSON.stringify(event, (
|
|
5774
|
+
return JSON.stringify(event, (key, value) => {
|
|
5775
|
+
if (OMIT_FROM_LOG.has(key) && Array.isArray(value)) {
|
|
5776
|
+
return `[${value.length} messages]`;
|
|
5777
|
+
}
|
|
5356
5778
|
if (typeof value === "string" && value.length > MAX_FIELD_LENGTH) {
|
|
5357
5779
|
return `${value.slice(0, 80)}...[${value.length} chars]`;
|
|
5358
5780
|
}
|
|
@@ -6002,8 +6424,11 @@ var AgentHarness = class _AgentHarness {
|
|
|
6002
6424
|
uploadStore;
|
|
6003
6425
|
skillContextWindow = "";
|
|
6004
6426
|
memoryStore;
|
|
6427
|
+
tenantMemoryStores = /* @__PURE__ */ new Map();
|
|
6428
|
+
memoryConfig;
|
|
6005
6429
|
todoStore;
|
|
6006
6430
|
reminderStore;
|
|
6431
|
+
secretsStore;
|
|
6007
6432
|
loadedConfig;
|
|
6008
6433
|
loadedSkills = [];
|
|
6009
6434
|
skillFingerprint = "";
|
|
@@ -6288,6 +6713,28 @@ var AgentHarness = class _AgentHarness {
|
|
|
6288
6713
|
if (!this.todoStore) return [];
|
|
6289
6714
|
return this.todoStore.get(conversationId);
|
|
6290
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
|
+
}
|
|
6291
6738
|
listActiveSkills() {
|
|
6292
6739
|
return [...this.activeSkillNames].sort();
|
|
6293
6740
|
}
|
|
@@ -6518,8 +6965,8 @@ var AgentHarness = class _AgentHarness {
|
|
|
6518
6965
|
return false;
|
|
6519
6966
|
}
|
|
6520
6967
|
try {
|
|
6521
|
-
const agentFilePath =
|
|
6522
|
-
const rawContent = await
|
|
6968
|
+
const agentFilePath = resolve13(this.workingDir, "AGENT.md");
|
|
6969
|
+
const rawContent = await readFile11(agentFilePath, "utf8");
|
|
6523
6970
|
if (rawContent === this.agentFileFingerprint) {
|
|
6524
6971
|
return false;
|
|
6525
6972
|
}
|
|
@@ -6588,8 +7035,8 @@ var AgentHarness = class _AgentHarness {
|
|
|
6588
7035
|
}
|
|
6589
7036
|
}
|
|
6590
7037
|
async initialize() {
|
|
6591
|
-
const agentFilePath =
|
|
6592
|
-
const agentRawContent = await
|
|
7038
|
+
const agentFilePath = resolve13(this.workingDir, "AGENT.md");
|
|
7039
|
+
const agentRawContent = await readFile11(agentFilePath, "utf8");
|
|
6593
7040
|
this.parsedAgent = parseAgentMarkdown(agentRawContent);
|
|
6594
7041
|
this.agentFileFingerprint = agentRawContent;
|
|
6595
7042
|
const identity = await ensureAgentIdentity(this.workingDir);
|
|
@@ -6613,6 +7060,7 @@ var AgentHarness = class _AgentHarness {
|
|
|
6613
7060
|
this.skillFingerprint = this.buildSkillFingerprint(skillMetadata);
|
|
6614
7061
|
this.registerSkillTools(skillMetadata);
|
|
6615
7062
|
const agentId = this.parsedAgent.frontmatter.id ?? this.parsedAgent.frontmatter.name;
|
|
7063
|
+
this.memoryConfig = memoryConfig ?? void 0;
|
|
6616
7064
|
if (memoryConfig?.enabled) {
|
|
6617
7065
|
this.memoryStore = createMemoryStore(
|
|
6618
7066
|
agentId,
|
|
@@ -6620,9 +7068,10 @@ var AgentHarness = class _AgentHarness {
|
|
|
6620
7068
|
{ workingDir: this.workingDir }
|
|
6621
7069
|
);
|
|
6622
7070
|
this.dispatcher.registerMany(
|
|
6623
|
-
createMemoryTools(
|
|
6624
|
-
|
|
6625
|
-
|
|
7071
|
+
createMemoryTools(
|
|
7072
|
+
(ctx) => this.getMemoryStore(ctx.tenantId) ?? this.memoryStore,
|
|
7073
|
+
{ maxRecallConversations: memoryConfig.maxRecallConversations }
|
|
7074
|
+
)
|
|
6626
7075
|
);
|
|
6627
7076
|
}
|
|
6628
7077
|
const stateConfig = resolveStateConfig(config);
|
|
@@ -6647,6 +7096,14 @@ var AgentHarness = class _AgentHarness {
|
|
|
6647
7096
|
);
|
|
6648
7097
|
});
|
|
6649
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
|
+
}
|
|
6650
7107
|
await bridge.startLocalServers();
|
|
6651
7108
|
await bridge.discoverTools();
|
|
6652
7109
|
await this.refreshMcpTools("initialize");
|
|
@@ -6680,14 +7137,14 @@ var AgentHarness = class _AgentHarness {
|
|
|
6680
7137
|
const filePath = pathResolve(stateDir, `${sessionId}.json`);
|
|
6681
7138
|
return {
|
|
6682
7139
|
async save(json) {
|
|
6683
|
-
const { mkdir:
|
|
6684
|
-
await
|
|
6685
|
-
await
|
|
7140
|
+
const { mkdir: mkdir9, writeFile: writeFile10 } = await import("fs/promises");
|
|
7141
|
+
await mkdir9(stateDir, { recursive: true });
|
|
7142
|
+
await writeFile10(filePath, json, "utf8");
|
|
6686
7143
|
},
|
|
6687
7144
|
async load() {
|
|
6688
|
-
const { readFile:
|
|
7145
|
+
const { readFile: readFile13 } = await import("fs/promises");
|
|
6689
7146
|
try {
|
|
6690
|
-
return await
|
|
7147
|
+
return await readFile13(filePath, "utf8");
|
|
6691
7148
|
} catch {
|
|
6692
7149
|
return void 0;
|
|
6693
7150
|
}
|
|
@@ -6753,7 +7210,7 @@ var AgentHarness = class _AgentHarness {
|
|
|
6753
7210
|
let browserMod;
|
|
6754
7211
|
try {
|
|
6755
7212
|
const { existsSync } = await import("fs");
|
|
6756
|
-
const { join, dirname:
|
|
7213
|
+
const { join, dirname: dirname9 } = await import("path");
|
|
6757
7214
|
const { pathToFileURL: pathToFileURL2 } = await import("url");
|
|
6758
7215
|
let searchDir = this.workingDir;
|
|
6759
7216
|
let entryPath;
|
|
@@ -6763,7 +7220,7 @@ var AgentHarness = class _AgentHarness {
|
|
|
6763
7220
|
entryPath = candidate;
|
|
6764
7221
|
break;
|
|
6765
7222
|
}
|
|
6766
|
-
const parent =
|
|
7223
|
+
const parent = dirname9(searchDir);
|
|
6767
7224
|
if (parent === searchDir) break;
|
|
6768
7225
|
searchDir = parent;
|
|
6769
7226
|
}
|
|
@@ -6839,7 +7296,8 @@ var AgentHarness = class _AgentHarness {
|
|
|
6839
7296
|
kind: SpanKind.INTERNAL,
|
|
6840
7297
|
attributes: {
|
|
6841
7298
|
"gen_ai.operation.name": "invoke_agent",
|
|
6842
|
-
...input.conversationId ? { "gen_ai.conversation.id": input.conversationId } : {}
|
|
7299
|
+
...input.conversationId ? { "gen_ai.conversation.id": input.conversationId } : {},
|
|
7300
|
+
...input.tenantId ? { "tenant.id": input.tenantId } : {}
|
|
6843
7301
|
}
|
|
6844
7302
|
});
|
|
6845
7303
|
const spanContext = trace.setSpan(otelContext.active(), rootSpan);
|
|
@@ -6885,10 +7343,18 @@ var AgentHarness = class _AgentHarness {
|
|
|
6885
7343
|
if (!this.parsedAgent) {
|
|
6886
7344
|
await this.initialize();
|
|
6887
7345
|
}
|
|
6888
|
-
const
|
|
7346
|
+
const activeMemoryStore = this.getMemoryStore(input.tenantId);
|
|
7347
|
+
const memoryPromise = activeMemoryStore ? activeMemoryStore.getMainMemory() : void 0;
|
|
6889
7348
|
const todosPromise = this.todoStore ? this.todoStore.get(input.conversationId ?? "__default__") : void 0;
|
|
6890
7349
|
await this.refreshAgentIfChanged();
|
|
6891
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
|
+
}
|
|
6892
7358
|
let agent = this.parsedAgent;
|
|
6893
7359
|
const runId = `run_${randomUUID3()}`;
|
|
6894
7360
|
const start = now();
|
|
@@ -7445,8 +7911,8 @@ ${textContent}` };
|
|
|
7445
7911
|
let timer;
|
|
7446
7912
|
nextPart = await Promise.race([
|
|
7447
7913
|
fullStreamIterator.next(),
|
|
7448
|
-
new Promise((
|
|
7449
|
-
timer = setTimeout(() =>
|
|
7914
|
+
new Promise((resolve15) => {
|
|
7915
|
+
timer = setTimeout(() => resolve15(null), effectiveTimeout);
|
|
7450
7916
|
})
|
|
7451
7917
|
]);
|
|
7452
7918
|
clearTimeout(timer);
|
|
@@ -7664,7 +8130,8 @@ ${textContent}` };
|
|
|
7664
8130
|
workingDir: this.workingDir,
|
|
7665
8131
|
parameters: input.parameters ?? {},
|
|
7666
8132
|
abortSignal: input.abortSignal,
|
|
7667
|
-
conversationId: input.conversationId
|
|
8133
|
+
conversationId: input.conversationId,
|
|
8134
|
+
tenantId: input.tenantId
|
|
7668
8135
|
};
|
|
7669
8136
|
const toolResultsForModel = [];
|
|
7670
8137
|
const richToolResults = [];
|
|
@@ -7766,7 +8233,7 @@ ${textContent}` };
|
|
|
7766
8233
|
const raced = await Promise.race([
|
|
7767
8234
|
this.dispatcher.executeBatch(approvedCalls, toolContext),
|
|
7768
8235
|
new Promise(
|
|
7769
|
-
(
|
|
8236
|
+
(resolve15) => setTimeout(() => resolve15(TOOL_DEADLINE_SENTINEL), toolDeadlineRemainingMs)
|
|
7770
8237
|
)
|
|
7771
8238
|
]);
|
|
7772
8239
|
if (raced === TOOL_DEADLINE_SENTINEL) {
|
|
@@ -8139,8 +8606,8 @@ ${this.skillFingerprint}`;
|
|
|
8139
8606
|
|
|
8140
8607
|
// src/state.ts
|
|
8141
8608
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
8142
|
-
import { mkdir as
|
|
8143
|
-
import { dirname as
|
|
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";
|
|
8144
8611
|
var DEFAULT_OWNER = "local-owner";
|
|
8145
8612
|
var LOCAL_STATE_FILE = "state.json";
|
|
8146
8613
|
var CONVERSATIONS_DIRECTORY = "conversations";
|
|
@@ -8156,9 +8623,9 @@ var toStoreIdentity = async ({
|
|
|
8156
8623
|
return { name: ensured.name, id: agentId };
|
|
8157
8624
|
};
|
|
8158
8625
|
var writeJsonAtomic4 = async (filePath, payload) => {
|
|
8159
|
-
await
|
|
8626
|
+
await mkdir8(dirname8(filePath), { recursive: true });
|
|
8160
8627
|
const tmpPath = `${filePath}.tmp`;
|
|
8161
|
-
await
|
|
8628
|
+
await writeFile9(tmpPath, JSON.stringify(payload, null, 2), "utf8");
|
|
8162
8629
|
await rename4(tmpPath, filePath);
|
|
8163
8630
|
};
|
|
8164
8631
|
var formatUtcTimestamp = (value) => new Date(value).toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
|
|
@@ -8254,18 +8721,19 @@ var InMemoryConversationStore = class {
|
|
|
8254
8721
|
}
|
|
8255
8722
|
}
|
|
8256
8723
|
}
|
|
8257
|
-
async list(ownerId) {
|
|
8724
|
+
async list(ownerId, tenantId) {
|
|
8258
8725
|
this.purgeExpired();
|
|
8259
|
-
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);
|
|
8260
8727
|
}
|
|
8261
|
-
async listSummaries(ownerId) {
|
|
8728
|
+
async listSummaries(ownerId, tenantId) {
|
|
8262
8729
|
this.purgeExpired();
|
|
8263
|
-
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) => ({
|
|
8264
8731
|
conversationId: c.conversationId,
|
|
8265
8732
|
title: c.title,
|
|
8266
8733
|
updatedAt: c.updatedAt,
|
|
8267
8734
|
createdAt: c.createdAt,
|
|
8268
8735
|
ownerId: c.ownerId,
|
|
8736
|
+
tenantId: c.tenantId,
|
|
8269
8737
|
parentConversationId: c.parentConversationId,
|
|
8270
8738
|
messageCount: c.messages.length,
|
|
8271
8739
|
hasPendingApprovals: Array.isArray(c.pendingApprovals) && c.pendingApprovals.length > 0,
|
|
@@ -8276,14 +8744,14 @@ var InMemoryConversationStore = class {
|
|
|
8276
8744
|
this.purgeExpired();
|
|
8277
8745
|
return this.conversations.get(conversationId);
|
|
8278
8746
|
}
|
|
8279
|
-
async create(ownerId = DEFAULT_OWNER, title) {
|
|
8747
|
+
async create(ownerId = DEFAULT_OWNER, title, tenantId = null) {
|
|
8280
8748
|
const now2 = Date.now();
|
|
8281
8749
|
const conversation = {
|
|
8282
8750
|
conversationId: globalThis.crypto?.randomUUID?.() ?? `${now2}-${Math.random()}`,
|
|
8283
8751
|
title: normalizeTitle(title),
|
|
8284
8752
|
messages: [],
|
|
8285
8753
|
ownerId,
|
|
8286
|
-
tenantId
|
|
8754
|
+
tenantId,
|
|
8287
8755
|
createdAt: now2,
|
|
8288
8756
|
updatedAt: now2
|
|
8289
8757
|
};
|
|
@@ -8347,8 +8815,8 @@ var FileConversationStore = class {
|
|
|
8347
8815
|
agentId: this.agentId
|
|
8348
8816
|
});
|
|
8349
8817
|
const agentDir = getAgentStoreDirectory(identity);
|
|
8350
|
-
const conversationsDir =
|
|
8351
|
-
const indexPath =
|
|
8818
|
+
const conversationsDir = resolve14(agentDir, CONVERSATIONS_DIRECTORY);
|
|
8819
|
+
const indexPath = resolve14(conversationsDir, LOCAL_CONVERSATION_INDEX_FILE);
|
|
8352
8820
|
this.paths = { conversationsDir, indexPath };
|
|
8353
8821
|
return this.paths;
|
|
8354
8822
|
}
|
|
@@ -8362,9 +8830,9 @@ var FileConversationStore = class {
|
|
|
8362
8830
|
}
|
|
8363
8831
|
async readConversationFile(fileName) {
|
|
8364
8832
|
const { conversationsDir } = await this.resolvePaths();
|
|
8365
|
-
const filePath =
|
|
8833
|
+
const filePath = resolve14(conversationsDir, fileName);
|
|
8366
8834
|
try {
|
|
8367
|
-
const raw = await
|
|
8835
|
+
const raw = await readFile12(filePath, "utf8");
|
|
8368
8836
|
return JSON.parse(raw);
|
|
8369
8837
|
} catch {
|
|
8370
8838
|
return void 0;
|
|
@@ -8389,6 +8857,7 @@ var FileConversationStore = class {
|
|
|
8389
8857
|
updatedAt: conversation.updatedAt,
|
|
8390
8858
|
createdAt: conversation.createdAt,
|
|
8391
8859
|
ownerId: conversation.ownerId,
|
|
8860
|
+
tenantId: conversation.tenantId,
|
|
8392
8861
|
fileName: entry.name,
|
|
8393
8862
|
parentConversationId: conversation.parentConversationId,
|
|
8394
8863
|
messageCount: conversation.messages.length,
|
|
@@ -8410,7 +8879,7 @@ var FileConversationStore = class {
|
|
|
8410
8879
|
this.loaded = true;
|
|
8411
8880
|
const { indexPath } = await this.resolvePaths();
|
|
8412
8881
|
try {
|
|
8413
|
-
const raw = await
|
|
8882
|
+
const raw = await readFile12(indexPath, "utf8");
|
|
8414
8883
|
const parsed = JSON.parse(raw);
|
|
8415
8884
|
for (const conversation of parsed.conversations ?? []) {
|
|
8416
8885
|
this.conversations.set(conversation.conversationId, conversation);
|
|
@@ -8433,7 +8902,7 @@ var FileConversationStore = class {
|
|
|
8433
8902
|
const { conversationsDir } = await this.resolvePaths();
|
|
8434
8903
|
const existing = this.conversations.get(conversation.conversationId);
|
|
8435
8904
|
const fileName = existing?.fileName ?? this.resolveConversationFileName(conversation);
|
|
8436
|
-
const filePath =
|
|
8905
|
+
const filePath = resolve14(conversationsDir, fileName);
|
|
8437
8906
|
this.writing = this.writing.then(async () => {
|
|
8438
8907
|
await writeJsonAtomic4(filePath, conversation);
|
|
8439
8908
|
this.conversations.set(conversation.conversationId, {
|
|
@@ -8442,6 +8911,7 @@ var FileConversationStore = class {
|
|
|
8442
8911
|
updatedAt: conversation.updatedAt,
|
|
8443
8912
|
createdAt: conversation.createdAt,
|
|
8444
8913
|
ownerId: conversation.ownerId,
|
|
8914
|
+
tenantId: conversation.tenantId,
|
|
8445
8915
|
fileName,
|
|
8446
8916
|
parentConversationId: conversation.parentConversationId,
|
|
8447
8917
|
messageCount: conversation.messages.length,
|
|
@@ -8452,9 +8922,9 @@ var FileConversationStore = class {
|
|
|
8452
8922
|
});
|
|
8453
8923
|
await this.writing;
|
|
8454
8924
|
}
|
|
8455
|
-
async list(ownerId) {
|
|
8925
|
+
async list(ownerId, tenantId) {
|
|
8456
8926
|
await this.ensureLoaded();
|
|
8457
|
-
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);
|
|
8458
8928
|
const conversations = [];
|
|
8459
8929
|
for (const summary of summaries) {
|
|
8460
8930
|
const loaded = await this.readConversationFile(summary.fileName);
|
|
@@ -8464,14 +8934,15 @@ var FileConversationStore = class {
|
|
|
8464
8934
|
}
|
|
8465
8935
|
return conversations;
|
|
8466
8936
|
}
|
|
8467
|
-
async listSummaries(ownerId) {
|
|
8937
|
+
async listSummaries(ownerId, tenantId) {
|
|
8468
8938
|
await this.ensureLoaded();
|
|
8469
|
-
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) => ({
|
|
8470
8940
|
conversationId: c.conversationId,
|
|
8471
8941
|
title: c.title,
|
|
8472
8942
|
updatedAt: c.updatedAt,
|
|
8473
8943
|
createdAt: c.createdAt,
|
|
8474
8944
|
ownerId: c.ownerId,
|
|
8945
|
+
tenantId: c.tenantId,
|
|
8475
8946
|
parentConversationId: c.parentConversationId,
|
|
8476
8947
|
messageCount: c.messageCount,
|
|
8477
8948
|
hasPendingApprovals: c.hasPendingApprovals,
|
|
@@ -8486,7 +8957,7 @@ var FileConversationStore = class {
|
|
|
8486
8957
|
}
|
|
8487
8958
|
return await this.readConversationFile(summary.fileName);
|
|
8488
8959
|
}
|
|
8489
|
-
async create(ownerId = DEFAULT_OWNER, title) {
|
|
8960
|
+
async create(ownerId = DEFAULT_OWNER, title, tenantId = null) {
|
|
8490
8961
|
await this.ensureLoaded();
|
|
8491
8962
|
const now2 = Date.now();
|
|
8492
8963
|
const conversation = {
|
|
@@ -8494,7 +8965,7 @@ var FileConversationStore = class {
|
|
|
8494
8965
|
title: normalizeTitle(title),
|
|
8495
8966
|
messages: [],
|
|
8496
8967
|
ownerId,
|
|
8497
|
-
tenantId
|
|
8968
|
+
tenantId,
|
|
8498
8969
|
createdAt: now2,
|
|
8499
8970
|
updatedAt: now2
|
|
8500
8971
|
};
|
|
@@ -8531,7 +9002,7 @@ var FileConversationStore = class {
|
|
|
8531
9002
|
if (removed) {
|
|
8532
9003
|
this.writing = this.writing.then(async () => {
|
|
8533
9004
|
if (existing) {
|
|
8534
|
-
await rm4(
|
|
9005
|
+
await rm4(resolve14(conversationsDir, existing.fileName), { force: true });
|
|
8535
9006
|
}
|
|
8536
9007
|
await this.writeIndex();
|
|
8537
9008
|
});
|
|
@@ -8553,7 +9024,7 @@ var FileConversationStore = class {
|
|
|
8553
9024
|
const summary = this.conversations.get(conversationId);
|
|
8554
9025
|
if (!summary) return void 0;
|
|
8555
9026
|
const { conversationsDir } = await this.resolvePaths();
|
|
8556
|
-
const filePath =
|
|
9027
|
+
const filePath = resolve14(conversationsDir, summary.fileName);
|
|
8557
9028
|
let result;
|
|
8558
9029
|
this.writing = this.writing.then(async () => {
|
|
8559
9030
|
const conv = await this.readConversationFile(summary.fileName);
|
|
@@ -8592,7 +9063,7 @@ var FileStateStore = class {
|
|
|
8592
9063
|
workingDir: this.workingDir,
|
|
8593
9064
|
agentId: this.agentId
|
|
8594
9065
|
});
|
|
8595
|
-
this.filePath =
|
|
9066
|
+
this.filePath = resolve14(getAgentStoreDirectory(identity), LOCAL_STATE_FILE);
|
|
8596
9067
|
}
|
|
8597
9068
|
isExpired(state) {
|
|
8598
9069
|
return typeof this.ttlMs === "number" && Date.now() - state.updatedAt > this.ttlMs;
|
|
@@ -8604,7 +9075,7 @@ var FileStateStore = class {
|
|
|
8604
9075
|
}
|
|
8605
9076
|
this.loaded = true;
|
|
8606
9077
|
try {
|
|
8607
|
-
const raw = await
|
|
9078
|
+
const raw = await readFile12(this.filePath, "utf8");
|
|
8608
9079
|
const parsed = JSON.parse(raw);
|
|
8609
9080
|
for (const state of parsed.states ?? []) {
|
|
8610
9081
|
this.states.set(state.runId, state);
|
|
@@ -8737,10 +9208,10 @@ var KeyValueConversationStoreBase = class {
|
|
|
8737
9208
|
return void 0;
|
|
8738
9209
|
}
|
|
8739
9210
|
}
|
|
8740
|
-
async list(ownerId) {
|
|
9211
|
+
async list(ownerId, tenantId) {
|
|
8741
9212
|
const kv = await this.client();
|
|
8742
9213
|
if (!kv) {
|
|
8743
|
-
return await this.memoryFallback.list(ownerId);
|
|
9214
|
+
return await this.memoryFallback.list(ownerId, tenantId);
|
|
8744
9215
|
}
|
|
8745
9216
|
if (!ownerId) {
|
|
8746
9217
|
return [];
|
|
@@ -8753,16 +9224,19 @@ var KeyValueConversationStoreBase = class {
|
|
|
8753
9224
|
for (const raw of rawValues) {
|
|
8754
9225
|
if (!raw) continue;
|
|
8755
9226
|
try {
|
|
8756
|
-
|
|
9227
|
+
const conv = JSON.parse(raw);
|
|
9228
|
+
if (tenantId === void 0 || conv.tenantId === tenantId) {
|
|
9229
|
+
conversations.push(conv);
|
|
9230
|
+
}
|
|
8757
9231
|
} catch {
|
|
8758
9232
|
}
|
|
8759
9233
|
}
|
|
8760
9234
|
return conversations.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
8761
9235
|
}
|
|
8762
|
-
async listSummaries(ownerId) {
|
|
9236
|
+
async listSummaries(ownerId, tenantId) {
|
|
8763
9237
|
const kv = await this.client();
|
|
8764
9238
|
if (!kv) {
|
|
8765
|
-
return await this.memoryFallback.listSummaries(ownerId);
|
|
9239
|
+
return await this.memoryFallback.listSummaries(ownerId, tenantId);
|
|
8766
9240
|
}
|
|
8767
9241
|
if (!ownerId) {
|
|
8768
9242
|
return [];
|
|
@@ -8776,13 +9250,14 @@ var KeyValueConversationStoreBase = class {
|
|
|
8776
9250
|
if (!raw) continue;
|
|
8777
9251
|
try {
|
|
8778
9252
|
const meta = JSON.parse(raw);
|
|
8779
|
-
if (meta.ownerId === ownerId) {
|
|
9253
|
+
if (meta.ownerId === ownerId && (tenantId === void 0 || (meta.tenantId ?? null) === tenantId)) {
|
|
8780
9254
|
summaries.push({
|
|
8781
9255
|
conversationId: meta.conversationId,
|
|
8782
9256
|
title: meta.title,
|
|
8783
9257
|
updatedAt: meta.updatedAt,
|
|
8784
9258
|
createdAt: meta.createdAt,
|
|
8785
9259
|
ownerId: meta.ownerId,
|
|
9260
|
+
tenantId: meta.tenantId,
|
|
8786
9261
|
parentConversationId: meta.parentConversationId,
|
|
8787
9262
|
messageCount: meta.messageCount,
|
|
8788
9263
|
hasPendingApprovals: meta.hasPendingApprovals,
|
|
@@ -8809,14 +9284,14 @@ var KeyValueConversationStoreBase = class {
|
|
|
8809
9284
|
return void 0;
|
|
8810
9285
|
}
|
|
8811
9286
|
}
|
|
8812
|
-
async create(ownerId = DEFAULT_OWNER, title) {
|
|
9287
|
+
async create(ownerId = DEFAULT_OWNER, title, tenantId = null) {
|
|
8813
9288
|
const now2 = Date.now();
|
|
8814
9289
|
const conversation = {
|
|
8815
9290
|
conversationId: globalThis.crypto?.randomUUID?.() ?? `${now2}-${Math.random()}`,
|
|
8816
9291
|
title: normalizeTitle(title),
|
|
8817
9292
|
messages: [],
|
|
8818
9293
|
ownerId,
|
|
8819
|
-
tenantId
|
|
9294
|
+
tenantId,
|
|
8820
9295
|
createdAt: now2,
|
|
8821
9296
|
updatedAt: now2
|
|
8822
9297
|
};
|
|
@@ -8845,6 +9320,7 @@ var KeyValueConversationStoreBase = class {
|
|
|
8845
9320
|
updatedAt: nextConversation.updatedAt,
|
|
8846
9321
|
createdAt: nextConversation.createdAt,
|
|
8847
9322
|
ownerId: nextConversation.ownerId,
|
|
9323
|
+
tenantId: nextConversation.tenantId,
|
|
8848
9324
|
parentConversationId: nextConversation.parentConversationId,
|
|
8849
9325
|
messageCount: nextConversation.messages.length,
|
|
8850
9326
|
hasPendingApprovals: Array.isArray(nextConversation.pendingApprovals) && nextConversation.pendingApprovals.length > 0,
|
|
@@ -9304,6 +9780,32 @@ var createConversationStore = (config, options) => {
|
|
|
9304
9780
|
return new InMemoryConversationStore(ttl);
|
|
9305
9781
|
};
|
|
9306
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
|
+
|
|
9307
9809
|
// src/index.ts
|
|
9308
9810
|
import { defineTool as defineTool9 } from "@poncho-ai/sdk";
|
|
9309
9811
|
export {
|
|
@@ -9334,6 +9836,7 @@ export {
|
|
|
9334
9836
|
createReminderStore,
|
|
9335
9837
|
createReminderTools,
|
|
9336
9838
|
createSearchTools,
|
|
9839
|
+
createSecretsStore,
|
|
9337
9840
|
createSkillTools,
|
|
9338
9841
|
createStateStore,
|
|
9339
9842
|
createSubagentTools,
|
|
@@ -9368,10 +9871,12 @@ export {
|
|
|
9368
9871
|
renderAgentPrompt,
|
|
9369
9872
|
resolveAgentIdentity,
|
|
9370
9873
|
resolveCompactionConfig,
|
|
9874
|
+
resolveEnv,
|
|
9371
9875
|
resolveMemoryConfig,
|
|
9372
9876
|
resolveSkillDirs,
|
|
9373
9877
|
resolveStateConfig,
|
|
9374
9878
|
slugifyStorageComponent,
|
|
9375
9879
|
startOpenAICodexDeviceAuth,
|
|
9880
|
+
verifyTenantToken,
|
|
9376
9881
|
writeOpenAICodexSession
|
|
9377
9882
|
};
|