@poncho-ai/cli 0.30.7 → 0.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +5 -5
- package/CHANGELOG.md +22 -0
- package/dist/{chunk-NPD5GM5C.js → chunk-73C227HM.js} +697 -164
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/dist/{run-interactive-ink-NV6LIQWU.js → run-interactive-ink-OKE5AV3N.js} +1 -1
- package/package.json +4 -4
- package/src/index.ts +645 -96
- package/src/web-ui-client.ts +106 -71
package/src/web-ui-client.ts
CHANGED
|
@@ -1345,10 +1345,11 @@ export const getWebUiClientScript = (markedSource: string): string => `
|
|
|
1345
1345
|
} else if (willStream) {
|
|
1346
1346
|
setStreaming(true);
|
|
1347
1347
|
} else if (payload.needsContinuation && !payload.conversation.parentConversationId) {
|
|
1348
|
-
console.log("[poncho] Detected orphaned continuation for", conversationId, "— auto-resuming");
|
|
1348
|
+
console.log("[poncho] Detected orphaned continuation for", conversationId, "— auto-resuming via /continue");
|
|
1349
1349
|
(async () => {
|
|
1350
1350
|
try {
|
|
1351
1351
|
setStreaming(true);
|
|
1352
|
+
state.activeStreamConversationId = conversationId;
|
|
1352
1353
|
var localMsgs = state.activeMessages || [];
|
|
1353
1354
|
var contAssistant = {
|
|
1354
1355
|
role: "assistant",
|
|
@@ -1365,30 +1366,36 @@ export const getWebUiClientScript = (markedSource: string): string => `
|
|
|
1365
1366
|
state.activeMessages = localMsgs;
|
|
1366
1367
|
state._activeStreamMessages = localMsgs;
|
|
1367
1368
|
renderMessages(localMsgs, true);
|
|
1369
|
+
|
|
1368
1370
|
var contResp = await fetch(
|
|
1369
|
-
"/api/conversations/" + encodeURIComponent(conversationId) + "/
|
|
1371
|
+
"/api/conversations/" + encodeURIComponent(conversationId) + "/continue",
|
|
1370
1372
|
{
|
|
1371
1373
|
method: "POST",
|
|
1372
1374
|
credentials: "include",
|
|
1373
1375
|
headers: { "Content-Type": "application/json", "x-csrf-token": state.csrfToken },
|
|
1374
|
-
body: JSON.stringify({ continuation: true }),
|
|
1375
1376
|
},
|
|
1376
1377
|
);
|
|
1377
1378
|
if (!contResp.ok || !contResp.body) {
|
|
1378
|
-
|
|
1379
|
+
// Server already claimed the continuation (safety net). Poll for completion.
|
|
1380
|
+
await pollUntilRunIdle(conversationId);
|
|
1379
1381
|
setStreaming(false);
|
|
1380
1382
|
renderMessages(localMsgs, false);
|
|
1381
1383
|
return;
|
|
1382
1384
|
}
|
|
1383
|
-
|
|
1385
|
+
|
|
1384
1386
|
var contReader = contResp.body.getReader();
|
|
1385
1387
|
var contDecoder = new TextDecoder();
|
|
1386
1388
|
var contBuffer = "";
|
|
1389
|
+
var gotStreamEnd = false;
|
|
1387
1390
|
while (true) {
|
|
1388
1391
|
var chunk = await contReader.read();
|
|
1389
1392
|
if (chunk.done) break;
|
|
1390
1393
|
contBuffer += contDecoder.decode(chunk.value, { stream: true });
|
|
1391
1394
|
contBuffer = parseSseChunk(contBuffer, function(evtName, evtPayload) {
|
|
1395
|
+
if (evtName === "stream:end") {
|
|
1396
|
+
gotStreamEnd = true;
|
|
1397
|
+
return;
|
|
1398
|
+
}
|
|
1392
1399
|
if (evtName === "model:chunk" && evtPayload.content) {
|
|
1393
1400
|
contAssistant.content = (contAssistant.content || "") + evtPayload.content;
|
|
1394
1401
|
contAssistant._currentText += evtPayload.content;
|
|
@@ -1419,14 +1426,14 @@ export const getWebUiClientScript = (markedSource: string): string => `
|
|
|
1419
1426
|
if (evtName === "run:error") {
|
|
1420
1427
|
contAssistant._error = evtPayload.error?.message || "Something went wrong";
|
|
1421
1428
|
}
|
|
1422
|
-
if (evtName === "run:completed" && evtPayload.result?.continuation === true) {
|
|
1423
|
-
// Another continuation needed — reload to pick it up
|
|
1424
|
-
loadConversation(conversationId).catch(function() {});
|
|
1425
|
-
}
|
|
1426
1429
|
}
|
|
1427
1430
|
renderMessages(localMsgs, true);
|
|
1428
1431
|
});
|
|
1429
1432
|
}
|
|
1433
|
+
if (gotStreamEnd) {
|
|
1434
|
+
// Safety net already claimed it. Poll for completion.
|
|
1435
|
+
await pollUntilRunIdle(conversationId);
|
|
1436
|
+
}
|
|
1430
1437
|
setStreaming(false);
|
|
1431
1438
|
renderMessages(localMsgs, false);
|
|
1432
1439
|
await loadConversations();
|
|
@@ -2521,59 +2528,30 @@ export const getWebUiClientScript = (markedSource: string): string => `
|
|
|
2521
2528
|
};
|
|
2522
2529
|
let _totalSteps = 0;
|
|
2523
2530
|
let _maxSteps = 0;
|
|
2524
|
-
let _isContinuation = false;
|
|
2525
2531
|
let _receivedTerminalEvent = false;
|
|
2526
|
-
while (true) {
|
|
2527
2532
|
let _shouldContinue = false;
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
headers: { "x-csrf-token": state.csrfToken },
|
|
2547
|
-
body: formData,
|
|
2548
|
-
signal: streamAbortController.signal,
|
|
2549
|
-
};
|
|
2550
|
-
} else {
|
|
2551
|
-
fetchOpts = {
|
|
2552
|
-
method: "POST",
|
|
2553
|
-
credentials: "include",
|
|
2554
|
-
headers: { "Content-Type": "application/json", "x-csrf-token": state.csrfToken },
|
|
2555
|
-
body: JSON.stringify({ message: messageText }),
|
|
2556
|
-
signal: streamAbortController.signal,
|
|
2557
|
-
};
|
|
2558
|
-
}
|
|
2559
|
-
const response = await fetch(
|
|
2560
|
-
"/api/conversations/" + encodeURIComponent(conversationId) + "/messages",
|
|
2561
|
-
fetchOpts,
|
|
2562
|
-
);
|
|
2563
|
-
if (!response.ok || !response.body) {
|
|
2564
|
-
throw new Error("Failed to stream response");
|
|
2565
|
-
}
|
|
2566
|
-
const reader = response.body.getReader();
|
|
2567
|
-
const decoder = new TextDecoder();
|
|
2568
|
-
let buffer = "";
|
|
2569
|
-
while (true) {
|
|
2570
|
-
const { value, done } = await reader.read();
|
|
2571
|
-
if (done) {
|
|
2572
|
-
break;
|
|
2533
|
+
|
|
2534
|
+
// Helper to read an SSE stream from a fetch response
|
|
2535
|
+
const readSseStream = async (response) => {
|
|
2536
|
+
_shouldContinue = false;
|
|
2537
|
+
const reader = response.body.getReader();
|
|
2538
|
+
const decoder = new TextDecoder();
|
|
2539
|
+
let buffer = "";
|
|
2540
|
+
while (true) {
|
|
2541
|
+
const { value, done } = await reader.read();
|
|
2542
|
+
if (done) break;
|
|
2543
|
+
buffer += decoder.decode(value, { stream: true });
|
|
2544
|
+
buffer = parseSseChunk(buffer, (eventName, payload) => {
|
|
2545
|
+
try {
|
|
2546
|
+
handleSseEvent(eventName, payload);
|
|
2547
|
+
} catch (error) {
|
|
2548
|
+
console.error("SSE event handling error:", eventName, error);
|
|
2549
|
+
}
|
|
2550
|
+
});
|
|
2573
2551
|
}
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2552
|
+
};
|
|
2553
|
+
|
|
2554
|
+
const handleSseEvent = (eventName, payload) => {
|
|
2577
2555
|
if (eventName === "model:chunk") {
|
|
2578
2556
|
const chunk = String(payload.content || "");
|
|
2579
2557
|
if (chunk.length > 0) clearResolvedApprovals(assistantMessage);
|
|
@@ -2857,6 +2835,12 @@ export const getWebUiClientScript = (markedSource: string): string => `
|
|
|
2857
2835
|
if (typeof payload.result?.maxSteps === "number") _maxSteps = payload.result.maxSteps;
|
|
2858
2836
|
if (payload.result?.continuation === true && (_maxSteps <= 0 || _totalSteps < _maxSteps)) {
|
|
2859
2837
|
_shouldContinue = true;
|
|
2838
|
+
if (assistantMessage._currentTools.length > 0) {
|
|
2839
|
+
assistantMessage._sections.push({ type: "tools", content: assistantMessage._currentTools });
|
|
2840
|
+
assistantMessage._currentTools = [];
|
|
2841
|
+
}
|
|
2842
|
+
assistantMessage._activeActivities = [];
|
|
2843
|
+
renderIfActiveConversation(true);
|
|
2860
2844
|
} else {
|
|
2861
2845
|
finalizeAssistantMessage();
|
|
2862
2846
|
if (!assistantMessage.content || assistantMessage.content.length === 0) {
|
|
@@ -2880,26 +2864,77 @@ export const getWebUiClientScript = (markedSource: string): string => `
|
|
|
2880
2864
|
assistantMessage._error = errMsg;
|
|
2881
2865
|
renderIfActiveConversation(false);
|
|
2882
2866
|
}
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2867
|
+
if (eventName === "stream:end") {
|
|
2868
|
+
// no-op: server signals empty continuation
|
|
2869
|
+
}
|
|
2870
|
+
};
|
|
2871
|
+
|
|
2872
|
+
// Initial message POST
|
|
2873
|
+
let fetchOpts;
|
|
2874
|
+
if (filesToSend.length > 0) {
|
|
2875
|
+
const formData = new FormData();
|
|
2876
|
+
formData.append("message", messageText);
|
|
2877
|
+
for (const f of filesToSend) {
|
|
2878
|
+
formData.append("files", f, f.name);
|
|
2879
|
+
}
|
|
2880
|
+
fetchOpts = {
|
|
2881
|
+
method: "POST",
|
|
2882
|
+
credentials: "include",
|
|
2883
|
+
headers: { "x-csrf-token": state.csrfToken },
|
|
2884
|
+
body: formData,
|
|
2885
|
+
signal: streamAbortController.signal,
|
|
2886
|
+
};
|
|
2887
|
+
} else {
|
|
2888
|
+
fetchOpts = {
|
|
2889
|
+
method: "POST",
|
|
2890
|
+
credentials: "include",
|
|
2891
|
+
headers: { "Content-Type": "application/json", "x-csrf-token": state.csrfToken },
|
|
2892
|
+
body: JSON.stringify({ message: messageText }),
|
|
2893
|
+
signal: streamAbortController.signal,
|
|
2894
|
+
};
|
|
2895
|
+
}
|
|
2896
|
+
const response = await fetch(
|
|
2897
|
+
"/api/conversations/" + encodeURIComponent(conversationId) + "/messages",
|
|
2898
|
+
fetchOpts,
|
|
2899
|
+
);
|
|
2900
|
+
if (!response.ok || !response.body) {
|
|
2901
|
+
throw new Error("Failed to stream response");
|
|
2902
|
+
}
|
|
2903
|
+
await readSseStream(response);
|
|
2904
|
+
|
|
2905
|
+
// Continuation loop: POST to /continue while the server signals more work
|
|
2906
|
+
while (_shouldContinue) {
|
|
2907
|
+
_shouldContinue = false;
|
|
2908
|
+
_receivedTerminalEvent = false;
|
|
2909
|
+
const contResponse = await fetch(
|
|
2910
|
+
"/api/conversations/" + encodeURIComponent(conversationId) + "/continue",
|
|
2911
|
+
{
|
|
2912
|
+
method: "POST",
|
|
2913
|
+
credentials: "include",
|
|
2914
|
+
headers: { "Content-Type": "application/json", "x-csrf-token": state.csrfToken },
|
|
2915
|
+
signal: streamAbortController.signal,
|
|
2916
|
+
},
|
|
2917
|
+
);
|
|
2918
|
+
if (!contResponse.ok || !contResponse.body) {
|
|
2919
|
+
// Server may have already handled continuation (safety net claimed it).
|
|
2920
|
+
// Fall back to polling for idle state.
|
|
2921
|
+
await pollUntilRunIdle(conversationId);
|
|
2922
|
+
break;
|
|
2923
|
+
}
|
|
2924
|
+
await readSseStream(contResponse);
|
|
2887
2925
|
}
|
|
2888
|
-
|
|
2926
|
+
|
|
2927
|
+
// If stream ended without terminal event and no continuation, check server
|
|
2928
|
+
if (!_receivedTerminalEvent && !_shouldContinue) {
|
|
2889
2929
|
try {
|
|
2890
2930
|
const recoveryPayload = await api("/api/conversations/" + encodeURIComponent(conversationId));
|
|
2891
|
-
if (recoveryPayload.needsContinuation) {
|
|
2892
|
-
|
|
2893
|
-
console.log("[poncho] Stream ended without terminal event, server has continuation — resuming");
|
|
2931
|
+
if (recoveryPayload.hasActiveRun || recoveryPayload.needsContinuation) {
|
|
2932
|
+
await pollUntilRunIdle(conversationId);
|
|
2894
2933
|
}
|
|
2895
2934
|
} catch (_recoverErr) {
|
|
2896
2935
|
console.warn("[poncho] Recovery check failed after abrupt stream end");
|
|
2897
2936
|
}
|
|
2898
2937
|
}
|
|
2899
|
-
if (!_shouldContinue) break;
|
|
2900
|
-
_receivedTerminalEvent = false;
|
|
2901
|
-
_isContinuation = true;
|
|
2902
|
-
}
|
|
2903
2938
|
// Update active state only if user is still on this conversation.
|
|
2904
2939
|
if (state.activeConversationId === streamConversationId) {
|
|
2905
2940
|
state.activeMessages = localMessages;
|