shadok-ai 0.15.6 → 0.15.7
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/package.json +1 -1
- package/public/index.html +90 -13
package/package.json
CHANGED
package/public/index.html
CHANGED
|
@@ -5546,6 +5546,16 @@
|
|
|
5546
5546
|
function scheduleReconnect(t) {
|
|
5547
5547
|
if (t.closing) return;
|
|
5548
5548
|
if (t.reconnectTimer) clearTimeout(t.reconnectTimer);
|
|
5549
|
+
// The socket that owed us a `working` is gone, so the optimistic gap can
|
|
5550
|
+
// never close by itself: drop it and hand back anything queued behind it.
|
|
5551
|
+
// Without this a drop INSIDE that gap latched `awaitingTurn` for good — the
|
|
5552
|
+
// reattach only sends `working` when the session is still busy, so a turn
|
|
5553
|
+
// that finished while we were away leaves no event to clear it, and every
|
|
5554
|
+
// later Enter queued behind a turn that will never end: box cleared, draft
|
|
5555
|
+
// dropped, nothing on the wire, nothing on screen. Measured in a browser.
|
|
5556
|
+
if (drainQueueToBox(t)) {
|
|
5557
|
+
addTurn(t, "system", "", "Your unsent message is back in the box — the link dropped before it was sent.");
|
|
5558
|
+
}
|
|
5549
5559
|
const n = t.reconnectAttempts++;
|
|
5550
5560
|
const delay = Math.min(15000, 1000 * 2 ** n) + Math.floor(Math.random() * 500);
|
|
5551
5561
|
setTabState(t, "dead", "link lost — reconnecting…");
|
|
@@ -6195,9 +6205,17 @@
|
|
|
6195
6205
|
if (msg.lastTurnMs) showFrozenTime(t, msg.lastTurnMs);
|
|
6196
6206
|
else t.timerEl.classList.add("done"); // freeze whatever's shown; don't keep ticking
|
|
6197
6207
|
addTurn(t, "system", "", "Session established. The pilot has the controls.");
|
|
6208
|
+
// Backstop to the same rule: "ready" asserts the process is idle NOW,
|
|
6209
|
+
// so there is no turn in flight to wait on — whatever path got us here.
|
|
6210
|
+
if (drainQueueToBox(t)) {
|
|
6211
|
+
addTurn(t, "system", "", "Your unsent message is back in the box — the turn it was waiting for is over.");
|
|
6212
|
+
}
|
|
6198
6213
|
if (t === active) promptInput.focus();
|
|
6199
6214
|
break;
|
|
6200
6215
|
case "working":
|
|
6216
|
+
// The turn is confirmed running: the optimistic gap is over, and any
|
|
6217
|
+
// further send now goes through the busy (takeover) path.
|
|
6218
|
+
t.awaitingTurn = false;
|
|
6201
6219
|
// Another client may have resolved the pending dialog.
|
|
6202
6220
|
retireChoices(t);
|
|
6203
6221
|
// Covers turns started elsewhere (other client, dialog answer, cron) and
|
|
@@ -6326,11 +6344,13 @@
|
|
|
6326
6344
|
persistChannels();
|
|
6327
6345
|
}
|
|
6328
6346
|
stopTurnTimer(t);
|
|
6329
|
-
|
|
6330
|
-
//
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6347
|
+
t.awaitingTurn = false;
|
|
6348
|
+
// Messages queued during this turn — a takeover (preemptTurn) or a fast
|
|
6349
|
+
// double-send (queueForNextTurn) — settle now. Send the NEXT one, in
|
|
6350
|
+
// order; submitPrompt re-arms the gap, so the rest flush on the following
|
|
6351
|
+
// turn-done. Mirrors the Telegram bridge's preempt.
|
|
6352
|
+
if (t.sendQueue && t.sendQueue.length) {
|
|
6353
|
+
const queued = t.sendQueue.shift();
|
|
6334
6354
|
clearTimeout(t.preemptTimer);
|
|
6335
6355
|
setTabState(t, "ready", "ready");
|
|
6336
6356
|
submitPrompt(t, queued);
|
|
@@ -6382,6 +6402,7 @@
|
|
|
6382
6402
|
// to restore it and forget it so it doesn't resurrect every reload.
|
|
6383
6403
|
addTurn(t, "system", "", msg.message);
|
|
6384
6404
|
forgetChannel(msg.sessionId);
|
|
6405
|
+
drainQueueToBox(t);
|
|
6385
6406
|
stopTurnTimer(t);
|
|
6386
6407
|
setTabMood(t, null);
|
|
6387
6408
|
setTabState(t, "dead", "directory gone");
|
|
@@ -6391,6 +6412,11 @@
|
|
|
6391
6412
|
// the instance's. Surface the fix instead of a line in the transcript.
|
|
6392
6413
|
if (msg.code === "logged-out") { openAuthCard(); }
|
|
6393
6414
|
addTurn(t, "system", "", "Error: " + msg.message);
|
|
6415
|
+
// A turn died before it could flush what was queued behind it (a fast
|
|
6416
|
+
// double-send): hand those back to the box rather than strand them.
|
|
6417
|
+
if (drainQueueToBox(t)) {
|
|
6418
|
+
addTurn(t, "system", "", "Your next message is back in the box — the turn didn't complete.");
|
|
6419
|
+
}
|
|
6394
6420
|
// Failed before ever going live (e.g. a bad restore): mark dead so it
|
|
6395
6421
|
// doesn't hang on "connecting…".
|
|
6396
6422
|
if (t.status === "connecting") setTabState(t, "dead", "failed to start");
|
|
@@ -6402,6 +6428,7 @@
|
|
|
6402
6428
|
break;
|
|
6403
6429
|
case "exited":
|
|
6404
6430
|
addTurn(t, "system", "", "The claude process exited (code " + msg.code + ").");
|
|
6431
|
+
drainQueueToBox(t);
|
|
6405
6432
|
stopTurnTimer(t);
|
|
6406
6433
|
setTabMood(t, null);
|
|
6407
6434
|
setTabState(t, "dead", "session ended");
|
|
@@ -6445,6 +6472,11 @@
|
|
|
6445
6472
|
// pilot exits while the server awaits getUsage(), "exited" marks the tab
|
|
6446
6473
|
// dead and this refusal would resurrect it as ready with a green LED,
|
|
6447
6474
|
// with no later message to correct it.
|
|
6475
|
+
// The refused prompt never became a turn: close the optimistic gap. Its
|
|
6476
|
+
// own text is re-offered by renderPaceConfirm below; anything queued
|
|
6477
|
+
// BEHIND it (a fast follow-up) has no turn to ride on, so hand it back.
|
|
6478
|
+
t.awaitingTurn = false;
|
|
6479
|
+
drainQueueToBox(t);
|
|
6448
6480
|
const rollback = t.status === "ready";
|
|
6449
6481
|
if (rollback) {
|
|
6450
6482
|
stopTurnTimer(t);
|
|
@@ -6585,21 +6617,54 @@
|
|
|
6585
6617
|
function preemptTurn(t, text) {
|
|
6586
6618
|
// Socket down: leave the text in the box, like submitPrompt's own guard.
|
|
6587
6619
|
if (!sendTo(t, { type: "key", key: "escape" })) return;
|
|
6588
|
-
t
|
|
6589
|
-
t.transcriptEl._stickBottom = true;
|
|
6590
|
-
if (t === active) { promptInput.value = ""; promptInput.style.height = "auto"; }
|
|
6591
|
-
dropDraft(t);
|
|
6592
|
-
clearAttachments(t); // their paths are already folded into `text`
|
|
6620
|
+
queueForNextTurn(t, text);
|
|
6593
6621
|
setTabState(t, "busy", "interrupting…");
|
|
6594
6622
|
clearTimeout(t.preemptTimer);
|
|
6595
6623
|
t.preemptTimer = setTimeout(() => {
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
6624
|
+
// Interrupt never landed (a wedged turn): hand the still-queued messages
|
|
6625
|
+
// back rather than lose them or loop. Only the interrupt path arms this —
|
|
6626
|
+
// a message merely QUEUED behind a healthy long turn must wait as long as
|
|
6627
|
+
// the turn takes, which can far exceed this failsafe.
|
|
6628
|
+
if (!t.sendQueue || !t.sendQueue.length) return;
|
|
6629
|
+
const stuck = t.sendQueue.join("\n");
|
|
6630
|
+
t.sendQueue = [];
|
|
6631
|
+
if (t === active && !promptInput.value) { promptInput.value = stuck; active.draft = stuck; }
|
|
6599
6632
|
addTurn(t, "system", "", "Couldn't interrupt the running turn — your message is back in the box. Try again, or use ⎋.");
|
|
6600
6633
|
}, PREEMPT_TIMEOUT_MS);
|
|
6601
6634
|
}
|
|
6602
6635
|
|
|
6636
|
+
/**
|
|
6637
|
+
* Stash a message to send once the in-flight turn settles, and clear the
|
|
6638
|
+
* composer so the user sees it was accepted. Nothing is rendered now: the
|
|
6639
|
+
* message appears as an ordinary prompt when it is actually sent (on
|
|
6640
|
+
* `turn-done`), so the transcript stays in order. Shared by the takeover
|
|
6641
|
+
* (preemptTurn, which also interrupts) and the fast-double-send guard, where a
|
|
6642
|
+
* second Enter lands before the server's `working` has flipped the tab to
|
|
6643
|
+
* busy — without this it fired a SECOND raw prompt, which the server refused as
|
|
6644
|
+
* "a response is already in progress" while the box was already cleared, so the
|
|
6645
|
+
* message was lost. A FIFO queue, not a single slot: several quick messages all
|
|
6646
|
+
* survive, delivered in order.
|
|
6647
|
+
*/
|
|
6648
|
+
function queueForNextTurn(t, text) {
|
|
6649
|
+
(t.sendQueue ||= []).push(text);
|
|
6650
|
+
t.transcriptEl._stickBottom = true;
|
|
6651
|
+
if (t === active) { promptInput.value = ""; promptInput.style.height = "auto"; }
|
|
6652
|
+
dropDraft(t);
|
|
6653
|
+
clearAttachments(t); // their paths are already folded into `text`
|
|
6654
|
+
}
|
|
6655
|
+
|
|
6656
|
+
/** Hand any queued-but-unsent messages back to the composer (a turn died
|
|
6657
|
+
* before they could be flushed), so a fast double-send is never swallowed by
|
|
6658
|
+
* an error/exit. Returns true if something was restored. */
|
|
6659
|
+
function drainQueueToBox(t) {
|
|
6660
|
+
t.awaitingTurn = false;
|
|
6661
|
+
if (!t.sendQueue || !t.sendQueue.length) return false;
|
|
6662
|
+
const stuck = t.sendQueue.join("\n");
|
|
6663
|
+
t.sendQueue = [];
|
|
6664
|
+
if (t === active && !promptInput.value) { promptInput.value = stuck; active.draft = stuck; autosizePrompt(); }
|
|
6665
|
+
return true;
|
|
6666
|
+
}
|
|
6667
|
+
|
|
6603
6668
|
function submitPrompt(t, text, force) {
|
|
6604
6669
|
// Send BEFORE touching the UI. Everything below is optimistic — it shows the
|
|
6605
6670
|
// turn, marks the tab busy, clears the composer and drops the draft — so on
|
|
@@ -6609,6 +6674,12 @@
|
|
|
6609
6674
|
addTurn(t, "system", "", "Not sent: this agent is not connected. Your text is still in the box.");
|
|
6610
6675
|
return;
|
|
6611
6676
|
}
|
|
6677
|
+
// The turn is in flight but the server's `working` has not come back yet, so
|
|
6678
|
+
// t.status is still "ready". Mark the gap so a second fast Enter QUEUES
|
|
6679
|
+
// (queueForNextTurn) instead of firing another raw prompt the server would
|
|
6680
|
+
// refuse as busy — the fast-double-send bug. Cleared on `working` (the busy
|
|
6681
|
+
// path takes over from there) and on every turn settle/terminal.
|
|
6682
|
+
t.awaitingTurn = true;
|
|
6612
6683
|
// Any outstanding pace question is answered by the fact that something is
|
|
6613
6684
|
// being sent — whether by clicking its own button or by a fresh submit.
|
|
6614
6685
|
retirePaceConfirm(t);
|
|
@@ -6714,6 +6785,12 @@
|
|
|
6714
6785
|
// Sending during a running turn is a takeover: interrupt it, then send the
|
|
6715
6786
|
// message once the turn settles. Same behaviour as Telegram.
|
|
6716
6787
|
if (t.status === "busy") return preemptTurn(t, text);
|
|
6788
|
+
// A prompt is already in flight but the server's `working` has not come back
|
|
6789
|
+
// yet (t.status still "ready"): a fast second Enter. QUEUE it instead of
|
|
6790
|
+
// firing a second raw prompt the server refuses as busy — the message would
|
|
6791
|
+
// otherwise be lost with the box already cleared. No interrupt: the turn is
|
|
6792
|
+
// only just starting, so let it finish and send this next.
|
|
6793
|
+
if (t.awaitingTurn) return queueForNextTurn(t, text);
|
|
6717
6794
|
if (t.status !== "ready") return;
|
|
6718
6795
|
// Checked here so nothing is optimistically rendered before the server
|
|
6719
6796
|
// refuses. The server re-checks anyway — it is the authority.
|