@poncho-ai/harness 0.23.0 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +5 -5
- package/CHANGELOG.md +12 -0
- package/dist/index.d.ts +20 -2
- package/dist/index.js +228 -80
- package/package.json +1 -1
- package/src/config.ts +1 -0
- package/src/default-tools.ts +53 -0
- package/src/harness.ts +129 -41
- package/src/memory.ts +63 -46
- package/test/harness.test.ts +193 -3
- package/test/memory.test.ts +100 -15
package/dist/index.js
CHANGED
|
@@ -1402,7 +1402,8 @@ if (result.compacted) {
|
|
|
1402
1402
|
When \`memory.enabled\` is true in \`poncho.config.js\`, the harness enables a simple memory model:
|
|
1403
1403
|
|
|
1404
1404
|
- A single persistent main memory document is loaded at run start and interpolated into the system prompt under \`## Persistent Memory\`.
|
|
1405
|
-
- \`
|
|
1405
|
+
- \`memory_main_write\` overwrites the entire memory document (for initial writes or full rewrites).
|
|
1406
|
+
- \`memory_main_edit\` performs targeted string-replacement edits on memory (find \`old_str\`, replace with \`new_str\`), mirroring \`edit_file\` semantics. The tool description instructs the model to proactively evaluate each turn whether durable memory should be updated.
|
|
1406
1407
|
- \`conversation_recall\` can search recent prior conversations (keyword scoring) when historical context is relevant (\`as we discussed\`, \`last time\`, etc.).
|
|
1407
1408
|
|
|
1408
1409
|
\`\`\`javascript
|
|
@@ -1420,9 +1421,10 @@ export default {
|
|
|
1420
1421
|
|
|
1421
1422
|
Available memory tools:
|
|
1422
1423
|
|
|
1423
|
-
- \`memory_main_get\`
|
|
1424
|
-
- \`
|
|
1425
|
-
- \`
|
|
1424
|
+
- \`memory_main_get\` \u2014 read the current memory document
|
|
1425
|
+
- \`memory_main_write\` \u2014 overwrite the entire memory document
|
|
1426
|
+
- \`memory_main_edit\` \u2014 edit memory via exact string replacement (\`old_str\` / \`new_str\`)
|
|
1427
|
+
- \`conversation_recall\` \u2014 search past conversations
|
|
1426
1428
|
`,
|
|
1427
1429
|
"configuration": `# Configuration & Security
|
|
1428
1430
|
|
|
@@ -1908,6 +1910,52 @@ var createWriteTool = (workingDir) => defineTool({
|
|
|
1908
1910
|
return { path, written: true };
|
|
1909
1911
|
}
|
|
1910
1912
|
});
|
|
1913
|
+
var createEditTool = (workingDir) => defineTool({
|
|
1914
|
+
name: "edit_file",
|
|
1915
|
+
description: "Edit a file by replacing an exact string match with new content. The old_str must match exactly one location in the file (including whitespace and indentation). Use an empty new_str to delete matched content.",
|
|
1916
|
+
inputSchema: {
|
|
1917
|
+
type: "object",
|
|
1918
|
+
properties: {
|
|
1919
|
+
path: {
|
|
1920
|
+
type: "string",
|
|
1921
|
+
description: "File path relative to working directory"
|
|
1922
|
+
},
|
|
1923
|
+
old_str: {
|
|
1924
|
+
type: "string",
|
|
1925
|
+
description: "The exact text to find and replace (must be unique in the file). Include surrounding context lines if needed to ensure uniqueness."
|
|
1926
|
+
},
|
|
1927
|
+
new_str: {
|
|
1928
|
+
type: "string",
|
|
1929
|
+
description: "The replacement text (use empty string to delete the matched content)"
|
|
1930
|
+
}
|
|
1931
|
+
},
|
|
1932
|
+
required: ["path", "old_str", "new_str"],
|
|
1933
|
+
additionalProperties: false
|
|
1934
|
+
},
|
|
1935
|
+
handler: async (input) => {
|
|
1936
|
+
const path = typeof input.path === "string" ? input.path : "";
|
|
1937
|
+
const oldStr = typeof input.old_str === "string" ? input.old_str : "";
|
|
1938
|
+
const newStr = typeof input.new_str === "string" ? input.new_str : "";
|
|
1939
|
+
if (!oldStr) throw new Error("old_str must not be empty.");
|
|
1940
|
+
const resolved = resolveSafePath(workingDir, path);
|
|
1941
|
+
const content = await readFile3(resolved, "utf8");
|
|
1942
|
+
const first = content.indexOf(oldStr);
|
|
1943
|
+
if (first === -1) {
|
|
1944
|
+
throw new Error(
|
|
1945
|
+
"old_str not found in file. Make sure it matches exactly, including whitespace and line breaks."
|
|
1946
|
+
);
|
|
1947
|
+
}
|
|
1948
|
+
const last = content.lastIndexOf(oldStr);
|
|
1949
|
+
if (first !== last) {
|
|
1950
|
+
throw new Error(
|
|
1951
|
+
"old_str appears multiple times in the file. Please provide more context to ensure a unique match."
|
|
1952
|
+
);
|
|
1953
|
+
}
|
|
1954
|
+
const newContent = content.slice(0, first) + newStr + content.slice(first + oldStr.length);
|
|
1955
|
+
await writeFile2(resolved, newContent, "utf8");
|
|
1956
|
+
return { path, edited: true };
|
|
1957
|
+
}
|
|
1958
|
+
});
|
|
1911
1959
|
var createDeleteTool = (workingDir) => defineTool({
|
|
1912
1960
|
name: "delete_file",
|
|
1913
1961
|
description: "Delete a file at a path inside the working directory",
|
|
@@ -1982,6 +2030,8 @@ var ponchoDocsTool = defineTool({
|
|
|
1982
2030
|
|
|
1983
2031
|
// src/harness.ts
|
|
1984
2032
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
2033
|
+
import { readFile as readFile7 } from "fs/promises";
|
|
2034
|
+
import { resolve as resolve9 } from "path";
|
|
1985
2035
|
import { getTextContent as getTextContent3 } from "@poncho-ai/sdk";
|
|
1986
2036
|
|
|
1987
2037
|
// src/upload-store.ts
|
|
@@ -2338,14 +2388,9 @@ var InMemoryMemoryStore = class {
|
|
|
2338
2388
|
return this.mainMemory;
|
|
2339
2389
|
}
|
|
2340
2390
|
async updateMainMemory(input) {
|
|
2341
|
-
const now2 = Date.now();
|
|
2342
|
-
const existing = await this.getMainMemory();
|
|
2343
|
-
const nextContent = input.mode === "append" && existing.content ? `${existing.content}
|
|
2344
|
-
|
|
2345
|
-
${input.content}`.trim() : input.content;
|
|
2346
2391
|
this.mainMemory = {
|
|
2347
|
-
content:
|
|
2348
|
-
updatedAt:
|
|
2392
|
+
content: input.content.trim(),
|
|
2393
|
+
updatedAt: Date.now()
|
|
2349
2394
|
};
|
|
2350
2395
|
return this.mainMemory;
|
|
2351
2396
|
}
|
|
@@ -2403,12 +2448,8 @@ var FileMainMemoryStore = class {
|
|
|
2403
2448
|
}
|
|
2404
2449
|
async updateMainMemory(input) {
|
|
2405
2450
|
await this.ensureLoaded();
|
|
2406
|
-
const existing = await this.getMainMemory();
|
|
2407
|
-
const nextContent = input.mode === "append" && existing.content ? `${existing.content}
|
|
2408
|
-
|
|
2409
|
-
${input.content}`.trim() : input.content;
|
|
2410
2451
|
this.mainMemory = {
|
|
2411
|
-
content:
|
|
2452
|
+
content: input.content.trim(),
|
|
2412
2453
|
updatedAt: Date.now()
|
|
2413
2454
|
};
|
|
2414
2455
|
await this.persist();
|
|
@@ -2447,8 +2488,7 @@ var KeyValueMainMemoryStoreBase = class {
|
|
|
2447
2488
|
}
|
|
2448
2489
|
} catch {
|
|
2449
2490
|
await this.memoryFallback.updateMainMemory({
|
|
2450
|
-
content: payload.main.content
|
|
2451
|
-
mode: "replace"
|
|
2491
|
+
content: payload.main.content
|
|
2452
2492
|
});
|
|
2453
2493
|
}
|
|
2454
2494
|
}
|
|
@@ -2459,11 +2499,8 @@ var KeyValueMainMemoryStoreBase = class {
|
|
|
2459
2499
|
async updateMainMemory(input) {
|
|
2460
2500
|
const key = this.key();
|
|
2461
2501
|
const payload = await this.readPayload(key);
|
|
2462
|
-
const nextContent = input.mode === "append" && payload.main.content ? `${payload.main.content}
|
|
2463
|
-
|
|
2464
|
-
${input.content}`.trim() : input.content;
|
|
2465
2502
|
payload.main = {
|
|
2466
|
-
content:
|
|
2503
|
+
content: input.content.trim(),
|
|
2467
2504
|
updatedAt: Date.now()
|
|
2468
2505
|
};
|
|
2469
2506
|
await this.writePayload(key, payload);
|
|
@@ -2729,19 +2766,14 @@ var createMemoryTools = (store, options) => {
|
|
|
2729
2766
|
}
|
|
2730
2767
|
}),
|
|
2731
2768
|
defineTool2({
|
|
2732
|
-
name: "
|
|
2733
|
-
description: "
|
|
2769
|
+
name: "memory_main_write",
|
|
2770
|
+
description: "Overwrite the entire persistent main memory document. Use for initial writes or full rewrites. Prefer memory_main_edit for targeted changes to existing memory.",
|
|
2734
2771
|
inputSchema: {
|
|
2735
2772
|
type: "object",
|
|
2736
2773
|
properties: {
|
|
2737
|
-
mode: {
|
|
2738
|
-
type: "string",
|
|
2739
|
-
enum: ["replace", "append"],
|
|
2740
|
-
description: "replace overwrites memory; append adds content to the end"
|
|
2741
|
-
},
|
|
2742
2774
|
content: {
|
|
2743
2775
|
type: "string",
|
|
2744
|
-
description: "The memory content to write"
|
|
2776
|
+
description: "The full memory content to write"
|
|
2745
2777
|
}
|
|
2746
2778
|
},
|
|
2747
2779
|
required: ["content"],
|
|
@@ -2752,8 +2784,50 @@ var createMemoryTools = (store, options) => {
|
|
|
2752
2784
|
if (!content) {
|
|
2753
2785
|
throw new Error("content is required");
|
|
2754
2786
|
}
|
|
2755
|
-
const
|
|
2756
|
-
|
|
2787
|
+
const memory = await store.updateMainMemory({ content });
|
|
2788
|
+
return { ok: true, memory };
|
|
2789
|
+
}
|
|
2790
|
+
}),
|
|
2791
|
+
defineTool2({
|
|
2792
|
+
name: "memory_main_edit",
|
|
2793
|
+
description: "Edit persistent main memory by replacing an exact string match with new content. The old_str must match exactly one location in memory. Use an empty new_str to delete matched content. Proactively evaluate every turn whether memory should be updated.",
|
|
2794
|
+
inputSchema: {
|
|
2795
|
+
type: "object",
|
|
2796
|
+
properties: {
|
|
2797
|
+
old_str: {
|
|
2798
|
+
type: "string",
|
|
2799
|
+
description: "The exact text to find and replace (must be unique in memory). Include surrounding context if needed to ensure uniqueness."
|
|
2800
|
+
},
|
|
2801
|
+
new_str: {
|
|
2802
|
+
type: "string",
|
|
2803
|
+
description: "The replacement text (use empty string to delete the matched content)"
|
|
2804
|
+
}
|
|
2805
|
+
},
|
|
2806
|
+
required: ["old_str", "new_str"],
|
|
2807
|
+
additionalProperties: false
|
|
2808
|
+
},
|
|
2809
|
+
handler: async (input) => {
|
|
2810
|
+
const oldStr = typeof input.old_str === "string" ? input.old_str : "";
|
|
2811
|
+
const newStr = typeof input.new_str === "string" ? input.new_str : "";
|
|
2812
|
+
if (!oldStr) {
|
|
2813
|
+
throw new Error("old_str must not be empty.");
|
|
2814
|
+
}
|
|
2815
|
+
const current = await store.getMainMemory();
|
|
2816
|
+
const content = current.content;
|
|
2817
|
+
const first = content.indexOf(oldStr);
|
|
2818
|
+
if (first === -1) {
|
|
2819
|
+
throw new Error(
|
|
2820
|
+
"old_str not found in memory. Make sure it matches exactly, including whitespace and line breaks."
|
|
2821
|
+
);
|
|
2822
|
+
}
|
|
2823
|
+
const last = content.lastIndexOf(oldStr);
|
|
2824
|
+
if (first !== last) {
|
|
2825
|
+
throw new Error(
|
|
2826
|
+
"old_str appears multiple times in memory. Please provide more context to ensure a unique match."
|
|
2827
|
+
);
|
|
2828
|
+
}
|
|
2829
|
+
const newContent = content.slice(0, first) + newStr + content.slice(first + oldStr.length);
|
|
2830
|
+
const memory = await store.updateMainMemory({ content: newContent });
|
|
2757
2831
|
return { ok: true, memory };
|
|
2758
2832
|
}
|
|
2759
2833
|
}),
|
|
@@ -4327,7 +4401,7 @@ You are running locally in development mode. Treat this as an editable agent wor
|
|
|
4327
4401
|
## Understanding Your Environment
|
|
4328
4402
|
|
|
4329
4403
|
- Built-in tools: \`list_directory\` and \`read_file\`
|
|
4330
|
-
- \`write_file\`
|
|
4404
|
+
- \`write_file\` and \`edit_file\` are available in development (disabled by default in production)
|
|
4331
4405
|
- A starter local skill is included (\`starter-echo\`)
|
|
4332
4406
|
- Bash/shell commands are **not** available unless you install and enable a shell tool/skill
|
|
4333
4407
|
- Git operations are only available if a git-capable tool/skill is configured
|
|
@@ -4588,6 +4662,7 @@ Since all fields have defaults, you only need to specify \`*Env\` when your env
|
|
|
4588
4662
|
- If shell/CLI access is unavailable, ask the user to run needed commands and provide exact copy-paste commands.
|
|
4589
4663
|
- For setup, skills, MCP, auth, storage, telemetry, or "how do I..." questions, proactively read \`README.md\` with \`read_file\` before answering.
|
|
4590
4664
|
- Prefer quoting concrete commands and examples from \`README.md\` over guessing.
|
|
4665
|
+
- Prefer \`edit_file\` for targeted changes to existing files (uses exact string matching); use \`write_file\` only for creating new files or full rewrites.
|
|
4591
4666
|
- Keep edits minimal, preserve unrelated settings/code, and summarize what changed.
|
|
4592
4667
|
|
|
4593
4668
|
## Detailed Documentation
|
|
@@ -4641,6 +4716,7 @@ var AgentHarness = class _AgentHarness {
|
|
|
4641
4716
|
_browserSession;
|
|
4642
4717
|
_browserMod;
|
|
4643
4718
|
parsedAgent;
|
|
4719
|
+
agentFileFingerprint = "";
|
|
4644
4720
|
mcpBridge;
|
|
4645
4721
|
subagentManager;
|
|
4646
4722
|
resolveToolAccess(toolName) {
|
|
@@ -4658,7 +4734,7 @@ var AgentHarness = class _AgentHarness {
|
|
|
4658
4734
|
isToolEnabled(name) {
|
|
4659
4735
|
const access3 = this.resolveToolAccess(name);
|
|
4660
4736
|
if (access3 === false) return false;
|
|
4661
|
-
if (name === "write_file" || name === "delete_file" || name === "delete_directory") {
|
|
4737
|
+
if (name === "write_file" || name === "edit_file" || name === "delete_file" || name === "delete_directory") {
|
|
4662
4738
|
return this.shouldEnableWriteTool();
|
|
4663
4739
|
}
|
|
4664
4740
|
return true;
|
|
@@ -4701,6 +4777,9 @@ var AgentHarness = class _AgentHarness {
|
|
|
4701
4777
|
if (this.isToolEnabled("write_file")) {
|
|
4702
4778
|
this.registerIfMissing(createWriteTool(this.workingDir));
|
|
4703
4779
|
}
|
|
4780
|
+
if (this.isToolEnabled("edit_file")) {
|
|
4781
|
+
this.registerIfMissing(createEditTool(this.workingDir));
|
|
4782
|
+
}
|
|
4704
4783
|
if (this.isToolEnabled("delete_file")) {
|
|
4705
4784
|
this.registerIfMissing(createDeleteTool(this.workingDir));
|
|
4706
4785
|
}
|
|
@@ -4750,20 +4829,17 @@ var AgentHarness = class _AgentHarness {
|
|
|
4750
4829
|
return this.parsedAgent?.frontmatter.approvalRequired?.scripts ?? [];
|
|
4751
4830
|
}
|
|
4752
4831
|
getRequestedMcpPatterns() {
|
|
4753
|
-
const
|
|
4832
|
+
const patterns = new Set(this.getAgentMcpIntent());
|
|
4754
4833
|
for (const skillName of this.activeSkillNames) {
|
|
4755
4834
|
const skill = this.loadedSkills.find((entry) => entry.name === skillName);
|
|
4756
4835
|
if (!skill) {
|
|
4757
4836
|
continue;
|
|
4758
4837
|
}
|
|
4759
4838
|
for (const pattern of skill.allowedTools.mcp) {
|
|
4760
|
-
|
|
4839
|
+
patterns.add(pattern);
|
|
4761
4840
|
}
|
|
4762
4841
|
}
|
|
4763
|
-
|
|
4764
|
-
return [...skillPatterns];
|
|
4765
|
-
}
|
|
4766
|
-
return this.getAgentMcpIntent();
|
|
4842
|
+
return [...patterns];
|
|
4767
4843
|
}
|
|
4768
4844
|
getRequestedScriptPatterns() {
|
|
4769
4845
|
const patterns = new Set(this.getAgentScriptIntent());
|
|
@@ -4779,20 +4855,17 @@ var AgentHarness = class _AgentHarness {
|
|
|
4779
4855
|
return [...patterns];
|
|
4780
4856
|
}
|
|
4781
4857
|
getRequestedMcpApprovalPatterns() {
|
|
4782
|
-
const
|
|
4858
|
+
const patterns = new Set(this.getAgentMcpApprovalPatterns());
|
|
4783
4859
|
for (const skillName of this.activeSkillNames) {
|
|
4784
4860
|
const skill = this.loadedSkills.find((entry) => entry.name === skillName);
|
|
4785
4861
|
if (!skill) {
|
|
4786
4862
|
continue;
|
|
4787
4863
|
}
|
|
4788
4864
|
for (const pattern of skill.approvalRequired.mcp) {
|
|
4789
|
-
|
|
4865
|
+
patterns.add(pattern);
|
|
4790
4866
|
}
|
|
4791
4867
|
}
|
|
4792
|
-
|
|
4793
|
-
return [...skillPatterns];
|
|
4794
|
-
}
|
|
4795
|
-
return this.getAgentMcpApprovalPatterns();
|
|
4868
|
+
return [...patterns];
|
|
4796
4869
|
}
|
|
4797
4870
|
getRequestedScriptApprovalPatterns() {
|
|
4798
4871
|
const patterns = new Set(this.getAgentScriptApprovalPatterns());
|
|
@@ -4923,13 +4996,54 @@ var AgentHarness = class _AgentHarness {
|
|
|
4923
4996
|
);
|
|
4924
4997
|
}
|
|
4925
4998
|
static SKILL_REFRESH_DEBOUNCE_MS = 3e3;
|
|
4926
|
-
|
|
4999
|
+
/**
|
|
5000
|
+
* Re-read AGENT.md and update the parsed agent when the file has changed
|
|
5001
|
+
* on disk. Returns `true` when the agent was actually re-parsed.
|
|
5002
|
+
*
|
|
5003
|
+
* Preserves the agent identity (id) across reloads so conversation
|
|
5004
|
+
* continuity isn't broken.
|
|
5005
|
+
*/
|
|
5006
|
+
async refreshAgentIfChanged() {
|
|
4927
5007
|
if (this.environment !== "development") {
|
|
4928
|
-
return;
|
|
5008
|
+
return false;
|
|
4929
5009
|
}
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
|
|
5010
|
+
try {
|
|
5011
|
+
const agentFilePath = resolve9(this.workingDir, "AGENT.md");
|
|
5012
|
+
const rawContent = await readFile7(agentFilePath, "utf8");
|
|
5013
|
+
if (rawContent === this.agentFileFingerprint) {
|
|
5014
|
+
return false;
|
|
5015
|
+
}
|
|
5016
|
+
const parsed = parseAgentMarkdown(rawContent);
|
|
5017
|
+
if (!parsed.frontmatter.id && this.parsedAgent?.frontmatter.id) {
|
|
5018
|
+
parsed.frontmatter.id = this.parsedAgent.frontmatter.id;
|
|
5019
|
+
}
|
|
5020
|
+
this.parsedAgent = parsed;
|
|
5021
|
+
this.agentFileFingerprint = rawContent;
|
|
5022
|
+
return true;
|
|
5023
|
+
} catch (error) {
|
|
5024
|
+
console.warn(
|
|
5025
|
+
`[poncho][agent] Failed to refresh AGENT.md in development mode: ${error instanceof Error ? error.message : String(error)}`
|
|
5026
|
+
);
|
|
5027
|
+
return false;
|
|
5028
|
+
}
|
|
5029
|
+
}
|
|
5030
|
+
/**
|
|
5031
|
+
* Re-scan skill directories and update metadata, tools, and context window
|
|
5032
|
+
* when skills have changed on disk. Returns `true` when the skill set was
|
|
5033
|
+
* actually updated.
|
|
5034
|
+
*
|
|
5035
|
+
* @param force - bypass the time-based debounce (used for mid-run refreshes
|
|
5036
|
+
* after the agent may have written new skill files).
|
|
5037
|
+
*/
|
|
5038
|
+
async refreshSkillsIfChanged(force = false) {
|
|
5039
|
+
if (this.environment !== "development") {
|
|
5040
|
+
return false;
|
|
5041
|
+
}
|
|
5042
|
+
if (!force) {
|
|
5043
|
+
const elapsed = Date.now() - this.lastSkillRefreshAt;
|
|
5044
|
+
if (this.lastSkillRefreshAt > 0 && elapsed < _AgentHarness.SKILL_REFRESH_DEBOUNCE_MS) {
|
|
5045
|
+
return false;
|
|
5046
|
+
}
|
|
4933
5047
|
}
|
|
4934
5048
|
this.lastSkillRefreshAt = Date.now();
|
|
4935
5049
|
try {
|
|
@@ -4939,22 +5053,35 @@ var AgentHarness = class _AgentHarness {
|
|
|
4939
5053
|
);
|
|
4940
5054
|
const nextFingerprint = this.buildSkillFingerprint(latestSkills);
|
|
4941
5055
|
if (nextFingerprint === this.skillFingerprint) {
|
|
4942
|
-
return;
|
|
5056
|
+
return false;
|
|
4943
5057
|
}
|
|
4944
5058
|
this.loadedSkills = latestSkills;
|
|
4945
5059
|
this.skillContextWindow = buildSkillContextWindow(latestSkills);
|
|
4946
5060
|
this.skillFingerprint = nextFingerprint;
|
|
4947
5061
|
this.registerSkillTools(latestSkills);
|
|
4948
|
-
|
|
5062
|
+
const latestSkillNames = new Set(latestSkills.map((s) => s.name));
|
|
5063
|
+
for (const name of this.activeSkillNames) {
|
|
5064
|
+
if (!latestSkillNames.has(name)) {
|
|
5065
|
+
this.activeSkillNames.delete(name);
|
|
5066
|
+
}
|
|
5067
|
+
}
|
|
5068
|
+
if (this.mcpBridge) {
|
|
5069
|
+
await this.mcpBridge.discoverTools();
|
|
5070
|
+
}
|
|
4949
5071
|
await this.refreshMcpTools("skills:changed");
|
|
5072
|
+
return true;
|
|
4950
5073
|
} catch (error) {
|
|
4951
5074
|
console.warn(
|
|
4952
5075
|
`[poncho][skills] Failed to refresh skills in development mode: ${error instanceof Error ? error.message : String(error)}`
|
|
4953
5076
|
);
|
|
5077
|
+
return false;
|
|
4954
5078
|
}
|
|
4955
5079
|
}
|
|
4956
5080
|
async initialize() {
|
|
4957
|
-
|
|
5081
|
+
const agentFilePath = resolve9(this.workingDir, "AGENT.md");
|
|
5082
|
+
const agentRawContent = await readFile7(agentFilePath, "utf8");
|
|
5083
|
+
this.parsedAgent = parseAgentMarkdown(agentRawContent);
|
|
5084
|
+
this.agentFileFingerprint = agentRawContent;
|
|
4958
5085
|
const identity = await ensureAgentIdentity(this.workingDir);
|
|
4959
5086
|
if (!this.parsedAgent.frontmatter.id) {
|
|
4960
5087
|
this.parsedAgent.frontmatter.id = identity.id;
|
|
@@ -5032,9 +5159,9 @@ var AgentHarness = class _AgentHarness {
|
|
|
5032
5159
|
await writeFile6(filePath, json, "utf8");
|
|
5033
5160
|
},
|
|
5034
5161
|
async load() {
|
|
5035
|
-
const { readFile:
|
|
5162
|
+
const { readFile: readFile9 } = await import("fs/promises");
|
|
5036
5163
|
try {
|
|
5037
|
-
return await
|
|
5164
|
+
return await readFile9(filePath, "utf8");
|
|
5038
5165
|
} catch {
|
|
5039
5166
|
return void 0;
|
|
5040
5167
|
}
|
|
@@ -5198,9 +5325,9 @@ var AgentHarness = class _AgentHarness {
|
|
|
5198
5325
|
for await (const event of this.run(input)) {
|
|
5199
5326
|
eventQueue.push(event);
|
|
5200
5327
|
if (queueResolve) {
|
|
5201
|
-
const
|
|
5328
|
+
const resolve11 = queueResolve;
|
|
5202
5329
|
queueResolve = null;
|
|
5203
|
-
|
|
5330
|
+
resolve11();
|
|
5204
5331
|
}
|
|
5205
5332
|
}
|
|
5206
5333
|
} catch (error) {
|
|
@@ -5219,8 +5346,8 @@ var AgentHarness = class _AgentHarness {
|
|
|
5219
5346
|
if (eventQueue.length > 0) {
|
|
5220
5347
|
yield eventQueue.shift();
|
|
5221
5348
|
} else if (!generatorDone) {
|
|
5222
|
-
await new Promise((
|
|
5223
|
-
queueResolve =
|
|
5349
|
+
await new Promise((resolve11) => {
|
|
5350
|
+
queueResolve = resolve11;
|
|
5224
5351
|
});
|
|
5225
5352
|
}
|
|
5226
5353
|
}
|
|
@@ -5258,13 +5385,14 @@ var AgentHarness = class _AgentHarness {
|
|
|
5258
5385
|
await this.initialize();
|
|
5259
5386
|
}
|
|
5260
5387
|
const memoryPromise = this.memoryStore ? this.memoryStore.getMainMemory() : void 0;
|
|
5388
|
+
await this.refreshAgentIfChanged();
|
|
5261
5389
|
await this.refreshSkillsIfChanged();
|
|
5262
5390
|
this._currentRunConversationId = input.conversationId;
|
|
5263
5391
|
const ownerParam = input.parameters?.__ownerId;
|
|
5264
5392
|
if (typeof ownerParam === "string") {
|
|
5265
5393
|
this._currentRunOwnerId = ownerParam;
|
|
5266
5394
|
}
|
|
5267
|
-
|
|
5395
|
+
let agent = this.parsedAgent;
|
|
5268
5396
|
const runId = `run_${randomUUID3()}`;
|
|
5269
5397
|
const start = now();
|
|
5270
5398
|
const maxSteps = agent.frontmatter.limits?.maxSteps ?? 50;
|
|
@@ -5275,11 +5403,11 @@ var AgentHarness = class _AgentHarness {
|
|
|
5275
5403
|
const messages = [...input.messages ?? []];
|
|
5276
5404
|
const inputMessageCount = messages.length;
|
|
5277
5405
|
const events = [];
|
|
5278
|
-
const
|
|
5406
|
+
const renderCurrentAgentPrompt = () => renderAgentPrompt(this.parsedAgent, {
|
|
5279
5407
|
parameters: input.parameters,
|
|
5280
5408
|
runtime: {
|
|
5281
5409
|
runId,
|
|
5282
|
-
agentId:
|
|
5410
|
+
agentId: this.parsedAgent.frontmatter.id ?? this.parsedAgent.frontmatter.name,
|
|
5283
5411
|
environment: this.environment,
|
|
5284
5412
|
workingDir: this.workingDir
|
|
5285
5413
|
}
|
|
@@ -5307,9 +5435,6 @@ Browser sessions (cookies, localStorage, login state) are automatically saved an
|
|
|
5307
5435
|
|
|
5308
5436
|
### Tabs and resources
|
|
5309
5437
|
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.` : "";
|
|
5310
|
-
const promptWithSkills = this.skillContextWindow ? `${systemPrompt}${developmentContext}
|
|
5311
|
-
|
|
5312
|
-
${this.skillContextWindow}${browserContext}` : `${systemPrompt}${developmentContext}${browserContext}`;
|
|
5313
5438
|
const mainMemory = await memoryPromise;
|
|
5314
5439
|
const boundedMainMemory = mainMemory && mainMemory.content.length > 4e3 ? `${mainMemory.content.slice(0, 4e3)}
|
|
5315
5440
|
...[truncated]` : mainMemory?.content;
|
|
@@ -5317,7 +5442,12 @@ ${this.skillContextWindow}${browserContext}` : `${systemPrompt}${developmentCont
|
|
|
5317
5442
|
## Persistent Memory
|
|
5318
5443
|
|
|
5319
5444
|
${boundedMainMemory.trim()}` : "";
|
|
5320
|
-
const
|
|
5445
|
+
const buildSystemPrompt = () => {
|
|
5446
|
+
const agentPrompt = renderCurrentAgentPrompt();
|
|
5447
|
+
const promptWithSkills = this.skillContextWindow ? `${agentPrompt}${developmentContext}
|
|
5448
|
+
|
|
5449
|
+
${this.skillContextWindow}${browserContext}` : `${agentPrompt}${developmentContext}${browserContext}`;
|
|
5450
|
+
return `${promptWithSkills}${memoryContext}
|
|
5321
5451
|
|
|
5322
5452
|
## Execution Integrity
|
|
5323
5453
|
|
|
@@ -5325,6 +5455,10 @@ ${boundedMainMemory.trim()}` : "";
|
|
|
5325
5455
|
- Do not fabricate "Tool Used" or "Tool Result" logs as plain text.
|
|
5326
5456
|
- Never output faux execution transcripts, markdown tool logs, or "Tool Used/Result" sections.
|
|
5327
5457
|
- If no suitable tool is available, explicitly say that and ask for guidance.`;
|
|
5458
|
+
};
|
|
5459
|
+
let integrityPrompt = buildSystemPrompt();
|
|
5460
|
+
let lastPromptFingerprint = `${this.agentFileFingerprint}
|
|
5461
|
+
${this.skillFingerprint}`;
|
|
5328
5462
|
const pushEvent = (event) => {
|
|
5329
5463
|
events.push(event);
|
|
5330
5464
|
return event;
|
|
@@ -5747,8 +5881,8 @@ ${textContent}` };
|
|
|
5747
5881
|
let timer;
|
|
5748
5882
|
nextPart = await Promise.race([
|
|
5749
5883
|
fullStreamIterator.next(),
|
|
5750
|
-
new Promise((
|
|
5751
|
-
timer = setTimeout(() =>
|
|
5884
|
+
new Promise((resolve11) => {
|
|
5885
|
+
timer = setTimeout(() => resolve11(null), timeout);
|
|
5752
5886
|
})
|
|
5753
5887
|
]);
|
|
5754
5888
|
clearTimeout(timer);
|
|
@@ -6056,6 +6190,19 @@ ${textContent}` };
|
|
|
6056
6190
|
content: JSON.stringify(toolResultsForModel),
|
|
6057
6191
|
metadata: toolMsgMeta
|
|
6058
6192
|
});
|
|
6193
|
+
if (this.environment === "development") {
|
|
6194
|
+
const agentChanged = await this.refreshAgentIfChanged();
|
|
6195
|
+
const skillsChanged = await this.refreshSkillsIfChanged(true);
|
|
6196
|
+
if (agentChanged || skillsChanged) {
|
|
6197
|
+
agent = this.parsedAgent;
|
|
6198
|
+
const currentFingerprint = `${this.agentFileFingerprint}
|
|
6199
|
+
${this.skillFingerprint}`;
|
|
6200
|
+
if (currentFingerprint !== lastPromptFingerprint) {
|
|
6201
|
+
integrityPrompt = buildSystemPrompt();
|
|
6202
|
+
lastPromptFingerprint = currentFingerprint;
|
|
6203
|
+
}
|
|
6204
|
+
}
|
|
6205
|
+
}
|
|
6059
6206
|
yield pushEvent({
|
|
6060
6207
|
type: "step:completed",
|
|
6061
6208
|
step,
|
|
@@ -6244,8 +6391,8 @@ var LatitudeCapture = class {
|
|
|
6244
6391
|
|
|
6245
6392
|
// src/state.ts
|
|
6246
6393
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
6247
|
-
import { mkdir as mkdir4, readFile as
|
|
6248
|
-
import { dirname as dirname4, resolve as
|
|
6394
|
+
import { mkdir as mkdir4, readFile as readFile8, readdir as readdir4, rename as rename2, rm as rm3, writeFile as writeFile5 } from "fs/promises";
|
|
6395
|
+
import { dirname as dirname4, resolve as resolve10 } from "path";
|
|
6249
6396
|
var DEFAULT_OWNER = "local-owner";
|
|
6250
6397
|
var LOCAL_STATE_FILE = "state.json";
|
|
6251
6398
|
var CONVERSATIONS_DIRECTORY = "conversations";
|
|
@@ -6438,8 +6585,8 @@ var FileConversationStore = class {
|
|
|
6438
6585
|
agentId: this.agentId
|
|
6439
6586
|
});
|
|
6440
6587
|
const agentDir = getAgentStoreDirectory(identity);
|
|
6441
|
-
const conversationsDir =
|
|
6442
|
-
const indexPath =
|
|
6588
|
+
const conversationsDir = resolve10(agentDir, CONVERSATIONS_DIRECTORY);
|
|
6589
|
+
const indexPath = resolve10(conversationsDir, LOCAL_CONVERSATION_INDEX_FILE);
|
|
6443
6590
|
this.paths = { conversationsDir, indexPath };
|
|
6444
6591
|
return this.paths;
|
|
6445
6592
|
}
|
|
@@ -6453,9 +6600,9 @@ var FileConversationStore = class {
|
|
|
6453
6600
|
}
|
|
6454
6601
|
async readConversationFile(fileName) {
|
|
6455
6602
|
const { conversationsDir } = await this.resolvePaths();
|
|
6456
|
-
const filePath =
|
|
6603
|
+
const filePath = resolve10(conversationsDir, fileName);
|
|
6457
6604
|
try {
|
|
6458
|
-
const raw = await
|
|
6605
|
+
const raw = await readFile8(filePath, "utf8");
|
|
6459
6606
|
return JSON.parse(raw);
|
|
6460
6607
|
} catch {
|
|
6461
6608
|
return void 0;
|
|
@@ -6501,7 +6648,7 @@ var FileConversationStore = class {
|
|
|
6501
6648
|
this.loaded = true;
|
|
6502
6649
|
const { indexPath } = await this.resolvePaths();
|
|
6503
6650
|
try {
|
|
6504
|
-
const raw = await
|
|
6651
|
+
const raw = await readFile8(indexPath, "utf8");
|
|
6505
6652
|
const parsed = JSON.parse(raw);
|
|
6506
6653
|
for (const conversation of parsed.conversations ?? []) {
|
|
6507
6654
|
this.conversations.set(conversation.conversationId, conversation);
|
|
@@ -6524,7 +6671,7 @@ var FileConversationStore = class {
|
|
|
6524
6671
|
const { conversationsDir } = await this.resolvePaths();
|
|
6525
6672
|
const existing = this.conversations.get(conversation.conversationId);
|
|
6526
6673
|
const fileName = existing?.fileName ?? this.resolveConversationFileName(conversation);
|
|
6527
|
-
const filePath =
|
|
6674
|
+
const filePath = resolve10(conversationsDir, fileName);
|
|
6528
6675
|
this.writing = this.writing.then(async () => {
|
|
6529
6676
|
await writeJsonAtomic2(filePath, conversation);
|
|
6530
6677
|
this.conversations.set(conversation.conversationId, {
|
|
@@ -6622,7 +6769,7 @@ var FileConversationStore = class {
|
|
|
6622
6769
|
if (removed) {
|
|
6623
6770
|
this.writing = this.writing.then(async () => {
|
|
6624
6771
|
if (existing) {
|
|
6625
|
-
await rm3(
|
|
6772
|
+
await rm3(resolve10(conversationsDir, existing.fileName), { force: true });
|
|
6626
6773
|
}
|
|
6627
6774
|
await this.writeIndex();
|
|
6628
6775
|
});
|
|
@@ -6652,7 +6799,7 @@ var FileStateStore = class {
|
|
|
6652
6799
|
workingDir: this.workingDir,
|
|
6653
6800
|
agentId: this.agentId
|
|
6654
6801
|
});
|
|
6655
|
-
this.filePath =
|
|
6802
|
+
this.filePath = resolve10(getAgentStoreDirectory(identity), LOCAL_STATE_FILE);
|
|
6656
6803
|
}
|
|
6657
6804
|
isExpired(state) {
|
|
6658
6805
|
return typeof this.ttlMs === "number" && Date.now() - state.updatedAt > this.ttlMs;
|
|
@@ -6664,7 +6811,7 @@ var FileStateStore = class {
|
|
|
6664
6811
|
}
|
|
6665
6812
|
this.loaded = true;
|
|
6666
6813
|
try {
|
|
6667
|
-
const raw = await
|
|
6814
|
+
const raw = await readFile8(this.filePath, "utf8");
|
|
6668
6815
|
const parsed = JSON.parse(raw);
|
|
6669
6816
|
for (const state of parsed.states ?? []) {
|
|
6670
6817
|
this.states.set(state.runId, state);
|
|
@@ -7412,6 +7559,7 @@ export {
|
|
|
7412
7559
|
createDefaultTools,
|
|
7413
7560
|
createDeleteDirectoryTool,
|
|
7414
7561
|
createDeleteTool,
|
|
7562
|
+
createEditTool,
|
|
7415
7563
|
createMemoryStore,
|
|
7416
7564
|
createMemoryTools,
|
|
7417
7565
|
createModelProvider,
|
package/package.json
CHANGED