@poncho-ai/cli 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 +16 -0
- package/dist/{chunk-ETZ3YKFP.js → chunk-7P53QSP5.js} +393 -145
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/dist/{run-interactive-ink-VSO7GB3Y.js → run-interactive-ink-4KVVPMR3.js} +1 -1
- package/package.json +4 -4
- package/src/index.ts +180 -119
- package/src/web-ui-client.ts +132 -30
- package/src/web-ui-store.ts +68 -1
- package/src/web-ui-styles.ts +51 -5
- package/src/web-ui.ts +6 -3
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@poncho-ai/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "CLI for building and deploying AI agents",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
"react": "^19.2.4",
|
|
28
28
|
"react-devtools-core": "^6.1.5",
|
|
29
29
|
"yaml": "^2.8.1",
|
|
30
|
-
"@poncho-ai/harness": "0.
|
|
31
|
-
"@poncho-ai/messaging": "0.2.
|
|
32
|
-
"@poncho-ai/sdk": "1.
|
|
30
|
+
"@poncho-ai/harness": "0.19.0",
|
|
31
|
+
"@poncho-ai/messaging": "0.2.7",
|
|
32
|
+
"@poncho-ai/sdk": "1.4.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@types/busboy": "^1.5.4",
|
package/src/index.ts
CHANGED
|
@@ -545,11 +545,17 @@ export default {
|
|
|
545
545
|
],
|
|
546
546
|
// Tool access: true (available), false (disabled), 'approval' (requires human approval)
|
|
547
547
|
tools: {
|
|
548
|
+
list_directory: true,
|
|
549
|
+
read_file: true,
|
|
548
550
|
write_file: true, // gated by environment for writes
|
|
551
|
+
delete_file: 'approval', // requires human approval
|
|
552
|
+
delete_directory: 'approval', // requires human approval
|
|
549
553
|
send_email: 'approval', // requires human approval
|
|
550
554
|
byEnvironment: {
|
|
551
555
|
production: {
|
|
552
556
|
write_file: false,
|
|
557
|
+
delete_file: false,
|
|
558
|
+
delete_directory: false,
|
|
553
559
|
},
|
|
554
560
|
development: {
|
|
555
561
|
send_email: true, // skip approval in dev
|
|
@@ -632,6 +638,7 @@ Connect your agent to email so users can interact by sending emails:
|
|
|
632
638
|
RESEND_API_KEY=re_...
|
|
633
639
|
RESEND_WEBHOOK_SECRET=whsec_...
|
|
634
640
|
RESEND_FROM=Agent <agent@yourdomain.com>
|
|
641
|
+
RESEND_REPLY_TO=support@yourdomain.com # optional
|
|
635
642
|
\`\`\`
|
|
636
643
|
5. Add to \`poncho.config.js\`:
|
|
637
644
|
\`\`\`javascript
|
|
@@ -1463,7 +1470,7 @@ export const createRequestHandler = async (options?: {
|
|
|
1463
1470
|
// Pending subagent approvals: when a subagent hits an approval checkpoint,
|
|
1464
1471
|
// we store a resolver here so the approval endpoint can signal the waiting runSubagent.
|
|
1465
1472
|
type PendingSubagentApproval = {
|
|
1466
|
-
resolve: (
|
|
1473
|
+
resolve: (decidedApprovals: NonNullable<Conversation["pendingApprovals"]>) => void;
|
|
1467
1474
|
childHarness: AgentHarness;
|
|
1468
1475
|
checkpoint: NonNullable<Conversation["pendingApprovals"]>[number];
|
|
1469
1476
|
childConversationId: string;
|
|
@@ -1584,84 +1591,86 @@ export const createRequestHandler = async (options?: {
|
|
|
1584
1591
|
});
|
|
1585
1592
|
}
|
|
1586
1593
|
if (event.type === "tool:approval:checkpoint") {
|
|
1587
|
-
// Persist checkpoint so the approval endpoint can find it
|
|
1588
1594
|
const cpConv = await conversationStore.get(childConversationId);
|
|
1589
1595
|
if (cpConv) {
|
|
1590
|
-
const
|
|
1591
|
-
approvalId:
|
|
1596
|
+
const allCpData: NonNullable<Conversation["pendingApprovals"]> = event.approvals.map(a => ({
|
|
1597
|
+
approvalId: a.approvalId,
|
|
1592
1598
|
runId: latestRunId,
|
|
1593
|
-
tool:
|
|
1594
|
-
toolCallId:
|
|
1595
|
-
input:
|
|
1599
|
+
tool: a.tool,
|
|
1600
|
+
toolCallId: a.toolCallId,
|
|
1601
|
+
input: a.input,
|
|
1596
1602
|
checkpointMessages: [...historyMessages, ...event.checkpointMessages],
|
|
1597
1603
|
baseMessageCount: 0,
|
|
1598
1604
|
pendingToolCalls: event.pendingToolCalls,
|
|
1599
|
-
};
|
|
1600
|
-
cpConv.pendingApprovals =
|
|
1605
|
+
}));
|
|
1606
|
+
cpConv.pendingApprovals = allCpData;
|
|
1601
1607
|
cpConv.updatedAt = Date.now();
|
|
1602
1608
|
await conversationStore.update(cpConv);
|
|
1603
1609
|
|
|
1604
|
-
// Wait for approval
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1610
|
+
// Wait for all approval decisions (signaled by the approval endpoint
|
|
1611
|
+
// once every entry in the batch has a decision)
|
|
1612
|
+
const decidedApprovals = await new Promise<NonNullable<Conversation["pendingApprovals"]>>((resolve) => {
|
|
1613
|
+
for (const cpData of allCpData) {
|
|
1614
|
+
pendingSubagentApprovals.set(cpData.approvalId, {
|
|
1615
|
+
resolve,
|
|
1616
|
+
childHarness,
|
|
1617
|
+
checkpoint: cpData,
|
|
1618
|
+
childConversationId,
|
|
1619
|
+
parentConversationId,
|
|
1620
|
+
});
|
|
1621
|
+
}
|
|
1613
1622
|
});
|
|
1614
1623
|
|
|
1615
|
-
// Execute
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1624
|
+
// Execute approved tools and collect results
|
|
1625
|
+
const checkpointRef = allCpData[0]!;
|
|
1626
|
+
const toolContext = {
|
|
1627
|
+
runId: checkpointRef.runId,
|
|
1628
|
+
agentId: identity.id,
|
|
1629
|
+
step: 0,
|
|
1630
|
+
workingDir,
|
|
1631
|
+
parameters: {},
|
|
1632
|
+
conversationId: childConversationId,
|
|
1633
|
+
};
|
|
1634
|
+
|
|
1635
|
+
const approvalToolCallIds = new Set(decidedApprovals.map(a => a.toolCallId));
|
|
1636
|
+
const callsToExecute: Array<{ id: string; name: string; input: Record<string, unknown> }> = [];
|
|
1637
|
+
const deniedResults: Array<{ callId: string; toolName: string; error: string }> = [];
|
|
1638
|
+
|
|
1639
|
+
for (const a of decidedApprovals) {
|
|
1640
|
+
if (a.decision === "approved" && a.toolCallId) {
|
|
1641
|
+
callsToExecute.push({ id: a.toolCallId, name: a.tool, input: a.input });
|
|
1642
|
+
const toolText = `- done \`${a.tool}\``;
|
|
1643
|
+
toolTimeline.push(toolText);
|
|
1644
|
+
currentTools.push(toolText);
|
|
1645
|
+
} else if (a.toolCallId) {
|
|
1646
|
+
deniedResults.push({ callId: a.toolCallId, toolName: a.tool, error: "Tool execution denied by user" });
|
|
1647
|
+
const toolText = `- denied \`${a.tool}\``;
|
|
1648
|
+
toolTimeline.push(toolText);
|
|
1649
|
+
currentTools.push(toolText);
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
// Auto-approved tools that were deferred alongside approval-needing ones
|
|
1654
|
+
const pendingToolCalls = checkpointRef.pendingToolCalls ?? [];
|
|
1655
|
+
for (const tc of pendingToolCalls) {
|
|
1656
|
+
if (!approvalToolCallIds.has(tc.id)) {
|
|
1657
|
+
callsToExecute.push(tc);
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
let toolResults: Array<{ callId: string; toolName: string; result?: unknown; error?: string }> = [...deniedResults];
|
|
1662
|
+
if (callsToExecute.length > 0) {
|
|
1663
|
+
const execResults = await childHarness.executeTools(callsToExecute, toolContext);
|
|
1664
|
+
toolResults.push(...execResults.map(r => ({
|
|
1631
1665
|
callId: r.callId,
|
|
1632
1666
|
toolName: r.tool,
|
|
1633
1667
|
result: r.output,
|
|
1634
1668
|
error: r.error,
|
|
1635
|
-
}));
|
|
1636
|
-
const toolText = `- done \`${cpData.tool}\``;
|
|
1637
|
-
toolTimeline.push(toolText);
|
|
1638
|
-
currentTools.push(toolText);
|
|
1639
|
-
} else {
|
|
1640
|
-
toolResults = [{
|
|
1641
|
-
callId: cpData.toolCallId!,
|
|
1642
|
-
toolName: cpData.tool,
|
|
1643
|
-
error: "Tool execution denied by user",
|
|
1644
|
-
}];
|
|
1645
|
-
const toolText = `- denied \`${cpData.tool}\``;
|
|
1646
|
-
toolTimeline.push(toolText);
|
|
1647
|
-
currentTools.push(toolText);
|
|
1648
|
-
}
|
|
1649
|
-
|
|
1650
|
-
broadcastEvent(childConversationId,
|
|
1651
|
-
approved
|
|
1652
|
-
? { type: "tool:approval:granted", approvalId: event.approvalId }
|
|
1653
|
-
: { type: "tool:approval:denied", approvalId: event.approvalId },
|
|
1654
|
-
);
|
|
1655
|
-
|
|
1656
|
-
// Clear pending approval from conversation
|
|
1657
|
-
const cpConv2 = await conversationStore.get(childConversationId);
|
|
1658
|
-
if (cpConv2) {
|
|
1659
|
-
cpConv2.pendingApprovals = [];
|
|
1660
|
-
await conversationStore.update(cpConv2);
|
|
1669
|
+
})));
|
|
1661
1670
|
}
|
|
1662
1671
|
|
|
1663
1672
|
// Resume the run -- process continuation events
|
|
1664
|
-
const resumeMessages = [...
|
|
1673
|
+
const resumeMessages = [...checkpointRef.checkpointMessages!];
|
|
1665
1674
|
for await (const resumeEvent of childHarness.continueFromToolResult({
|
|
1666
1675
|
messages: resumeMessages,
|
|
1667
1676
|
toolResults,
|
|
@@ -1986,16 +1995,16 @@ export const createRequestHandler = async (options?: {
|
|
|
1986
1995
|
if (event.type === "tool:approval:checkpoint") {
|
|
1987
1996
|
const conv = await conversationStore.get(conversationId);
|
|
1988
1997
|
if (conv) {
|
|
1989
|
-
conv.pendingApprovals =
|
|
1990
|
-
approvalId:
|
|
1998
|
+
conv.pendingApprovals = event.approvals.map(a => ({
|
|
1999
|
+
approvalId: a.approvalId,
|
|
1991
2000
|
runId: latestRunId,
|
|
1992
|
-
tool:
|
|
1993
|
-
toolCallId:
|
|
1994
|
-
input:
|
|
2001
|
+
tool: a.tool,
|
|
2002
|
+
toolCallId: a.toolCallId,
|
|
2003
|
+
input: a.input,
|
|
1995
2004
|
checkpointMessages: [...fullCheckpointMessages, ...event.checkpointMessages],
|
|
1996
2005
|
baseMessageCount: 0,
|
|
1997
2006
|
pendingToolCalls: event.pendingToolCalls,
|
|
1998
|
-
}
|
|
2007
|
+
}));
|
|
1999
2008
|
conv.updatedAt = Date.now();
|
|
2000
2009
|
await conversationStore.update(conv);
|
|
2001
2010
|
}
|
|
@@ -2263,16 +2272,16 @@ export const createRequestHandler = async (options?: {
|
|
|
2263
2272
|
if (event.type === "tool:approval:checkpoint") {
|
|
2264
2273
|
await updateConversation((c) => {
|
|
2265
2274
|
c.messages = buildMessages();
|
|
2266
|
-
c.pendingApprovals =
|
|
2267
|
-
approvalId:
|
|
2275
|
+
c.pendingApprovals = event.approvals.map(a => ({
|
|
2276
|
+
approvalId: a.approvalId,
|
|
2268
2277
|
runId: latestRunId,
|
|
2269
|
-
tool:
|
|
2270
|
-
toolCallId:
|
|
2271
|
-
input:
|
|
2278
|
+
tool: a.tool,
|
|
2279
|
+
toolCallId: a.toolCallId,
|
|
2280
|
+
input: a.input,
|
|
2272
2281
|
checkpointMessages: event.checkpointMessages,
|
|
2273
2282
|
baseMessageCount: historyMessages.length,
|
|
2274
2283
|
pendingToolCalls: event.pendingToolCalls,
|
|
2275
|
-
}
|
|
2284
|
+
}));
|
|
2276
2285
|
});
|
|
2277
2286
|
checkpointedRun = true;
|
|
2278
2287
|
}
|
|
@@ -2352,9 +2361,9 @@ export const createRequestHandler = async (options?: {
|
|
|
2352
2361
|
waitUntil: waitUntilHook,
|
|
2353
2362
|
ownerId: "local-owner",
|
|
2354
2363
|
});
|
|
2355
|
-
adapter.registerRoutes(messagingRouteRegistrar);
|
|
2356
2364
|
try {
|
|
2357
2365
|
await bridge.start();
|
|
2366
|
+
adapter.registerRoutes(messagingRouteRegistrar);
|
|
2358
2367
|
messagingBridges.push(bridge);
|
|
2359
2368
|
console.log(` Slack messaging enabled at /api/messaging/slack`);
|
|
2360
2369
|
} catch (err) {
|
|
@@ -2367,6 +2376,7 @@ export const createRequestHandler = async (options?: {
|
|
|
2367
2376
|
apiKeyEnv: channelConfig.apiKeyEnv,
|
|
2368
2377
|
webhookSecretEnv: channelConfig.webhookSecretEnv,
|
|
2369
2378
|
fromEnv: channelConfig.fromEnv,
|
|
2379
|
+
replyToEnv: channelConfig.replyToEnv,
|
|
2370
2380
|
allowedSenders: channelConfig.allowedSenders,
|
|
2371
2381
|
mode: channelConfig.mode,
|
|
2372
2382
|
allowedRecipients: channelConfig.allowedRecipients,
|
|
@@ -2378,9 +2388,9 @@ export const createRequestHandler = async (options?: {
|
|
|
2378
2388
|
waitUntil: waitUntilHook,
|
|
2379
2389
|
ownerId: "local-owner",
|
|
2380
2390
|
});
|
|
2381
|
-
adapter.registerRoutes(messagingRouteRegistrar);
|
|
2382
2391
|
try {
|
|
2383
2392
|
await bridge.start();
|
|
2393
|
+
adapter.registerRoutes(messagingRouteRegistrar);
|
|
2384
2394
|
messagingBridges.push(bridge);
|
|
2385
2395
|
const adapterTools = adapter.getToolDefinitions?.() ?? [];
|
|
2386
2396
|
if (adapterTools.length > 0) {
|
|
@@ -2405,6 +2415,10 @@ export const createRequestHandler = async (options?: {
|
|
|
2405
2415
|
const authRequired = config?.auth?.required ?? false;
|
|
2406
2416
|
const requireAuth = authRequired && authToken.length > 0;
|
|
2407
2417
|
|
|
2418
|
+
if (requireAuth) {
|
|
2419
|
+
sessionStore.setSigningKey(authToken);
|
|
2420
|
+
}
|
|
2421
|
+
|
|
2408
2422
|
const webUiEnabled = config?.webUi !== false;
|
|
2409
2423
|
const isProduction = resolveHarnessEnvironment() === "production";
|
|
2410
2424
|
const secureCookies = isProduction;
|
|
@@ -2493,8 +2507,10 @@ export const createRequestHandler = async (options?: {
|
|
|
2493
2507
|
}
|
|
2494
2508
|
|
|
2495
2509
|
const cookies = parseCookies(request);
|
|
2496
|
-
const
|
|
2497
|
-
const session =
|
|
2510
|
+
const cookieValue = cookies.poncho_session;
|
|
2511
|
+
const session = cookieValue
|
|
2512
|
+
? (sessionStore.get(cookieValue) ?? sessionStore.restoreFromSigned(cookieValue))
|
|
2513
|
+
: undefined;
|
|
2498
2514
|
const ownerId = session?.ownerId ?? "local-owner";
|
|
2499
2515
|
const requiresCsrfValidation =
|
|
2500
2516
|
request.method !== "GET" && request.method !== "HEAD" && request.method !== "OPTIONS";
|
|
@@ -2545,7 +2561,8 @@ export const createRequestHandler = async (options?: {
|
|
|
2545
2561
|
}
|
|
2546
2562
|
loginRateLimiter.registerSuccess(ip);
|
|
2547
2563
|
const createdSession = sessionStore.create(ownerId);
|
|
2548
|
-
|
|
2564
|
+
const signedValue = sessionStore.signSession(createdSession);
|
|
2565
|
+
setCookie(response, "poncho_session", signedValue ?? createdSession.sessionId, {
|
|
2549
2566
|
httpOnly: true,
|
|
2550
2567
|
secure: secureCookies,
|
|
2551
2568
|
sameSite: "Lax",
|
|
@@ -2787,14 +2804,32 @@ export const createRequestHandler = async (options?: {
|
|
|
2787
2804
|
// Check if this is a pending subagent approval (handled inline by runSubagent)
|
|
2788
2805
|
const pendingSubagent = pendingSubagentApprovals.get(approvalId);
|
|
2789
2806
|
if (pendingSubagent) {
|
|
2790
|
-
|
|
2791
|
-
|
|
2807
|
+
pendingSubagent.checkpoint.decision = approved ? "approved" : "denied";
|
|
2808
|
+
|
|
2809
|
+
broadcastEvent(pendingSubagent.childConversationId,
|
|
2810
|
+
approved
|
|
2811
|
+
? { type: "tool:approval:granted", approvalId }
|
|
2812
|
+
: { type: "tool:approval:denied", approvalId },
|
|
2813
|
+
);
|
|
2814
|
+
|
|
2792
2815
|
const childConv = await conversationStore.get(pendingSubagent.childConversationId);
|
|
2793
|
-
|
|
2794
|
-
|
|
2816
|
+
const allApprovals = childConv?.pendingApprovals ?? [];
|
|
2817
|
+
const allDecided = allApprovals.length > 0 && allApprovals.every(pa => pa.decision != null);
|
|
2818
|
+
|
|
2819
|
+
if (allDecided) {
|
|
2820
|
+
// All approvals in the batch are decided — resolve the subagent
|
|
2821
|
+
for (const pa of allApprovals) {
|
|
2822
|
+
pendingSubagentApprovals.delete(pa.approvalId);
|
|
2823
|
+
}
|
|
2824
|
+
if (childConv) {
|
|
2825
|
+
childConv.pendingApprovals = [];
|
|
2826
|
+
await conversationStore.update(childConv);
|
|
2827
|
+
}
|
|
2828
|
+
pendingSubagent.resolve(allApprovals);
|
|
2829
|
+
} else if (childConv) {
|
|
2795
2830
|
await conversationStore.update(childConv);
|
|
2796
2831
|
}
|
|
2797
|
-
|
|
2832
|
+
|
|
2798
2833
|
writeJson(response, 200, { ok: true, approvalId, approved });
|
|
2799
2834
|
return;
|
|
2800
2835
|
}
|
|
@@ -2824,7 +2859,6 @@ export const createRequestHandler = async (options?: {
|
|
|
2824
2859
|
const conversationId = foundConversation.conversationId;
|
|
2825
2860
|
|
|
2826
2861
|
if (!foundApproval.checkpointMessages || !foundApproval.toolCallId) {
|
|
2827
|
-
// Legacy approval without checkpoint data — cannot resume
|
|
2828
2862
|
foundConversation.pendingApprovals = (foundConversation.pendingApprovals ?? [])
|
|
2829
2863
|
.filter(a => a.approvalId !== approvalId);
|
|
2830
2864
|
await conversationStore.update(foundConversation);
|
|
@@ -2835,55 +2869,82 @@ export const createRequestHandler = async (options?: {
|
|
|
2835
2869
|
return;
|
|
2836
2870
|
}
|
|
2837
2871
|
|
|
2838
|
-
//
|
|
2839
|
-
|
|
2840
|
-
.filter(a => a.approvalId !== approvalId);
|
|
2841
|
-
await conversationStore.update(foundConversation);
|
|
2872
|
+
// Record the decision on this approval entry
|
|
2873
|
+
foundApproval.decision = approved ? "approved" : "denied";
|
|
2842
2874
|
|
|
2843
|
-
// Initialize the event stream so the web UI can connect immediately
|
|
2844
2875
|
broadcastEvent(conversationId,
|
|
2845
2876
|
approved
|
|
2846
2877
|
? { type: "tool:approval:granted", approvalId }
|
|
2847
2878
|
: { type: "tool:approval:denied", approvalId },
|
|
2848
2879
|
);
|
|
2849
2880
|
|
|
2850
|
-
|
|
2881
|
+
const allApprovals = foundConversation.pendingApprovals ?? [];
|
|
2882
|
+
const allDecided = allApprovals.length > 0 && allApprovals.every(a => a.decision != null);
|
|
2883
|
+
|
|
2884
|
+
if (!allDecided) {
|
|
2885
|
+
// Still waiting for more decisions — persist and respond
|
|
2886
|
+
await conversationStore.update(foundConversation);
|
|
2887
|
+
writeJson(response, 200, { ok: true, approvalId, approved, batchComplete: false });
|
|
2888
|
+
return;
|
|
2889
|
+
}
|
|
2890
|
+
|
|
2891
|
+
// All approvals in the batch are decided — execute and resume
|
|
2892
|
+
foundConversation.pendingApprovals = [];
|
|
2893
|
+
await conversationStore.update(foundConversation);
|
|
2894
|
+
|
|
2895
|
+
// Use the first approval as the checkpoint reference (all share the same checkpoint data)
|
|
2896
|
+
const checkpointRef = allApprovals[0]!;
|
|
2897
|
+
|
|
2851
2898
|
void (async () => {
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2899
|
+
const toolContext = {
|
|
2900
|
+
runId: checkpointRef.runId,
|
|
2901
|
+
agentId: identity.id,
|
|
2902
|
+
step: 0,
|
|
2903
|
+
workingDir,
|
|
2904
|
+
parameters: {},
|
|
2905
|
+
};
|
|
2906
|
+
|
|
2907
|
+
// Collect tool calls to execute: approved approval-gated tools + auto-approved deferred tools
|
|
2908
|
+
const approvalToolCallIds = new Set(allApprovals.map(a => a.toolCallId));
|
|
2909
|
+
const callsToExecute: Array<{ id: string; name: string; input: Record<string, unknown> }> = [];
|
|
2910
|
+
const deniedResults: Array<{ callId: string; toolName: string; error: string }> = [];
|
|
2911
|
+
|
|
2912
|
+
for (const a of allApprovals) {
|
|
2913
|
+
if (a.decision === "approved" && a.toolCallId) {
|
|
2914
|
+
callsToExecute.push({ id: a.toolCallId, name: a.tool, input: a.input });
|
|
2915
|
+
} else if (a.decision === "denied" && a.toolCallId) {
|
|
2916
|
+
deniedResults.push({ callId: a.toolCallId, toolName: a.tool, error: "Tool execution denied by user" });
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
|
|
2920
|
+
// Auto-approved tools that were deferred alongside the approval-needing ones
|
|
2921
|
+
const pendingToolCalls = checkpointRef.pendingToolCalls ?? [];
|
|
2922
|
+
for (const tc of pendingToolCalls) {
|
|
2923
|
+
if (!approvalToolCallIds.has(tc.id)) {
|
|
2924
|
+
callsToExecute.push(tc);
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2927
|
+
|
|
2928
|
+
let toolResults: Array<{ callId: string; toolName: string; result?: unknown; error?: string }> = [...deniedResults];
|
|
2929
|
+
if (callsToExecute.length > 0) {
|
|
2930
|
+
const execResults = await harness.executeTools(callsToExecute, toolContext);
|
|
2931
|
+
toolResults.push(...execResults.map(r => ({
|
|
2866
2932
|
callId: r.callId,
|
|
2867
2933
|
toolName: r.tool,
|
|
2868
2934
|
result: r.output,
|
|
2869
2935
|
error: r.error,
|
|
2870
|
-
}));
|
|
2871
|
-
} else {
|
|
2872
|
-
toolResults = [{
|
|
2873
|
-
callId: foundApproval.toolCallId!,
|
|
2874
|
-
toolName: foundApproval.tool,
|
|
2875
|
-
error: "Tool execution denied by user",
|
|
2876
|
-
}];
|
|
2936
|
+
})));
|
|
2877
2937
|
}
|
|
2938
|
+
|
|
2878
2939
|
await resumeRunFromCheckpoint(
|
|
2879
2940
|
conversationId,
|
|
2880
2941
|
foundConversation!,
|
|
2881
|
-
|
|
2942
|
+
checkpointRef,
|
|
2882
2943
|
toolResults,
|
|
2883
2944
|
);
|
|
2884
2945
|
})();
|
|
2885
2946
|
|
|
2886
|
-
writeJson(response, 200, { ok: true, approvalId, approved });
|
|
2947
|
+
writeJson(response, 200, { ok: true, approvalId, approved, batchComplete: true });
|
|
2887
2948
|
return;
|
|
2888
2949
|
}
|
|
2889
2950
|
|
|
@@ -3402,16 +3463,16 @@ export const createRequestHandler = async (options?: {
|
|
|
3402
3463
|
}]
|
|
3403
3464
|
: []),
|
|
3404
3465
|
];
|
|
3405
|
-
conversation.pendingApprovals =
|
|
3406
|
-
approvalId:
|
|
3466
|
+
conversation.pendingApprovals = event.approvals.map(a => ({
|
|
3467
|
+
approvalId: a.approvalId,
|
|
3407
3468
|
runId: latestRunId,
|
|
3408
|
-
tool:
|
|
3409
|
-
toolCallId:
|
|
3410
|
-
input:
|
|
3469
|
+
tool: a.tool,
|
|
3470
|
+
toolCallId: a.toolCallId,
|
|
3471
|
+
input: a.input,
|
|
3411
3472
|
checkpointMessages: event.checkpointMessages,
|
|
3412
3473
|
baseMessageCount: historyMessages.length,
|
|
3413
3474
|
pendingToolCalls: event.pendingToolCalls,
|
|
3414
|
-
}
|
|
3475
|
+
}));
|
|
3415
3476
|
conversation.updatedAt = Date.now();
|
|
3416
3477
|
await conversationStore.update(conversation);
|
|
3417
3478
|
checkpointedRun = true;
|