@poncho-ai/harness 0.18.0 → 0.19.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 +20 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.js +117 -47
- package/package.json +2 -2
- package/src/config.ts +3 -0
- package/src/default-tools.ts +51 -1
- package/src/harness.ts +68 -42
- package/src/state.ts +1 -0
- package/src/upload-store.ts +21 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @poncho-ai/harness@0.
|
|
2
|
+
> @poncho-ai/harness@0.19.1 build /home/runner/work/poncho-ai/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[32m199.42 KB[39m
|
|
11
|
+
[32mESM[39m ⚡️ Build success in 125ms
|
|
12
12
|
[34mDTS[39m Build start
|
|
13
|
-
[32mDTS[39m ⚡️ Build success in
|
|
14
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[32m24.
|
|
13
|
+
[32mDTS[39m ⚡️ Build success in 7505ms
|
|
14
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m24.33 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @poncho-ai/harness
|
|
2
2
|
|
|
3
|
+
## 0.19.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`470563b`](https://github.com/cesr/poncho-ai/commit/470563b96bbb5d2c6358a1c89dd3b52beb7799c8) Thanks [@cesr](https://github.com/cesr)! - Fix LocalUploadStore ENOENT on Vercel: use /tmp for uploads on serverless environments instead of the read-only working directory.
|
|
8
|
+
|
|
9
|
+
## 0.19.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- [`075b9ac`](https://github.com/cesr/poncho-ai/commit/075b9ac3556847af913bf2b58f030575c3b99852) Thanks [@cesr](https://github.com/cesr)! - Batch tool approvals, fix serverless session persistence and adapter init
|
|
14
|
+
- Batch tool approvals: all approval-requiring tool calls in a single step are now collected and presented together instead of one at a time.
|
|
15
|
+
- Fix messaging adapter route registration: routes are only registered after successful initialization, preventing "Adapter not initialised" errors on Vercel.
|
|
16
|
+
- Add stateless signed-cookie sessions so web UI auth survives serverless cold starts.
|
|
17
|
+
|
|
18
|
+
### Patch Changes
|
|
19
|
+
|
|
20
|
+
- Updated dependencies [[`075b9ac`](https://github.com/cesr/poncho-ai/commit/075b9ac3556847af913bf2b58f030575c3b99852)]:
|
|
21
|
+
- @poncho-ai/sdk@1.4.0
|
|
22
|
+
|
|
3
23
|
## 0.18.0
|
|
4
24
|
|
|
5
25
|
### Minor Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -94,6 +94,7 @@ interface Conversation {
|
|
|
94
94
|
name: string;
|
|
95
95
|
input: Record<string, unknown>;
|
|
96
96
|
}>;
|
|
97
|
+
decision?: "approved" | "denied";
|
|
97
98
|
}>;
|
|
98
99
|
ownerId: string;
|
|
99
100
|
tenantId: string | null;
|
|
@@ -268,6 +269,8 @@ type BuiltInToolToggles = {
|
|
|
268
269
|
list_directory?: boolean;
|
|
269
270
|
read_file?: boolean;
|
|
270
271
|
write_file?: boolean;
|
|
272
|
+
delete_file?: boolean;
|
|
273
|
+
delete_directory?: boolean;
|
|
271
274
|
};
|
|
272
275
|
interface MessagingChannelConfig {
|
|
273
276
|
platform: "slack" | "resend";
|
|
@@ -276,6 +279,7 @@ interface MessagingChannelConfig {
|
|
|
276
279
|
apiKeyEnv?: string;
|
|
277
280
|
webhookSecretEnv?: string;
|
|
278
281
|
fromEnv?: string;
|
|
282
|
+
replyToEnv?: string;
|
|
279
283
|
allowedSenders?: string[];
|
|
280
284
|
mode?: "auto-reply" | "tool";
|
|
281
285
|
allowedRecipients?: string[];
|
|
@@ -359,6 +363,8 @@ declare const loadPonchoConfig: (workingDir: string) => Promise<PonchoConfig | u
|
|
|
359
363
|
|
|
360
364
|
declare const createDefaultTools: (workingDir: string) => ToolDefinition[];
|
|
361
365
|
declare const createWriteTool: (workingDir: string) => ToolDefinition;
|
|
366
|
+
declare const createDeleteTool: (workingDir: string) => ToolDefinition;
|
|
367
|
+
declare const createDeleteDirectoryTool: (workingDir: string) => ToolDefinition;
|
|
362
368
|
|
|
363
369
|
declare const PONCHO_UPLOAD_SCHEME = "poncho-upload://";
|
|
364
370
|
interface UploadStore {
|
|
@@ -681,4 +687,4 @@ declare class TelemetryEmitter {
|
|
|
681
687
|
|
|
682
688
|
declare const createSubagentTools: (manager: SubagentManager, getConversationId: () => string | undefined, getOwnerId: () => string) => ToolDefinition[];
|
|
683
689
|
|
|
684
|
-
export { type AgentFrontmatter, AgentHarness, type AgentIdentity, type AgentLimitsConfig, type AgentModelConfig, type BuiltInToolToggles, 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, createConversationStore, createDefaultTools, createMemoryStore, createMemoryTools, createModelProvider, createSkillTools, createStateStore, createSubagentTools, createUploadStore, createWriteTool, deriveUploadKey, ensureAgentIdentity, generateAgentId, getAgentStoreDirectory, getModelContextWindow, getPonchoStoreRoot, jsonSchemaToZod, loadPonchoConfig, loadSkillContext, loadSkillInstructions, loadSkillMetadata, normalizeScriptPolicyPath, parseAgentFile, parseAgentMarkdown, readSkillResource, renderAgentPrompt, resolveAgentIdentity, resolveMemoryConfig, resolveSkillDirs, resolveStateConfig, slugifyStorageComponent };
|
|
690
|
+
export { type AgentFrontmatter, AgentHarness, type AgentIdentity, type AgentLimitsConfig, type AgentModelConfig, type BuiltInToolToggles, 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, createConversationStore, createDefaultTools, createDeleteDirectoryTool, createDeleteTool, createMemoryStore, createMemoryTools, createModelProvider, createSkillTools, createStateStore, createSubagentTools, createUploadStore, createWriteTool, deriveUploadKey, ensureAgentIdentity, generateAgentId, getAgentStoreDirectory, getModelContextWindow, getPonchoStoreRoot, jsonSchemaToZod, loadPonchoConfig, loadSkillContext, loadSkillInstructions, loadSkillMetadata, normalizeScriptPolicyPath, parseAgentFile, parseAgentMarkdown, readSkillResource, renderAgentPrompt, resolveAgentIdentity, resolveMemoryConfig, resolveSkillDirs, resolveStateConfig, slugifyStorageComponent };
|
package/dist/index.js
CHANGED
|
@@ -378,7 +378,7 @@ var loadPonchoConfig = async (workingDir) => {
|
|
|
378
378
|
};
|
|
379
379
|
|
|
380
380
|
// src/default-tools.ts
|
|
381
|
-
import { mkdir, readdir, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
|
|
381
|
+
import { mkdir, readdir, readFile as readFile3, rm, unlink, writeFile as writeFile2 } from "fs/promises";
|
|
382
382
|
import { dirname, resolve as resolve4, sep } from "path";
|
|
383
383
|
import { defineTool } from "@poncho-ai/sdk";
|
|
384
384
|
var resolveSafePath = (workingDir, inputPath) => {
|
|
@@ -463,6 +463,52 @@ var createWriteTool = (workingDir) => defineTool({
|
|
|
463
463
|
return { path, written: true };
|
|
464
464
|
}
|
|
465
465
|
});
|
|
466
|
+
var createDeleteTool = (workingDir) => defineTool({
|
|
467
|
+
name: "delete_file",
|
|
468
|
+
description: "Delete a file at a path inside the working directory",
|
|
469
|
+
inputSchema: {
|
|
470
|
+
type: "object",
|
|
471
|
+
properties: {
|
|
472
|
+
path: {
|
|
473
|
+
type: "string",
|
|
474
|
+
description: "File path relative to working directory"
|
|
475
|
+
}
|
|
476
|
+
},
|
|
477
|
+
required: ["path"],
|
|
478
|
+
additionalProperties: false
|
|
479
|
+
},
|
|
480
|
+
handler: async (input) => {
|
|
481
|
+
const path = typeof input.path === "string" ? input.path : "";
|
|
482
|
+
const resolved = resolveSafePath(workingDir, path);
|
|
483
|
+
await unlink(resolved);
|
|
484
|
+
return { path, deleted: true };
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
var createDeleteDirectoryTool = (workingDir) => defineTool({
|
|
488
|
+
name: "delete_directory",
|
|
489
|
+
description: "Recursively delete a directory and all its contents inside the working directory",
|
|
490
|
+
inputSchema: {
|
|
491
|
+
type: "object",
|
|
492
|
+
properties: {
|
|
493
|
+
path: {
|
|
494
|
+
type: "string",
|
|
495
|
+
description: "Directory path relative to working directory"
|
|
496
|
+
}
|
|
497
|
+
},
|
|
498
|
+
required: ["path"],
|
|
499
|
+
additionalProperties: false
|
|
500
|
+
},
|
|
501
|
+
handler: async (input) => {
|
|
502
|
+
const path = typeof input.path === "string" ? input.path : "";
|
|
503
|
+
if (!path) throw new Error("Path must not be empty.");
|
|
504
|
+
const resolved = resolveSafePath(workingDir, path);
|
|
505
|
+
if (resolved === resolve4(workingDir)) {
|
|
506
|
+
throw new Error("Cannot delete the working directory root.");
|
|
507
|
+
}
|
|
508
|
+
await rm(resolved, { recursive: true });
|
|
509
|
+
return { path, deleted: true };
|
|
510
|
+
}
|
|
511
|
+
});
|
|
466
512
|
|
|
467
513
|
// src/harness.ts
|
|
468
514
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
@@ -470,8 +516,9 @@ import { getTextContent as getTextContent2 } from "@poncho-ai/sdk";
|
|
|
470
516
|
|
|
471
517
|
// src/upload-store.ts
|
|
472
518
|
import { createHash as createHash2 } from "crypto";
|
|
473
|
-
import { mkdir as mkdir2, readFile as readFile4, writeFile as writeFile3, rm } from "fs/promises";
|
|
519
|
+
import { mkdir as mkdir2, readFile as readFile4, writeFile as writeFile3, rm as rm2 } from "fs/promises";
|
|
474
520
|
import { createRequire } from "module";
|
|
521
|
+
import { homedir as homedir2 } from "os";
|
|
475
522
|
import { resolve as resolve5 } from "path";
|
|
476
523
|
var tryImport = async (mod, workingDir) => {
|
|
477
524
|
try {
|
|
@@ -559,10 +606,15 @@ var MIME_EXT_MAP = {
|
|
|
559
606
|
"audio/wav": ".wav"
|
|
560
607
|
};
|
|
561
608
|
var mimeToExt = (mediaType) => MIME_EXT_MAP[mediaType] ?? `.${mediaType.split("/").pop() ?? "bin"}`;
|
|
609
|
+
var isServerlessEnv = () => {
|
|
610
|
+
const cwd = process.cwd();
|
|
611
|
+
const home = homedir2();
|
|
612
|
+
return process.env.VERCEL === "1" || process.env.VERCEL_ENV !== void 0 || process.env.VERCEL_URL !== void 0 || process.env.AWS_LAMBDA_FUNCTION_NAME !== void 0 || process.env.AWS_EXECUTION_ENV?.includes("AWS_Lambda") === true || process.env.LAMBDA_TASK_ROOT !== void 0 || process.env.NOW_REGION !== void 0 || cwd.startsWith("/var/task") || home.startsWith("/var/task") || process.env.SERVERLESS === "1";
|
|
613
|
+
};
|
|
562
614
|
var LocalUploadStore = class {
|
|
563
615
|
uploadsDir;
|
|
564
616
|
constructor(workingDir) {
|
|
565
|
-
this.uploadsDir = resolve5(workingDir, ".poncho", "uploads");
|
|
617
|
+
this.uploadsDir = isServerlessEnv() ? "/tmp/.poncho/uploads" : resolve5(workingDir, ".poncho", "uploads");
|
|
566
618
|
}
|
|
567
619
|
async put(_key, data, mediaType) {
|
|
568
620
|
const key = deriveUploadKey(data, mediaType);
|
|
@@ -577,7 +629,7 @@ var LocalUploadStore = class {
|
|
|
577
629
|
}
|
|
578
630
|
async delete(urlOrKey) {
|
|
579
631
|
const key = urlOrKey.startsWith(PONCHO_UPLOAD_SCHEME) ? urlOrKey.slice(PONCHO_UPLOAD_SCHEME.length) : urlOrKey;
|
|
580
|
-
await
|
|
632
|
+
await rm2(resolve5(this.uploadsDir, key), { force: true });
|
|
581
633
|
}
|
|
582
634
|
};
|
|
583
635
|
var VercelBlobUploadStore = class {
|
|
@@ -2908,6 +2960,7 @@ The agent will respond in Slack threads when @mentioned. Each Slack thread maps
|
|
|
2908
2960
|
RESEND_API_KEY=re_...
|
|
2909
2961
|
RESEND_WEBHOOK_SECRET=whsec_...
|
|
2910
2962
|
RESEND_FROM=Agent <agent@yourdomain.com>
|
|
2963
|
+
RESEND_REPLY_TO=support@yourdomain.com # optional
|
|
2911
2964
|
\`\`\`
|
|
2912
2965
|
5. Add to \`poncho.config.js\`:
|
|
2913
2966
|
\`\`\`javascript
|
|
@@ -3080,7 +3133,7 @@ var AgentHarness = class {
|
|
|
3080
3133
|
isToolEnabled(name) {
|
|
3081
3134
|
const access3 = this.resolveToolAccess(name);
|
|
3082
3135
|
if (access3 === false) return false;
|
|
3083
|
-
if (name === "write_file") {
|
|
3136
|
+
if (name === "write_file" || name === "delete_file" || name === "delete_directory") {
|
|
3084
3137
|
return this.shouldEnableWriteTool();
|
|
3085
3138
|
}
|
|
3086
3139
|
return true;
|
|
@@ -3123,6 +3176,12 @@ var AgentHarness = class {
|
|
|
3123
3176
|
if (this.isToolEnabled("write_file")) {
|
|
3124
3177
|
this.registerIfMissing(createWriteTool(this.workingDir));
|
|
3125
3178
|
}
|
|
3179
|
+
if (this.isToolEnabled("delete_file")) {
|
|
3180
|
+
this.registerIfMissing(createDeleteTool(this.workingDir));
|
|
3181
|
+
}
|
|
3182
|
+
if (this.isToolEnabled("delete_directory")) {
|
|
3183
|
+
this.registerIfMissing(createDeleteDirectoryTool(this.workingDir));
|
|
3184
|
+
}
|
|
3126
3185
|
}
|
|
3127
3186
|
shouldEnableWriteTool() {
|
|
3128
3187
|
const override = process.env.PONCHO_FS_WRITE?.toLowerCase();
|
|
@@ -4242,6 +4301,7 @@ ${textContent}` };
|
|
|
4242
4301
|
const toolResultsForModel = [];
|
|
4243
4302
|
const richToolResults = [];
|
|
4244
4303
|
const approvedCalls = [];
|
|
4304
|
+
const approvalNeeded = [];
|
|
4245
4305
|
for (const call of toolCalls) {
|
|
4246
4306
|
if (isCancelled()) {
|
|
4247
4307
|
yield emitCancellation();
|
|
@@ -4249,52 +4309,60 @@ ${textContent}` };
|
|
|
4249
4309
|
}
|
|
4250
4310
|
const runtimeToolName = exposedToolNames.get(call.name) ?? call.name;
|
|
4251
4311
|
yield pushEvent({ type: "tool:started", tool: runtimeToolName, input: call.input });
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
yield pushEvent({
|
|
4259
|
-
type: "tool:approval:required",
|
|
4260
|
-
tool: runtimeToolName,
|
|
4261
|
-
input: call.input,
|
|
4262
|
-
approvalId
|
|
4312
|
+
if (this.requiresApprovalForToolCall(runtimeToolName, call.input)) {
|
|
4313
|
+
approvalNeeded.push({
|
|
4314
|
+
approvalId: `approval_${randomUUID3()}`,
|
|
4315
|
+
id: call.id,
|
|
4316
|
+
name: runtimeToolName,
|
|
4317
|
+
input: call.input
|
|
4263
4318
|
});
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
input: tc.input
|
|
4270
|
-
}))
|
|
4319
|
+
} else {
|
|
4320
|
+
approvedCalls.push({
|
|
4321
|
+
id: call.id,
|
|
4322
|
+
name: runtimeToolName,
|
|
4323
|
+
input: call.input
|
|
4271
4324
|
});
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
};
|
|
4277
|
-
const deltaMessages = [...messages.slice(inputMessageCount), assistantMsg];
|
|
4325
|
+
}
|
|
4326
|
+
}
|
|
4327
|
+
if (approvalNeeded.length > 0) {
|
|
4328
|
+
for (const an of approvalNeeded) {
|
|
4278
4329
|
yield pushEvent({
|
|
4279
|
-
type: "tool:approval:
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
input: call.input,
|
|
4284
|
-
checkpointMessages: deltaMessages,
|
|
4285
|
-
pendingToolCalls: toolCalls.map((tc) => ({
|
|
4286
|
-
id: tc.id,
|
|
4287
|
-
name: exposedToolNames.get(tc.name) ?? tc.name,
|
|
4288
|
-
input: tc.input
|
|
4289
|
-
}))
|
|
4330
|
+
type: "tool:approval:required",
|
|
4331
|
+
tool: an.name,
|
|
4332
|
+
input: an.input,
|
|
4333
|
+
approvalId: an.approvalId
|
|
4290
4334
|
});
|
|
4291
|
-
return;
|
|
4292
4335
|
}
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4336
|
+
const assistantContent2 = JSON.stringify({
|
|
4337
|
+
text: fullText,
|
|
4338
|
+
tool_calls: toolCalls.map((tc) => ({
|
|
4339
|
+
id: tc.id,
|
|
4340
|
+
name: exposedToolNames.get(tc.name) ?? tc.name,
|
|
4341
|
+
input: tc.input
|
|
4342
|
+
}))
|
|
4343
|
+
});
|
|
4344
|
+
const assistantMsg = {
|
|
4345
|
+
role: "assistant",
|
|
4346
|
+
content: assistantContent2,
|
|
4347
|
+
metadata: { timestamp: now(), id: randomUUID3(), step }
|
|
4348
|
+
};
|
|
4349
|
+
const deltaMessages = [...messages.slice(inputMessageCount), assistantMsg];
|
|
4350
|
+
yield pushEvent({
|
|
4351
|
+
type: "tool:approval:checkpoint",
|
|
4352
|
+
approvals: approvalNeeded.map((an) => ({
|
|
4353
|
+
approvalId: an.approvalId,
|
|
4354
|
+
tool: an.name,
|
|
4355
|
+
toolCallId: an.id,
|
|
4356
|
+
input: an.input
|
|
4357
|
+
})),
|
|
4358
|
+
checkpointMessages: deltaMessages,
|
|
4359
|
+
pendingToolCalls: toolCalls.map((tc) => ({
|
|
4360
|
+
id: tc.id,
|
|
4361
|
+
name: exposedToolNames.get(tc.name) ?? tc.name,
|
|
4362
|
+
input: tc.input
|
|
4363
|
+
}))
|
|
4297
4364
|
});
|
|
4365
|
+
return;
|
|
4298
4366
|
}
|
|
4299
4367
|
const batchStart = now();
|
|
4300
4368
|
if (isCancelled()) {
|
|
@@ -4588,7 +4656,7 @@ var LatitudeCapture = class {
|
|
|
4588
4656
|
|
|
4589
4657
|
// src/state.ts
|
|
4590
4658
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
4591
|
-
import { mkdir as mkdir4, readFile as readFile7, readdir as readdir4, rename as rename2, rm as
|
|
4659
|
+
import { mkdir as mkdir4, readFile as readFile7, readdir as readdir4, rename as rename2, rm as rm3, writeFile as writeFile5 } from "fs/promises";
|
|
4592
4660
|
import { dirname as dirname4, resolve as resolve9 } from "path";
|
|
4593
4661
|
var DEFAULT_OWNER = "local-owner";
|
|
4594
4662
|
var LOCAL_STATE_FILE = "state.json";
|
|
@@ -4963,7 +5031,7 @@ var FileConversationStore = class {
|
|
|
4963
5031
|
if (removed) {
|
|
4964
5032
|
this.writing = this.writing.then(async () => {
|
|
4965
5033
|
if (existing) {
|
|
4966
|
-
await
|
|
5034
|
+
await rm3(resolve9(conversationsDir, existing.fileName), { force: true });
|
|
4967
5035
|
}
|
|
4968
5036
|
await this.writeIndex();
|
|
4969
5037
|
});
|
|
@@ -5711,6 +5779,8 @@ export {
|
|
|
5711
5779
|
buildSkillContextWindow,
|
|
5712
5780
|
createConversationStore,
|
|
5713
5781
|
createDefaultTools,
|
|
5782
|
+
createDeleteDirectoryTool,
|
|
5783
|
+
createDeleteTool,
|
|
5714
5784
|
createMemoryStore,
|
|
5715
5785
|
createMemoryTools,
|
|
5716
5786
|
createModelProvider,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@poncho-ai/harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.1",
|
|
4
4
|
"description": "Agent execution runtime - conversation loop, tool dispatch, streaming",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"redis": "^5.10.0",
|
|
32
32
|
"yaml": "^2.4.0",
|
|
33
33
|
"zod": "^3.22.0",
|
|
34
|
-
"@poncho-ai/sdk": "1.
|
|
34
|
+
"@poncho-ai/sdk": "1.4.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/mustache": "^4.2.6",
|
package/src/config.ts
CHANGED
|
@@ -39,6 +39,8 @@ export type BuiltInToolToggles = {
|
|
|
39
39
|
list_directory?: boolean;
|
|
40
40
|
read_file?: boolean;
|
|
41
41
|
write_file?: boolean;
|
|
42
|
+
delete_file?: boolean;
|
|
43
|
+
delete_directory?: boolean;
|
|
42
44
|
};
|
|
43
45
|
|
|
44
46
|
export interface MessagingChannelConfig {
|
|
@@ -50,6 +52,7 @@ export interface MessagingChannelConfig {
|
|
|
50
52
|
apiKeyEnv?: string;
|
|
51
53
|
webhookSecretEnv?: string;
|
|
52
54
|
fromEnv?: string;
|
|
55
|
+
replyToEnv?: string;
|
|
53
56
|
allowedSenders?: string[];
|
|
54
57
|
mode?: "auto-reply" | "tool";
|
|
55
58
|
allowedRecipients?: string[];
|
package/src/default-tools.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
1
|
+
import { mkdir, readdir, readFile, rm, unlink, writeFile } from "node:fs/promises";
|
|
2
2
|
import { dirname, resolve, sep } from "node:path";
|
|
3
3
|
import { defineTool, type ToolDefinition } from "@poncho-ai/sdk";
|
|
4
4
|
|
|
@@ -87,3 +87,53 @@ export const createWriteTool = (workingDir: string): ToolDefinition =>
|
|
|
87
87
|
return { path, written: true };
|
|
88
88
|
},
|
|
89
89
|
});
|
|
90
|
+
|
|
91
|
+
export const createDeleteTool = (workingDir: string): ToolDefinition =>
|
|
92
|
+
defineTool({
|
|
93
|
+
name: "delete_file",
|
|
94
|
+
description: "Delete a file at a path inside the working directory",
|
|
95
|
+
inputSchema: {
|
|
96
|
+
type: "object",
|
|
97
|
+
properties: {
|
|
98
|
+
path: {
|
|
99
|
+
type: "string",
|
|
100
|
+
description: "File path relative to working directory",
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
required: ["path"],
|
|
104
|
+
additionalProperties: false,
|
|
105
|
+
},
|
|
106
|
+
handler: async (input) => {
|
|
107
|
+
const path = typeof input.path === "string" ? input.path : "";
|
|
108
|
+
const resolved = resolveSafePath(workingDir, path);
|
|
109
|
+
await unlink(resolved);
|
|
110
|
+
return { path, deleted: true };
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
export const createDeleteDirectoryTool = (workingDir: string): ToolDefinition =>
|
|
115
|
+
defineTool({
|
|
116
|
+
name: "delete_directory",
|
|
117
|
+
description: "Recursively delete a directory and all its contents inside the working directory",
|
|
118
|
+
inputSchema: {
|
|
119
|
+
type: "object",
|
|
120
|
+
properties: {
|
|
121
|
+
path: {
|
|
122
|
+
type: "string",
|
|
123
|
+
description: "Directory path relative to working directory",
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
required: ["path"],
|
|
127
|
+
additionalProperties: false,
|
|
128
|
+
},
|
|
129
|
+
handler: async (input) => {
|
|
130
|
+
const path = typeof input.path === "string" ? input.path : "";
|
|
131
|
+
if (!path) throw new Error("Path must not be empty.");
|
|
132
|
+
const resolved = resolveSafePath(workingDir, path);
|
|
133
|
+
if (resolved === resolve(workingDir)) {
|
|
134
|
+
throw new Error("Cannot delete the working directory root.");
|
|
135
|
+
}
|
|
136
|
+
await rm(resolved, { recursive: true });
|
|
137
|
+
return { path, deleted: true };
|
|
138
|
+
},
|
|
139
|
+
});
|
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, createWriteTool } from "./default-tools.js";
|
|
18
|
+
import { createDefaultTools, createDeleteDirectoryTool, createDeleteTool, createWriteTool } from "./default-tools.js";
|
|
19
19
|
import {
|
|
20
20
|
createMemoryStore,
|
|
21
21
|
createMemoryTools,
|
|
@@ -324,6 +324,7 @@ The agent will respond in Slack threads when @mentioned. Each Slack thread maps
|
|
|
324
324
|
RESEND_API_KEY=re_...
|
|
325
325
|
RESEND_WEBHOOK_SECRET=whsec_...
|
|
326
326
|
RESEND_FROM=Agent <agent@yourdomain.com>
|
|
327
|
+
RESEND_REPLY_TO=support@yourdomain.com # optional
|
|
327
328
|
\`\`\`
|
|
328
329
|
5. Add to \`poncho.config.js\`:
|
|
329
330
|
\`\`\`javascript
|
|
@@ -526,7 +527,7 @@ export class AgentHarness {
|
|
|
526
527
|
private isToolEnabled(name: string): boolean {
|
|
527
528
|
const access = this.resolveToolAccess(name);
|
|
528
529
|
if (access === false) return false;
|
|
529
|
-
if (name === "write_file") {
|
|
530
|
+
if (name === "write_file" || name === "delete_file" || name === "delete_directory") {
|
|
530
531
|
return this.shouldEnableWriteTool();
|
|
531
532
|
}
|
|
532
533
|
return true;
|
|
@@ -574,6 +575,12 @@ export class AgentHarness {
|
|
|
574
575
|
if (this.isToolEnabled("write_file")) {
|
|
575
576
|
this.registerIfMissing(createWriteTool(this.workingDir));
|
|
576
577
|
}
|
|
578
|
+
if (this.isToolEnabled("delete_file")) {
|
|
579
|
+
this.registerIfMissing(createDeleteTool(this.workingDir));
|
|
580
|
+
}
|
|
581
|
+
if (this.isToolEnabled("delete_directory")) {
|
|
582
|
+
this.registerIfMissing(createDeleteDirectoryTool(this.workingDir));
|
|
583
|
+
}
|
|
577
584
|
}
|
|
578
585
|
|
|
579
586
|
private shouldEnableWriteTool(): boolean {
|
|
@@ -1906,7 +1913,14 @@ ${boundedMainMemory.trim()}`
|
|
|
1906
1913
|
name: string;
|
|
1907
1914
|
input: Record<string, unknown>;
|
|
1908
1915
|
}> = [];
|
|
1916
|
+
const approvalNeeded: Array<{
|
|
1917
|
+
approvalId: string;
|
|
1918
|
+
id: string;
|
|
1919
|
+
name: string;
|
|
1920
|
+
input: Record<string, unknown>;
|
|
1921
|
+
}> = [];
|
|
1909
1922
|
|
|
1923
|
+
// Phase 1: classify all tool calls
|
|
1910
1924
|
for (const call of toolCalls) {
|
|
1911
1925
|
if (isCancelled()) {
|
|
1912
1926
|
yield emitCancellation();
|
|
@@ -1914,54 +1928,66 @@ ${boundedMainMemory.trim()}`
|
|
|
1914
1928
|
}
|
|
1915
1929
|
const runtimeToolName = exposedToolNames.get(call.name) ?? call.name;
|
|
1916
1930
|
yield pushEvent({ type: "tool:started", tool: runtimeToolName, input: call.input });
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
const approvalId = `approval_${randomUUID()}`;
|
|
1923
|
-
yield pushEvent({
|
|
1924
|
-
type: "tool:approval:required",
|
|
1925
|
-
tool: runtimeToolName,
|
|
1931
|
+
if (this.requiresApprovalForToolCall(runtimeToolName, call.input)) {
|
|
1932
|
+
approvalNeeded.push({
|
|
1933
|
+
approvalId: `approval_${randomUUID()}`,
|
|
1934
|
+
id: call.id,
|
|
1935
|
+
name: runtimeToolName,
|
|
1926
1936
|
input: call.input,
|
|
1927
|
-
approvalId,
|
|
1928
1937
|
});
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
name: exposedToolNames.get(tc.name) ?? tc.name,
|
|
1935
|
-
input: tc.input,
|
|
1936
|
-
})),
|
|
1938
|
+
} else {
|
|
1939
|
+
approvedCalls.push({
|
|
1940
|
+
id: call.id,
|
|
1941
|
+
name: runtimeToolName,
|
|
1942
|
+
input: call.input,
|
|
1937
1943
|
});
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
// Phase 2a: if any tools need approval, emit events for ALL of them and checkpoint
|
|
1948
|
+
if (approvalNeeded.length > 0) {
|
|
1949
|
+
for (const an of approvalNeeded) {
|
|
1944
1950
|
yield pushEvent({
|
|
1945
|
-
type: "tool:approval:
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
input: call.input,
|
|
1950
|
-
checkpointMessages: deltaMessages,
|
|
1951
|
-
pendingToolCalls: toolCalls.map(tc => ({
|
|
1952
|
-
id: tc.id,
|
|
1953
|
-
name: exposedToolNames.get(tc.name) ?? tc.name,
|
|
1954
|
-
input: tc.input,
|
|
1955
|
-
})),
|
|
1951
|
+
type: "tool:approval:required",
|
|
1952
|
+
tool: an.name,
|
|
1953
|
+
input: an.input,
|
|
1954
|
+
approvalId: an.approvalId,
|
|
1956
1955
|
});
|
|
1957
|
-
return;
|
|
1958
1956
|
}
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1957
|
+
|
|
1958
|
+
const assistantContent = JSON.stringify({
|
|
1959
|
+
text: fullText,
|
|
1960
|
+
tool_calls: toolCalls.map(tc => ({
|
|
1961
|
+
id: tc.id,
|
|
1962
|
+
name: exposedToolNames.get(tc.name) ?? tc.name,
|
|
1963
|
+
input: tc.input,
|
|
1964
|
+
})),
|
|
1965
|
+
});
|
|
1966
|
+
const assistantMsg: Message = {
|
|
1967
|
+
role: "assistant",
|
|
1968
|
+
content: assistantContent,
|
|
1969
|
+
metadata: { timestamp: now(), id: randomUUID(), step },
|
|
1970
|
+
};
|
|
1971
|
+
const deltaMessages = [...messages.slice(inputMessageCount), assistantMsg];
|
|
1972
|
+
yield pushEvent({
|
|
1973
|
+
type: "tool:approval:checkpoint",
|
|
1974
|
+
approvals: approvalNeeded.map(an => ({
|
|
1975
|
+
approvalId: an.approvalId,
|
|
1976
|
+
tool: an.name,
|
|
1977
|
+
toolCallId: an.id,
|
|
1978
|
+
input: an.input,
|
|
1979
|
+
})),
|
|
1980
|
+
checkpointMessages: deltaMessages,
|
|
1981
|
+
pendingToolCalls: toolCalls.map(tc => ({
|
|
1982
|
+
id: tc.id,
|
|
1983
|
+
name: exposedToolNames.get(tc.name) ?? tc.name,
|
|
1984
|
+
input: tc.input,
|
|
1985
|
+
})),
|
|
1963
1986
|
});
|
|
1987
|
+
return;
|
|
1964
1988
|
}
|
|
1989
|
+
|
|
1990
|
+
// Phase 2b: no approvals needed — execute all auto-approved calls
|
|
1965
1991
|
const batchStart = now();
|
|
1966
1992
|
if (isCancelled()) {
|
|
1967
1993
|
yield emitCancellation();
|
package/src/state.ts
CHANGED
|
@@ -35,6 +35,7 @@ export interface Conversation {
|
|
|
35
35
|
checkpointMessages?: Message[];
|
|
36
36
|
baseMessageCount?: number;
|
|
37
37
|
pendingToolCalls?: Array<{ id: string; name: string; input: Record<string, unknown> }>;
|
|
38
|
+
decision?: "approved" | "denied";
|
|
38
39
|
}>;
|
|
39
40
|
ownerId: string;
|
|
40
41
|
tenantId: string | null;
|
package/src/upload-store.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { mkdir, readFile, writeFile, rm } from "node:fs/promises";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
|
+
import { homedir } from "node:os";
|
|
4
5
|
import { resolve } from "node:path";
|
|
5
6
|
import type { UploadsConfig } from "./config.js";
|
|
6
7
|
|
|
@@ -127,11 +128,30 @@ const mimeToExt = (mediaType: string): string =>
|
|
|
127
128
|
// Local filesystem implementation
|
|
128
129
|
// ---------------------------------------------------------------------------
|
|
129
130
|
|
|
131
|
+
const isServerlessEnv = (): boolean => {
|
|
132
|
+
const cwd = process.cwd();
|
|
133
|
+
const home = homedir();
|
|
134
|
+
return (
|
|
135
|
+
process.env.VERCEL === "1" ||
|
|
136
|
+
process.env.VERCEL_ENV !== undefined ||
|
|
137
|
+
process.env.VERCEL_URL !== undefined ||
|
|
138
|
+
process.env.AWS_LAMBDA_FUNCTION_NAME !== undefined ||
|
|
139
|
+
process.env.AWS_EXECUTION_ENV?.includes("AWS_Lambda") === true ||
|
|
140
|
+
process.env.LAMBDA_TASK_ROOT !== undefined ||
|
|
141
|
+
process.env.NOW_REGION !== undefined ||
|
|
142
|
+
cwd.startsWith("/var/task") ||
|
|
143
|
+
home.startsWith("/var/task") ||
|
|
144
|
+
process.env.SERVERLESS === "1"
|
|
145
|
+
);
|
|
146
|
+
};
|
|
147
|
+
|
|
130
148
|
export class LocalUploadStore implements UploadStore {
|
|
131
149
|
private readonly uploadsDir: string;
|
|
132
150
|
|
|
133
151
|
constructor(workingDir: string) {
|
|
134
|
-
this.uploadsDir =
|
|
152
|
+
this.uploadsDir = isServerlessEnv()
|
|
153
|
+
? "/tmp/.poncho/uploads"
|
|
154
|
+
: resolve(workingDir, ".poncho", "uploads");
|
|
135
155
|
}
|
|
136
156
|
|
|
137
157
|
async put(_key: string, data: Buffer, mediaType: string): Promise<string> {
|