@poncho-ai/harness 0.26.0 → 0.28.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 +4 -4
- package/CHANGELOG.md +28 -0
- package/dist/index.d.ts +28 -8
- package/dist/index.js +265 -86
- package/package.json +3 -2
- package/src/config.ts +2 -0
- package/src/harness.ts +33 -22
- package/src/index.ts +1 -0
- package/src/kv-store.ts +18 -8
- package/src/search-tools.ts +181 -0
- package/src/state.ts +74 -9
- package/src/subagent-manager.ts +6 -2
- package/src/subagent-tools.ts +21 -48
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
|
|
@@ -2032,7 +2034,7 @@ var ponchoDocsTool = defineTool({
|
|
|
2032
2034
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
2033
2035
|
import { readFile as readFile8 } from "fs/promises";
|
|
2034
2036
|
import { resolve as resolve10 } from "path";
|
|
2035
|
-
import { getTextContent as
|
|
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";
|
|
@@ -2368,16 +2370,26 @@ var UpstashKVStore = class {
|
|
|
2368
2370
|
return payload.result ?? void 0;
|
|
2369
2371
|
}
|
|
2370
2372
|
async set(key, value) {
|
|
2371
|
-
await fetch(
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
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
|
+
}
|
|
2375
2382
|
}
|
|
2376
2383
|
async setWithTtl(key, value, ttl) {
|
|
2377
|
-
await fetch(
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
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
|
+
}
|
|
2381
2393
|
}
|
|
2382
2394
|
};
|
|
2383
2395
|
var RedisKVStore = class {
|
|
@@ -4246,37 +4258,148 @@ var extractRunnableFunction = (value) => {
|
|
|
4246
4258
|
return void 0;
|
|
4247
4259
|
};
|
|
4248
4260
|
|
|
4249
|
-
// src/
|
|
4250
|
-
import {
|
|
4251
|
-
|
|
4252
|
-
var
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
summary.error = r.error;
|
|
4267
|
-
}
|
|
4268
|
-
if (r.latestMessages && r.latestMessages.length > 0) {
|
|
4269
|
-
summary.latestMessages = r.latestMessages.slice(-LAST_MESSAGES_TO_RETURN).map((m) => ({
|
|
4270
|
-
role: m.role,
|
|
4271
|
-
content: getTextContent2(m).slice(0, 2e3)
|
|
4272
|
-
}));
|
|
4261
|
+
// src/search-tools.ts
|
|
4262
|
+
import { load as cheerioLoad } from "cheerio";
|
|
4263
|
+
import { defineTool as defineTool5 } from "@poncho-ai/sdk";
|
|
4264
|
+
var SEARCH_UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36";
|
|
4265
|
+
var FETCH_TIMEOUT_MS = 15e3;
|
|
4266
|
+
async function braveSearch(query, maxResults) {
|
|
4267
|
+
const url = `https://search.brave.com/search?q=${encodeURIComponent(query)}`;
|
|
4268
|
+
const res = await fetch(url, {
|
|
4269
|
+
headers: {
|
|
4270
|
+
"User-Agent": SEARCH_UA,
|
|
4271
|
+
Accept: "text/html,application/xhtml+xml",
|
|
4272
|
+
"Accept-Language": "en-US,en;q=0.9"
|
|
4273
|
+
},
|
|
4274
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
4275
|
+
});
|
|
4276
|
+
if (!res.ok) {
|
|
4277
|
+
throw new Error(`Search request failed (${res.status} ${res.statusText})`);
|
|
4273
4278
|
}
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4279
|
+
const html = await res.text();
|
|
4280
|
+
return parseBraveResults(html, maxResults);
|
|
4281
|
+
}
|
|
4282
|
+
function parseBraveResults(html, max) {
|
|
4283
|
+
const $ = cheerioLoad(html);
|
|
4284
|
+
const results = [];
|
|
4285
|
+
$('div.snippet[data-type="web"]').each((_i, el) => {
|
|
4286
|
+
if (results.length >= max) return false;
|
|
4287
|
+
const $el = $(el);
|
|
4288
|
+
const anchor = $el.find(".result-content a").first();
|
|
4289
|
+
const href = anchor.attr("href") ?? "";
|
|
4290
|
+
if (!href.startsWith("http")) return;
|
|
4291
|
+
const title = $el.find(".title").first().text().trim();
|
|
4292
|
+
const snippet = $el.find(".generic-snippet .content").first().text().trim();
|
|
4293
|
+
if (title) {
|
|
4294
|
+
results.push({ title, url: href, snippet });
|
|
4295
|
+
}
|
|
4296
|
+
});
|
|
4297
|
+
return results;
|
|
4298
|
+
}
|
|
4299
|
+
var DEFAULT_MAX_LENGTH = 16e3;
|
|
4300
|
+
function extractReadableText($, maxLength) {
|
|
4301
|
+
const title = $("title").first().text().trim();
|
|
4302
|
+
$("script, style, noscript, nav, footer, header, aside, [role='navigation'], [role='banner'], [role='contentinfo']").remove();
|
|
4303
|
+
$("svg, iframe, form, button, input, select, textarea").remove();
|
|
4304
|
+
let root = $("article").first();
|
|
4305
|
+
if (!root.length) root = $("main").first();
|
|
4306
|
+
if (!root.length) root = $("[role='main']").first();
|
|
4307
|
+
if (!root.length) root = $("body").first();
|
|
4308
|
+
const text = root.text().replace(/[ \t]+/g, " ").replace(/\n{3,}/g, "\n\n").trim();
|
|
4309
|
+
const content = text.length > maxLength ? text.slice(0, maxLength) + "\n\u2026(truncated)" : text;
|
|
4310
|
+
return { title, content };
|
|
4311
|
+
}
|
|
4312
|
+
var createSearchTools = () => [
|
|
4277
4313
|
defineTool5({
|
|
4314
|
+
name: "web_search",
|
|
4315
|
+
description: "Search the web and return a list of results (title, URL, snippet). Use this instead of opening a browser when you need to find information online.",
|
|
4316
|
+
inputSchema: {
|
|
4317
|
+
type: "object",
|
|
4318
|
+
properties: {
|
|
4319
|
+
query: {
|
|
4320
|
+
type: "string",
|
|
4321
|
+
description: "The search query"
|
|
4322
|
+
},
|
|
4323
|
+
max_results: {
|
|
4324
|
+
type: "number",
|
|
4325
|
+
description: "Maximum number of results to return (1-10, default 5)"
|
|
4326
|
+
}
|
|
4327
|
+
},
|
|
4328
|
+
required: ["query"],
|
|
4329
|
+
additionalProperties: false
|
|
4330
|
+
},
|
|
4331
|
+
handler: async (input) => {
|
|
4332
|
+
const query = typeof input.query === "string" ? input.query.trim() : "";
|
|
4333
|
+
if (!query) {
|
|
4334
|
+
return { error: "A non-empty query string is required." };
|
|
4335
|
+
}
|
|
4336
|
+
const max = Math.min(Math.max(Number(input.max_results) || 5, 1), 10);
|
|
4337
|
+
try {
|
|
4338
|
+
const results = await braveSearch(query, max);
|
|
4339
|
+
if (results.length === 0) {
|
|
4340
|
+
return { query, results: [], note: "No results found. Try rephrasing your query." };
|
|
4341
|
+
}
|
|
4342
|
+
return { query, results };
|
|
4343
|
+
} catch (err) {
|
|
4344
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4345
|
+
return {
|
|
4346
|
+
error: `Search failed: ${msg}`,
|
|
4347
|
+
hint: "The search provider may be rate-limiting requests. Try again shortly, or use browser tools as a fallback."
|
|
4348
|
+
};
|
|
4349
|
+
}
|
|
4350
|
+
}
|
|
4351
|
+
}),
|
|
4352
|
+
defineTool5({
|
|
4353
|
+
name: "web_fetch",
|
|
4354
|
+
description: "Fetch a web page and return its text content (HTML tags stripped). Useful for reading articles, documentation, or any web page without opening a browser.",
|
|
4355
|
+
inputSchema: {
|
|
4356
|
+
type: "object",
|
|
4357
|
+
properties: {
|
|
4358
|
+
url: {
|
|
4359
|
+
type: "string",
|
|
4360
|
+
description: "The URL to fetch"
|
|
4361
|
+
},
|
|
4362
|
+
max_length: {
|
|
4363
|
+
type: "number",
|
|
4364
|
+
description: `Maximum character length of returned content (default ${DEFAULT_MAX_LENGTH})`
|
|
4365
|
+
}
|
|
4366
|
+
},
|
|
4367
|
+
required: ["url"],
|
|
4368
|
+
additionalProperties: false
|
|
4369
|
+
},
|
|
4370
|
+
handler: async (input) => {
|
|
4371
|
+
const url = typeof input.url === "string" ? input.url.trim() : "";
|
|
4372
|
+
if (!url) {
|
|
4373
|
+
return { error: 'A "url" string is required.' };
|
|
4374
|
+
}
|
|
4375
|
+
const maxLength = Math.max(Number(input.max_length) || DEFAULT_MAX_LENGTH, 1e3);
|
|
4376
|
+
try {
|
|
4377
|
+
const res = await fetch(url, {
|
|
4378
|
+
headers: { "User-Agent": SEARCH_UA, Accept: "text/html,application/xhtml+xml" },
|
|
4379
|
+
redirect: "follow",
|
|
4380
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
4381
|
+
});
|
|
4382
|
+
if (!res.ok) {
|
|
4383
|
+
return { url, status: res.status, error: res.statusText };
|
|
4384
|
+
}
|
|
4385
|
+
const html = await res.text();
|
|
4386
|
+
const $ = cheerioLoad(html);
|
|
4387
|
+
const { title, content } = extractReadableText($, maxLength);
|
|
4388
|
+
return { url, status: res.status, title, content };
|
|
4389
|
+
} catch (err) {
|
|
4390
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4391
|
+
return { url, error: `Fetch failed: ${msg}` };
|
|
4392
|
+
}
|
|
4393
|
+
}
|
|
4394
|
+
})
|
|
4395
|
+
];
|
|
4396
|
+
|
|
4397
|
+
// src/subagent-tools.ts
|
|
4398
|
+
import { defineTool as defineTool6 } from "@poncho-ai/sdk";
|
|
4399
|
+
var createSubagentTools = (manager) => [
|
|
4400
|
+
defineTool6({
|
|
4278
4401
|
name: "spawn_subagent",
|
|
4279
|
-
description: "Spawn a subagent to work on a task
|
|
4402
|
+
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.",
|
|
4280
4403
|
inputSchema: {
|
|
4281
4404
|
type: "object",
|
|
4282
4405
|
properties: {
|
|
@@ -4288,26 +4411,27 @@ var createSubagentTools = (manager, getConversationId, getOwnerId) => [
|
|
|
4288
4411
|
required: ["task"],
|
|
4289
4412
|
additionalProperties: false
|
|
4290
4413
|
},
|
|
4291
|
-
handler: async (input) => {
|
|
4414
|
+
handler: async (input, context) => {
|
|
4292
4415
|
const task = typeof input.task === "string" ? input.task : "";
|
|
4293
4416
|
if (!task.trim()) {
|
|
4294
4417
|
return { error: "task is required" };
|
|
4295
4418
|
}
|
|
4296
|
-
const conversationId =
|
|
4419
|
+
const conversationId = context.conversationId;
|
|
4297
4420
|
if (!conversationId) {
|
|
4298
4421
|
return { error: "no active conversation to spawn subagent from" };
|
|
4299
4422
|
}
|
|
4300
|
-
const
|
|
4423
|
+
const ownerId = typeof context.parameters.__ownerId === "string" ? context.parameters.__ownerId : "anonymous";
|
|
4424
|
+
const { subagentId } = await manager.spawn({
|
|
4301
4425
|
task: task.trim(),
|
|
4302
4426
|
parentConversationId: conversationId,
|
|
4303
|
-
ownerId
|
|
4427
|
+
ownerId
|
|
4304
4428
|
});
|
|
4305
|
-
return
|
|
4429
|
+
return { subagentId, status: "running" };
|
|
4306
4430
|
}
|
|
4307
4431
|
}),
|
|
4308
|
-
|
|
4432
|
+
defineTool6({
|
|
4309
4433
|
name: "message_subagent",
|
|
4310
|
-
description: "Send a follow-up message to a completed or stopped subagent
|
|
4434
|
+
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.",
|
|
4311
4435
|
inputSchema: {
|
|
4312
4436
|
type: "object",
|
|
4313
4437
|
properties: {
|
|
@@ -4329,11 +4453,11 @@ var createSubagentTools = (manager, getConversationId, getOwnerId) => [
|
|
|
4329
4453
|
if (!subagentId || !message.trim()) {
|
|
4330
4454
|
return { error: "subagent_id and message are required" };
|
|
4331
4455
|
}
|
|
4332
|
-
const
|
|
4333
|
-
return
|
|
4456
|
+
const { subagentId: id } = await manager.sendMessage(subagentId, message.trim());
|
|
4457
|
+
return { subagentId: id, status: "running" };
|
|
4334
4458
|
}
|
|
4335
4459
|
}),
|
|
4336
|
-
|
|
4460
|
+
defineTool6({
|
|
4337
4461
|
name: "stop_subagent",
|
|
4338
4462
|
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.",
|
|
4339
4463
|
inputSchema: {
|
|
@@ -4356,7 +4480,7 @@ var createSubagentTools = (manager, getConversationId, getOwnerId) => [
|
|
|
4356
4480
|
return { message: `Subagent "${subagentId}" has been stopped.` };
|
|
4357
4481
|
}
|
|
4358
4482
|
}),
|
|
4359
|
-
|
|
4483
|
+
defineTool6({
|
|
4360
4484
|
name: "list_subagents",
|
|
4361
4485
|
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.",
|
|
4362
4486
|
inputSchema: {
|
|
@@ -4364,8 +4488,8 @@ var createSubagentTools = (manager, getConversationId, getOwnerId) => [
|
|
|
4364
4488
|
properties: {},
|
|
4365
4489
|
additionalProperties: false
|
|
4366
4490
|
},
|
|
4367
|
-
handler: async () => {
|
|
4368
|
-
const conversationId =
|
|
4491
|
+
handler: async (_input, context) => {
|
|
4492
|
+
const conversationId = context.conversationId;
|
|
4369
4493
|
if (!conversationId) {
|
|
4370
4494
|
return { error: "no active conversation" };
|
|
4371
4495
|
}
|
|
@@ -4961,11 +5085,7 @@ var AgentHarness = class _AgentHarness {
|
|
|
4961
5085
|
setSubagentManager(manager) {
|
|
4962
5086
|
this.subagentManager = manager;
|
|
4963
5087
|
this.dispatcher.registerMany(
|
|
4964
|
-
createSubagentTools(
|
|
4965
|
-
manager,
|
|
4966
|
-
() => this._currentRunConversationId,
|
|
4967
|
-
() => this._currentRunOwnerId ?? "anonymous"
|
|
4968
|
-
)
|
|
5088
|
+
createSubagentTools(manager)
|
|
4969
5089
|
);
|
|
4970
5090
|
}
|
|
4971
5091
|
registerConfiguredBuiltInTools(config) {
|
|
@@ -4986,6 +5106,11 @@ var AgentHarness = class _AgentHarness {
|
|
|
4986
5106
|
if (this.isToolEnabled("delete_directory")) {
|
|
4987
5107
|
this.registerIfMissing(createDeleteDirectoryTool(this.workingDir));
|
|
4988
5108
|
}
|
|
5109
|
+
for (const tool of createSearchTools()) {
|
|
5110
|
+
if (this.isToolEnabled(tool.name)) {
|
|
5111
|
+
this.registerIfMissing(tool);
|
|
5112
|
+
}
|
|
5113
|
+
}
|
|
4989
5114
|
if (this.environment === "development" && this.isToolEnabled("poncho_docs")) {
|
|
4990
5115
|
this.registerIfMissing(ponchoDocsTool);
|
|
4991
5116
|
}
|
|
@@ -5472,7 +5597,10 @@ var AgentHarness = class _AgentHarness {
|
|
|
5472
5597
|
this._browserSession = session;
|
|
5473
5598
|
const tools = browserMod.createBrowserTools(
|
|
5474
5599
|
() => session,
|
|
5475
|
-
|
|
5600
|
+
// Backward compat: older @poncho-ai/browser versions expect a second
|
|
5601
|
+
// getConversationId callback. Current versions read from ToolContext
|
|
5602
|
+
// and ignore extra args.
|
|
5603
|
+
() => "__default__"
|
|
5476
5604
|
);
|
|
5477
5605
|
for (const tool of tools) {
|
|
5478
5606
|
if (this.isToolEnabled(tool.name)) {
|
|
@@ -5480,10 +5608,6 @@ var AgentHarness = class _AgentHarness {
|
|
|
5480
5608
|
}
|
|
5481
5609
|
}
|
|
5482
5610
|
}
|
|
5483
|
-
/** Conversation ID of the currently executing run (set during run, cleared after). */
|
|
5484
|
-
_currentRunConversationId;
|
|
5485
|
-
/** Owner ID of the currently executing run (used by subagent tools). */
|
|
5486
|
-
_currentRunOwnerId;
|
|
5487
5611
|
get browserSession() {
|
|
5488
5612
|
return this._browserSession;
|
|
5489
5613
|
}
|
|
@@ -5598,11 +5722,6 @@ var AgentHarness = class _AgentHarness {
|
|
|
5598
5722
|
const memoryPromise = this.memoryStore ? this.memoryStore.getMainMemory() : void 0;
|
|
5599
5723
|
await this.refreshAgentIfChanged();
|
|
5600
5724
|
await this.refreshSkillsIfChanged();
|
|
5601
|
-
this._currentRunConversationId = input.conversationId;
|
|
5602
|
-
const ownerParam = input.parameters?.__ownerId;
|
|
5603
|
-
if (typeof ownerParam === "string") {
|
|
5604
|
-
this._currentRunOwnerId = ownerParam;
|
|
5605
|
-
}
|
|
5606
5725
|
let agent = this.parsedAgent;
|
|
5607
5726
|
const runId = `run_${randomUUID3()}`;
|
|
5608
5727
|
const start = now();
|
|
@@ -5610,7 +5729,7 @@ var AgentHarness = class _AgentHarness {
|
|
|
5610
5729
|
const configuredTimeout = agent.frontmatter.limits?.timeout;
|
|
5611
5730
|
const timeoutMs = this.environment === "development" && configuredTimeout == null ? 0 : (configuredTimeout ?? 300) * 1e3;
|
|
5612
5731
|
const platformMaxDurationSec = Number(process.env.PONCHO_MAX_DURATION) || 0;
|
|
5613
|
-
const softDeadlineMs = platformMaxDurationSec
|
|
5732
|
+
const softDeadlineMs = input.disableSoftDeadline || platformMaxDurationSec <= 0 ? 0 : platformMaxDurationSec * 800;
|
|
5614
5733
|
const messages = [...input.messages ?? []];
|
|
5615
5734
|
const inputMessageCount = messages.length;
|
|
5616
5735
|
const events = [];
|
|
@@ -5744,6 +5863,15 @@ ${this.skillFingerprint}`;
|
|
|
5744
5863
|
metadata: { timestamp: now(), id: randomUUID3() }
|
|
5745
5864
|
});
|
|
5746
5865
|
}
|
|
5866
|
+
} else {
|
|
5867
|
+
const lastMsg = messages[messages.length - 1];
|
|
5868
|
+
if (lastMsg && lastMsg.role !== "user") {
|
|
5869
|
+
messages.push({
|
|
5870
|
+
role: "user",
|
|
5871
|
+
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.]",
|
|
5872
|
+
metadata: { timestamp: now(), id: randomUUID3() }
|
|
5873
|
+
});
|
|
5874
|
+
}
|
|
5747
5875
|
}
|
|
5748
5876
|
let responseText = "";
|
|
5749
5877
|
let totalInputTokens = 0;
|
|
@@ -5778,6 +5906,7 @@ ${this.skillFingerprint}`;
|
|
|
5778
5906
|
tokens: { input: totalInputTokens, output: totalOutputTokens, cached: totalCachedTokens },
|
|
5779
5907
|
duration: now() - start,
|
|
5780
5908
|
continuation: true,
|
|
5909
|
+
continuationMessages: [...messages],
|
|
5781
5910
|
maxSteps
|
|
5782
5911
|
};
|
|
5783
5912
|
yield pushEvent({ type: "run:completed", runId, result: result2 });
|
|
@@ -5811,7 +5940,7 @@ ${this.skillFingerprint}`;
|
|
|
5811
5940
|
if (rich && rich.length > 0) {
|
|
5812
5941
|
return [{ role: "tool", content: rich }];
|
|
5813
5942
|
}
|
|
5814
|
-
const textContent = typeof msg.content === "string" ? msg.content :
|
|
5943
|
+
const textContent = typeof msg.content === "string" ? msg.content : getTextContent2(msg);
|
|
5815
5944
|
try {
|
|
5816
5945
|
const parsed = JSON.parse(textContent);
|
|
5817
5946
|
if (!Array.isArray(parsed)) {
|
|
@@ -5861,7 +5990,7 @@ ${this.skillFingerprint}`;
|
|
|
5861
5990
|
}
|
|
5862
5991
|
}
|
|
5863
5992
|
if (msg.role === "assistant") {
|
|
5864
|
-
const assistantText = typeof msg.content === "string" ? msg.content :
|
|
5993
|
+
const assistantText = typeof msg.content === "string" ? msg.content : getTextContent2(msg);
|
|
5865
5994
|
try {
|
|
5866
5995
|
const parsed = JSON.parse(assistantText);
|
|
5867
5996
|
if (typeof parsed === "object" && parsed !== null) {
|
|
@@ -5895,12 +6024,15 @@ ${this.skillFingerprint}`;
|
|
|
5895
6024
|
}
|
|
5896
6025
|
} catch {
|
|
5897
6026
|
}
|
|
6027
|
+
if (!assistantText || assistantText.trim().length === 0) {
|
|
6028
|
+
return [];
|
|
6029
|
+
}
|
|
5898
6030
|
return [{ role: "assistant", content: assistantText }];
|
|
5899
6031
|
}
|
|
5900
6032
|
if (msg.role === "system") {
|
|
5901
6033
|
return [{
|
|
5902
6034
|
role: "system",
|
|
5903
|
-
content: typeof msg.content === "string" ? msg.content :
|
|
6035
|
+
content: typeof msg.content === "string" ? msg.content : getTextContent2(msg)
|
|
5904
6036
|
}];
|
|
5905
6037
|
}
|
|
5906
6038
|
if (msg.role === "user") {
|
|
@@ -6454,6 +6586,7 @@ ${this.skillFingerprint}`;
|
|
|
6454
6586
|
tokens: { input: totalInputTokens, output: totalOutputTokens, cached: totalCachedTokens },
|
|
6455
6587
|
duration: now() - start,
|
|
6456
6588
|
continuation: true,
|
|
6589
|
+
continuationMessages: [...messages],
|
|
6457
6590
|
maxSteps
|
|
6458
6591
|
};
|
|
6459
6592
|
yield pushEvent({ type: "run:completed", runId, result });
|
|
@@ -6775,6 +6908,13 @@ var InMemoryConversationStore = class {
|
|
|
6775
6908
|
async delete(conversationId) {
|
|
6776
6909
|
return this.conversations.delete(conversationId);
|
|
6777
6910
|
}
|
|
6911
|
+
async appendSubagentResult(conversationId, result) {
|
|
6912
|
+
const conversation = this.conversations.get(conversationId);
|
|
6913
|
+
if (!conversation) return;
|
|
6914
|
+
if (!conversation.pendingSubagentResults) conversation.pendingSubagentResults = [];
|
|
6915
|
+
conversation.pendingSubagentResults.push(result);
|
|
6916
|
+
conversation.updatedAt = Date.now();
|
|
6917
|
+
}
|
|
6778
6918
|
};
|
|
6779
6919
|
var FileConversationStore = class {
|
|
6780
6920
|
workingDir;
|
|
@@ -6988,6 +7128,15 @@ var FileConversationStore = class {
|
|
|
6988
7128
|
}
|
|
6989
7129
|
return removed;
|
|
6990
7130
|
}
|
|
7131
|
+
async appendSubagentResult(conversationId, result) {
|
|
7132
|
+
await this.ensureLoaded();
|
|
7133
|
+
const conversation = await this.get(conversationId);
|
|
7134
|
+
if (!conversation) return;
|
|
7135
|
+
if (!conversation.pendingSubagentResults) conversation.pendingSubagentResults = [];
|
|
7136
|
+
conversation.pendingSubagentResults.push(result);
|
|
7137
|
+
conversation.updatedAt = Date.now();
|
|
7138
|
+
await this.update(conversation);
|
|
7139
|
+
}
|
|
6991
7140
|
};
|
|
6992
7141
|
var FileStateStore = class {
|
|
6993
7142
|
workingDir;
|
|
@@ -7067,6 +7216,7 @@ var KeyValueConversationStoreBase = class {
|
|
|
7067
7216
|
ttl;
|
|
7068
7217
|
agentIdPromise;
|
|
7069
7218
|
ownerLocks = /* @__PURE__ */ new Map();
|
|
7219
|
+
appendLocks = /* @__PURE__ */ new Map();
|
|
7070
7220
|
memoryFallback;
|
|
7071
7221
|
constructor(ttl, workingDir, agentId) {
|
|
7072
7222
|
this.ttl = ttl;
|
|
@@ -7085,6 +7235,18 @@ var KeyValueConversationStoreBase = class {
|
|
|
7085
7235
|
}
|
|
7086
7236
|
}
|
|
7087
7237
|
}
|
|
7238
|
+
async withAppendLock(conversationId, task) {
|
|
7239
|
+
const prev = this.appendLocks.get(conversationId) ?? Promise.resolve();
|
|
7240
|
+
const next = prev.then(task, task);
|
|
7241
|
+
this.appendLocks.set(conversationId, next);
|
|
7242
|
+
try {
|
|
7243
|
+
await next;
|
|
7244
|
+
} finally {
|
|
7245
|
+
if (this.appendLocks.get(conversationId) === next) {
|
|
7246
|
+
this.appendLocks.delete(conversationId);
|
|
7247
|
+
}
|
|
7248
|
+
}
|
|
7249
|
+
}
|
|
7088
7250
|
async namespace() {
|
|
7089
7251
|
const agentId = await this.agentIdPromise;
|
|
7090
7252
|
return `poncho:${STORAGE_SCHEMA_VERSION}:${slugifyStorageComponent(agentId)}`;
|
|
@@ -7305,6 +7467,16 @@ var KeyValueConversationStoreBase = class {
|
|
|
7305
7467
|
});
|
|
7306
7468
|
return true;
|
|
7307
7469
|
}
|
|
7470
|
+
async appendSubagentResult(conversationId, result) {
|
|
7471
|
+
await this.withAppendLock(conversationId, async () => {
|
|
7472
|
+
const conversation = await this.get(conversationId);
|
|
7473
|
+
if (!conversation) return;
|
|
7474
|
+
if (!conversation.pendingSubagentResults) conversation.pendingSubagentResults = [];
|
|
7475
|
+
conversation.pendingSubagentResults.push(result);
|
|
7476
|
+
conversation.updatedAt = Date.now();
|
|
7477
|
+
await this.update(conversation);
|
|
7478
|
+
});
|
|
7479
|
+
}
|
|
7308
7480
|
};
|
|
7309
7481
|
var UpstashConversationStore = class extends KeyValueConversationStoreBase {
|
|
7310
7482
|
baseUrl;
|
|
@@ -7347,20 +7519,26 @@ var UpstashConversationStore = class extends KeyValueConversationStoreBase {
|
|
|
7347
7519
|
return (payload.result ?? []).map((v) => v ?? void 0);
|
|
7348
7520
|
},
|
|
7349
7521
|
set: async (key, value, ttl) => {
|
|
7350
|
-
const
|
|
7351
|
-
|
|
7352
|
-
ttl
|
|
7353
|
-
)}/${encodeURIComponent(value)}` : `${this.baseUrl}/set/${encodeURIComponent(key)}/${encodeURIComponent(value)}`;
|
|
7354
|
-
await fetch(endpoint, {
|
|
7522
|
+
const command = typeof ttl === "number" ? ["SETEX", key, Math.max(1, ttl), value] : ["SET", key, value];
|
|
7523
|
+
const response = await fetch(this.baseUrl, {
|
|
7355
7524
|
method: "POST",
|
|
7356
|
-
headers: this.headers()
|
|
7525
|
+
headers: this.headers(),
|
|
7526
|
+
body: JSON.stringify(command)
|
|
7357
7527
|
});
|
|
7528
|
+
if (!response.ok) {
|
|
7529
|
+
const text = await response.text().catch(() => "");
|
|
7530
|
+
console.error(`[store][upstash] SET failed (${response.status}): ${text.slice(0, 200)}`);
|
|
7531
|
+
}
|
|
7358
7532
|
},
|
|
7359
7533
|
del: async (key) => {
|
|
7360
|
-
await fetch(`${this.baseUrl}/del/${encodeURIComponent(key)}`, {
|
|
7534
|
+
const response = await fetch(`${this.baseUrl}/del/${encodeURIComponent(key)}`, {
|
|
7361
7535
|
method: "POST",
|
|
7362
7536
|
headers: this.headers()
|
|
7363
7537
|
});
|
|
7538
|
+
if (!response.ok) {
|
|
7539
|
+
const text = await response.text().catch(() => "");
|
|
7540
|
+
console.error(`[store][upstash] DEL failed (${response.status}): ${text.slice(0, 200)}`);
|
|
7541
|
+
}
|
|
7364
7542
|
}
|
|
7365
7543
|
};
|
|
7366
7544
|
}
|
|
@@ -7749,7 +7927,7 @@ var TelemetryEmitter = class {
|
|
|
7749
7927
|
};
|
|
7750
7928
|
|
|
7751
7929
|
// src/index.ts
|
|
7752
|
-
import { defineTool as
|
|
7930
|
+
import { defineTool as defineTool7 } from "@poncho-ai/sdk";
|
|
7753
7931
|
export {
|
|
7754
7932
|
AgentHarness,
|
|
7755
7933
|
InMemoryConversationStore,
|
|
@@ -7774,12 +7952,13 @@ export {
|
|
|
7774
7952
|
createMemoryStore,
|
|
7775
7953
|
createMemoryTools,
|
|
7776
7954
|
createModelProvider,
|
|
7955
|
+
createSearchTools,
|
|
7777
7956
|
createSkillTools,
|
|
7778
7957
|
createStateStore,
|
|
7779
7958
|
createSubagentTools,
|
|
7780
7959
|
createUploadStore,
|
|
7781
7960
|
createWriteTool,
|
|
7782
|
-
|
|
7961
|
+
defineTool7 as defineTool,
|
|
7783
7962
|
deriveUploadKey,
|
|
7784
7963
|
ensureAgentIdentity,
|
|
7785
7964
|
estimateTokens,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@poncho-ai/harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"description": "Agent execution runtime - conversation loop, tool dispatch, streaming",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -26,12 +26,13 @@
|
|
|
26
26
|
"@latitude-data/telemetry": "^2.0.4",
|
|
27
27
|
"@opentelemetry/api": "1.9.0",
|
|
28
28
|
"ai": "^6.0.86",
|
|
29
|
+
"cheerio": "^1.2.0",
|
|
29
30
|
"jiti": "^2.6.1",
|
|
30
31
|
"mustache": "^4.2.0",
|
|
31
32
|
"redis": "^5.10.0",
|
|
32
33
|
"yaml": "^2.4.0",
|
|
33
34
|
"zod": "^3.22.0",
|
|
34
|
-
"@poncho-ai/sdk": "1.
|
|
35
|
+
"@poncho-ai/sdk": "1.6.0"
|
|
35
36
|
},
|
|
36
37
|
"devDependencies": {
|
|
37
38
|
"@types/mustache": "^4.2.6",
|