@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/.turbo/turbo-build.log +7 -7
- package/CHANGELOG.md +32 -0
- package/dist/{chunk-GDB5X2OS.js → chunk-7P53QSP5.js} +494 -157
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -1
- package/dist/{run-interactive-ink-B6JJ33SN.js → run-interactive-ink-4KVVPMR3.js} +1 -1
- package/package.json +4 -4
- package/src/index.ts +216 -124
- package/src/web-ui-client.ts +203 -36
- package/src/web-ui-store.ts +68 -1
- package/src/web-ui-styles.ts +51 -5
- package/src/web-ui.ts +6 -3
|
@@ -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:
|
|
1364
|
-
|
|
1365
|
-
|
|
1408
|
+
position: relative;
|
|
1409
|
+
order: -1;
|
|
1410
|
+
max-height: 35vh;
|
|
1366
1411
|
flex: none !important;
|
|
1367
|
-
|
|
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 {
|
|
@@ -2547,8 +2656,32 @@ var getWebUiClientScript = (markedSource2) => `
|
|
|
2547
2656
|
updateContextRing();
|
|
2548
2657
|
}
|
|
2549
2658
|
}
|
|
2659
|
+
if (eventName === "tool:generating") {
|
|
2660
|
+
const toolName = payload.tool || "tool";
|
|
2661
|
+
if (!Array.isArray(assistantMessage._activeActivities)) {
|
|
2662
|
+
assistantMessage._activeActivities = [];
|
|
2663
|
+
}
|
|
2664
|
+
assistantMessage._activeActivities.push({
|
|
2665
|
+
kind: "generating",
|
|
2666
|
+
tool: toolName,
|
|
2667
|
+
label: "Preparing " + toolName,
|
|
2668
|
+
});
|
|
2669
|
+
if (assistantMessage._currentText.length > 0) {
|
|
2670
|
+
assistantMessage._sections.push({
|
|
2671
|
+
type: "text",
|
|
2672
|
+
content: assistantMessage._currentText,
|
|
2673
|
+
});
|
|
2674
|
+
assistantMessage._currentText = "";
|
|
2675
|
+
}
|
|
2676
|
+
const prepText =
|
|
2677
|
+
"- preparing \\x60" + toolName + "\\x60";
|
|
2678
|
+
assistantMessage._currentTools.push(prepText);
|
|
2679
|
+
assistantMessage.metadata.toolActivity.push(prepText);
|
|
2680
|
+
renderIfActiveConversation(true);
|
|
2681
|
+
}
|
|
2550
2682
|
if (eventName === "tool:started") {
|
|
2551
2683
|
const toolName = payload.tool || "tool";
|
|
2684
|
+
removeActiveActivityForTool(assistantMessage, toolName);
|
|
2552
2685
|
const startedActivity = addActiveActivityFromToolStart(
|
|
2553
2686
|
assistantMessage,
|
|
2554
2687
|
payload,
|
|
@@ -2560,14 +2693,24 @@ var getWebUiClientScript = (markedSource2) => `
|
|
|
2560
2693
|
});
|
|
2561
2694
|
assistantMessage._currentText = "";
|
|
2562
2695
|
}
|
|
2696
|
+
const tick = "\\x60";
|
|
2697
|
+
const prepPrefix = "- preparing " + tick + toolName + tick;
|
|
2698
|
+
const prepToolIdx = assistantMessage._currentTools.indexOf(prepPrefix);
|
|
2699
|
+
if (prepToolIdx >= 0) {
|
|
2700
|
+
assistantMessage._currentTools.splice(prepToolIdx, 1);
|
|
2701
|
+
}
|
|
2702
|
+
const prepMetaIdx = assistantMessage.metadata.toolActivity.indexOf(prepPrefix);
|
|
2703
|
+
if (prepMetaIdx >= 0) {
|
|
2704
|
+
assistantMessage.metadata.toolActivity.splice(prepMetaIdx, 1);
|
|
2705
|
+
}
|
|
2563
2706
|
const detail =
|
|
2564
2707
|
startedActivity && typeof startedActivity.detail === "string"
|
|
2565
2708
|
? startedActivity.detail.trim()
|
|
2566
2709
|
: "";
|
|
2567
2710
|
const toolText =
|
|
2568
|
-
"- start
|
|
2711
|
+
"- start " + tick +
|
|
2569
2712
|
toolName +
|
|
2570
|
-
|
|
2713
|
+
tick +
|
|
2571
2714
|
(detail ? " (" + detail + ")" : "");
|
|
2572
2715
|
assistantMessage._currentTools.push(toolText);
|
|
2573
2716
|
assistantMessage.metadata.toolActivity.push(toolText);
|
|
@@ -3290,26 +3433,57 @@ var getWebUiClientScript = (markedSource2) => `
|
|
|
3290
3433
|
updateContextRing();
|
|
3291
3434
|
}
|
|
3292
3435
|
}
|
|
3436
|
+
if (eventName === "tool:generating") {
|
|
3437
|
+
const toolName = payload.tool || "tool";
|
|
3438
|
+
if (!Array.isArray(assistantMessage._activeActivities)) {
|
|
3439
|
+
assistantMessage._activeActivities = [];
|
|
3440
|
+
}
|
|
3441
|
+
assistantMessage._activeActivities.push({
|
|
3442
|
+
kind: "generating",
|
|
3443
|
+
tool: toolName,
|
|
3444
|
+
label: "Preparing " + toolName,
|
|
3445
|
+
});
|
|
3446
|
+
if (assistantMessage._currentText.length > 0) {
|
|
3447
|
+
assistantMessage._sections.push({ type: "text", content: assistantMessage._currentText });
|
|
3448
|
+
assistantMessage._currentText = "";
|
|
3449
|
+
}
|
|
3450
|
+
if (!assistantMessage.metadata) assistantMessage.metadata = {};
|
|
3451
|
+
if (!assistantMessage.metadata.toolActivity) assistantMessage.metadata.toolActivity = [];
|
|
3452
|
+
const prepText = "- preparing \\x60" + toolName + "\\x60";
|
|
3453
|
+
assistantMessage._currentTools.push(prepText);
|
|
3454
|
+
assistantMessage.metadata.toolActivity.push(prepText);
|
|
3455
|
+
renderIfActiveConversation(true);
|
|
3456
|
+
}
|
|
3293
3457
|
if (eventName === "tool:started") {
|
|
3294
3458
|
const toolName = payload.tool || "tool";
|
|
3459
|
+
removeActiveActivityForTool(assistantMessage, toolName);
|
|
3295
3460
|
const startedActivity = addActiveActivityFromToolStart(
|
|
3296
3461
|
assistantMessage,
|
|
3297
3462
|
payload,
|
|
3298
3463
|
);
|
|
3299
|
-
// If we have text accumulated, push it as a text section
|
|
3300
3464
|
if (assistantMessage._currentText.length > 0) {
|
|
3301
3465
|
assistantMessage._sections.push({ type: "text", content: assistantMessage._currentText });
|
|
3302
3466
|
assistantMessage._currentText = "";
|
|
3303
3467
|
}
|
|
3468
|
+
if (!assistantMessage.metadata) assistantMessage.metadata = {};
|
|
3469
|
+
if (!assistantMessage.metadata.toolActivity) assistantMessage.metadata.toolActivity = [];
|
|
3470
|
+
const tick = "\\x60";
|
|
3471
|
+
const prepPrefix = "- preparing " + tick + toolName + tick;
|
|
3472
|
+
const prepToolIdx = assistantMessage._currentTools.indexOf(prepPrefix);
|
|
3473
|
+
if (prepToolIdx >= 0) {
|
|
3474
|
+
assistantMessage._currentTools.splice(prepToolIdx, 1);
|
|
3475
|
+
}
|
|
3476
|
+
const prepMetaIdx = assistantMessage.metadata.toolActivity.indexOf(prepPrefix);
|
|
3477
|
+
if (prepMetaIdx >= 0) {
|
|
3478
|
+
assistantMessage.metadata.toolActivity.splice(prepMetaIdx, 1);
|
|
3479
|
+
}
|
|
3304
3480
|
const detail =
|
|
3305
3481
|
startedActivity && typeof startedActivity.detail === "string"
|
|
3306
3482
|
? startedActivity.detail.trim()
|
|
3307
3483
|
: "";
|
|
3308
3484
|
const toolText =
|
|
3309
|
-
"- start
|
|
3485
|
+
"- start " + tick + toolName + tick + (detail ? " (" + detail + ")" : "");
|
|
3310
3486
|
assistantMessage._currentTools.push(toolText);
|
|
3311
|
-
if (!assistantMessage.metadata) assistantMessage.metadata = {};
|
|
3312
|
-
if (!assistantMessage.metadata.toolActivity) assistantMessage.metadata.toolActivity = [];
|
|
3313
3487
|
assistantMessage.metadata.toolActivity.push(toolText);
|
|
3314
3488
|
renderIfActiveConversation(true);
|
|
3315
3489
|
}
|
|
@@ -3763,45 +3937,20 @@ var getWebUiClientScript = (markedSource2) => `
|
|
|
3763
3937
|
openLightbox(img.src);
|
|
3764
3938
|
});
|
|
3765
3939
|
|
|
3766
|
-
|
|
3767
|
-
const
|
|
3768
|
-
if (!(target instanceof Element)) {
|
|
3769
|
-
return;
|
|
3770
|
-
}
|
|
3771
|
-
const button = target.closest(".approval-action-btn");
|
|
3772
|
-
if (!button) {
|
|
3773
|
-
return;
|
|
3774
|
-
}
|
|
3775
|
-
const approvalId = button.getAttribute("data-approval-id") || "";
|
|
3776
|
-
const decision = button.getAttribute("data-approval-decision") || "";
|
|
3777
|
-
if (!approvalId || (decision !== "approve" && decision !== "deny")) {
|
|
3778
|
-
return;
|
|
3779
|
-
}
|
|
3780
|
-
if (state.approvalRequestsInFlight[approvalId]) {
|
|
3781
|
-
return;
|
|
3782
|
-
}
|
|
3940
|
+
const submitApproval = async (approvalId, decision, opts) => {
|
|
3941
|
+
const wasStreaming = opts && opts.wasStreaming;
|
|
3783
3942
|
state.approvalRequestsInFlight[approvalId] = true;
|
|
3784
|
-
const wasStreaming = state.isStreaming;
|
|
3785
|
-
if (!wasStreaming) {
|
|
3786
|
-
setStreaming(true);
|
|
3787
|
-
}
|
|
3788
3943
|
updatePendingApproval(approvalId, (request) => ({
|
|
3789
3944
|
...request,
|
|
3790
3945
|
state: "submitting",
|
|
3791
3946
|
pendingDecision: decision,
|
|
3792
3947
|
}));
|
|
3793
|
-
renderMessages(state.activeMessages, state.isStreaming);
|
|
3794
3948
|
try {
|
|
3795
3949
|
await api("/api/approvals/" + encodeURIComponent(approvalId), {
|
|
3796
3950
|
method: "POST",
|
|
3797
3951
|
body: JSON.stringify({ approved: decision === "approve" }),
|
|
3798
3952
|
});
|
|
3799
3953
|
updatePendingApproval(approvalId, () => null);
|
|
3800
|
-
renderMessages(state.activeMessages, state.isStreaming);
|
|
3801
|
-
loadConversations();
|
|
3802
|
-
if (!wasStreaming && state.activeConversationId) {
|
|
3803
|
-
await streamConversationEvents(state.activeConversationId, { liveOnly: true });
|
|
3804
|
-
}
|
|
3805
3954
|
} catch (error) {
|
|
3806
3955
|
const isStale = error && error.payload && error.payload.code === "APPROVAL_NOT_FOUND";
|
|
3807
3956
|
if (isStale) {
|
|
@@ -3815,13 +3964,77 @@ var getWebUiClientScript = (markedSource2) => `
|
|
|
3815
3964
|
_error: errMsg,
|
|
3816
3965
|
}));
|
|
3817
3966
|
}
|
|
3818
|
-
renderMessages(state.activeMessages, state.isStreaming);
|
|
3819
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
|
+
}
|
|
3820
4004
|
if (!wasStreaming) {
|
|
3821
4005
|
setStreaming(false);
|
|
3822
4006
|
renderMessages(state.activeMessages, false);
|
|
3823
4007
|
}
|
|
3824
|
-
|
|
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);
|
|
3825
4038
|
}
|
|
3826
4039
|
});
|
|
3827
4040
|
|
|
@@ -4378,7 +4591,7 @@ var getWebUiClientScript = (markedSource2) => `
|
|
|
4378
4591
|
`;
|
|
4379
4592
|
|
|
4380
4593
|
// src/web-ui-store.ts
|
|
4381
|
-
import { createHash, randomUUID, timingSafeEqual } from "crypto";
|
|
4594
|
+
import { createHash, createHmac, randomUUID, timingSafeEqual } from "crypto";
|
|
4382
4595
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
4383
4596
|
import { basename, dirname, resolve } from "path";
|
|
4384
4597
|
import { homedir } from "os";
|
|
@@ -4386,9 +4599,13 @@ var DEFAULT_OWNER = "local-owner";
|
|
|
4386
4599
|
var SessionStore = class {
|
|
4387
4600
|
sessions = /* @__PURE__ */ new Map();
|
|
4388
4601
|
ttlMs;
|
|
4602
|
+
signingKey;
|
|
4389
4603
|
constructor(ttlMs = 1e3 * 60 * 60 * 8) {
|
|
4390
4604
|
this.ttlMs = ttlMs;
|
|
4391
4605
|
}
|
|
4606
|
+
setSigningKey(key) {
|
|
4607
|
+
if (key) this.signingKey = key;
|
|
4608
|
+
}
|
|
4392
4609
|
create(ownerId = DEFAULT_OWNER) {
|
|
4393
4610
|
const now = Date.now();
|
|
4394
4611
|
const session = {
|
|
@@ -4417,6 +4634,57 @@ var SessionStore = class {
|
|
|
4417
4634
|
delete(sessionId) {
|
|
4418
4635
|
this.sessions.delete(sessionId);
|
|
4419
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
|
+
}
|
|
4420
4688
|
};
|
|
4421
4689
|
var LoginRateLimiter = class {
|
|
4422
4690
|
constructor(maxAttempts = 5, windowMs = 1e3 * 60 * 5, lockoutMs = 1e3 * 60 * 10) {
|
|
@@ -4633,9 +4901,12 @@ ${WEB_UI_STYLES}
|
|
|
4633
4901
|
|
|
4634
4902
|
<div id="app" class="shell hidden">
|
|
4635
4903
|
<aside class="sidebar">
|
|
4636
|
-
<
|
|
4637
|
-
<
|
|
4638
|
-
|
|
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>
|
|
4639
4910
|
<div id="conversation-list" class="conversation-list"></div>
|
|
4640
4911
|
<div class="sidebar-footer">
|
|
4641
4912
|
<button id="logout" class="logout-btn">Log out</button>
|
|
@@ -6150,6 +6421,10 @@ Connect remote MCP servers and expose their tools to the agent:
|
|
|
6150
6421
|
# Add remote MCP server
|
|
6151
6422
|
poncho mcp add --url https://mcp.example.com/github --name github --auth-bearer-env GITHUB_TOKEN
|
|
6152
6423
|
|
|
6424
|
+
# Server with custom headers (e.g. Arcade)
|
|
6425
|
+
poncho mcp add --url https://mcp.arcade.dev --name arcade \\
|
|
6426
|
+
--auth-bearer-env ARCADE_API_KEY --header "Arcade-User-ID: user@example.com"
|
|
6427
|
+
|
|
6153
6428
|
# List configured servers
|
|
6154
6429
|
poncho mcp list
|
|
6155
6430
|
|
|
@@ -6231,14 +6506,22 @@ export default {
|
|
|
6231
6506
|
url: "https://mcp.example.com/github",
|
|
6232
6507
|
auth: { type: "bearer", tokenEnv: "GITHUB_TOKEN" },
|
|
6233
6508
|
},
|
|
6509
|
+
// Custom headers for servers that require them (e.g. Arcade)
|
|
6510
|
+
// { name: "arcade", url: "https://mcp.arcade.dev", auth: { type: "bearer", tokenEnv: "ARCADE_API_KEY" }, headers: { "Arcade-User-ID": "user@example.com" } },
|
|
6234
6511
|
],
|
|
6235
6512
|
// Tool access: true (available), false (disabled), 'approval' (requires human approval)
|
|
6236
6513
|
tools: {
|
|
6514
|
+
list_directory: true,
|
|
6515
|
+
read_file: true,
|
|
6237
6516
|
write_file: true, // gated by environment for writes
|
|
6517
|
+
delete_file: 'approval', // requires human approval
|
|
6518
|
+
delete_directory: 'approval', // requires human approval
|
|
6238
6519
|
send_email: 'approval', // requires human approval
|
|
6239
6520
|
byEnvironment: {
|
|
6240
6521
|
production: {
|
|
6241
6522
|
write_file: false,
|
|
6523
|
+
delete_file: false,
|
|
6524
|
+
delete_directory: false,
|
|
6242
6525
|
},
|
|
6243
6526
|
development: {
|
|
6244
6527
|
send_email: true, // skip approval in dev
|
|
@@ -6321,6 +6604,7 @@ Connect your agent to email so users can interact by sending emails:
|
|
|
6321
6604
|
RESEND_API_KEY=re_...
|
|
6322
6605
|
RESEND_WEBHOOK_SECRET=whsec_...
|
|
6323
6606
|
RESEND_FROM=Agent <agent@yourdomain.com>
|
|
6607
|
+
RESEND_REPLY_TO=support@yourdomain.com # optional
|
|
6324
6608
|
\`\`\`
|
|
6325
6609
|
5. Add to \`poncho.config.js\`:
|
|
6326
6610
|
\`\`\`javascript
|
|
@@ -7157,71 +7441,72 @@ var createRequestHandler = async (options) => {
|
|
|
7157
7441
|
if (event.type === "tool:approval:checkpoint") {
|
|
7158
7442
|
const cpConv = await conversationStore.get(childConversationId);
|
|
7159
7443
|
if (cpConv) {
|
|
7160
|
-
const
|
|
7161
|
-
approvalId:
|
|
7444
|
+
const allCpData = event.approvals.map((a) => ({
|
|
7445
|
+
approvalId: a.approvalId,
|
|
7162
7446
|
runId: latestRunId,
|
|
7163
|
-
tool:
|
|
7164
|
-
toolCallId:
|
|
7165
|
-
input:
|
|
7447
|
+
tool: a.tool,
|
|
7448
|
+
toolCallId: a.toolCallId,
|
|
7449
|
+
input: a.input,
|
|
7166
7450
|
checkpointMessages: [...historyMessages, ...event.checkpointMessages],
|
|
7167
7451
|
baseMessageCount: 0,
|
|
7168
7452
|
pendingToolCalls: event.pendingToolCalls
|
|
7169
|
-
};
|
|
7170
|
-
cpConv.pendingApprovals =
|
|
7453
|
+
}));
|
|
7454
|
+
cpConv.pendingApprovals = allCpData;
|
|
7171
7455
|
cpConv.updatedAt = Date.now();
|
|
7172
7456
|
await conversationStore.update(cpConv);
|
|
7173
|
-
const
|
|
7174
|
-
|
|
7175
|
-
|
|
7176
|
-
|
|
7177
|
-
|
|
7178
|
-
|
|
7179
|
-
|
|
7180
|
-
|
|
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
|
+
}
|
|
7181
7467
|
});
|
|
7182
|
-
|
|
7183
|
-
|
|
7184
|
-
|
|
7185
|
-
|
|
7186
|
-
|
|
7187
|
-
|
|
7188
|
-
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
7192
|
-
|
|
7193
|
-
|
|
7194
|
-
|
|
7195
|
-
)
|
|
7196
|
-
|
|
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) => ({
|
|
7197
7503
|
callId: r.callId,
|
|
7198
7504
|
toolName: r.tool,
|
|
7199
7505
|
result: r.output,
|
|
7200
7506
|
error: r.error
|
|
7201
|
-
}));
|
|
7202
|
-
const toolText = `- done \`${cpData.tool}\``;
|
|
7203
|
-
toolTimeline.push(toolText);
|
|
7204
|
-
currentTools.push(toolText);
|
|
7205
|
-
} else {
|
|
7206
|
-
toolResults = [{
|
|
7207
|
-
callId: cpData.toolCallId,
|
|
7208
|
-
toolName: cpData.tool,
|
|
7209
|
-
error: "Tool execution denied by user"
|
|
7210
|
-
}];
|
|
7211
|
-
const toolText = `- denied \`${cpData.tool}\``;
|
|
7212
|
-
toolTimeline.push(toolText);
|
|
7213
|
-
currentTools.push(toolText);
|
|
7214
|
-
}
|
|
7215
|
-
broadcastEvent(
|
|
7216
|
-
childConversationId,
|
|
7217
|
-
approved ? { type: "tool:approval:granted", approvalId: event.approvalId } : { type: "tool:approval:denied", approvalId: event.approvalId }
|
|
7218
|
-
);
|
|
7219
|
-
const cpConv2 = await conversationStore.get(childConversationId);
|
|
7220
|
-
if (cpConv2) {
|
|
7221
|
-
cpConv2.pendingApprovals = [];
|
|
7222
|
-
await conversationStore.update(cpConv2);
|
|
7507
|
+
})));
|
|
7223
7508
|
}
|
|
7224
|
-
const resumeMessages = [...
|
|
7509
|
+
const resumeMessages = [...checkpointRef.checkpointMessages];
|
|
7225
7510
|
for await (const resumeEvent of childHarness.continueFromToolResult({
|
|
7226
7511
|
messages: resumeMessages,
|
|
7227
7512
|
toolResults,
|
|
@@ -7505,16 +7790,16 @@ var createRequestHandler = async (options) => {
|
|
|
7505
7790
|
if (event.type === "tool:approval:checkpoint") {
|
|
7506
7791
|
const conv = await conversationStore.get(conversationId);
|
|
7507
7792
|
if (conv) {
|
|
7508
|
-
conv.pendingApprovals =
|
|
7509
|
-
approvalId:
|
|
7793
|
+
conv.pendingApprovals = event.approvals.map((a) => ({
|
|
7794
|
+
approvalId: a.approvalId,
|
|
7510
7795
|
runId: latestRunId,
|
|
7511
|
-
tool:
|
|
7512
|
-
toolCallId:
|
|
7513
|
-
input:
|
|
7796
|
+
tool: a.tool,
|
|
7797
|
+
toolCallId: a.toolCallId,
|
|
7798
|
+
input: a.input,
|
|
7514
7799
|
checkpointMessages: [...fullCheckpointMessages, ...event.checkpointMessages],
|
|
7515
7800
|
baseMessageCount: 0,
|
|
7516
7801
|
pendingToolCalls: event.pendingToolCalls
|
|
7517
|
-
}
|
|
7802
|
+
}));
|
|
7518
7803
|
conv.updatedAt = Date.now();
|
|
7519
7804
|
await conversationStore.update(conv);
|
|
7520
7805
|
}
|
|
@@ -7749,16 +8034,16 @@ var createRequestHandler = async (options) => {
|
|
|
7749
8034
|
if (event.type === "tool:approval:checkpoint") {
|
|
7750
8035
|
await updateConversation((c) => {
|
|
7751
8036
|
c.messages = buildMessages();
|
|
7752
|
-
c.pendingApprovals =
|
|
7753
|
-
approvalId:
|
|
8037
|
+
c.pendingApprovals = event.approvals.map((a) => ({
|
|
8038
|
+
approvalId: a.approvalId,
|
|
7754
8039
|
runId: latestRunId,
|
|
7755
|
-
tool:
|
|
7756
|
-
toolCallId:
|
|
7757
|
-
input:
|
|
8040
|
+
tool: a.tool,
|
|
8041
|
+
toolCallId: a.toolCallId,
|
|
8042
|
+
input: a.input,
|
|
7758
8043
|
checkpointMessages: event.checkpointMessages,
|
|
7759
8044
|
baseMessageCount: historyMessages.length,
|
|
7760
8045
|
pendingToolCalls: event.pendingToolCalls
|
|
7761
|
-
}
|
|
8046
|
+
}));
|
|
7762
8047
|
});
|
|
7763
8048
|
checkpointedRun = true;
|
|
7764
8049
|
}
|
|
@@ -7827,9 +8112,9 @@ var createRequestHandler = async (options) => {
|
|
|
7827
8112
|
waitUntil: waitUntilHook,
|
|
7828
8113
|
ownerId: "local-owner"
|
|
7829
8114
|
});
|
|
7830
|
-
adapter.registerRoutes(messagingRouteRegistrar);
|
|
7831
8115
|
try {
|
|
7832
8116
|
await bridge.start();
|
|
8117
|
+
adapter.registerRoutes(messagingRouteRegistrar);
|
|
7833
8118
|
messagingBridges.push(bridge);
|
|
7834
8119
|
console.log(` Slack messaging enabled at /api/messaging/slack`);
|
|
7835
8120
|
} catch (err) {
|
|
@@ -7842,6 +8127,7 @@ var createRequestHandler = async (options) => {
|
|
|
7842
8127
|
apiKeyEnv: channelConfig.apiKeyEnv,
|
|
7843
8128
|
webhookSecretEnv: channelConfig.webhookSecretEnv,
|
|
7844
8129
|
fromEnv: channelConfig.fromEnv,
|
|
8130
|
+
replyToEnv: channelConfig.replyToEnv,
|
|
7845
8131
|
allowedSenders: channelConfig.allowedSenders,
|
|
7846
8132
|
mode: channelConfig.mode,
|
|
7847
8133
|
allowedRecipients: channelConfig.allowedRecipients,
|
|
@@ -7853,9 +8139,9 @@ var createRequestHandler = async (options) => {
|
|
|
7853
8139
|
waitUntil: waitUntilHook,
|
|
7854
8140
|
ownerId: "local-owner"
|
|
7855
8141
|
});
|
|
7856
|
-
adapter.registerRoutes(messagingRouteRegistrar);
|
|
7857
8142
|
try {
|
|
7858
8143
|
await bridge.start();
|
|
8144
|
+
adapter.registerRoutes(messagingRouteRegistrar);
|
|
7859
8145
|
messagingBridges.push(bridge);
|
|
7860
8146
|
const adapterTools = adapter.getToolDefinitions?.() ?? [];
|
|
7861
8147
|
if (adapterTools.length > 0) {
|
|
@@ -7877,6 +8163,9 @@ var createRequestHandler = async (options) => {
|
|
|
7877
8163
|
const authToken = process.env[authTokenEnv] ?? "";
|
|
7878
8164
|
const authRequired = config?.auth?.required ?? false;
|
|
7879
8165
|
const requireAuth = authRequired && authToken.length > 0;
|
|
8166
|
+
if (requireAuth) {
|
|
8167
|
+
sessionStore.setSigningKey(authToken);
|
|
8168
|
+
}
|
|
7880
8169
|
const webUiEnabled = config?.webUi !== false;
|
|
7881
8170
|
const isProduction = resolveHarnessEnvironment() === "production";
|
|
7882
8171
|
const secureCookies = isProduction;
|
|
@@ -7950,8 +8239,8 @@ var createRequestHandler = async (options) => {
|
|
|
7950
8239
|
}
|
|
7951
8240
|
}
|
|
7952
8241
|
const cookies = parseCookies(request);
|
|
7953
|
-
const
|
|
7954
|
-
const session =
|
|
8242
|
+
const cookieValue = cookies.poncho_session;
|
|
8243
|
+
const session = cookieValue ? sessionStore.get(cookieValue) ?? sessionStore.restoreFromSigned(cookieValue) : void 0;
|
|
7955
8244
|
const ownerId = session?.ownerId ?? "local-owner";
|
|
7956
8245
|
const requiresCsrfValidation = request.method !== "GET" && request.method !== "HEAD" && request.method !== "OPTIONS";
|
|
7957
8246
|
if (pathname === "/api/auth/session" && request.method === "GET") {
|
|
@@ -7999,7 +8288,8 @@ var createRequestHandler = async (options) => {
|
|
|
7999
8288
|
}
|
|
8000
8289
|
loginRateLimiter.registerSuccess(ip);
|
|
8001
8290
|
const createdSession = sessionStore.create(ownerId);
|
|
8002
|
-
|
|
8291
|
+
const signedValue = sessionStore.signSession(createdSession);
|
|
8292
|
+
setCookie(response, "poncho_session", signedValue ?? createdSession.sessionId, {
|
|
8003
8293
|
httpOnly: true,
|
|
8004
8294
|
secure: secureCookies,
|
|
8005
8295
|
sameSite: "Lax",
|
|
@@ -8194,13 +8484,26 @@ data: ${JSON.stringify(data)}
|
|
|
8194
8484
|
const approved = body.approved === true;
|
|
8195
8485
|
const pendingSubagent = pendingSubagentApprovals.get(approvalId);
|
|
8196
8486
|
if (pendingSubagent) {
|
|
8197
|
-
|
|
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
|
+
);
|
|
8198
8492
|
const childConv = await conversationStore.get(pendingSubagent.childConversationId);
|
|
8199
|
-
|
|
8200
|
-
|
|
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) {
|
|
8201
8505
|
await conversationStore.update(childConv);
|
|
8202
8506
|
}
|
|
8203
|
-
pendingSubagent.resolve(approved);
|
|
8204
8507
|
writeJson(response, 200, { ok: true, approvalId, approved });
|
|
8205
8508
|
return;
|
|
8206
8509
|
}
|
|
@@ -8233,47 +8536,63 @@ data: ${JSON.stringify(data)}
|
|
|
8233
8536
|
});
|
|
8234
8537
|
return;
|
|
8235
8538
|
}
|
|
8236
|
-
|
|
8237
|
-
await conversationStore.update(foundConversation);
|
|
8539
|
+
foundApproval.decision = approved ? "approved" : "denied";
|
|
8238
8540
|
broadcastEvent(
|
|
8239
8541
|
conversationId,
|
|
8240
8542
|
approved ? { type: "tool:approval:granted", approvalId } : { type: "tool:approval:denied", approvalId }
|
|
8241
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];
|
|
8242
8554
|
void (async () => {
|
|
8243
|
-
|
|
8244
|
-
|
|
8245
|
-
|
|
8246
|
-
|
|
8247
|
-
|
|
8248
|
-
|
|
8249
|
-
|
|
8250
|
-
|
|
8251
|
-
|
|
8252
|
-
|
|
8253
|
-
|
|
8254
|
-
|
|
8255
|
-
|
|
8256
|
-
|
|
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) => ({
|
|
8257
8582
|
callId: r.callId,
|
|
8258
8583
|
toolName: r.tool,
|
|
8259
8584
|
result: r.output,
|
|
8260
8585
|
error: r.error
|
|
8261
|
-
}));
|
|
8262
|
-
} else {
|
|
8263
|
-
toolResults = [{
|
|
8264
|
-
callId: foundApproval.toolCallId,
|
|
8265
|
-
toolName: foundApproval.tool,
|
|
8266
|
-
error: "Tool execution denied by user"
|
|
8267
|
-
}];
|
|
8586
|
+
})));
|
|
8268
8587
|
}
|
|
8269
8588
|
await resumeRunFromCheckpoint(
|
|
8270
8589
|
conversationId,
|
|
8271
8590
|
foundConversation,
|
|
8272
|
-
|
|
8591
|
+
checkpointRef,
|
|
8273
8592
|
toolResults
|
|
8274
8593
|
);
|
|
8275
8594
|
})();
|
|
8276
|
-
writeJson(response, 200, { ok: true, approvalId, approved });
|
|
8595
|
+
writeJson(response, 200, { ok: true, approvalId, approved, batchComplete: true });
|
|
8277
8596
|
return;
|
|
8278
8597
|
}
|
|
8279
8598
|
const conversationEventsMatch = pathname.match(
|
|
@@ -8757,16 +9076,16 @@ data: ${JSON.stringify(data)}
|
|
|
8757
9076
|
metadata: toolTimeline.length > 0 || checkpointSections.length > 0 ? { toolActivity: [...toolTimeline], sections: checkpointSections.length > 0 ? checkpointSections : void 0 } : void 0
|
|
8758
9077
|
}] : []
|
|
8759
9078
|
];
|
|
8760
|
-
conversation.pendingApprovals =
|
|
8761
|
-
approvalId:
|
|
9079
|
+
conversation.pendingApprovals = event.approvals.map((a) => ({
|
|
9080
|
+
approvalId: a.approvalId,
|
|
8762
9081
|
runId: latestRunId,
|
|
8763
|
-
tool:
|
|
8764
|
-
toolCallId:
|
|
8765
|
-
input:
|
|
9082
|
+
tool: a.tool,
|
|
9083
|
+
toolCallId: a.toolCallId,
|
|
9084
|
+
input: a.input,
|
|
8766
9085
|
checkpointMessages: event.checkpointMessages,
|
|
8767
9086
|
baseMessageCount: historyMessages.length,
|
|
8768
9087
|
pendingToolCalls: event.pendingToolCalls
|
|
8769
|
-
}
|
|
9088
|
+
}));
|
|
8770
9089
|
conversation.updatedAt = Date.now();
|
|
8771
9090
|
await conversationStore.update(conversation);
|
|
8772
9091
|
checkpointedRun = true;
|
|
@@ -9065,9 +9384,11 @@ data: ${JSON.stringify(data)}
|
|
|
9065
9384
|
handler._cronJobs = cronJobs;
|
|
9066
9385
|
handler._conversationStore = conversationStore;
|
|
9067
9386
|
try {
|
|
9068
|
-
const
|
|
9069
|
-
|
|
9070
|
-
|
|
9387
|
+
const allSummaries = await conversationStore.listSummaries();
|
|
9388
|
+
const subagentSummaries = allSummaries.filter((s) => s.parentConversationId);
|
|
9389
|
+
for (const s of subagentSummaries) {
|
|
9390
|
+
const conv = await conversationStore.get(s.conversationId);
|
|
9391
|
+
if (conv?.subagentMeta?.status === "running") {
|
|
9071
9392
|
conv.subagentMeta.status = "stopped";
|
|
9072
9393
|
conv.subagentMeta.error = { code: "SERVER_RESTART", message: "Interrupted by server restart" };
|
|
9073
9394
|
conv.updatedAt = Date.now();
|
|
@@ -9299,7 +9620,7 @@ var runInteractive = async (workingDir, params) => {
|
|
|
9299
9620
|
await harness.initialize();
|
|
9300
9621
|
const identity = await ensureAgentIdentity2(workingDir);
|
|
9301
9622
|
try {
|
|
9302
|
-
const { runInteractiveInk } = await import("./run-interactive-ink-
|
|
9623
|
+
const { runInteractiveInk } = await import("./run-interactive-ink-4KVVPMR3.js");
|
|
9303
9624
|
await runInteractiveInk({
|
|
9304
9625
|
harness,
|
|
9305
9626
|
params,
|
|
@@ -9680,6 +10001,15 @@ var mcpAdd = async (workingDir, options) => {
|
|
|
9680
10001
|
if (!options.url.startsWith("http://") && !options.url.startsWith("https://")) {
|
|
9681
10002
|
throw new Error("Invalid MCP URL. Expected http:// or https://.");
|
|
9682
10003
|
}
|
|
10004
|
+
const parsedHeaders = options.headers && options.headers.length > 0 ? Object.fromEntries(
|
|
10005
|
+
options.headers.map((h) => {
|
|
10006
|
+
const idx = h.indexOf(":");
|
|
10007
|
+
if (idx < 1) {
|
|
10008
|
+
throw new Error(`Invalid header format "${h}". Expected "Name: value".`);
|
|
10009
|
+
}
|
|
10010
|
+
return [h.slice(0, idx).trim(), h.slice(idx + 1).trim()];
|
|
10011
|
+
})
|
|
10012
|
+
) : void 0;
|
|
9683
10013
|
const serverName = options.name ?? normalizeMcpName({ url: options.url });
|
|
9684
10014
|
mcp.push({
|
|
9685
10015
|
name: serverName,
|
|
@@ -9688,7 +10018,8 @@ var mcpAdd = async (workingDir, options) => {
|
|
|
9688
10018
|
auth: options.authBearerEnv ? {
|
|
9689
10019
|
type: "bearer",
|
|
9690
10020
|
tokenEnv: options.authBearerEnv
|
|
9691
|
-
} : void 0
|
|
10021
|
+
} : void 0,
|
|
10022
|
+
headers: parsedHeaders
|
|
9692
10023
|
});
|
|
9693
10024
|
await writeConfigFile(workingDir, { ...config, mcp });
|
|
9694
10025
|
let envSeedMessage;
|
|
@@ -9732,8 +10063,10 @@ var mcpList = async (workingDir) => {
|
|
|
9732
10063
|
process.stdout.write("Configured MCP servers:\n");
|
|
9733
10064
|
for (const entry of mcp) {
|
|
9734
10065
|
const auth = entry.auth?.type === "bearer" ? `auth=bearer:${entry.auth.tokenEnv}` : "auth=none";
|
|
10066
|
+
const headerKeys = entry.headers ? Object.keys(entry.headers) : [];
|
|
10067
|
+
const headerInfo = headerKeys.length > 0 ? `, headers=${headerKeys.join(",")}` : "";
|
|
9735
10068
|
process.stdout.write(
|
|
9736
|
-
`- ${entry.name ?? entry.url} (remote: ${entry.url}, ${auth})
|
|
10069
|
+
`- ${entry.name ?? entry.url} (remote: ${entry.url}, ${auth}${headerInfo})
|
|
9737
10070
|
`
|
|
9738
10071
|
);
|
|
9739
10072
|
}
|
|
@@ -9937,13 +10270,17 @@ var buildCli = () => {
|
|
|
9937
10270
|
).option("--env <name>", "env variable (repeatable)", (value, all) => {
|
|
9938
10271
|
all.push(value);
|
|
9939
10272
|
return all;
|
|
10273
|
+
}, []).option("--header <header>", "custom header as 'Name: value' (repeatable)", (value, all) => {
|
|
10274
|
+
all.push(value);
|
|
10275
|
+
return all;
|
|
9940
10276
|
}, []).action(
|
|
9941
10277
|
async (options) => {
|
|
9942
10278
|
await mcpAdd(process.cwd(), {
|
|
9943
10279
|
url: options.url,
|
|
9944
10280
|
name: options.name,
|
|
9945
10281
|
envVars: options.env,
|
|
9946
|
-
authBearerEnv: options.authBearerEnv
|
|
10282
|
+
authBearerEnv: options.authBearerEnv,
|
|
10283
|
+
headers: options.header
|
|
9947
10284
|
});
|
|
9948
10285
|
}
|
|
9949
10286
|
);
|