@poncho-ai/harness 0.25.0 → 0.27.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 +28 -0
- package/dist/index.d.ts +41 -8
- package/dist/index.js +603 -359
- package/package.json +2 -2
- package/src/config.ts +4 -0
- package/src/harness.ts +41 -24
- package/src/kv-store.ts +216 -0
- package/src/memory.ts +26 -291
- package/src/state.ts +74 -9
- package/src/subagent-manager.ts +6 -2
- package/src/subagent-tools.ts +21 -48
- package/src/todo-tools.ts +363 -0
- package/.turbo/turbo-lint.log +0 -6
- package/.turbo/turbo-test.log +0 -135
package/dist/index.js
CHANGED
|
@@ -617,8 +617,10 @@ Response: Server-Sent Events (\`run:started\`, \`model:chunk\`, \`tool:*\`, \`ru
|
|
|
617
617
|
|
|
618
618
|
On serverless deployments with \`PONCHO_MAX_DURATION\` set, the \`run:completed\` event may
|
|
619
619
|
include \`continuation: true\` in \`result\`, indicating the agent stopped early due to a
|
|
620
|
-
platform timeout
|
|
621
|
-
|
|
620
|
+
platform timeout. The server preserves the full internal message chain so the agent
|
|
621
|
+
resumes with complete context. The web UI and client SDK handle continuation automatically
|
|
622
|
+
by re-posting to the same conversation with \`{ continuation: true }\` \u2014 no manual
|
|
623
|
+
"Continue" message is needed.
|
|
622
624
|
|
|
623
625
|
## Build a custom chat UI
|
|
624
626
|
|
|
@@ -1288,28 +1290,28 @@ When \`@sparticuz/chromium\` is installed and a serverless environment is detect
|
|
|
1288
1290
|
|
|
1289
1291
|
## Subagents
|
|
1290
1292
|
|
|
1291
|
-
Poncho agents can spawn
|
|
1293
|
+
Poncho agents can spawn **subagents** \u2014 independent background tasks that run in their own conversations. Each subagent has full access to the agent's tools and skills. Subagents run asynchronously and their results are delivered back to the parent automatically.
|
|
1292
1294
|
|
|
1293
1295
|
Subagents are useful when an agent needs to parallelize work, delegate a subtask, or isolate a line of investigation without polluting the main conversation context.
|
|
1294
1296
|
|
|
1295
1297
|
### How it works
|
|
1296
1298
|
|
|
1297
|
-
When the agent decides to use a subagent, it calls \`spawn_subagent\` with a task description. The subagent runs
|
|
1299
|
+
When the agent decides to use a subagent, it calls \`spawn_subagent\` with a task description. The tool returns immediately with a subagent ID and \`status: "running"\`. The subagent runs in the background and, when it completes, its result is delivered to the parent conversation as a message \u2014 triggering a callback that lets the parent process or summarize the result.
|
|
1298
1300
|
|
|
1299
|
-
The parent can also send follow-up messages to existing subagents with \`message_subagent\`, stop a running subagent with \`stop_subagent\`, or list all its subagents with \`list_subagents\`.
|
|
1301
|
+
The agent can spawn multiple subagents in a single response and they run concurrently. The parent can also send follow-up messages to existing subagents with \`message_subagent\`, stop a running subagent with \`stop_subagent\`, or list all its subagents with \`list_subagents\`.
|
|
1300
1302
|
|
|
1301
1303
|
### Available tools
|
|
1302
1304
|
|
|
1303
1305
|
| Tool | Description |
|
|
1304
1306
|
|------|-------------|
|
|
1305
|
-
| \`spawn_subagent\` | Create a new subagent with a task.
|
|
1306
|
-
| \`message_subagent\` | Send a follow-up message to an existing subagent.
|
|
1307
|
+
| \`spawn_subagent\` | Create a new subagent with a task. Returns immediately; results are delivered asynchronously. |
|
|
1308
|
+
| \`message_subagent\` | Send a follow-up message to an existing subagent. Returns immediately. |
|
|
1307
1309
|
| \`stop_subagent\` | Stop a running subagent. |
|
|
1308
1310
|
| \`list_subagents\` | List all subagents for the current conversation with their IDs, tasks, and statuses. |
|
|
1309
1311
|
|
|
1310
1312
|
### Limits
|
|
1311
1313
|
|
|
1312
|
-
- **
|
|
1314
|
+
- **No nesting**: subagents cannot spawn their own subagents.
|
|
1313
1315
|
- **Max concurrent**: 5 subagents per parent conversation.
|
|
1314
1316
|
|
|
1315
1317
|
### Memory isolation
|
|
@@ -2030,9 +2032,9 @@ var ponchoDocsTool = defineTool({
|
|
|
2030
2032
|
|
|
2031
2033
|
// src/harness.ts
|
|
2032
2034
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
2033
|
-
import { readFile as
|
|
2034
|
-
import { resolve as
|
|
2035
|
-
import { getTextContent as
|
|
2035
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
2036
|
+
import { resolve as resolve10 } from "path";
|
|
2037
|
+
import { getTextContent as getTextContent2 } from "@poncho-ai/sdk";
|
|
2036
2038
|
|
|
2037
2039
|
// src/upload-store.ts
|
|
2038
2040
|
import { createHash as createHash2 } from "crypto";
|
|
@@ -2346,6 +2348,152 @@ var createUploadStore = async (config, workingDir) => {
|
|
|
2346
2348
|
import { mkdir as mkdir3, readFile as readFile5, rename, writeFile as writeFile4 } from "fs/promises";
|
|
2347
2349
|
import { dirname as dirname2, resolve as resolve6 } from "path";
|
|
2348
2350
|
import { defineTool as defineTool2 } from "@poncho-ai/sdk";
|
|
2351
|
+
|
|
2352
|
+
// src/kv-store.ts
|
|
2353
|
+
var UpstashKVStore = class {
|
|
2354
|
+
baseUrl;
|
|
2355
|
+
token;
|
|
2356
|
+
constructor(baseUrl, token) {
|
|
2357
|
+
this.baseUrl = baseUrl.replace(/\/+$/, "");
|
|
2358
|
+
this.token = token;
|
|
2359
|
+
}
|
|
2360
|
+
headers() {
|
|
2361
|
+
return { Authorization: `Bearer ${this.token}`, "Content-Type": "application/json" };
|
|
2362
|
+
}
|
|
2363
|
+
async get(key) {
|
|
2364
|
+
const response = await fetch(`${this.baseUrl}/get/${encodeURIComponent(key)}`, {
|
|
2365
|
+
method: "POST",
|
|
2366
|
+
headers: this.headers()
|
|
2367
|
+
});
|
|
2368
|
+
if (!response.ok) return void 0;
|
|
2369
|
+
const payload = await response.json();
|
|
2370
|
+
return payload.result ?? void 0;
|
|
2371
|
+
}
|
|
2372
|
+
async set(key, value) {
|
|
2373
|
+
const response = await fetch(this.baseUrl, {
|
|
2374
|
+
method: "POST",
|
|
2375
|
+
headers: this.headers(),
|
|
2376
|
+
body: JSON.stringify(["SET", key, value])
|
|
2377
|
+
});
|
|
2378
|
+
if (!response.ok) {
|
|
2379
|
+
const text = await response.text().catch(() => "");
|
|
2380
|
+
console.error(`[kv][upstash] SET failed (${response.status}): ${text.slice(0, 200)}`);
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
async setWithTtl(key, value, ttl) {
|
|
2384
|
+
const response = await fetch(this.baseUrl, {
|
|
2385
|
+
method: "POST",
|
|
2386
|
+
headers: this.headers(),
|
|
2387
|
+
body: JSON.stringify(["SETEX", key, Math.max(1, ttl), value])
|
|
2388
|
+
});
|
|
2389
|
+
if (!response.ok) {
|
|
2390
|
+
const text = await response.text().catch(() => "");
|
|
2391
|
+
console.error(`[kv][upstash] SETEX failed (${response.status}): ${text.slice(0, 200)}`);
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
};
|
|
2395
|
+
var RedisKVStore = class {
|
|
2396
|
+
clientPromise;
|
|
2397
|
+
constructor(url) {
|
|
2398
|
+
this.clientPromise = (async () => {
|
|
2399
|
+
try {
|
|
2400
|
+
const redisModule = await import("redis");
|
|
2401
|
+
const client = redisModule.createClient({ url });
|
|
2402
|
+
await client.connect();
|
|
2403
|
+
return client;
|
|
2404
|
+
} catch {
|
|
2405
|
+
return void 0;
|
|
2406
|
+
}
|
|
2407
|
+
})();
|
|
2408
|
+
}
|
|
2409
|
+
async get(key) {
|
|
2410
|
+
const client = await this.clientPromise;
|
|
2411
|
+
if (!client) throw new Error("Redis unavailable");
|
|
2412
|
+
const value = await client.get(key);
|
|
2413
|
+
return value ?? void 0;
|
|
2414
|
+
}
|
|
2415
|
+
async set(key, value) {
|
|
2416
|
+
const client = await this.clientPromise;
|
|
2417
|
+
if (!client) throw new Error("Redis unavailable");
|
|
2418
|
+
await client.set(key, value);
|
|
2419
|
+
}
|
|
2420
|
+
async setWithTtl(key, value, ttl) {
|
|
2421
|
+
const client = await this.clientPromise;
|
|
2422
|
+
if (!client) throw new Error("Redis unavailable");
|
|
2423
|
+
await client.set(key, value, { EX: Math.max(1, ttl) });
|
|
2424
|
+
}
|
|
2425
|
+
};
|
|
2426
|
+
var DynamoDbKVStore = class {
|
|
2427
|
+
table;
|
|
2428
|
+
clientPromise;
|
|
2429
|
+
constructor(table, region) {
|
|
2430
|
+
this.table = table;
|
|
2431
|
+
this.clientPromise = (async () => {
|
|
2432
|
+
try {
|
|
2433
|
+
const module = await import("@aws-sdk/client-dynamodb");
|
|
2434
|
+
const client = new module.DynamoDBClient({ region });
|
|
2435
|
+
return {
|
|
2436
|
+
send: client.send.bind(client),
|
|
2437
|
+
GetItemCommand: module.GetItemCommand,
|
|
2438
|
+
PutItemCommand: module.PutItemCommand
|
|
2439
|
+
};
|
|
2440
|
+
} catch {
|
|
2441
|
+
return void 0;
|
|
2442
|
+
}
|
|
2443
|
+
})();
|
|
2444
|
+
}
|
|
2445
|
+
async get(key) {
|
|
2446
|
+
const client = await this.clientPromise;
|
|
2447
|
+
if (!client) throw new Error("DynamoDB unavailable");
|
|
2448
|
+
const result = await client.send(
|
|
2449
|
+
new client.GetItemCommand({ TableName: this.table, Key: { runId: { S: key } } })
|
|
2450
|
+
);
|
|
2451
|
+
return result.Item?.value?.S;
|
|
2452
|
+
}
|
|
2453
|
+
async set(key, value) {
|
|
2454
|
+
const client = await this.clientPromise;
|
|
2455
|
+
if (!client) throw new Error("DynamoDB unavailable");
|
|
2456
|
+
await client.send(
|
|
2457
|
+
new client.PutItemCommand({
|
|
2458
|
+
TableName: this.table,
|
|
2459
|
+
Item: { runId: { S: key }, value: { S: value } }
|
|
2460
|
+
})
|
|
2461
|
+
);
|
|
2462
|
+
}
|
|
2463
|
+
async setWithTtl(key, value, ttl) {
|
|
2464
|
+
const client = await this.clientPromise;
|
|
2465
|
+
if (!client) throw new Error("DynamoDB unavailable");
|
|
2466
|
+
const ttlEpoch = Math.floor(Date.now() / 1e3) + Math.max(1, ttl);
|
|
2467
|
+
await client.send(
|
|
2468
|
+
new client.PutItemCommand({
|
|
2469
|
+
TableName: this.table,
|
|
2470
|
+
Item: { runId: { S: key }, value: { S: value }, ttl: { N: String(ttlEpoch) } }
|
|
2471
|
+
})
|
|
2472
|
+
);
|
|
2473
|
+
}
|
|
2474
|
+
};
|
|
2475
|
+
var createRawKVStore = (config) => {
|
|
2476
|
+
const provider = config?.provider ?? "local";
|
|
2477
|
+
if (provider === "upstash") {
|
|
2478
|
+
const urlEnv = config?.urlEnv ?? (process.env.UPSTASH_REDIS_REST_URL ? "UPSTASH_REDIS_REST_URL" : "KV_REST_API_URL");
|
|
2479
|
+
const tokenEnv = config?.tokenEnv ?? (process.env.UPSTASH_REDIS_REST_TOKEN ? "UPSTASH_REDIS_REST_TOKEN" : "KV_REST_API_TOKEN");
|
|
2480
|
+
const url = process.env[urlEnv] ?? "";
|
|
2481
|
+
const token = process.env[tokenEnv] ?? "";
|
|
2482
|
+
if (url && token) return new UpstashKVStore(url, token);
|
|
2483
|
+
}
|
|
2484
|
+
if (provider === "redis") {
|
|
2485
|
+
const urlEnv = config?.urlEnv ?? "REDIS_URL";
|
|
2486
|
+
const url = process.env[urlEnv] ?? "";
|
|
2487
|
+
if (url) return new RedisKVStore(url);
|
|
2488
|
+
}
|
|
2489
|
+
if (provider === "dynamodb") {
|
|
2490
|
+
const table = config?.table ?? process.env.PONCHO_DYNAMODB_TABLE ?? "";
|
|
2491
|
+
if (table) return new DynamoDbKVStore(table, config?.region);
|
|
2492
|
+
}
|
|
2493
|
+
return void 0;
|
|
2494
|
+
};
|
|
2495
|
+
|
|
2496
|
+
// src/memory.ts
|
|
2349
2497
|
var DEFAULT_MAIN_MEMORY = {
|
|
2350
2498
|
content: "",
|
|
2351
2499
|
updatedAt: 0
|
|
@@ -2456,19 +2604,21 @@ var FileMainMemoryStore = class {
|
|
|
2456
2604
|
return this.mainMemory;
|
|
2457
2605
|
}
|
|
2458
2606
|
};
|
|
2459
|
-
var
|
|
2607
|
+
var KVBackedMemoryStore = class {
|
|
2608
|
+
kv;
|
|
2609
|
+
storageKey;
|
|
2460
2610
|
ttl;
|
|
2461
2611
|
memoryFallback;
|
|
2462
|
-
constructor(ttl) {
|
|
2612
|
+
constructor(kv, storageKey, ttl) {
|
|
2613
|
+
this.kv = kv;
|
|
2614
|
+
this.storageKey = storageKey;
|
|
2463
2615
|
this.ttl = ttl;
|
|
2464
2616
|
this.memoryFallback = new InMemoryMemoryStore(ttl);
|
|
2465
2617
|
}
|
|
2466
|
-
async readPayload(
|
|
2618
|
+
async readPayload() {
|
|
2467
2619
|
try {
|
|
2468
|
-
const raw = await this.
|
|
2469
|
-
if (!raw) {
|
|
2470
|
-
return { main: { ...DEFAULT_MAIN_MEMORY } };
|
|
2471
|
-
}
|
|
2620
|
+
const raw = await this.kv.get(this.storageKey);
|
|
2621
|
+
if (!raw) return { main: { ...DEFAULT_MAIN_MEMORY } };
|
|
2472
2622
|
const parsed = JSON.parse(raw);
|
|
2473
2623
|
const content = typeof parsed.main?.content === "string" ? parsed.main.content : "";
|
|
2474
2624
|
const updatedAt = typeof parsed.main?.updatedAt === "number" ? parsed.main.updatedAt : 0;
|
|
@@ -2478,200 +2628,32 @@ var KeyValueMainMemoryStoreBase = class {
|
|
|
2478
2628
|
return { main };
|
|
2479
2629
|
}
|
|
2480
2630
|
}
|
|
2481
|
-
async writePayload(
|
|
2631
|
+
async writePayload(payload) {
|
|
2482
2632
|
try {
|
|
2483
2633
|
const serialized = JSON.stringify(payload);
|
|
2484
2634
|
if (typeof this.ttl === "number") {
|
|
2485
|
-
await this.
|
|
2635
|
+
await this.kv.setWithTtl(this.storageKey, serialized, Math.max(1, this.ttl));
|
|
2486
2636
|
} else {
|
|
2487
|
-
await this.
|
|
2637
|
+
await this.kv.set(this.storageKey, serialized);
|
|
2488
2638
|
}
|
|
2489
2639
|
} catch {
|
|
2490
|
-
await this.memoryFallback.updateMainMemory({
|
|
2491
|
-
content: payload.main.content
|
|
2492
|
-
});
|
|
2640
|
+
await this.memoryFallback.updateMainMemory({ content: payload.main.content });
|
|
2493
2641
|
}
|
|
2494
2642
|
}
|
|
2495
2643
|
async getMainMemory() {
|
|
2496
|
-
const payload = await this.readPayload(
|
|
2644
|
+
const payload = await this.readPayload();
|
|
2497
2645
|
return payload.main;
|
|
2498
2646
|
}
|
|
2499
2647
|
async updateMainMemory(input) {
|
|
2500
|
-
const
|
|
2501
|
-
|
|
2502
|
-
payload
|
|
2503
|
-
content: input.content.trim(),
|
|
2504
|
-
updatedAt: Date.now()
|
|
2505
|
-
};
|
|
2506
|
-
await this.writePayload(key, payload);
|
|
2648
|
+
const payload = await this.readPayload();
|
|
2649
|
+
payload.main = { content: input.content.trim(), updatedAt: Date.now() };
|
|
2650
|
+
await this.writePayload(payload);
|
|
2507
2651
|
return payload.main;
|
|
2508
2652
|
}
|
|
2509
2653
|
};
|
|
2510
|
-
var UpstashMemoryStore = class extends KeyValueMainMemoryStoreBase {
|
|
2511
|
-
baseUrl;
|
|
2512
|
-
token;
|
|
2513
|
-
storageKey;
|
|
2514
|
-
constructor(options) {
|
|
2515
|
-
super(options.ttl);
|
|
2516
|
-
this.baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
2517
|
-
this.token = options.token;
|
|
2518
|
-
this.storageKey = options.storageKey;
|
|
2519
|
-
}
|
|
2520
|
-
key() {
|
|
2521
|
-
return this.storageKey;
|
|
2522
|
-
}
|
|
2523
|
-
headers() {
|
|
2524
|
-
return {
|
|
2525
|
-
Authorization: `Bearer ${this.token}`,
|
|
2526
|
-
"Content-Type": "application/json"
|
|
2527
|
-
};
|
|
2528
|
-
}
|
|
2529
|
-
async getRaw(key) {
|
|
2530
|
-
const response = await fetch(`${this.baseUrl}/get/${encodeURIComponent(key)}`, {
|
|
2531
|
-
method: "POST",
|
|
2532
|
-
headers: this.headers()
|
|
2533
|
-
});
|
|
2534
|
-
if (!response.ok) {
|
|
2535
|
-
return void 0;
|
|
2536
|
-
}
|
|
2537
|
-
const payload = await response.json();
|
|
2538
|
-
return payload.result ?? void 0;
|
|
2539
|
-
}
|
|
2540
|
-
async setRaw(key, value) {
|
|
2541
|
-
await fetch(
|
|
2542
|
-
`${this.baseUrl}/set/${encodeURIComponent(key)}/${encodeURIComponent(value)}`,
|
|
2543
|
-
{ method: "POST", headers: this.headers() }
|
|
2544
|
-
);
|
|
2545
|
-
}
|
|
2546
|
-
async setRawWithTtl(key, value, ttl) {
|
|
2547
|
-
await fetch(
|
|
2548
|
-
`${this.baseUrl}/setex/${encodeURIComponent(key)}/${Math.max(1, ttl)}/${encodeURIComponent(
|
|
2549
|
-
value
|
|
2550
|
-
)}`,
|
|
2551
|
-
{ method: "POST", headers: this.headers() }
|
|
2552
|
-
);
|
|
2553
|
-
}
|
|
2554
|
-
};
|
|
2555
|
-
var RedisMemoryStore = class extends KeyValueMainMemoryStoreBase {
|
|
2556
|
-
storageKey;
|
|
2557
|
-
clientPromise;
|
|
2558
|
-
constructor(options) {
|
|
2559
|
-
super(options.ttl);
|
|
2560
|
-
this.storageKey = options.storageKey;
|
|
2561
|
-
this.clientPromise = (async () => {
|
|
2562
|
-
try {
|
|
2563
|
-
const redisModule = await import("redis");
|
|
2564
|
-
const client = redisModule.createClient({ url: options.url });
|
|
2565
|
-
await client.connect();
|
|
2566
|
-
return client;
|
|
2567
|
-
} catch {
|
|
2568
|
-
return void 0;
|
|
2569
|
-
}
|
|
2570
|
-
})();
|
|
2571
|
-
}
|
|
2572
|
-
key() {
|
|
2573
|
-
return this.storageKey;
|
|
2574
|
-
}
|
|
2575
|
-
async getRaw(key) {
|
|
2576
|
-
const client = await this.clientPromise;
|
|
2577
|
-
if (!client) {
|
|
2578
|
-
throw new Error("Redis unavailable");
|
|
2579
|
-
}
|
|
2580
|
-
const value = await client.get(key);
|
|
2581
|
-
return value ?? void 0;
|
|
2582
|
-
}
|
|
2583
|
-
async setRaw(key, value) {
|
|
2584
|
-
const client = await this.clientPromise;
|
|
2585
|
-
if (!client) {
|
|
2586
|
-
throw new Error("Redis unavailable");
|
|
2587
|
-
}
|
|
2588
|
-
await client.set(key, value);
|
|
2589
|
-
}
|
|
2590
|
-
async setRawWithTtl(key, value, ttl) {
|
|
2591
|
-
const client = await this.clientPromise;
|
|
2592
|
-
if (!client) {
|
|
2593
|
-
throw new Error("Redis unavailable");
|
|
2594
|
-
}
|
|
2595
|
-
await client.set(key, value, { EX: Math.max(1, ttl) });
|
|
2596
|
-
}
|
|
2597
|
-
};
|
|
2598
|
-
var DynamoDbMemoryStore = class extends KeyValueMainMemoryStoreBase {
|
|
2599
|
-
storageKey;
|
|
2600
|
-
table;
|
|
2601
|
-
clientPromise;
|
|
2602
|
-
constructor(options) {
|
|
2603
|
-
super(options.ttl);
|
|
2604
|
-
this.storageKey = options.storageKey;
|
|
2605
|
-
this.table = options.table;
|
|
2606
|
-
this.clientPromise = (async () => {
|
|
2607
|
-
try {
|
|
2608
|
-
const module = await import("@aws-sdk/client-dynamodb");
|
|
2609
|
-
const client = new module.DynamoDBClient({ region: options.region });
|
|
2610
|
-
return {
|
|
2611
|
-
send: client.send.bind(client),
|
|
2612
|
-
GetItemCommand: module.GetItemCommand,
|
|
2613
|
-
PutItemCommand: module.PutItemCommand
|
|
2614
|
-
};
|
|
2615
|
-
} catch {
|
|
2616
|
-
return void 0;
|
|
2617
|
-
}
|
|
2618
|
-
})();
|
|
2619
|
-
}
|
|
2620
|
-
key() {
|
|
2621
|
-
return this.storageKey;
|
|
2622
|
-
}
|
|
2623
|
-
async getRaw(key) {
|
|
2624
|
-
const client = await this.clientPromise;
|
|
2625
|
-
if (!client) {
|
|
2626
|
-
throw new Error("DynamoDB unavailable");
|
|
2627
|
-
}
|
|
2628
|
-
const result = await client.send(
|
|
2629
|
-
new client.GetItemCommand({
|
|
2630
|
-
TableName: this.table,
|
|
2631
|
-
Key: { runId: { S: key } }
|
|
2632
|
-
})
|
|
2633
|
-
);
|
|
2634
|
-
return result.Item?.value?.S;
|
|
2635
|
-
}
|
|
2636
|
-
async setRaw(key, value) {
|
|
2637
|
-
const client = await this.clientPromise;
|
|
2638
|
-
if (!client) {
|
|
2639
|
-
throw new Error("DynamoDB unavailable");
|
|
2640
|
-
}
|
|
2641
|
-
await client.send(
|
|
2642
|
-
new client.PutItemCommand({
|
|
2643
|
-
TableName: this.table,
|
|
2644
|
-
Item: {
|
|
2645
|
-
runId: { S: key },
|
|
2646
|
-
value: { S: value }
|
|
2647
|
-
}
|
|
2648
|
-
})
|
|
2649
|
-
);
|
|
2650
|
-
}
|
|
2651
|
-
async setRawWithTtl(key, value, ttl) {
|
|
2652
|
-
const client = await this.clientPromise;
|
|
2653
|
-
if (!client) {
|
|
2654
|
-
throw new Error("DynamoDB unavailable");
|
|
2655
|
-
}
|
|
2656
|
-
const ttlEpoch = Math.floor(Date.now() / 1e3) + Math.max(1, ttl);
|
|
2657
|
-
await client.send(
|
|
2658
|
-
new client.PutItemCommand({
|
|
2659
|
-
TableName: this.table,
|
|
2660
|
-
Item: {
|
|
2661
|
-
runId: { S: key },
|
|
2662
|
-
value: { S: value },
|
|
2663
|
-
ttl: { N: String(ttlEpoch) }
|
|
2664
|
-
}
|
|
2665
|
-
})
|
|
2666
|
-
);
|
|
2667
|
-
}
|
|
2668
|
-
};
|
|
2669
2654
|
var createMemoryStore = (agentId, config, options) => {
|
|
2670
2655
|
const provider = config?.provider ?? "local";
|
|
2671
2656
|
const ttl = config?.ttl;
|
|
2672
|
-
const storageKey = `poncho:${STORAGE_SCHEMA_VERSION}:${slugifyStorageComponent(
|
|
2673
|
-
agentId
|
|
2674
|
-
)}:memory:main`;
|
|
2675
2657
|
const workingDir = options?.workingDir ?? process.cwd();
|
|
2676
2658
|
if (provider === "local") {
|
|
2677
2659
|
return new FileMainMemoryStore(workingDir, ttl);
|
|
@@ -2679,44 +2661,10 @@ var createMemoryStore = (agentId, config, options) => {
|
|
|
2679
2661
|
if (provider === "memory") {
|
|
2680
2662
|
return new InMemoryMemoryStore(ttl);
|
|
2681
2663
|
}
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
const
|
|
2685
|
-
|
|
2686
|
-
const token = process.env[tokenEnv] ?? "";
|
|
2687
|
-
if (url && token) {
|
|
2688
|
-
return new UpstashMemoryStore({
|
|
2689
|
-
baseUrl: url,
|
|
2690
|
-
token,
|
|
2691
|
-
storageKey,
|
|
2692
|
-
ttl
|
|
2693
|
-
});
|
|
2694
|
-
}
|
|
2695
|
-
return new InMemoryMemoryStore(ttl);
|
|
2696
|
-
}
|
|
2697
|
-
if (provider === "redis") {
|
|
2698
|
-
const urlEnv = config?.urlEnv ?? "REDIS_URL";
|
|
2699
|
-
const url = process.env[urlEnv] ?? "";
|
|
2700
|
-
if (url) {
|
|
2701
|
-
return new RedisMemoryStore({
|
|
2702
|
-
url,
|
|
2703
|
-
storageKey,
|
|
2704
|
-
ttl
|
|
2705
|
-
});
|
|
2706
|
-
}
|
|
2707
|
-
return new InMemoryMemoryStore(ttl);
|
|
2708
|
-
}
|
|
2709
|
-
if (provider === "dynamodb") {
|
|
2710
|
-
const table = config?.table ?? process.env.PONCHO_DYNAMODB_TABLE ?? "";
|
|
2711
|
-
if (table) {
|
|
2712
|
-
return new DynamoDbMemoryStore({
|
|
2713
|
-
table,
|
|
2714
|
-
storageKey,
|
|
2715
|
-
region: config?.region,
|
|
2716
|
-
ttl
|
|
2717
|
-
});
|
|
2718
|
-
}
|
|
2719
|
-
return new InMemoryMemoryStore(ttl);
|
|
2664
|
+
const kv = createRawKVStore(config);
|
|
2665
|
+
if (kv) {
|
|
2666
|
+
const storageKey = `poncho:${STORAGE_SCHEMA_VERSION}:${slugifyStorageComponent(agentId)}:memory:main`;
|
|
2667
|
+
return new KVBackedMemoryStore(kv, storageKey, ttl);
|
|
2720
2668
|
}
|
|
2721
2669
|
return new InMemoryMemoryStore(ttl);
|
|
2722
2670
|
};
|
|
@@ -2889,6 +2837,269 @@ ${item.content}`, query)
|
|
|
2889
2837
|
];
|
|
2890
2838
|
};
|
|
2891
2839
|
|
|
2840
|
+
// src/todo-tools.ts
|
|
2841
|
+
import { mkdir as mkdir4, readFile as readFile6, rename as rename2, writeFile as writeFile5 } from "fs/promises";
|
|
2842
|
+
import { dirname as dirname3, resolve as resolve7 } from "path";
|
|
2843
|
+
import { defineTool as defineTool3 } from "@poncho-ai/sdk";
|
|
2844
|
+
var VALID_STATUSES = ["pending", "in_progress", "completed"];
|
|
2845
|
+
var VALID_PRIORITIES = ["high", "medium", "low"];
|
|
2846
|
+
var TODOS_DIRECTORY = "todos";
|
|
2847
|
+
var writeJsonAtomic2 = async (filePath, payload) => {
|
|
2848
|
+
await mkdir4(dirname3(filePath), { recursive: true });
|
|
2849
|
+
const tmpPath = `${filePath}.tmp`;
|
|
2850
|
+
await writeFile5(tmpPath, JSON.stringify(payload, null, 2), "utf8");
|
|
2851
|
+
await rename2(tmpPath, filePath);
|
|
2852
|
+
};
|
|
2853
|
+
var parseTodoList = (raw) => {
|
|
2854
|
+
if (!Array.isArray(raw)) return [];
|
|
2855
|
+
return raw.filter(
|
|
2856
|
+
(item) => typeof item === "object" && item !== null && typeof item.id === "string" && typeof item.content === "string"
|
|
2857
|
+
);
|
|
2858
|
+
};
|
|
2859
|
+
var generateId = () => (globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`).slice(0, 8);
|
|
2860
|
+
var InMemoryTodoStore = class {
|
|
2861
|
+
store = /* @__PURE__ */ new Map();
|
|
2862
|
+
async get(conversationId) {
|
|
2863
|
+
return this.store.get(conversationId) ?? [];
|
|
2864
|
+
}
|
|
2865
|
+
async set(conversationId, todos) {
|
|
2866
|
+
this.store.set(conversationId, todos);
|
|
2867
|
+
}
|
|
2868
|
+
};
|
|
2869
|
+
var FileTodoStore = class {
|
|
2870
|
+
workingDir;
|
|
2871
|
+
todosDir = "";
|
|
2872
|
+
constructor(workingDir) {
|
|
2873
|
+
this.workingDir = workingDir;
|
|
2874
|
+
}
|
|
2875
|
+
async ensureTodosDir() {
|
|
2876
|
+
if (this.todosDir) return this.todosDir;
|
|
2877
|
+
const identity = await ensureAgentIdentity(this.workingDir);
|
|
2878
|
+
this.todosDir = resolve7(getAgentStoreDirectory(identity), TODOS_DIRECTORY);
|
|
2879
|
+
await mkdir4(this.todosDir, { recursive: true });
|
|
2880
|
+
return this.todosDir;
|
|
2881
|
+
}
|
|
2882
|
+
async filePath(conversationId) {
|
|
2883
|
+
const dir = await this.ensureTodosDir();
|
|
2884
|
+
return resolve7(dir, `${slugifyStorageComponent(conversationId)}.json`);
|
|
2885
|
+
}
|
|
2886
|
+
async get(conversationId) {
|
|
2887
|
+
try {
|
|
2888
|
+
const fp = await this.filePath(conversationId);
|
|
2889
|
+
const raw = await readFile6(fp, "utf8");
|
|
2890
|
+
return parseTodoList(JSON.parse(raw));
|
|
2891
|
+
} catch {
|
|
2892
|
+
return [];
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
async set(conversationId, todos) {
|
|
2896
|
+
const fp = await this.filePath(conversationId);
|
|
2897
|
+
await writeJsonAtomic2(fp, todos);
|
|
2898
|
+
}
|
|
2899
|
+
};
|
|
2900
|
+
var KVBackedTodoStore = class {
|
|
2901
|
+
kv;
|
|
2902
|
+
baseKey;
|
|
2903
|
+
ttl;
|
|
2904
|
+
memoryFallback = new InMemoryTodoStore();
|
|
2905
|
+
constructor(kv, baseKey, ttl) {
|
|
2906
|
+
this.kv = kv;
|
|
2907
|
+
this.baseKey = baseKey;
|
|
2908
|
+
this.ttl = ttl;
|
|
2909
|
+
}
|
|
2910
|
+
keyFor(conversationId) {
|
|
2911
|
+
return `${this.baseKey}:${slugifyStorageComponent(conversationId)}`;
|
|
2912
|
+
}
|
|
2913
|
+
async get(conversationId) {
|
|
2914
|
+
try {
|
|
2915
|
+
const raw = await this.kv.get(this.keyFor(conversationId));
|
|
2916
|
+
if (!raw) return [];
|
|
2917
|
+
return parseTodoList(JSON.parse(raw));
|
|
2918
|
+
} catch {
|
|
2919
|
+
return this.memoryFallback.get(conversationId);
|
|
2920
|
+
}
|
|
2921
|
+
}
|
|
2922
|
+
async set(conversationId, todos) {
|
|
2923
|
+
try {
|
|
2924
|
+
const serialized = JSON.stringify(todos);
|
|
2925
|
+
const key = this.keyFor(conversationId);
|
|
2926
|
+
if (typeof this.ttl === "number") {
|
|
2927
|
+
await this.kv.setWithTtl(key, serialized, Math.max(1, this.ttl));
|
|
2928
|
+
} else {
|
|
2929
|
+
await this.kv.set(key, serialized);
|
|
2930
|
+
}
|
|
2931
|
+
} catch {
|
|
2932
|
+
await this.memoryFallback.set(conversationId, todos);
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
};
|
|
2936
|
+
var createTodoStore = (agentId, config, options) => {
|
|
2937
|
+
const provider = config?.provider ?? "local";
|
|
2938
|
+
const ttl = config?.ttl;
|
|
2939
|
+
const workingDir = options?.workingDir ?? process.cwd();
|
|
2940
|
+
if (provider === "local") {
|
|
2941
|
+
return new FileTodoStore(workingDir);
|
|
2942
|
+
}
|
|
2943
|
+
if (provider === "memory") {
|
|
2944
|
+
return new InMemoryTodoStore();
|
|
2945
|
+
}
|
|
2946
|
+
const kv = createRawKVStore(config);
|
|
2947
|
+
if (kv) {
|
|
2948
|
+
const baseKey = `poncho:${STORAGE_SCHEMA_VERSION}:${slugifyStorageComponent(agentId)}:todos`;
|
|
2949
|
+
return new KVBackedTodoStore(kv, baseKey, ttl);
|
|
2950
|
+
}
|
|
2951
|
+
return new InMemoryTodoStore();
|
|
2952
|
+
};
|
|
2953
|
+
var createTodoTools = (store) => {
|
|
2954
|
+
const resolveKey = (context) => context.conversationId || context.runId;
|
|
2955
|
+
return [
|
|
2956
|
+
defineTool3({
|
|
2957
|
+
name: "todo_list",
|
|
2958
|
+
description: "List all todo items for the current conversation. Use this to check progress and plan next steps.",
|
|
2959
|
+
inputSchema: {
|
|
2960
|
+
type: "object",
|
|
2961
|
+
properties: {
|
|
2962
|
+
status: {
|
|
2963
|
+
type: "string",
|
|
2964
|
+
enum: VALID_STATUSES,
|
|
2965
|
+
description: "Filter by status (omit to list all)"
|
|
2966
|
+
}
|
|
2967
|
+
},
|
|
2968
|
+
additionalProperties: false
|
|
2969
|
+
},
|
|
2970
|
+
handler: async (input, context) => {
|
|
2971
|
+
const key = resolveKey(context);
|
|
2972
|
+
let todos = await store.get(key);
|
|
2973
|
+
const status = typeof input.status === "string" ? input.status : void 0;
|
|
2974
|
+
if (status && VALID_STATUSES.includes(status)) {
|
|
2975
|
+
todos = todos.filter((t) => t.status === status);
|
|
2976
|
+
}
|
|
2977
|
+
return { todos, count: todos.length };
|
|
2978
|
+
}
|
|
2979
|
+
}),
|
|
2980
|
+
defineTool3({
|
|
2981
|
+
name: "todo_add",
|
|
2982
|
+
description: "Add a new todo item for the current conversation. Use proactively for complex multi-step tasks (3+ steps).",
|
|
2983
|
+
inputSchema: {
|
|
2984
|
+
type: "object",
|
|
2985
|
+
properties: {
|
|
2986
|
+
content: {
|
|
2987
|
+
type: "string",
|
|
2988
|
+
description: "Description of the task"
|
|
2989
|
+
},
|
|
2990
|
+
status: {
|
|
2991
|
+
type: "string",
|
|
2992
|
+
enum: VALID_STATUSES,
|
|
2993
|
+
description: "Initial status (default: pending)"
|
|
2994
|
+
},
|
|
2995
|
+
priority: {
|
|
2996
|
+
type: "string",
|
|
2997
|
+
enum: VALID_PRIORITIES,
|
|
2998
|
+
description: "Priority level (default: medium)"
|
|
2999
|
+
}
|
|
3000
|
+
},
|
|
3001
|
+
required: ["content"],
|
|
3002
|
+
additionalProperties: false
|
|
3003
|
+
},
|
|
3004
|
+
handler: async (input, context) => {
|
|
3005
|
+
const content = typeof input.content === "string" ? input.content.trim() : "";
|
|
3006
|
+
if (!content) throw new Error("content is required");
|
|
3007
|
+
const status = typeof input.status === "string" && VALID_STATUSES.includes(input.status) ? input.status : "pending";
|
|
3008
|
+
const priority = typeof input.priority === "string" && VALID_PRIORITIES.includes(input.priority) ? input.priority : "medium";
|
|
3009
|
+
const now2 = Date.now();
|
|
3010
|
+
const todo = {
|
|
3011
|
+
id: generateId(),
|
|
3012
|
+
content,
|
|
3013
|
+
status,
|
|
3014
|
+
priority,
|
|
3015
|
+
createdAt: now2,
|
|
3016
|
+
updatedAt: now2
|
|
3017
|
+
};
|
|
3018
|
+
const key = resolveKey(context);
|
|
3019
|
+
const todos = await store.get(key);
|
|
3020
|
+
todos.push(todo);
|
|
3021
|
+
await store.set(key, todos);
|
|
3022
|
+
return { todo, todos };
|
|
3023
|
+
}
|
|
3024
|
+
}),
|
|
3025
|
+
defineTool3({
|
|
3026
|
+
name: "todo_update",
|
|
3027
|
+
description: "Update an existing todo item's status, content, or priority. Mark tasks in_progress when starting and completed when done.",
|
|
3028
|
+
inputSchema: {
|
|
3029
|
+
type: "object",
|
|
3030
|
+
properties: {
|
|
3031
|
+
id: {
|
|
3032
|
+
type: "string",
|
|
3033
|
+
description: "ID of the todo to update"
|
|
3034
|
+
},
|
|
3035
|
+
status: {
|
|
3036
|
+
type: "string",
|
|
3037
|
+
enum: VALID_STATUSES,
|
|
3038
|
+
description: "New status"
|
|
3039
|
+
},
|
|
3040
|
+
content: {
|
|
3041
|
+
type: "string",
|
|
3042
|
+
description: "New content/description"
|
|
3043
|
+
},
|
|
3044
|
+
priority: {
|
|
3045
|
+
type: "string",
|
|
3046
|
+
enum: VALID_PRIORITIES,
|
|
3047
|
+
description: "New priority level"
|
|
3048
|
+
}
|
|
3049
|
+
},
|
|
3050
|
+
required: ["id"],
|
|
3051
|
+
additionalProperties: false
|
|
3052
|
+
},
|
|
3053
|
+
handler: async (input, context) => {
|
|
3054
|
+
const id = typeof input.id === "string" ? input.id : "";
|
|
3055
|
+
if (!id) throw new Error("id is required");
|
|
3056
|
+
const key = resolveKey(context);
|
|
3057
|
+
const todos = await store.get(key);
|
|
3058
|
+
const todo = todos.find((t) => t.id === id);
|
|
3059
|
+
if (!todo) throw new Error(`Todo with id "${id}" not found`);
|
|
3060
|
+
if (typeof input.status === "string" && VALID_STATUSES.includes(input.status)) {
|
|
3061
|
+
todo.status = input.status;
|
|
3062
|
+
}
|
|
3063
|
+
if (typeof input.content === "string" && input.content.trim()) {
|
|
3064
|
+
todo.content = input.content.trim();
|
|
3065
|
+
}
|
|
3066
|
+
if (typeof input.priority === "string" && VALID_PRIORITIES.includes(input.priority)) {
|
|
3067
|
+
todo.priority = input.priority;
|
|
3068
|
+
}
|
|
3069
|
+
todo.updatedAt = Date.now();
|
|
3070
|
+
await store.set(key, todos);
|
|
3071
|
+
return { todo, todos };
|
|
3072
|
+
}
|
|
3073
|
+
}),
|
|
3074
|
+
defineTool3({
|
|
3075
|
+
name: "todo_remove",
|
|
3076
|
+
description: "Remove a todo item by ID.",
|
|
3077
|
+
inputSchema: {
|
|
3078
|
+
type: "object",
|
|
3079
|
+
properties: {
|
|
3080
|
+
id: {
|
|
3081
|
+
type: "string",
|
|
3082
|
+
description: "ID of the todo to remove"
|
|
3083
|
+
}
|
|
3084
|
+
},
|
|
3085
|
+
required: ["id"],
|
|
3086
|
+
additionalProperties: false
|
|
3087
|
+
},
|
|
3088
|
+
handler: async (input, context) => {
|
|
3089
|
+
const id = typeof input.id === "string" ? input.id : "";
|
|
3090
|
+
if (!id) throw new Error("id is required");
|
|
3091
|
+
const key = resolveKey(context);
|
|
3092
|
+
const todos = await store.get(key);
|
|
3093
|
+
const index = todos.findIndex((t) => t.id === id);
|
|
3094
|
+
if (index === -1) throw new Error(`Todo with id "${id}" not found`);
|
|
3095
|
+
const [removed] = todos.splice(index, 1);
|
|
3096
|
+
await store.set(key, todos);
|
|
3097
|
+
return { removed, todos };
|
|
3098
|
+
}
|
|
3099
|
+
})
|
|
3100
|
+
];
|
|
3101
|
+
};
|
|
3102
|
+
|
|
2892
3103
|
// src/mcp.ts
|
|
2893
3104
|
var McpHttpError = class extends Error {
|
|
2894
3105
|
status;
|
|
@@ -3383,8 +3594,8 @@ var createModelProvider = (provider, config) => {
|
|
|
3383
3594
|
};
|
|
3384
3595
|
|
|
3385
3596
|
// src/skill-context.ts
|
|
3386
|
-
import { readFile as
|
|
3387
|
-
import { dirname as
|
|
3597
|
+
import { readFile as readFile7, readdir as readdir2, stat } from "fs/promises";
|
|
3598
|
+
import { dirname as dirname4, resolve as resolve8, normalize } from "path";
|
|
3388
3599
|
import YAML3 from "yaml";
|
|
3389
3600
|
var DEFAULT_SKILL_DIRS = ["skills"];
|
|
3390
3601
|
var resolveSkillDirs = (workingDir, extraPaths) => {
|
|
@@ -3396,7 +3607,7 @@ var resolveSkillDirs = (workingDir, extraPaths) => {
|
|
|
3396
3607
|
}
|
|
3397
3608
|
}
|
|
3398
3609
|
}
|
|
3399
|
-
return dirs.map((d) =>
|
|
3610
|
+
return dirs.map((d) => resolve8(workingDir, d));
|
|
3400
3611
|
};
|
|
3401
3612
|
var FRONTMATTER_PATTERN3 = /^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/;
|
|
3402
3613
|
var asRecord2 = (value) => typeof value === "object" && value !== null ? value : {};
|
|
@@ -3465,7 +3676,7 @@ var collectSkillManifests = async (directory) => {
|
|
|
3465
3676
|
const entries = await readdir2(directory, { withFileTypes: true });
|
|
3466
3677
|
const files = [];
|
|
3467
3678
|
for (const entry of entries) {
|
|
3468
|
-
const fullPath =
|
|
3679
|
+
const fullPath = resolve8(directory, entry.name);
|
|
3469
3680
|
let isDir = entry.isDirectory();
|
|
3470
3681
|
let isFile = entry.isFile();
|
|
3471
3682
|
if (entry.isSymbolicLink()) {
|
|
@@ -3500,13 +3711,13 @@ var loadSkillMetadata = async (workingDir, extraSkillPaths) => {
|
|
|
3500
3711
|
const seen = /* @__PURE__ */ new Set();
|
|
3501
3712
|
for (const manifest of allManifests) {
|
|
3502
3713
|
try {
|
|
3503
|
-
const content = await
|
|
3714
|
+
const content = await readFile7(manifest, "utf8");
|
|
3504
3715
|
const parsed = parseSkillFrontmatter(content);
|
|
3505
3716
|
if (parsed && !seen.has(parsed.name)) {
|
|
3506
3717
|
seen.add(parsed.name);
|
|
3507
3718
|
skills.push({
|
|
3508
3719
|
...parsed,
|
|
3509
|
-
skillDir:
|
|
3720
|
+
skillDir: dirname4(manifest),
|
|
3510
3721
|
skillPath: manifest
|
|
3511
3722
|
});
|
|
3512
3723
|
}
|
|
@@ -3545,7 +3756,7 @@ ${xmlSkills}
|
|
|
3545
3756
|
};
|
|
3546
3757
|
var escapeXml = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
3547
3758
|
var loadSkillInstructions = async (skill) => {
|
|
3548
|
-
const content = await
|
|
3759
|
+
const content = await readFile7(skill.skillPath, "utf8");
|
|
3549
3760
|
const match = content.match(FRONTMATTER_PATTERN3);
|
|
3550
3761
|
return match ? match[2].trim() : content.trim();
|
|
3551
3762
|
};
|
|
@@ -3554,11 +3765,11 @@ var readSkillResource = async (skill, relativePath) => {
|
|
|
3554
3765
|
if (normalized.startsWith("..") || normalized.startsWith("/")) {
|
|
3555
3766
|
throw new Error("Path must be relative and within the skill directory");
|
|
3556
3767
|
}
|
|
3557
|
-
const fullPath =
|
|
3768
|
+
const fullPath = resolve8(skill.skillDir, normalized);
|
|
3558
3769
|
if (!fullPath.startsWith(skill.skillDir)) {
|
|
3559
3770
|
throw new Error("Path escapes the skill directory");
|
|
3560
3771
|
}
|
|
3561
|
-
return await
|
|
3772
|
+
return await readFile7(fullPath, "utf8");
|
|
3562
3773
|
};
|
|
3563
3774
|
var MAX_INSTRUCTIONS_PER_SKILL = 1200;
|
|
3564
3775
|
var loadSkillContext = async (workingDir) => {
|
|
@@ -3675,16 +3886,16 @@ function convertSchema(schema) {
|
|
|
3675
3886
|
}
|
|
3676
3887
|
|
|
3677
3888
|
// src/skill-tools.ts
|
|
3678
|
-
import { defineTool as
|
|
3889
|
+
import { defineTool as defineTool4 } from "@poncho-ai/sdk";
|
|
3679
3890
|
import { access as access2, readdir as readdir3, stat as stat2 } from "fs/promises";
|
|
3680
|
-
import { extname, normalize as normalize2, resolve as
|
|
3891
|
+
import { extname, normalize as normalize2, resolve as resolve9, sep as sep2 } from "path";
|
|
3681
3892
|
import { pathToFileURL } from "url";
|
|
3682
3893
|
import { createJiti as createJiti2 } from "jiti";
|
|
3683
3894
|
var createSkillTools = (skills, options) => {
|
|
3684
3895
|
const skillsByName = new Map(skills.map((skill) => [skill.name, skill]));
|
|
3685
3896
|
const knownNames = skills.length > 0 ? skills.map((skill) => skill.name).join(", ") : "(none)";
|
|
3686
3897
|
return [
|
|
3687
|
-
|
|
3898
|
+
defineTool4({
|
|
3688
3899
|
name: "activate_skill",
|
|
3689
3900
|
description: `Load the full instructions for an available skill. Use this when a user's request matches a skill's description. Available skills: ${knownNames}`,
|
|
3690
3901
|
inputSchema: {
|
|
@@ -3721,7 +3932,7 @@ var createSkillTools = (skills, options) => {
|
|
|
3721
3932
|
}
|
|
3722
3933
|
}
|
|
3723
3934
|
}),
|
|
3724
|
-
|
|
3935
|
+
defineTool4({
|
|
3725
3936
|
name: "deactivate_skill",
|
|
3726
3937
|
description: "Deactivate a previously activated skill and update scoped tool availability.",
|
|
3727
3938
|
inputSchema: {
|
|
@@ -3750,7 +3961,7 @@ var createSkillTools = (skills, options) => {
|
|
|
3750
3961
|
}
|
|
3751
3962
|
}
|
|
3752
3963
|
}),
|
|
3753
|
-
|
|
3964
|
+
defineTool4({
|
|
3754
3965
|
name: "list_active_skills",
|
|
3755
3966
|
description: "List currently active skills with scoped MCP tools.",
|
|
3756
3967
|
inputSchema: {
|
|
@@ -3762,7 +3973,7 @@ var createSkillTools = (skills, options) => {
|
|
|
3762
3973
|
activeSkills: options?.onListActiveSkills ? options.onListActiveSkills() : []
|
|
3763
3974
|
})
|
|
3764
3975
|
}),
|
|
3765
|
-
|
|
3976
|
+
defineTool4({
|
|
3766
3977
|
name: "read_skill_resource",
|
|
3767
3978
|
description: `Read a file from a skill's directory (references, scripts, assets). Use relative paths from the skill root. Available skills: ${knownNames}`,
|
|
3768
3979
|
inputSchema: {
|
|
@@ -3802,7 +4013,7 @@ var createSkillTools = (skills, options) => {
|
|
|
3802
4013
|
}
|
|
3803
4014
|
}
|
|
3804
4015
|
}),
|
|
3805
|
-
|
|
4016
|
+
defineTool4({
|
|
3806
4017
|
name: "list_skill_scripts",
|
|
3807
4018
|
description: `List JavaScript/TypeScript script files available under a skill directory (recursive). Available skills: ${knownNames}`,
|
|
3808
4019
|
inputSchema: {
|
|
@@ -3837,7 +4048,7 @@ var createSkillTools = (skills, options) => {
|
|
|
3837
4048
|
}
|
|
3838
4049
|
}
|
|
3839
4050
|
}),
|
|
3840
|
-
|
|
4051
|
+
defineTool4({
|
|
3841
4052
|
name: "run_skill_script",
|
|
3842
4053
|
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}`,
|
|
3843
4054
|
inputSchema: {
|
|
@@ -3934,7 +4145,7 @@ var collectScriptFiles = async (directory) => {
|
|
|
3934
4145
|
if (entry.name === "node_modules") {
|
|
3935
4146
|
continue;
|
|
3936
4147
|
}
|
|
3937
|
-
const fullPath =
|
|
4148
|
+
const fullPath = resolve9(directory, entry.name);
|
|
3938
4149
|
let isDir = entry.isDirectory();
|
|
3939
4150
|
let isFile = entry.isFile();
|
|
3940
4151
|
if (entry.isSymbolicLink()) {
|
|
@@ -3973,8 +4184,8 @@ var normalizeScriptPolicyPath = (relativePath) => {
|
|
|
3973
4184
|
};
|
|
3974
4185
|
var resolveScriptPath = (baseDir, relativePath) => {
|
|
3975
4186
|
const normalized = normalizeScriptPolicyPath(relativePath);
|
|
3976
|
-
const fullPath =
|
|
3977
|
-
if (!fullPath.startsWith(`${
|
|
4187
|
+
const fullPath = resolve9(baseDir, normalized);
|
|
4188
|
+
if (!fullPath.startsWith(`${resolve9(baseDir)}${sep2}`) && fullPath !== resolve9(baseDir)) {
|
|
3978
4189
|
throw new Error("Script path must stay inside the allowed directory");
|
|
3979
4190
|
}
|
|
3980
4191
|
const extension = extname(fullPath).toLowerCase();
|
|
@@ -4048,36 +4259,11 @@ var extractRunnableFunction = (value) => {
|
|
|
4048
4259
|
};
|
|
4049
4260
|
|
|
4050
4261
|
// src/subagent-tools.ts
|
|
4051
|
-
import { defineTool as
|
|
4052
|
-
var
|
|
4053
|
-
|
|
4054
|
-
const summary = {
|
|
4055
|
-
subagentId: r.subagentId,
|
|
4056
|
-
status: r.status
|
|
4057
|
-
};
|
|
4058
|
-
if (r.result) {
|
|
4059
|
-
summary.result = {
|
|
4060
|
-
status: r.result.status,
|
|
4061
|
-
response: r.result.response,
|
|
4062
|
-
steps: r.result.steps,
|
|
4063
|
-
duration: r.result.duration
|
|
4064
|
-
};
|
|
4065
|
-
}
|
|
4066
|
-
if (r.error) {
|
|
4067
|
-
summary.error = r.error;
|
|
4068
|
-
}
|
|
4069
|
-
if (r.latestMessages && r.latestMessages.length > 0) {
|
|
4070
|
-
summary.latestMessages = r.latestMessages.slice(-LAST_MESSAGES_TO_RETURN).map((m) => ({
|
|
4071
|
-
role: m.role,
|
|
4072
|
-
content: getTextContent2(m).slice(0, 2e3)
|
|
4073
|
-
}));
|
|
4074
|
-
}
|
|
4075
|
-
return summary;
|
|
4076
|
-
};
|
|
4077
|
-
var createSubagentTools = (manager, getConversationId, getOwnerId) => [
|
|
4078
|
-
defineTool4({
|
|
4262
|
+
import { defineTool as defineTool5 } from "@poncho-ai/sdk";
|
|
4263
|
+
var createSubagentTools = (manager) => [
|
|
4264
|
+
defineTool5({
|
|
4079
4265
|
name: "spawn_subagent",
|
|
4080
|
-
description: "Spawn a subagent to work on a task
|
|
4266
|
+
description: "Spawn a subagent to work on a task in the background. Returns immediately with a subagent ID. The subagent runs independently and its result will be delivered to you as a message in the conversation when it completes.\n\nGuidelines:\n- Spawn all needed subagents in a SINGLE response (they run concurrently), then end your turn with a brief message to the user.\n- Do NOT spawn more subagents in follow-up steps. Wait for results to be delivered before deciding if more work is needed.\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.",
|
|
4081
4267
|
inputSchema: {
|
|
4082
4268
|
type: "object",
|
|
4083
4269
|
properties: {
|
|
@@ -4089,26 +4275,27 @@ var createSubagentTools = (manager, getConversationId, getOwnerId) => [
|
|
|
4089
4275
|
required: ["task"],
|
|
4090
4276
|
additionalProperties: false
|
|
4091
4277
|
},
|
|
4092
|
-
handler: async (input) => {
|
|
4278
|
+
handler: async (input, context) => {
|
|
4093
4279
|
const task = typeof input.task === "string" ? input.task : "";
|
|
4094
4280
|
if (!task.trim()) {
|
|
4095
4281
|
return { error: "task is required" };
|
|
4096
4282
|
}
|
|
4097
|
-
const conversationId =
|
|
4283
|
+
const conversationId = context.conversationId;
|
|
4098
4284
|
if (!conversationId) {
|
|
4099
4285
|
return { error: "no active conversation to spawn subagent from" };
|
|
4100
4286
|
}
|
|
4101
|
-
const
|
|
4287
|
+
const ownerId = typeof context.parameters.__ownerId === "string" ? context.parameters.__ownerId : "anonymous";
|
|
4288
|
+
const { subagentId } = await manager.spawn({
|
|
4102
4289
|
task: task.trim(),
|
|
4103
4290
|
parentConversationId: conversationId,
|
|
4104
|
-
ownerId
|
|
4291
|
+
ownerId
|
|
4105
4292
|
});
|
|
4106
|
-
return
|
|
4293
|
+
return { subagentId, status: "running" };
|
|
4107
4294
|
}
|
|
4108
4295
|
}),
|
|
4109
|
-
|
|
4296
|
+
defineTool5({
|
|
4110
4297
|
name: "message_subagent",
|
|
4111
|
-
description: "Send a follow-up message to a completed or stopped subagent
|
|
4298
|
+
description: "Send a follow-up message to a completed or stopped subagent. The subagent restarts in the background and its result will be delivered to you as a message when it completes. Only works when the subagent is not currently running.",
|
|
4112
4299
|
inputSchema: {
|
|
4113
4300
|
type: "object",
|
|
4114
4301
|
properties: {
|
|
@@ -4130,11 +4317,11 @@ var createSubagentTools = (manager, getConversationId, getOwnerId) => [
|
|
|
4130
4317
|
if (!subagentId || !message.trim()) {
|
|
4131
4318
|
return { error: "subagent_id and message are required" };
|
|
4132
4319
|
}
|
|
4133
|
-
const
|
|
4134
|
-
return
|
|
4320
|
+
const { subagentId: id } = await manager.sendMessage(subagentId, message.trim());
|
|
4321
|
+
return { subagentId: id, status: "running" };
|
|
4135
4322
|
}
|
|
4136
4323
|
}),
|
|
4137
|
-
|
|
4324
|
+
defineTool5({
|
|
4138
4325
|
name: "stop_subagent",
|
|
4139
4326
|
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.",
|
|
4140
4327
|
inputSchema: {
|
|
@@ -4157,7 +4344,7 @@ var createSubagentTools = (manager, getConversationId, getOwnerId) => [
|
|
|
4157
4344
|
return { message: `Subagent "${subagentId}" has been stopped.` };
|
|
4158
4345
|
}
|
|
4159
4346
|
}),
|
|
4160
|
-
|
|
4347
|
+
defineTool5({
|
|
4161
4348
|
name: "list_subagents",
|
|
4162
4349
|
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.",
|
|
4163
4350
|
inputSchema: {
|
|
@@ -4165,8 +4352,8 @@ var createSubagentTools = (manager, getConversationId, getOwnerId) => [
|
|
|
4165
4352
|
properties: {},
|
|
4166
4353
|
additionalProperties: false
|
|
4167
4354
|
},
|
|
4168
|
-
handler: async () => {
|
|
4169
|
-
const conversationId =
|
|
4355
|
+
handler: async (_input, context) => {
|
|
4356
|
+
const conversationId = context.conversationId;
|
|
4170
4357
|
if (!conversationId) {
|
|
4171
4358
|
return { error: "no active conversation" };
|
|
4172
4359
|
}
|
|
@@ -4705,6 +4892,7 @@ var AgentHarness = class _AgentHarness {
|
|
|
4705
4892
|
uploadStore;
|
|
4706
4893
|
skillContextWindow = "";
|
|
4707
4894
|
memoryStore;
|
|
4895
|
+
todoStore;
|
|
4708
4896
|
loadedConfig;
|
|
4709
4897
|
loadedSkills = [];
|
|
4710
4898
|
skillFingerprint = "";
|
|
@@ -4761,11 +4949,7 @@ var AgentHarness = class _AgentHarness {
|
|
|
4761
4949
|
setSubagentManager(manager) {
|
|
4762
4950
|
this.subagentManager = manager;
|
|
4763
4951
|
this.dispatcher.registerMany(
|
|
4764
|
-
createSubagentTools(
|
|
4765
|
-
manager,
|
|
4766
|
-
() => this._currentRunConversationId,
|
|
4767
|
-
() => this._currentRunOwnerId ?? "anonymous"
|
|
4768
|
-
)
|
|
4952
|
+
createSubagentTools(manager)
|
|
4769
4953
|
);
|
|
4770
4954
|
}
|
|
4771
4955
|
registerConfiguredBuiltInTools(config) {
|
|
@@ -4813,6 +4997,10 @@ var AgentHarness = class _AgentHarness {
|
|
|
4813
4997
|
get frontmatter() {
|
|
4814
4998
|
return this.parsedAgent?.frontmatter;
|
|
4815
4999
|
}
|
|
5000
|
+
async getTodos(conversationId) {
|
|
5001
|
+
if (!this.todoStore) return [];
|
|
5002
|
+
return this.todoStore.get(conversationId);
|
|
5003
|
+
}
|
|
4816
5004
|
listActiveSkills() {
|
|
4817
5005
|
return [...this.activeSkillNames].sort();
|
|
4818
5006
|
}
|
|
@@ -5008,8 +5196,8 @@ var AgentHarness = class _AgentHarness {
|
|
|
5008
5196
|
return false;
|
|
5009
5197
|
}
|
|
5010
5198
|
try {
|
|
5011
|
-
const agentFilePath =
|
|
5012
|
-
const rawContent = await
|
|
5199
|
+
const agentFilePath = resolve10(this.workingDir, "AGENT.md");
|
|
5200
|
+
const rawContent = await readFile8(agentFilePath, "utf8");
|
|
5013
5201
|
if (rawContent === this.agentFileFingerprint) {
|
|
5014
5202
|
return false;
|
|
5015
5203
|
}
|
|
@@ -5078,8 +5266,8 @@ var AgentHarness = class _AgentHarness {
|
|
|
5078
5266
|
}
|
|
5079
5267
|
}
|
|
5080
5268
|
async initialize() {
|
|
5081
|
-
const agentFilePath =
|
|
5082
|
-
const agentRawContent = await
|
|
5269
|
+
const agentFilePath = resolve10(this.workingDir, "AGENT.md");
|
|
5270
|
+
const agentRawContent = await readFile8(agentFilePath, "utf8");
|
|
5083
5271
|
this.parsedAgent = parseAgentMarkdown(agentRawContent);
|
|
5084
5272
|
this.agentFileFingerprint = agentRawContent;
|
|
5085
5273
|
const identity = await ensureAgentIdentity(this.workingDir);
|
|
@@ -5102,8 +5290,8 @@ var AgentHarness = class _AgentHarness {
|
|
|
5102
5290
|
this.skillContextWindow = buildSkillContextWindow(skillMetadata);
|
|
5103
5291
|
this.skillFingerprint = this.buildSkillFingerprint(skillMetadata);
|
|
5104
5292
|
this.registerSkillTools(skillMetadata);
|
|
5293
|
+
const agentId = this.parsedAgent.frontmatter.id ?? this.parsedAgent.frontmatter.name;
|
|
5105
5294
|
if (memoryConfig?.enabled) {
|
|
5106
|
-
const agentId = this.parsedAgent.frontmatter.id ?? this.parsedAgent.frontmatter.name;
|
|
5107
5295
|
this.memoryStore = createMemoryStore(
|
|
5108
5296
|
agentId,
|
|
5109
5297
|
memoryConfig,
|
|
@@ -5115,6 +5303,13 @@ var AgentHarness = class _AgentHarness {
|
|
|
5115
5303
|
})
|
|
5116
5304
|
);
|
|
5117
5305
|
}
|
|
5306
|
+
const stateConfig = resolveStateConfig(config);
|
|
5307
|
+
this.todoStore = createTodoStore(agentId, stateConfig, { workingDir: this.workingDir });
|
|
5308
|
+
for (const tool of createTodoTools(this.todoStore)) {
|
|
5309
|
+
if (this.isToolEnabled(tool.name)) {
|
|
5310
|
+
this.registerIfMissing(tool);
|
|
5311
|
+
}
|
|
5312
|
+
}
|
|
5118
5313
|
if (config?.browser) {
|
|
5119
5314
|
await this.initBrowserTools(config).catch((e) => {
|
|
5120
5315
|
console.warn(
|
|
@@ -5154,14 +5349,14 @@ var AgentHarness = class _AgentHarness {
|
|
|
5154
5349
|
const filePath = pathResolve(stateDir, `${sessionId}.json`);
|
|
5155
5350
|
return {
|
|
5156
5351
|
async save(json) {
|
|
5157
|
-
const { mkdir:
|
|
5158
|
-
await
|
|
5159
|
-
await
|
|
5352
|
+
const { mkdir: mkdir6, writeFile: writeFile7 } = await import("fs/promises");
|
|
5353
|
+
await mkdir6(stateDir, { recursive: true });
|
|
5354
|
+
await writeFile7(filePath, json, "utf8");
|
|
5160
5355
|
},
|
|
5161
5356
|
async load() {
|
|
5162
|
-
const { readFile:
|
|
5357
|
+
const { readFile: readFile10 } = await import("fs/promises");
|
|
5163
5358
|
try {
|
|
5164
|
-
return await
|
|
5359
|
+
return await readFile10(filePath, "utf8");
|
|
5165
5360
|
} catch {
|
|
5166
5361
|
return void 0;
|
|
5167
5362
|
}
|
|
@@ -5227,7 +5422,7 @@ var AgentHarness = class _AgentHarness {
|
|
|
5227
5422
|
let browserMod;
|
|
5228
5423
|
try {
|
|
5229
5424
|
const { existsSync } = await import("fs");
|
|
5230
|
-
const { join, dirname:
|
|
5425
|
+
const { join, dirname: dirname6 } = await import("path");
|
|
5231
5426
|
const { pathToFileURL: pathToFileURL2 } = await import("url");
|
|
5232
5427
|
let searchDir = this.workingDir;
|
|
5233
5428
|
let entryPath;
|
|
@@ -5237,7 +5432,7 @@ var AgentHarness = class _AgentHarness {
|
|
|
5237
5432
|
entryPath = candidate;
|
|
5238
5433
|
break;
|
|
5239
5434
|
}
|
|
5240
|
-
const parent =
|
|
5435
|
+
const parent = dirname6(searchDir);
|
|
5241
5436
|
if (parent === searchDir) break;
|
|
5242
5437
|
searchDir = parent;
|
|
5243
5438
|
}
|
|
@@ -5260,8 +5455,7 @@ var AgentHarness = class _AgentHarness {
|
|
|
5260
5455
|
const session = new browserMod.BrowserSession(sessionId, browserCfg);
|
|
5261
5456
|
this._browserSession = session;
|
|
5262
5457
|
const tools = browserMod.createBrowserTools(
|
|
5263
|
-
() => session
|
|
5264
|
-
() => this._currentRunConversationId ?? "__default__"
|
|
5458
|
+
() => session
|
|
5265
5459
|
);
|
|
5266
5460
|
for (const tool of tools) {
|
|
5267
5461
|
if (this.isToolEnabled(tool.name)) {
|
|
@@ -5269,10 +5463,6 @@ var AgentHarness = class _AgentHarness {
|
|
|
5269
5463
|
}
|
|
5270
5464
|
}
|
|
5271
5465
|
}
|
|
5272
|
-
/** Conversation ID of the currently executing run (set during run, cleared after). */
|
|
5273
|
-
_currentRunConversationId;
|
|
5274
|
-
/** Owner ID of the currently executing run (used by subagent tools). */
|
|
5275
|
-
_currentRunOwnerId;
|
|
5276
5466
|
get browserSession() {
|
|
5277
5467
|
return this._browserSession;
|
|
5278
5468
|
}
|
|
@@ -5325,9 +5515,9 @@ var AgentHarness = class _AgentHarness {
|
|
|
5325
5515
|
for await (const event of this.run(input)) {
|
|
5326
5516
|
eventQueue.push(event);
|
|
5327
5517
|
if (queueResolve) {
|
|
5328
|
-
const
|
|
5518
|
+
const resolve12 = queueResolve;
|
|
5329
5519
|
queueResolve = null;
|
|
5330
|
-
|
|
5520
|
+
resolve12();
|
|
5331
5521
|
}
|
|
5332
5522
|
}
|
|
5333
5523
|
} catch (error) {
|
|
@@ -5346,8 +5536,8 @@ var AgentHarness = class _AgentHarness {
|
|
|
5346
5536
|
if (eventQueue.length > 0) {
|
|
5347
5537
|
yield eventQueue.shift();
|
|
5348
5538
|
} else if (!generatorDone) {
|
|
5349
|
-
await new Promise((
|
|
5350
|
-
queueResolve =
|
|
5539
|
+
await new Promise((resolve12) => {
|
|
5540
|
+
queueResolve = resolve12;
|
|
5351
5541
|
});
|
|
5352
5542
|
}
|
|
5353
5543
|
}
|
|
@@ -5387,11 +5577,6 @@ var AgentHarness = class _AgentHarness {
|
|
|
5387
5577
|
const memoryPromise = this.memoryStore ? this.memoryStore.getMainMemory() : void 0;
|
|
5388
5578
|
await this.refreshAgentIfChanged();
|
|
5389
5579
|
await this.refreshSkillsIfChanged();
|
|
5390
|
-
this._currentRunConversationId = input.conversationId;
|
|
5391
|
-
const ownerParam = input.parameters?.__ownerId;
|
|
5392
|
-
if (typeof ownerParam === "string") {
|
|
5393
|
-
this._currentRunOwnerId = ownerParam;
|
|
5394
|
-
}
|
|
5395
5580
|
let agent = this.parsedAgent;
|
|
5396
5581
|
const runId = `run_${randomUUID3()}`;
|
|
5397
5582
|
const start = now();
|
|
@@ -5399,7 +5584,7 @@ var AgentHarness = class _AgentHarness {
|
|
|
5399
5584
|
const configuredTimeout = agent.frontmatter.limits?.timeout;
|
|
5400
5585
|
const timeoutMs = this.environment === "development" && configuredTimeout == null ? 0 : (configuredTimeout ?? 300) * 1e3;
|
|
5401
5586
|
const platformMaxDurationSec = Number(process.env.PONCHO_MAX_DURATION) || 0;
|
|
5402
|
-
const softDeadlineMs = platformMaxDurationSec
|
|
5587
|
+
const softDeadlineMs = input.disableSoftDeadline || platformMaxDurationSec <= 0 ? 0 : platformMaxDurationSec * 800;
|
|
5403
5588
|
const messages = [...input.messages ?? []];
|
|
5404
5589
|
const inputMessageCount = messages.length;
|
|
5405
5590
|
const events = [];
|
|
@@ -5533,6 +5718,15 @@ ${this.skillFingerprint}`;
|
|
|
5533
5718
|
metadata: { timestamp: now(), id: randomUUID3() }
|
|
5534
5719
|
});
|
|
5535
5720
|
}
|
|
5721
|
+
} else {
|
|
5722
|
+
const lastMsg = messages[messages.length - 1];
|
|
5723
|
+
if (lastMsg && lastMsg.role !== "user") {
|
|
5724
|
+
messages.push({
|
|
5725
|
+
role: "user",
|
|
5726
|
+
content: "[System: Your previous turn was interrupted by a time limit. Continue from where you left off \u2014 do NOT repeat what you already said. Proceed directly with the next action or tool call.]",
|
|
5727
|
+
metadata: { timestamp: now(), id: randomUUID3() }
|
|
5728
|
+
});
|
|
5729
|
+
}
|
|
5536
5730
|
}
|
|
5537
5731
|
let responseText = "";
|
|
5538
5732
|
let totalInputTokens = 0;
|
|
@@ -5567,6 +5761,7 @@ ${this.skillFingerprint}`;
|
|
|
5567
5761
|
tokens: { input: totalInputTokens, output: totalOutputTokens, cached: totalCachedTokens },
|
|
5568
5762
|
duration: now() - start,
|
|
5569
5763
|
continuation: true,
|
|
5764
|
+
continuationMessages: [...messages],
|
|
5570
5765
|
maxSteps
|
|
5571
5766
|
};
|
|
5572
5767
|
yield pushEvent({ type: "run:completed", runId, result: result2 });
|
|
@@ -5600,7 +5795,7 @@ ${this.skillFingerprint}`;
|
|
|
5600
5795
|
if (rich && rich.length > 0) {
|
|
5601
5796
|
return [{ role: "tool", content: rich }];
|
|
5602
5797
|
}
|
|
5603
|
-
const textContent = typeof msg.content === "string" ? msg.content :
|
|
5798
|
+
const textContent = typeof msg.content === "string" ? msg.content : getTextContent2(msg);
|
|
5604
5799
|
try {
|
|
5605
5800
|
const parsed = JSON.parse(textContent);
|
|
5606
5801
|
if (!Array.isArray(parsed)) {
|
|
@@ -5650,7 +5845,7 @@ ${this.skillFingerprint}`;
|
|
|
5650
5845
|
}
|
|
5651
5846
|
}
|
|
5652
5847
|
if (msg.role === "assistant") {
|
|
5653
|
-
const assistantText = typeof msg.content === "string" ? msg.content :
|
|
5848
|
+
const assistantText = typeof msg.content === "string" ? msg.content : getTextContent2(msg);
|
|
5654
5849
|
try {
|
|
5655
5850
|
const parsed = JSON.parse(assistantText);
|
|
5656
5851
|
if (typeof parsed === "object" && parsed !== null) {
|
|
@@ -5684,12 +5879,15 @@ ${this.skillFingerprint}`;
|
|
|
5684
5879
|
}
|
|
5685
5880
|
} catch {
|
|
5686
5881
|
}
|
|
5882
|
+
if (!assistantText || assistantText.trim().length === 0) {
|
|
5883
|
+
return [];
|
|
5884
|
+
}
|
|
5687
5885
|
return [{ role: "assistant", content: assistantText }];
|
|
5688
5886
|
}
|
|
5689
5887
|
if (msg.role === "system") {
|
|
5690
5888
|
return [{
|
|
5691
5889
|
role: "system",
|
|
5692
|
-
content: typeof msg.content === "string" ? msg.content :
|
|
5890
|
+
content: typeof msg.content === "string" ? msg.content : getTextContent2(msg)
|
|
5693
5891
|
}];
|
|
5694
5892
|
}
|
|
5695
5893
|
if (msg.role === "user") {
|
|
@@ -5881,8 +6079,8 @@ ${textContent}` };
|
|
|
5881
6079
|
let timer;
|
|
5882
6080
|
nextPart = await Promise.race([
|
|
5883
6081
|
fullStreamIterator.next(),
|
|
5884
|
-
new Promise((
|
|
5885
|
-
timer = setTimeout(() =>
|
|
6082
|
+
new Promise((resolve12) => {
|
|
6083
|
+
timer = setTimeout(() => resolve12(null), timeout);
|
|
5886
6084
|
})
|
|
5887
6085
|
]);
|
|
5888
6086
|
clearTimeout(timer);
|
|
@@ -6243,6 +6441,7 @@ ${this.skillFingerprint}`;
|
|
|
6243
6441
|
tokens: { input: totalInputTokens, output: totalOutputTokens, cached: totalCachedTokens },
|
|
6244
6442
|
duration: now() - start,
|
|
6245
6443
|
continuation: true,
|
|
6444
|
+
continuationMessages: [...messages],
|
|
6246
6445
|
maxSteps
|
|
6247
6446
|
};
|
|
6248
6447
|
yield pushEvent({ type: "run:completed", runId, result });
|
|
@@ -6391,8 +6590,8 @@ var LatitudeCapture = class {
|
|
|
6391
6590
|
|
|
6392
6591
|
// src/state.ts
|
|
6393
6592
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
6394
|
-
import { mkdir as
|
|
6395
|
-
import { dirname as
|
|
6593
|
+
import { mkdir as mkdir5, readFile as readFile9, readdir as readdir4, rename as rename3, rm as rm3, writeFile as writeFile6 } from "fs/promises";
|
|
6594
|
+
import { dirname as dirname5, resolve as resolve11 } from "path";
|
|
6396
6595
|
var DEFAULT_OWNER = "local-owner";
|
|
6397
6596
|
var LOCAL_STATE_FILE = "state.json";
|
|
6398
6597
|
var CONVERSATIONS_DIRECTORY = "conversations";
|
|
@@ -6407,11 +6606,11 @@ var toStoreIdentity = async ({
|
|
|
6407
6606
|
}
|
|
6408
6607
|
return { name: ensured.name, id: agentId };
|
|
6409
6608
|
};
|
|
6410
|
-
var
|
|
6411
|
-
await
|
|
6609
|
+
var writeJsonAtomic3 = async (filePath, payload) => {
|
|
6610
|
+
await mkdir5(dirname5(filePath), { recursive: true });
|
|
6412
6611
|
const tmpPath = `${filePath}.tmp`;
|
|
6413
|
-
await
|
|
6414
|
-
await
|
|
6612
|
+
await writeFile6(tmpPath, JSON.stringify(payload, null, 2), "utf8");
|
|
6613
|
+
await rename3(tmpPath, filePath);
|
|
6415
6614
|
};
|
|
6416
6615
|
var formatUtcTimestamp = (value) => new Date(value).toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
|
|
6417
6616
|
var normalizeTitle = (title) => {
|
|
@@ -6564,6 +6763,13 @@ var InMemoryConversationStore = class {
|
|
|
6564
6763
|
async delete(conversationId) {
|
|
6565
6764
|
return this.conversations.delete(conversationId);
|
|
6566
6765
|
}
|
|
6766
|
+
async appendSubagentResult(conversationId, result) {
|
|
6767
|
+
const conversation = this.conversations.get(conversationId);
|
|
6768
|
+
if (!conversation) return;
|
|
6769
|
+
if (!conversation.pendingSubagentResults) conversation.pendingSubagentResults = [];
|
|
6770
|
+
conversation.pendingSubagentResults.push(result);
|
|
6771
|
+
conversation.updatedAt = Date.now();
|
|
6772
|
+
}
|
|
6567
6773
|
};
|
|
6568
6774
|
var FileConversationStore = class {
|
|
6569
6775
|
workingDir;
|
|
@@ -6585,8 +6791,8 @@ var FileConversationStore = class {
|
|
|
6585
6791
|
agentId: this.agentId
|
|
6586
6792
|
});
|
|
6587
6793
|
const agentDir = getAgentStoreDirectory(identity);
|
|
6588
|
-
const conversationsDir =
|
|
6589
|
-
const indexPath =
|
|
6794
|
+
const conversationsDir = resolve11(agentDir, CONVERSATIONS_DIRECTORY);
|
|
6795
|
+
const indexPath = resolve11(conversationsDir, LOCAL_CONVERSATION_INDEX_FILE);
|
|
6590
6796
|
this.paths = { conversationsDir, indexPath };
|
|
6591
6797
|
return this.paths;
|
|
6592
6798
|
}
|
|
@@ -6596,13 +6802,13 @@ var FileConversationStore = class {
|
|
|
6596
6802
|
schemaVersion: STORAGE_SCHEMA_VERSION,
|
|
6597
6803
|
conversations: Array.from(this.conversations.values()).sort((a, b) => b.updatedAt - a.updatedAt)
|
|
6598
6804
|
};
|
|
6599
|
-
await
|
|
6805
|
+
await writeJsonAtomic3(indexPath, payload);
|
|
6600
6806
|
}
|
|
6601
6807
|
async readConversationFile(fileName) {
|
|
6602
6808
|
const { conversationsDir } = await this.resolvePaths();
|
|
6603
|
-
const filePath =
|
|
6809
|
+
const filePath = resolve11(conversationsDir, fileName);
|
|
6604
6810
|
try {
|
|
6605
|
-
const raw = await
|
|
6811
|
+
const raw = await readFile9(filePath, "utf8");
|
|
6606
6812
|
return JSON.parse(raw);
|
|
6607
6813
|
} catch {
|
|
6608
6814
|
return void 0;
|
|
@@ -6648,7 +6854,7 @@ var FileConversationStore = class {
|
|
|
6648
6854
|
this.loaded = true;
|
|
6649
6855
|
const { indexPath } = await this.resolvePaths();
|
|
6650
6856
|
try {
|
|
6651
|
-
const raw = await
|
|
6857
|
+
const raw = await readFile9(indexPath, "utf8");
|
|
6652
6858
|
const parsed = JSON.parse(raw);
|
|
6653
6859
|
for (const conversation of parsed.conversations ?? []) {
|
|
6654
6860
|
this.conversations.set(conversation.conversationId, conversation);
|
|
@@ -6671,9 +6877,9 @@ var FileConversationStore = class {
|
|
|
6671
6877
|
const { conversationsDir } = await this.resolvePaths();
|
|
6672
6878
|
const existing = this.conversations.get(conversation.conversationId);
|
|
6673
6879
|
const fileName = existing?.fileName ?? this.resolveConversationFileName(conversation);
|
|
6674
|
-
const filePath =
|
|
6880
|
+
const filePath = resolve11(conversationsDir, fileName);
|
|
6675
6881
|
this.writing = this.writing.then(async () => {
|
|
6676
|
-
await
|
|
6882
|
+
await writeJsonAtomic3(filePath, conversation);
|
|
6677
6883
|
this.conversations.set(conversation.conversationId, {
|
|
6678
6884
|
conversationId: conversation.conversationId,
|
|
6679
6885
|
title: conversation.title,
|
|
@@ -6769,7 +6975,7 @@ var FileConversationStore = class {
|
|
|
6769
6975
|
if (removed) {
|
|
6770
6976
|
this.writing = this.writing.then(async () => {
|
|
6771
6977
|
if (existing) {
|
|
6772
|
-
await rm3(
|
|
6978
|
+
await rm3(resolve11(conversationsDir, existing.fileName), { force: true });
|
|
6773
6979
|
}
|
|
6774
6980
|
await this.writeIndex();
|
|
6775
6981
|
});
|
|
@@ -6777,6 +6983,15 @@ var FileConversationStore = class {
|
|
|
6777
6983
|
}
|
|
6778
6984
|
return removed;
|
|
6779
6985
|
}
|
|
6986
|
+
async appendSubagentResult(conversationId, result) {
|
|
6987
|
+
await this.ensureLoaded();
|
|
6988
|
+
const conversation = await this.get(conversationId);
|
|
6989
|
+
if (!conversation) return;
|
|
6990
|
+
if (!conversation.pendingSubagentResults) conversation.pendingSubagentResults = [];
|
|
6991
|
+
conversation.pendingSubagentResults.push(result);
|
|
6992
|
+
conversation.updatedAt = Date.now();
|
|
6993
|
+
await this.update(conversation);
|
|
6994
|
+
}
|
|
6780
6995
|
};
|
|
6781
6996
|
var FileStateStore = class {
|
|
6782
6997
|
workingDir;
|
|
@@ -6799,7 +7014,7 @@ var FileStateStore = class {
|
|
|
6799
7014
|
workingDir: this.workingDir,
|
|
6800
7015
|
agentId: this.agentId
|
|
6801
7016
|
});
|
|
6802
|
-
this.filePath =
|
|
7017
|
+
this.filePath = resolve11(getAgentStoreDirectory(identity), LOCAL_STATE_FILE);
|
|
6803
7018
|
}
|
|
6804
7019
|
isExpired(state) {
|
|
6805
7020
|
return typeof this.ttlMs === "number" && Date.now() - state.updatedAt > this.ttlMs;
|
|
@@ -6811,7 +7026,7 @@ var FileStateStore = class {
|
|
|
6811
7026
|
}
|
|
6812
7027
|
this.loaded = true;
|
|
6813
7028
|
try {
|
|
6814
|
-
const raw = await
|
|
7029
|
+
const raw = await readFile9(this.filePath, "utf8");
|
|
6815
7030
|
const parsed = JSON.parse(raw);
|
|
6816
7031
|
for (const state of parsed.states ?? []) {
|
|
6817
7032
|
this.states.set(state.runId, state);
|
|
@@ -6824,7 +7039,7 @@ var FileStateStore = class {
|
|
|
6824
7039
|
states: Array.from(this.states.values())
|
|
6825
7040
|
};
|
|
6826
7041
|
this.writing = this.writing.then(async () => {
|
|
6827
|
-
await
|
|
7042
|
+
await writeJsonAtomic3(this.filePath, payload);
|
|
6828
7043
|
});
|
|
6829
7044
|
await this.writing;
|
|
6830
7045
|
}
|
|
@@ -6856,6 +7071,7 @@ var KeyValueConversationStoreBase = class {
|
|
|
6856
7071
|
ttl;
|
|
6857
7072
|
agentIdPromise;
|
|
6858
7073
|
ownerLocks = /* @__PURE__ */ new Map();
|
|
7074
|
+
appendLocks = /* @__PURE__ */ new Map();
|
|
6859
7075
|
memoryFallback;
|
|
6860
7076
|
constructor(ttl, workingDir, agentId) {
|
|
6861
7077
|
this.ttl = ttl;
|
|
@@ -6874,6 +7090,18 @@ var KeyValueConversationStoreBase = class {
|
|
|
6874
7090
|
}
|
|
6875
7091
|
}
|
|
6876
7092
|
}
|
|
7093
|
+
async withAppendLock(conversationId, task) {
|
|
7094
|
+
const prev = this.appendLocks.get(conversationId) ?? Promise.resolve();
|
|
7095
|
+
const next = prev.then(task, task);
|
|
7096
|
+
this.appendLocks.set(conversationId, next);
|
|
7097
|
+
try {
|
|
7098
|
+
await next;
|
|
7099
|
+
} finally {
|
|
7100
|
+
if (this.appendLocks.get(conversationId) === next) {
|
|
7101
|
+
this.appendLocks.delete(conversationId);
|
|
7102
|
+
}
|
|
7103
|
+
}
|
|
7104
|
+
}
|
|
6877
7105
|
async namespace() {
|
|
6878
7106
|
const agentId = await this.agentIdPromise;
|
|
6879
7107
|
return `poncho:${STORAGE_SCHEMA_VERSION}:${slugifyStorageComponent(agentId)}`;
|
|
@@ -7094,6 +7322,16 @@ var KeyValueConversationStoreBase = class {
|
|
|
7094
7322
|
});
|
|
7095
7323
|
return true;
|
|
7096
7324
|
}
|
|
7325
|
+
async appendSubagentResult(conversationId, result) {
|
|
7326
|
+
await this.withAppendLock(conversationId, async () => {
|
|
7327
|
+
const conversation = await this.get(conversationId);
|
|
7328
|
+
if (!conversation) return;
|
|
7329
|
+
if (!conversation.pendingSubagentResults) conversation.pendingSubagentResults = [];
|
|
7330
|
+
conversation.pendingSubagentResults.push(result);
|
|
7331
|
+
conversation.updatedAt = Date.now();
|
|
7332
|
+
await this.update(conversation);
|
|
7333
|
+
});
|
|
7334
|
+
}
|
|
7097
7335
|
};
|
|
7098
7336
|
var UpstashConversationStore = class extends KeyValueConversationStoreBase {
|
|
7099
7337
|
baseUrl;
|
|
@@ -7136,20 +7374,26 @@ var UpstashConversationStore = class extends KeyValueConversationStoreBase {
|
|
|
7136
7374
|
return (payload.result ?? []).map((v) => v ?? void 0);
|
|
7137
7375
|
},
|
|
7138
7376
|
set: async (key, value, ttl) => {
|
|
7139
|
-
const
|
|
7140
|
-
|
|
7141
|
-
ttl
|
|
7142
|
-
)}/${encodeURIComponent(value)}` : `${this.baseUrl}/set/${encodeURIComponent(key)}/${encodeURIComponent(value)}`;
|
|
7143
|
-
await fetch(endpoint, {
|
|
7377
|
+
const command = typeof ttl === "number" ? ["SETEX", key, Math.max(1, ttl), value] : ["SET", key, value];
|
|
7378
|
+
const response = await fetch(this.baseUrl, {
|
|
7144
7379
|
method: "POST",
|
|
7145
|
-
headers: this.headers()
|
|
7380
|
+
headers: this.headers(),
|
|
7381
|
+
body: JSON.stringify(command)
|
|
7146
7382
|
});
|
|
7383
|
+
if (!response.ok) {
|
|
7384
|
+
const text = await response.text().catch(() => "");
|
|
7385
|
+
console.error(`[store][upstash] SET failed (${response.status}): ${text.slice(0, 200)}`);
|
|
7386
|
+
}
|
|
7147
7387
|
},
|
|
7148
7388
|
del: async (key) => {
|
|
7149
|
-
await fetch(`${this.baseUrl}/del/${encodeURIComponent(key)}`, {
|
|
7389
|
+
const response = await fetch(`${this.baseUrl}/del/${encodeURIComponent(key)}`, {
|
|
7150
7390
|
method: "POST",
|
|
7151
7391
|
headers: this.headers()
|
|
7152
7392
|
});
|
|
7393
|
+
if (!response.ok) {
|
|
7394
|
+
const text = await response.text().catch(() => "");
|
|
7395
|
+
console.error(`[store][upstash] DEL failed (${response.status}): ${text.slice(0, 200)}`);
|
|
7396
|
+
}
|
|
7153
7397
|
}
|
|
7154
7398
|
};
|
|
7155
7399
|
}
|
|
@@ -7538,7 +7782,7 @@ var TelemetryEmitter = class {
|
|
|
7538
7782
|
};
|
|
7539
7783
|
|
|
7540
7784
|
// src/index.ts
|
|
7541
|
-
import { defineTool as
|
|
7785
|
+
import { defineTool as defineTool6 } from "@poncho-ai/sdk";
|
|
7542
7786
|
export {
|
|
7543
7787
|
AgentHarness,
|
|
7544
7788
|
InMemoryConversationStore,
|
|
@@ -7568,7 +7812,7 @@ export {
|
|
|
7568
7812
|
createSubagentTools,
|
|
7569
7813
|
createUploadStore,
|
|
7570
7814
|
createWriteTool,
|
|
7571
|
-
|
|
7815
|
+
defineTool6 as defineTool,
|
|
7572
7816
|
deriveUploadKey,
|
|
7573
7817
|
ensureAgentIdentity,
|
|
7574
7818
|
estimateTokens,
|