@poncho-ai/harness 0.27.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 +5 -5
- package/CHANGELOG.md +6 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +154 -8
- package/package.json +2 -1
- package/src/config.ts +2 -0
- package/src/harness.ts +12 -2
- package/src/index.ts +1 -0
- package/src/search-tools.ts +181 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @poncho-ai/harness@0.
|
|
2
|
+
> @poncho-ai/harness@0.28.0 build /home/runner/work/poncho-ai/poncho-ai/packages/harness
|
|
3
3
|
> node scripts/embed-docs.js && tsup src/index.ts --format esm --dts
|
|
4
4
|
|
|
5
5
|
[embed-docs] Generated poncho-docs.ts with 4 topics
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
[34mCLI[39m tsup v8.5.1
|
|
9
9
|
[34mCLI[39m Target: es2022
|
|
10
10
|
[34mESM[39m Build start
|
|
11
|
-
[32mESM[39m [1mdist/index.js [22m[
|
|
12
|
-
[32mESM[39m ⚡️ Build success in
|
|
11
|
+
[32mESM[39m [1mdist/index.js [22m[32m284.05 KB[39m
|
|
12
|
+
[32mESM[39m ⚡️ Build success in 147ms
|
|
13
13
|
[34mDTS[39m Build start
|
|
14
|
-
[32mDTS[39m ⚡️ Build success in
|
|
15
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[32m29.
|
|
14
|
+
[32mDTS[39m ⚡️ Build success in 7926ms
|
|
15
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m29.26 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @poncho-ai/harness
|
|
2
2
|
|
|
3
|
+
## 0.28.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`c0ca56b`](https://github.com/cesr/poncho-ai/commit/c0ca56b54bb877d96ba8088537d6f1c7461d2a55) Thanks [@cesr](https://github.com/cesr)! - Add built-in `web_search` and `web_fetch` tools so agents can search the web and fetch page content without a browser or API keys. Remove the scaffolded `fetch-page` skill (superseded by `web_fetch`). Fix `browser_open` crash when agent projects have an older `@poncho-ai/browser` installed.
|
|
8
|
+
|
|
3
9
|
## 0.27.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -351,6 +351,8 @@ type BuiltInToolToggles = {
|
|
|
351
351
|
todo_add?: boolean;
|
|
352
352
|
todo_update?: boolean;
|
|
353
353
|
todo_remove?: boolean;
|
|
354
|
+
web_search?: boolean;
|
|
355
|
+
web_fetch?: boolean;
|
|
354
356
|
};
|
|
355
357
|
interface MessagingChannelConfig {
|
|
356
358
|
platform: "slack" | "resend" | "telegram";
|
|
@@ -738,6 +740,8 @@ declare class LatitudeCapture {
|
|
|
738
740
|
*/
|
|
739
741
|
declare function jsonSchemaToZod(schema: JsonSchema): z.ZodType;
|
|
740
742
|
|
|
743
|
+
declare const createSearchTools: () => ToolDefinition[];
|
|
744
|
+
|
|
741
745
|
/**
|
|
742
746
|
* Resolve the full list of skill directories to scan.
|
|
743
747
|
* Merges the defaults with any extra paths provided via config.
|
|
@@ -813,4 +817,4 @@ declare class TelemetryEmitter {
|
|
|
813
817
|
|
|
814
818
|
declare const createSubagentTools: (manager: SubagentManager) => ToolDefinition[];
|
|
815
819
|
|
|
816
|
-
export { type AgentFrontmatter, AgentHarness, type AgentIdentity, type AgentLimitsConfig, type AgentModelConfig, type BuiltInToolToggles, type CompactMessagesOptions, type CompactResult, type CompactionConfig, type Conversation, type ConversationState, type ConversationStore, type ConversationSummary, type CronJobConfig, type HarnessOptions, type HarnessRunOutput, InMemoryConversationStore, InMemoryStateStore, LatitudeCapture, type LatitudeCaptureConfig, LocalMcpBridge, LocalUploadStore, type MainMemory, type McpConfig, type MemoryConfig, type MemoryStore, type MessagingChannelConfig, type ModelProviderFactory, PONCHO_UPLOAD_SCHEME, type ParsedAgent, type PendingSubagentResult, type PonchoConfig, type ProviderConfig, type RemoteMcpServerConfig, type RuntimeRenderContext, S3UploadStore, STORAGE_SCHEMA_VERSION, type SkillContextEntry, type SkillMetadata, type StateConfig, type StateProviderName, type StateStore, type StorageConfig, type SubagentManager, type SubagentResult, type SubagentSpawnResult, type SubagentSummary, type TelemetryConfig, TelemetryEmitter, type ToolAccess, type ToolCall, ToolDispatcher, type ToolExecutionResult, type UploadStore, type UploadsConfig, VercelBlobUploadStore, buildAgentDirectoryName, buildSkillContextWindow, compactMessages, createConversationStore, createDefaultTools, createDeleteDirectoryTool, createDeleteTool, createEditTool, createMemoryStore, createMemoryTools, createModelProvider, createSkillTools, createStateStore, createSubagentTools, createUploadStore, createWriteTool, deriveUploadKey, ensureAgentIdentity, estimateTokens, estimateTotalTokens, findSafeSplitPoint, generateAgentId, getAgentStoreDirectory, getModelContextWindow, getPonchoStoreRoot, jsonSchemaToZod, loadPonchoConfig, loadSkillContext, loadSkillInstructions, loadSkillMetadata, normalizeScriptPolicyPath, parseAgentFile, parseAgentMarkdown, ponchoDocsTool, readSkillResource, renderAgentPrompt, resolveAgentIdentity, resolveCompactionConfig, resolveMemoryConfig, resolveSkillDirs, resolveStateConfig, slugifyStorageComponent };
|
|
820
|
+
export { type AgentFrontmatter, AgentHarness, type AgentIdentity, type AgentLimitsConfig, type AgentModelConfig, type BuiltInToolToggles, type CompactMessagesOptions, type CompactResult, type CompactionConfig, type Conversation, type ConversationState, type ConversationStore, type ConversationSummary, type CronJobConfig, type HarnessOptions, type HarnessRunOutput, InMemoryConversationStore, InMemoryStateStore, LatitudeCapture, type LatitudeCaptureConfig, LocalMcpBridge, LocalUploadStore, type MainMemory, type McpConfig, type MemoryConfig, type MemoryStore, type MessagingChannelConfig, type ModelProviderFactory, PONCHO_UPLOAD_SCHEME, type ParsedAgent, type PendingSubagentResult, type PonchoConfig, type ProviderConfig, type RemoteMcpServerConfig, type RuntimeRenderContext, S3UploadStore, STORAGE_SCHEMA_VERSION, type SkillContextEntry, type SkillMetadata, type StateConfig, type StateProviderName, type StateStore, type StorageConfig, type SubagentManager, type SubagentResult, type SubagentSpawnResult, type SubagentSummary, type TelemetryConfig, TelemetryEmitter, type ToolAccess, type ToolCall, ToolDispatcher, type ToolExecutionResult, type UploadStore, type UploadsConfig, VercelBlobUploadStore, buildAgentDirectoryName, buildSkillContextWindow, compactMessages, createConversationStore, createDefaultTools, createDeleteDirectoryTool, createDeleteTool, createEditTool, createMemoryStore, createMemoryTools, createModelProvider, createSearchTools, createSkillTools, createStateStore, createSubagentTools, createUploadStore, createWriteTool, deriveUploadKey, ensureAgentIdentity, estimateTokens, estimateTotalTokens, findSafeSplitPoint, generateAgentId, getAgentStoreDirectory, getModelContextWindow, getPonchoStoreRoot, jsonSchemaToZod, loadPonchoConfig, loadSkillContext, loadSkillInstructions, loadSkillMetadata, normalizeScriptPolicyPath, parseAgentFile, parseAgentMarkdown, ponchoDocsTool, readSkillResource, renderAgentPrompt, resolveAgentIdentity, resolveCompactionConfig, resolveMemoryConfig, resolveSkillDirs, resolveStateConfig, slugifyStorageComponent };
|
package/dist/index.js
CHANGED
|
@@ -4258,10 +4258,146 @@ var extractRunnableFunction = (value) => {
|
|
|
4258
4258
|
return void 0;
|
|
4259
4259
|
};
|
|
4260
4260
|
|
|
4261
|
-
// src/
|
|
4261
|
+
// src/search-tools.ts
|
|
4262
|
+
import { load as cheerioLoad } from "cheerio";
|
|
4262
4263
|
import { defineTool as defineTool5 } from "@poncho-ai/sdk";
|
|
4263
|
-
var
|
|
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})`);
|
|
4278
|
+
}
|
|
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 = () => [
|
|
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
|
+
}),
|
|
4264
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({
|
|
4265
4401
|
name: "spawn_subagent",
|
|
4266
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.",
|
|
4267
4403
|
inputSchema: {
|
|
@@ -4293,7 +4429,7 @@ var createSubagentTools = (manager) => [
|
|
|
4293
4429
|
return { subagentId, status: "running" };
|
|
4294
4430
|
}
|
|
4295
4431
|
}),
|
|
4296
|
-
|
|
4432
|
+
defineTool6({
|
|
4297
4433
|
name: "message_subagent",
|
|
4298
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.",
|
|
4299
4435
|
inputSchema: {
|
|
@@ -4321,7 +4457,7 @@ var createSubagentTools = (manager) => [
|
|
|
4321
4457
|
return { subagentId: id, status: "running" };
|
|
4322
4458
|
}
|
|
4323
4459
|
}),
|
|
4324
|
-
|
|
4460
|
+
defineTool6({
|
|
4325
4461
|
name: "stop_subagent",
|
|
4326
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.",
|
|
4327
4463
|
inputSchema: {
|
|
@@ -4344,7 +4480,7 @@ var createSubagentTools = (manager) => [
|
|
|
4344
4480
|
return { message: `Subagent "${subagentId}" has been stopped.` };
|
|
4345
4481
|
}
|
|
4346
4482
|
}),
|
|
4347
|
-
|
|
4483
|
+
defineTool6({
|
|
4348
4484
|
name: "list_subagents",
|
|
4349
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.",
|
|
4350
4486
|
inputSchema: {
|
|
@@ -4970,6 +5106,11 @@ var AgentHarness = class _AgentHarness {
|
|
|
4970
5106
|
if (this.isToolEnabled("delete_directory")) {
|
|
4971
5107
|
this.registerIfMissing(createDeleteDirectoryTool(this.workingDir));
|
|
4972
5108
|
}
|
|
5109
|
+
for (const tool of createSearchTools()) {
|
|
5110
|
+
if (this.isToolEnabled(tool.name)) {
|
|
5111
|
+
this.registerIfMissing(tool);
|
|
5112
|
+
}
|
|
5113
|
+
}
|
|
4973
5114
|
if (this.environment === "development" && this.isToolEnabled("poncho_docs")) {
|
|
4974
5115
|
this.registerIfMissing(ponchoDocsTool);
|
|
4975
5116
|
}
|
|
@@ -5455,7 +5596,11 @@ var AgentHarness = class _AgentHarness {
|
|
|
5455
5596
|
const session = new browserMod.BrowserSession(sessionId, browserCfg);
|
|
5456
5597
|
this._browserSession = session;
|
|
5457
5598
|
const tools = browserMod.createBrowserTools(
|
|
5458
|
-
() => session
|
|
5599
|
+
() => session,
|
|
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__"
|
|
5459
5604
|
);
|
|
5460
5605
|
for (const tool of tools) {
|
|
5461
5606
|
if (this.isToolEnabled(tool.name)) {
|
|
@@ -7782,7 +7927,7 @@ var TelemetryEmitter = class {
|
|
|
7782
7927
|
};
|
|
7783
7928
|
|
|
7784
7929
|
// src/index.ts
|
|
7785
|
-
import { defineTool as
|
|
7930
|
+
import { defineTool as defineTool7 } from "@poncho-ai/sdk";
|
|
7786
7931
|
export {
|
|
7787
7932
|
AgentHarness,
|
|
7788
7933
|
InMemoryConversationStore,
|
|
@@ -7807,12 +7952,13 @@ export {
|
|
|
7807
7952
|
createMemoryStore,
|
|
7808
7953
|
createMemoryTools,
|
|
7809
7954
|
createModelProvider,
|
|
7955
|
+
createSearchTools,
|
|
7810
7956
|
createSkillTools,
|
|
7811
7957
|
createStateStore,
|
|
7812
7958
|
createSubagentTools,
|
|
7813
7959
|
createUploadStore,
|
|
7814
7960
|
createWriteTool,
|
|
7815
|
-
|
|
7961
|
+
defineTool7 as defineTool,
|
|
7816
7962
|
deriveUploadKey,
|
|
7817
7963
|
ensureAgentIdentity,
|
|
7818
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,6 +26,7 @@
|
|
|
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",
|
package/src/config.ts
CHANGED
package/src/harness.ts
CHANGED
|
@@ -32,6 +32,7 @@ import { addPromptCacheBreakpoints } from "./prompt-cache.js";
|
|
|
32
32
|
import { jsonSchemaToZod } from "./schema-converter.js";
|
|
33
33
|
import type { SkillMetadata } from "./skill-context.js";
|
|
34
34
|
import { createSkillTools, normalizeScriptPolicyPath } from "./skill-tools.js";
|
|
35
|
+
import { createSearchTools } from "./search-tools.js";
|
|
35
36
|
import { createSubagentTools } from "./subagent-tools.js";
|
|
36
37
|
import type { SubagentManager } from "./subagent-manager.js";
|
|
37
38
|
import { LatitudeTelemetry } from "@latitude-data/telemetry";
|
|
@@ -562,7 +563,7 @@ export class AgentHarness {
|
|
|
562
563
|
private insideTelemetryCapture = false;
|
|
563
564
|
private _browserSession?: unknown;
|
|
564
565
|
private _browserMod?: {
|
|
565
|
-
createBrowserTools: (getSession: () => unknown) => ToolDefinition[];
|
|
566
|
+
createBrowserTools: (getSession: () => unknown, getConversationId?: () => string) => ToolDefinition[];
|
|
566
567
|
BrowserSession: new (sessionId: string, config: Record<string, unknown>) => unknown;
|
|
567
568
|
};
|
|
568
569
|
|
|
@@ -644,6 +645,11 @@ export class AgentHarness {
|
|
|
644
645
|
if (this.isToolEnabled("delete_directory")) {
|
|
645
646
|
this.registerIfMissing(createDeleteDirectoryTool(this.workingDir));
|
|
646
647
|
}
|
|
648
|
+
for (const tool of createSearchTools()) {
|
|
649
|
+
if (this.isToolEnabled(tool.name)) {
|
|
650
|
+
this.registerIfMissing(tool);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
647
653
|
if (this.environment === "development" && this.isToolEnabled("poncho_docs")) {
|
|
648
654
|
this.registerIfMissing(ponchoDocsTool);
|
|
649
655
|
}
|
|
@@ -1161,7 +1167,7 @@ export class AgentHarness {
|
|
|
1161
1167
|
private async initBrowserTools(config: PonchoConfig): Promise<void> {
|
|
1162
1168
|
const spec = ["@poncho-ai", "browser"].join("/");
|
|
1163
1169
|
let browserMod: {
|
|
1164
|
-
createBrowserTools: (getSession: () => unknown) => ToolDefinition[];
|
|
1170
|
+
createBrowserTools: (getSession: () => unknown, getConversationId?: () => string) => ToolDefinition[];
|
|
1165
1171
|
BrowserSession: new (sessionId: string, cfg?: Record<string, unknown>) => unknown;
|
|
1166
1172
|
};
|
|
1167
1173
|
try {
|
|
@@ -1205,6 +1211,10 @@ export class AgentHarness {
|
|
|
1205
1211
|
|
|
1206
1212
|
const tools = browserMod.createBrowserTools(
|
|
1207
1213
|
() => session,
|
|
1214
|
+
// Backward compat: older @poncho-ai/browser versions expect a second
|
|
1215
|
+
// getConversationId callback. Current versions read from ToolContext
|
|
1216
|
+
// and ignore extra args.
|
|
1217
|
+
() => "__default__",
|
|
1208
1218
|
);
|
|
1209
1219
|
for (const tool of tools) {
|
|
1210
1220
|
if (this.isToolEnabled(tool.name)) {
|
package/src/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ export * from "./memory.js";
|
|
|
9
9
|
export * from "./mcp.js";
|
|
10
10
|
export * from "./model-factory.js";
|
|
11
11
|
export * from "./schema-converter.js";
|
|
12
|
+
export * from "./search-tools.js";
|
|
12
13
|
export * from "./skill-context.js";
|
|
13
14
|
export * from "./skill-tools.js";
|
|
14
15
|
export * from "./state.js";
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { load as cheerioLoad, type CheerioAPI } from "cheerio";
|
|
2
|
+
import { defineTool, type ToolDefinition } from "@poncho-ai/sdk";
|
|
3
|
+
|
|
4
|
+
const SEARCH_UA =
|
|
5
|
+
"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";
|
|
6
|
+
|
|
7
|
+
const FETCH_TIMEOUT_MS = 15_000;
|
|
8
|
+
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// web_search — Brave Search HTML scraping (no API key)
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
interface SearchResult {
|
|
14
|
+
title: string;
|
|
15
|
+
url: string;
|
|
16
|
+
snippet: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function braveSearch(query: string, maxResults: number): Promise<SearchResult[]> {
|
|
20
|
+
const url = `https://search.brave.com/search?q=${encodeURIComponent(query)}`;
|
|
21
|
+
const res = await fetch(url, {
|
|
22
|
+
headers: {
|
|
23
|
+
"User-Agent": SEARCH_UA,
|
|
24
|
+
Accept: "text/html,application/xhtml+xml",
|
|
25
|
+
"Accept-Language": "en-US,en;q=0.9",
|
|
26
|
+
},
|
|
27
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
28
|
+
});
|
|
29
|
+
if (!res.ok) {
|
|
30
|
+
throw new Error(`Search request failed (${res.status} ${res.statusText})`);
|
|
31
|
+
}
|
|
32
|
+
const html = await res.text();
|
|
33
|
+
return parseBraveResults(html, maxResults);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function parseBraveResults(html: string, max: number): SearchResult[] {
|
|
37
|
+
const $ = cheerioLoad(html);
|
|
38
|
+
const results: SearchResult[] = [];
|
|
39
|
+
|
|
40
|
+
$('div.snippet[data-type="web"]').each((_i, el) => {
|
|
41
|
+
if (results.length >= max) return false;
|
|
42
|
+
|
|
43
|
+
const $el = $(el);
|
|
44
|
+
const anchor = $el.find(".result-content a").first();
|
|
45
|
+
const href = anchor.attr("href") ?? "";
|
|
46
|
+
if (!href.startsWith("http")) return;
|
|
47
|
+
|
|
48
|
+
const title = $el.find(".title").first().text().trim();
|
|
49
|
+
const snippet = $el.find(".generic-snippet .content").first().text().trim();
|
|
50
|
+
|
|
51
|
+
if (title) {
|
|
52
|
+
results.push({ title, url: href, snippet });
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return results;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// web_fetch — fetch a URL and extract readable text via cheerio
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
const DEFAULT_MAX_LENGTH = 16_000;
|
|
64
|
+
|
|
65
|
+
function extractReadableText($: CheerioAPI, maxLength: number): { title: string; content: string } {
|
|
66
|
+
const title = $("title").first().text().trim();
|
|
67
|
+
|
|
68
|
+
$("script, style, noscript, nav, footer, header, aside, [role='navigation'], [role='banner'], [role='contentinfo']").remove();
|
|
69
|
+
$("svg, iframe, form, button, input, select, textarea").remove();
|
|
70
|
+
|
|
71
|
+
let root = $("article").first();
|
|
72
|
+
if (!root.length) root = $("main").first();
|
|
73
|
+
if (!root.length) root = $("[role='main']").first();
|
|
74
|
+
if (!root.length) root = $("body").first();
|
|
75
|
+
|
|
76
|
+
const text = root
|
|
77
|
+
.text()
|
|
78
|
+
.replace(/[ \t]+/g, " ")
|
|
79
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
80
|
+
.trim();
|
|
81
|
+
|
|
82
|
+
const content =
|
|
83
|
+
text.length > maxLength ? text.slice(0, maxLength) + "\n…(truncated)" : text;
|
|
84
|
+
|
|
85
|
+
return { title, content };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// Tool definitions
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
export const createSearchTools = (): ToolDefinition[] => [
|
|
93
|
+
defineTool({
|
|
94
|
+
name: "web_search",
|
|
95
|
+
description:
|
|
96
|
+
"Search the web and return a list of results (title, URL, snippet). " +
|
|
97
|
+
"Use this instead of opening a browser when you need to find information online.",
|
|
98
|
+
inputSchema: {
|
|
99
|
+
type: "object",
|
|
100
|
+
properties: {
|
|
101
|
+
query: {
|
|
102
|
+
type: "string",
|
|
103
|
+
description: "The search query",
|
|
104
|
+
},
|
|
105
|
+
max_results: {
|
|
106
|
+
type: "number",
|
|
107
|
+
description: "Maximum number of results to return (1-10, default 5)",
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
required: ["query"],
|
|
111
|
+
additionalProperties: false,
|
|
112
|
+
},
|
|
113
|
+
handler: async (input) => {
|
|
114
|
+
const query = typeof input.query === "string" ? input.query.trim() : "";
|
|
115
|
+
if (!query) {
|
|
116
|
+
return { error: "A non-empty query string is required." };
|
|
117
|
+
}
|
|
118
|
+
const max = Math.min(Math.max(Number(input.max_results) || 5, 1), 10);
|
|
119
|
+
try {
|
|
120
|
+
const results = await braveSearch(query, max);
|
|
121
|
+
if (results.length === 0) {
|
|
122
|
+
return { query, results: [], note: "No results found. Try rephrasing your query." };
|
|
123
|
+
}
|
|
124
|
+
return { query, results };
|
|
125
|
+
} catch (err) {
|
|
126
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
127
|
+
return {
|
|
128
|
+
error: `Search failed: ${msg}`,
|
|
129
|
+
hint: "The search provider may be rate-limiting requests. Try again shortly, or use browser tools as a fallback.",
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
}),
|
|
134
|
+
|
|
135
|
+
defineTool({
|
|
136
|
+
name: "web_fetch",
|
|
137
|
+
description:
|
|
138
|
+
"Fetch a web page and return its text content (HTML tags stripped). " +
|
|
139
|
+
"Useful for reading articles, documentation, or any web page without opening a browser.",
|
|
140
|
+
inputSchema: {
|
|
141
|
+
type: "object",
|
|
142
|
+
properties: {
|
|
143
|
+
url: {
|
|
144
|
+
type: "string",
|
|
145
|
+
description: "The URL to fetch",
|
|
146
|
+
},
|
|
147
|
+
max_length: {
|
|
148
|
+
type: "number",
|
|
149
|
+
description: `Maximum character length of returned content (default ${DEFAULT_MAX_LENGTH})`,
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
required: ["url"],
|
|
153
|
+
additionalProperties: false,
|
|
154
|
+
},
|
|
155
|
+
handler: async (input) => {
|
|
156
|
+
const url = typeof input.url === "string" ? input.url.trim() : "";
|
|
157
|
+
if (!url) {
|
|
158
|
+
return { error: 'A "url" string is required.' };
|
|
159
|
+
}
|
|
160
|
+
const maxLength = Math.max(Number(input.max_length) || DEFAULT_MAX_LENGTH, 1_000);
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
const res = await fetch(url, {
|
|
164
|
+
headers: { "User-Agent": SEARCH_UA, Accept: "text/html,application/xhtml+xml" },
|
|
165
|
+
redirect: "follow",
|
|
166
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
167
|
+
});
|
|
168
|
+
if (!res.ok) {
|
|
169
|
+
return { url, status: res.status, error: res.statusText };
|
|
170
|
+
}
|
|
171
|
+
const html = await res.text();
|
|
172
|
+
const $ = cheerioLoad(html);
|
|
173
|
+
const { title, content } = extractReadableText($, maxLength);
|
|
174
|
+
return { url, status: res.status, title, content };
|
|
175
|
+
} catch (err) {
|
|
176
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
177
|
+
return { url, error: `Fetch failed: ${msg}` };
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
}),
|
|
181
|
+
];
|