@poncho-ai/cli 0.17.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/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  main
4
- } from "./chunk-GDB5X2OS.js";
4
+ } from "./chunk-7P53QSP5.js";
5
5
 
6
6
  // src/cli.ts
7
7
  void main();
package/dist/index.d.ts CHANGED
@@ -66,6 +66,7 @@ declare const mcpAdd: (workingDir: string, options: {
66
66
  name?: string;
67
67
  envVars?: string[];
68
68
  authBearerEnv?: string;
69
+ headers?: string[];
69
70
  }) => Promise<void>;
70
71
  declare const mcpList: (workingDir: string) => Promise<void>;
71
72
  declare const mcpRemove: (workingDir: string, name: string) => Promise<void>;
package/dist/index.js CHANGED
@@ -23,7 +23,7 @@ import {
23
23
  runTests,
24
24
  startDevServer,
25
25
  updateAgentGuidance
26
- } from "./chunk-GDB5X2OS.js";
26
+ } from "./chunk-7P53QSP5.js";
27
27
  export {
28
28
  addSkill,
29
29
  buildCli,
@@ -2,7 +2,7 @@ import {
2
2
  consumeFirstRunIntro,
3
3
  inferConversationTitle,
4
4
  resolveHarnessEnvironment
5
- } from "./chunk-GDB5X2OS.js";
5
+ } from "./chunk-7P53QSP5.js";
6
6
 
7
7
  // src/run-interactive-ink.ts
8
8
  import * as readline from "readline";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poncho-ai/cli",
3
- "version": "0.17.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.17.0",
31
- "@poncho-ai/messaging": "0.2.5",
32
- "@poncho-ai/sdk": "1.2.0"
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
@@ -455,6 +455,10 @@ Connect remote MCP servers and expose their tools to the agent:
455
455
  # Add remote MCP server
456
456
  poncho mcp add --url https://mcp.example.com/github --name github --auth-bearer-env GITHUB_TOKEN
457
457
 
458
+ # Server with custom headers (e.g. Arcade)
459
+ poncho mcp add --url https://mcp.arcade.dev --name arcade \\
460
+ --auth-bearer-env ARCADE_API_KEY --header "Arcade-User-ID: user@example.com"
461
+
458
462
  # List configured servers
459
463
  poncho mcp list
460
464
 
@@ -536,14 +540,22 @@ export default {
536
540
  url: "https://mcp.example.com/github",
537
541
  auth: { type: "bearer", tokenEnv: "GITHUB_TOKEN" },
538
542
  },
543
+ // Custom headers for servers that require them (e.g. Arcade)
544
+ // { name: "arcade", url: "https://mcp.arcade.dev", auth: { type: "bearer", tokenEnv: "ARCADE_API_KEY" }, headers: { "Arcade-User-ID": "user@example.com" } },
539
545
  ],
540
546
  // Tool access: true (available), false (disabled), 'approval' (requires human approval)
541
547
  tools: {
548
+ list_directory: true,
549
+ read_file: true,
542
550
  write_file: true, // gated by environment for writes
551
+ delete_file: 'approval', // requires human approval
552
+ delete_directory: 'approval', // requires human approval
543
553
  send_email: 'approval', // requires human approval
544
554
  byEnvironment: {
545
555
  production: {
546
556
  write_file: false,
557
+ delete_file: false,
558
+ delete_directory: false,
547
559
  },
548
560
  development: {
549
561
  send_email: true, // skip approval in dev
@@ -626,6 +638,7 @@ Connect your agent to email so users can interact by sending emails:
626
638
  RESEND_API_KEY=re_...
627
639
  RESEND_WEBHOOK_SECRET=whsec_...
628
640
  RESEND_FROM=Agent <agent@yourdomain.com>
641
+ RESEND_REPLY_TO=support@yourdomain.com # optional
629
642
  \`\`\`
630
643
  5. Add to \`poncho.config.js\`:
631
644
  \`\`\`javascript
@@ -1457,7 +1470,7 @@ export const createRequestHandler = async (options?: {
1457
1470
  // Pending subagent approvals: when a subagent hits an approval checkpoint,
1458
1471
  // we store a resolver here so the approval endpoint can signal the waiting runSubagent.
1459
1472
  type PendingSubagentApproval = {
1460
- resolve: (approved: boolean) => void;
1473
+ resolve: (decidedApprovals: NonNullable<Conversation["pendingApprovals"]>) => void;
1461
1474
  childHarness: AgentHarness;
1462
1475
  checkpoint: NonNullable<Conversation["pendingApprovals"]>[number];
1463
1476
  childConversationId: string;
@@ -1578,84 +1591,86 @@ export const createRequestHandler = async (options?: {
1578
1591
  });
1579
1592
  }
1580
1593
  if (event.type === "tool:approval:checkpoint") {
1581
- // Persist checkpoint so the approval endpoint can find it
1582
1594
  const cpConv = await conversationStore.get(childConversationId);
1583
1595
  if (cpConv) {
1584
- const cpData: NonNullable<Conversation["pendingApprovals"]>[number] = {
1585
- approvalId: event.approvalId,
1596
+ const allCpData: NonNullable<Conversation["pendingApprovals"]> = event.approvals.map(a => ({
1597
+ approvalId: a.approvalId,
1586
1598
  runId: latestRunId,
1587
- tool: event.tool,
1588
- toolCallId: event.toolCallId,
1589
- input: event.input,
1599
+ tool: a.tool,
1600
+ toolCallId: a.toolCallId,
1601
+ input: a.input,
1590
1602
  checkpointMessages: [...historyMessages, ...event.checkpointMessages],
1591
1603
  baseMessageCount: 0,
1592
1604
  pendingToolCalls: event.pendingToolCalls,
1593
- };
1594
- cpConv.pendingApprovals = [cpData];
1605
+ }));
1606
+ cpConv.pendingApprovals = allCpData;
1595
1607
  cpConv.updatedAt = Date.now();
1596
1608
  await conversationStore.update(cpConv);
1597
1609
 
1598
- // Wait for approval decision (signaled by the approval endpoint)
1599
- const approved = await new Promise<boolean>((resolve) => {
1600
- pendingSubagentApprovals.set(event.approvalId, {
1601
- resolve,
1602
- childHarness,
1603
- checkpoint: cpData,
1604
- childConversationId,
1605
- parentConversationId,
1606
- });
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
+ }
1607
1622
  });
1608
1623
 
1609
- // Execute tool and resume the run
1610
- let toolResults: Array<{ callId: string; toolName: string; result?: unknown; error?: string }>;
1611
- if (approved) {
1612
- const toolContext = {
1613
- runId: cpData.runId,
1614
- agentId: identity.id,
1615
- step: 0,
1616
- workingDir,
1617
- parameters: {},
1618
- conversationId: childConversationId,
1619
- };
1620
- const execResults = await childHarness.executeTools(
1621
- [{ id: cpData.toolCallId!, name: cpData.tool, input: cpData.input }],
1622
- toolContext,
1623
- );
1624
- toolResults = execResults.map(r => ({
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 => ({
1625
1665
  callId: r.callId,
1626
1666
  toolName: r.tool,
1627
1667
  result: r.output,
1628
1668
  error: r.error,
1629
- }));
1630
- const toolText = `- done \`${cpData.tool}\``;
1631
- toolTimeline.push(toolText);
1632
- currentTools.push(toolText);
1633
- } else {
1634
- toolResults = [{
1635
- callId: cpData.toolCallId!,
1636
- toolName: cpData.tool,
1637
- error: "Tool execution denied by user",
1638
- }];
1639
- const toolText = `- denied \`${cpData.tool}\``;
1640
- toolTimeline.push(toolText);
1641
- currentTools.push(toolText);
1642
- }
1643
-
1644
- broadcastEvent(childConversationId,
1645
- approved
1646
- ? { type: "tool:approval:granted", approvalId: event.approvalId }
1647
- : { type: "tool:approval:denied", approvalId: event.approvalId },
1648
- );
1649
-
1650
- // Clear pending approval from conversation
1651
- const cpConv2 = await conversationStore.get(childConversationId);
1652
- if (cpConv2) {
1653
- cpConv2.pendingApprovals = [];
1654
- await conversationStore.update(cpConv2);
1669
+ })));
1655
1670
  }
1656
1671
 
1657
1672
  // Resume the run -- process continuation events
1658
- const resumeMessages = [...cpData.checkpointMessages!];
1673
+ const resumeMessages = [...checkpointRef.checkpointMessages!];
1659
1674
  for await (const resumeEvent of childHarness.continueFromToolResult({
1660
1675
  messages: resumeMessages,
1661
1676
  toolResults,
@@ -1980,16 +1995,16 @@ export const createRequestHandler = async (options?: {
1980
1995
  if (event.type === "tool:approval:checkpoint") {
1981
1996
  const conv = await conversationStore.get(conversationId);
1982
1997
  if (conv) {
1983
- conv.pendingApprovals = [{
1984
- approvalId: event.approvalId,
1998
+ conv.pendingApprovals = event.approvals.map(a => ({
1999
+ approvalId: a.approvalId,
1985
2000
  runId: latestRunId,
1986
- tool: event.tool,
1987
- toolCallId: event.toolCallId,
1988
- input: event.input,
2001
+ tool: a.tool,
2002
+ toolCallId: a.toolCallId,
2003
+ input: a.input,
1989
2004
  checkpointMessages: [...fullCheckpointMessages, ...event.checkpointMessages],
1990
2005
  baseMessageCount: 0,
1991
2006
  pendingToolCalls: event.pendingToolCalls,
1992
- }];
2007
+ }));
1993
2008
  conv.updatedAt = Date.now();
1994
2009
  await conversationStore.update(conv);
1995
2010
  }
@@ -2257,16 +2272,16 @@ export const createRequestHandler = async (options?: {
2257
2272
  if (event.type === "tool:approval:checkpoint") {
2258
2273
  await updateConversation((c) => {
2259
2274
  c.messages = buildMessages();
2260
- c.pendingApprovals = [{
2261
- approvalId: event.approvalId,
2275
+ c.pendingApprovals = event.approvals.map(a => ({
2276
+ approvalId: a.approvalId,
2262
2277
  runId: latestRunId,
2263
- tool: event.tool,
2264
- toolCallId: event.toolCallId,
2265
- input: event.input,
2278
+ tool: a.tool,
2279
+ toolCallId: a.toolCallId,
2280
+ input: a.input,
2266
2281
  checkpointMessages: event.checkpointMessages,
2267
2282
  baseMessageCount: historyMessages.length,
2268
2283
  pendingToolCalls: event.pendingToolCalls,
2269
- }];
2284
+ }));
2270
2285
  });
2271
2286
  checkpointedRun = true;
2272
2287
  }
@@ -2346,9 +2361,9 @@ export const createRequestHandler = async (options?: {
2346
2361
  waitUntil: waitUntilHook,
2347
2362
  ownerId: "local-owner",
2348
2363
  });
2349
- adapter.registerRoutes(messagingRouteRegistrar);
2350
2364
  try {
2351
2365
  await bridge.start();
2366
+ adapter.registerRoutes(messagingRouteRegistrar);
2352
2367
  messagingBridges.push(bridge);
2353
2368
  console.log(` Slack messaging enabled at /api/messaging/slack`);
2354
2369
  } catch (err) {
@@ -2361,6 +2376,7 @@ export const createRequestHandler = async (options?: {
2361
2376
  apiKeyEnv: channelConfig.apiKeyEnv,
2362
2377
  webhookSecretEnv: channelConfig.webhookSecretEnv,
2363
2378
  fromEnv: channelConfig.fromEnv,
2379
+ replyToEnv: channelConfig.replyToEnv,
2364
2380
  allowedSenders: channelConfig.allowedSenders,
2365
2381
  mode: channelConfig.mode,
2366
2382
  allowedRecipients: channelConfig.allowedRecipients,
@@ -2372,9 +2388,9 @@ export const createRequestHandler = async (options?: {
2372
2388
  waitUntil: waitUntilHook,
2373
2389
  ownerId: "local-owner",
2374
2390
  });
2375
- adapter.registerRoutes(messagingRouteRegistrar);
2376
2391
  try {
2377
2392
  await bridge.start();
2393
+ adapter.registerRoutes(messagingRouteRegistrar);
2378
2394
  messagingBridges.push(bridge);
2379
2395
  const adapterTools = adapter.getToolDefinitions?.() ?? [];
2380
2396
  if (adapterTools.length > 0) {
@@ -2399,6 +2415,10 @@ export const createRequestHandler = async (options?: {
2399
2415
  const authRequired = config?.auth?.required ?? false;
2400
2416
  const requireAuth = authRequired && authToken.length > 0;
2401
2417
 
2418
+ if (requireAuth) {
2419
+ sessionStore.setSigningKey(authToken);
2420
+ }
2421
+
2402
2422
  const webUiEnabled = config?.webUi !== false;
2403
2423
  const isProduction = resolveHarnessEnvironment() === "production";
2404
2424
  const secureCookies = isProduction;
@@ -2487,8 +2507,10 @@ export const createRequestHandler = async (options?: {
2487
2507
  }
2488
2508
 
2489
2509
  const cookies = parseCookies(request);
2490
- const sessionId = cookies.poncho_session;
2491
- const session = sessionId ? sessionStore.get(sessionId) : undefined;
2510
+ const cookieValue = cookies.poncho_session;
2511
+ const session = cookieValue
2512
+ ? (sessionStore.get(cookieValue) ?? sessionStore.restoreFromSigned(cookieValue))
2513
+ : undefined;
2492
2514
  const ownerId = session?.ownerId ?? "local-owner";
2493
2515
  const requiresCsrfValidation =
2494
2516
  request.method !== "GET" && request.method !== "HEAD" && request.method !== "OPTIONS";
@@ -2539,7 +2561,8 @@ export const createRequestHandler = async (options?: {
2539
2561
  }
2540
2562
  loginRateLimiter.registerSuccess(ip);
2541
2563
  const createdSession = sessionStore.create(ownerId);
2542
- setCookie(response, "poncho_session", createdSession.sessionId, {
2564
+ const signedValue = sessionStore.signSession(createdSession);
2565
+ setCookie(response, "poncho_session", signedValue ?? createdSession.sessionId, {
2543
2566
  httpOnly: true,
2544
2567
  secure: secureCookies,
2545
2568
  sameSite: "Lax",
@@ -2781,14 +2804,32 @@ export const createRequestHandler = async (options?: {
2781
2804
  // Check if this is a pending subagent approval (handled inline by runSubagent)
2782
2805
  const pendingSubagent = pendingSubagentApprovals.get(approvalId);
2783
2806
  if (pendingSubagent) {
2784
- pendingSubagentApprovals.delete(approvalId);
2785
- // Clear pendingApprovals from the subagent's conversation immediately
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
+
2786
2815
  const childConv = await conversationStore.get(pendingSubagent.childConversationId);
2787
- if (childConv && Array.isArray(childConv.pendingApprovals)) {
2788
- childConv.pendingApprovals = childConv.pendingApprovals.filter(pa => pa.approvalId !== approvalId);
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) {
2789
2830
  await conversationStore.update(childConv);
2790
2831
  }
2791
- pendingSubagent.resolve(approved);
2832
+
2792
2833
  writeJson(response, 200, { ok: true, approvalId, approved });
2793
2834
  return;
2794
2835
  }
@@ -2818,7 +2859,6 @@ export const createRequestHandler = async (options?: {
2818
2859
  const conversationId = foundConversation.conversationId;
2819
2860
 
2820
2861
  if (!foundApproval.checkpointMessages || !foundApproval.toolCallId) {
2821
- // Legacy approval without checkpoint data — cannot resume
2822
2862
  foundConversation.pendingApprovals = (foundConversation.pendingApprovals ?? [])
2823
2863
  .filter(a => a.approvalId !== approvalId);
2824
2864
  await conversationStore.update(foundConversation);
@@ -2829,55 +2869,82 @@ export const createRequestHandler = async (options?: {
2829
2869
  return;
2830
2870
  }
2831
2871
 
2832
- // Clear this approval from the conversation before resuming
2833
- foundConversation.pendingApprovals = (foundConversation.pendingApprovals ?? [])
2834
- .filter(a => a.approvalId !== approvalId);
2835
- await conversationStore.update(foundConversation);
2872
+ // Record the decision on this approval entry
2873
+ foundApproval.decision = approved ? "approved" : "denied";
2836
2874
 
2837
- // Initialize the event stream so the web UI can connect immediately
2838
2875
  broadcastEvent(conversationId,
2839
2876
  approved
2840
2877
  ? { type: "tool:approval:granted", approvalId }
2841
2878
  : { type: "tool:approval:denied", approvalId },
2842
2879
  );
2843
2880
 
2844
- // Resume the run asynchronously (tool execution + continuation)
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
+
2845
2898
  void (async () => {
2846
- let toolResults: Array<{ callId: string; toolName: string; result?: unknown; error?: string }>;
2847
- if (approved) {
2848
- const toolContext = {
2849
- runId: foundApproval.runId,
2850
- agentId: identity.id,
2851
- step: 0,
2852
- workingDir,
2853
- parameters: {},
2854
- };
2855
- const execResults = await harness.executeTools(
2856
- [{ id: foundApproval.toolCallId!, name: foundApproval.tool, input: foundApproval.input }],
2857
- toolContext,
2858
- );
2859
- toolResults = execResults.map(r => ({
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 => ({
2860
2932
  callId: r.callId,
2861
2933
  toolName: r.tool,
2862
2934
  result: r.output,
2863
2935
  error: r.error,
2864
- }));
2865
- } else {
2866
- toolResults = [{
2867
- callId: foundApproval.toolCallId!,
2868
- toolName: foundApproval.tool,
2869
- error: "Tool execution denied by user",
2870
- }];
2936
+ })));
2871
2937
  }
2938
+
2872
2939
  await resumeRunFromCheckpoint(
2873
2940
  conversationId,
2874
2941
  foundConversation!,
2875
- foundApproval!,
2942
+ checkpointRef,
2876
2943
  toolResults,
2877
2944
  );
2878
2945
  })();
2879
2946
 
2880
- writeJson(response, 200, { ok: true, approvalId, approved });
2947
+ writeJson(response, 200, { ok: true, approvalId, approved, batchComplete: true });
2881
2948
  return;
2882
2949
  }
2883
2950
 
@@ -3396,16 +3463,16 @@ export const createRequestHandler = async (options?: {
3396
3463
  }]
3397
3464
  : []),
3398
3465
  ];
3399
- conversation.pendingApprovals = [{
3400
- approvalId: event.approvalId,
3466
+ conversation.pendingApprovals = event.approvals.map(a => ({
3467
+ approvalId: a.approvalId,
3401
3468
  runId: latestRunId,
3402
- tool: event.tool,
3403
- toolCallId: event.toolCallId,
3404
- input: event.input,
3469
+ tool: a.tool,
3470
+ toolCallId: a.toolCallId,
3471
+ input: a.input,
3405
3472
  checkpointMessages: event.checkpointMessages,
3406
3473
  baseMessageCount: historyMessages.length,
3407
3474
  pendingToolCalls: event.pendingToolCalls,
3408
- }];
3475
+ }));
3409
3476
  conversation.updatedAt = Date.now();
3410
3477
  await conversationStore.update(conversation);
3411
3478
  checkpointedRun = true;
@@ -3753,11 +3820,14 @@ export const createRequestHandler = async (options?: {
3753
3820
  handler._cronJobs = cronJobs;
3754
3821
  handler._conversationStore = conversationStore;
3755
3822
 
3756
- // Recover stale subagent runs that were "running" when the server last stopped
3823
+ // Recover stale subagent runs that were "running" when the server last stopped.
3824
+ // Pass no ownerId so we scan across all owners (not just "local-owner").
3757
3825
  try {
3758
- const allConvs = await conversationStore.list();
3759
- for (const conv of allConvs) {
3760
- if (conv.subagentMeta?.status === "running") {
3826
+ const allSummaries = await conversationStore.listSummaries();
3827
+ const subagentSummaries = allSummaries.filter((s) => s.parentConversationId);
3828
+ for (const s of subagentSummaries) {
3829
+ const conv = await conversationStore.get(s.conversationId);
3830
+ if (conv?.subagentMeta?.status === "running") {
3761
3831
  conv.subagentMeta.status = "stopped";
3762
3832
  conv.subagentMeta.error = { code: "SERVER_RESTART", message: "Interrupted by server restart" };
3763
3833
  conv.updatedAt = Date.now();
@@ -4535,6 +4605,7 @@ export const mcpAdd = async (
4535
4605
  name?: string;
4536
4606
  envVars?: string[];
4537
4607
  authBearerEnv?: string;
4608
+ headers?: string[];
4538
4609
  },
4539
4610
  ): Promise<void> => {
4540
4611
  const config = (await loadPonchoConfig(workingDir)) ?? { mcp: [] };
@@ -4548,6 +4619,18 @@ export const mcpAdd = async (
4548
4619
  if (!options.url.startsWith("http://") && !options.url.startsWith("https://")) {
4549
4620
  throw new Error("Invalid MCP URL. Expected http:// or https://.");
4550
4621
  }
4622
+ const parsedHeaders: Record<string, string> | undefined =
4623
+ options.headers && options.headers.length > 0
4624
+ ? Object.fromEntries(
4625
+ options.headers.map((h) => {
4626
+ const idx = h.indexOf(":");
4627
+ if (idx < 1) {
4628
+ throw new Error(`Invalid header format "${h}". Expected "Name: value".`);
4629
+ }
4630
+ return [h.slice(0, idx).trim(), h.slice(idx + 1).trim()];
4631
+ }),
4632
+ )
4633
+ : undefined;
4551
4634
  const serverName = options.name ?? normalizeMcpName({ url: options.url });
4552
4635
  mcp.push({
4553
4636
  name: serverName,
@@ -4559,6 +4642,7 @@ export const mcpAdd = async (
4559
4642
  tokenEnv: options.authBearerEnv,
4560
4643
  }
4561
4644
  : undefined,
4645
+ headers: parsedHeaders,
4562
4646
  });
4563
4647
 
4564
4648
  await writeConfigFile(workingDir, { ...config, mcp });
@@ -4605,8 +4689,10 @@ export const mcpList = async (workingDir: string): Promise<void> => {
4605
4689
  for (const entry of mcp) {
4606
4690
  const auth =
4607
4691
  entry.auth?.type === "bearer" ? `auth=bearer:${entry.auth.tokenEnv}` : "auth=none";
4692
+ const headerKeys = entry.headers ? Object.keys(entry.headers) : [];
4693
+ const headerInfo = headerKeys.length > 0 ? `, headers=${headerKeys.join(",")}` : "";
4608
4694
  process.stdout.write(
4609
- `- ${entry.name ?? entry.url} (remote: ${entry.url}, ${auth})\n`,
4695
+ `- ${entry.name ?? entry.url} (remote: ${entry.url}, ${auth}${headerInfo})\n`,
4610
4696
  );
4611
4697
  }
4612
4698
  };
@@ -4933,6 +5019,10 @@ export const buildCli = (): Command => {
4933
5019
  all.push(value);
4934
5020
  return all;
4935
5021
  }, [] as string[])
5022
+ .option("--header <header>", "custom header as 'Name: value' (repeatable)", (value, all: string[]) => {
5023
+ all.push(value);
5024
+ return all;
5025
+ }, [] as string[])
4936
5026
  .action(
4937
5027
  async (
4938
5028
  options: {
@@ -4940,6 +5030,7 @@ export const buildCli = (): Command => {
4940
5030
  name?: string;
4941
5031
  authBearerEnv?: string;
4942
5032
  env: string[];
5033
+ header: string[];
4943
5034
  },
4944
5035
  ) => {
4945
5036
  await mcpAdd(process.cwd(), {
@@ -4947,6 +5038,7 @@ export const buildCli = (): Command => {
4947
5038
  name: options.name,
4948
5039
  envVars: options.env,
4949
5040
  authBearerEnv: options.authBearerEnv,
5041
+ headers: options.header,
4950
5042
  });
4951
5043
  },
4952
5044
  );