shadok-ai 0.16.8 → 0.16.10
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/README.md +15 -11
- package/package.json +1 -1
- package/public/index.html +54 -33
package/README.md
CHANGED
|
@@ -84,7 +84,8 @@ Enter to skip; you can add it later from the web UI).
|
|
|
84
84
|
emoji, or **type a word** (`rocket`, `bug`, `money`…) to search for one; its
|
|
85
85
|
**+** creates the new agent straight into that group. Dragging an agent onto a
|
|
86
86
|
group files it there, dragging groups reorders them, and double-clicking a name
|
|
87
|
-
renames it
|
|
87
|
+
renames it — a group can also be renamed by clicking its name at the top of the
|
|
88
|
+
agents column. A question the agent asks shows as an *Approval requested* card. The
|
|
88
89
|
whole cockpit is skinned like Microsoft Teams (grey light mode / near-black dark
|
|
89
90
|
mode, the Shadok mark in the header). On a phone the tools sit in the ⋯ menu and
|
|
90
91
|
the agents sheet lists every group.
|
|
@@ -531,16 +532,19 @@ every 25s (tunable with `SHADOK_WS_PING_MS`) to keep the connection warm, so no
|
|
|
531
532
|
proxy change is normally needed; if your proxy still cuts it, raise
|
|
532
533
|
`proxy_read_timeout` above the ping interval.
|
|
533
534
|
|
|
534
|
-
**Sleep / wake
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
535
|
+
**Sleep / wake / network blips.** When the machine comes back (an `online`
|
|
536
|
+
event, the tab becoming visible, a back/forward-cache restore), the cockpit
|
|
537
|
+
revives the links that actually **dropped** — resetting their backoff so a
|
|
538
|
+
genuinely-closed socket reconnects promptly instead of waiting out a delay that
|
|
539
|
+
climbed while you were away. A socket that still reports "open" is left alone:
|
|
540
|
+
`online` fires on every network blip (wifi roam, VPN, interface change), and
|
|
541
|
+
force-reconnecting healthy links on each one reconnected all your agents at
|
|
542
|
+
once — tripping the upstream single-use-token OAuth refresh race so a batch
|
|
543
|
+
stuck on "connecting…". A slept socket that looks "open" but is dead is
|
|
544
|
+
terminated by the server heartbeat (~50s) and reconnects on the resulting
|
|
545
|
+
`close` without that churn. Only a back/forward-cache restore force-replaces an
|
|
546
|
+
"open" socket, because there the restored page's old sockets are dead yet fire
|
|
547
|
+
no `close`.
|
|
544
548
|
|
|
545
549
|
### Cross-instance peers
|
|
546
550
|
|
package/package.json
CHANGED
package/public/index.html
CHANGED
|
@@ -528,6 +528,9 @@
|
|
|
528
528
|
overflow-y: auto;
|
|
529
529
|
}
|
|
530
530
|
#tabbar .side-label { padding: 0 8px 8px; font-size: 13px; font-weight: 600; color: var(--text); }
|
|
531
|
+
#tabbar .side-label #viewLabel.editable { cursor: text; border-radius: 4px; padding: 1px 3px; margin: -1px -3px; }
|
|
532
|
+
#tabbar .side-label #viewLabel.editable:hover { background: var(--bg-inset); box-shadow: inset 0 0 0 1px var(--line); }
|
|
533
|
+
#tabbar .side-label input.rename { font: inherit; width: calc(100% - 2em); }
|
|
531
534
|
.tab {
|
|
532
535
|
position: relative;
|
|
533
536
|
display: flex;
|
|
@@ -3035,7 +3038,7 @@
|
|
|
3035
3038
|
<div id="iconEmpty" class="icon-empty" hidden>No emoji for that word.</div>
|
|
3036
3039
|
</div>
|
|
3037
3040
|
<nav id="tabbar" aria-label="Agents">
|
|
3038
|
-
<div class="label side-label"><span class="sl-all">Agents</span><span class="sl-view" id="viewLabel"></span></div>
|
|
3041
|
+
<div class="label side-label"><span class="sl-all">Agents</span><span class="sl-view"><span id="viewIcon"></span> <span id="viewLabel"></span></span></div>
|
|
3039
3042
|
<div id="ungrouped"></div>
|
|
3040
3043
|
<div id="groups"></div>
|
|
3041
3044
|
<p id="viewEmpty" class="check-hint" hidden>No agent in this group yet — create one, or drag one onto the group.</p>
|
|
@@ -3427,6 +3430,7 @@
|
|
|
3427
3430
|
|
|
3428
3431
|
const $ = (id) => document.getElementById(id);
|
|
3429
3432
|
const tabbar = $("tabbar"), newTabBtn = $("newTab"), newGroupBtn = $("newGroup");
|
|
3433
|
+
const viewLabelEl = $("viewLabel");
|
|
3430
3434
|
const ungroupedEl = $("ungrouped"), groupsEl = $("groups");
|
|
3431
3435
|
const transcripts = $("transcripts"), thinking = $("thinking");
|
|
3432
3436
|
const composer = $("composer"), promptInput = $("promptInput"), sendBtn = $("sendBtn");
|
|
@@ -4813,11 +4817,34 @@
|
|
|
4813
4817
|
if (gi && gi.textContent !== g.icon) gi.textContent = g.icon;
|
|
4814
4818
|
}
|
|
4815
4819
|
const g = groups.find((x) => String(x.id) === v);
|
|
4816
|
-
$("
|
|
4820
|
+
$("viewIcon").textContent = g ? g.icon : "";
|
|
4821
|
+
// A cached reference, never $("viewLabel"): while it is being renamed the
|
|
4822
|
+
// span is swapped out for the input, and a lookup by id would return null
|
|
4823
|
+
// and throw on the very next sync.
|
|
4824
|
+
viewLabelEl.textContent = g ? g.nameEl.textContent : "Main";
|
|
4825
|
+
// Only a real group can be renamed here: "Main" is the ungrouped view, not
|
|
4826
|
+
// a group, and has no name of its own to store.
|
|
4827
|
+
viewLabelEl.classList.toggle("editable", !!g);
|
|
4828
|
+
viewLabelEl.title = g ? "Click to rename this group" : "";
|
|
4817
4829
|
$("viewEmpty").hidden = tabsInView(v).length > 0 || v === "ungrouped";
|
|
4818
4830
|
renderRail();
|
|
4819
4831
|
}
|
|
4820
4832
|
|
|
4833
|
+
// The column's title names the group you are looking at, so it is where one
|
|
4834
|
+
// reaches to rename it — the rail item needs a double-click and, collapsed to
|
|
4835
|
+
// icons, shows no name at all. Wired ONCE on the stable span: applyView
|
|
4836
|
+
// rewrites its text, never the element.
|
|
4837
|
+
viewLabelEl.addEventListener("click", (e) => {
|
|
4838
|
+
const g = groups.find((x) => String(x.id) === currentView());
|
|
4839
|
+
if (!g) return;
|
|
4840
|
+
e.stopPropagation();
|
|
4841
|
+
inlineRename(tabbar, viewLabelEl, (v) => {
|
|
4842
|
+
g.nameEl.textContent = v;
|
|
4843
|
+
persistGroups();
|
|
4844
|
+
applyView();
|
|
4845
|
+
});
|
|
4846
|
+
});
|
|
4847
|
+
|
|
4821
4848
|
const moodOf = (t) =>
|
|
4822
4849
|
["needs-answer", "unread", "working"].find((m) => t.btnEl.classList.contains(m)) || null;
|
|
4823
4850
|
|
|
@@ -7095,51 +7122,45 @@
|
|
|
7095
7122
|
// off. Bounded so an update that never lands doesn't leave us hammering.
|
|
7096
7123
|
let reloadExpectedUntil = 0;
|
|
7097
7124
|
|
|
7098
|
-
/**
|
|
7099
|
-
*
|
|
7100
|
-
*
|
|
7101
|
-
*
|
|
7102
|
-
*
|
|
7103
|
-
*
|
|
7104
|
-
*
|
|
7105
|
-
*
|
|
7106
|
-
*
|
|
7125
|
+
/** Revive the tabs whose link actually DROPPED — fired when the machine/tab
|
|
7126
|
+
* comes back (online / visible). It resets the backoff and reconnects at
|
|
7127
|
+
* once, so a genuinely-closed socket comes back promptly instead of waiting
|
|
7128
|
+
* out an exponential delay that climbed while we were away.
|
|
7129
|
+
*
|
|
7130
|
+
* It deliberately does NOT touch a socket that still reports OPEN. An earlier
|
|
7131
|
+
* version force-replaced OPEN sockets too (to catch a laptop-sleep "zombie" —
|
|
7132
|
+
* OPEN on the client, dead on the wire). But `online` fires on EVERY network
|
|
7133
|
+
* blip (wifi roam, VPN, interface change), and force-closing every healthy
|
|
7134
|
+
* link on each blip reconnected all N agents at once — tripping the upstream
|
|
7135
|
+
* OAuth refresh-token race (single-use tokens, concurrent `claude --resume`,
|
|
7136
|
+
* biting at ~10–30 agents) so a whole batch stuck on "connecting…". That mass
|
|
7137
|
+
* churn was worse than the zombie it chased: a slept socket is terminated by
|
|
7138
|
+
* the server heartbeat (~50s) and the resulting `close` reconnects it anyway,
|
|
7139
|
+
* no force needed. `force` is kept ONLY for a bfcache `pageshow`, where the
|
|
7140
|
+
* restored page's old sockets are genuinely dead and fire no `close`. */
|
|
7107
7141
|
function reconnectAllNow(force) {
|
|
7108
7142
|
for (const t of tabs) {
|
|
7109
7143
|
if (t.closing || !t.sessionId) continue;
|
|
7110
7144
|
const st = t.ws && t.ws.readyState;
|
|
7111
7145
|
if (st === WebSocket.CONNECTING) continue; // a link is already on its way
|
|
7112
|
-
if (st === WebSocket.OPEN && !force) continue; // healthy
|
|
7146
|
+
if (st === WebSocket.OPEN && !force) continue; // healthy — leave it, a blip is not a death
|
|
7113
7147
|
if (t.reconnectTimer) clearTimeout(t.reconnectTimer);
|
|
7114
|
-
t.reconnectAttempts = 0;
|
|
7148
|
+
t.reconnectAttempts = 0; // reset the backoff: reconnect promptly, not after a climbed delay
|
|
7115
7149
|
const old = t.ws;
|
|
7116
7150
|
openLink(t); // replaces t.ws with the fresh socket
|
|
7117
|
-
// Close
|
|
7151
|
+
// Close a forced (bfcache) zombie only after openLink swapped t.ws, so its
|
|
7118
7152
|
// `close` handler sees `t.ws !== ws` and does not schedule a competing
|
|
7119
7153
|
// reconnect. A no-op when `old` was already closed.
|
|
7120
7154
|
if (old && old !== t.ws) { try { old.close(); } catch {} }
|
|
7121
7155
|
}
|
|
7122
7156
|
}
|
|
7123
7157
|
|
|
7124
|
-
//
|
|
7125
|
-
//
|
|
7126
|
-
//
|
|
7127
|
-
|
|
7128
|
-
|
|
7129
|
-
|
|
7130
|
-
const STALE_WAKE_MS = 10_000;
|
|
7131
|
-
let hiddenAt = 0;
|
|
7132
|
-
function wokeAfterLongAway() {
|
|
7133
|
-
const away = hiddenAt ? Date.now() - hiddenAt : 0;
|
|
7134
|
-
hiddenAt = 0;
|
|
7135
|
-
return away > STALE_WAKE_MS;
|
|
7136
|
-
}
|
|
7137
|
-
window.addEventListener("online", () => reconnectAllNow(true));
|
|
7138
|
-
window.addEventListener("pageshow", (e) => reconnectAllNow(!!e.persisted || wokeAfterLongAway()));
|
|
7139
|
-
document.addEventListener("visibilitychange", () => {
|
|
7140
|
-
if (document.hidden) { hiddenAt = Date.now(); return; }
|
|
7141
|
-
reconnectAllNow(wokeAfterLongAway());
|
|
7142
|
-
});
|
|
7158
|
+
// Reconnect the links that dropped while we were away — but never disturb a
|
|
7159
|
+
// healthy one (see reconnectAllNow). Only a bfcache restore forces, because
|
|
7160
|
+
// there the old sockets are dead yet fire no `close`.
|
|
7161
|
+
window.addEventListener("online", () => reconnectAllNow(false));
|
|
7162
|
+
window.addEventListener("pageshow", (e) => reconnectAllNow(!!e.persisted));
|
|
7163
|
+
document.addEventListener("visibilitychange", () => { if (!document.hidden) reconnectAllNow(false); });
|
|
7143
7164
|
|
|
7144
7165
|
/**
|
|
7145
7166
|
* Lists the agents alive in the server right now — including ones spawned
|