@poncho-ai/harness 0.23.0 → 0.24.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 +3 -1
- package/dist/index.js +53 -2
- package/package.json +1 -1
- package/src/config.ts +1 -0
- package/src/default-tools.ts +53 -0
- package/src/harness.ts +7 -3
- package/test/harness.test.ts +64 -1
- package/.turbo/turbo-lint.log +0 -6
- package/.turbo/turbo-test.log +0 -135
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @poncho-ai/harness@0.
|
|
2
|
+
> @poncho-ai/harness@0.24.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[32m263.89 KB[39m
|
|
12
|
+
[32mESM[39m ⚡️ Build success in 136ms
|
|
13
13
|
[34mDTS[39m Build start
|
|
14
|
-
[32mDTS[39m ⚡️ Build success in
|
|
15
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[32m27.
|
|
14
|
+
[32mDTS[39m ⚡️ Build success in 6918ms
|
|
15
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m27.50 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @poncho-ai/harness
|
|
2
2
|
|
|
3
|
+
## 0.24.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`aee4f17`](https://github.com/cesr/poncho-ai/commit/aee4f17237d33b2cc134ed9934b709d967ca3f10) Thanks [@cesr](https://github.com/cesr)! - Add `edit_file` built-in tool with str_replace semantics for targeted file edits. The tool takes `path`, `old_str`, and `new_str` parameters, enforces uniqueness of the match, and is write-gated like `write_file` (disabled in production by default). Also improves browser SSE frame streaming with backpressure handling and auto-stops screencast when all listeners disconnect.
|
|
8
|
+
|
|
3
9
|
## 0.23.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -328,6 +328,7 @@ type BuiltInToolToggles = {
|
|
|
328
328
|
list_directory?: boolean;
|
|
329
329
|
read_file?: boolean;
|
|
330
330
|
write_file?: boolean;
|
|
331
|
+
edit_file?: boolean;
|
|
331
332
|
delete_file?: boolean;
|
|
332
333
|
delete_directory?: boolean;
|
|
333
334
|
};
|
|
@@ -434,6 +435,7 @@ declare const loadPonchoConfig: (workingDir: string) => Promise<PonchoConfig | u
|
|
|
434
435
|
|
|
435
436
|
declare const createDefaultTools: (workingDir: string) => ToolDefinition[];
|
|
436
437
|
declare const createWriteTool: (workingDir: string) => ToolDefinition;
|
|
438
|
+
declare const createEditTool: (workingDir: string) => ToolDefinition;
|
|
437
439
|
declare const createDeleteTool: (workingDir: string) => ToolDefinition;
|
|
438
440
|
declare const createDeleteDirectoryTool: (workingDir: string) => ToolDefinition;
|
|
439
441
|
declare const ponchoDocsTool: ToolDefinition;
|
|
@@ -762,4 +764,4 @@ declare class TelemetryEmitter {
|
|
|
762
764
|
|
|
763
765
|
declare const createSubagentTools: (manager: SubagentManager, getConversationId: () => string | undefined, getOwnerId: () => string) => ToolDefinition[];
|
|
764
766
|
|
|
765
|
-
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 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 SubagentSummary, type TelemetryConfig, TelemetryEmitter, type ToolAccess, type ToolCall, ToolDispatcher, type ToolExecutionResult, type UploadStore, type UploadsConfig, VercelBlobUploadStore, buildAgentDirectoryName, buildSkillContextWindow, compactMessages, createConversationStore, createDefaultTools, createDeleteDirectoryTool, createDeleteTool, 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 };
|
|
767
|
+
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 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 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 };
|
package/dist/index.js
CHANGED
|
@@ -1908,6 +1908,52 @@ var createWriteTool = (workingDir) => defineTool({
|
|
|
1908
1908
|
return { path, written: true };
|
|
1909
1909
|
}
|
|
1910
1910
|
});
|
|
1911
|
+
var createEditTool = (workingDir) => defineTool({
|
|
1912
|
+
name: "edit_file",
|
|
1913
|
+
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.",
|
|
1914
|
+
inputSchema: {
|
|
1915
|
+
type: "object",
|
|
1916
|
+
properties: {
|
|
1917
|
+
path: {
|
|
1918
|
+
type: "string",
|
|
1919
|
+
description: "File path relative to working directory"
|
|
1920
|
+
},
|
|
1921
|
+
old_str: {
|
|
1922
|
+
type: "string",
|
|
1923
|
+
description: "The exact text to find and replace (must be unique in the file). Include surrounding context lines if needed to ensure uniqueness."
|
|
1924
|
+
},
|
|
1925
|
+
new_str: {
|
|
1926
|
+
type: "string",
|
|
1927
|
+
description: "The replacement text (use empty string to delete the matched content)"
|
|
1928
|
+
}
|
|
1929
|
+
},
|
|
1930
|
+
required: ["path", "old_str", "new_str"],
|
|
1931
|
+
additionalProperties: false
|
|
1932
|
+
},
|
|
1933
|
+
handler: async (input) => {
|
|
1934
|
+
const path = typeof input.path === "string" ? input.path : "";
|
|
1935
|
+
const oldStr = typeof input.old_str === "string" ? input.old_str : "";
|
|
1936
|
+
const newStr = typeof input.new_str === "string" ? input.new_str : "";
|
|
1937
|
+
if (!oldStr) throw new Error("old_str must not be empty.");
|
|
1938
|
+
const resolved = resolveSafePath(workingDir, path);
|
|
1939
|
+
const content = await readFile3(resolved, "utf8");
|
|
1940
|
+
const first = content.indexOf(oldStr);
|
|
1941
|
+
if (first === -1) {
|
|
1942
|
+
throw new Error(
|
|
1943
|
+
"old_str not found in file. Make sure it matches exactly, including whitespace and line breaks."
|
|
1944
|
+
);
|
|
1945
|
+
}
|
|
1946
|
+
const last = content.lastIndexOf(oldStr);
|
|
1947
|
+
if (first !== last) {
|
|
1948
|
+
throw new Error(
|
|
1949
|
+
"old_str appears multiple times in the file. Please provide more context to ensure a unique match."
|
|
1950
|
+
);
|
|
1951
|
+
}
|
|
1952
|
+
const newContent = content.slice(0, first) + newStr + content.slice(first + oldStr.length);
|
|
1953
|
+
await writeFile2(resolved, newContent, "utf8");
|
|
1954
|
+
return { path, edited: true };
|
|
1955
|
+
}
|
|
1956
|
+
});
|
|
1911
1957
|
var createDeleteTool = (workingDir) => defineTool({
|
|
1912
1958
|
name: "delete_file",
|
|
1913
1959
|
description: "Delete a file at a path inside the working directory",
|
|
@@ -4327,7 +4373,7 @@ You are running locally in development mode. Treat this as an editable agent wor
|
|
|
4327
4373
|
## Understanding Your Environment
|
|
4328
4374
|
|
|
4329
4375
|
- Built-in tools: \`list_directory\` and \`read_file\`
|
|
4330
|
-
- \`write_file\`
|
|
4376
|
+
- \`write_file\` and \`edit_file\` are available in development (disabled by default in production)
|
|
4331
4377
|
- A starter local skill is included (\`starter-echo\`)
|
|
4332
4378
|
- Bash/shell commands are **not** available unless you install and enable a shell tool/skill
|
|
4333
4379
|
- Git operations are only available if a git-capable tool/skill is configured
|
|
@@ -4588,6 +4634,7 @@ Since all fields have defaults, you only need to specify \`*Env\` when your env
|
|
|
4588
4634
|
- If shell/CLI access is unavailable, ask the user to run needed commands and provide exact copy-paste commands.
|
|
4589
4635
|
- For setup, skills, MCP, auth, storage, telemetry, or "how do I..." questions, proactively read \`README.md\` with \`read_file\` before answering.
|
|
4590
4636
|
- Prefer quoting concrete commands and examples from \`README.md\` over guessing.
|
|
4637
|
+
- 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
4638
|
- Keep edits minimal, preserve unrelated settings/code, and summarize what changed.
|
|
4592
4639
|
|
|
4593
4640
|
## Detailed Documentation
|
|
@@ -4658,7 +4705,7 @@ var AgentHarness = class _AgentHarness {
|
|
|
4658
4705
|
isToolEnabled(name) {
|
|
4659
4706
|
const access3 = this.resolveToolAccess(name);
|
|
4660
4707
|
if (access3 === false) return false;
|
|
4661
|
-
if (name === "write_file" || name === "delete_file" || name === "delete_directory") {
|
|
4708
|
+
if (name === "write_file" || name === "edit_file" || name === "delete_file" || name === "delete_directory") {
|
|
4662
4709
|
return this.shouldEnableWriteTool();
|
|
4663
4710
|
}
|
|
4664
4711
|
return true;
|
|
@@ -4701,6 +4748,9 @@ var AgentHarness = class _AgentHarness {
|
|
|
4701
4748
|
if (this.isToolEnabled("write_file")) {
|
|
4702
4749
|
this.registerIfMissing(createWriteTool(this.workingDir));
|
|
4703
4750
|
}
|
|
4751
|
+
if (this.isToolEnabled("edit_file")) {
|
|
4752
|
+
this.registerIfMissing(createEditTool(this.workingDir));
|
|
4753
|
+
}
|
|
4704
4754
|
if (this.isToolEnabled("delete_file")) {
|
|
4705
4755
|
this.registerIfMissing(createDeleteTool(this.workingDir));
|
|
4706
4756
|
}
|
|
@@ -7412,6 +7462,7 @@ export {
|
|
|
7412
7462
|
createDefaultTools,
|
|
7413
7463
|
createDeleteDirectoryTool,
|
|
7414
7464
|
createDeleteTool,
|
|
7465
|
+
createEditTool,
|
|
7415
7466
|
createMemoryStore,
|
|
7416
7467
|
createMemoryTools,
|
|
7417
7468
|
createModelProvider,
|
package/package.json
CHANGED
package/src/config.ts
CHANGED
package/src/default-tools.ts
CHANGED
|
@@ -89,6 +89,59 @@ export const createWriteTool = (workingDir: string): ToolDefinition =>
|
|
|
89
89
|
},
|
|
90
90
|
});
|
|
91
91
|
|
|
92
|
+
export const createEditTool = (workingDir: string): ToolDefinition =>
|
|
93
|
+
defineTool({
|
|
94
|
+
name: "edit_file",
|
|
95
|
+
description:
|
|
96
|
+
"Edit a file by replacing an exact string match with new content. " +
|
|
97
|
+
"The old_str must match exactly one location in the file (including whitespace and indentation). " +
|
|
98
|
+
"Use an empty new_str to delete matched content.",
|
|
99
|
+
inputSchema: {
|
|
100
|
+
type: "object",
|
|
101
|
+
properties: {
|
|
102
|
+
path: {
|
|
103
|
+
type: "string",
|
|
104
|
+
description: "File path relative to working directory",
|
|
105
|
+
},
|
|
106
|
+
old_str: {
|
|
107
|
+
type: "string",
|
|
108
|
+
description:
|
|
109
|
+
"The exact text to find and replace (must be unique in the file). " +
|
|
110
|
+
"Include surrounding context lines if needed to ensure uniqueness.",
|
|
111
|
+
},
|
|
112
|
+
new_str: {
|
|
113
|
+
type: "string",
|
|
114
|
+
description: "The replacement text (use empty string to delete the matched content)",
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
required: ["path", "old_str", "new_str"],
|
|
118
|
+
additionalProperties: false,
|
|
119
|
+
},
|
|
120
|
+
handler: async (input) => {
|
|
121
|
+
const path = typeof input.path === "string" ? input.path : "";
|
|
122
|
+
const oldStr = typeof input.old_str === "string" ? input.old_str : "";
|
|
123
|
+
const newStr = typeof input.new_str === "string" ? input.new_str : "";
|
|
124
|
+
if (!oldStr) throw new Error("old_str must not be empty.");
|
|
125
|
+
const resolved = resolveSafePath(workingDir, path);
|
|
126
|
+
const content = await readFile(resolved, "utf8");
|
|
127
|
+
const first = content.indexOf(oldStr);
|
|
128
|
+
if (first === -1) {
|
|
129
|
+
throw new Error(
|
|
130
|
+
"old_str not found in file. Make sure it matches exactly, including whitespace and line breaks.",
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
const last = content.lastIndexOf(oldStr);
|
|
134
|
+
if (first !== last) {
|
|
135
|
+
throw new Error(
|
|
136
|
+
"old_str appears multiple times in the file. Please provide more context to ensure a unique match.",
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
const newContent = content.slice(0, first) + newStr + content.slice(first + oldStr.length);
|
|
140
|
+
await writeFile(resolved, newContent, "utf8");
|
|
141
|
+
return { path, edited: true };
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
|
|
92
145
|
export const createDeleteTool = (workingDir: string): ToolDefinition =>
|
|
93
146
|
defineTool({
|
|
94
147
|
name: "delete_file",
|
package/src/harness.ts
CHANGED
|
@@ -15,7 +15,7 @@ import type { UploadStore } from "./upload-store.js";
|
|
|
15
15
|
import { PONCHO_UPLOAD_SCHEME, deriveUploadKey } from "./upload-store.js";
|
|
16
16
|
import { parseAgentFile, renderAgentPrompt, type ParsedAgent, type AgentFrontmatter } from "./agent-parser.js";
|
|
17
17
|
import { loadPonchoConfig, resolveMemoryConfig, type PonchoConfig, type ToolAccess, type BuiltInToolToggles } from "./config.js";
|
|
18
|
-
import { createDefaultTools, createDeleteDirectoryTool, createDeleteTool, createWriteTool, ponchoDocsTool } from "./default-tools.js";
|
|
18
|
+
import { createDefaultTools, createDeleteDirectoryTool, createDeleteTool, createEditTool, createWriteTool, ponchoDocsTool } from "./default-tools.js";
|
|
19
19
|
import {
|
|
20
20
|
createMemoryStore,
|
|
21
21
|
createMemoryTools,
|
|
@@ -224,7 +224,7 @@ You are running locally in development mode. Treat this as an editable agent wor
|
|
|
224
224
|
## Understanding Your Environment
|
|
225
225
|
|
|
226
226
|
- Built-in tools: \`list_directory\` and \`read_file\`
|
|
227
|
-
- \`write_file\`
|
|
227
|
+
- \`write_file\` and \`edit_file\` are available in development (disabled by default in production)
|
|
228
228
|
- A starter local skill is included (\`starter-echo\`)
|
|
229
229
|
- Bash/shell commands are **not** available unless you install and enable a shell tool/skill
|
|
230
230
|
- Git operations are only available if a git-capable tool/skill is configured
|
|
@@ -485,6 +485,7 @@ Since all fields have defaults, you only need to specify \`*Env\` when your env
|
|
|
485
485
|
- If shell/CLI access is unavailable, ask the user to run needed commands and provide exact copy-paste commands.
|
|
486
486
|
- For setup, skills, MCP, auth, storage, telemetry, or "how do I..." questions, proactively read \`README.md\` with \`read_file\` before answering.
|
|
487
487
|
- Prefer quoting concrete commands and examples from \`README.md\` over guessing.
|
|
488
|
+
- Prefer \`edit_file\` for targeted changes to existing files (uses exact string matching); use \`write_file\` only for creating new files or full rewrites.
|
|
488
489
|
- Keep edits minimal, preserve unrelated settings/code, and summarize what changed.
|
|
489
490
|
|
|
490
491
|
## Detailed Documentation
|
|
@@ -585,7 +586,7 @@ export class AgentHarness {
|
|
|
585
586
|
private isToolEnabled(name: string): boolean {
|
|
586
587
|
const access = this.resolveToolAccess(name);
|
|
587
588
|
if (access === false) return false;
|
|
588
|
-
if (name === "write_file" || name === "delete_file" || name === "delete_directory") {
|
|
589
|
+
if (name === "write_file" || name === "edit_file" || name === "delete_file" || name === "delete_directory") {
|
|
589
590
|
return this.shouldEnableWriteTool();
|
|
590
591
|
}
|
|
591
592
|
return true;
|
|
@@ -633,6 +634,9 @@ export class AgentHarness {
|
|
|
633
634
|
if (this.isToolEnabled("write_file")) {
|
|
634
635
|
this.registerIfMissing(createWriteTool(this.workingDir));
|
|
635
636
|
}
|
|
637
|
+
if (this.isToolEnabled("edit_file")) {
|
|
638
|
+
this.registerIfMissing(createEditTool(this.workingDir));
|
|
639
|
+
}
|
|
636
640
|
if (this.isToolEnabled("delete_file")) {
|
|
637
641
|
this.registerIfMissing(createDeleteTool(this.workingDir));
|
|
638
642
|
}
|
package/test/harness.test.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
1
|
+
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
2
|
import { createServer } from "node:http";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "path";
|
|
5
5
|
import { describe, expect, it } from "vitest";
|
|
6
6
|
import type { ToolContext } from "@poncho-ai/sdk";
|
|
7
7
|
import { AgentHarness } from "../src/harness.js";
|
|
8
|
+
import { createEditTool } from "../src/default-tools.js";
|
|
8
9
|
import { loadSkillMetadata } from "../src/skill-context.js";
|
|
9
10
|
|
|
10
11
|
const stubContext: ToolContext = {
|
|
@@ -39,6 +40,7 @@ model:
|
|
|
39
40
|
expect(names).toContain("list_directory");
|
|
40
41
|
expect(names).toContain("read_file");
|
|
41
42
|
expect(names).toContain("write_file");
|
|
43
|
+
expect(names).toContain("edit_file");
|
|
42
44
|
});
|
|
43
45
|
|
|
44
46
|
it("disables write_file by default in production environment", async () => {
|
|
@@ -64,6 +66,7 @@ model:
|
|
|
64
66
|
expect(names).toContain("list_directory");
|
|
65
67
|
expect(names).toContain("read_file");
|
|
66
68
|
expect(names).not.toContain("write_file");
|
|
69
|
+
expect(names).not.toContain("edit_file");
|
|
67
70
|
});
|
|
68
71
|
|
|
69
72
|
it("allows disabling built-in tools via poncho.config.js", async () => {
|
|
@@ -1230,3 +1233,63 @@ allowed-tools:
|
|
|
1230
1233
|
});
|
|
1231
1234
|
|
|
1232
1235
|
});
|
|
1236
|
+
|
|
1237
|
+
describe("edit_file tool", () => {
|
|
1238
|
+
it("replaces a unique string match in a file", async () => {
|
|
1239
|
+
const dir = await mkdtemp(join(tmpdir(), "poncho-edit-tool-"));
|
|
1240
|
+
const filePath = join(dir, "test.txt");
|
|
1241
|
+
await writeFile(filePath, "hello world\nfoo bar\nbaz qux\n", "utf8");
|
|
1242
|
+
|
|
1243
|
+
const tool = createEditTool(dir);
|
|
1244
|
+
const result = await tool.handler(
|
|
1245
|
+
{ path: "test.txt", old_str: "foo bar", new_str: "replaced" },
|
|
1246
|
+
stubContext,
|
|
1247
|
+
);
|
|
1248
|
+
|
|
1249
|
+
expect(result).toEqual({ path: "test.txt", edited: true });
|
|
1250
|
+
const content = await readFile(filePath, "utf8");
|
|
1251
|
+
expect(content).toBe("hello world\nreplaced\nbaz qux\n");
|
|
1252
|
+
});
|
|
1253
|
+
|
|
1254
|
+
it("errors when old_str is not found in the file", async () => {
|
|
1255
|
+
const dir = await mkdtemp(join(tmpdir(), "poncho-edit-tool-notfound-"));
|
|
1256
|
+
await writeFile(join(dir, "test.txt"), "hello world\n", "utf8");
|
|
1257
|
+
|
|
1258
|
+
const tool = createEditTool(dir);
|
|
1259
|
+
await expect(
|
|
1260
|
+
tool.handler({ path: "test.txt", old_str: "nonexistent", new_str: "x" }, stubContext),
|
|
1261
|
+
).rejects.toThrow("old_str not found in file");
|
|
1262
|
+
});
|
|
1263
|
+
|
|
1264
|
+
it("errors when old_str matches multiple locations", async () => {
|
|
1265
|
+
const dir = await mkdtemp(join(tmpdir(), "poncho-edit-tool-multi-"));
|
|
1266
|
+
await writeFile(join(dir, "test.txt"), "aaa\nbbb\naaa\n", "utf8");
|
|
1267
|
+
|
|
1268
|
+
const tool = createEditTool(dir);
|
|
1269
|
+
await expect(
|
|
1270
|
+
tool.handler({ path: "test.txt", old_str: "aaa", new_str: "ccc" }, stubContext),
|
|
1271
|
+
).rejects.toThrow("old_str appears multiple times");
|
|
1272
|
+
});
|
|
1273
|
+
|
|
1274
|
+
it("deletes matched content when new_str is empty", async () => {
|
|
1275
|
+
const dir = await mkdtemp(join(tmpdir(), "poncho-edit-tool-delete-"));
|
|
1276
|
+
const filePath = join(dir, "test.txt");
|
|
1277
|
+
await writeFile(filePath, "keep this\nremove this\nkeep this too\n", "utf8");
|
|
1278
|
+
|
|
1279
|
+
const tool = createEditTool(dir);
|
|
1280
|
+
await tool.handler({ path: "test.txt", old_str: "remove this\n", new_str: "" }, stubContext);
|
|
1281
|
+
|
|
1282
|
+
const content = await readFile(filePath, "utf8");
|
|
1283
|
+
expect(content).toBe("keep this\nkeep this too\n");
|
|
1284
|
+
});
|
|
1285
|
+
|
|
1286
|
+
it("errors when old_str is empty", async () => {
|
|
1287
|
+
const dir = await mkdtemp(join(tmpdir(), "poncho-edit-tool-empty-"));
|
|
1288
|
+
await writeFile(join(dir, "test.txt"), "content\n", "utf8");
|
|
1289
|
+
|
|
1290
|
+
const tool = createEditTool(dir);
|
|
1291
|
+
await expect(
|
|
1292
|
+
tool.handler({ path: "test.txt", old_str: "", new_str: "x" }, stubContext),
|
|
1293
|
+
).rejects.toThrow("old_str must not be empty");
|
|
1294
|
+
});
|
|
1295
|
+
});
|
package/.turbo/turbo-lint.log
DELETED
package/.turbo/turbo-test.log
DELETED
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
> @poncho-ai/harness@0.16.1 test /Users/cesar/Dev/latitude/poncho-ai/packages/harness
|
|
3
|
-
> vitest
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
[7m[1m[36m RUN [39m[22m[27m [36mv1.6.1[39m [90m/Users/cesar/Dev/latitude/poncho-ai/packages/harness[39m
|
|
7
|
-
|
|
8
|
-
[32m✓[39m test/telemetry.test.ts [2m ([22m[2m3 tests[22m[2m)[22m[90m 2[2mms[22m[39m
|
|
9
|
-
[event] step:completed {"type":"step:completed","step":1,"duration":1}
|
|
10
|
-
[event] step:started {"type":"step:started","step":2}
|
|
11
|
-
[32m✓[39m test/schema-converter.test.ts [2m ([22m[2m27 tests[22m[2m)[22m[90m 19[2mms[22m[39m
|
|
12
|
-
[90mstdout[2m | test/mcp.test.ts[2m > [22m[2mmcp bridge protocol transports[2m > [22m[2mdiscovers and calls tools over streamable HTTP[22m[39m
|
|
13
|
-
[poncho][mcp] {"event":"catalog.loaded","server":"remote","discoveredCount":1}
|
|
14
|
-
[poncho][mcp] {"event":"tools.selected","requestedPatternCount":1,"registeredCount":1,"filteredByPolicyCount":0,"filteredByIntentCount":0}
|
|
15
|
-
|
|
16
|
-
[90mstdout[2m | test/mcp.test.ts[2m > [22m[2mmcp bridge protocol transports[2m > [22m[2mselects discovered tools by requested patterns[22m[39m
|
|
17
|
-
[poncho][mcp] {"event":"catalog.loaded","server":"remote","discoveredCount":2}
|
|
18
|
-
[poncho][mcp] {"event":"tools.selected","requestedPatternCount":1,"registeredCount":1,"filteredByPolicyCount":0,"filteredByIntentCount":1}
|
|
19
|
-
[poncho][mcp] {"event":"tools.selected","requestedPatternCount":1,"registeredCount":2,"filteredByPolicyCount":0,"filteredByIntentCount":0}
|
|
20
|
-
|
|
21
|
-
[32m✓[39m test/agent-parser.test.ts [2m ([22m[2m10 tests[22m[2m)[22m[90m 24[2mms[22m[39m
|
|
22
|
-
[90mstdout[2m | test/mcp.test.ts[2m > [22m[2mmcp bridge protocol transports[2m > [22m[2mskips discovery when bearer token env value is missing[22m[39m
|
|
23
|
-
[poncho][mcp] {"event":"tools.selected","requestedPatternCount":1,"registeredCount":0,"filteredByPolicyCount":0,"filteredByIntentCount":0}
|
|
24
|
-
|
|
25
|
-
[90mstderr[2m | test/mcp.test.ts[2m > [22m[2mmcp bridge protocol transports[2m > [22m[2mskips discovery when bearer token env value is missing[22m[39m
|
|
26
|
-
[poncho][mcp] {"event":"auth.token_missing","server":"remote","tokenEnv":"MISSING_TOKEN_ENV"}
|
|
27
|
-
|
|
28
|
-
[90mstdout[2m | test/mcp.test.ts[2m > [22m[2mmcp bridge protocol transports[2m > [22m[2mreturns actionable errors for 403 permission failures[22m[39m
|
|
29
|
-
[poncho][mcp] {"event":"catalog.loaded","server":"remote","discoveredCount":1}
|
|
30
|
-
[poncho][mcp] {"event":"tools.selected","requestedPatternCount":1,"registeredCount":1,"filteredByPolicyCount":0,"filteredByIntentCount":0}
|
|
31
|
-
|
|
32
|
-
[32m✓[39m test/mcp.test.ts [2m ([22m[2m6 tests[22m[2m)[22m[90m 81[2mms[22m[39m
|
|
33
|
-
[32m✓[39m test/memory.test.ts [2m ([22m[2m4 tests[22m[2m)[22m[90m 56[2mms[22m[39m
|
|
34
|
-
[32m✓[39m test/state.test.ts [2m ([22m[2m5 tests[22m[2m)[22m[90m 237[2mms[22m[39m
|
|
35
|
-
[32m✓[39m test/model-factory.test.ts [2m ([22m[2m4 tests[22m[2m)[22m[90m 2[2mms[22m[39m
|
|
36
|
-
[32m✓[39m test/agent-identity.test.ts [2m ([22m[2m2 tests[22m[2m)[22m[90m 43[2mms[22m[39m
|
|
37
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mregisters default filesystem tools[22m[39m
|
|
38
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
39
|
-
|
|
40
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mdisables write_file by default in production environment[22m[39m
|
|
41
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
42
|
-
|
|
43
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mallows disabling built-in tools via poncho.config.js[22m[39m
|
|
44
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
45
|
-
|
|
46
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2msupports per-environment tool overrides[22m[39m
|
|
47
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
48
|
-
|
|
49
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2msupports per-environment tool overrides[22m[39m
|
|
50
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
51
|
-
|
|
52
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mdoes not auto-register exported tool objects from skill scripts[22m[39m
|
|
53
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
54
|
-
|
|
55
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mrefreshes skill metadata and tools in development mode[22m[39m
|
|
56
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
57
|
-
|
|
58
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mrefreshes skill metadata and tools in development mode[22m[39m
|
|
59
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"skills:changed","requestedPatterns":[]}
|
|
60
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"activate:beta","requestedPatterns":[]}
|
|
61
|
-
|
|
62
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mprunes removed active skills after refresh in development mode[22m[39m
|
|
63
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
64
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"activate:obsolete","requestedPatterns":[]}
|
|
65
|
-
|
|
66
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mprunes removed active skills after refresh in development mode[22m[39m
|
|
67
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"skills:changed","requestedPatterns":[]}
|
|
68
|
-
|
|
69
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mdoes not refresh skills outside development mode[22m[39m
|
|
70
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
71
|
-
|
|
72
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mclears active skills when skill metadata changes in development mode[22m[39m
|
|
73
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
74
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"activate:alpha","requestedPatterns":[]}
|
|
75
|
-
|
|
76
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mclears active skills when skill metadata changes in development mode[22m[39m
|
|
77
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"skills:changed","requestedPatterns":[]}
|
|
78
|
-
|
|
79
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mlists skill scripts through list_skill_scripts[22m[39m
|
|
80
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
81
|
-
|
|
82
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mruns JavaScript/TypeScript skill scripts through run_skill_script[22m[39m
|
|
83
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
84
|
-
|
|
85
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mruns AGENT-scope scripts from root scripts directory[22m[39m
|
|
86
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
87
|
-
|
|
88
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mblocks path traversal in run_skill_script[22m[39m
|
|
89
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
90
|
-
|
|
91
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mrequires allowed-tools entries for non-standard script directories[22m[39m
|
|
92
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
93
|
-
|
|
94
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mregisters MCP tools dynamically for stacked active skills and supports deactivation[22m[39m
|
|
95
|
-
[poncho][mcp] {"event":"catalog.loaded","server":"remote","discoveredCount":2}
|
|
96
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
97
|
-
[poncho][mcp] {"event":"tools.selected","requestedPatternCount":1,"registeredCount":1,"filteredByPolicyCount":0,"filteredByIntentCount":1}
|
|
98
|
-
[poncho][mcp] {"event":"tools.refreshed","reason":"activate:skill-a","requestedPatterns":["remote/a"],"registeredCount":1,"activeSkills":["skill-a"]}
|
|
99
|
-
[poncho][mcp] {"event":"tools.selected","requestedPatternCount":2,"registeredCount":2,"filteredByPolicyCount":0,"filteredByIntentCount":0}
|
|
100
|
-
[poncho][mcp] {"event":"tools.refreshed","reason":"activate:skill-b","requestedPatterns":["remote/a","remote/b"],"registeredCount":2,"activeSkills":["skill-a","skill-b"]}
|
|
101
|
-
[poncho][mcp] {"event":"tools.selected","requestedPatternCount":1,"registeredCount":1,"filteredByPolicyCount":0,"filteredByIntentCount":1}
|
|
102
|
-
[poncho][mcp] {"event":"tools.refreshed","reason":"deactivate:skill-a","requestedPatterns":["remote/b"],"registeredCount":1,"activeSkills":["skill-b"]}
|
|
103
|
-
|
|
104
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2msupports flat tool access config format[22m[39m
|
|
105
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
106
|
-
|
|
107
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mflat tool access takes priority over legacy defaults[22m[39m
|
|
108
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
109
|
-
|
|
110
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mbyEnvironment overrides flat tool access[22m[39m
|
|
111
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
112
|
-
|
|
113
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mregisterTools skips tools disabled via config[22m[39m
|
|
114
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
115
|
-
|
|
116
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mapproval access level registers the tool but marks it for approval[22m[39m
|
|
117
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
118
|
-
|
|
119
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mtools without approval config do not require approval[22m[39m
|
|
120
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
121
|
-
|
|
122
|
-
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mallows in-flight MCP calls to finish after skill deactivation[22m[39m
|
|
123
|
-
[poncho][mcp] {"event":"catalog.loaded","server":"remote","discoveredCount":1}
|
|
124
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
125
|
-
[poncho][mcp] {"event":"tools.selected","requestedPatternCount":1,"registeredCount":1,"filteredByPolicyCount":0,"filteredByIntentCount":0}
|
|
126
|
-
[poncho][mcp] {"event":"tools.refreshed","reason":"activate:skill-slow","requestedPatterns":["remote/slow"],"registeredCount":1,"activeSkills":["skill-slow"]}
|
|
127
|
-
[poncho][mcp] {"event":"tools.cleared","reason":"deactivate:skill-slow","requestedPatterns":[]}
|
|
128
|
-
|
|
129
|
-
[32m✓[39m test/harness.test.ts [2m ([22m[2m25 tests[22m[2m)[22m[90m 291[2mms[22m[39m
|
|
130
|
-
|
|
131
|
-
[2m Test Files [22m [1m[32m9 passed[39m[22m[90m (9)[39m
|
|
132
|
-
[2m Tests [22m [1m[32m86 passed[39m[22m[90m (86)[39m
|
|
133
|
-
[2m Start at [22m 17:47:43
|
|
134
|
-
[2m Duration [22m 1.88s[2m (transform 684ms, setup 1ms, collect 2.34s, tests 755ms, environment 2ms, prepare 1.27s)[22m
|
|
135
|
-
|