shadok-ai 0.15.44 → 0.15.45

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 CHANGED
@@ -76,20 +76,18 @@ Enter to skip; you can add it later from the web UI).
76
76
  - **Channels** — one `claude` process each, running in parallel. Renaming,
77
77
  grouping, and closing all sync live between the browser and Telegram: it's
78
78
  one list, server-owned, not two copies.
79
- - **Groups** — on a desktop the cockpit is laid out like Microsoft Teams: an
80
- app bar on the left (Activity — a click opens the next agent waiting for you —
81
- then Profiles, Ledger, Secrets, Users, Security, Telegram, Sound), a search box
82
- in the title bar that narrows the agents list as you type (Enter opens the first
83
- match), and an agents pane where **every group is a collapsible section**, each
84
- agent a round avatar with a presence dot (green ready, red working, hollow
79
+ - **Groups** — on a desktop the groups live in their own **left column**: pick
80
+ one to narrow the agents pane to it, and the global tools (Profiles, Ledger,
81
+ Secrets, Users, Security, Telegram, Sound) stay in the header at the top. Each
82
+ agent is a round avatar with a presence dot (green ready, red working, hollow
85
83
  offline). A group's icon opens the icon picker: choose from the grid, type any
86
84
  emoji, or **type a word** (`rocket`, `bug`, `money`…) to search for one; its
87
85
  **+** creates the new agent straight into that group. Dragging an agent onto a
88
86
  group files it there, dragging groups reorders them, and double-clicking a name
89
- renames it. The agent's header carries Chat / Changes / Schedule / Terminal as
90
- tabs, and a question the agent asks shows as an *Approval requested* card. On a
91
- phone nothing changes: the tools sit in the ⋯ menu and the agents sheet lists
92
- every group.
87
+ renames it. A question the agent asks shows as an *Approval requested* card. The
88
+ whole cockpit is skinned like Microsoft Teams (grey light mode / near-black dark
89
+ mode, the Shadok mark in the header). On a phone the tools sit in the ⋯ menu and
90
+ the agents sheet lists every group.
93
91
  - **Worktree isolation** — spawned agents get their own git worktree and branch
94
92
  by default, so parallel agents never collide. Work is never auto-discarded:
95
93
  an empty worktree is reclaimed on close, anything with changes or commits
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shadok-ai",
3
- "version": "0.15.44",
3
+ "version": "0.15.45",
4
4
  "main": "dist/session.js",
5
5
  "scripts": {
6
6
  "test": "node --import tsx --test --test-timeout=120000 test/*.ts context/*/test/*.test.mjs",
package/public/index.html CHANGED
@@ -2139,27 +2139,18 @@
2139
2139
  .sec-reload button:hover { background: color-mix(in srgb, var(--amber) 15%, transparent); }
2140
2140
 
2141
2141
  /* ══ Teams layer ═════════════════════════════════════════════════════════
2142
- The cockpit's shape follows Microsoft Teams: a title bar with search, an app
2143
- bar on the left, every group as a collapsible section of the agents pane,
2144
- round avatars with a presence dot, a channel header with tabs, bubbles, and
2145
- dialogs as approval cards. Colours stay tokens, so light mode reads as Teams
2146
- and dark mode as the neutral near-black. Placed LAST so it wins on equal
2147
- specificity; the phone layout (≤640px) keeps its sheet and its ⋯ menu. */
2142
+ A Teams-style SKIN over the cockpit's original layout: round avatars with a
2143
+ presence dot, a channel header, bubbles, and dialogs as approval cards.
2144
+ Colours stay tokens, so light mode reads as Teams and dark mode as the
2145
+ neutral near-black. The layout itself is unchanged — the global tools live
2146
+ in the header, and the groups keep their own left column (#groupRail). Placed
2147
+ LAST so it wins on equal specificity; the phone layout (≤640px) keeps its
2148
+ sheet and its ⋯ menu. */
2148
2149
  :root { --mine: color-mix(in srgb, var(--amber) 20%, var(--bg-raised)); --pane-main: color-mix(in srgb, var(--bg) 55%, var(--bg-raised)); }
2149
2150
  :root[data-mode="light"] { --mine: color-mix(in srgb, var(--amber) 13%, #fff); }
2150
2151
 
2151
2152
  /* Title bar */
2152
2153
  header { padding: 8px 14px; gap: 14px; }
2153
- #agentSearch {
2154
- flex: 0 1 520px; margin: 0 auto; display: flex; align-items: center; gap: 8px;
2155
- height: 32px; padding: 0 12px; border-radius: 6px; cursor: text;
2156
- background: var(--bg-raised); color: var(--text-dim); border: 1px solid var(--line);
2157
- }
2158
- #agentSearch:focus-within { border-color: var(--amber); }
2159
- #agentSearch input { flex: 1; min-width: 0; border: 0; outline: none; background: none; color: var(--text); font: inherit; font-size: 13.5px; padding: 0; }
2160
- #agentSearch input::placeholder { color: var(--text-dim); }
2161
- #agentSearch + .spacer { display: none; }
2162
- @media (max-width: 640px) { #agentSearch { display: none; } #agentSearch + .spacer { display: block; } }
2163
2154
  @media (min-width: 641px) { header { flex-wrap: nowrap; } }
2164
2155
  /* The star pill is the widest tool; under 1100px it lives in the ⋯ menu. */
2165
2156
  @media (min-width: 641px) and (max-width: 1100px) { #starBtn { display: none; } #moreMenu #starMenuItem { display: block; } }
@@ -2183,7 +2174,6 @@
2183
2174
  }
2184
2175
  .tav .led { position: absolute; right: -2px; bottom: -2px; width: 12px; height: 12px; margin: 0; border: 2px solid var(--bg); }
2185
2176
  .ab-txt, .ct-txt { display: none; }
2186
- #appBar { display: none; }
2187
2177
  @media (max-width: 640px) {
2188
2178
  /* A phone's single header row has no room for the mark beside the name,
2189
2179
  the version and the dials; the favicon carries it there. */
@@ -2203,8 +2193,6 @@
2203
2193
  :root:not([data-mode="light"]) .tab.active { background: var(--bg-inset); box-shadow: inset 0 0 0 1px var(--line); }
2204
2194
  .tab.active .tav .led { border-color: var(--bg-raised); }
2205
2195
  .tab.unread { border-left: 0; border-radius: 6px; font-weight: 700; }
2206
- .tab.filtered-out { display: none !important; }
2207
- body.filtering .group.collapsed .group-body { display: flex !important; }
2208
2196
  .group-head { gap: 10px; padding: 8px; font-size: 14px; color: var(--text); border-radius: 6px; }
2209
2197
  .group-head .gicon {
2210
2198
  flex: none; width: 30px; height: 30px; border-radius: 6px; display: grid; place-items: center;
@@ -2215,62 +2203,25 @@
2215
2203
 
2216
2204
  @media (min-width: 641px) {
2217
2205
  main { padding: 0; gap: 0; }
2218
- /* App bar */
2219
- #appBar { width: 68px; flex: none; display: flex; flex-direction: column; align-items: center; gap: 2px; padding: 4px 0 12px; background: var(--page); overflow-y: auto; }
2220
- #appTools { display: contents; }
2221
- #appBar button {
2222
- position: relative; width: 62px; padding: 8px 0 6px; display: flex; flex-direction: column; align-items: center; gap: 3px;
2223
- border: 0; border-radius: 6px; background: none; color: var(--text-dim); font-size: 10.5px; line-height: 1.1;
2224
- }
2225
- #appBar button:hover { color: var(--amber); background: none; }
2226
- #appBar button.on { color: var(--amber); font-weight: 600; }
2227
- #appBar button.on::before { content: ""; position: absolute; left: -2px; top: 10px; bottom: 10px; width: 3px; border-radius: 2px; background: var(--amber); }
2228
- #appBar button[hidden] { display: none; }
2229
- #appBar svg { width: 20px !important; height: 20px !important; vertical-align: 0 !important; }
2230
- #appBar .ab-txt { display: block; }
2231
- #appBar .ab-n, #appBar .sec-badge {
2232
- position: absolute; top: 3px; right: 12px; min-width: 16px; height: 16px; padding: 0 4px; border-radius: 8px;
2233
- background: var(--err); color: #fff; font-size: 10px; font-weight: 600; line-height: 16px; text-align: center;
2234
- }
2235
- #appBar .ab-n[hidden] { display: none; }
2236
-
2237
- /* Every group is a section of the agents pane; the rail goes. */
2238
- body.has-groups #groupRail { display: none; }
2239
- #tabbar, body.has-groups #tabbar {
2240
- width: 300px; border: 0; border-radius: 8px 0 0 0; background: var(--bg); padding: 12px 8px;
2241
- }
2242
- #tabbar .side-label { font-size: 18px; font-weight: 700; padding: 4px 8px 12px; }
2243
- body.has-groups #tabbar .side-label .sl-all { display: inline; }
2244
- #tabbar .side-label .sl-view, #viewEmpty { display: none !important; }
2245
- #tabbar .group-head { display: flex; }
2246
- #tabbar .group { margin-top: 6px; }
2247
- #tabbar .group-body { padding-left: 20px; }
2248
- #tabbar .group.collapsed .group-body { display: none; }
2249
- #tabbar:not([data-view="ungrouped"]) #ungrouped,
2250
- #tabbar[data-view="ungrouped"] #groups { display: flex; flex-direction: column; }
2251
- #tabbar .group:not(.in-view) { display: flex; }
2252
- #newGroupBare, body.has-groups #newGroupBare { display: block; }
2253
-
2254
2206
  /* Conversation */
2255
2207
  .chat-pane { border: 0; border-radius: 0; background: var(--pane-main); }
2256
2208
  #machine, #diffpanel { border-radius: 0; border-top: 0; border-bottom: 0; border-right: 0; }
2257
2209
  }
2258
2210
 
2259
- /* Channel header: avatar, name, status, then tabs. */
2211
+ /* Channel header: avatar, name, status, then actions. */
2260
2212
  #chanBar { gap: 12px; padding: 0 20px; min-height: 58px; background: var(--bg-raised); border-bottom: 1px solid var(--line); }
2261
2213
  #chanBar .tav { width: 36px; height: 36px; font-size: 13px; }
2262
2214
  #chanBar #chanBarName { font-size: 16px; font-weight: 700; }
2263
2215
  #chanBar .gauge .label { display: none; }
2264
2216
  #chanBar .gauge .value { color: var(--text-dim); font-size: 12px; }
2265
2217
  #chanBar .gauge:nth-of-type(3) { display: none; } /* the session id: in the tooltip of the menu */
2266
- .chan-tabs { display: flex; align-self: stretch; gap: 2px; margin-left: 10px; }
2267
- .chan-tabs button, #chanBar .chan-actions #chanCron, #chanBar .chan-actions #chanTerm {
2218
+ #chanBar .chan-actions #chanCron, #chanBar .chan-actions #chanTerm {
2268
2219
  position: relative; align-self: stretch; display: inline-flex; align-items: center; gap: 6px;
2269
2220
  border: 0; border-radius: 0; background: none; padding: 0 10px; color: var(--text-dim); font-size: 14px;
2270
2221
  }
2271
- .chan-tabs button:hover, #chanBar .chan-actions #chanCron:hover, #chanBar .chan-actions #chanTerm:hover { color: var(--text); background: none; }
2272
- .chan-tabs button.on, #chanBar .chan-actions #chanTerm.on { color: var(--text); font-weight: 600; background: none; border: 0; }
2273
- .chan-tabs button.on::after, #chanBar .chan-actions #chanTerm.on::after {
2222
+ #chanBar .chan-actions #chanCron:hover, #chanBar .chan-actions #chanTerm:hover { color: var(--text); background: none; }
2223
+ #chanBar .chan-actions #chanTerm.on { color: var(--text); font-weight: 600; background: none; border: 0; }
2224
+ #chanBar .chan-actions #chanTerm.on::after {
2274
2225
  content: ""; position: absolute; left: 10px; right: 10px; bottom: 0; height: 3px; border-radius: 3px 3px 0 0; background: var(--amber);
2275
2226
  }
2276
2227
  #chanBar .chan-actions { align-self: stretch; }
@@ -2278,7 +2229,7 @@
2278
2229
  #chanBar .chan-actions #chanStop, #chanBar .chan-actions #chanMenu { align-self: center; }
2279
2230
  @media (max-width: 640px) {
2280
2231
  #chanBar { min-height: 0; padding: 8px 12px; }
2281
- #chanBar .tav, .chan-tabs, #chanBar .chan-actions .ct-txt { display: none; }
2232
+ #chanBar .tav, #chanBar .chan-actions .ct-txt { display: none; }
2282
2233
  }
2283
2234
 
2284
2235
  /* Messages: Teams bubbles. */
@@ -2308,8 +2259,6 @@
2308
2259
 
2309
2260
  <header>
2310
2261
  <span class="brand"><span class="brand-logo" aria-hidden="true"><svg viewBox="0 0 40 40"><g fill="none" stroke="currentColor" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="17" cy="17" rx="10" ry="8.5"/><path d="M25 12.5c3-3 7.5-3 10-1.2-2.8.4-5 1.9-6.6 4"/><path d="M14 25.3l-2.4 10M20 25.3l1.6 10"/></g></svg></span><span class="brand-name" id="brandName" title="Rename this cockpit">shadok-ai</span><span class="brand-edit" id="brandEdit" title="Rename this cockpit" aria-hidden="true">✎</span> <span class="ver" id="verView" title="Running version"></span><span class="ver-chan" id="verChan" aria-hidden="true"></span></span>
2311
- <!-- Teams-style search: filters the agents list as you type (desktop). -->
2312
- <label id="agentSearch"><svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true"><circle cx="10.5" cy="10.5" r="6" fill="none" stroke="currentColor" stroke-width="1.8"/><path d="M15 15l5 5" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg><input id="agentFilter" type="search" placeholder="Search agents" autocomplete="off" spellcheck="false" aria-label="Search agents"></label>
2313
2262
  <span class="spacer"></span>
2314
2263
  <div class="gauge quota" id="quota5h" data-window="5h" title="5h rolling limit">
2315
2264
  <svg class="dial" viewBox="0 0 56 44" role="img" aria-label="5h rolling limit">
@@ -2873,14 +2822,6 @@
2873
2822
  </div>
2874
2823
 
2875
2824
  <main>
2876
- <!-- The app bar (desktop): Teams' left column. Activity jumps to an agent that
2877
- waits for you; the global tools are MOVED here by reflowHeaderTools (the
2878
- real buttons, handlers included) and back to the header/⋯ on a phone. -->
2879
- <nav id="appBar" aria-label="Apps">
2880
- <button id="abActivity" type="button" title="Jump to an agent waiting for you"><svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true"><path fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round" d="M6 16V11a6 6 0 1 1 12 0v5l1.5 2h-15zM10 20.5a2 2 0 0 0 4 0"/></svg><span class="ab-txt">Activity</span><span id="abActivityN" class="ab-n" hidden></span></button>
2881
- <button id="abAgents" type="button" class="on" title="Agents"><svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true"><g fill="none" stroke="currentColor" stroke-width="1.6"><circle cx="9" cy="9" r="3.2"/><circle cx="17" cy="10" r="2.4"/><path d="M3.5 19c.6-3 2.8-4.6 5.5-4.6s4.9 1.6 5.5 4.6M14.5 15c2.6-.6 5 .6 5.8 3.4"/></g></svg><span class="ab-txt">Agents</span></button>
2882
- <div id="appTools"></div>
2883
- </nav>
2884
2825
  <!-- The groups column (desktop only): a VIEW over the agents column beside it,
2885
2826
  which still holds the groups, their order and their members — that is what
2886
2827
  is persisted. Picking a group narrows the agents column to it. On a phone
@@ -2950,10 +2891,6 @@
2950
2891
  <span class="label">Branch</span>
2951
2892
  <span class="value" id="branchView">—</span>
2952
2893
  </div>
2953
- <span class="chan-tabs" role="tablist">
2954
- <button id="chanChat" type="button" role="tab">Chat</button>
2955
- <button id="chanDiff" type="button" role="tab">Changes</button>
2956
- </span>
2957
2894
  <span class="chan-actions">
2958
2895
  <button id="chanUpdate" class="icon" title="Claude Code updated — reload this agent to apply it" aria-label="Reload to apply update" hidden>↻ update</button>
2959
2896
  <button id="chanCron" class="icon" title="Scheduled prompts for this agent (monitoring / reporting)" aria-label="Schedule"><svg viewBox="0 0 24 24" width="15" height="15" style="vertical-align:-2px" aria-hidden="true"><circle cx="12" cy="13" r="6.3" fill="none" stroke="currentColor" stroke-width="1.8"/><path d="M12 9.8V13l2.2 1.4" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/><path d="M4.8 6.6 7.2 4.6M19.2 6.6 16.8 4.6" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg><span class="ct-txt">Schedule</span></button>
@@ -8493,21 +8430,17 @@
8493
8430
  // the real button elements, so their click handlers travel with them.
8494
8431
  // Recomputed on load, on resize, and when the instance name changes.
8495
8432
  const OVERFLOW_TOOLS = ["secretsBtn", "ledgerBtn", "profilesBtn", "telegramBtn", "muteNotif"];
8496
- // Every tool the desktop app bar carries, in its order.
8497
- const APP_TOOLS = ["profilesBtn", "ledgerBtn", "secretsBtn", "usersBtn", "securityBtn", "telegramBtn", "muteNotif"];
8498
8433
  function reflowHeaderTools() {
8499
8434
  const hdrTools = document.querySelector(".hdr-tools");
8500
8435
  const moreWrap = document.querySelector(".more-wrap");
8501
8436
  const overflow = $("overflowTools");
8502
8437
  if (!hdrTools || !moreWrap || !overflow) return;
8503
8438
  const mobile = matchMedia("(max-width: 640px)").matches;
8504
- const appTools = $("appTools");
8505
- for (const id of APP_TOOLS) {
8439
+ for (const id of OVERFLOW_TOOLS) {
8506
8440
  const el = $(id);
8507
8441
  if (!el) continue;
8508
- if (!mobile && appTools) appTools.appendChild(el); // desktop: the app bar
8509
- else if (mobile && OVERFLOW_TOOLS.includes(id)) overflow.appendChild(el); // phone: the ⋯ menu
8510
- else hdrTools.insertBefore(el, moreWrap); // phone: stays in the header
8442
+ if (mobile) overflow.appendChild(el); // all into the ⋯ menu
8443
+ else hdrTools.insertBefore(el, moreWrap); // back in the header, in order
8511
8444
  }
8512
8445
  }
8513
8446
  let reflowPending = false;
@@ -8520,57 +8453,6 @@
8520
8453
  document.addEventListener("DOMContentLoaded", reflowHeaderTools);
8521
8454
  reflowHeaderTools();
8522
8455
 
8523
- // ── Teams layer: app bar, search, channel tabs ─────────────────────────────
8524
- // Activity: how many agents wait for an answer; a click opens the next one.
8525
- function paintActivity() {
8526
- const n = tabs.filter((t) => t.btnEl.classList.contains("needs-answer")).length;
8527
- const badge = $("abActivityN");
8528
- badge.hidden = !n;
8529
- badge.textContent = n || "";
8530
- }
8531
- new MutationObserver(paintActivity).observe($("tabbar"), { subtree: true, attributes: true, attributeFilter: ["class"], childList: true });
8532
- $("abActivity").addEventListener("click", () => {
8533
- const waiting = tabs.filter((t) => t.btnEl.classList.contains("needs-answer"));
8534
- if (!waiting.length) return;
8535
- activate(waiting[(waiting.indexOf(active) + 1) % waiting.length]);
8536
- });
8537
- $("abAgents").addEventListener("click", () => $("agentFilter").focus());
8538
-
8539
- // Search: narrows the agents list to the names that match; groups unfold
8540
- // while a query is typed so nothing matching hides in a collapsed one.
8541
- function applyAgentFilter() {
8542
- const q = $("agentFilter").value.trim().toLowerCase();
8543
- document.body.classList.toggle("filtering", !!q);
8544
- for (const t of tabs) {
8545
- const hit = !q || (t.nameEl ? t.nameEl.textContent : "").toLowerCase().includes(q);
8546
- t.btnEl.classList.toggle("filtered-out", !hit);
8547
- }
8548
- }
8549
- $("agentFilter").addEventListener("input", applyAgentFilter);
8550
- $("agentFilter").addEventListener("keydown", (e) => {
8551
- if (e.key === "Escape") { e.target.value = ""; applyAgentFilter(); e.target.blur(); }
8552
- if (e.key === "Enter") {
8553
- const first = tabs.find((t) => !t.btnEl.classList.contains("filtered-out"));
8554
- if (first) { activate(first); e.target.value = ""; applyAgentFilter(); }
8555
- }
8556
- });
8557
-
8558
- // Channel tabs: Chat leaves the terminal and the diff; Changes toggles the diff.
8559
- $("chanChat").addEventListener("click", () => {
8560
- if (chatPane.classList.contains("terminal-mode")) $("chanTerm").click();
8561
- if ($("diffpanel").classList.contains("open")) $("toggleDiff").click();
8562
- });
8563
- $("chanDiff").addEventListener("click", () => $("toggleDiff").click());
8564
- const paintChanTabs = () => {
8565
- const term = chatPane.classList.contains("terminal-mode");
8566
- const diff = $("diffpanel").classList.contains("open");
8567
- $("chanChat").classList.toggle("on", !term && !diff);
8568
- $("chanDiff").classList.toggle("on", diff);
8569
- };
8570
- new MutationObserver(paintChanTabs).observe(chatPane, { attributes: true, attributeFilter: ["class"] });
8571
- new MutationObserver(paintChanTabs).observe($("diffpanel"), { attributes: true, attributeFilter: ["class"] });
8572
- paintChanTabs();
8573
-
8574
8456
  // ── Diff panel ──────────────────────────────────────────
8575
8457
  function colorDiff(text) {
8576
8458
  const esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");