@poncho-ai/harness 0.15.1 → 0.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +5 -5
- package/CHANGELOG.md +12 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +107 -3
- package/package.json +1 -1
- package/src/harness.ts +119 -4
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @poncho-ai/harness@0.
|
|
2
|
+
> @poncho-ai/harness@0.16.1 build /Users/cesar/Dev/latitude/poncho-ai/packages/harness
|
|
3
3
|
> tsup src/index.ts --format esm --dts
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/index.ts
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
[34mCLI[39m tsup v8.5.1
|
|
8
8
|
[34mCLI[39m Target: es2022
|
|
9
9
|
[34mESM[39m Build start
|
|
10
|
-
[32mESM[39m [1mdist/index.js [22m[
|
|
11
|
-
[32mESM[39m ⚡️ Build success in
|
|
10
|
+
[32mESM[39m [1mdist/index.js [22m[32m187.36 KB[39m
|
|
11
|
+
[32mESM[39m ⚡️ Build success in 91ms
|
|
12
12
|
[34mDTS[39m Build start
|
|
13
|
-
[32mDTS[39m ⚡️ Build success in
|
|
14
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[32m22.
|
|
13
|
+
[32mDTS[39m ⚡️ Build success in 3621ms
|
|
14
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m22.38 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @poncho-ai/harness
|
|
2
2
|
|
|
3
|
+
## 0.16.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`7475da5`](https://github.com/cesr/poncho-ai/commit/7475da5c0c2399e79064a2622137c0eb2fb16871) Thanks [@cesr](https://github.com/cesr)! - Inject browser usage context into agent system prompt (auth flow, session persistence, tool selection guidance).
|
|
8
|
+
|
|
9
|
+
## 0.16.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- [`12f2845`](https://github.com/cesr/poncho-ai/commit/12f28457c20e650640ff2a1c1dbece2a6e4a9ddd) Thanks [@cesr](https://github.com/cesr)! - Add browser storage persistence (cookies/localStorage survive restarts via configured storage provider) and new `browser_content` tool for fast text extraction from pages.
|
|
14
|
+
|
|
3
15
|
## 0.15.1
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -486,6 +486,7 @@ declare class AgentHarness {
|
|
|
486
486
|
private registerSkillTools;
|
|
487
487
|
private refreshSkillsIfChanged;
|
|
488
488
|
initialize(): Promise<void>;
|
|
489
|
+
private buildBrowserStoragePersistence;
|
|
489
490
|
private initBrowserTools;
|
|
490
491
|
/** Conversation ID of the currently executing run (set during run, cleared after). */
|
|
491
492
|
private _currentRunConversationId?;
|
package/dist/index.js
CHANGED
|
@@ -3290,6 +3290,85 @@ var AgentHarness = class {
|
|
|
3290
3290
|
);
|
|
3291
3291
|
}
|
|
3292
3292
|
}
|
|
3293
|
+
async buildBrowserStoragePersistence(config, sessionId) {
|
|
3294
|
+
const provider = config.storage?.provider ?? config.state?.provider ?? "local";
|
|
3295
|
+
const stateKey = `poncho:browser:state:${sessionId}`;
|
|
3296
|
+
if (provider === "memory") return void 0;
|
|
3297
|
+
if (provider === "local") {
|
|
3298
|
+
const { resolve: pathResolve } = await import("path");
|
|
3299
|
+
const { homedir: home } = await import("os");
|
|
3300
|
+
const stateDir = pathResolve(home(), ".poncho", "browser-state");
|
|
3301
|
+
const filePath = pathResolve(stateDir, `${sessionId}.json`);
|
|
3302
|
+
return {
|
|
3303
|
+
async save(json) {
|
|
3304
|
+
const { mkdir: mkdir5, writeFile: writeFile6 } = await import("fs/promises");
|
|
3305
|
+
await mkdir5(stateDir, { recursive: true });
|
|
3306
|
+
await writeFile6(filePath, json, "utf8");
|
|
3307
|
+
},
|
|
3308
|
+
async load() {
|
|
3309
|
+
const { readFile: readFile8 } = await import("fs/promises");
|
|
3310
|
+
try {
|
|
3311
|
+
return await readFile8(filePath, "utf8");
|
|
3312
|
+
} catch {
|
|
3313
|
+
return void 0;
|
|
3314
|
+
}
|
|
3315
|
+
}
|
|
3316
|
+
};
|
|
3317
|
+
}
|
|
3318
|
+
if (provider === "upstash") {
|
|
3319
|
+
const urlEnv = config.storage?.urlEnv ?? (process.env.UPSTASH_REDIS_REST_URL ? "UPSTASH_REDIS_REST_URL" : "KV_REST_API_URL");
|
|
3320
|
+
const tokenEnv = config.storage?.tokenEnv ?? (process.env.UPSTASH_REDIS_REST_TOKEN ? "UPSTASH_REDIS_REST_TOKEN" : "KV_REST_API_TOKEN");
|
|
3321
|
+
const baseUrl = (process.env[urlEnv] ?? "").replace(/\/+$/, "");
|
|
3322
|
+
const token = process.env[tokenEnv] ?? "";
|
|
3323
|
+
if (!baseUrl || !token) return void 0;
|
|
3324
|
+
const headers = { Authorization: `Bearer ${token}`, "Content-Type": "application/json" };
|
|
3325
|
+
return {
|
|
3326
|
+
async save(json) {
|
|
3327
|
+
await fetch(`${baseUrl}/set/${encodeURIComponent(stateKey)}/${encodeURIComponent(json)}`, { method: "POST", headers });
|
|
3328
|
+
},
|
|
3329
|
+
async load() {
|
|
3330
|
+
const res = await fetch(`${baseUrl}/get/${encodeURIComponent(stateKey)}`, { headers });
|
|
3331
|
+
if (!res.ok) return void 0;
|
|
3332
|
+
const body = await res.json();
|
|
3333
|
+
return body.result ?? void 0;
|
|
3334
|
+
}
|
|
3335
|
+
};
|
|
3336
|
+
}
|
|
3337
|
+
if (provider === "redis") {
|
|
3338
|
+
const urlEnv = config.storage?.urlEnv ?? "REDIS_URL";
|
|
3339
|
+
const url = process.env[urlEnv] ?? "";
|
|
3340
|
+
if (!url) return void 0;
|
|
3341
|
+
let clientPromise;
|
|
3342
|
+
const getClient = () => {
|
|
3343
|
+
if (!clientPromise) {
|
|
3344
|
+
clientPromise = (async () => {
|
|
3345
|
+
try {
|
|
3346
|
+
const mod = await import("redis");
|
|
3347
|
+
const c = mod.createClient({ url });
|
|
3348
|
+
await c.connect();
|
|
3349
|
+
return c;
|
|
3350
|
+
} catch {
|
|
3351
|
+
return void 0;
|
|
3352
|
+
}
|
|
3353
|
+
})();
|
|
3354
|
+
}
|
|
3355
|
+
return clientPromise;
|
|
3356
|
+
};
|
|
3357
|
+
return {
|
|
3358
|
+
async save(json) {
|
|
3359
|
+
const c = await getClient();
|
|
3360
|
+
if (c) await c.set(stateKey, json);
|
|
3361
|
+
},
|
|
3362
|
+
async load() {
|
|
3363
|
+
const c = await getClient();
|
|
3364
|
+
if (!c) return void 0;
|
|
3365
|
+
const val = await c.get(stateKey);
|
|
3366
|
+
return val ?? void 0;
|
|
3367
|
+
}
|
|
3368
|
+
};
|
|
3369
|
+
}
|
|
3370
|
+
return void 0;
|
|
3371
|
+
}
|
|
3293
3372
|
async initBrowserTools(config) {
|
|
3294
3373
|
const spec = ["@poncho-ai", "browser"].join("/");
|
|
3295
3374
|
let browserMod;
|
|
@@ -3318,9 +3397,14 @@ var AgentHarness = class {
|
|
|
3318
3397
|
);
|
|
3319
3398
|
}
|
|
3320
3399
|
this._browserMod = browserMod;
|
|
3321
|
-
const browserCfg = typeof config.browser === "object" ? config.browser : {};
|
|
3400
|
+
const browserCfg = typeof config.browser === "object" ? { ...config.browser } : {};
|
|
3322
3401
|
const agentId = this.parsedAgent?.frontmatter.id ?? this.parsedAgent?.frontmatter.name ?? "default";
|
|
3323
|
-
const
|
|
3402
|
+
const sessionId = `poncho-${agentId}`;
|
|
3403
|
+
const storagePersistence = await this.buildBrowserStoragePersistence(config, sessionId);
|
|
3404
|
+
if (storagePersistence) {
|
|
3405
|
+
browserCfg.storagePersistence = storagePersistence;
|
|
3406
|
+
}
|
|
3407
|
+
const session = new browserMod.BrowserSession(sessionId, browserCfg);
|
|
3324
3408
|
this._browserSession = session;
|
|
3325
3409
|
const tools = browserMod.createBrowserTools(
|
|
3326
3410
|
() => session,
|
|
@@ -3454,9 +3538,29 @@ var AgentHarness = class {
|
|
|
3454
3538
|
const developmentContext = this.environment === "development" ? `
|
|
3455
3539
|
|
|
3456
3540
|
${DEVELOPMENT_MODE_CONTEXT}` : "";
|
|
3541
|
+
const browserContext = this._browserSession ? `
|
|
3542
|
+
|
|
3543
|
+
## Browser Tools
|
|
3544
|
+
|
|
3545
|
+
The user has a live browser viewport displayed alongside the conversation. They can see everything the browser shows in real time and interact with it directly (click, type, scroll, paste).
|
|
3546
|
+
|
|
3547
|
+
### Authentication
|
|
3548
|
+
When a website requires authentication or credentials, do NOT ask the user to send them in the chat. Instead, navigate to the login page and let the user enter their credentials directly in the browser viewport. Wait for them to confirm they have logged in before continuing.
|
|
3549
|
+
|
|
3550
|
+
### Session persistence
|
|
3551
|
+
Browser sessions (cookies, localStorage, login state) are automatically saved and restored across conversations. If the user logged into a website in a previous conversation, that session is likely still active. Try navigating directly to the authenticated page before asking the user to log in again.
|
|
3552
|
+
|
|
3553
|
+
### Reading page content
|
|
3554
|
+
- Use \`browser_content\` to read the visible text on a page. This is fast and token-efficient.
|
|
3555
|
+
- Use \`browser_snapshot\` to get the accessibility tree with interactive element refs for clicking and typing.
|
|
3556
|
+
- Use \`browser_screenshot\` only when you need to see visual layout or images. Screenshots consume significantly more tokens.
|
|
3557
|
+
- The accessibility tree may be sparse on some pages. If \`browser_snapshot\` returns little or no content, fall back to \`browser_content\` or \`browser_screenshot\`.
|
|
3558
|
+
|
|
3559
|
+
### Tabs and resources
|
|
3560
|
+
Each conversation gets its own browser tab sharing a single browser instance. Call \`browser_close\` when done to free the tab. If you don't close it, the tab stays open and the user can continue interacting with it.` : "";
|
|
3457
3561
|
const promptWithSkills = this.skillContextWindow ? `${systemPrompt}${developmentContext}
|
|
3458
3562
|
|
|
3459
|
-
${this.skillContextWindow}` : `${systemPrompt}${developmentContext}`;
|
|
3563
|
+
${this.skillContextWindow}${browserContext}` : `${systemPrompt}${developmentContext}${browserContext}`;
|
|
3460
3564
|
const mainMemory = this.memoryStore ? await this.memoryStore.getMainMemory() : void 0;
|
|
3461
3565
|
const boundedMainMemory = mainMemory && mainMemory.content.length > 4e3 ? `${mainMemory.content.slice(0, 4e3)}
|
|
3462
3566
|
...[truncated]` : mainMemory?.content;
|
package/package.json
CHANGED
package/src/harness.ts
CHANGED
|
@@ -914,6 +914,94 @@ export class AgentHarness {
|
|
|
914
914
|
}
|
|
915
915
|
}
|
|
916
916
|
|
|
917
|
+
private async buildBrowserStoragePersistence(
|
|
918
|
+
config: PonchoConfig,
|
|
919
|
+
sessionId: string,
|
|
920
|
+
): Promise<{ save(json: string): Promise<void>; load(): Promise<string | undefined> } | undefined> {
|
|
921
|
+
const provider = config.storage?.provider ?? (config.state as Record<string, unknown> | undefined)?.provider as string | undefined ?? "local";
|
|
922
|
+
const stateKey = `poncho:browser:state:${sessionId}`;
|
|
923
|
+
|
|
924
|
+
if (provider === "memory") return undefined;
|
|
925
|
+
|
|
926
|
+
if (provider === "local") {
|
|
927
|
+
const { resolve: pathResolve } = await import("node:path");
|
|
928
|
+
const { homedir: home } = await import("node:os");
|
|
929
|
+
const stateDir = pathResolve(home(), ".poncho", "browser-state");
|
|
930
|
+
const filePath = pathResolve(stateDir, `${sessionId}.json`);
|
|
931
|
+
return {
|
|
932
|
+
async save(json: string) {
|
|
933
|
+
const { mkdir, writeFile } = await import("node:fs/promises");
|
|
934
|
+
await mkdir(stateDir, { recursive: true });
|
|
935
|
+
await writeFile(filePath, json, "utf8");
|
|
936
|
+
},
|
|
937
|
+
async load() {
|
|
938
|
+
const { readFile } = await import("node:fs/promises");
|
|
939
|
+
try { return await readFile(filePath, "utf8"); } catch { return undefined; }
|
|
940
|
+
},
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
if (provider === "upstash") {
|
|
945
|
+
const urlEnv = config.storage?.urlEnv ?? (process.env.UPSTASH_REDIS_REST_URL ? "UPSTASH_REDIS_REST_URL" : "KV_REST_API_URL");
|
|
946
|
+
const tokenEnv = config.storage?.tokenEnv ?? (process.env.UPSTASH_REDIS_REST_TOKEN ? "UPSTASH_REDIS_REST_TOKEN" : "KV_REST_API_TOKEN");
|
|
947
|
+
const baseUrl = (process.env[urlEnv] ?? "").replace(/\/+$/, "");
|
|
948
|
+
const token = process.env[tokenEnv] ?? "";
|
|
949
|
+
if (!baseUrl || !token) return undefined;
|
|
950
|
+
const headers = { Authorization: `Bearer ${token}`, "Content-Type": "application/json" };
|
|
951
|
+
return {
|
|
952
|
+
async save(json: string) {
|
|
953
|
+
await fetch(`${baseUrl}/set/${encodeURIComponent(stateKey)}/${encodeURIComponent(json)}`, { method: "POST", headers });
|
|
954
|
+
},
|
|
955
|
+
async load() {
|
|
956
|
+
const res = await fetch(`${baseUrl}/get/${encodeURIComponent(stateKey)}`, { headers });
|
|
957
|
+
if (!res.ok) return undefined;
|
|
958
|
+
const body = await res.json() as { result?: string | null };
|
|
959
|
+
return body.result ?? undefined;
|
|
960
|
+
},
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
if (provider === "redis") {
|
|
965
|
+
const urlEnv = config.storage?.urlEnv ?? "REDIS_URL";
|
|
966
|
+
const url = process.env[urlEnv] ?? "";
|
|
967
|
+
if (!url) return undefined;
|
|
968
|
+
let clientPromise: Promise<{ get(k: string): Promise<string | null>; set(k: string, v: string): Promise<unknown> } | undefined> | undefined;
|
|
969
|
+
const getClient = () => {
|
|
970
|
+
if (!clientPromise) {
|
|
971
|
+
clientPromise = (async () => {
|
|
972
|
+
try {
|
|
973
|
+
const mod = (await import("redis")) as unknown as {
|
|
974
|
+
createClient: (opts: { url: string }) => {
|
|
975
|
+
connect(): Promise<unknown>;
|
|
976
|
+
get(k: string): Promise<string | null>;
|
|
977
|
+
set(k: string, v: string): Promise<unknown>;
|
|
978
|
+
};
|
|
979
|
+
};
|
|
980
|
+
const c = mod.createClient({ url });
|
|
981
|
+
await c.connect();
|
|
982
|
+
return c;
|
|
983
|
+
} catch { return undefined; }
|
|
984
|
+
})();
|
|
985
|
+
}
|
|
986
|
+
return clientPromise;
|
|
987
|
+
};
|
|
988
|
+
return {
|
|
989
|
+
async save(json: string) {
|
|
990
|
+
const c = await getClient();
|
|
991
|
+
if (c) await c.set(stateKey, json);
|
|
992
|
+
},
|
|
993
|
+
async load() {
|
|
994
|
+
const c = await getClient();
|
|
995
|
+
if (!c) return undefined;
|
|
996
|
+
const val = await c.get(stateKey);
|
|
997
|
+
return val ?? undefined;
|
|
998
|
+
},
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
return undefined;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
917
1005
|
private async initBrowserTools(config: PonchoConfig): Promise<void> {
|
|
918
1006
|
const spec = ["@poncho-ai", "browser"].join("/");
|
|
919
1007
|
let browserMod: {
|
|
@@ -947,9 +1035,16 @@ export class AgentHarness {
|
|
|
947
1035
|
}
|
|
948
1036
|
|
|
949
1037
|
this._browserMod = browserMod;
|
|
950
|
-
const browserCfg = typeof config.browser === "object" ? config.browser : {};
|
|
1038
|
+
const browserCfg: Record<string, unknown> = typeof config.browser === "object" ? { ...config.browser } : {};
|
|
951
1039
|
const agentId = this.parsedAgent?.frontmatter.id ?? this.parsedAgent?.frontmatter.name ?? "default";
|
|
952
|
-
const
|
|
1040
|
+
const sessionId = `poncho-${agentId}`;
|
|
1041
|
+
|
|
1042
|
+
const storagePersistence = await this.buildBrowserStoragePersistence(config, sessionId);
|
|
1043
|
+
if (storagePersistence) {
|
|
1044
|
+
browserCfg.storagePersistence = storagePersistence;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
const session = new browserMod.BrowserSession(sessionId, browserCfg);
|
|
953
1048
|
this._browserSession = session;
|
|
954
1049
|
|
|
955
1050
|
const tools = browserMod.createBrowserTools(
|
|
@@ -1115,9 +1210,29 @@ export class AgentHarness {
|
|
|
1115
1210
|
});
|
|
1116
1211
|
const developmentContext =
|
|
1117
1212
|
this.environment === "development" ? `\n\n${DEVELOPMENT_MODE_CONTEXT}` : "";
|
|
1213
|
+
const browserContext = this._browserSession
|
|
1214
|
+
? `\n\n## Browser Tools
|
|
1215
|
+
|
|
1216
|
+
The user has a live browser viewport displayed alongside the conversation. They can see everything the browser shows in real time and interact with it directly (click, type, scroll, paste).
|
|
1217
|
+
|
|
1218
|
+
### Authentication
|
|
1219
|
+
When a website requires authentication or credentials, do NOT ask the user to send them in the chat. Instead, navigate to the login page and let the user enter their credentials directly in the browser viewport. Wait for them to confirm they have logged in before continuing.
|
|
1220
|
+
|
|
1221
|
+
### Session persistence
|
|
1222
|
+
Browser sessions (cookies, localStorage, login state) are automatically saved and restored across conversations. If the user logged into a website in a previous conversation, that session is likely still active. Try navigating directly to the authenticated page before asking the user to log in again.
|
|
1223
|
+
|
|
1224
|
+
### Reading page content
|
|
1225
|
+
- Use \`browser_content\` to read the visible text on a page. This is fast and token-efficient.
|
|
1226
|
+
- Use \`browser_snapshot\` to get the accessibility tree with interactive element refs for clicking and typing.
|
|
1227
|
+
- Use \`browser_screenshot\` only when you need to see visual layout or images. Screenshots consume significantly more tokens.
|
|
1228
|
+
- The accessibility tree may be sparse on some pages. If \`browser_snapshot\` returns little or no content, fall back to \`browser_content\` or \`browser_screenshot\`.
|
|
1229
|
+
|
|
1230
|
+
### Tabs and resources
|
|
1231
|
+
Each conversation gets its own browser tab sharing a single browser instance. Call \`browser_close\` when done to free the tab. If you don't close it, the tab stays open and the user can continue interacting with it.`
|
|
1232
|
+
: "";
|
|
1118
1233
|
const promptWithSkills = this.skillContextWindow
|
|
1119
|
-
? `${systemPrompt}${developmentContext}\n\n${this.skillContextWindow}`
|
|
1120
|
-
: `${systemPrompt}${developmentContext}`;
|
|
1234
|
+
? `${systemPrompt}${developmentContext}\n\n${this.skillContextWindow}${browserContext}`
|
|
1235
|
+
: `${systemPrompt}${developmentContext}${browserContext}`;
|
|
1121
1236
|
const mainMemory = this.memoryStore
|
|
1122
1237
|
? await this.memoryStore.getMainMemory()
|
|
1123
1238
|
: undefined;
|