@poncho-ai/cli 0.30.0 → 0.30.2
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 +6 -6
- package/CHANGELOG.md +28 -0
- package/dist/{chunk-5OLH7U3C.js → chunk-FA546WPW.js} +703 -208
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.js +1 -1
- package/dist/{run-interactive-ink-EMTC7MK7.js → run-interactive-ink-FUMHN6DS.js} +1 -1
- package/package.json +4 -4
- package/src/index.ts +368 -110
- package/src/web-ui-client.ts +241 -9
- package/src/web-ui-styles.ts +50 -0
|
@@ -442,6 +442,41 @@ var WEB_UI_STYLES = `
|
|
|
442
442
|
.conversation-item .delete-btn.confirming:hover {
|
|
443
443
|
color: var(--error-alt);
|
|
444
444
|
}
|
|
445
|
+
.cron-section-header {
|
|
446
|
+
display: flex;
|
|
447
|
+
align-items: center;
|
|
448
|
+
gap: 6px;
|
|
449
|
+
padding: 8px 10px 4px;
|
|
450
|
+
cursor: pointer;
|
|
451
|
+
font-size: 11px;
|
|
452
|
+
font-weight: 600;
|
|
453
|
+
color: var(--fg-7);
|
|
454
|
+
text-transform: uppercase;
|
|
455
|
+
letter-spacing: 0.04em;
|
|
456
|
+
user-select: none;
|
|
457
|
+
transition: color 0.15s;
|
|
458
|
+
}
|
|
459
|
+
.cron-section-header:hover { color: var(--fg-5); }
|
|
460
|
+
.cron-section-caret {
|
|
461
|
+
display: inline-flex;
|
|
462
|
+
transition: transform 0.15s;
|
|
463
|
+
}
|
|
464
|
+
.cron-section-caret.open { transform: rotate(90deg); }
|
|
465
|
+
.cron-section-count {
|
|
466
|
+
font-weight: 400;
|
|
467
|
+
color: var(--fg-8);
|
|
468
|
+
font-size: 11px;
|
|
469
|
+
}
|
|
470
|
+
.cron-view-more {
|
|
471
|
+
padding: 6px 10px;
|
|
472
|
+
font-size: 12px;
|
|
473
|
+
color: var(--fg-7);
|
|
474
|
+
cursor: pointer;
|
|
475
|
+
text-align: center;
|
|
476
|
+
transition: color 0.15s;
|
|
477
|
+
user-select: none;
|
|
478
|
+
}
|
|
479
|
+
.cron-view-more:hover { color: var(--fg-3); }
|
|
445
480
|
.sidebar-footer {
|
|
446
481
|
margin-top: auto;
|
|
447
482
|
padding-top: 8px;
|
|
@@ -1613,6 +1648,8 @@ var WEB_UI_STYLES = `
|
|
|
1613
1648
|
line-height: 1.45;
|
|
1614
1649
|
color: var(--fg-tool-code);
|
|
1615
1650
|
width: 100%;
|
|
1651
|
+
min-width: 0;
|
|
1652
|
+
overflow: hidden;
|
|
1616
1653
|
}
|
|
1617
1654
|
.subagent-result-summary {
|
|
1618
1655
|
list-style: none;
|
|
@@ -1657,6 +1694,19 @@ var WEB_UI_STYLES = `
|
|
|
1657
1694
|
display: grid;
|
|
1658
1695
|
gap: 6px;
|
|
1659
1696
|
padding: 0 12px 10px;
|
|
1697
|
+
min-width: 0;
|
|
1698
|
+
overflow-x: auto;
|
|
1699
|
+
overflow-wrap: break-word;
|
|
1700
|
+
word-break: break-word;
|
|
1701
|
+
}
|
|
1702
|
+
.subagent-result-body pre {
|
|
1703
|
+
max-width: 100%;
|
|
1704
|
+
overflow-x: auto;
|
|
1705
|
+
}
|
|
1706
|
+
.subagent-result-body table {
|
|
1707
|
+
max-width: 100%;
|
|
1708
|
+
overflow-x: auto;
|
|
1709
|
+
display: block;
|
|
1660
1710
|
}
|
|
1661
1711
|
|
|
1662
1712
|
/* Todo panel \u2014 inside composer-inner, above the input shell */
|
|
@@ -1809,6 +1859,8 @@ var getWebUiClientScript = (markedSource2) => `
|
|
|
1809
1859
|
parentConversationId: null,
|
|
1810
1860
|
todos: [],
|
|
1811
1861
|
todoPanelCollapsed: false,
|
|
1862
|
+
cronSectionCollapsed: true,
|
|
1863
|
+
cronShowAll: false,
|
|
1812
1864
|
};
|
|
1813
1865
|
|
|
1814
1866
|
const agentInitial = document.body.dataset.agentInitial || "A";
|
|
@@ -2552,11 +2604,86 @@ var getWebUiClientScript = (markedSource2) => `
|
|
|
2552
2604
|
}
|
|
2553
2605
|
};
|
|
2554
2606
|
|
|
2607
|
+
const cronCaretSvg = '<svg viewBox="0 0 12 12" fill="none" width="10" height="10"><path d="M4.5 2.75L8 6L4.5 9.25" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg>';
|
|
2608
|
+
|
|
2609
|
+
const parseCronTitle = (title) => {
|
|
2610
|
+
const rest = title.replace(/^[cron]s*/, "");
|
|
2611
|
+
const isoMatch = rest.match(/s(d{4}-d{2}-d{2}T[d:.]+Z?)$/);
|
|
2612
|
+
if (isoMatch) {
|
|
2613
|
+
return { jobName: rest.slice(0, isoMatch.index).trim(), timestamp: isoMatch[1] };
|
|
2614
|
+
}
|
|
2615
|
+
return { jobName: rest, timestamp: "" };
|
|
2616
|
+
};
|
|
2617
|
+
|
|
2618
|
+
const formatCronTimestamp = (isoStr) => {
|
|
2619
|
+
if (!isoStr) return "";
|
|
2620
|
+
try {
|
|
2621
|
+
const d = new Date(isoStr);
|
|
2622
|
+
if (isNaN(d.getTime())) return isoStr;
|
|
2623
|
+
return d.toLocaleString(undefined, { month: "short", day: "numeric", hour: "numeric", minute: "2-digit" });
|
|
2624
|
+
} catch { return isoStr; }
|
|
2625
|
+
};
|
|
2626
|
+
|
|
2627
|
+
const CRON_PAGE_SIZE = 20;
|
|
2628
|
+
|
|
2629
|
+
const appendCronSection = (cronConvs, needsDivider) => {
|
|
2630
|
+
if (needsDivider) {
|
|
2631
|
+
const divider = document.createElement("div");
|
|
2632
|
+
divider.className = "sidebar-section-divider";
|
|
2633
|
+
elements.list.appendChild(divider);
|
|
2634
|
+
}
|
|
2635
|
+
|
|
2636
|
+
cronConvs.sort((a, b) => (b.updatedAt || b.createdAt || 0) - (a.updatedAt || a.createdAt || 0));
|
|
2637
|
+
|
|
2638
|
+
const isOpen = !state.cronSectionCollapsed;
|
|
2639
|
+
const header = document.createElement("div");
|
|
2640
|
+
header.className = "cron-section-header";
|
|
2641
|
+
header.innerHTML =
|
|
2642
|
+
'<span class="cron-section-caret' + (isOpen ? ' open' : '') + '">' + cronCaretSvg + '</span>' +
|
|
2643
|
+
'<span>Cron jobs</span>' +
|
|
2644
|
+
'<span class="cron-section-count">' + cronConvs.length + '</span>';
|
|
2645
|
+
header.onclick = () => {
|
|
2646
|
+
state.cronSectionCollapsed = !state.cronSectionCollapsed;
|
|
2647
|
+
state.cronShowAll = false;
|
|
2648
|
+
renderConversationList();
|
|
2649
|
+
};
|
|
2650
|
+
elements.list.appendChild(header);
|
|
2651
|
+
|
|
2652
|
+
if (state.cronSectionCollapsed) return;
|
|
2653
|
+
|
|
2654
|
+
const limit = state.cronShowAll ? cronConvs.length : CRON_PAGE_SIZE;
|
|
2655
|
+
const visible = cronConvs.slice(0, limit);
|
|
2656
|
+
|
|
2657
|
+
for (const c of visible) {
|
|
2658
|
+
const { jobName, timestamp } = parseCronTitle(c.title);
|
|
2659
|
+
const fmtTime = formatCronTimestamp(timestamp);
|
|
2660
|
+
const displayTitle = fmtTime ? jobName + " \\u00b7 " + fmtTime : c.title;
|
|
2661
|
+
elements.list.appendChild(buildConversationItem(Object.assign({}, c, { title: displayTitle })));
|
|
2662
|
+
appendSubagentsIfActive(c.conversationId);
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2665
|
+
if (!state.cronShowAll && cronConvs.length > CRON_PAGE_SIZE) {
|
|
2666
|
+
const remaining = cronConvs.length - CRON_PAGE_SIZE;
|
|
2667
|
+
const viewMore = document.createElement("div");
|
|
2668
|
+
viewMore.className = "cron-view-more";
|
|
2669
|
+
viewMore.textContent = "View " + remaining + " more\\u2026";
|
|
2670
|
+
viewMore.onclick = () => {
|
|
2671
|
+
state.cronShowAll = true;
|
|
2672
|
+
renderConversationList();
|
|
2673
|
+
};
|
|
2674
|
+
elements.list.appendChild(viewMore);
|
|
2675
|
+
}
|
|
2676
|
+
};
|
|
2677
|
+
|
|
2555
2678
|
const renderConversationList = () => {
|
|
2556
2679
|
elements.list.innerHTML = "";
|
|
2557
2680
|
const pending = state.conversations.filter(c => c.hasPendingApprovals);
|
|
2558
2681
|
const rest = state.conversations.filter(c => !c.hasPendingApprovals);
|
|
2559
2682
|
|
|
2683
|
+
const isCron = (c) => c.title && c.title.startsWith("[cron]");
|
|
2684
|
+
const cronConvs = rest.filter(isCron);
|
|
2685
|
+
const nonCron = rest.filter(c => !isCron(c));
|
|
2686
|
+
|
|
2560
2687
|
if (pending.length > 0) {
|
|
2561
2688
|
const label = document.createElement("div");
|
|
2562
2689
|
label.className = "sidebar-section-label";
|
|
@@ -2575,7 +2702,7 @@ var getWebUiClientScript = (markedSource2) => `
|
|
|
2575
2702
|
const latest = [];
|
|
2576
2703
|
const previous7 = [];
|
|
2577
2704
|
const older = [];
|
|
2578
|
-
for (const c of
|
|
2705
|
+
for (const c of nonCron) {
|
|
2579
2706
|
const ts = c.updatedAt || c.createdAt || 0;
|
|
2580
2707
|
if (ts >= startOfToday) {
|
|
2581
2708
|
latest.push(c);
|
|
@@ -2587,6 +2714,12 @@ var getWebUiClientScript = (markedSource2) => `
|
|
|
2587
2714
|
}
|
|
2588
2715
|
|
|
2589
2716
|
let sectionRendered = pending.length > 0;
|
|
2717
|
+
|
|
2718
|
+
if (cronConvs.length > 0) {
|
|
2719
|
+
appendCronSection(cronConvs, sectionRendered);
|
|
2720
|
+
sectionRendered = true;
|
|
2721
|
+
}
|
|
2722
|
+
|
|
2590
2723
|
const appendSection = (items, labelText) => {
|
|
2591
2724
|
if (items.length === 0) return;
|
|
2592
2725
|
if (sectionRendered) {
|
|
@@ -3040,6 +3173,101 @@ var getWebUiClientScript = (markedSource2) => `
|
|
|
3040
3173
|
});
|
|
3041
3174
|
} else if (willStream) {
|
|
3042
3175
|
setStreaming(true);
|
|
3176
|
+
} else if (payload.needsContinuation && !payload.conversation.parentConversationId) {
|
|
3177
|
+
console.log("[poncho] Detected orphaned continuation for", conversationId, "\u2014 auto-resuming");
|
|
3178
|
+
(async () => {
|
|
3179
|
+
try {
|
|
3180
|
+
setStreaming(true);
|
|
3181
|
+
var localMsgs = state.activeMessages || [];
|
|
3182
|
+
var contAssistant = {
|
|
3183
|
+
role: "assistant",
|
|
3184
|
+
content: "",
|
|
3185
|
+
_sections: [],
|
|
3186
|
+
_currentText: "",
|
|
3187
|
+
_currentTools: [],
|
|
3188
|
+
_toolImages: [],
|
|
3189
|
+
_activeActivities: [],
|
|
3190
|
+
_pendingApprovals: [],
|
|
3191
|
+
metadata: { toolActivity: [] }
|
|
3192
|
+
};
|
|
3193
|
+
localMsgs.push(contAssistant);
|
|
3194
|
+
state.activeMessages = localMsgs;
|
|
3195
|
+
state._activeStreamMessages = localMsgs;
|
|
3196
|
+
renderMessages(localMsgs, true);
|
|
3197
|
+
var contResp = await fetch(
|
|
3198
|
+
"/api/conversations/" + encodeURIComponent(conversationId) + "/messages",
|
|
3199
|
+
{
|
|
3200
|
+
method: "POST",
|
|
3201
|
+
credentials: "include",
|
|
3202
|
+
headers: { "Content-Type": "application/json", "x-csrf-token": state.csrfToken },
|
|
3203
|
+
body: JSON.stringify({ continuation: true }),
|
|
3204
|
+
},
|
|
3205
|
+
);
|
|
3206
|
+
if (!contResp.ok || !contResp.body) {
|
|
3207
|
+
contAssistant._error = "Failed to resume \u2014 reload to retry";
|
|
3208
|
+
setStreaming(false);
|
|
3209
|
+
renderMessages(localMsgs, false);
|
|
3210
|
+
return;
|
|
3211
|
+
}
|
|
3212
|
+
state.activeStreamConversationId = conversationId;
|
|
3213
|
+
var contReader = contResp.body.getReader();
|
|
3214
|
+
var contDecoder = new TextDecoder();
|
|
3215
|
+
var contBuffer = "";
|
|
3216
|
+
while (true) {
|
|
3217
|
+
var chunk = await contReader.read();
|
|
3218
|
+
if (chunk.done) break;
|
|
3219
|
+
contBuffer += contDecoder.decode(chunk.value, { stream: true });
|
|
3220
|
+
contBuffer = parseSseChunk(contBuffer, function(evtName, evtPayload) {
|
|
3221
|
+
if (evtName === "model:chunk" && evtPayload.content) {
|
|
3222
|
+
contAssistant.content = (contAssistant.content || "") + evtPayload.content;
|
|
3223
|
+
contAssistant._currentText += evtPayload.content;
|
|
3224
|
+
}
|
|
3225
|
+
if (evtName === "tool:started") {
|
|
3226
|
+
if (contAssistant._currentText) {
|
|
3227
|
+
contAssistant._sections.push({ type: "text", content: contAssistant._currentText });
|
|
3228
|
+
contAssistant._currentText = "";
|
|
3229
|
+
}
|
|
3230
|
+
contAssistant._currentTools.push("- start \`" + evtPayload.tool + "\`");
|
|
3231
|
+
}
|
|
3232
|
+
if (evtName === "tool:completed") {
|
|
3233
|
+
contAssistant._currentTools.push("- done \`" + evtPayload.tool + "\` (" + evtPayload.duration + "ms)");
|
|
3234
|
+
}
|
|
3235
|
+
if (evtName === "tool:error") {
|
|
3236
|
+
contAssistant._currentTools.push("- error \`" + evtPayload.tool + "\`: " + evtPayload.error);
|
|
3237
|
+
}
|
|
3238
|
+
if (evtName === "run:completed" || evtName === "run:error" || evtName === "run:cancelled") {
|
|
3239
|
+
if (contAssistant._currentTools.length > 0) {
|
|
3240
|
+
contAssistant._sections.push({ type: "tools", content: contAssistant._currentTools });
|
|
3241
|
+
contAssistant._currentTools = [];
|
|
3242
|
+
}
|
|
3243
|
+
if (contAssistant._currentText) {
|
|
3244
|
+
contAssistant._sections.push({ type: "text", content: contAssistant._currentText });
|
|
3245
|
+
contAssistant._currentText = "";
|
|
3246
|
+
}
|
|
3247
|
+
contAssistant._activeActivities = [];
|
|
3248
|
+
if (evtName === "run:error") {
|
|
3249
|
+
contAssistant._error = evtPayload.error?.message || "Something went wrong";
|
|
3250
|
+
}
|
|
3251
|
+
if (evtName === "run:completed" && evtPayload.result?.continuation === true) {
|
|
3252
|
+
// Another continuation needed \u2014 reload to pick it up
|
|
3253
|
+
loadConversation(conversationId).catch(function() {});
|
|
3254
|
+
}
|
|
3255
|
+
}
|
|
3256
|
+
renderMessages(localMsgs, true);
|
|
3257
|
+
});
|
|
3258
|
+
}
|
|
3259
|
+
setStreaming(false);
|
|
3260
|
+
renderMessages(localMsgs, false);
|
|
3261
|
+
await loadConversations();
|
|
3262
|
+
} catch (contErr) {
|
|
3263
|
+
console.error("[poncho] Auto-continuation failed:", contErr);
|
|
3264
|
+
setStreaming(false);
|
|
3265
|
+
await loadConversation(conversationId).catch(function() {});
|
|
3266
|
+
} finally {
|
|
3267
|
+
state.activeStreamConversationId = null;
|
|
3268
|
+
state._activeStreamMessages = null;
|
|
3269
|
+
}
|
|
3270
|
+
})();
|
|
3043
3271
|
}
|
|
3044
3272
|
};
|
|
3045
3273
|
|
|
@@ -4123,6 +4351,7 @@ var getWebUiClientScript = (markedSource2) => `
|
|
|
4123
4351
|
let _totalSteps = 0;
|
|
4124
4352
|
let _maxSteps = 0;
|
|
4125
4353
|
let _isContinuation = false;
|
|
4354
|
+
let _receivedTerminalEvent = false;
|
|
4126
4355
|
while (true) {
|
|
4127
4356
|
let _shouldContinue = false;
|
|
4128
4357
|
let fetchOpts;
|
|
@@ -4452,6 +4681,7 @@ var getWebUiClientScript = (markedSource2) => `
|
|
|
4452
4681
|
}
|
|
4453
4682
|
}
|
|
4454
4683
|
if (eventName === "run:completed") {
|
|
4684
|
+
_receivedTerminalEvent = true;
|
|
4455
4685
|
_totalSteps += typeof payload.result?.steps === "number" ? payload.result.steps : 0;
|
|
4456
4686
|
if (typeof payload.result?.maxSteps === "number") _maxSteps = payload.result.maxSteps;
|
|
4457
4687
|
if (payload.result?.continuation === true && (_maxSteps <= 0 || _totalSteps < _maxSteps)) {
|
|
@@ -4468,10 +4698,12 @@ var getWebUiClientScript = (markedSource2) => `
|
|
|
4468
4698
|
}
|
|
4469
4699
|
}
|
|
4470
4700
|
if (eventName === "run:cancelled") {
|
|
4701
|
+
_receivedTerminalEvent = true;
|
|
4471
4702
|
finalizeAssistantMessage();
|
|
4472
4703
|
renderIfActiveConversation(false);
|
|
4473
4704
|
}
|
|
4474
4705
|
if (eventName === "run:error") {
|
|
4706
|
+
_receivedTerminalEvent = true;
|
|
4475
4707
|
finalizeAssistantMessage();
|
|
4476
4708
|
const errMsg = payload.error?.message || "Something went wrong";
|
|
4477
4709
|
assistantMessage._error = errMsg;
|
|
@@ -4482,7 +4714,19 @@ var getWebUiClientScript = (markedSource2) => `
|
|
|
4482
4714
|
}
|
|
4483
4715
|
});
|
|
4484
4716
|
}
|
|
4717
|
+
if (!_shouldContinue && !_receivedTerminalEvent) {
|
|
4718
|
+
try {
|
|
4719
|
+
const recoveryPayload = await api("/api/conversations/" + encodeURIComponent(conversationId));
|
|
4720
|
+
if (recoveryPayload.needsContinuation) {
|
|
4721
|
+
_shouldContinue = true;
|
|
4722
|
+
console.log("[poncho] Stream ended without terminal event, server has continuation \u2014 resuming");
|
|
4723
|
+
}
|
|
4724
|
+
} catch (_recoverErr) {
|
|
4725
|
+
console.warn("[poncho] Recovery check failed after abrupt stream end");
|
|
4726
|
+
}
|
|
4727
|
+
}
|
|
4485
4728
|
if (!_shouldContinue) break;
|
|
4729
|
+
_receivedTerminalEvent = false;
|
|
4486
4730
|
_isContinuation = true;
|
|
4487
4731
|
}
|
|
4488
4732
|
// Update active state only if user is still on this conversation.
|
|
@@ -4756,7 +5000,7 @@ var getWebUiClientScript = (markedSource2) => `
|
|
|
4756
5000
|
state: "resolved",
|
|
4757
5001
|
resolvedDecision: decision,
|
|
4758
5002
|
}));
|
|
4759
|
-
api("/api/approvals/" + encodeURIComponent(approvalId), {
|
|
5003
|
+
return api("/api/approvals/" + encodeURIComponent(approvalId), {
|
|
4760
5004
|
method: "POST",
|
|
4761
5005
|
body: JSON.stringify({ approved: decision === "approve" }),
|
|
4762
5006
|
}).catch((error) => {
|
|
@@ -4804,16 +5048,54 @@ var getWebUiClientScript = (markedSource2) => `
|
|
|
4804
5048
|
if (pending.length === 0) return;
|
|
4805
5049
|
const wasStreaming = state.isStreaming;
|
|
4806
5050
|
if (!wasStreaming) setStreaming(true);
|
|
4807
|
-
|
|
5051
|
+
// Mark all items as resolved in the UI immediately
|
|
5052
|
+
for (const aid of pending) {
|
|
5053
|
+
state.approvalRequestsInFlight[aid] = true;
|
|
5054
|
+
updatePendingApproval(aid, (request) => ({
|
|
5055
|
+
...request,
|
|
5056
|
+
state: "resolved",
|
|
5057
|
+
resolvedDecision: decision,
|
|
5058
|
+
}));
|
|
5059
|
+
}
|
|
4808
5060
|
renderMessages(state.activeMessages, state.isStreaming);
|
|
4809
5061
|
loadConversations();
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
5062
|
+
const streamCid = !wasStreaming && state.activeConversationId
|
|
5063
|
+
? state.activeConversationId
|
|
5064
|
+
: null;
|
|
5065
|
+
if (streamCid) {
|
|
5066
|
+
streamConversationEvents(streamCid, { liveOnly: true }).finally(() => {
|
|
5067
|
+
if (state.activeConversationId === streamCid) {
|
|
5068
|
+
pollUntilRunIdle(streamCid);
|
|
5069
|
+
}
|
|
5070
|
+
});
|
|
4816
5071
|
}
|
|
5072
|
+
// Send API calls sequentially so each store write completes
|
|
5073
|
+
// before the next read (avoids last-writer-wins in serverless).
|
|
5074
|
+
void (async () => {
|
|
5075
|
+
for (const aid of pending) {
|
|
5076
|
+
await api("/api/approvals/" + encodeURIComponent(aid), {
|
|
5077
|
+
method: "POST",
|
|
5078
|
+
body: JSON.stringify({ approved: decision === "approve" }),
|
|
5079
|
+
}).catch((error) => {
|
|
5080
|
+
const isStale = error && error.payload && error.payload.code === "APPROVAL_NOT_FOUND";
|
|
5081
|
+
if (isStale) {
|
|
5082
|
+
updatePendingApproval(aid, () => null);
|
|
5083
|
+
} else {
|
|
5084
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
5085
|
+
updatePendingApproval(aid, (request) => ({
|
|
5086
|
+
...request,
|
|
5087
|
+
state: "pending",
|
|
5088
|
+
pendingDecision: null,
|
|
5089
|
+
resolvedDecision: null,
|
|
5090
|
+
_error: errMsg,
|
|
5091
|
+
}));
|
|
5092
|
+
}
|
|
5093
|
+
renderMessages(state.activeMessages, state.isStreaming);
|
|
5094
|
+
}).finally(() => {
|
|
5095
|
+
delete state.approvalRequestsInFlight[aid];
|
|
5096
|
+
});
|
|
5097
|
+
}
|
|
5098
|
+
})();
|
|
4817
5099
|
return;
|
|
4818
5100
|
}
|
|
4819
5101
|
|
|
@@ -7493,12 +7775,16 @@ Set environment variables on your deployment platform:
|
|
|
7493
7775
|
ANTHROPIC_API_KEY=sk-ant-... # Required
|
|
7494
7776
|
PONCHO_AUTH_TOKEN=your-secret # Optional: protect your endpoint
|
|
7495
7777
|
PONCHO_MAX_DURATION=55 # Optional: serverless timeout in seconds (enables auto-continuation)
|
|
7778
|
+
PONCHO_INTERNAL_SECRET=... # Recommended on serverless: shared secret for internal callback auth
|
|
7496
7779
|
\`\`\`
|
|
7497
7780
|
|
|
7498
7781
|
When \`PONCHO_MAX_DURATION\` is set, the agent automatically checkpoints and resumes across
|
|
7499
7782
|
request cycles when it approaches the platform timeout. The web UI and client SDK handle
|
|
7500
7783
|
this transparently.
|
|
7501
7784
|
|
|
7785
|
+
For serverless deployments with subagents or background callbacks, use a shared state backend
|
|
7786
|
+
(\`upstash\`, \`redis\`, or \`dynamodb\`) instead of \`state.provider: 'local'\` / \`'memory'\`.
|
|
7787
|
+
|
|
7502
7788
|
## Troubleshooting
|
|
7503
7789
|
|
|
7504
7790
|
### Vercel deploy issues
|
|
@@ -7506,6 +7792,7 @@ this transparently.
|
|
|
7506
7792
|
- After upgrading \`@poncho-ai/cli\`, re-run \`poncho build vercel --force\` to refresh generated deploy files.
|
|
7507
7793
|
- If Vercel fails during \`pnpm install\` due to a lockfile mismatch, run \`pnpm install --no-frozen-lockfile\` locally and commit \`pnpm-lock.yaml\`.
|
|
7508
7794
|
- Deploy from the project root: \`vercel deploy --prod\`.
|
|
7795
|
+
- For subagents/background callbacks, set \`PONCHO_INTERNAL_SECRET\` and use non-local state storage.
|
|
7509
7796
|
|
|
7510
7797
|
For full reference:
|
|
7511
7798
|
https://github.com/cesr/poncho-ai
|
|
@@ -8221,7 +8508,8 @@ data: ${JSON.stringify(statusPayload)}
|
|
|
8221
8508
|
await harness.initialize();
|
|
8222
8509
|
const telemetry = new TelemetryEmitter(config?.telemetry);
|
|
8223
8510
|
const identity = await ensureAgentIdentity2(workingDir);
|
|
8224
|
-
const
|
|
8511
|
+
const stateConfig = resolveStateConfig(config);
|
|
8512
|
+
const conversationStore = createConversationStore(stateConfig, {
|
|
8225
8513
|
workingDir,
|
|
8226
8514
|
agentId: identity.id
|
|
8227
8515
|
});
|
|
@@ -8229,6 +8517,7 @@ data: ${JSON.stringify(statusPayload)}
|
|
|
8229
8517
|
const MAX_CONCURRENT_SUBAGENTS = 5;
|
|
8230
8518
|
const activeSubagentRuns = /* @__PURE__ */ new Map();
|
|
8231
8519
|
const pendingSubagentApprovals = /* @__PURE__ */ new Map();
|
|
8520
|
+
const approvalDecisionTracker = /* @__PURE__ */ new Map();
|
|
8232
8521
|
const getSubagentDepth = async (conversationId) => {
|
|
8233
8522
|
let depth = 0;
|
|
8234
8523
|
let current = await conversationStore.get(conversationId);
|
|
@@ -8559,9 +8848,10 @@ data: ${JSON.stringify(statusPayload)}
|
|
|
8559
8848
|
} catch {
|
|
8560
8849
|
}
|
|
8561
8850
|
if (isServerless) {
|
|
8562
|
-
selfFetchWithRetry(`/api/internal/subagent/${encodeURIComponent(childConversationId)}/run`, { continuation: true }).catch(
|
|
8851
|
+
const work = selfFetchWithRetry(`/api/internal/subagent/${encodeURIComponent(childConversationId)}/run`, { continuation: true }).catch(
|
|
8563
8852
|
(err) => console.error(`[poncho][subagent] Continuation self-fetch failed:`, err instanceof Error ? err.message : err)
|
|
8564
8853
|
);
|
|
8854
|
+
doWaitUntil(work);
|
|
8565
8855
|
} else {
|
|
8566
8856
|
runSubagent(childConversationId, parentConversationId, task, ownerId, true).catch(
|
|
8567
8857
|
(err) => console.error(`[poncho][subagent] Continuation failed:`, err instanceof Error ? err.message : err)
|
|
@@ -8646,8 +8936,10 @@ data: ${JSON.stringify(statusPayload)}
|
|
|
8646
8936
|
}
|
|
8647
8937
|
};
|
|
8648
8938
|
const MAX_SUBAGENT_CALLBACK_COUNT = 20;
|
|
8939
|
+
const pendingCallbackNeeded = /* @__PURE__ */ new Set();
|
|
8649
8940
|
const triggerParentCallback = async (parentConversationId) => {
|
|
8650
8941
|
if (activeConversationRuns.has(parentConversationId)) {
|
|
8942
|
+
pendingCallbackNeeded.add(parentConversationId);
|
|
8651
8943
|
return;
|
|
8652
8944
|
}
|
|
8653
8945
|
if (isServerless) {
|
|
@@ -8659,12 +8951,13 @@ data: ${JSON.stringify(statusPayload)}
|
|
|
8659
8951
|
await processSubagentCallback(parentConversationId);
|
|
8660
8952
|
};
|
|
8661
8953
|
const CALLBACK_LOCK_STALE_MS = 5 * 60 * 1e3;
|
|
8662
|
-
const processSubagentCallback = async (conversationId) => {
|
|
8954
|
+
const processSubagentCallback = async (conversationId, skipLockCheck = false) => {
|
|
8663
8955
|
const conversation = await conversationStore.get(conversationId);
|
|
8664
8956
|
if (!conversation) return;
|
|
8665
8957
|
const pendingResults = conversation.pendingSubagentResults ?? [];
|
|
8666
|
-
|
|
8667
|
-
if (
|
|
8958
|
+
const hasOrphanedContinuation = pendingResults.length === 0 && Array.isArray(conversation._continuationMessages) && conversation._continuationMessages.length > 0 && !activeConversationRuns.has(conversationId);
|
|
8959
|
+
if (pendingResults.length === 0 && !hasOrphanedContinuation) return;
|
|
8960
|
+
if (!skipLockCheck && conversation.runningCallbackSince) {
|
|
8668
8961
|
const elapsed = Date.now() - conversation.runningCallbackSince;
|
|
8669
8962
|
if (elapsed < CALLBACK_LOCK_STALE_MS) {
|
|
8670
8963
|
return;
|
|
@@ -8673,6 +8966,7 @@ data: ${JSON.stringify(statusPayload)}
|
|
|
8673
8966
|
}
|
|
8674
8967
|
conversation.pendingSubagentResults = [];
|
|
8675
8968
|
conversation.runningCallbackSince = Date.now();
|
|
8969
|
+
conversation.runStatus = "running";
|
|
8676
8970
|
const callbackCount = (conversation.subagentCallbackCount ?? 0) + 1;
|
|
8677
8971
|
conversation.subagentCallbackCount = callbackCount;
|
|
8678
8972
|
for (const pr of pendingResults) {
|
|
@@ -8692,21 +8986,36 @@ ${resultBody}`,
|
|
|
8692
8986
|
if (callbackCount > MAX_SUBAGENT_CALLBACK_COUNT) {
|
|
8693
8987
|
console.warn(`[poncho][subagent-callback] Circuit breaker: ${callbackCount} callbacks for ${conversationId}, skipping re-run`);
|
|
8694
8988
|
conversation.runningCallbackSince = void 0;
|
|
8989
|
+
conversation.runStatus = "idle";
|
|
8695
8990
|
await conversationStore.update(conversation);
|
|
8696
8991
|
return;
|
|
8697
8992
|
}
|
|
8698
|
-
|
|
8993
|
+
const isContinuationResume = hasOrphanedContinuation && pendingResults.length === 0;
|
|
8994
|
+
console.log(`[poncho][subagent-callback] Processing ${pendingResults.length} result(s) for ${conversationId} (callback #${callbackCount})${isContinuationResume ? " (continuation resume)" : ""}`);
|
|
8699
8995
|
const abortController = new AbortController();
|
|
8700
8996
|
activeConversationRuns.set(conversationId, {
|
|
8701
8997
|
ownerId: conversation.ownerId,
|
|
8702
8998
|
abortController,
|
|
8703
8999
|
runId: null
|
|
8704
9000
|
});
|
|
8705
|
-
const
|
|
9001
|
+
const prevStream = conversationEventStreams.get(conversationId);
|
|
9002
|
+
if (prevStream) {
|
|
9003
|
+
prevStream.finished = false;
|
|
9004
|
+
prevStream.buffer = [];
|
|
9005
|
+
} else {
|
|
9006
|
+
conversationEventStreams.set(conversationId, {
|
|
9007
|
+
buffer: [],
|
|
9008
|
+
subscribers: /* @__PURE__ */ new Set(),
|
|
9009
|
+
finished: false
|
|
9010
|
+
});
|
|
9011
|
+
}
|
|
9012
|
+
const historyMessages = isContinuationResume && conversation._continuationMessages?.length ? [...conversation._continuationMessages] : [...conversation.messages];
|
|
8706
9013
|
let assistantResponse = "";
|
|
8707
9014
|
let latestRunId = "";
|
|
8708
9015
|
let runContinuation = false;
|
|
8709
9016
|
let runContinuationMessages;
|
|
9017
|
+
let runContextTokens = conversation.contextTokens ?? 0;
|
|
9018
|
+
let runContextWindow = conversation.contextWindow ?? 0;
|
|
8710
9019
|
const toolTimeline = [];
|
|
8711
9020
|
const sections = [];
|
|
8712
9021
|
let currentTools = [];
|
|
@@ -8761,6 +9070,8 @@ ${resultBody}`,
|
|
|
8761
9070
|
if (assistantResponse.length === 0 && event.result.response) {
|
|
8762
9071
|
assistantResponse = event.result.response;
|
|
8763
9072
|
}
|
|
9073
|
+
runContextTokens = event.result.contextTokens ?? runContextTokens;
|
|
9074
|
+
runContextWindow = event.result.contextWindow ?? runContextWindow;
|
|
8764
9075
|
if (event.result.continuation) {
|
|
8765
9076
|
runContinuation = true;
|
|
8766
9077
|
if (event.result.continuationMessages) {
|
|
@@ -8787,6 +9098,9 @@ ${resultBody}`,
|
|
|
8787
9098
|
}
|
|
8788
9099
|
freshConv.runtimeRunId = latestRunId || freshConv.runtimeRunId;
|
|
8789
9100
|
freshConv.runningCallbackSince = void 0;
|
|
9101
|
+
freshConv.runStatus = "idle";
|
|
9102
|
+
if (runContextTokens > 0) freshConv.contextTokens = runContextTokens;
|
|
9103
|
+
if (runContextWindow > 0) freshConv.contextWindow = runContextWindow;
|
|
8790
9104
|
freshConv.updatedAt = Date.now();
|
|
8791
9105
|
await conversationStore.update(freshConv);
|
|
8792
9106
|
if (freshConv.channelMeta && assistantResponse.length > 0) {
|
|
@@ -8809,9 +9123,14 @@ ${resultBody}`,
|
|
|
8809
9123
|
}
|
|
8810
9124
|
if (runContinuation) {
|
|
8811
9125
|
if (isServerless) {
|
|
8812
|
-
selfFetchWithRetry(`/api/internal/conversations/${encodeURIComponent(conversationId)}/subagent-callback`).catch(
|
|
9126
|
+
const work = selfFetchWithRetry(`/api/internal/conversations/${encodeURIComponent(conversationId)}/subagent-callback`).catch(
|
|
8813
9127
|
(err) => console.error(`[poncho][subagent-callback] Continuation self-fetch failed:`, err instanceof Error ? err.message : err)
|
|
8814
9128
|
);
|
|
9129
|
+
doWaitUntil(work);
|
|
9130
|
+
} else {
|
|
9131
|
+
processSubagentCallback(conversationId, true).catch(
|
|
9132
|
+
(err) => console.error(`[poncho][subagent-callback] Continuation failed:`, err instanceof Error ? err.message : err)
|
|
9133
|
+
);
|
|
8815
9134
|
}
|
|
8816
9135
|
}
|
|
8817
9136
|
} catch (err) {
|
|
@@ -8819,28 +9138,38 @@ ${resultBody}`,
|
|
|
8819
9138
|
const errConv = await conversationStore.get(conversationId);
|
|
8820
9139
|
if (errConv) {
|
|
8821
9140
|
errConv.runningCallbackSince = void 0;
|
|
9141
|
+
errConv.runStatus = "idle";
|
|
8822
9142
|
await conversationStore.update(errConv);
|
|
8823
9143
|
}
|
|
8824
9144
|
} finally {
|
|
8825
9145
|
activeConversationRuns.delete(conversationId);
|
|
8826
9146
|
finishConversationStream(conversationId);
|
|
9147
|
+
const hadDeferredTrigger = pendingCallbackNeeded.delete(conversationId);
|
|
8827
9148
|
const freshConv = await conversationStore.get(conversationId);
|
|
8828
|
-
|
|
8829
|
-
|
|
8830
|
-
freshConv.runningCallbackSince = void 0;
|
|
8831
|
-
await conversationStore.update(freshConv);
|
|
8832
|
-
}
|
|
8833
|
-
}
|
|
8834
|
-
if (freshConv?.pendingSubagentResults?.length) {
|
|
9149
|
+
const hasPendingInStore = !!freshConv?.pendingSubagentResults?.length;
|
|
9150
|
+
if (hadDeferredTrigger || hasPendingInStore) {
|
|
8835
9151
|
if (isServerless) {
|
|
8836
9152
|
selfFetchWithRetry(`/api/internal/conversations/${encodeURIComponent(conversationId)}/subagent-callback`).catch(
|
|
8837
9153
|
(err) => console.error(`[poncho][subagent-callback] Recursive callback self-fetch failed:`, err instanceof Error ? err.message : err)
|
|
8838
9154
|
);
|
|
8839
9155
|
} else {
|
|
8840
|
-
processSubagentCallback(conversationId).catch(
|
|
9156
|
+
processSubagentCallback(conversationId, true).catch(
|
|
8841
9157
|
(err) => console.error(`[poncho][subagent-callback] Recursive callback failed:`, err instanceof Error ? err.message : err)
|
|
8842
9158
|
);
|
|
8843
9159
|
}
|
|
9160
|
+
} else if (freshConv?.runningCallbackSince) {
|
|
9161
|
+
const afterClear = await conversationStore.clearCallbackLock(conversationId);
|
|
9162
|
+
if (afterClear?.pendingSubagentResults?.length) {
|
|
9163
|
+
if (isServerless) {
|
|
9164
|
+
selfFetchWithRetry(`/api/internal/conversations/${encodeURIComponent(conversationId)}/subagent-callback`).catch(
|
|
9165
|
+
(err) => console.error(`[poncho][subagent-callback] Post-clear callback self-fetch failed:`, err instanceof Error ? err.message : err)
|
|
9166
|
+
);
|
|
9167
|
+
} else {
|
|
9168
|
+
processSubagentCallback(conversationId, true).catch(
|
|
9169
|
+
(err) => console.error(`[poncho][subagent-callback] Post-clear callback failed:`, err instanceof Error ? err.message : err)
|
|
9170
|
+
);
|
|
9171
|
+
}
|
|
9172
|
+
}
|
|
8844
9173
|
}
|
|
8845
9174
|
}
|
|
8846
9175
|
};
|
|
@@ -9007,14 +9336,6 @@ ${resultBody}`,
|
|
|
9007
9336
|
if (active && active.abortController === abortController) {
|
|
9008
9337
|
active.runId = event.runId;
|
|
9009
9338
|
}
|
|
9010
|
-
if (typeof event.contextWindow === "number" && event.contextWindow > 0) {
|
|
9011
|
-
runContextWindow = event.contextWindow;
|
|
9012
|
-
}
|
|
9013
|
-
}
|
|
9014
|
-
if (event.type === "model:response") {
|
|
9015
|
-
if (typeof event.usage?.input === "number") {
|
|
9016
|
-
runContextTokens = event.usage.input;
|
|
9017
|
-
}
|
|
9018
9339
|
}
|
|
9019
9340
|
if (event.type === "model:chunk") {
|
|
9020
9341
|
if (currentTools.length > 0) {
|
|
@@ -9079,8 +9400,12 @@ ${resultBody}`,
|
|
|
9079
9400
|
}
|
|
9080
9401
|
checkpointedRun = true;
|
|
9081
9402
|
}
|
|
9082
|
-
if (event.type === "run:completed"
|
|
9083
|
-
assistantResponse
|
|
9403
|
+
if (event.type === "run:completed") {
|
|
9404
|
+
if (assistantResponse.length === 0 && event.result.response) {
|
|
9405
|
+
assistantResponse = event.result.response;
|
|
9406
|
+
}
|
|
9407
|
+
runContextTokens = event.result.contextTokens ?? runContextTokens;
|
|
9408
|
+
runContextWindow = event.result.contextWindow ?? runContextWindow;
|
|
9084
9409
|
}
|
|
9085
9410
|
if (event.type === "run:error") {
|
|
9086
9411
|
assistantResponse = assistantResponse || `[Error: ${event.error.message}]`;
|
|
@@ -9163,6 +9488,13 @@ ${resultBody}`,
|
|
|
9163
9488
|
runConversations.delete(latestRunId);
|
|
9164
9489
|
}
|
|
9165
9490
|
console.log("[resume-run] complete for", conversationId);
|
|
9491
|
+
const hadDeferred = pendingCallbackNeeded.delete(conversationId);
|
|
9492
|
+
const postConv = await conversationStore.get(conversationId);
|
|
9493
|
+
if (hadDeferred || postConv?.pendingSubagentResults?.length) {
|
|
9494
|
+
processSubagentCallback(conversationId, true).catch(
|
|
9495
|
+
(err) => console.error(`[poncho][subagent-callback] Post-resume callback failed:`, err instanceof Error ? err.message : err)
|
|
9496
|
+
);
|
|
9497
|
+
}
|
|
9166
9498
|
};
|
|
9167
9499
|
const messagingRoutes = /* @__PURE__ */ new Map();
|
|
9168
9500
|
const messagingRouteRegistrar = (method, path, routeHandler) => {
|
|
@@ -9293,14 +9625,6 @@ ${resultBody}`,
|
|
|
9293
9625
|
latestRunId = event.runId;
|
|
9294
9626
|
runOwners.set(event.runId, "local-owner");
|
|
9295
9627
|
runConversations.set(event.runId, conversationId);
|
|
9296
|
-
if (typeof event.contextWindow === "number" && event.contextWindow > 0) {
|
|
9297
|
-
runContextWindow = event.contextWindow;
|
|
9298
|
-
}
|
|
9299
|
-
}
|
|
9300
|
-
if (event.type === "model:response") {
|
|
9301
|
-
if (typeof event.usage?.input === "number") {
|
|
9302
|
-
runContextTokens = event.usage.input;
|
|
9303
|
-
}
|
|
9304
9628
|
}
|
|
9305
9629
|
if (event.type === "model:chunk") {
|
|
9306
9630
|
if (currentTools.length > 0) {
|
|
@@ -9399,6 +9723,8 @@ ${resultBody}`,
|
|
|
9399
9723
|
}
|
|
9400
9724
|
runSteps = event.result.steps;
|
|
9401
9725
|
if (typeof event.result.maxSteps === "number") runMaxSteps = event.result.maxSteps;
|
|
9726
|
+
runContextTokens = event.result.contextTokens ?? runContextTokens;
|
|
9727
|
+
runContextWindow = event.result.contextWindow ?? runContextWindow;
|
|
9402
9728
|
}
|
|
9403
9729
|
if (event.type === "run:error") {
|
|
9404
9730
|
assistantResponse = assistantResponse || `[Error: ${event.error.message}]`;
|
|
@@ -9464,34 +9790,89 @@ ${resultBody}`,
|
|
|
9464
9790
|
}
|
|
9465
9791
|
}
|
|
9466
9792
|
const isServerless = !!waitUntilHook;
|
|
9467
|
-
const
|
|
9793
|
+
const configuredInternalSecret = process.env.PONCHO_INTERNAL_SECRET?.trim();
|
|
9794
|
+
const vercelDeploymentSecret = process.env.VERCEL_DEPLOYMENT_ID?.trim();
|
|
9795
|
+
const fallbackInternalSecret = globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`;
|
|
9796
|
+
const internalSecret = configuredInternalSecret || vercelDeploymentSecret || fallbackInternalSecret;
|
|
9797
|
+
const isUsingEphemeralInternalSecret = !configuredInternalSecret && !vercelDeploymentSecret;
|
|
9468
9798
|
let selfBaseUrl = process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : null;
|
|
9799
|
+
if (!selfBaseUrl && process.env.VERCEL_PROJECT_PRODUCTION_URL) {
|
|
9800
|
+
selfBaseUrl = `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`;
|
|
9801
|
+
}
|
|
9802
|
+
if (!selfBaseUrl && process.env.PONCHO_SELF_BASE_URL) {
|
|
9803
|
+
selfBaseUrl = process.env.PONCHO_SELF_BASE_URL.replace(/\/+$/, "");
|
|
9804
|
+
}
|
|
9805
|
+
if (isServerless && isUsingEphemeralInternalSecret) {
|
|
9806
|
+
console.warn(
|
|
9807
|
+
"[poncho][serverless] No stable internal secret found. Set PONCHO_INTERNAL_SECRET to avoid intermittent internal callback failures across instances."
|
|
9808
|
+
);
|
|
9809
|
+
}
|
|
9810
|
+
if (isServerless && !selfBaseUrl) {
|
|
9811
|
+
console.warn(
|
|
9812
|
+
"[poncho][serverless] No self base URL available. Set PONCHO_SELF_BASE_URL if internal background callbacks fail."
|
|
9813
|
+
);
|
|
9814
|
+
}
|
|
9815
|
+
const stateProvider = stateConfig?.provider ?? "local";
|
|
9816
|
+
if (isServerless && (stateProvider === "local" || stateProvider === "memory")) {
|
|
9817
|
+
console.warn(
|
|
9818
|
+
`[poncho][serverless] state.provider="${stateProvider}" may lose cross-invocation state. Prefer "upstash", "redis", or "dynamodb" for subagents/reliability.`
|
|
9819
|
+
);
|
|
9820
|
+
}
|
|
9469
9821
|
const doWaitUntil = (promise) => {
|
|
9470
9822
|
if (waitUntilHook) waitUntilHook(promise);
|
|
9471
9823
|
};
|
|
9472
|
-
const selfFetch = (path, body) => {
|
|
9473
|
-
if (!selfBaseUrl) return Promise.resolve();
|
|
9474
|
-
return fetch(`${selfBaseUrl}${path}`, {
|
|
9475
|
-
method: "POST",
|
|
9476
|
-
headers: {
|
|
9477
|
-
"Content-Type": "application/json",
|
|
9478
|
-
"x-poncho-internal": internalSecret
|
|
9479
|
-
},
|
|
9480
|
-
body: body ? JSON.stringify(body) : void 0
|
|
9481
|
-
}).catch((err) => {
|
|
9482
|
-
console.error(`[poncho][self-fetch] Failed ${path}:`, err instanceof Error ? err.message : err);
|
|
9483
|
-
});
|
|
9484
|
-
};
|
|
9485
9824
|
const selfFetchWithRetry = async (path, body, retries = 3) => {
|
|
9825
|
+
if (!selfBaseUrl) {
|
|
9826
|
+
console.error(`[poncho][self-fetch] Missing self base URL for ${path}`);
|
|
9827
|
+
return;
|
|
9828
|
+
}
|
|
9829
|
+
let lastError;
|
|
9486
9830
|
for (let attempt = 0; attempt < retries; attempt++) {
|
|
9487
9831
|
try {
|
|
9488
|
-
const result = await
|
|
9489
|
-
|
|
9832
|
+
const result = await fetch(`${selfBaseUrl}${path}`, {
|
|
9833
|
+
method: "POST",
|
|
9834
|
+
headers: {
|
|
9835
|
+
"Content-Type": "application/json",
|
|
9836
|
+
"x-poncho-internal": internalSecret
|
|
9837
|
+
},
|
|
9838
|
+
body: body ? JSON.stringify(body) : void 0
|
|
9839
|
+
});
|
|
9840
|
+
if (result.ok) {
|
|
9841
|
+
return result;
|
|
9842
|
+
}
|
|
9843
|
+
const responseText = await result.text().catch(() => "");
|
|
9844
|
+
lastError = new Error(
|
|
9845
|
+
`HTTP ${result.status}${responseText ? `: ${responseText.slice(0, 200)}` : ""}`
|
|
9846
|
+
);
|
|
9490
9847
|
} catch (err) {
|
|
9491
|
-
|
|
9492
|
-
|
|
9848
|
+
lastError = err;
|
|
9849
|
+
}
|
|
9850
|
+
if (attempt === retries - 1) {
|
|
9851
|
+
break;
|
|
9493
9852
|
}
|
|
9853
|
+
await new Promise((resolveSleep) => setTimeout(resolveSleep, 1e3 * (attempt + 1)));
|
|
9494
9854
|
}
|
|
9855
|
+
if (lastError) {
|
|
9856
|
+
console.error(
|
|
9857
|
+
`[poncho][self-fetch] Failed ${path} after ${retries} attempt(s):`,
|
|
9858
|
+
lastError instanceof Error ? lastError.message : String(lastError)
|
|
9859
|
+
);
|
|
9860
|
+
if (lastError instanceof Error && (lastError.message.includes("HTTP 403") || lastError.message.includes("HTTP 401"))) {
|
|
9861
|
+
console.error(
|
|
9862
|
+
"[poncho][self-fetch] Internal auth failed. Ensure all serverless instances share PONCHO_INTERNAL_SECRET."
|
|
9863
|
+
);
|
|
9864
|
+
}
|
|
9865
|
+
} else {
|
|
9866
|
+
console.error(`[poncho][self-fetch] Failed ${path} after ${retries} attempt(s).`);
|
|
9867
|
+
}
|
|
9868
|
+
};
|
|
9869
|
+
const getInternalRequestHeader = (headers) => {
|
|
9870
|
+
const value = headers["x-poncho-internal"];
|
|
9871
|
+
return Array.isArray(value) ? value[0] : value;
|
|
9872
|
+
};
|
|
9873
|
+
const isValidInternalRequest = (headers) => {
|
|
9874
|
+
const headerValue = getInternalRequestHeader(headers);
|
|
9875
|
+
return typeof headerValue === "string" && headerValue === internalSecret;
|
|
9495
9876
|
};
|
|
9496
9877
|
const messagingAdapters = /* @__PURE__ */ new Map();
|
|
9497
9878
|
const messagingBridges = [];
|
|
@@ -9808,7 +10189,7 @@ ${resultBody}`,
|
|
|
9808
10189
|
}
|
|
9809
10190
|
}
|
|
9810
10191
|
if (pathname?.startsWith("/api/internal/") && request.method === "POST") {
|
|
9811
|
-
if (request.headers
|
|
10192
|
+
if (!isValidInternalRequest(request.headers)) {
|
|
9812
10193
|
writeJson(response, 403, { code: "FORBIDDEN", message: "Internal endpoint" });
|
|
9813
10194
|
return;
|
|
9814
10195
|
}
|
|
@@ -10187,18 +10568,29 @@ data: ${JSON.stringify(frame)}
|
|
|
10187
10568
|
});
|
|
10188
10569
|
return;
|
|
10189
10570
|
}
|
|
10571
|
+
let batchDecisions = approvalDecisionTracker.get(conversationId);
|
|
10572
|
+
if (!batchDecisions) {
|
|
10573
|
+
batchDecisions = /* @__PURE__ */ new Map();
|
|
10574
|
+
approvalDecisionTracker.set(conversationId, batchDecisions);
|
|
10575
|
+
}
|
|
10576
|
+
batchDecisions.set(approvalId, approved);
|
|
10190
10577
|
foundApproval.decision = approved ? "approved" : "denied";
|
|
10191
10578
|
broadcastEvent(
|
|
10192
10579
|
conversationId,
|
|
10193
10580
|
approved ? { type: "tool:approval:granted", approvalId } : { type: "tool:approval:denied", approvalId }
|
|
10194
10581
|
);
|
|
10195
10582
|
const allApprovals = foundConversation.pendingApprovals ?? [];
|
|
10196
|
-
const allDecided = allApprovals.length > 0 && allApprovals.every((a) => a.
|
|
10583
|
+
const allDecided = allApprovals.length > 0 && allApprovals.every((a) => batchDecisions.has(a.approvalId));
|
|
10197
10584
|
if (!allDecided) {
|
|
10198
10585
|
await conversationStore.update(foundConversation);
|
|
10199
10586
|
writeJson(response, 200, { ok: true, approvalId, approved, batchComplete: false });
|
|
10200
10587
|
return;
|
|
10201
10588
|
}
|
|
10589
|
+
for (const a of allApprovals) {
|
|
10590
|
+
const d = batchDecisions.get(a.approvalId);
|
|
10591
|
+
if (d != null) a.decision = d ? "approved" : "denied";
|
|
10592
|
+
}
|
|
10593
|
+
approvalDecisionTracker.delete(conversationId);
|
|
10202
10594
|
foundConversation.pendingApprovals = [];
|
|
10203
10595
|
foundConversation.runStatus = "running";
|
|
10204
10596
|
await conversationStore.update(foundConversation);
|
|
@@ -10433,14 +10825,18 @@ data: ${JSON.stringify(frame)}
|
|
|
10433
10825
|
}
|
|
10434
10826
|
}
|
|
10435
10827
|
}
|
|
10828
|
+
const hasPendingCallbackResults = Array.isArray(conversation.pendingSubagentResults) && conversation.pendingSubagentResults.length > 0;
|
|
10829
|
+
const needsContinuation = !hasActiveRun && Array.isArray(conversation._continuationMessages) && conversation._continuationMessages.length > 0;
|
|
10436
10830
|
writeJson(response, 200, {
|
|
10437
10831
|
conversation: {
|
|
10438
10832
|
...conversation,
|
|
10439
|
-
pendingApprovals: storedPending
|
|
10833
|
+
pendingApprovals: storedPending,
|
|
10834
|
+
_continuationMessages: void 0
|
|
10440
10835
|
},
|
|
10441
10836
|
subagentPendingApprovals: subagentPending,
|
|
10442
|
-
hasActiveRun,
|
|
10443
|
-
hasRunningSubagents
|
|
10837
|
+
hasActiveRun: hasActiveRun || hasPendingCallbackResults,
|
|
10838
|
+
hasRunningSubagents,
|
|
10839
|
+
needsContinuation
|
|
10444
10840
|
});
|
|
10445
10841
|
return;
|
|
10446
10842
|
}
|
|
@@ -10820,14 +11216,6 @@ data: ${JSON.stringify(frame)}
|
|
|
10820
11216
|
if (active && active.abortController === abortController) {
|
|
10821
11217
|
active.runId = event.runId;
|
|
10822
11218
|
}
|
|
10823
|
-
if (typeof event.contextWindow === "number" && event.contextWindow > 0) {
|
|
10824
|
-
runContextWindow = event.contextWindow;
|
|
10825
|
-
}
|
|
10826
|
-
}
|
|
10827
|
-
if (event.type === "model:response") {
|
|
10828
|
-
if (typeof event.usage?.input === "number") {
|
|
10829
|
-
runContextTokens = event.usage.input;
|
|
10830
|
-
}
|
|
10831
11219
|
}
|
|
10832
11220
|
if (event.type === "run:cancelled") {
|
|
10833
11221
|
runCancelled = true;
|
|
@@ -10920,6 +11308,8 @@ data: ${JSON.stringify(frame)}
|
|
|
10920
11308
|
if (assistantResponse.length === 0 && event.result.response) {
|
|
10921
11309
|
assistantResponse = event.result.response;
|
|
10922
11310
|
}
|
|
11311
|
+
runContextTokens = event.result.contextTokens ?? runContextTokens;
|
|
11312
|
+
runContextWindow = event.result.contextWindow ?? runContextWindow;
|
|
10923
11313
|
if (event.result.continuation && event.result.continuationMessages) {
|
|
10924
11314
|
runContinuationMessages = event.result.continuationMessages;
|
|
10925
11315
|
conversation._continuationMessages = runContinuationMessages;
|
|
@@ -11059,9 +11449,10 @@ data: ${JSON.stringify(frame)}
|
|
|
11059
11449
|
response.end();
|
|
11060
11450
|
} catch {
|
|
11061
11451
|
}
|
|
11452
|
+
const hadDeferred = pendingCallbackNeeded.delete(conversationId);
|
|
11062
11453
|
const freshConv = await conversationStore.get(conversationId);
|
|
11063
|
-
if (freshConv?.pendingSubagentResults?.length) {
|
|
11064
|
-
processSubagentCallback(conversationId).catch(
|
|
11454
|
+
if (hadDeferred || freshConv?.pendingSubagentResults?.length) {
|
|
11455
|
+
processSubagentCallback(conversationId, true).catch(
|
|
11065
11456
|
(err) => console.error(`[poncho][subagent-callback] Post-run callback failed:`, err instanceof Error ? err.message : err)
|
|
11066
11457
|
);
|
|
11067
11458
|
}
|
|
@@ -11209,125 +11600,157 @@ ${cronJob.task}`;
|
|
|
11209
11600
|
`[cron] ${jobName} ${timestamp}`
|
|
11210
11601
|
);
|
|
11211
11602
|
}
|
|
11212
|
-
const
|
|
11213
|
-
|
|
11214
|
-
|
|
11215
|
-
|
|
11216
|
-
|
|
11217
|
-
|
|
11218
|
-
|
|
11219
|
-
|
|
11220
|
-
|
|
11221
|
-
|
|
11222
|
-
|
|
11223
|
-
|
|
11224
|
-
|
|
11225
|
-
|
|
11226
|
-
|
|
11227
|
-
|
|
11228
|
-
|
|
11229
|
-
|
|
11230
|
-
|
|
11231
|
-
|
|
11232
|
-
|
|
11233
|
-
|
|
11234
|
-
|
|
11235
|
-
|
|
11236
|
-
|
|
11237
|
-
|
|
11238
|
-
|
|
11239
|
-
|
|
11240
|
-
|
|
11603
|
+
const convId = conversation.conversationId;
|
|
11604
|
+
activeConversationRuns.set(convId, {
|
|
11605
|
+
ownerId: conversation.ownerId,
|
|
11606
|
+
abortController: new AbortController(),
|
|
11607
|
+
runId: null
|
|
11608
|
+
});
|
|
11609
|
+
try {
|
|
11610
|
+
const abortController = new AbortController();
|
|
11611
|
+
let assistantResponse = "";
|
|
11612
|
+
let latestRunId = "";
|
|
11613
|
+
const toolTimeline = [];
|
|
11614
|
+
const sections = [];
|
|
11615
|
+
let currentTools = [];
|
|
11616
|
+
let currentText = "";
|
|
11617
|
+
let runResult = {
|
|
11618
|
+
status: "completed",
|
|
11619
|
+
steps: 0
|
|
11620
|
+
};
|
|
11621
|
+
const platformMaxDurationSec = Number(process.env.PONCHO_MAX_DURATION) || 0;
|
|
11622
|
+
const softDeadlineMs = platformMaxDurationSec > 0 ? platformMaxDurationSec * 800 : 0;
|
|
11623
|
+
for await (const event of harness.runWithTelemetry({
|
|
11624
|
+
task: cronJob.task,
|
|
11625
|
+
conversationId: convId,
|
|
11626
|
+
parameters: { __activeConversationId: convId },
|
|
11627
|
+
messages: historyMessages,
|
|
11628
|
+
abortSignal: abortController.signal
|
|
11629
|
+
})) {
|
|
11630
|
+
if (event.type === "run:started") {
|
|
11631
|
+
latestRunId = event.runId;
|
|
11632
|
+
}
|
|
11633
|
+
if (event.type === "model:chunk") {
|
|
11634
|
+
if (currentTools.length > 0) {
|
|
11635
|
+
sections.push({ type: "tools", content: currentTools });
|
|
11636
|
+
currentTools = [];
|
|
11637
|
+
if (assistantResponse.length > 0 && !/\s$/.test(assistantResponse)) {
|
|
11638
|
+
assistantResponse += " ";
|
|
11639
|
+
}
|
|
11241
11640
|
}
|
|
11641
|
+
assistantResponse += event.content;
|
|
11642
|
+
currentText += event.content;
|
|
11242
11643
|
}
|
|
11243
|
-
|
|
11244
|
-
|
|
11245
|
-
|
|
11246
|
-
|
|
11247
|
-
|
|
11248
|
-
|
|
11249
|
-
|
|
11644
|
+
if (event.type === "tool:started") {
|
|
11645
|
+
if (currentText.length > 0) {
|
|
11646
|
+
sections.push({ type: "text", content: currentText });
|
|
11647
|
+
currentText = "";
|
|
11648
|
+
}
|
|
11649
|
+
const toolText = `- start \`${event.tool}\``;
|
|
11650
|
+
toolTimeline.push(toolText);
|
|
11651
|
+
currentTools.push(toolText);
|
|
11250
11652
|
}
|
|
11251
|
-
|
|
11252
|
-
|
|
11253
|
-
|
|
11653
|
+
if (event.type === "tool:completed") {
|
|
11654
|
+
const toolText = `- done \`${event.tool}\` (${event.duration}ms)`;
|
|
11655
|
+
toolTimeline.push(toolText);
|
|
11656
|
+
currentTools.push(toolText);
|
|
11657
|
+
}
|
|
11658
|
+
if (event.type === "tool:error") {
|
|
11659
|
+
const toolText = `- error \`${event.tool}\`: ${event.error}`;
|
|
11660
|
+
toolTimeline.push(toolText);
|
|
11661
|
+
currentTools.push(toolText);
|
|
11662
|
+
}
|
|
11663
|
+
if (event.type === "run:completed") {
|
|
11664
|
+
runResult = {
|
|
11665
|
+
status: event.result.status,
|
|
11666
|
+
steps: event.result.steps,
|
|
11667
|
+
continuation: event.result.continuation,
|
|
11668
|
+
contextTokens: event.result.contextTokens,
|
|
11669
|
+
contextWindow: event.result.contextWindow
|
|
11670
|
+
};
|
|
11671
|
+
if (!assistantResponse && event.result.response) {
|
|
11672
|
+
assistantResponse = event.result.response;
|
|
11673
|
+
}
|
|
11674
|
+
}
|
|
11675
|
+
broadcastEvent(convId, event);
|
|
11676
|
+
await telemetry.emit(event);
|
|
11254
11677
|
}
|
|
11255
|
-
|
|
11256
|
-
|
|
11257
|
-
|
|
11258
|
-
currentTools.push(toolText);
|
|
11678
|
+
finishConversationStream(convId);
|
|
11679
|
+
if (currentTools.length > 0) {
|
|
11680
|
+
sections.push({ type: "tools", content: currentTools });
|
|
11259
11681
|
}
|
|
11260
|
-
if (
|
|
11261
|
-
|
|
11262
|
-
|
|
11263
|
-
currentTools.push(toolText);
|
|
11682
|
+
if (currentText.length > 0) {
|
|
11683
|
+
sections.push({ type: "text", content: currentText });
|
|
11684
|
+
currentText = "";
|
|
11264
11685
|
}
|
|
11265
|
-
|
|
11266
|
-
|
|
11267
|
-
|
|
11268
|
-
|
|
11269
|
-
|
|
11270
|
-
|
|
11271
|
-
|
|
11272
|
-
|
|
11686
|
+
const hasContent = assistantResponse.length > 0 || toolTimeline.length > 0;
|
|
11687
|
+
const assistantMetadata = toolTimeline.length > 0 || sections.length > 0 ? {
|
|
11688
|
+
toolActivity: [...toolTimeline],
|
|
11689
|
+
sections: sections.length > 0 ? sections : void 0
|
|
11690
|
+
} : void 0;
|
|
11691
|
+
const messages = [
|
|
11692
|
+
...historyMessages,
|
|
11693
|
+
...continueConversationId ? [] : [{ role: "user", content: cronJob.task }],
|
|
11694
|
+
...hasContent ? [{ role: "assistant", content: assistantResponse, metadata: assistantMetadata }] : []
|
|
11695
|
+
];
|
|
11696
|
+
const freshConv = await conversationStore.get(convId);
|
|
11697
|
+
if (freshConv) {
|
|
11698
|
+
freshConv.messages = messages;
|
|
11699
|
+
freshConv.runtimeRunId = latestRunId || freshConv.runtimeRunId;
|
|
11700
|
+
if (runResult.contextTokens) freshConv.contextTokens = runResult.contextTokens;
|
|
11701
|
+
if (runResult.contextWindow) freshConv.contextWindow = runResult.contextWindow;
|
|
11702
|
+
freshConv.updatedAt = Date.now();
|
|
11703
|
+
await conversationStore.update(freshConv);
|
|
11704
|
+
}
|
|
11705
|
+
if (runResult.continuation && softDeadlineMs > 0) {
|
|
11706
|
+
const selfUrl = `http://${request.headers.host ?? "localhost"}${pathname}?continue=${encodeURIComponent(convId)}&continuation=${continuationCount + 1}`;
|
|
11707
|
+
try {
|
|
11708
|
+
const selfRes = await fetch(selfUrl, {
|
|
11709
|
+
method: "GET",
|
|
11710
|
+
headers: request.headers.authorization ? { authorization: request.headers.authorization } : {}
|
|
11711
|
+
});
|
|
11712
|
+
const selfBody = await selfRes.json();
|
|
11713
|
+
writeJson(response, 200, {
|
|
11714
|
+
conversationId: convId,
|
|
11715
|
+
status: "continued",
|
|
11716
|
+
continuations: continuationCount + 1,
|
|
11717
|
+
finalResult: selfBody,
|
|
11718
|
+
duration: Date.now() - start
|
|
11719
|
+
});
|
|
11720
|
+
} catch (continueError) {
|
|
11721
|
+
writeJson(response, 200, {
|
|
11722
|
+
conversationId: convId,
|
|
11723
|
+
status: "continuation_failed",
|
|
11724
|
+
error: continueError instanceof Error ? continueError.message : "Unknown error",
|
|
11725
|
+
duration: Date.now() - start,
|
|
11726
|
+
steps: runResult.steps
|
|
11727
|
+
});
|
|
11273
11728
|
}
|
|
11729
|
+
return;
|
|
11274
11730
|
}
|
|
11275
|
-
|
|
11276
|
-
|
|
11277
|
-
|
|
11278
|
-
|
|
11279
|
-
|
|
11280
|
-
|
|
11281
|
-
|
|
11282
|
-
|
|
11283
|
-
|
|
11284
|
-
|
|
11285
|
-
|
|
11286
|
-
|
|
11287
|
-
|
|
11288
|
-
|
|
11289
|
-
|
|
11290
|
-
|
|
11291
|
-
|
|
11292
|
-
|
|
11293
|
-
|
|
11294
|
-
|
|
11295
|
-
|
|
11296
|
-
conversation.updatedAt = Date.now();
|
|
11297
|
-
await conversationStore.update(conversation);
|
|
11298
|
-
if (runResult.continuation && softDeadlineMs > 0) {
|
|
11299
|
-
const selfUrl = `http://${request.headers.host ?? "localhost"}${pathname}?continue=${encodeURIComponent(conversation.conversationId)}&continuation=${continuationCount + 1}`;
|
|
11300
|
-
try {
|
|
11301
|
-
const selfRes = await fetch(selfUrl, {
|
|
11302
|
-
method: "GET",
|
|
11303
|
-
headers: request.headers.authorization ? { authorization: request.headers.authorization } : {}
|
|
11304
|
-
});
|
|
11305
|
-
const selfBody = await selfRes.json();
|
|
11306
|
-
writeJson(response, 200, {
|
|
11307
|
-
conversationId: conversation.conversationId,
|
|
11308
|
-
status: "continued",
|
|
11309
|
-
continuations: continuationCount + 1,
|
|
11310
|
-
finalResult: selfBody,
|
|
11311
|
-
duration: Date.now() - start
|
|
11312
|
-
});
|
|
11313
|
-
} catch (continueError) {
|
|
11314
|
-
writeJson(response, 200, {
|
|
11315
|
-
conversationId: conversation.conversationId,
|
|
11316
|
-
status: "continuation_failed",
|
|
11317
|
-
error: continueError instanceof Error ? continueError.message : "Unknown error",
|
|
11318
|
-
duration: Date.now() - start,
|
|
11319
|
-
steps: runResult.steps
|
|
11320
|
-
});
|
|
11731
|
+
writeJson(response, 200, {
|
|
11732
|
+
conversationId: convId,
|
|
11733
|
+
status: runResult.status,
|
|
11734
|
+
response: assistantResponse.slice(0, 500),
|
|
11735
|
+
duration: Date.now() - start,
|
|
11736
|
+
steps: runResult.steps
|
|
11737
|
+
});
|
|
11738
|
+
} finally {
|
|
11739
|
+
activeConversationRuns.delete(convId);
|
|
11740
|
+
const hadDeferred = pendingCallbackNeeded.delete(convId);
|
|
11741
|
+
const checkConv = await conversationStore.get(convId);
|
|
11742
|
+
if (hadDeferred || checkConv?.pendingSubagentResults?.length) {
|
|
11743
|
+
if (isServerless) {
|
|
11744
|
+
selfFetchWithRetry(`/api/internal/conversations/${encodeURIComponent(convId)}/subagent-callback`).catch(
|
|
11745
|
+
(err) => console.error(`[cron] subagent callback self-fetch failed:`, err instanceof Error ? err.message : err)
|
|
11746
|
+
);
|
|
11747
|
+
} else {
|
|
11748
|
+
processSubagentCallback(convId, true).catch(
|
|
11749
|
+
(err) => console.error(`[cron] subagent callback failed:`, err instanceof Error ? err.message : err)
|
|
11750
|
+
);
|
|
11751
|
+
}
|
|
11321
11752
|
}
|
|
11322
|
-
return;
|
|
11323
11753
|
}
|
|
11324
|
-
writeJson(response, 200, {
|
|
11325
|
-
conversationId: conversation.conversationId,
|
|
11326
|
-
status: runResult.status,
|
|
11327
|
-
response: assistantResponse.slice(0, 500),
|
|
11328
|
-
duration: Date.now() - start,
|
|
11329
|
-
steps: runResult.steps
|
|
11330
|
-
});
|
|
11331
11754
|
} catch (error) {
|
|
11332
11755
|
writeJson(response, 500, {
|
|
11333
11756
|
code: "CRON_RUN_ERROR",
|
|
@@ -11342,6 +11765,11 @@ ${cronJob.task}`;
|
|
|
11342
11765
|
handler._cronJobs = cronJobs;
|
|
11343
11766
|
handler._conversationStore = conversationStore;
|
|
11344
11767
|
handler._messagingAdapters = messagingAdapters;
|
|
11768
|
+
handler._activeConversationRuns = activeConversationRuns;
|
|
11769
|
+
handler._pendingCallbackNeeded = pendingCallbackNeeded;
|
|
11770
|
+
handler._processSubagentCallback = processSubagentCallback;
|
|
11771
|
+
handler._broadcastEvent = broadcastEvent;
|
|
11772
|
+
handler._finishConversationStream = finishConversationStream;
|
|
11345
11773
|
const STALE_SUBAGENT_THRESHOLD_MS = 5 * 60 * 1e3;
|
|
11346
11774
|
try {
|
|
11347
11775
|
const allSummaries = await conversationStore.listSummaries();
|
|
@@ -11392,9 +11820,11 @@ var startDevServer = async (port, options) => {
|
|
|
11392
11820
|
await checkVercelCronDrift(workingDir);
|
|
11393
11821
|
const { Cron } = await import("croner");
|
|
11394
11822
|
let activeJobs = [];
|
|
11395
|
-
const runCronAgent = async (harnessRef, task, conversationId, historyMessages) => {
|
|
11823
|
+
const runCronAgent = async (harnessRef, task, conversationId, historyMessages, onEvent) => {
|
|
11396
11824
|
let assistantResponse = "";
|
|
11397
11825
|
let steps = 0;
|
|
11826
|
+
let contextTokens = 0;
|
|
11827
|
+
let contextWindow = 0;
|
|
11398
11828
|
const toolTimeline = [];
|
|
11399
11829
|
const sections = [];
|
|
11400
11830
|
let currentTools = [];
|
|
@@ -11405,6 +11835,7 @@ var startDevServer = async (port, options) => {
|
|
|
11405
11835
|
parameters: { __activeConversationId: conversationId },
|
|
11406
11836
|
messages: historyMessages
|
|
11407
11837
|
})) {
|
|
11838
|
+
onEvent?.(event);
|
|
11408
11839
|
if (event.type === "model:chunk") {
|
|
11409
11840
|
if (currentTools.length > 0) {
|
|
11410
11841
|
sections.push({ type: "tools", content: currentTools });
|
|
@@ -11437,6 +11868,8 @@ var startDevServer = async (port, options) => {
|
|
|
11437
11868
|
}
|
|
11438
11869
|
if (event.type === "run:completed") {
|
|
11439
11870
|
steps = event.result.steps;
|
|
11871
|
+
contextTokens = event.result.contextTokens ?? 0;
|
|
11872
|
+
contextWindow = event.result.contextWindow ?? 0;
|
|
11440
11873
|
if (!assistantResponse && event.result.response) {
|
|
11441
11874
|
assistantResponse = event.result.response;
|
|
11442
11875
|
}
|
|
@@ -11453,7 +11886,7 @@ var startDevServer = async (port, options) => {
|
|
|
11453
11886
|
toolActivity: [...toolTimeline],
|
|
11454
11887
|
sections: sections.length > 0 ? sections : void 0
|
|
11455
11888
|
} : void 0;
|
|
11456
|
-
return { response: assistantResponse, steps, assistantMetadata, hasContent };
|
|
11889
|
+
return { response: assistantResponse, steps, assistantMetadata, hasContent, contextTokens, contextWindow };
|
|
11457
11890
|
};
|
|
11458
11891
|
const buildCronMessages = (task, historyMessages, result) => [
|
|
11459
11892
|
...historyMessages,
|
|
@@ -11470,6 +11903,9 @@ var startDevServer = async (port, options) => {
|
|
|
11470
11903
|
const harnessRef = handler._harness;
|
|
11471
11904
|
const store = handler._conversationStore;
|
|
11472
11905
|
const adapters = handler._messagingAdapters;
|
|
11906
|
+
const activeRuns = handler._activeConversationRuns;
|
|
11907
|
+
const deferredCallbacks = handler._pendingCallbackNeeded;
|
|
11908
|
+
const runCallback = handler._processSubagentCallback;
|
|
11473
11909
|
if (!harnessRef || !store) return;
|
|
11474
11910
|
for (const [jobName, config] of entries) {
|
|
11475
11911
|
const job = new Cron(
|
|
@@ -11510,24 +11946,43 @@ var startDevServer = async (port, options) => {
|
|
|
11510
11946
|
const task = `[Scheduled: ${jobName}]
|
|
11511
11947
|
${config.task}`;
|
|
11512
11948
|
const historyMessages = [...conversation.messages];
|
|
11949
|
+
const convId = conversation.conversationId;
|
|
11950
|
+
activeRuns?.set(convId, {
|
|
11951
|
+
ownerId: "local-owner",
|
|
11952
|
+
abortController: new AbortController(),
|
|
11953
|
+
runId: null
|
|
11954
|
+
});
|
|
11513
11955
|
try {
|
|
11514
|
-
const
|
|
11515
|
-
|
|
11516
|
-
|
|
11517
|
-
|
|
11518
|
-
|
|
11519
|
-
|
|
11520
|
-
|
|
11521
|
-
|
|
11522
|
-
|
|
11523
|
-
|
|
11524
|
-
|
|
11525
|
-
|
|
11526
|
-
|
|
11527
|
-
|
|
11528
|
-
|
|
11529
|
-
|
|
11956
|
+
const broadcastCh = handler._broadcastEvent;
|
|
11957
|
+
const result = await runCronAgent(
|
|
11958
|
+
harnessRef,
|
|
11959
|
+
task,
|
|
11960
|
+
convId,
|
|
11961
|
+
historyMessages,
|
|
11962
|
+
broadcastCh ? (ev) => broadcastCh(convId, ev) : void 0
|
|
11963
|
+
);
|
|
11964
|
+
handler._finishConversationStream?.(convId);
|
|
11965
|
+
const freshConv = await store.get(convId);
|
|
11966
|
+
if (freshConv) {
|
|
11967
|
+
freshConv.messages = buildCronMessages(task, historyMessages, result);
|
|
11968
|
+
if (result.contextTokens > 0) freshConv.contextTokens = result.contextTokens;
|
|
11969
|
+
if (result.contextWindow > 0) freshConv.contextWindow = result.contextWindow;
|
|
11970
|
+
freshConv.updatedAt = Date.now();
|
|
11971
|
+
await store.update(freshConv);
|
|
11972
|
+
if (result.response) {
|
|
11973
|
+
try {
|
|
11974
|
+
await adapter.sendReply(
|
|
11975
|
+
{
|
|
11976
|
+
channelId: chatId,
|
|
11977
|
+
platformThreadId: freshConv.channelMeta?.platformThreadId ?? chatId
|
|
11978
|
+
},
|
|
11979
|
+
result.response
|
|
11980
|
+
);
|
|
11981
|
+
} catch (sendError) {
|
|
11982
|
+
const sendMsg = sendError instanceof Error ? sendError.message : String(sendError);
|
|
11983
|
+
process.stderr.write(`[cron] ${jobName}: send to ${chatId} failed: ${sendMsg}
|
|
11530
11984
|
`);
|
|
11985
|
+
}
|
|
11531
11986
|
}
|
|
11532
11987
|
}
|
|
11533
11988
|
totalChats++;
|
|
@@ -11535,6 +11990,15 @@ ${config.task}`;
|
|
|
11535
11990
|
const runMsg = runError instanceof Error ? runError.message : String(runError);
|
|
11536
11991
|
process.stderr.write(`[cron] ${jobName}: run for chat ${chatId} failed: ${runMsg}
|
|
11537
11992
|
`);
|
|
11993
|
+
} finally {
|
|
11994
|
+
activeRuns?.delete(convId);
|
|
11995
|
+
const hadDeferred = deferredCallbacks?.delete(convId) ?? false;
|
|
11996
|
+
const checkConv = await store.get(convId);
|
|
11997
|
+
if (hadDeferred || checkConv?.pendingSubagentResults?.length) {
|
|
11998
|
+
runCallback?.(convId, true).catch(
|
|
11999
|
+
(err) => console.error(`[cron] ${jobName}: subagent callback for ${chatId} failed:`, err instanceof Error ? err.message : err)
|
|
12000
|
+
);
|
|
12001
|
+
}
|
|
11538
12002
|
}
|
|
11539
12003
|
}
|
|
11540
12004
|
const elapsed = ((Date.now() - start) / 1e3).toFixed(1);
|
|
@@ -11548,15 +12012,35 @@ ${config.task}`;
|
|
|
11548
12012
|
}
|
|
11549
12013
|
return;
|
|
11550
12014
|
}
|
|
12015
|
+
let cronConvId;
|
|
11551
12016
|
try {
|
|
11552
12017
|
const conversation = await store.create(
|
|
11553
12018
|
"local-owner",
|
|
11554
12019
|
`[cron] ${jobName} ${timestamp}`
|
|
11555
12020
|
);
|
|
11556
|
-
|
|
11557
|
-
|
|
11558
|
-
|
|
11559
|
-
|
|
12021
|
+
cronConvId = conversation.conversationId;
|
|
12022
|
+
activeRuns?.set(cronConvId, {
|
|
12023
|
+
ownerId: "local-owner",
|
|
12024
|
+
abortController: new AbortController(),
|
|
12025
|
+
runId: null
|
|
12026
|
+
});
|
|
12027
|
+
const broadcast = handler._broadcastEvent;
|
|
12028
|
+
const result = await runCronAgent(
|
|
12029
|
+
harnessRef,
|
|
12030
|
+
config.task,
|
|
12031
|
+
cronConvId,
|
|
12032
|
+
[],
|
|
12033
|
+
broadcast ? (ev) => broadcast(cronConvId, ev) : void 0
|
|
12034
|
+
);
|
|
12035
|
+
handler._finishConversationStream?.(cronConvId);
|
|
12036
|
+
const freshConv = await store.get(cronConvId);
|
|
12037
|
+
if (freshConv) {
|
|
12038
|
+
freshConv.messages = buildCronMessages(config.task, [], result);
|
|
12039
|
+
if (result.contextTokens > 0) freshConv.contextTokens = result.contextTokens;
|
|
12040
|
+
if (result.contextWindow > 0) freshConv.contextWindow = result.contextWindow;
|
|
12041
|
+
freshConv.updatedAt = Date.now();
|
|
12042
|
+
await store.update(freshConv);
|
|
12043
|
+
}
|
|
11560
12044
|
const elapsed = ((Date.now() - start) / 1e3).toFixed(1);
|
|
11561
12045
|
process.stdout.write(
|
|
11562
12046
|
`[cron] ${jobName} completed in ${elapsed}s (${result.steps} steps)
|
|
@@ -11569,6 +12053,17 @@ ${config.task}`;
|
|
|
11569
12053
|
`[cron] ${jobName} failed after ${elapsed}s: ${msg}
|
|
11570
12054
|
`
|
|
11571
12055
|
);
|
|
12056
|
+
} finally {
|
|
12057
|
+
if (cronConvId) {
|
|
12058
|
+
activeRuns?.delete(cronConvId);
|
|
12059
|
+
const hadDeferred = deferredCallbacks?.delete(cronConvId) ?? false;
|
|
12060
|
+
const checkConv = await store.get(cronConvId);
|
|
12061
|
+
if (hadDeferred || checkConv?.pendingSubagentResults?.length) {
|
|
12062
|
+
runCallback?.(cronConvId, true).catch(
|
|
12063
|
+
(err) => console.error(`[cron] ${jobName}: subagent callback failed:`, err instanceof Error ? err.message : err)
|
|
12064
|
+
);
|
|
12065
|
+
}
|
|
12066
|
+
}
|
|
11572
12067
|
}
|
|
11573
12068
|
}
|
|
11574
12069
|
);
|
|
@@ -11676,7 +12171,7 @@ var runInteractive = async (workingDir, params) => {
|
|
|
11676
12171
|
await harness.initialize();
|
|
11677
12172
|
const identity = await ensureAgentIdentity2(workingDir);
|
|
11678
12173
|
try {
|
|
11679
|
-
const { runInteractiveInk } = await import("./run-interactive-ink-
|
|
12174
|
+
const { runInteractiveInk } = await import("./run-interactive-ink-FUMHN6DS.js");
|
|
11680
12175
|
await runInteractiveInk({
|
|
11681
12176
|
harness,
|
|
11682
12177
|
params,
|