@poncho-ai/harness 0.24.0 → 0.26.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 +5 -5
- package/CHANGELOG.md +12 -0
- package/dist/index.d.ts +34 -1
- package/dist/index.js +645 -337
- package/package.json +1 -1
- package/src/config.ts +4 -0
- package/src/harness.ts +140 -40
- package/src/kv-store.ts +206 -0
- package/src/memory.ts +88 -336
- package/src/todo-tools.ts +363 -0
- package/test/harness.test.ts +129 -2
- package/test/memory.test.ts +100 -15
package/dist/index.js
CHANGED
|
@@ -1402,7 +1402,8 @@ if (result.compacted) {
|
|
|
1402
1402
|
When \`memory.enabled\` is true in \`poncho.config.js\`, the harness enables a simple memory model:
|
|
1403
1403
|
|
|
1404
1404
|
- A single persistent main memory document is loaded at run start and interpolated into the system prompt under \`## Persistent Memory\`.
|
|
1405
|
-
- \`
|
|
1405
|
+
- \`memory_main_write\` overwrites the entire memory document (for initial writes or full rewrites).
|
|
1406
|
+
- \`memory_main_edit\` performs targeted string-replacement edits on memory (find \`old_str\`, replace with \`new_str\`), mirroring \`edit_file\` semantics. The tool description instructs the model to proactively evaluate each turn whether durable memory should be updated.
|
|
1406
1407
|
- \`conversation_recall\` can search recent prior conversations (keyword scoring) when historical context is relevant (\`as we discussed\`, \`last time\`, etc.).
|
|
1407
1408
|
|
|
1408
1409
|
\`\`\`javascript
|
|
@@ -1420,9 +1421,10 @@ export default {
|
|
|
1420
1421
|
|
|
1421
1422
|
Available memory tools:
|
|
1422
1423
|
|
|
1423
|
-
- \`memory_main_get\`
|
|
1424
|
-
- \`
|
|
1425
|
-
- \`
|
|
1424
|
+
- \`memory_main_get\` \u2014 read the current memory document
|
|
1425
|
+
- \`memory_main_write\` \u2014 overwrite the entire memory document
|
|
1426
|
+
- \`memory_main_edit\` \u2014 edit memory via exact string replacement (\`old_str\` / \`new_str\`)
|
|
1427
|
+
- \`conversation_recall\` \u2014 search past conversations
|
|
1426
1428
|
`,
|
|
1427
1429
|
"configuration": `# Configuration & Security
|
|
1428
1430
|
|
|
@@ -2028,6 +2030,8 @@ var ponchoDocsTool = defineTool({
|
|
|
2028
2030
|
|
|
2029
2031
|
// src/harness.ts
|
|
2030
2032
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
2033
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
2034
|
+
import { resolve as resolve10 } from "path";
|
|
2031
2035
|
import { getTextContent as getTextContent3 } from "@poncho-ai/sdk";
|
|
2032
2036
|
|
|
2033
2037
|
// src/upload-store.ts
|
|
@@ -2342,6 +2346,142 @@ var createUploadStore = async (config, workingDir) => {
|
|
|
2342
2346
|
import { mkdir as mkdir3, readFile as readFile5, rename, writeFile as writeFile4 } from "fs/promises";
|
|
2343
2347
|
import { dirname as dirname2, resolve as resolve6 } from "path";
|
|
2344
2348
|
import { defineTool as defineTool2 } from "@poncho-ai/sdk";
|
|
2349
|
+
|
|
2350
|
+
// src/kv-store.ts
|
|
2351
|
+
var UpstashKVStore = class {
|
|
2352
|
+
baseUrl;
|
|
2353
|
+
token;
|
|
2354
|
+
constructor(baseUrl, token) {
|
|
2355
|
+
this.baseUrl = baseUrl.replace(/\/+$/, "");
|
|
2356
|
+
this.token = token;
|
|
2357
|
+
}
|
|
2358
|
+
headers() {
|
|
2359
|
+
return { Authorization: `Bearer ${this.token}`, "Content-Type": "application/json" };
|
|
2360
|
+
}
|
|
2361
|
+
async get(key) {
|
|
2362
|
+
const response = await fetch(`${this.baseUrl}/get/${encodeURIComponent(key)}`, {
|
|
2363
|
+
method: "POST",
|
|
2364
|
+
headers: this.headers()
|
|
2365
|
+
});
|
|
2366
|
+
if (!response.ok) return void 0;
|
|
2367
|
+
const payload = await response.json();
|
|
2368
|
+
return payload.result ?? void 0;
|
|
2369
|
+
}
|
|
2370
|
+
async set(key, value) {
|
|
2371
|
+
await fetch(
|
|
2372
|
+
`${this.baseUrl}/set/${encodeURIComponent(key)}/${encodeURIComponent(value)}`,
|
|
2373
|
+
{ method: "POST", headers: this.headers() }
|
|
2374
|
+
);
|
|
2375
|
+
}
|
|
2376
|
+
async setWithTtl(key, value, ttl) {
|
|
2377
|
+
await fetch(
|
|
2378
|
+
`${this.baseUrl}/setex/${encodeURIComponent(key)}/${Math.max(1, ttl)}/${encodeURIComponent(value)}`,
|
|
2379
|
+
{ method: "POST", headers: this.headers() }
|
|
2380
|
+
);
|
|
2381
|
+
}
|
|
2382
|
+
};
|
|
2383
|
+
var RedisKVStore = class {
|
|
2384
|
+
clientPromise;
|
|
2385
|
+
constructor(url) {
|
|
2386
|
+
this.clientPromise = (async () => {
|
|
2387
|
+
try {
|
|
2388
|
+
const redisModule = await import("redis");
|
|
2389
|
+
const client = redisModule.createClient({ url });
|
|
2390
|
+
await client.connect();
|
|
2391
|
+
return client;
|
|
2392
|
+
} catch {
|
|
2393
|
+
return void 0;
|
|
2394
|
+
}
|
|
2395
|
+
})();
|
|
2396
|
+
}
|
|
2397
|
+
async get(key) {
|
|
2398
|
+
const client = await this.clientPromise;
|
|
2399
|
+
if (!client) throw new Error("Redis unavailable");
|
|
2400
|
+
const value = await client.get(key);
|
|
2401
|
+
return value ?? void 0;
|
|
2402
|
+
}
|
|
2403
|
+
async set(key, value) {
|
|
2404
|
+
const client = await this.clientPromise;
|
|
2405
|
+
if (!client) throw new Error("Redis unavailable");
|
|
2406
|
+
await client.set(key, value);
|
|
2407
|
+
}
|
|
2408
|
+
async setWithTtl(key, value, ttl) {
|
|
2409
|
+
const client = await this.clientPromise;
|
|
2410
|
+
if (!client) throw new Error("Redis unavailable");
|
|
2411
|
+
await client.set(key, value, { EX: Math.max(1, ttl) });
|
|
2412
|
+
}
|
|
2413
|
+
};
|
|
2414
|
+
var DynamoDbKVStore = class {
|
|
2415
|
+
table;
|
|
2416
|
+
clientPromise;
|
|
2417
|
+
constructor(table, region) {
|
|
2418
|
+
this.table = table;
|
|
2419
|
+
this.clientPromise = (async () => {
|
|
2420
|
+
try {
|
|
2421
|
+
const module = await import("@aws-sdk/client-dynamodb");
|
|
2422
|
+
const client = new module.DynamoDBClient({ region });
|
|
2423
|
+
return {
|
|
2424
|
+
send: client.send.bind(client),
|
|
2425
|
+
GetItemCommand: module.GetItemCommand,
|
|
2426
|
+
PutItemCommand: module.PutItemCommand
|
|
2427
|
+
};
|
|
2428
|
+
} catch {
|
|
2429
|
+
return void 0;
|
|
2430
|
+
}
|
|
2431
|
+
})();
|
|
2432
|
+
}
|
|
2433
|
+
async get(key) {
|
|
2434
|
+
const client = await this.clientPromise;
|
|
2435
|
+
if (!client) throw new Error("DynamoDB unavailable");
|
|
2436
|
+
const result = await client.send(
|
|
2437
|
+
new client.GetItemCommand({ TableName: this.table, Key: { runId: { S: key } } })
|
|
2438
|
+
);
|
|
2439
|
+
return result.Item?.value?.S;
|
|
2440
|
+
}
|
|
2441
|
+
async set(key, value) {
|
|
2442
|
+
const client = await this.clientPromise;
|
|
2443
|
+
if (!client) throw new Error("DynamoDB unavailable");
|
|
2444
|
+
await client.send(
|
|
2445
|
+
new client.PutItemCommand({
|
|
2446
|
+
TableName: this.table,
|
|
2447
|
+
Item: { runId: { S: key }, value: { S: value } }
|
|
2448
|
+
})
|
|
2449
|
+
);
|
|
2450
|
+
}
|
|
2451
|
+
async setWithTtl(key, value, ttl) {
|
|
2452
|
+
const client = await this.clientPromise;
|
|
2453
|
+
if (!client) throw new Error("DynamoDB unavailable");
|
|
2454
|
+
const ttlEpoch = Math.floor(Date.now() / 1e3) + Math.max(1, ttl);
|
|
2455
|
+
await client.send(
|
|
2456
|
+
new client.PutItemCommand({
|
|
2457
|
+
TableName: this.table,
|
|
2458
|
+
Item: { runId: { S: key }, value: { S: value }, ttl: { N: String(ttlEpoch) } }
|
|
2459
|
+
})
|
|
2460
|
+
);
|
|
2461
|
+
}
|
|
2462
|
+
};
|
|
2463
|
+
var createRawKVStore = (config) => {
|
|
2464
|
+
const provider = config?.provider ?? "local";
|
|
2465
|
+
if (provider === "upstash") {
|
|
2466
|
+
const urlEnv = config?.urlEnv ?? (process.env.UPSTASH_REDIS_REST_URL ? "UPSTASH_REDIS_REST_URL" : "KV_REST_API_URL");
|
|
2467
|
+
const tokenEnv = config?.tokenEnv ?? (process.env.UPSTASH_REDIS_REST_TOKEN ? "UPSTASH_REDIS_REST_TOKEN" : "KV_REST_API_TOKEN");
|
|
2468
|
+
const url = process.env[urlEnv] ?? "";
|
|
2469
|
+
const token = process.env[tokenEnv] ?? "";
|
|
2470
|
+
if (url && token) return new UpstashKVStore(url, token);
|
|
2471
|
+
}
|
|
2472
|
+
if (provider === "redis") {
|
|
2473
|
+
const urlEnv = config?.urlEnv ?? "REDIS_URL";
|
|
2474
|
+
const url = process.env[urlEnv] ?? "";
|
|
2475
|
+
if (url) return new RedisKVStore(url);
|
|
2476
|
+
}
|
|
2477
|
+
if (provider === "dynamodb") {
|
|
2478
|
+
const table = config?.table ?? process.env.PONCHO_DYNAMODB_TABLE ?? "";
|
|
2479
|
+
if (table) return new DynamoDbKVStore(table, config?.region);
|
|
2480
|
+
}
|
|
2481
|
+
return void 0;
|
|
2482
|
+
};
|
|
2483
|
+
|
|
2484
|
+
// src/memory.ts
|
|
2345
2485
|
var DEFAULT_MAIN_MEMORY = {
|
|
2346
2486
|
content: "",
|
|
2347
2487
|
updatedAt: 0
|
|
@@ -2384,14 +2524,9 @@ var InMemoryMemoryStore = class {
|
|
|
2384
2524
|
return this.mainMemory;
|
|
2385
2525
|
}
|
|
2386
2526
|
async updateMainMemory(input) {
|
|
2387
|
-
const now2 = Date.now();
|
|
2388
|
-
const existing = await this.getMainMemory();
|
|
2389
|
-
const nextContent = input.mode === "append" && existing.content ? `${existing.content}
|
|
2390
|
-
|
|
2391
|
-
${input.content}`.trim() : input.content;
|
|
2392
2527
|
this.mainMemory = {
|
|
2393
|
-
content:
|
|
2394
|
-
updatedAt:
|
|
2528
|
+
content: input.content.trim(),
|
|
2529
|
+
updatedAt: Date.now()
|
|
2395
2530
|
};
|
|
2396
2531
|
return this.mainMemory;
|
|
2397
2532
|
}
|
|
@@ -2449,31 +2584,29 @@ var FileMainMemoryStore = class {
|
|
|
2449
2584
|
}
|
|
2450
2585
|
async updateMainMemory(input) {
|
|
2451
2586
|
await this.ensureLoaded();
|
|
2452
|
-
const existing = await this.getMainMemory();
|
|
2453
|
-
const nextContent = input.mode === "append" && existing.content ? `${existing.content}
|
|
2454
|
-
|
|
2455
|
-
${input.content}`.trim() : input.content;
|
|
2456
2587
|
this.mainMemory = {
|
|
2457
|
-
content:
|
|
2588
|
+
content: input.content.trim(),
|
|
2458
2589
|
updatedAt: Date.now()
|
|
2459
2590
|
};
|
|
2460
2591
|
await this.persist();
|
|
2461
2592
|
return this.mainMemory;
|
|
2462
2593
|
}
|
|
2463
2594
|
};
|
|
2464
|
-
var
|
|
2595
|
+
var KVBackedMemoryStore = class {
|
|
2596
|
+
kv;
|
|
2597
|
+
storageKey;
|
|
2465
2598
|
ttl;
|
|
2466
2599
|
memoryFallback;
|
|
2467
|
-
constructor(ttl) {
|
|
2600
|
+
constructor(kv, storageKey, ttl) {
|
|
2601
|
+
this.kv = kv;
|
|
2602
|
+
this.storageKey = storageKey;
|
|
2468
2603
|
this.ttl = ttl;
|
|
2469
2604
|
this.memoryFallback = new InMemoryMemoryStore(ttl);
|
|
2470
2605
|
}
|
|
2471
|
-
async readPayload(
|
|
2606
|
+
async readPayload() {
|
|
2472
2607
|
try {
|
|
2473
|
-
const raw = await this.
|
|
2474
|
-
if (!raw) {
|
|
2475
|
-
return { main: { ...DEFAULT_MAIN_MEMORY } };
|
|
2476
|
-
}
|
|
2608
|
+
const raw = await this.kv.get(this.storageKey);
|
|
2609
|
+
if (!raw) return { main: { ...DEFAULT_MAIN_MEMORY } };
|
|
2477
2610
|
const parsed = JSON.parse(raw);
|
|
2478
2611
|
const content = typeof parsed.main?.content === "string" ? parsed.main.content : "";
|
|
2479
2612
|
const updatedAt = typeof parsed.main?.updatedAt === "number" ? parsed.main.updatedAt : 0;
|
|
@@ -2483,204 +2616,32 @@ var KeyValueMainMemoryStoreBase = class {
|
|
|
2483
2616
|
return { main };
|
|
2484
2617
|
}
|
|
2485
2618
|
}
|
|
2486
|
-
async writePayload(
|
|
2619
|
+
async writePayload(payload) {
|
|
2487
2620
|
try {
|
|
2488
2621
|
const serialized = JSON.stringify(payload);
|
|
2489
2622
|
if (typeof this.ttl === "number") {
|
|
2490
|
-
await this.
|
|
2623
|
+
await this.kv.setWithTtl(this.storageKey, serialized, Math.max(1, this.ttl));
|
|
2491
2624
|
} else {
|
|
2492
|
-
await this.
|
|
2625
|
+
await this.kv.set(this.storageKey, serialized);
|
|
2493
2626
|
}
|
|
2494
2627
|
} catch {
|
|
2495
|
-
await this.memoryFallback.updateMainMemory({
|
|
2496
|
-
content: payload.main.content,
|
|
2497
|
-
mode: "replace"
|
|
2498
|
-
});
|
|
2628
|
+
await this.memoryFallback.updateMainMemory({ content: payload.main.content });
|
|
2499
2629
|
}
|
|
2500
2630
|
}
|
|
2501
2631
|
async getMainMemory() {
|
|
2502
|
-
const payload = await this.readPayload(
|
|
2632
|
+
const payload = await this.readPayload();
|
|
2503
2633
|
return payload.main;
|
|
2504
2634
|
}
|
|
2505
2635
|
async updateMainMemory(input) {
|
|
2506
|
-
const
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
${input.content}`.trim() : input.content;
|
|
2511
|
-
payload.main = {
|
|
2512
|
-
content: nextContent.trim(),
|
|
2513
|
-
updatedAt: Date.now()
|
|
2514
|
-
};
|
|
2515
|
-
await this.writePayload(key, payload);
|
|
2636
|
+
const payload = await this.readPayload();
|
|
2637
|
+
payload.main = { content: input.content.trim(), updatedAt: Date.now() };
|
|
2638
|
+
await this.writePayload(payload);
|
|
2516
2639
|
return payload.main;
|
|
2517
2640
|
}
|
|
2518
2641
|
};
|
|
2519
|
-
var UpstashMemoryStore = class extends KeyValueMainMemoryStoreBase {
|
|
2520
|
-
baseUrl;
|
|
2521
|
-
token;
|
|
2522
|
-
storageKey;
|
|
2523
|
-
constructor(options) {
|
|
2524
|
-
super(options.ttl);
|
|
2525
|
-
this.baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
2526
|
-
this.token = options.token;
|
|
2527
|
-
this.storageKey = options.storageKey;
|
|
2528
|
-
}
|
|
2529
|
-
key() {
|
|
2530
|
-
return this.storageKey;
|
|
2531
|
-
}
|
|
2532
|
-
headers() {
|
|
2533
|
-
return {
|
|
2534
|
-
Authorization: `Bearer ${this.token}`,
|
|
2535
|
-
"Content-Type": "application/json"
|
|
2536
|
-
};
|
|
2537
|
-
}
|
|
2538
|
-
async getRaw(key) {
|
|
2539
|
-
const response = await fetch(`${this.baseUrl}/get/${encodeURIComponent(key)}`, {
|
|
2540
|
-
method: "POST",
|
|
2541
|
-
headers: this.headers()
|
|
2542
|
-
});
|
|
2543
|
-
if (!response.ok) {
|
|
2544
|
-
return void 0;
|
|
2545
|
-
}
|
|
2546
|
-
const payload = await response.json();
|
|
2547
|
-
return payload.result ?? void 0;
|
|
2548
|
-
}
|
|
2549
|
-
async setRaw(key, value) {
|
|
2550
|
-
await fetch(
|
|
2551
|
-
`${this.baseUrl}/set/${encodeURIComponent(key)}/${encodeURIComponent(value)}`,
|
|
2552
|
-
{ method: "POST", headers: this.headers() }
|
|
2553
|
-
);
|
|
2554
|
-
}
|
|
2555
|
-
async setRawWithTtl(key, value, ttl) {
|
|
2556
|
-
await fetch(
|
|
2557
|
-
`${this.baseUrl}/setex/${encodeURIComponent(key)}/${Math.max(1, ttl)}/${encodeURIComponent(
|
|
2558
|
-
value
|
|
2559
|
-
)}`,
|
|
2560
|
-
{ method: "POST", headers: this.headers() }
|
|
2561
|
-
);
|
|
2562
|
-
}
|
|
2563
|
-
};
|
|
2564
|
-
var RedisMemoryStore = class extends KeyValueMainMemoryStoreBase {
|
|
2565
|
-
storageKey;
|
|
2566
|
-
clientPromise;
|
|
2567
|
-
constructor(options) {
|
|
2568
|
-
super(options.ttl);
|
|
2569
|
-
this.storageKey = options.storageKey;
|
|
2570
|
-
this.clientPromise = (async () => {
|
|
2571
|
-
try {
|
|
2572
|
-
const redisModule = await import("redis");
|
|
2573
|
-
const client = redisModule.createClient({ url: options.url });
|
|
2574
|
-
await client.connect();
|
|
2575
|
-
return client;
|
|
2576
|
-
} catch {
|
|
2577
|
-
return void 0;
|
|
2578
|
-
}
|
|
2579
|
-
})();
|
|
2580
|
-
}
|
|
2581
|
-
key() {
|
|
2582
|
-
return this.storageKey;
|
|
2583
|
-
}
|
|
2584
|
-
async getRaw(key) {
|
|
2585
|
-
const client = await this.clientPromise;
|
|
2586
|
-
if (!client) {
|
|
2587
|
-
throw new Error("Redis unavailable");
|
|
2588
|
-
}
|
|
2589
|
-
const value = await client.get(key);
|
|
2590
|
-
return value ?? void 0;
|
|
2591
|
-
}
|
|
2592
|
-
async setRaw(key, value) {
|
|
2593
|
-
const client = await this.clientPromise;
|
|
2594
|
-
if (!client) {
|
|
2595
|
-
throw new Error("Redis unavailable");
|
|
2596
|
-
}
|
|
2597
|
-
await client.set(key, value);
|
|
2598
|
-
}
|
|
2599
|
-
async setRawWithTtl(key, value, ttl) {
|
|
2600
|
-
const client = await this.clientPromise;
|
|
2601
|
-
if (!client) {
|
|
2602
|
-
throw new Error("Redis unavailable");
|
|
2603
|
-
}
|
|
2604
|
-
await client.set(key, value, { EX: Math.max(1, ttl) });
|
|
2605
|
-
}
|
|
2606
|
-
};
|
|
2607
|
-
var DynamoDbMemoryStore = class extends KeyValueMainMemoryStoreBase {
|
|
2608
|
-
storageKey;
|
|
2609
|
-
table;
|
|
2610
|
-
clientPromise;
|
|
2611
|
-
constructor(options) {
|
|
2612
|
-
super(options.ttl);
|
|
2613
|
-
this.storageKey = options.storageKey;
|
|
2614
|
-
this.table = options.table;
|
|
2615
|
-
this.clientPromise = (async () => {
|
|
2616
|
-
try {
|
|
2617
|
-
const module = await import("@aws-sdk/client-dynamodb");
|
|
2618
|
-
const client = new module.DynamoDBClient({ region: options.region });
|
|
2619
|
-
return {
|
|
2620
|
-
send: client.send.bind(client),
|
|
2621
|
-
GetItemCommand: module.GetItemCommand,
|
|
2622
|
-
PutItemCommand: module.PutItemCommand
|
|
2623
|
-
};
|
|
2624
|
-
} catch {
|
|
2625
|
-
return void 0;
|
|
2626
|
-
}
|
|
2627
|
-
})();
|
|
2628
|
-
}
|
|
2629
|
-
key() {
|
|
2630
|
-
return this.storageKey;
|
|
2631
|
-
}
|
|
2632
|
-
async getRaw(key) {
|
|
2633
|
-
const client = await this.clientPromise;
|
|
2634
|
-
if (!client) {
|
|
2635
|
-
throw new Error("DynamoDB unavailable");
|
|
2636
|
-
}
|
|
2637
|
-
const result = await client.send(
|
|
2638
|
-
new client.GetItemCommand({
|
|
2639
|
-
TableName: this.table,
|
|
2640
|
-
Key: { runId: { S: key } }
|
|
2641
|
-
})
|
|
2642
|
-
);
|
|
2643
|
-
return result.Item?.value?.S;
|
|
2644
|
-
}
|
|
2645
|
-
async setRaw(key, value) {
|
|
2646
|
-
const client = await this.clientPromise;
|
|
2647
|
-
if (!client) {
|
|
2648
|
-
throw new Error("DynamoDB unavailable");
|
|
2649
|
-
}
|
|
2650
|
-
await client.send(
|
|
2651
|
-
new client.PutItemCommand({
|
|
2652
|
-
TableName: this.table,
|
|
2653
|
-
Item: {
|
|
2654
|
-
runId: { S: key },
|
|
2655
|
-
value: { S: value }
|
|
2656
|
-
}
|
|
2657
|
-
})
|
|
2658
|
-
);
|
|
2659
|
-
}
|
|
2660
|
-
async setRawWithTtl(key, value, ttl) {
|
|
2661
|
-
const client = await this.clientPromise;
|
|
2662
|
-
if (!client) {
|
|
2663
|
-
throw new Error("DynamoDB unavailable");
|
|
2664
|
-
}
|
|
2665
|
-
const ttlEpoch = Math.floor(Date.now() / 1e3) + Math.max(1, ttl);
|
|
2666
|
-
await client.send(
|
|
2667
|
-
new client.PutItemCommand({
|
|
2668
|
-
TableName: this.table,
|
|
2669
|
-
Item: {
|
|
2670
|
-
runId: { S: key },
|
|
2671
|
-
value: { S: value },
|
|
2672
|
-
ttl: { N: String(ttlEpoch) }
|
|
2673
|
-
}
|
|
2674
|
-
})
|
|
2675
|
-
);
|
|
2676
|
-
}
|
|
2677
|
-
};
|
|
2678
2642
|
var createMemoryStore = (agentId, config, options) => {
|
|
2679
2643
|
const provider = config?.provider ?? "local";
|
|
2680
2644
|
const ttl = config?.ttl;
|
|
2681
|
-
const storageKey = `poncho:${STORAGE_SCHEMA_VERSION}:${slugifyStorageComponent(
|
|
2682
|
-
agentId
|
|
2683
|
-
)}:memory:main`;
|
|
2684
2645
|
const workingDir = options?.workingDir ?? process.cwd();
|
|
2685
2646
|
if (provider === "local") {
|
|
2686
2647
|
return new FileMainMemoryStore(workingDir, ttl);
|
|
@@ -2688,44 +2649,10 @@ var createMemoryStore = (agentId, config, options) => {
|
|
|
2688
2649
|
if (provider === "memory") {
|
|
2689
2650
|
return new InMemoryMemoryStore(ttl);
|
|
2690
2651
|
}
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
const
|
|
2694
|
-
|
|
2695
|
-
const token = process.env[tokenEnv] ?? "";
|
|
2696
|
-
if (url && token) {
|
|
2697
|
-
return new UpstashMemoryStore({
|
|
2698
|
-
baseUrl: url,
|
|
2699
|
-
token,
|
|
2700
|
-
storageKey,
|
|
2701
|
-
ttl
|
|
2702
|
-
});
|
|
2703
|
-
}
|
|
2704
|
-
return new InMemoryMemoryStore(ttl);
|
|
2705
|
-
}
|
|
2706
|
-
if (provider === "redis") {
|
|
2707
|
-
const urlEnv = config?.urlEnv ?? "REDIS_URL";
|
|
2708
|
-
const url = process.env[urlEnv] ?? "";
|
|
2709
|
-
if (url) {
|
|
2710
|
-
return new RedisMemoryStore({
|
|
2711
|
-
url,
|
|
2712
|
-
storageKey,
|
|
2713
|
-
ttl
|
|
2714
|
-
});
|
|
2715
|
-
}
|
|
2716
|
-
return new InMemoryMemoryStore(ttl);
|
|
2717
|
-
}
|
|
2718
|
-
if (provider === "dynamodb") {
|
|
2719
|
-
const table = config?.table ?? process.env.PONCHO_DYNAMODB_TABLE ?? "";
|
|
2720
|
-
if (table) {
|
|
2721
|
-
return new DynamoDbMemoryStore({
|
|
2722
|
-
table,
|
|
2723
|
-
storageKey,
|
|
2724
|
-
region: config?.region,
|
|
2725
|
-
ttl
|
|
2726
|
-
});
|
|
2727
|
-
}
|
|
2728
|
-
return new InMemoryMemoryStore(ttl);
|
|
2652
|
+
const kv = createRawKVStore(config);
|
|
2653
|
+
if (kv) {
|
|
2654
|
+
const storageKey = `poncho:${STORAGE_SCHEMA_VERSION}:${slugifyStorageComponent(agentId)}:memory:main`;
|
|
2655
|
+
return new KVBackedMemoryStore(kv, storageKey, ttl);
|
|
2729
2656
|
}
|
|
2730
2657
|
return new InMemoryMemoryStore(ttl);
|
|
2731
2658
|
};
|
|
@@ -2775,19 +2702,14 @@ var createMemoryTools = (store, options) => {
|
|
|
2775
2702
|
}
|
|
2776
2703
|
}),
|
|
2777
2704
|
defineTool2({
|
|
2778
|
-
name: "
|
|
2779
|
-
description: "
|
|
2705
|
+
name: "memory_main_write",
|
|
2706
|
+
description: "Overwrite the entire persistent main memory document. Use for initial writes or full rewrites. Prefer memory_main_edit for targeted changes to existing memory.",
|
|
2780
2707
|
inputSchema: {
|
|
2781
2708
|
type: "object",
|
|
2782
2709
|
properties: {
|
|
2783
|
-
mode: {
|
|
2784
|
-
type: "string",
|
|
2785
|
-
enum: ["replace", "append"],
|
|
2786
|
-
description: "replace overwrites memory; append adds content to the end"
|
|
2787
|
-
},
|
|
2788
2710
|
content: {
|
|
2789
2711
|
type: "string",
|
|
2790
|
-
description: "The memory content to write"
|
|
2712
|
+
description: "The full memory content to write"
|
|
2791
2713
|
}
|
|
2792
2714
|
},
|
|
2793
2715
|
required: ["content"],
|
|
@@ -2798,8 +2720,50 @@ var createMemoryTools = (store, options) => {
|
|
|
2798
2720
|
if (!content) {
|
|
2799
2721
|
throw new Error("content is required");
|
|
2800
2722
|
}
|
|
2801
|
-
const
|
|
2802
|
-
|
|
2723
|
+
const memory = await store.updateMainMemory({ content });
|
|
2724
|
+
return { ok: true, memory };
|
|
2725
|
+
}
|
|
2726
|
+
}),
|
|
2727
|
+
defineTool2({
|
|
2728
|
+
name: "memory_main_edit",
|
|
2729
|
+
description: "Edit persistent main memory by replacing an exact string match with new content. The old_str must match exactly one location in memory. Use an empty new_str to delete matched content. Proactively evaluate every turn whether memory should be updated.",
|
|
2730
|
+
inputSchema: {
|
|
2731
|
+
type: "object",
|
|
2732
|
+
properties: {
|
|
2733
|
+
old_str: {
|
|
2734
|
+
type: "string",
|
|
2735
|
+
description: "The exact text to find and replace (must be unique in memory). Include surrounding context if needed to ensure uniqueness."
|
|
2736
|
+
},
|
|
2737
|
+
new_str: {
|
|
2738
|
+
type: "string",
|
|
2739
|
+
description: "The replacement text (use empty string to delete the matched content)"
|
|
2740
|
+
}
|
|
2741
|
+
},
|
|
2742
|
+
required: ["old_str", "new_str"],
|
|
2743
|
+
additionalProperties: false
|
|
2744
|
+
},
|
|
2745
|
+
handler: async (input) => {
|
|
2746
|
+
const oldStr = typeof input.old_str === "string" ? input.old_str : "";
|
|
2747
|
+
const newStr = typeof input.new_str === "string" ? input.new_str : "";
|
|
2748
|
+
if (!oldStr) {
|
|
2749
|
+
throw new Error("old_str must not be empty.");
|
|
2750
|
+
}
|
|
2751
|
+
const current = await store.getMainMemory();
|
|
2752
|
+
const content = current.content;
|
|
2753
|
+
const first = content.indexOf(oldStr);
|
|
2754
|
+
if (first === -1) {
|
|
2755
|
+
throw new Error(
|
|
2756
|
+
"old_str not found in memory. Make sure it matches exactly, including whitespace and line breaks."
|
|
2757
|
+
);
|
|
2758
|
+
}
|
|
2759
|
+
const last = content.lastIndexOf(oldStr);
|
|
2760
|
+
if (first !== last) {
|
|
2761
|
+
throw new Error(
|
|
2762
|
+
"old_str appears multiple times in memory. Please provide more context to ensure a unique match."
|
|
2763
|
+
);
|
|
2764
|
+
}
|
|
2765
|
+
const newContent = content.slice(0, first) + newStr + content.slice(first + oldStr.length);
|
|
2766
|
+
const memory = await store.updateMainMemory({ content: newContent });
|
|
2803
2767
|
return { ok: true, memory };
|
|
2804
2768
|
}
|
|
2805
2769
|
}),
|
|
@@ -2861,6 +2825,269 @@ ${item.content}`, query)
|
|
|
2861
2825
|
];
|
|
2862
2826
|
};
|
|
2863
2827
|
|
|
2828
|
+
// src/todo-tools.ts
|
|
2829
|
+
import { mkdir as mkdir4, readFile as readFile6, rename as rename2, writeFile as writeFile5 } from "fs/promises";
|
|
2830
|
+
import { dirname as dirname3, resolve as resolve7 } from "path";
|
|
2831
|
+
import { defineTool as defineTool3 } from "@poncho-ai/sdk";
|
|
2832
|
+
var VALID_STATUSES = ["pending", "in_progress", "completed"];
|
|
2833
|
+
var VALID_PRIORITIES = ["high", "medium", "low"];
|
|
2834
|
+
var TODOS_DIRECTORY = "todos";
|
|
2835
|
+
var writeJsonAtomic2 = async (filePath, payload) => {
|
|
2836
|
+
await mkdir4(dirname3(filePath), { recursive: true });
|
|
2837
|
+
const tmpPath = `${filePath}.tmp`;
|
|
2838
|
+
await writeFile5(tmpPath, JSON.stringify(payload, null, 2), "utf8");
|
|
2839
|
+
await rename2(tmpPath, filePath);
|
|
2840
|
+
};
|
|
2841
|
+
var parseTodoList = (raw) => {
|
|
2842
|
+
if (!Array.isArray(raw)) return [];
|
|
2843
|
+
return raw.filter(
|
|
2844
|
+
(item) => typeof item === "object" && item !== null && typeof item.id === "string" && typeof item.content === "string"
|
|
2845
|
+
);
|
|
2846
|
+
};
|
|
2847
|
+
var generateId = () => (globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`).slice(0, 8);
|
|
2848
|
+
var InMemoryTodoStore = class {
|
|
2849
|
+
store = /* @__PURE__ */ new Map();
|
|
2850
|
+
async get(conversationId) {
|
|
2851
|
+
return this.store.get(conversationId) ?? [];
|
|
2852
|
+
}
|
|
2853
|
+
async set(conversationId, todos) {
|
|
2854
|
+
this.store.set(conversationId, todos);
|
|
2855
|
+
}
|
|
2856
|
+
};
|
|
2857
|
+
var FileTodoStore = class {
|
|
2858
|
+
workingDir;
|
|
2859
|
+
todosDir = "";
|
|
2860
|
+
constructor(workingDir) {
|
|
2861
|
+
this.workingDir = workingDir;
|
|
2862
|
+
}
|
|
2863
|
+
async ensureTodosDir() {
|
|
2864
|
+
if (this.todosDir) return this.todosDir;
|
|
2865
|
+
const identity = await ensureAgentIdentity(this.workingDir);
|
|
2866
|
+
this.todosDir = resolve7(getAgentStoreDirectory(identity), TODOS_DIRECTORY);
|
|
2867
|
+
await mkdir4(this.todosDir, { recursive: true });
|
|
2868
|
+
return this.todosDir;
|
|
2869
|
+
}
|
|
2870
|
+
async filePath(conversationId) {
|
|
2871
|
+
const dir = await this.ensureTodosDir();
|
|
2872
|
+
return resolve7(dir, `${slugifyStorageComponent(conversationId)}.json`);
|
|
2873
|
+
}
|
|
2874
|
+
async get(conversationId) {
|
|
2875
|
+
try {
|
|
2876
|
+
const fp = await this.filePath(conversationId);
|
|
2877
|
+
const raw = await readFile6(fp, "utf8");
|
|
2878
|
+
return parseTodoList(JSON.parse(raw));
|
|
2879
|
+
} catch {
|
|
2880
|
+
return [];
|
|
2881
|
+
}
|
|
2882
|
+
}
|
|
2883
|
+
async set(conversationId, todos) {
|
|
2884
|
+
const fp = await this.filePath(conversationId);
|
|
2885
|
+
await writeJsonAtomic2(fp, todos);
|
|
2886
|
+
}
|
|
2887
|
+
};
|
|
2888
|
+
var KVBackedTodoStore = class {
|
|
2889
|
+
kv;
|
|
2890
|
+
baseKey;
|
|
2891
|
+
ttl;
|
|
2892
|
+
memoryFallback = new InMemoryTodoStore();
|
|
2893
|
+
constructor(kv, baseKey, ttl) {
|
|
2894
|
+
this.kv = kv;
|
|
2895
|
+
this.baseKey = baseKey;
|
|
2896
|
+
this.ttl = ttl;
|
|
2897
|
+
}
|
|
2898
|
+
keyFor(conversationId) {
|
|
2899
|
+
return `${this.baseKey}:${slugifyStorageComponent(conversationId)}`;
|
|
2900
|
+
}
|
|
2901
|
+
async get(conversationId) {
|
|
2902
|
+
try {
|
|
2903
|
+
const raw = await this.kv.get(this.keyFor(conversationId));
|
|
2904
|
+
if (!raw) return [];
|
|
2905
|
+
return parseTodoList(JSON.parse(raw));
|
|
2906
|
+
} catch {
|
|
2907
|
+
return this.memoryFallback.get(conversationId);
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
async set(conversationId, todos) {
|
|
2911
|
+
try {
|
|
2912
|
+
const serialized = JSON.stringify(todos);
|
|
2913
|
+
const key = this.keyFor(conversationId);
|
|
2914
|
+
if (typeof this.ttl === "number") {
|
|
2915
|
+
await this.kv.setWithTtl(key, serialized, Math.max(1, this.ttl));
|
|
2916
|
+
} else {
|
|
2917
|
+
await this.kv.set(key, serialized);
|
|
2918
|
+
}
|
|
2919
|
+
} catch {
|
|
2920
|
+
await this.memoryFallback.set(conversationId, todos);
|
|
2921
|
+
}
|
|
2922
|
+
}
|
|
2923
|
+
};
|
|
2924
|
+
var createTodoStore = (agentId, config, options) => {
|
|
2925
|
+
const provider = config?.provider ?? "local";
|
|
2926
|
+
const ttl = config?.ttl;
|
|
2927
|
+
const workingDir = options?.workingDir ?? process.cwd();
|
|
2928
|
+
if (provider === "local") {
|
|
2929
|
+
return new FileTodoStore(workingDir);
|
|
2930
|
+
}
|
|
2931
|
+
if (provider === "memory") {
|
|
2932
|
+
return new InMemoryTodoStore();
|
|
2933
|
+
}
|
|
2934
|
+
const kv = createRawKVStore(config);
|
|
2935
|
+
if (kv) {
|
|
2936
|
+
const baseKey = `poncho:${STORAGE_SCHEMA_VERSION}:${slugifyStorageComponent(agentId)}:todos`;
|
|
2937
|
+
return new KVBackedTodoStore(kv, baseKey, ttl);
|
|
2938
|
+
}
|
|
2939
|
+
return new InMemoryTodoStore();
|
|
2940
|
+
};
|
|
2941
|
+
var createTodoTools = (store) => {
|
|
2942
|
+
const resolveKey = (context) => context.conversationId || context.runId;
|
|
2943
|
+
return [
|
|
2944
|
+
defineTool3({
|
|
2945
|
+
name: "todo_list",
|
|
2946
|
+
description: "List all todo items for the current conversation. Use this to check progress and plan next steps.",
|
|
2947
|
+
inputSchema: {
|
|
2948
|
+
type: "object",
|
|
2949
|
+
properties: {
|
|
2950
|
+
status: {
|
|
2951
|
+
type: "string",
|
|
2952
|
+
enum: VALID_STATUSES,
|
|
2953
|
+
description: "Filter by status (omit to list all)"
|
|
2954
|
+
}
|
|
2955
|
+
},
|
|
2956
|
+
additionalProperties: false
|
|
2957
|
+
},
|
|
2958
|
+
handler: async (input, context) => {
|
|
2959
|
+
const key = resolveKey(context);
|
|
2960
|
+
let todos = await store.get(key);
|
|
2961
|
+
const status = typeof input.status === "string" ? input.status : void 0;
|
|
2962
|
+
if (status && VALID_STATUSES.includes(status)) {
|
|
2963
|
+
todos = todos.filter((t) => t.status === status);
|
|
2964
|
+
}
|
|
2965
|
+
return { todos, count: todos.length };
|
|
2966
|
+
}
|
|
2967
|
+
}),
|
|
2968
|
+
defineTool3({
|
|
2969
|
+
name: "todo_add",
|
|
2970
|
+
description: "Add a new todo item for the current conversation. Use proactively for complex multi-step tasks (3+ steps).",
|
|
2971
|
+
inputSchema: {
|
|
2972
|
+
type: "object",
|
|
2973
|
+
properties: {
|
|
2974
|
+
content: {
|
|
2975
|
+
type: "string",
|
|
2976
|
+
description: "Description of the task"
|
|
2977
|
+
},
|
|
2978
|
+
status: {
|
|
2979
|
+
type: "string",
|
|
2980
|
+
enum: VALID_STATUSES,
|
|
2981
|
+
description: "Initial status (default: pending)"
|
|
2982
|
+
},
|
|
2983
|
+
priority: {
|
|
2984
|
+
type: "string",
|
|
2985
|
+
enum: VALID_PRIORITIES,
|
|
2986
|
+
description: "Priority level (default: medium)"
|
|
2987
|
+
}
|
|
2988
|
+
},
|
|
2989
|
+
required: ["content"],
|
|
2990
|
+
additionalProperties: false
|
|
2991
|
+
},
|
|
2992
|
+
handler: async (input, context) => {
|
|
2993
|
+
const content = typeof input.content === "string" ? input.content.trim() : "";
|
|
2994
|
+
if (!content) throw new Error("content is required");
|
|
2995
|
+
const status = typeof input.status === "string" && VALID_STATUSES.includes(input.status) ? input.status : "pending";
|
|
2996
|
+
const priority = typeof input.priority === "string" && VALID_PRIORITIES.includes(input.priority) ? input.priority : "medium";
|
|
2997
|
+
const now2 = Date.now();
|
|
2998
|
+
const todo = {
|
|
2999
|
+
id: generateId(),
|
|
3000
|
+
content,
|
|
3001
|
+
status,
|
|
3002
|
+
priority,
|
|
3003
|
+
createdAt: now2,
|
|
3004
|
+
updatedAt: now2
|
|
3005
|
+
};
|
|
3006
|
+
const key = resolveKey(context);
|
|
3007
|
+
const todos = await store.get(key);
|
|
3008
|
+
todos.push(todo);
|
|
3009
|
+
await store.set(key, todos);
|
|
3010
|
+
return { todo, todos };
|
|
3011
|
+
}
|
|
3012
|
+
}),
|
|
3013
|
+
defineTool3({
|
|
3014
|
+
name: "todo_update",
|
|
3015
|
+
description: "Update an existing todo item's status, content, or priority. Mark tasks in_progress when starting and completed when done.",
|
|
3016
|
+
inputSchema: {
|
|
3017
|
+
type: "object",
|
|
3018
|
+
properties: {
|
|
3019
|
+
id: {
|
|
3020
|
+
type: "string",
|
|
3021
|
+
description: "ID of the todo to update"
|
|
3022
|
+
},
|
|
3023
|
+
status: {
|
|
3024
|
+
type: "string",
|
|
3025
|
+
enum: VALID_STATUSES,
|
|
3026
|
+
description: "New status"
|
|
3027
|
+
},
|
|
3028
|
+
content: {
|
|
3029
|
+
type: "string",
|
|
3030
|
+
description: "New content/description"
|
|
3031
|
+
},
|
|
3032
|
+
priority: {
|
|
3033
|
+
type: "string",
|
|
3034
|
+
enum: VALID_PRIORITIES,
|
|
3035
|
+
description: "New priority level"
|
|
3036
|
+
}
|
|
3037
|
+
},
|
|
3038
|
+
required: ["id"],
|
|
3039
|
+
additionalProperties: false
|
|
3040
|
+
},
|
|
3041
|
+
handler: async (input, context) => {
|
|
3042
|
+
const id = typeof input.id === "string" ? input.id : "";
|
|
3043
|
+
if (!id) throw new Error("id is required");
|
|
3044
|
+
const key = resolveKey(context);
|
|
3045
|
+
const todos = await store.get(key);
|
|
3046
|
+
const todo = todos.find((t) => t.id === id);
|
|
3047
|
+
if (!todo) throw new Error(`Todo with id "${id}" not found`);
|
|
3048
|
+
if (typeof input.status === "string" && VALID_STATUSES.includes(input.status)) {
|
|
3049
|
+
todo.status = input.status;
|
|
3050
|
+
}
|
|
3051
|
+
if (typeof input.content === "string" && input.content.trim()) {
|
|
3052
|
+
todo.content = input.content.trim();
|
|
3053
|
+
}
|
|
3054
|
+
if (typeof input.priority === "string" && VALID_PRIORITIES.includes(input.priority)) {
|
|
3055
|
+
todo.priority = input.priority;
|
|
3056
|
+
}
|
|
3057
|
+
todo.updatedAt = Date.now();
|
|
3058
|
+
await store.set(key, todos);
|
|
3059
|
+
return { todo, todos };
|
|
3060
|
+
}
|
|
3061
|
+
}),
|
|
3062
|
+
defineTool3({
|
|
3063
|
+
name: "todo_remove",
|
|
3064
|
+
description: "Remove a todo item by ID.",
|
|
3065
|
+
inputSchema: {
|
|
3066
|
+
type: "object",
|
|
3067
|
+
properties: {
|
|
3068
|
+
id: {
|
|
3069
|
+
type: "string",
|
|
3070
|
+
description: "ID of the todo to remove"
|
|
3071
|
+
}
|
|
3072
|
+
},
|
|
3073
|
+
required: ["id"],
|
|
3074
|
+
additionalProperties: false
|
|
3075
|
+
},
|
|
3076
|
+
handler: async (input, context) => {
|
|
3077
|
+
const id = typeof input.id === "string" ? input.id : "";
|
|
3078
|
+
if (!id) throw new Error("id is required");
|
|
3079
|
+
const key = resolveKey(context);
|
|
3080
|
+
const todos = await store.get(key);
|
|
3081
|
+
const index = todos.findIndex((t) => t.id === id);
|
|
3082
|
+
if (index === -1) throw new Error(`Todo with id "${id}" not found`);
|
|
3083
|
+
const [removed] = todos.splice(index, 1);
|
|
3084
|
+
await store.set(key, todos);
|
|
3085
|
+
return { removed, todos };
|
|
3086
|
+
}
|
|
3087
|
+
})
|
|
3088
|
+
];
|
|
3089
|
+
};
|
|
3090
|
+
|
|
2864
3091
|
// src/mcp.ts
|
|
2865
3092
|
var McpHttpError = class extends Error {
|
|
2866
3093
|
status;
|
|
@@ -3355,8 +3582,8 @@ var createModelProvider = (provider, config) => {
|
|
|
3355
3582
|
};
|
|
3356
3583
|
|
|
3357
3584
|
// src/skill-context.ts
|
|
3358
|
-
import { readFile as
|
|
3359
|
-
import { dirname as
|
|
3585
|
+
import { readFile as readFile7, readdir as readdir2, stat } from "fs/promises";
|
|
3586
|
+
import { dirname as dirname4, resolve as resolve8, normalize } from "path";
|
|
3360
3587
|
import YAML3 from "yaml";
|
|
3361
3588
|
var DEFAULT_SKILL_DIRS = ["skills"];
|
|
3362
3589
|
var resolveSkillDirs = (workingDir, extraPaths) => {
|
|
@@ -3368,7 +3595,7 @@ var resolveSkillDirs = (workingDir, extraPaths) => {
|
|
|
3368
3595
|
}
|
|
3369
3596
|
}
|
|
3370
3597
|
}
|
|
3371
|
-
return dirs.map((d) =>
|
|
3598
|
+
return dirs.map((d) => resolve8(workingDir, d));
|
|
3372
3599
|
};
|
|
3373
3600
|
var FRONTMATTER_PATTERN3 = /^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/;
|
|
3374
3601
|
var asRecord2 = (value) => typeof value === "object" && value !== null ? value : {};
|
|
@@ -3437,7 +3664,7 @@ var collectSkillManifests = async (directory) => {
|
|
|
3437
3664
|
const entries = await readdir2(directory, { withFileTypes: true });
|
|
3438
3665
|
const files = [];
|
|
3439
3666
|
for (const entry of entries) {
|
|
3440
|
-
const fullPath =
|
|
3667
|
+
const fullPath = resolve8(directory, entry.name);
|
|
3441
3668
|
let isDir = entry.isDirectory();
|
|
3442
3669
|
let isFile = entry.isFile();
|
|
3443
3670
|
if (entry.isSymbolicLink()) {
|
|
@@ -3472,13 +3699,13 @@ var loadSkillMetadata = async (workingDir, extraSkillPaths) => {
|
|
|
3472
3699
|
const seen = /* @__PURE__ */ new Set();
|
|
3473
3700
|
for (const manifest of allManifests) {
|
|
3474
3701
|
try {
|
|
3475
|
-
const content = await
|
|
3702
|
+
const content = await readFile7(manifest, "utf8");
|
|
3476
3703
|
const parsed = parseSkillFrontmatter(content);
|
|
3477
3704
|
if (parsed && !seen.has(parsed.name)) {
|
|
3478
3705
|
seen.add(parsed.name);
|
|
3479
3706
|
skills.push({
|
|
3480
3707
|
...parsed,
|
|
3481
|
-
skillDir:
|
|
3708
|
+
skillDir: dirname4(manifest),
|
|
3482
3709
|
skillPath: manifest
|
|
3483
3710
|
});
|
|
3484
3711
|
}
|
|
@@ -3517,7 +3744,7 @@ ${xmlSkills}
|
|
|
3517
3744
|
};
|
|
3518
3745
|
var escapeXml = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
3519
3746
|
var loadSkillInstructions = async (skill) => {
|
|
3520
|
-
const content = await
|
|
3747
|
+
const content = await readFile7(skill.skillPath, "utf8");
|
|
3521
3748
|
const match = content.match(FRONTMATTER_PATTERN3);
|
|
3522
3749
|
return match ? match[2].trim() : content.trim();
|
|
3523
3750
|
};
|
|
@@ -3526,11 +3753,11 @@ var readSkillResource = async (skill, relativePath) => {
|
|
|
3526
3753
|
if (normalized.startsWith("..") || normalized.startsWith("/")) {
|
|
3527
3754
|
throw new Error("Path must be relative and within the skill directory");
|
|
3528
3755
|
}
|
|
3529
|
-
const fullPath =
|
|
3756
|
+
const fullPath = resolve8(skill.skillDir, normalized);
|
|
3530
3757
|
if (!fullPath.startsWith(skill.skillDir)) {
|
|
3531
3758
|
throw new Error("Path escapes the skill directory");
|
|
3532
3759
|
}
|
|
3533
|
-
return await
|
|
3760
|
+
return await readFile7(fullPath, "utf8");
|
|
3534
3761
|
};
|
|
3535
3762
|
var MAX_INSTRUCTIONS_PER_SKILL = 1200;
|
|
3536
3763
|
var loadSkillContext = async (workingDir) => {
|
|
@@ -3647,16 +3874,16 @@ function convertSchema(schema) {
|
|
|
3647
3874
|
}
|
|
3648
3875
|
|
|
3649
3876
|
// src/skill-tools.ts
|
|
3650
|
-
import { defineTool as
|
|
3877
|
+
import { defineTool as defineTool4 } from "@poncho-ai/sdk";
|
|
3651
3878
|
import { access as access2, readdir as readdir3, stat as stat2 } from "fs/promises";
|
|
3652
|
-
import { extname, normalize as normalize2, resolve as
|
|
3879
|
+
import { extname, normalize as normalize2, resolve as resolve9, sep as sep2 } from "path";
|
|
3653
3880
|
import { pathToFileURL } from "url";
|
|
3654
3881
|
import { createJiti as createJiti2 } from "jiti";
|
|
3655
3882
|
var createSkillTools = (skills, options) => {
|
|
3656
3883
|
const skillsByName = new Map(skills.map((skill) => [skill.name, skill]));
|
|
3657
3884
|
const knownNames = skills.length > 0 ? skills.map((skill) => skill.name).join(", ") : "(none)";
|
|
3658
3885
|
return [
|
|
3659
|
-
|
|
3886
|
+
defineTool4({
|
|
3660
3887
|
name: "activate_skill",
|
|
3661
3888
|
description: `Load the full instructions for an available skill. Use this when a user's request matches a skill's description. Available skills: ${knownNames}`,
|
|
3662
3889
|
inputSchema: {
|
|
@@ -3693,7 +3920,7 @@ var createSkillTools = (skills, options) => {
|
|
|
3693
3920
|
}
|
|
3694
3921
|
}
|
|
3695
3922
|
}),
|
|
3696
|
-
|
|
3923
|
+
defineTool4({
|
|
3697
3924
|
name: "deactivate_skill",
|
|
3698
3925
|
description: "Deactivate a previously activated skill and update scoped tool availability.",
|
|
3699
3926
|
inputSchema: {
|
|
@@ -3722,7 +3949,7 @@ var createSkillTools = (skills, options) => {
|
|
|
3722
3949
|
}
|
|
3723
3950
|
}
|
|
3724
3951
|
}),
|
|
3725
|
-
|
|
3952
|
+
defineTool4({
|
|
3726
3953
|
name: "list_active_skills",
|
|
3727
3954
|
description: "List currently active skills with scoped MCP tools.",
|
|
3728
3955
|
inputSchema: {
|
|
@@ -3734,7 +3961,7 @@ var createSkillTools = (skills, options) => {
|
|
|
3734
3961
|
activeSkills: options?.onListActiveSkills ? options.onListActiveSkills() : []
|
|
3735
3962
|
})
|
|
3736
3963
|
}),
|
|
3737
|
-
|
|
3964
|
+
defineTool4({
|
|
3738
3965
|
name: "read_skill_resource",
|
|
3739
3966
|
description: `Read a file from a skill's directory (references, scripts, assets). Use relative paths from the skill root. Available skills: ${knownNames}`,
|
|
3740
3967
|
inputSchema: {
|
|
@@ -3774,7 +4001,7 @@ var createSkillTools = (skills, options) => {
|
|
|
3774
4001
|
}
|
|
3775
4002
|
}
|
|
3776
4003
|
}),
|
|
3777
|
-
|
|
4004
|
+
defineTool4({
|
|
3778
4005
|
name: "list_skill_scripts",
|
|
3779
4006
|
description: `List JavaScript/TypeScript script files available under a skill directory (recursive). Available skills: ${knownNames}`,
|
|
3780
4007
|
inputSchema: {
|
|
@@ -3809,7 +4036,7 @@ var createSkillTools = (skills, options) => {
|
|
|
3809
4036
|
}
|
|
3810
4037
|
}
|
|
3811
4038
|
}),
|
|
3812
|
-
|
|
4039
|
+
defineTool4({
|
|
3813
4040
|
name: "run_skill_script",
|
|
3814
4041
|
description: `Run a JavaScript/TypeScript module in a skill or project directory. Uses default export function or named run/main/handler function. Available skills: ${knownNames}`,
|
|
3815
4042
|
inputSchema: {
|
|
@@ -3906,7 +4133,7 @@ var collectScriptFiles = async (directory) => {
|
|
|
3906
4133
|
if (entry.name === "node_modules") {
|
|
3907
4134
|
continue;
|
|
3908
4135
|
}
|
|
3909
|
-
const fullPath =
|
|
4136
|
+
const fullPath = resolve9(directory, entry.name);
|
|
3910
4137
|
let isDir = entry.isDirectory();
|
|
3911
4138
|
let isFile = entry.isFile();
|
|
3912
4139
|
if (entry.isSymbolicLink()) {
|
|
@@ -3945,8 +4172,8 @@ var normalizeScriptPolicyPath = (relativePath) => {
|
|
|
3945
4172
|
};
|
|
3946
4173
|
var resolveScriptPath = (baseDir, relativePath) => {
|
|
3947
4174
|
const normalized = normalizeScriptPolicyPath(relativePath);
|
|
3948
|
-
const fullPath =
|
|
3949
|
-
if (!fullPath.startsWith(`${
|
|
4175
|
+
const fullPath = resolve9(baseDir, normalized);
|
|
4176
|
+
if (!fullPath.startsWith(`${resolve9(baseDir)}${sep2}`) && fullPath !== resolve9(baseDir)) {
|
|
3950
4177
|
throw new Error("Script path must stay inside the allowed directory");
|
|
3951
4178
|
}
|
|
3952
4179
|
const extension = extname(fullPath).toLowerCase();
|
|
@@ -4020,7 +4247,7 @@ var extractRunnableFunction = (value) => {
|
|
|
4020
4247
|
};
|
|
4021
4248
|
|
|
4022
4249
|
// src/subagent-tools.ts
|
|
4023
|
-
import { defineTool as
|
|
4250
|
+
import { defineTool as defineTool5, getTextContent as getTextContent2 } from "@poncho-ai/sdk";
|
|
4024
4251
|
var LAST_MESSAGES_TO_RETURN = 10;
|
|
4025
4252
|
var summarizeResult = (r) => {
|
|
4026
4253
|
const summary = {
|
|
@@ -4047,7 +4274,7 @@ var summarizeResult = (r) => {
|
|
|
4047
4274
|
return summary;
|
|
4048
4275
|
};
|
|
4049
4276
|
var createSubagentTools = (manager, getConversationId, getOwnerId) => [
|
|
4050
|
-
|
|
4277
|
+
defineTool5({
|
|
4051
4278
|
name: "spawn_subagent",
|
|
4052
4279
|
description: "Spawn a subagent to work on a task and wait for it to finish. The subagent is a full copy of yourself running in its own conversation context with access to the same tools (except memory writes). This call blocks until the subagent completes and returns its result.\n\nGuidelines:\n- Use subagents to parallelize work: call spawn_subagent multiple times in one response for independent sub-tasks -- they run concurrently.\n- Prefer doing work yourself for simple or quick tasks. Spawn subagents for substantial, self-contained work.\n- The subagent has no memory of your conversation -- write thorough, self-contained instructions in the task.",
|
|
4053
4280
|
inputSchema: {
|
|
@@ -4078,7 +4305,7 @@ var createSubagentTools = (manager, getConversationId, getOwnerId) => [
|
|
|
4078
4305
|
return summarizeResult(result);
|
|
4079
4306
|
}
|
|
4080
4307
|
}),
|
|
4081
|
-
|
|
4308
|
+
defineTool5({
|
|
4082
4309
|
name: "message_subagent",
|
|
4083
4310
|
description: "Send a follow-up message to a completed or stopped subagent and wait for it to finish. This restarts the subagent with the new message and blocks until it completes. Only works when the subagent is not currently running.",
|
|
4084
4311
|
inputSchema: {
|
|
@@ -4106,7 +4333,7 @@ var createSubagentTools = (manager, getConversationId, getOwnerId) => [
|
|
|
4106
4333
|
return summarizeResult(result);
|
|
4107
4334
|
}
|
|
4108
4335
|
}),
|
|
4109
|
-
|
|
4336
|
+
defineTool5({
|
|
4110
4337
|
name: "stop_subagent",
|
|
4111
4338
|
description: "Stop a running subagent. The subagent's conversation is preserved but it will stop processing. Use this to cancel work that is no longer needed.",
|
|
4112
4339
|
inputSchema: {
|
|
@@ -4129,7 +4356,7 @@ var createSubagentTools = (manager, getConversationId, getOwnerId) => [
|
|
|
4129
4356
|
return { message: `Subagent "${subagentId}" has been stopped.` };
|
|
4130
4357
|
}
|
|
4131
4358
|
}),
|
|
4132
|
-
|
|
4359
|
+
defineTool5({
|
|
4133
4360
|
name: "list_subagents",
|
|
4134
4361
|
description: "List all subagents that have been spawned in this conversation. Returns each subagent's ID, original task, current status, and message count. Use this to look up subagent IDs before calling message_subagent or stop_subagent.",
|
|
4135
4362
|
inputSchema: {
|
|
@@ -4677,6 +4904,7 @@ var AgentHarness = class _AgentHarness {
|
|
|
4677
4904
|
uploadStore;
|
|
4678
4905
|
skillContextWindow = "";
|
|
4679
4906
|
memoryStore;
|
|
4907
|
+
todoStore;
|
|
4680
4908
|
loadedConfig;
|
|
4681
4909
|
loadedSkills = [];
|
|
4682
4910
|
skillFingerprint = "";
|
|
@@ -4688,6 +4916,7 @@ var AgentHarness = class _AgentHarness {
|
|
|
4688
4916
|
_browserSession;
|
|
4689
4917
|
_browserMod;
|
|
4690
4918
|
parsedAgent;
|
|
4919
|
+
agentFileFingerprint = "";
|
|
4691
4920
|
mcpBridge;
|
|
4692
4921
|
subagentManager;
|
|
4693
4922
|
resolveToolAccess(toolName) {
|
|
@@ -4784,6 +5013,10 @@ var AgentHarness = class _AgentHarness {
|
|
|
4784
5013
|
get frontmatter() {
|
|
4785
5014
|
return this.parsedAgent?.frontmatter;
|
|
4786
5015
|
}
|
|
5016
|
+
async getTodos(conversationId) {
|
|
5017
|
+
if (!this.todoStore) return [];
|
|
5018
|
+
return this.todoStore.get(conversationId);
|
|
5019
|
+
}
|
|
4787
5020
|
listActiveSkills() {
|
|
4788
5021
|
return [...this.activeSkillNames].sort();
|
|
4789
5022
|
}
|
|
@@ -4800,20 +5033,17 @@ var AgentHarness = class _AgentHarness {
|
|
|
4800
5033
|
return this.parsedAgent?.frontmatter.approvalRequired?.scripts ?? [];
|
|
4801
5034
|
}
|
|
4802
5035
|
getRequestedMcpPatterns() {
|
|
4803
|
-
const
|
|
5036
|
+
const patterns = new Set(this.getAgentMcpIntent());
|
|
4804
5037
|
for (const skillName of this.activeSkillNames) {
|
|
4805
5038
|
const skill = this.loadedSkills.find((entry) => entry.name === skillName);
|
|
4806
5039
|
if (!skill) {
|
|
4807
5040
|
continue;
|
|
4808
5041
|
}
|
|
4809
5042
|
for (const pattern of skill.allowedTools.mcp) {
|
|
4810
|
-
|
|
5043
|
+
patterns.add(pattern);
|
|
4811
5044
|
}
|
|
4812
5045
|
}
|
|
4813
|
-
|
|
4814
|
-
return [...skillPatterns];
|
|
4815
|
-
}
|
|
4816
|
-
return this.getAgentMcpIntent();
|
|
5046
|
+
return [...patterns];
|
|
4817
5047
|
}
|
|
4818
5048
|
getRequestedScriptPatterns() {
|
|
4819
5049
|
const patterns = new Set(this.getAgentScriptIntent());
|
|
@@ -4829,20 +5059,17 @@ var AgentHarness = class _AgentHarness {
|
|
|
4829
5059
|
return [...patterns];
|
|
4830
5060
|
}
|
|
4831
5061
|
getRequestedMcpApprovalPatterns() {
|
|
4832
|
-
const
|
|
5062
|
+
const patterns = new Set(this.getAgentMcpApprovalPatterns());
|
|
4833
5063
|
for (const skillName of this.activeSkillNames) {
|
|
4834
5064
|
const skill = this.loadedSkills.find((entry) => entry.name === skillName);
|
|
4835
5065
|
if (!skill) {
|
|
4836
5066
|
continue;
|
|
4837
5067
|
}
|
|
4838
5068
|
for (const pattern of skill.approvalRequired.mcp) {
|
|
4839
|
-
|
|
5069
|
+
patterns.add(pattern);
|
|
4840
5070
|
}
|
|
4841
5071
|
}
|
|
4842
|
-
|
|
4843
|
-
return [...skillPatterns];
|
|
4844
|
-
}
|
|
4845
|
-
return this.getAgentMcpApprovalPatterns();
|
|
5072
|
+
return [...patterns];
|
|
4846
5073
|
}
|
|
4847
5074
|
getRequestedScriptApprovalPatterns() {
|
|
4848
5075
|
const patterns = new Set(this.getAgentScriptApprovalPatterns());
|
|
@@ -4973,13 +5200,54 @@ var AgentHarness = class _AgentHarness {
|
|
|
4973
5200
|
);
|
|
4974
5201
|
}
|
|
4975
5202
|
static SKILL_REFRESH_DEBOUNCE_MS = 3e3;
|
|
4976
|
-
|
|
5203
|
+
/**
|
|
5204
|
+
* Re-read AGENT.md and update the parsed agent when the file has changed
|
|
5205
|
+
* on disk. Returns `true` when the agent was actually re-parsed.
|
|
5206
|
+
*
|
|
5207
|
+
* Preserves the agent identity (id) across reloads so conversation
|
|
5208
|
+
* continuity isn't broken.
|
|
5209
|
+
*/
|
|
5210
|
+
async refreshAgentIfChanged() {
|
|
4977
5211
|
if (this.environment !== "development") {
|
|
4978
|
-
return;
|
|
5212
|
+
return false;
|
|
4979
5213
|
}
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
5214
|
+
try {
|
|
5215
|
+
const agentFilePath = resolve10(this.workingDir, "AGENT.md");
|
|
5216
|
+
const rawContent = await readFile8(agentFilePath, "utf8");
|
|
5217
|
+
if (rawContent === this.agentFileFingerprint) {
|
|
5218
|
+
return false;
|
|
5219
|
+
}
|
|
5220
|
+
const parsed = parseAgentMarkdown(rawContent);
|
|
5221
|
+
if (!parsed.frontmatter.id && this.parsedAgent?.frontmatter.id) {
|
|
5222
|
+
parsed.frontmatter.id = this.parsedAgent.frontmatter.id;
|
|
5223
|
+
}
|
|
5224
|
+
this.parsedAgent = parsed;
|
|
5225
|
+
this.agentFileFingerprint = rawContent;
|
|
5226
|
+
return true;
|
|
5227
|
+
} catch (error) {
|
|
5228
|
+
console.warn(
|
|
5229
|
+
`[poncho][agent] Failed to refresh AGENT.md in development mode: ${error instanceof Error ? error.message : String(error)}`
|
|
5230
|
+
);
|
|
5231
|
+
return false;
|
|
5232
|
+
}
|
|
5233
|
+
}
|
|
5234
|
+
/**
|
|
5235
|
+
* Re-scan skill directories and update metadata, tools, and context window
|
|
5236
|
+
* when skills have changed on disk. Returns `true` when the skill set was
|
|
5237
|
+
* actually updated.
|
|
5238
|
+
*
|
|
5239
|
+
* @param force - bypass the time-based debounce (used for mid-run refreshes
|
|
5240
|
+
* after the agent may have written new skill files).
|
|
5241
|
+
*/
|
|
5242
|
+
async refreshSkillsIfChanged(force = false) {
|
|
5243
|
+
if (this.environment !== "development") {
|
|
5244
|
+
return false;
|
|
5245
|
+
}
|
|
5246
|
+
if (!force) {
|
|
5247
|
+
const elapsed = Date.now() - this.lastSkillRefreshAt;
|
|
5248
|
+
if (this.lastSkillRefreshAt > 0 && elapsed < _AgentHarness.SKILL_REFRESH_DEBOUNCE_MS) {
|
|
5249
|
+
return false;
|
|
5250
|
+
}
|
|
4983
5251
|
}
|
|
4984
5252
|
this.lastSkillRefreshAt = Date.now();
|
|
4985
5253
|
try {
|
|
@@ -4989,22 +5257,35 @@ var AgentHarness = class _AgentHarness {
|
|
|
4989
5257
|
);
|
|
4990
5258
|
const nextFingerprint = this.buildSkillFingerprint(latestSkills);
|
|
4991
5259
|
if (nextFingerprint === this.skillFingerprint) {
|
|
4992
|
-
return;
|
|
5260
|
+
return false;
|
|
4993
5261
|
}
|
|
4994
5262
|
this.loadedSkills = latestSkills;
|
|
4995
5263
|
this.skillContextWindow = buildSkillContextWindow(latestSkills);
|
|
4996
5264
|
this.skillFingerprint = nextFingerprint;
|
|
4997
5265
|
this.registerSkillTools(latestSkills);
|
|
4998
|
-
|
|
5266
|
+
const latestSkillNames = new Set(latestSkills.map((s) => s.name));
|
|
5267
|
+
for (const name of this.activeSkillNames) {
|
|
5268
|
+
if (!latestSkillNames.has(name)) {
|
|
5269
|
+
this.activeSkillNames.delete(name);
|
|
5270
|
+
}
|
|
5271
|
+
}
|
|
5272
|
+
if (this.mcpBridge) {
|
|
5273
|
+
await this.mcpBridge.discoverTools();
|
|
5274
|
+
}
|
|
4999
5275
|
await this.refreshMcpTools("skills:changed");
|
|
5276
|
+
return true;
|
|
5000
5277
|
} catch (error) {
|
|
5001
5278
|
console.warn(
|
|
5002
5279
|
`[poncho][skills] Failed to refresh skills in development mode: ${error instanceof Error ? error.message : String(error)}`
|
|
5003
5280
|
);
|
|
5281
|
+
return false;
|
|
5004
5282
|
}
|
|
5005
5283
|
}
|
|
5006
5284
|
async initialize() {
|
|
5007
|
-
|
|
5285
|
+
const agentFilePath = resolve10(this.workingDir, "AGENT.md");
|
|
5286
|
+
const agentRawContent = await readFile8(agentFilePath, "utf8");
|
|
5287
|
+
this.parsedAgent = parseAgentMarkdown(agentRawContent);
|
|
5288
|
+
this.agentFileFingerprint = agentRawContent;
|
|
5008
5289
|
const identity = await ensureAgentIdentity(this.workingDir);
|
|
5009
5290
|
if (!this.parsedAgent.frontmatter.id) {
|
|
5010
5291
|
this.parsedAgent.frontmatter.id = identity.id;
|
|
@@ -5025,8 +5306,8 @@ var AgentHarness = class _AgentHarness {
|
|
|
5025
5306
|
this.skillContextWindow = buildSkillContextWindow(skillMetadata);
|
|
5026
5307
|
this.skillFingerprint = this.buildSkillFingerprint(skillMetadata);
|
|
5027
5308
|
this.registerSkillTools(skillMetadata);
|
|
5309
|
+
const agentId = this.parsedAgent.frontmatter.id ?? this.parsedAgent.frontmatter.name;
|
|
5028
5310
|
if (memoryConfig?.enabled) {
|
|
5029
|
-
const agentId = this.parsedAgent.frontmatter.id ?? this.parsedAgent.frontmatter.name;
|
|
5030
5311
|
this.memoryStore = createMemoryStore(
|
|
5031
5312
|
agentId,
|
|
5032
5313
|
memoryConfig,
|
|
@@ -5038,6 +5319,13 @@ var AgentHarness = class _AgentHarness {
|
|
|
5038
5319
|
})
|
|
5039
5320
|
);
|
|
5040
5321
|
}
|
|
5322
|
+
const stateConfig = resolveStateConfig(config);
|
|
5323
|
+
this.todoStore = createTodoStore(agentId, stateConfig, { workingDir: this.workingDir });
|
|
5324
|
+
for (const tool of createTodoTools(this.todoStore)) {
|
|
5325
|
+
if (this.isToolEnabled(tool.name)) {
|
|
5326
|
+
this.registerIfMissing(tool);
|
|
5327
|
+
}
|
|
5328
|
+
}
|
|
5041
5329
|
if (config?.browser) {
|
|
5042
5330
|
await this.initBrowserTools(config).catch((e) => {
|
|
5043
5331
|
console.warn(
|
|
@@ -5077,14 +5365,14 @@ var AgentHarness = class _AgentHarness {
|
|
|
5077
5365
|
const filePath = pathResolve(stateDir, `${sessionId}.json`);
|
|
5078
5366
|
return {
|
|
5079
5367
|
async save(json) {
|
|
5080
|
-
const { mkdir:
|
|
5081
|
-
await
|
|
5082
|
-
await
|
|
5368
|
+
const { mkdir: mkdir6, writeFile: writeFile7 } = await import("fs/promises");
|
|
5369
|
+
await mkdir6(stateDir, { recursive: true });
|
|
5370
|
+
await writeFile7(filePath, json, "utf8");
|
|
5083
5371
|
},
|
|
5084
5372
|
async load() {
|
|
5085
|
-
const { readFile:
|
|
5373
|
+
const { readFile: readFile10 } = await import("fs/promises");
|
|
5086
5374
|
try {
|
|
5087
|
-
return await
|
|
5375
|
+
return await readFile10(filePath, "utf8");
|
|
5088
5376
|
} catch {
|
|
5089
5377
|
return void 0;
|
|
5090
5378
|
}
|
|
@@ -5150,7 +5438,7 @@ var AgentHarness = class _AgentHarness {
|
|
|
5150
5438
|
let browserMod;
|
|
5151
5439
|
try {
|
|
5152
5440
|
const { existsSync } = await import("fs");
|
|
5153
|
-
const { join, dirname:
|
|
5441
|
+
const { join, dirname: dirname6 } = await import("path");
|
|
5154
5442
|
const { pathToFileURL: pathToFileURL2 } = await import("url");
|
|
5155
5443
|
let searchDir = this.workingDir;
|
|
5156
5444
|
let entryPath;
|
|
@@ -5160,7 +5448,7 @@ var AgentHarness = class _AgentHarness {
|
|
|
5160
5448
|
entryPath = candidate;
|
|
5161
5449
|
break;
|
|
5162
5450
|
}
|
|
5163
|
-
const parent =
|
|
5451
|
+
const parent = dirname6(searchDir);
|
|
5164
5452
|
if (parent === searchDir) break;
|
|
5165
5453
|
searchDir = parent;
|
|
5166
5454
|
}
|
|
@@ -5248,9 +5536,9 @@ var AgentHarness = class _AgentHarness {
|
|
|
5248
5536
|
for await (const event of this.run(input)) {
|
|
5249
5537
|
eventQueue.push(event);
|
|
5250
5538
|
if (queueResolve) {
|
|
5251
|
-
const
|
|
5539
|
+
const resolve12 = queueResolve;
|
|
5252
5540
|
queueResolve = null;
|
|
5253
|
-
|
|
5541
|
+
resolve12();
|
|
5254
5542
|
}
|
|
5255
5543
|
}
|
|
5256
5544
|
} catch (error) {
|
|
@@ -5269,8 +5557,8 @@ var AgentHarness = class _AgentHarness {
|
|
|
5269
5557
|
if (eventQueue.length > 0) {
|
|
5270
5558
|
yield eventQueue.shift();
|
|
5271
5559
|
} else if (!generatorDone) {
|
|
5272
|
-
await new Promise((
|
|
5273
|
-
queueResolve =
|
|
5560
|
+
await new Promise((resolve12) => {
|
|
5561
|
+
queueResolve = resolve12;
|
|
5274
5562
|
});
|
|
5275
5563
|
}
|
|
5276
5564
|
}
|
|
@@ -5308,13 +5596,14 @@ var AgentHarness = class _AgentHarness {
|
|
|
5308
5596
|
await this.initialize();
|
|
5309
5597
|
}
|
|
5310
5598
|
const memoryPromise = this.memoryStore ? this.memoryStore.getMainMemory() : void 0;
|
|
5599
|
+
await this.refreshAgentIfChanged();
|
|
5311
5600
|
await this.refreshSkillsIfChanged();
|
|
5312
5601
|
this._currentRunConversationId = input.conversationId;
|
|
5313
5602
|
const ownerParam = input.parameters?.__ownerId;
|
|
5314
5603
|
if (typeof ownerParam === "string") {
|
|
5315
5604
|
this._currentRunOwnerId = ownerParam;
|
|
5316
5605
|
}
|
|
5317
|
-
|
|
5606
|
+
let agent = this.parsedAgent;
|
|
5318
5607
|
const runId = `run_${randomUUID3()}`;
|
|
5319
5608
|
const start = now();
|
|
5320
5609
|
const maxSteps = agent.frontmatter.limits?.maxSteps ?? 50;
|
|
@@ -5325,11 +5614,11 @@ var AgentHarness = class _AgentHarness {
|
|
|
5325
5614
|
const messages = [...input.messages ?? []];
|
|
5326
5615
|
const inputMessageCount = messages.length;
|
|
5327
5616
|
const events = [];
|
|
5328
|
-
const
|
|
5617
|
+
const renderCurrentAgentPrompt = () => renderAgentPrompt(this.parsedAgent, {
|
|
5329
5618
|
parameters: input.parameters,
|
|
5330
5619
|
runtime: {
|
|
5331
5620
|
runId,
|
|
5332
|
-
agentId:
|
|
5621
|
+
agentId: this.parsedAgent.frontmatter.id ?? this.parsedAgent.frontmatter.name,
|
|
5333
5622
|
environment: this.environment,
|
|
5334
5623
|
workingDir: this.workingDir
|
|
5335
5624
|
}
|
|
@@ -5357,9 +5646,6 @@ Browser sessions (cookies, localStorage, login state) are automatically saved an
|
|
|
5357
5646
|
|
|
5358
5647
|
### Tabs and resources
|
|
5359
5648
|
Each conversation gets its own browser tab sharing a single browser instance. Call \`browser_close\` when done to free the tab. If you don't close it, the tab stays open and the user can continue interacting with it.` : "";
|
|
5360
|
-
const promptWithSkills = this.skillContextWindow ? `${systemPrompt}${developmentContext}
|
|
5361
|
-
|
|
5362
|
-
${this.skillContextWindow}${browserContext}` : `${systemPrompt}${developmentContext}${browserContext}`;
|
|
5363
5649
|
const mainMemory = await memoryPromise;
|
|
5364
5650
|
const boundedMainMemory = mainMemory && mainMemory.content.length > 4e3 ? `${mainMemory.content.slice(0, 4e3)}
|
|
5365
5651
|
...[truncated]` : mainMemory?.content;
|
|
@@ -5367,7 +5653,12 @@ ${this.skillContextWindow}${browserContext}` : `${systemPrompt}${developmentCont
|
|
|
5367
5653
|
## Persistent Memory
|
|
5368
5654
|
|
|
5369
5655
|
${boundedMainMemory.trim()}` : "";
|
|
5370
|
-
const
|
|
5656
|
+
const buildSystemPrompt = () => {
|
|
5657
|
+
const agentPrompt = renderCurrentAgentPrompt();
|
|
5658
|
+
const promptWithSkills = this.skillContextWindow ? `${agentPrompt}${developmentContext}
|
|
5659
|
+
|
|
5660
|
+
${this.skillContextWindow}${browserContext}` : `${agentPrompt}${developmentContext}${browserContext}`;
|
|
5661
|
+
return `${promptWithSkills}${memoryContext}
|
|
5371
5662
|
|
|
5372
5663
|
## Execution Integrity
|
|
5373
5664
|
|
|
@@ -5375,6 +5666,10 @@ ${boundedMainMemory.trim()}` : "";
|
|
|
5375
5666
|
- Do not fabricate "Tool Used" or "Tool Result" logs as plain text.
|
|
5376
5667
|
- Never output faux execution transcripts, markdown tool logs, or "Tool Used/Result" sections.
|
|
5377
5668
|
- If no suitable tool is available, explicitly say that and ask for guidance.`;
|
|
5669
|
+
};
|
|
5670
|
+
let integrityPrompt = buildSystemPrompt();
|
|
5671
|
+
let lastPromptFingerprint = `${this.agentFileFingerprint}
|
|
5672
|
+
${this.skillFingerprint}`;
|
|
5378
5673
|
const pushEvent = (event) => {
|
|
5379
5674
|
events.push(event);
|
|
5380
5675
|
return event;
|
|
@@ -5797,8 +6092,8 @@ ${textContent}` };
|
|
|
5797
6092
|
let timer;
|
|
5798
6093
|
nextPart = await Promise.race([
|
|
5799
6094
|
fullStreamIterator.next(),
|
|
5800
|
-
new Promise((
|
|
5801
|
-
timer = setTimeout(() =>
|
|
6095
|
+
new Promise((resolve12) => {
|
|
6096
|
+
timer = setTimeout(() => resolve12(null), timeout);
|
|
5802
6097
|
})
|
|
5803
6098
|
]);
|
|
5804
6099
|
clearTimeout(timer);
|
|
@@ -6106,6 +6401,19 @@ ${textContent}` };
|
|
|
6106
6401
|
content: JSON.stringify(toolResultsForModel),
|
|
6107
6402
|
metadata: toolMsgMeta
|
|
6108
6403
|
});
|
|
6404
|
+
if (this.environment === "development") {
|
|
6405
|
+
const agentChanged = await this.refreshAgentIfChanged();
|
|
6406
|
+
const skillsChanged = await this.refreshSkillsIfChanged(true);
|
|
6407
|
+
if (agentChanged || skillsChanged) {
|
|
6408
|
+
agent = this.parsedAgent;
|
|
6409
|
+
const currentFingerprint = `${this.agentFileFingerprint}
|
|
6410
|
+
${this.skillFingerprint}`;
|
|
6411
|
+
if (currentFingerprint !== lastPromptFingerprint) {
|
|
6412
|
+
integrityPrompt = buildSystemPrompt();
|
|
6413
|
+
lastPromptFingerprint = currentFingerprint;
|
|
6414
|
+
}
|
|
6415
|
+
}
|
|
6416
|
+
}
|
|
6109
6417
|
yield pushEvent({
|
|
6110
6418
|
type: "step:completed",
|
|
6111
6419
|
step,
|
|
@@ -6294,8 +6602,8 @@ var LatitudeCapture = class {
|
|
|
6294
6602
|
|
|
6295
6603
|
// src/state.ts
|
|
6296
6604
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
6297
|
-
import { mkdir as
|
|
6298
|
-
import { dirname as
|
|
6605
|
+
import { mkdir as mkdir5, readFile as readFile9, readdir as readdir4, rename as rename3, rm as rm3, writeFile as writeFile6 } from "fs/promises";
|
|
6606
|
+
import { dirname as dirname5, resolve as resolve11 } from "path";
|
|
6299
6607
|
var DEFAULT_OWNER = "local-owner";
|
|
6300
6608
|
var LOCAL_STATE_FILE = "state.json";
|
|
6301
6609
|
var CONVERSATIONS_DIRECTORY = "conversations";
|
|
@@ -6310,11 +6618,11 @@ var toStoreIdentity = async ({
|
|
|
6310
6618
|
}
|
|
6311
6619
|
return { name: ensured.name, id: agentId };
|
|
6312
6620
|
};
|
|
6313
|
-
var
|
|
6314
|
-
await
|
|
6621
|
+
var writeJsonAtomic3 = async (filePath, payload) => {
|
|
6622
|
+
await mkdir5(dirname5(filePath), { recursive: true });
|
|
6315
6623
|
const tmpPath = `${filePath}.tmp`;
|
|
6316
|
-
await
|
|
6317
|
-
await
|
|
6624
|
+
await writeFile6(tmpPath, JSON.stringify(payload, null, 2), "utf8");
|
|
6625
|
+
await rename3(tmpPath, filePath);
|
|
6318
6626
|
};
|
|
6319
6627
|
var formatUtcTimestamp = (value) => new Date(value).toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
|
|
6320
6628
|
var normalizeTitle = (title) => {
|
|
@@ -6488,8 +6796,8 @@ var FileConversationStore = class {
|
|
|
6488
6796
|
agentId: this.agentId
|
|
6489
6797
|
});
|
|
6490
6798
|
const agentDir = getAgentStoreDirectory(identity);
|
|
6491
|
-
const conversationsDir =
|
|
6492
|
-
const indexPath =
|
|
6799
|
+
const conversationsDir = resolve11(agentDir, CONVERSATIONS_DIRECTORY);
|
|
6800
|
+
const indexPath = resolve11(conversationsDir, LOCAL_CONVERSATION_INDEX_FILE);
|
|
6493
6801
|
this.paths = { conversationsDir, indexPath };
|
|
6494
6802
|
return this.paths;
|
|
6495
6803
|
}
|
|
@@ -6499,13 +6807,13 @@ var FileConversationStore = class {
|
|
|
6499
6807
|
schemaVersion: STORAGE_SCHEMA_VERSION,
|
|
6500
6808
|
conversations: Array.from(this.conversations.values()).sort((a, b) => b.updatedAt - a.updatedAt)
|
|
6501
6809
|
};
|
|
6502
|
-
await
|
|
6810
|
+
await writeJsonAtomic3(indexPath, payload);
|
|
6503
6811
|
}
|
|
6504
6812
|
async readConversationFile(fileName) {
|
|
6505
6813
|
const { conversationsDir } = await this.resolvePaths();
|
|
6506
|
-
const filePath =
|
|
6814
|
+
const filePath = resolve11(conversationsDir, fileName);
|
|
6507
6815
|
try {
|
|
6508
|
-
const raw = await
|
|
6816
|
+
const raw = await readFile9(filePath, "utf8");
|
|
6509
6817
|
return JSON.parse(raw);
|
|
6510
6818
|
} catch {
|
|
6511
6819
|
return void 0;
|
|
@@ -6551,7 +6859,7 @@ var FileConversationStore = class {
|
|
|
6551
6859
|
this.loaded = true;
|
|
6552
6860
|
const { indexPath } = await this.resolvePaths();
|
|
6553
6861
|
try {
|
|
6554
|
-
const raw = await
|
|
6862
|
+
const raw = await readFile9(indexPath, "utf8");
|
|
6555
6863
|
const parsed = JSON.parse(raw);
|
|
6556
6864
|
for (const conversation of parsed.conversations ?? []) {
|
|
6557
6865
|
this.conversations.set(conversation.conversationId, conversation);
|
|
@@ -6574,9 +6882,9 @@ var FileConversationStore = class {
|
|
|
6574
6882
|
const { conversationsDir } = await this.resolvePaths();
|
|
6575
6883
|
const existing = this.conversations.get(conversation.conversationId);
|
|
6576
6884
|
const fileName = existing?.fileName ?? this.resolveConversationFileName(conversation);
|
|
6577
|
-
const filePath =
|
|
6885
|
+
const filePath = resolve11(conversationsDir, fileName);
|
|
6578
6886
|
this.writing = this.writing.then(async () => {
|
|
6579
|
-
await
|
|
6887
|
+
await writeJsonAtomic3(filePath, conversation);
|
|
6580
6888
|
this.conversations.set(conversation.conversationId, {
|
|
6581
6889
|
conversationId: conversation.conversationId,
|
|
6582
6890
|
title: conversation.title,
|
|
@@ -6672,7 +6980,7 @@ var FileConversationStore = class {
|
|
|
6672
6980
|
if (removed) {
|
|
6673
6981
|
this.writing = this.writing.then(async () => {
|
|
6674
6982
|
if (existing) {
|
|
6675
|
-
await rm3(
|
|
6983
|
+
await rm3(resolve11(conversationsDir, existing.fileName), { force: true });
|
|
6676
6984
|
}
|
|
6677
6985
|
await this.writeIndex();
|
|
6678
6986
|
});
|
|
@@ -6702,7 +7010,7 @@ var FileStateStore = class {
|
|
|
6702
7010
|
workingDir: this.workingDir,
|
|
6703
7011
|
agentId: this.agentId
|
|
6704
7012
|
});
|
|
6705
|
-
this.filePath =
|
|
7013
|
+
this.filePath = resolve11(getAgentStoreDirectory(identity), LOCAL_STATE_FILE);
|
|
6706
7014
|
}
|
|
6707
7015
|
isExpired(state) {
|
|
6708
7016
|
return typeof this.ttlMs === "number" && Date.now() - state.updatedAt > this.ttlMs;
|
|
@@ -6714,7 +7022,7 @@ var FileStateStore = class {
|
|
|
6714
7022
|
}
|
|
6715
7023
|
this.loaded = true;
|
|
6716
7024
|
try {
|
|
6717
|
-
const raw = await
|
|
7025
|
+
const raw = await readFile9(this.filePath, "utf8");
|
|
6718
7026
|
const parsed = JSON.parse(raw);
|
|
6719
7027
|
for (const state of parsed.states ?? []) {
|
|
6720
7028
|
this.states.set(state.runId, state);
|
|
@@ -6727,7 +7035,7 @@ var FileStateStore = class {
|
|
|
6727
7035
|
states: Array.from(this.states.values())
|
|
6728
7036
|
};
|
|
6729
7037
|
this.writing = this.writing.then(async () => {
|
|
6730
|
-
await
|
|
7038
|
+
await writeJsonAtomic3(this.filePath, payload);
|
|
6731
7039
|
});
|
|
6732
7040
|
await this.writing;
|
|
6733
7041
|
}
|
|
@@ -7441,7 +7749,7 @@ var TelemetryEmitter = class {
|
|
|
7441
7749
|
};
|
|
7442
7750
|
|
|
7443
7751
|
// src/index.ts
|
|
7444
|
-
import { defineTool as
|
|
7752
|
+
import { defineTool as defineTool6 } from "@poncho-ai/sdk";
|
|
7445
7753
|
export {
|
|
7446
7754
|
AgentHarness,
|
|
7447
7755
|
InMemoryConversationStore,
|
|
@@ -7471,7 +7779,7 @@ export {
|
|
|
7471
7779
|
createSubagentTools,
|
|
7472
7780
|
createUploadStore,
|
|
7473
7781
|
createWriteTool,
|
|
7474
|
-
|
|
7782
|
+
defineTool6 as defineTool,
|
|
7475
7783
|
deriveUploadKey,
|
|
7476
7784
|
ensureAgentIdentity,
|
|
7477
7785
|
estimateTokens,
|