@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/.turbo/turbo-build.log +11 -11
- package/.turbo/turbo-lint.log +6 -0
- package/.turbo/turbo-test.log +11931 -0
- package/CHANGELOG.md +28 -0
- package/dist/index.d.ts +82 -9
- package/dist/index.js +629 -118
- package/package.json +3 -2
- package/src/compaction.ts +10 -2
- package/src/config.ts +6 -0
- package/src/harness.ts +82 -16
- 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
|
@@ -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
|
-
|
|
402
|
+
tokens += Math.ceil(toolDefinitionsJson.length / 6);
|
|
402
403
|
}
|
|
403
|
-
return
|
|
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
|
|
2105
|
-
import { resolve as
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
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
|
-
|
|
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
|
|
3989
|
-
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";
|
|
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
|
|
4439
|
+
return resolve10(fromEnv);
|
|
4022
4440
|
}
|
|
4023
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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
|
|
4364
|
-
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";
|
|
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) =>
|
|
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 =
|
|
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
|
|
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:
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
4524
4942
|
var loadSkillInstructions = async (skill) => {
|
|
4525
|
-
const content = await
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
4955
|
-
const boundary =
|
|
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((
|
|
5062
|
-
release =
|
|
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, (
|
|
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 =
|
|
6521
|
-
const rawContent = await
|
|
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 =
|
|
6591
|
-
const agentRawContent = await
|
|
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(
|
|
6623
|
-
|
|
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:
|
|
6683
|
-
await
|
|
6684
|
-
await
|
|
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:
|
|
7145
|
+
const { readFile: readFile13 } = await import("fs/promises");
|
|
6688
7146
|
try {
|
|
6689
|
-
return await
|
|
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:
|
|
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 =
|
|
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
|
|
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
|
-
|
|
7293
|
-
|
|
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
|
-
|
|
7301
|
-
resolvedData = Buffer.from(await resp.arrayBuffer()).toString("base64");
|
|
7772
|
+
resolvedData = part.data;
|
|
7302
7773
|
}
|
|
7303
|
-
}
|
|
7304
|
-
|
|
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
|
|
7334
|
-
const effectiveTokens = Math.max(estimated,
|
|
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((
|
|
7443
|
-
timer = setTimeout(() =>
|
|
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
|
-
(
|
|
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
|
|
8137
|
-
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";
|
|
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
|
|
8626
|
+
await mkdir8(dirname8(filePath), { recursive: true });
|
|
8154
8627
|
const tmpPath = `${filePath}.tmp`;
|
|
8155
|
-
await
|
|
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
|
|
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 =
|
|
8345
|
-
const indexPath =
|
|
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 =
|
|
8833
|
+
const filePath = resolve14(conversationsDir, fileName);
|
|
8360
8834
|
try {
|
|
8361
|
-
const raw = await
|
|
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
|
|
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 =
|
|
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
|
|
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(
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
};
|