@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.
@@ -310,6 +310,22 @@ var WEB_UI_STYLES = `
310
310
  flex-direction: column;
311
311
  padding: 12px 8px;
312
312
  }
313
+ .sidebar-header {
314
+ display: flex;
315
+ align-items: center;
316
+ gap: 8px;
317
+ }
318
+ .sidebar-agent-name {
319
+ font-size: 14px;
320
+ font-weight: 500;
321
+ color: var(--fg-strong);
322
+ flex: 1;
323
+ min-width: 0;
324
+ overflow: hidden;
325
+ text-overflow: ellipsis;
326
+ white-space: nowrap;
327
+ padding-left: 10px;
328
+ }
313
329
  .new-chat-btn {
314
330
  background: transparent;
315
331
  border: 0;
@@ -322,6 +338,7 @@ var WEB_UI_STYLES = `
322
338
  gap: 8px;
323
339
  font-size: 13px;
324
340
  cursor: pointer;
341
+ flex-shrink: 0;
325
342
  transition: background 0.15s, color 0.15s;
326
343
  }
327
344
  .new-chat-btn:hover { color: var(--fg); }
@@ -824,6 +841,30 @@ var WEB_UI_STYLES = `
824
841
  opacity: 0.55;
825
842
  cursor: not-allowed;
826
843
  }
844
+ .approval-batch-actions {
845
+ display: flex;
846
+ gap: 6px;
847
+ margin-bottom: 8px;
848
+ }
849
+ .approval-batch-btn {
850
+ border-radius: 6px;
851
+ border: 1px solid var(--border-5);
852
+ background: var(--surface-4);
853
+ color: var(--fg-approval-btn);
854
+ font-size: 11px;
855
+ font-weight: 600;
856
+ padding: 4px 10px;
857
+ cursor: pointer;
858
+ }
859
+ .approval-batch-btn:hover { background: var(--surface-7); }
860
+ .approval-batch-btn.approve {
861
+ border-color: var(--approve-border);
862
+ color: var(--approve);
863
+ }
864
+ .approval-batch-btn.deny {
865
+ border-color: var(--deny-border);
866
+ color: var(--deny);
867
+ }
827
868
  .user-bubble {
828
869
  background: var(--bg-elevated);
829
870
  border: 1px solid var(--border-2);
@@ -1220,6 +1261,9 @@ var WEB_UI_STYLES = `
1220
1261
  .shell.sidebar-open .sidebar { transform: translateX(0); }
1221
1262
  .sidebar-toggle { display: grid; place-items: center; }
1222
1263
  .topbar-new-chat { display: grid; place-items: center; }
1264
+ .sidebar-header { padding-right: 130px; }
1265
+ .sidebar-agent-name { padding-left: 0; }
1266
+ .new-chat-btn { order: -1; }
1223
1267
  .poncho-badge {
1224
1268
  display: none;
1225
1269
  position: fixed;
@@ -1359,15 +1403,17 @@ var WEB_UI_STYLES = `
1359
1403
  font-size: 13px;
1360
1404
  }
1361
1405
  @media (max-width: 768px) {
1406
+ .main-body { flex-direction: column; }
1362
1407
  .browser-panel {
1363
- position: fixed;
1364
- inset: 0;
1365
- width: 100% !important;
1408
+ position: relative;
1409
+ order: -1;
1410
+ max-height: 35vh;
1366
1411
  flex: none !important;
1367
- z-index: 200;
1412
+ width: auto !important;
1413
+ border-bottom: 1px solid var(--border-1);
1368
1414
  }
1369
1415
  .browser-panel-resize { display: none !important; }
1370
- .main-chat.has-browser { flex: 1 1 auto !important; min-width: 0; }
1416
+ .main-chat.has-browser { flex: 1 1 auto !important; min-width: 0; min-height: 0; }
1371
1417
  }
1372
1418
 
1373
1419
  /* --- Subagent UI --- */
@@ -1649,6 +1695,62 @@ var getWebUiClientScript = (markedSource2) => `
1649
1695
  }
1650
1696
  };
1651
1697
 
1698
+ // During streaming, incomplete backtick sequences cause marked to
1699
+ // swallow all subsequent text into an invisible code element. This
1700
+ // helper detects unclosed fences and inline code delimiters and
1701
+ // appends the missing closing so marked can render partial text.
1702
+ const closeStreamingMarkdown = (text) => {
1703
+ const BT = "\\x60";
1704
+ let result = text;
1705
+
1706
+ // 1. Unclosed fenced code blocks (lines starting with 3+ backticks)
1707
+ const lines = result.split("\\n");
1708
+ let openFenceLen = 0;
1709
+ for (let li = 0; li < lines.length; li++) {
1710
+ const trimmed = lines[li].trimStart();
1711
+ let btCount = 0;
1712
+ while (btCount < trimmed.length && trimmed[btCount] === BT) btCount++;
1713
+ if (btCount >= 3) {
1714
+ if (openFenceLen === 0) {
1715
+ openFenceLen = btCount;
1716
+ } else if (btCount >= openFenceLen) {
1717
+ openFenceLen = 0;
1718
+ }
1719
+ }
1720
+ }
1721
+ if (openFenceLen > 0) {
1722
+ let fence = "";
1723
+ for (let k = 0; k < openFenceLen; k++) fence += BT;
1724
+ return result + "\\n" + fence;
1725
+ }
1726
+
1727
+ // 2. Unclosed inline code delimiters
1728
+ let idx = 0;
1729
+ let inCode = false;
1730
+ let delimLen = 0;
1731
+ while (idx < result.length) {
1732
+ if (result[idx] === BT) {
1733
+ let run = 0;
1734
+ while (idx < result.length && result[idx] === BT) { run++; idx++; }
1735
+ if (!inCode) {
1736
+ inCode = true;
1737
+ delimLen = run;
1738
+ } else if (run === delimLen) {
1739
+ inCode = false;
1740
+ }
1741
+ } else {
1742
+ idx++;
1743
+ }
1744
+ }
1745
+ if (inCode) {
1746
+ let closing = "";
1747
+ for (let k = 0; k < delimLen; k++) closing += BT;
1748
+ result += closing;
1749
+ }
1750
+
1751
+ return result;
1752
+ };
1753
+
1652
1754
  const extractToolActivity = (value) => {
1653
1755
  const source = String(value || "");
1654
1756
  let markerIndex = source.lastIndexOf("\\n### Tool activity\\n");
@@ -1715,8 +1817,15 @@ var getWebUiClientScript = (markedSource2) => `
1715
1817
  );
1716
1818
  })
1717
1819
  .join("");
1820
+ const batchButtons = requests.length > 1
1821
+ ? '<div class="approval-batch-actions">' +
1822
+ '<button class="approval-batch-btn approve" data-approval-batch="approve">Approve all (' + requests.length + ')</button>' +
1823
+ '<button class="approval-batch-btn deny" data-approval-batch="deny">Deny all (' + requests.length + ')</button>' +
1824
+ "</div>"
1825
+ : "";
1718
1826
  return (
1719
1827
  '<div class="approval-requests">' +
1828
+ batchButtons +
1720
1829
  rows +
1721
1830
  "</div>"
1722
1831
  );
@@ -2246,7 +2355,7 @@ var getWebUiClientScript = (markedSource2) => `
2246
2355
  // Show current text being typed
2247
2356
  if (isStreaming && i === messages.length - 1 && m._currentText) {
2248
2357
  const textDiv = document.createElement("div");
2249
- textDiv.innerHTML = renderAssistantMarkdown(m._currentText);
2358
+ textDiv.innerHTML = renderAssistantMarkdown(closeStreamingMarkdown(m._currentText));
2250
2359
  content.appendChild(textDiv);
2251
2360
  }
2252
2361
  } else {
@@ -3828,45 +3937,20 @@ var getWebUiClientScript = (markedSource2) => `
3828
3937
  openLightbox(img.src);
3829
3938
  });
3830
3939
 
3831
- elements.messages.addEventListener("click", async (event) => {
3832
- const target = event.target;
3833
- if (!(target instanceof Element)) {
3834
- return;
3835
- }
3836
- const button = target.closest(".approval-action-btn");
3837
- if (!button) {
3838
- return;
3839
- }
3840
- const approvalId = button.getAttribute("data-approval-id") || "";
3841
- const decision = button.getAttribute("data-approval-decision") || "";
3842
- if (!approvalId || (decision !== "approve" && decision !== "deny")) {
3843
- return;
3844
- }
3845
- if (state.approvalRequestsInFlight[approvalId]) {
3846
- return;
3847
- }
3940
+ const submitApproval = async (approvalId, decision, opts) => {
3941
+ const wasStreaming = opts && opts.wasStreaming;
3848
3942
  state.approvalRequestsInFlight[approvalId] = true;
3849
- const wasStreaming = state.isStreaming;
3850
- if (!wasStreaming) {
3851
- setStreaming(true);
3852
- }
3853
3943
  updatePendingApproval(approvalId, (request) => ({
3854
3944
  ...request,
3855
3945
  state: "submitting",
3856
3946
  pendingDecision: decision,
3857
3947
  }));
3858
- renderMessages(state.activeMessages, state.isStreaming);
3859
3948
  try {
3860
3949
  await api("/api/approvals/" + encodeURIComponent(approvalId), {
3861
3950
  method: "POST",
3862
3951
  body: JSON.stringify({ approved: decision === "approve" }),
3863
3952
  });
3864
3953
  updatePendingApproval(approvalId, () => null);
3865
- renderMessages(state.activeMessages, state.isStreaming);
3866
- loadConversations();
3867
- if (!wasStreaming && state.activeConversationId) {
3868
- await streamConversationEvents(state.activeConversationId, { liveOnly: true });
3869
- }
3870
3954
  } catch (error) {
3871
3955
  const isStale = error && error.payload && error.payload.code === "APPROVAL_NOT_FOUND";
3872
3956
  if (isStale) {
@@ -3880,13 +3964,77 @@ var getWebUiClientScript = (markedSource2) => `
3880
3964
  _error: errMsg,
3881
3965
  }));
3882
3966
  }
3883
- renderMessages(state.activeMessages, state.isStreaming);
3884
3967
  } finally {
3968
+ delete state.approvalRequestsInFlight[approvalId];
3969
+ }
3970
+ };
3971
+
3972
+ elements.messages.addEventListener("click", async (event) => {
3973
+ const target = event.target;
3974
+ if (!(target instanceof Element)) {
3975
+ return;
3976
+ }
3977
+
3978
+ // Batch approve/deny all
3979
+ const batchBtn = target.closest(".approval-batch-btn");
3980
+ if (batchBtn) {
3981
+ const decision = batchBtn.getAttribute("data-approval-batch") || "";
3982
+ if (decision !== "approve" && decision !== "deny") return;
3983
+ const messages = state.activeMessages || [];
3984
+ const pending = [];
3985
+ for (const m of messages) {
3986
+ if (Array.isArray(m._pendingApprovals)) {
3987
+ for (const req of m._pendingApprovals) {
3988
+ if (req.approvalId && req.state !== "submitting" && !state.approvalRequestsInFlight[req.approvalId]) {
3989
+ pending.push(req.approvalId);
3990
+ }
3991
+ }
3992
+ }
3993
+ }
3994
+ if (pending.length === 0) return;
3995
+ const wasStreaming = state.isStreaming;
3996
+ if (!wasStreaming) setStreaming(true);
3997
+ renderMessages(state.activeMessages, state.isStreaming);
3998
+ await Promise.all(pending.map((aid) => submitApproval(aid, decision, { wasStreaming })));
3999
+ renderMessages(state.activeMessages, state.isStreaming);
4000
+ loadConversations();
4001
+ if (!wasStreaming && state.activeConversationId) {
4002
+ await streamConversationEvents(state.activeConversationId, { liveOnly: true });
4003
+ }
3885
4004
  if (!wasStreaming) {
3886
4005
  setStreaming(false);
3887
4006
  renderMessages(state.activeMessages, false);
3888
4007
  }
3889
- delete state.approvalRequestsInFlight[approvalId];
4008
+ return;
4009
+ }
4010
+
4011
+ // Individual approve/deny
4012
+ const button = target.closest(".approval-action-btn");
4013
+ if (!button) {
4014
+ return;
4015
+ }
4016
+ const approvalId = button.getAttribute("data-approval-id") || "";
4017
+ const decision = button.getAttribute("data-approval-decision") || "";
4018
+ if (!approvalId || (decision !== "approve" && decision !== "deny")) {
4019
+ return;
4020
+ }
4021
+ if (state.approvalRequestsInFlight[approvalId]) {
4022
+ return;
4023
+ }
4024
+ const wasStreaming = state.isStreaming;
4025
+ if (!wasStreaming) {
4026
+ setStreaming(true);
4027
+ }
4028
+ renderMessages(state.activeMessages, state.isStreaming);
4029
+ await submitApproval(approvalId, decision, { wasStreaming });
4030
+ renderMessages(state.activeMessages, state.isStreaming);
4031
+ loadConversations();
4032
+ if (!wasStreaming && state.activeConversationId) {
4033
+ await streamConversationEvents(state.activeConversationId, { liveOnly: true });
4034
+ }
4035
+ if (!wasStreaming) {
4036
+ setStreaming(false);
4037
+ renderMessages(state.activeMessages, false);
3890
4038
  }
3891
4039
  });
3892
4040
 
@@ -4443,7 +4591,7 @@ var getWebUiClientScript = (markedSource2) => `
4443
4591
  `;
4444
4592
 
4445
4593
  // src/web-ui-store.ts
4446
- import { createHash, randomUUID, timingSafeEqual } from "crypto";
4594
+ import { createHash, createHmac, randomUUID, timingSafeEqual } from "crypto";
4447
4595
  import { mkdir, readFile, writeFile } from "fs/promises";
4448
4596
  import { basename, dirname, resolve } from "path";
4449
4597
  import { homedir } from "os";
@@ -4451,9 +4599,13 @@ var DEFAULT_OWNER = "local-owner";
4451
4599
  var SessionStore = class {
4452
4600
  sessions = /* @__PURE__ */ new Map();
4453
4601
  ttlMs;
4602
+ signingKey;
4454
4603
  constructor(ttlMs = 1e3 * 60 * 60 * 8) {
4455
4604
  this.ttlMs = ttlMs;
4456
4605
  }
4606
+ setSigningKey(key) {
4607
+ if (key) this.signingKey = key;
4608
+ }
4457
4609
  create(ownerId = DEFAULT_OWNER) {
4458
4610
  const now = Date.now();
4459
4611
  const session = {
@@ -4482,6 +4634,57 @@ var SessionStore = class {
4482
4634
  delete(sessionId) {
4483
4635
  this.sessions.delete(sessionId);
4484
4636
  }
4637
+ /**
4638
+ * Encode a session into a signed cookie value that survives serverless
4639
+ * cold starts. Format: `base64url(payload).signature`
4640
+ */
4641
+ signSession(session) {
4642
+ if (!this.signingKey) return void 0;
4643
+ const payload = Buffer.from(
4644
+ JSON.stringify({
4645
+ sid: session.sessionId,
4646
+ o: session.ownerId,
4647
+ csrf: session.csrfToken,
4648
+ exp: session.expiresAt
4649
+ })
4650
+ ).toString("base64url");
4651
+ const sig = createHmac("sha256", this.signingKey).update(payload).digest("base64url");
4652
+ return `${payload}.${sig}`;
4653
+ }
4654
+ /**
4655
+ * Restore a session from a signed cookie value. Returns the session
4656
+ * (also added to the in-memory store) or undefined if invalid/expired.
4657
+ */
4658
+ restoreFromSigned(cookieValue) {
4659
+ if (!this.signingKey) return void 0;
4660
+ const dotIdx = cookieValue.lastIndexOf(".");
4661
+ if (dotIdx <= 0) return void 0;
4662
+ const payload = cookieValue.slice(0, dotIdx);
4663
+ const sig = cookieValue.slice(dotIdx + 1);
4664
+ const expected = createHmac("sha256", this.signingKey).update(payload).digest("base64url");
4665
+ if (sig.length !== expected.length) return void 0;
4666
+ if (!timingSafeEqual(Buffer.from(sig, "utf8"), Buffer.from(expected, "utf8")))
4667
+ return void 0;
4668
+ try {
4669
+ const data = JSON.parse(
4670
+ Buffer.from(payload, "base64url").toString("utf8")
4671
+ );
4672
+ if (!data.sid || !data.o || !data.csrf || !data.exp) return void 0;
4673
+ if (Date.now() > data.exp) return void 0;
4674
+ const session = {
4675
+ sessionId: data.sid,
4676
+ ownerId: data.o,
4677
+ csrfToken: data.csrf,
4678
+ createdAt: data.exp - this.ttlMs,
4679
+ expiresAt: data.exp,
4680
+ lastSeenAt: Date.now()
4681
+ };
4682
+ this.sessions.set(session.sessionId, session);
4683
+ return session;
4684
+ } catch {
4685
+ return void 0;
4686
+ }
4687
+ }
4485
4688
  };
4486
4689
  var LoginRateLimiter = class {
4487
4690
  constructor(maxAttempts = 5, windowMs = 1e3 * 60 * 5, lockoutMs = 1e3 * 60 * 10) {
@@ -4698,9 +4901,12 @@ ${WEB_UI_STYLES}
4698
4901
 
4699
4902
  <div id="app" class="shell hidden">
4700
4903
  <aside class="sidebar">
4701
- <button id="new-chat" class="new-chat-btn">
4702
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg>
4703
- </button>
4904
+ <div class="sidebar-header">
4905
+ <span class="sidebar-agent-name">${agentName}</span>
4906
+ <button id="new-chat" class="new-chat-btn">
4907
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg>
4908
+ </button>
4909
+ </div>
4704
4910
  <div id="conversation-list" class="conversation-list"></div>
4705
4911
  <div class="sidebar-footer">
4706
4912
  <button id="logout" class="logout-btn">Log out</button>
@@ -6305,11 +6511,17 @@ export default {
6305
6511
  ],
6306
6512
  // Tool access: true (available), false (disabled), 'approval' (requires human approval)
6307
6513
  tools: {
6514
+ list_directory: true,
6515
+ read_file: true,
6308
6516
  write_file: true, // gated by environment for writes
6517
+ delete_file: 'approval', // requires human approval
6518
+ delete_directory: 'approval', // requires human approval
6309
6519
  send_email: 'approval', // requires human approval
6310
6520
  byEnvironment: {
6311
6521
  production: {
6312
6522
  write_file: false,
6523
+ delete_file: false,
6524
+ delete_directory: false,
6313
6525
  },
6314
6526
  development: {
6315
6527
  send_email: true, // skip approval in dev
@@ -6392,6 +6604,7 @@ Connect your agent to email so users can interact by sending emails:
6392
6604
  RESEND_API_KEY=re_...
6393
6605
  RESEND_WEBHOOK_SECRET=whsec_...
6394
6606
  RESEND_FROM=Agent <agent@yourdomain.com>
6607
+ RESEND_REPLY_TO=support@yourdomain.com # optional
6395
6608
  \`\`\`
6396
6609
  5. Add to \`poncho.config.js\`:
6397
6610
  \`\`\`javascript
@@ -7228,71 +7441,72 @@ var createRequestHandler = async (options) => {
7228
7441
  if (event.type === "tool:approval:checkpoint") {
7229
7442
  const cpConv = await conversationStore.get(childConversationId);
7230
7443
  if (cpConv) {
7231
- const cpData = {
7232
- approvalId: event.approvalId,
7444
+ const allCpData = event.approvals.map((a) => ({
7445
+ approvalId: a.approvalId,
7233
7446
  runId: latestRunId,
7234
- tool: event.tool,
7235
- toolCallId: event.toolCallId,
7236
- input: event.input,
7447
+ tool: a.tool,
7448
+ toolCallId: a.toolCallId,
7449
+ input: a.input,
7237
7450
  checkpointMessages: [...historyMessages, ...event.checkpointMessages],
7238
7451
  baseMessageCount: 0,
7239
7452
  pendingToolCalls: event.pendingToolCalls
7240
- };
7241
- cpConv.pendingApprovals = [cpData];
7453
+ }));
7454
+ cpConv.pendingApprovals = allCpData;
7242
7455
  cpConv.updatedAt = Date.now();
7243
7456
  await conversationStore.update(cpConv);
7244
- const approved = await new Promise((resolve4) => {
7245
- pendingSubagentApprovals.set(event.approvalId, {
7246
- resolve: resolve4,
7247
- childHarness,
7248
- checkpoint: cpData,
7249
- childConversationId,
7250
- parentConversationId
7251
- });
7457
+ const decidedApprovals = await new Promise((resolve4) => {
7458
+ for (const cpData of allCpData) {
7459
+ pendingSubagentApprovals.set(cpData.approvalId, {
7460
+ resolve: resolve4,
7461
+ childHarness,
7462
+ checkpoint: cpData,
7463
+ childConversationId,
7464
+ parentConversationId
7465
+ });
7466
+ }
7252
7467
  });
7253
- let toolResults;
7254
- if (approved) {
7255
- const toolContext = {
7256
- runId: cpData.runId,
7257
- agentId: identity.id,
7258
- step: 0,
7259
- workingDir,
7260
- parameters: {},
7261
- conversationId: childConversationId
7262
- };
7263
- const execResults = await childHarness.executeTools(
7264
- [{ id: cpData.toolCallId, name: cpData.tool, input: cpData.input }],
7265
- toolContext
7266
- );
7267
- toolResults = execResults.map((r) => ({
7468
+ const checkpointRef = allCpData[0];
7469
+ const toolContext = {
7470
+ runId: checkpointRef.runId,
7471
+ agentId: identity.id,
7472
+ step: 0,
7473
+ workingDir,
7474
+ parameters: {},
7475
+ conversationId: childConversationId
7476
+ };
7477
+ const approvalToolCallIds = new Set(decidedApprovals.map((a) => a.toolCallId));
7478
+ const callsToExecute = [];
7479
+ const deniedResults = [];
7480
+ for (const a of decidedApprovals) {
7481
+ if (a.decision === "approved" && a.toolCallId) {
7482
+ callsToExecute.push({ id: a.toolCallId, name: a.tool, input: a.input });
7483
+ const toolText = `- done \`${a.tool}\``;
7484
+ toolTimeline.push(toolText);
7485
+ currentTools.push(toolText);
7486
+ } else if (a.toolCallId) {
7487
+ deniedResults.push({ callId: a.toolCallId, toolName: a.tool, error: "Tool execution denied by user" });
7488
+ const toolText = `- denied \`${a.tool}\``;
7489
+ toolTimeline.push(toolText);
7490
+ currentTools.push(toolText);
7491
+ }
7492
+ }
7493
+ const pendingToolCalls = checkpointRef.pendingToolCalls ?? [];
7494
+ for (const tc of pendingToolCalls) {
7495
+ if (!approvalToolCallIds.has(tc.id)) {
7496
+ callsToExecute.push(tc);
7497
+ }
7498
+ }
7499
+ let toolResults = [...deniedResults];
7500
+ if (callsToExecute.length > 0) {
7501
+ const execResults = await childHarness.executeTools(callsToExecute, toolContext);
7502
+ toolResults.push(...execResults.map((r) => ({
7268
7503
  callId: r.callId,
7269
7504
  toolName: r.tool,
7270
7505
  result: r.output,
7271
7506
  error: r.error
7272
- }));
7273
- const toolText = `- done \`${cpData.tool}\``;
7274
- toolTimeline.push(toolText);
7275
- currentTools.push(toolText);
7276
- } else {
7277
- toolResults = [{
7278
- callId: cpData.toolCallId,
7279
- toolName: cpData.tool,
7280
- error: "Tool execution denied by user"
7281
- }];
7282
- const toolText = `- denied \`${cpData.tool}\``;
7283
- toolTimeline.push(toolText);
7284
- currentTools.push(toolText);
7285
- }
7286
- broadcastEvent(
7287
- childConversationId,
7288
- approved ? { type: "tool:approval:granted", approvalId: event.approvalId } : { type: "tool:approval:denied", approvalId: event.approvalId }
7289
- );
7290
- const cpConv2 = await conversationStore.get(childConversationId);
7291
- if (cpConv2) {
7292
- cpConv2.pendingApprovals = [];
7293
- await conversationStore.update(cpConv2);
7507
+ })));
7294
7508
  }
7295
- const resumeMessages = [...cpData.checkpointMessages];
7509
+ const resumeMessages = [...checkpointRef.checkpointMessages];
7296
7510
  for await (const resumeEvent of childHarness.continueFromToolResult({
7297
7511
  messages: resumeMessages,
7298
7512
  toolResults,
@@ -7576,16 +7790,16 @@ var createRequestHandler = async (options) => {
7576
7790
  if (event.type === "tool:approval:checkpoint") {
7577
7791
  const conv = await conversationStore.get(conversationId);
7578
7792
  if (conv) {
7579
- conv.pendingApprovals = [{
7580
- approvalId: event.approvalId,
7793
+ conv.pendingApprovals = event.approvals.map((a) => ({
7794
+ approvalId: a.approvalId,
7581
7795
  runId: latestRunId,
7582
- tool: event.tool,
7583
- toolCallId: event.toolCallId,
7584
- input: event.input,
7796
+ tool: a.tool,
7797
+ toolCallId: a.toolCallId,
7798
+ input: a.input,
7585
7799
  checkpointMessages: [...fullCheckpointMessages, ...event.checkpointMessages],
7586
7800
  baseMessageCount: 0,
7587
7801
  pendingToolCalls: event.pendingToolCalls
7588
- }];
7802
+ }));
7589
7803
  conv.updatedAt = Date.now();
7590
7804
  await conversationStore.update(conv);
7591
7805
  }
@@ -7820,16 +8034,16 @@ var createRequestHandler = async (options) => {
7820
8034
  if (event.type === "tool:approval:checkpoint") {
7821
8035
  await updateConversation((c) => {
7822
8036
  c.messages = buildMessages();
7823
- c.pendingApprovals = [{
7824
- approvalId: event.approvalId,
8037
+ c.pendingApprovals = event.approvals.map((a) => ({
8038
+ approvalId: a.approvalId,
7825
8039
  runId: latestRunId,
7826
- tool: event.tool,
7827
- toolCallId: event.toolCallId,
7828
- input: event.input,
8040
+ tool: a.tool,
8041
+ toolCallId: a.toolCallId,
8042
+ input: a.input,
7829
8043
  checkpointMessages: event.checkpointMessages,
7830
8044
  baseMessageCount: historyMessages.length,
7831
8045
  pendingToolCalls: event.pendingToolCalls
7832
- }];
8046
+ }));
7833
8047
  });
7834
8048
  checkpointedRun = true;
7835
8049
  }
@@ -7898,9 +8112,9 @@ var createRequestHandler = async (options) => {
7898
8112
  waitUntil: waitUntilHook,
7899
8113
  ownerId: "local-owner"
7900
8114
  });
7901
- adapter.registerRoutes(messagingRouteRegistrar);
7902
8115
  try {
7903
8116
  await bridge.start();
8117
+ adapter.registerRoutes(messagingRouteRegistrar);
7904
8118
  messagingBridges.push(bridge);
7905
8119
  console.log(` Slack messaging enabled at /api/messaging/slack`);
7906
8120
  } catch (err) {
@@ -7913,6 +8127,7 @@ var createRequestHandler = async (options) => {
7913
8127
  apiKeyEnv: channelConfig.apiKeyEnv,
7914
8128
  webhookSecretEnv: channelConfig.webhookSecretEnv,
7915
8129
  fromEnv: channelConfig.fromEnv,
8130
+ replyToEnv: channelConfig.replyToEnv,
7916
8131
  allowedSenders: channelConfig.allowedSenders,
7917
8132
  mode: channelConfig.mode,
7918
8133
  allowedRecipients: channelConfig.allowedRecipients,
@@ -7924,9 +8139,9 @@ var createRequestHandler = async (options) => {
7924
8139
  waitUntil: waitUntilHook,
7925
8140
  ownerId: "local-owner"
7926
8141
  });
7927
- adapter.registerRoutes(messagingRouteRegistrar);
7928
8142
  try {
7929
8143
  await bridge.start();
8144
+ adapter.registerRoutes(messagingRouteRegistrar);
7930
8145
  messagingBridges.push(bridge);
7931
8146
  const adapterTools = adapter.getToolDefinitions?.() ?? [];
7932
8147
  if (adapterTools.length > 0) {
@@ -7948,6 +8163,9 @@ var createRequestHandler = async (options) => {
7948
8163
  const authToken = process.env[authTokenEnv] ?? "";
7949
8164
  const authRequired = config?.auth?.required ?? false;
7950
8165
  const requireAuth = authRequired && authToken.length > 0;
8166
+ if (requireAuth) {
8167
+ sessionStore.setSigningKey(authToken);
8168
+ }
7951
8169
  const webUiEnabled = config?.webUi !== false;
7952
8170
  const isProduction = resolveHarnessEnvironment() === "production";
7953
8171
  const secureCookies = isProduction;
@@ -8021,8 +8239,8 @@ var createRequestHandler = async (options) => {
8021
8239
  }
8022
8240
  }
8023
8241
  const cookies = parseCookies(request);
8024
- const sessionId = cookies.poncho_session;
8025
- const session = sessionId ? sessionStore.get(sessionId) : void 0;
8242
+ const cookieValue = cookies.poncho_session;
8243
+ const session = cookieValue ? sessionStore.get(cookieValue) ?? sessionStore.restoreFromSigned(cookieValue) : void 0;
8026
8244
  const ownerId = session?.ownerId ?? "local-owner";
8027
8245
  const requiresCsrfValidation = request.method !== "GET" && request.method !== "HEAD" && request.method !== "OPTIONS";
8028
8246
  if (pathname === "/api/auth/session" && request.method === "GET") {
@@ -8070,7 +8288,8 @@ var createRequestHandler = async (options) => {
8070
8288
  }
8071
8289
  loginRateLimiter.registerSuccess(ip);
8072
8290
  const createdSession = sessionStore.create(ownerId);
8073
- setCookie(response, "poncho_session", createdSession.sessionId, {
8291
+ const signedValue = sessionStore.signSession(createdSession);
8292
+ setCookie(response, "poncho_session", signedValue ?? createdSession.sessionId, {
8074
8293
  httpOnly: true,
8075
8294
  secure: secureCookies,
8076
8295
  sameSite: "Lax",
@@ -8265,13 +8484,26 @@ data: ${JSON.stringify(data)}
8265
8484
  const approved = body.approved === true;
8266
8485
  const pendingSubagent = pendingSubagentApprovals.get(approvalId);
8267
8486
  if (pendingSubagent) {
8268
- pendingSubagentApprovals.delete(approvalId);
8487
+ pendingSubagent.checkpoint.decision = approved ? "approved" : "denied";
8488
+ broadcastEvent(
8489
+ pendingSubagent.childConversationId,
8490
+ approved ? { type: "tool:approval:granted", approvalId } : { type: "tool:approval:denied", approvalId }
8491
+ );
8269
8492
  const childConv = await conversationStore.get(pendingSubagent.childConversationId);
8270
- if (childConv && Array.isArray(childConv.pendingApprovals)) {
8271
- childConv.pendingApprovals = childConv.pendingApprovals.filter((pa) => pa.approvalId !== approvalId);
8493
+ const allApprovals2 = childConv?.pendingApprovals ?? [];
8494
+ const allDecided2 = allApprovals2.length > 0 && allApprovals2.every((pa) => pa.decision != null);
8495
+ if (allDecided2) {
8496
+ for (const pa of allApprovals2) {
8497
+ pendingSubagentApprovals.delete(pa.approvalId);
8498
+ }
8499
+ if (childConv) {
8500
+ childConv.pendingApprovals = [];
8501
+ await conversationStore.update(childConv);
8502
+ }
8503
+ pendingSubagent.resolve(allApprovals2);
8504
+ } else if (childConv) {
8272
8505
  await conversationStore.update(childConv);
8273
8506
  }
8274
- pendingSubagent.resolve(approved);
8275
8507
  writeJson(response, 200, { ok: true, approvalId, approved });
8276
8508
  return;
8277
8509
  }
@@ -8304,47 +8536,63 @@ data: ${JSON.stringify(data)}
8304
8536
  });
8305
8537
  return;
8306
8538
  }
8307
- foundConversation.pendingApprovals = (foundConversation.pendingApprovals ?? []).filter((a) => a.approvalId !== approvalId);
8308
- await conversationStore.update(foundConversation);
8539
+ foundApproval.decision = approved ? "approved" : "denied";
8309
8540
  broadcastEvent(
8310
8541
  conversationId,
8311
8542
  approved ? { type: "tool:approval:granted", approvalId } : { type: "tool:approval:denied", approvalId }
8312
8543
  );
8544
+ const allApprovals = foundConversation.pendingApprovals ?? [];
8545
+ const allDecided = allApprovals.length > 0 && allApprovals.every((a) => a.decision != null);
8546
+ if (!allDecided) {
8547
+ await conversationStore.update(foundConversation);
8548
+ writeJson(response, 200, { ok: true, approvalId, approved, batchComplete: false });
8549
+ return;
8550
+ }
8551
+ foundConversation.pendingApprovals = [];
8552
+ await conversationStore.update(foundConversation);
8553
+ const checkpointRef = allApprovals[0];
8313
8554
  void (async () => {
8314
- let toolResults;
8315
- if (approved) {
8316
- const toolContext = {
8317
- runId: foundApproval.runId,
8318
- agentId: identity.id,
8319
- step: 0,
8320
- workingDir,
8321
- parameters: {}
8322
- };
8323
- const execResults = await harness.executeTools(
8324
- [{ id: foundApproval.toolCallId, name: foundApproval.tool, input: foundApproval.input }],
8325
- toolContext
8326
- );
8327
- toolResults = execResults.map((r) => ({
8555
+ const toolContext = {
8556
+ runId: checkpointRef.runId,
8557
+ agentId: identity.id,
8558
+ step: 0,
8559
+ workingDir,
8560
+ parameters: {}
8561
+ };
8562
+ const approvalToolCallIds = new Set(allApprovals.map((a) => a.toolCallId));
8563
+ const callsToExecute = [];
8564
+ const deniedResults = [];
8565
+ for (const a of allApprovals) {
8566
+ if (a.decision === "approved" && a.toolCallId) {
8567
+ callsToExecute.push({ id: a.toolCallId, name: a.tool, input: a.input });
8568
+ } else if (a.decision === "denied" && a.toolCallId) {
8569
+ deniedResults.push({ callId: a.toolCallId, toolName: a.tool, error: "Tool execution denied by user" });
8570
+ }
8571
+ }
8572
+ const pendingToolCalls = checkpointRef.pendingToolCalls ?? [];
8573
+ for (const tc of pendingToolCalls) {
8574
+ if (!approvalToolCallIds.has(tc.id)) {
8575
+ callsToExecute.push(tc);
8576
+ }
8577
+ }
8578
+ let toolResults = [...deniedResults];
8579
+ if (callsToExecute.length > 0) {
8580
+ const execResults = await harness.executeTools(callsToExecute, toolContext);
8581
+ toolResults.push(...execResults.map((r) => ({
8328
8582
  callId: r.callId,
8329
8583
  toolName: r.tool,
8330
8584
  result: r.output,
8331
8585
  error: r.error
8332
- }));
8333
- } else {
8334
- toolResults = [{
8335
- callId: foundApproval.toolCallId,
8336
- toolName: foundApproval.tool,
8337
- error: "Tool execution denied by user"
8338
- }];
8586
+ })));
8339
8587
  }
8340
8588
  await resumeRunFromCheckpoint(
8341
8589
  conversationId,
8342
8590
  foundConversation,
8343
- foundApproval,
8591
+ checkpointRef,
8344
8592
  toolResults
8345
8593
  );
8346
8594
  })();
8347
- writeJson(response, 200, { ok: true, approvalId, approved });
8595
+ writeJson(response, 200, { ok: true, approvalId, approved, batchComplete: true });
8348
8596
  return;
8349
8597
  }
8350
8598
  const conversationEventsMatch = pathname.match(
@@ -8828,16 +9076,16 @@ data: ${JSON.stringify(data)}
8828
9076
  metadata: toolTimeline.length > 0 || checkpointSections.length > 0 ? { toolActivity: [...toolTimeline], sections: checkpointSections.length > 0 ? checkpointSections : void 0 } : void 0
8829
9077
  }] : []
8830
9078
  ];
8831
- conversation.pendingApprovals = [{
8832
- approvalId: event.approvalId,
9079
+ conversation.pendingApprovals = event.approvals.map((a) => ({
9080
+ approvalId: a.approvalId,
8833
9081
  runId: latestRunId,
8834
- tool: event.tool,
8835
- toolCallId: event.toolCallId,
8836
- input: event.input,
9082
+ tool: a.tool,
9083
+ toolCallId: a.toolCallId,
9084
+ input: a.input,
8837
9085
  checkpointMessages: event.checkpointMessages,
8838
9086
  baseMessageCount: historyMessages.length,
8839
9087
  pendingToolCalls: event.pendingToolCalls
8840
- }];
9088
+ }));
8841
9089
  conversation.updatedAt = Date.now();
8842
9090
  await conversationStore.update(conversation);
8843
9091
  checkpointedRun = true;
@@ -9372,7 +9620,7 @@ var runInteractive = async (workingDir, params) => {
9372
9620
  await harness.initialize();
9373
9621
  const identity = await ensureAgentIdentity2(workingDir);
9374
9622
  try {
9375
- const { runInteractiveInk } = await import("./run-interactive-ink-VSO7GB3Y.js");
9623
+ const { runInteractiveInk } = await import("./run-interactive-ink-4KVVPMR3.js");
9376
9624
  await runInteractiveInk({
9377
9625
  harness,
9378
9626
  params,