@poncho-ai/harness 0.18.0 → 0.19.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 +14 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.js +110 -46
- 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/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @poncho-ai/harness@0.
|
|
2
|
+
> @poncho-ai/harness@0.19.0 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[32m198.85 KB[39m
|
|
11
|
+
[32mESM[39m ⚡️ Build success in 137ms
|
|
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 7145ms
|
|
14
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m24.33 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @poncho-ai/harness
|
|
2
2
|
|
|
3
|
+
## 0.19.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`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
|
|
8
|
+
- Batch tool approvals: all approval-requiring tool calls in a single step are now collected and presented together instead of one at a time.
|
|
9
|
+
- Fix messaging adapter route registration: routes are only registered after successful initialization, preventing "Adapter not initialised" errors on Vercel.
|
|
10
|
+
- Add stateless signed-cookie sessions so web UI auth survives serverless cold starts.
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- Updated dependencies [[`075b9ac`](https://github.com/cesr/poncho-ai/commit/075b9ac3556847af913bf2b58f030575c3b99852)]:
|
|
15
|
+
- @poncho-ai/sdk@1.4.0
|
|
16
|
+
|
|
3
17
|
## 0.18.0
|
|
4
18
|
|
|
5
19
|
### 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,7 +516,7 @@ 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";
|
|
475
521
|
import { resolve as resolve5 } from "path";
|
|
476
522
|
var tryImport = async (mod, workingDir) => {
|
|
@@ -577,7 +623,7 @@ var LocalUploadStore = class {
|
|
|
577
623
|
}
|
|
578
624
|
async delete(urlOrKey) {
|
|
579
625
|
const key = urlOrKey.startsWith(PONCHO_UPLOAD_SCHEME) ? urlOrKey.slice(PONCHO_UPLOAD_SCHEME.length) : urlOrKey;
|
|
580
|
-
await
|
|
626
|
+
await rm2(resolve5(this.uploadsDir, key), { force: true });
|
|
581
627
|
}
|
|
582
628
|
};
|
|
583
629
|
var VercelBlobUploadStore = class {
|
|
@@ -2908,6 +2954,7 @@ The agent will respond in Slack threads when @mentioned. Each Slack thread maps
|
|
|
2908
2954
|
RESEND_API_KEY=re_...
|
|
2909
2955
|
RESEND_WEBHOOK_SECRET=whsec_...
|
|
2910
2956
|
RESEND_FROM=Agent <agent@yourdomain.com>
|
|
2957
|
+
RESEND_REPLY_TO=support@yourdomain.com # optional
|
|
2911
2958
|
\`\`\`
|
|
2912
2959
|
5. Add to \`poncho.config.js\`:
|
|
2913
2960
|
\`\`\`javascript
|
|
@@ -3080,7 +3127,7 @@ var AgentHarness = class {
|
|
|
3080
3127
|
isToolEnabled(name) {
|
|
3081
3128
|
const access3 = this.resolveToolAccess(name);
|
|
3082
3129
|
if (access3 === false) return false;
|
|
3083
|
-
if (name === "write_file") {
|
|
3130
|
+
if (name === "write_file" || name === "delete_file" || name === "delete_directory") {
|
|
3084
3131
|
return this.shouldEnableWriteTool();
|
|
3085
3132
|
}
|
|
3086
3133
|
return true;
|
|
@@ -3123,6 +3170,12 @@ var AgentHarness = class {
|
|
|
3123
3170
|
if (this.isToolEnabled("write_file")) {
|
|
3124
3171
|
this.registerIfMissing(createWriteTool(this.workingDir));
|
|
3125
3172
|
}
|
|
3173
|
+
if (this.isToolEnabled("delete_file")) {
|
|
3174
|
+
this.registerIfMissing(createDeleteTool(this.workingDir));
|
|
3175
|
+
}
|
|
3176
|
+
if (this.isToolEnabled("delete_directory")) {
|
|
3177
|
+
this.registerIfMissing(createDeleteDirectoryTool(this.workingDir));
|
|
3178
|
+
}
|
|
3126
3179
|
}
|
|
3127
3180
|
shouldEnableWriteTool() {
|
|
3128
3181
|
const override = process.env.PONCHO_FS_WRITE?.toLowerCase();
|
|
@@ -4242,6 +4295,7 @@ ${textContent}` };
|
|
|
4242
4295
|
const toolResultsForModel = [];
|
|
4243
4296
|
const richToolResults = [];
|
|
4244
4297
|
const approvedCalls = [];
|
|
4298
|
+
const approvalNeeded = [];
|
|
4245
4299
|
for (const call of toolCalls) {
|
|
4246
4300
|
if (isCancelled()) {
|
|
4247
4301
|
yield emitCancellation();
|
|
@@ -4249,52 +4303,60 @@ ${textContent}` };
|
|
|
4249
4303
|
}
|
|
4250
4304
|
const runtimeToolName = exposedToolNames.get(call.name) ?? call.name;
|
|
4251
4305
|
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
|
|
4306
|
+
if (this.requiresApprovalForToolCall(runtimeToolName, call.input)) {
|
|
4307
|
+
approvalNeeded.push({
|
|
4308
|
+
approvalId: `approval_${randomUUID3()}`,
|
|
4309
|
+
id: call.id,
|
|
4310
|
+
name: runtimeToolName,
|
|
4311
|
+
input: call.input
|
|
4263
4312
|
});
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
input: tc.input
|
|
4270
|
-
}))
|
|
4313
|
+
} else {
|
|
4314
|
+
approvedCalls.push({
|
|
4315
|
+
id: call.id,
|
|
4316
|
+
name: runtimeToolName,
|
|
4317
|
+
input: call.input
|
|
4271
4318
|
});
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
};
|
|
4277
|
-
const deltaMessages = [...messages.slice(inputMessageCount), assistantMsg];
|
|
4319
|
+
}
|
|
4320
|
+
}
|
|
4321
|
+
if (approvalNeeded.length > 0) {
|
|
4322
|
+
for (const an of approvalNeeded) {
|
|
4278
4323
|
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
|
-
}))
|
|
4324
|
+
type: "tool:approval:required",
|
|
4325
|
+
tool: an.name,
|
|
4326
|
+
input: an.input,
|
|
4327
|
+
approvalId: an.approvalId
|
|
4290
4328
|
});
|
|
4291
|
-
return;
|
|
4292
4329
|
}
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4330
|
+
const assistantContent2 = JSON.stringify({
|
|
4331
|
+
text: fullText,
|
|
4332
|
+
tool_calls: toolCalls.map((tc) => ({
|
|
4333
|
+
id: tc.id,
|
|
4334
|
+
name: exposedToolNames.get(tc.name) ?? tc.name,
|
|
4335
|
+
input: tc.input
|
|
4336
|
+
}))
|
|
4297
4337
|
});
|
|
4338
|
+
const assistantMsg = {
|
|
4339
|
+
role: "assistant",
|
|
4340
|
+
content: assistantContent2,
|
|
4341
|
+
metadata: { timestamp: now(), id: randomUUID3(), step }
|
|
4342
|
+
};
|
|
4343
|
+
const deltaMessages = [...messages.slice(inputMessageCount), assistantMsg];
|
|
4344
|
+
yield pushEvent({
|
|
4345
|
+
type: "tool:approval:checkpoint",
|
|
4346
|
+
approvals: approvalNeeded.map((an) => ({
|
|
4347
|
+
approvalId: an.approvalId,
|
|
4348
|
+
tool: an.name,
|
|
4349
|
+
toolCallId: an.id,
|
|
4350
|
+
input: an.input
|
|
4351
|
+
})),
|
|
4352
|
+
checkpointMessages: deltaMessages,
|
|
4353
|
+
pendingToolCalls: toolCalls.map((tc) => ({
|
|
4354
|
+
id: tc.id,
|
|
4355
|
+
name: exposedToolNames.get(tc.name) ?? tc.name,
|
|
4356
|
+
input: tc.input
|
|
4357
|
+
}))
|
|
4358
|
+
});
|
|
4359
|
+
return;
|
|
4298
4360
|
}
|
|
4299
4361
|
const batchStart = now();
|
|
4300
4362
|
if (isCancelled()) {
|
|
@@ -4588,7 +4650,7 @@ var LatitudeCapture = class {
|
|
|
4588
4650
|
|
|
4589
4651
|
// src/state.ts
|
|
4590
4652
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
4591
|
-
import { mkdir as mkdir4, readFile as readFile7, readdir as readdir4, rename as rename2, rm as
|
|
4653
|
+
import { mkdir as mkdir4, readFile as readFile7, readdir as readdir4, rename as rename2, rm as rm3, writeFile as writeFile5 } from "fs/promises";
|
|
4592
4654
|
import { dirname as dirname4, resolve as resolve9 } from "path";
|
|
4593
4655
|
var DEFAULT_OWNER = "local-owner";
|
|
4594
4656
|
var LOCAL_STATE_FILE = "state.json";
|
|
@@ -4963,7 +5025,7 @@ var FileConversationStore = class {
|
|
|
4963
5025
|
if (removed) {
|
|
4964
5026
|
this.writing = this.writing.then(async () => {
|
|
4965
5027
|
if (existing) {
|
|
4966
|
-
await
|
|
5028
|
+
await rm3(resolve9(conversationsDir, existing.fileName), { force: true });
|
|
4967
5029
|
}
|
|
4968
5030
|
await this.writeIndex();
|
|
4969
5031
|
});
|
|
@@ -5711,6 +5773,8 @@ export {
|
|
|
5711
5773
|
buildSkillContextWindow,
|
|
5712
5774
|
createConversationStore,
|
|
5713
5775
|
createDefaultTools,
|
|
5776
|
+
createDeleteDirectoryTool,
|
|
5777
|
+
createDeleteTool,
|
|
5714
5778
|
createMemoryStore,
|
|
5715
5779
|
createMemoryTools,
|
|
5716
5780
|
createModelProvider,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@poncho-ai/harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
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;
|