shadok-ai 0.2.17 → 0.2.19
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 +28 -5
- package/package.json +1 -1
- package/public/index.html +70 -18
package/README.md
CHANGED
|
@@ -63,15 +63,17 @@ Enter to skip; you can add it later from the web UI).
|
|
|
63
63
|
the chat can't express. With tmux there is also an **experimental real
|
|
64
64
|
terminal** (xterm.js over the pane's byte stream) when a snapshot isn't enough.
|
|
65
65
|
- **Diff panel** — what an agent actually changed, against its base.
|
|
66
|
-
- **Tweak Shadok-AI** — a card at the bottom of the agents column. One
|
|
67
|
-
clones shadok-ai's own source into `~/.shadok-ai/self/shadok-ai
|
|
68
|
-
agent on it in its own worktree
|
|
66
|
+
- **Tweak Shadok-AI** — a card pinned at the bottom of the agents column. One
|
|
67
|
+
click clones shadok-ai's own source into `~/.shadok-ai/self/shadok-ai` and
|
|
68
|
+
starts an agent on it in its own worktree; that agent delivers its change as a
|
|
69
69
|
**pull request** — a fork under your GitHub account, since you need no rights
|
|
70
70
|
on the repo. Nothing to configure and no token to paste: the clone is
|
|
71
71
|
anonymous, so you describe an idea, watch it work and read the diff first, and
|
|
72
72
|
it only asks for GitHub (via `gh auth login`, device code relayed in the chat)
|
|
73
|
-
when there is something worth pushing.
|
|
74
|
-
|
|
73
|
+
when there is something worth pushing. It is **one channel, not a launcher**:
|
|
74
|
+
the card becomes that channel's tab, with the usual menu — mute, reload,
|
|
75
|
+
rename, change profile, mirror to Telegram, close — and closing it brings the
|
|
76
|
+
card back. Desktop only: the agents column is hidden on phones.
|
|
75
77
|
- **Self-update** — polls npm and can update and reload itself in place.
|
|
76
78
|
|
|
77
79
|
### Telegram (optional)
|
|
@@ -122,6 +124,27 @@ the same-origin policy, so without that check any page you happen to visit could
|
|
|
122
124
|
drive your agents. Behind a reverse proxy that rewrites `Host`, list the public
|
|
123
125
|
origin in `SHADOK_ORIGINS`.
|
|
124
126
|
|
|
127
|
+
### Behind TLS (nginx, Caddy…)
|
|
128
|
+
|
|
129
|
+
Serving the cockpit over HTTPS works, with one requirement: **the proxy must
|
|
130
|
+
forward the WebSocket upgrade**. Everything live — the channel list included —
|
|
131
|
+
travels over `/ws`, and the page itself is static HTML, so a proxy that drops
|
|
132
|
+
`Upgrade`/`Connection` produces a cockpit that loads perfectly and then never
|
|
133
|
+
connects. In nginx:
|
|
134
|
+
|
|
135
|
+
```nginx
|
|
136
|
+
location / {
|
|
137
|
+
proxy_pass http://127.0.0.1:3789;
|
|
138
|
+
proxy_http_version 1.1;
|
|
139
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
140
|
+
proxy_set_header Connection "upgrade";
|
|
141
|
+
proxy_set_header Host $host;
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The client picks `wss://` on its own when the page is HTTPS, so there is nothing
|
|
146
|
+
to configure on that side.
|
|
147
|
+
|
|
125
148
|
### SSH identity in Docker
|
|
126
149
|
|
|
127
150
|
When shadok-ai runs **in a container** it gives itself an SSH key on first boot
|
package/package.json
CHANGED
package/public/index.html
CHANGED
|
@@ -374,10 +374,27 @@
|
|
|
374
374
|
bordure et les + des boutons fantômes, donc une carte pleine se lit comme
|
|
375
375
|
une action et non comme une session. Épinglée en bas (margin-top:auto +
|
|
376
376
|
sticky) pour rester atteignable quand la liste des agents défile. */
|
|
377
|
-
|
|
377
|
+
/* L'emplacement réservé au canal « tweak », épinglé en bas de la colonne. Il
|
|
378
|
+
porte tantôt le CTA (aucune session), tantôt l'ONGLET du canal lui-même —
|
|
379
|
+
qui est un `.tab` ordinaire déplacé ici, donc états, menu contextuel et
|
|
380
|
+
rattachement Telegram marchent sans code en double. */
|
|
381
|
+
#tweakSlot {
|
|
378
382
|
margin-top: auto;
|
|
379
383
|
position: sticky;
|
|
380
384
|
bottom: 0;
|
|
385
|
+
padding-top: 8px;
|
|
386
|
+
background: var(--bg-raised);
|
|
387
|
+
/* Sépare de la liste qui défile dessous, sans ajouter de filet. */
|
|
388
|
+
box-shadow: 0 -8px 10px -8px rgba(0, 0, 0, 0.6);
|
|
389
|
+
}
|
|
390
|
+
/* L'onglet du canal tweak garde la teinte de son emplacement, pour qu'on le
|
|
391
|
+
reconnaisse au même endroit qu'avant de l'avoir démarré. */
|
|
392
|
+
#tweakSlot .tab {
|
|
393
|
+
border: 1px solid var(--amber);
|
|
394
|
+
background: linear-gradient(var(--amber-soft), var(--amber-soft)), var(--bg-raised);
|
|
395
|
+
}
|
|
396
|
+
#tweakSlot .tab .name { color: var(--amber); }
|
|
397
|
+
#tweakCta {
|
|
381
398
|
display: flex;
|
|
382
399
|
flex-direction: column;
|
|
383
400
|
gap: 2px;
|
|
@@ -385,13 +402,8 @@
|
|
|
385
402
|
padding: 8px;
|
|
386
403
|
border: 1px solid var(--amber);
|
|
387
404
|
border-radius: 6px;
|
|
388
|
-
|
|
389
|
-
est sticky, et un fond translucide laisserait les onglets défiler au
|
|
390
|
-
travers. */
|
|
405
|
+
width: 100%;
|
|
391
406
|
background: linear-gradient(var(--amber-soft), var(--amber-soft)), var(--bg-raised);
|
|
392
|
-
/* Ombre douce vers le haut : sépare la carte de la liste qui passe dessous,
|
|
393
|
-
sans ajouter de filet qui doublerait la bordure. */
|
|
394
|
-
box-shadow: 0 -8px 10px -8px rgba(0, 0, 0, 0.6);
|
|
395
407
|
}
|
|
396
408
|
#tweakCta .tweak-title {
|
|
397
409
|
font-family: var(--mono);
|
|
@@ -404,10 +416,7 @@
|
|
|
404
416
|
/* Aucune animation permanente : un élément qui clignote ici entrerait en
|
|
405
417
|
concurrence avec les vrais signaux (onglet en attente, jauges) et
|
|
406
418
|
deviendrait du bruit. */
|
|
407
|
-
#tweakCta:hover {
|
|
408
|
-
border-color: var(--amber);
|
|
409
|
-
box-shadow: 0 -8px 10px -8px rgba(0, 0, 0, 0.6), 0 0 12px rgba(240, 168, 72, 0.18);
|
|
410
|
-
}
|
|
419
|
+
#tweakCta:hover { border-color: var(--amber); box-shadow: 0 0 12px rgba(240, 168, 72, 0.18); }
|
|
411
420
|
#tweakCta:hover .tweak-arrow { transform: translateX(2px); }
|
|
412
421
|
#tweakCta.busy { opacity: 0.7; cursor: default; }
|
|
413
422
|
#tweakCta.failed { border-color: var(--err); background: transparent; }
|
|
@@ -1411,11 +1420,13 @@
|
|
|
1411
1420
|
<div id="groups"></div>
|
|
1412
1421
|
<button id="newTab" title="New agent">+ new agent</button>
|
|
1413
1422
|
<button id="newGroup" title="New agent group">+ new group</button>
|
|
1414
|
-
<
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1423
|
+
<div id="tweakSlot">
|
|
1424
|
+
<button id="tweakCta" type="button"
|
|
1425
|
+
title="Change shadok-ai itself — the agent works on a branch and opens a pull request">
|
|
1426
|
+
<span class="tweak-title">🛠 Tweak Shadok-AI <span class="tweak-arrow">›</span></span>
|
|
1427
|
+
<span class="tweak-sub">change the cockpit itself</span>
|
|
1428
|
+
</button>
|
|
1429
|
+
</div>
|
|
1419
1430
|
</nav>
|
|
1420
1431
|
|
|
1421
1432
|
<section class="chat-pane">
|
|
@@ -2209,7 +2220,9 @@
|
|
|
2209
2220
|
e.dataTransfer.dropEffect = "move";
|
|
2210
2221
|
if (dragTabEl) {
|
|
2211
2222
|
const overTab = e.target.closest(".tab");
|
|
2212
|
-
|
|
2223
|
+
// L'emplacement du bas n'accueille QUE le canal tweak : sans ce garde, un
|
|
2224
|
+
// onglet traîné au-dessus s'y insérerait et se retrouverait épinglé là.
|
|
2225
|
+
if (overTab && overTab !== dragTabEl && !overTab.classList.contains("tweak")) {
|
|
2213
2226
|
const r = overTab.getBoundingClientRect();
|
|
2214
2227
|
const after = e.clientY > r.top + r.height / 2;
|
|
2215
2228
|
overTab.parentNode.insertBefore(dragTabEl, after ? overTab.nextSibling : overTab);
|
|
@@ -2330,6 +2343,9 @@
|
|
|
2330
2343
|
if (tab.reconnectTimer) clearTimeout(tab.reconnectTimer);
|
|
2331
2344
|
if (tab.ws && tab.ws.readyState === WebSocket.OPEN) tab.ws.close();
|
|
2332
2345
|
dropDraft(tab);
|
|
2346
|
+
// Le canal tweak fermé, son emplacement redevient le CTA — sinon le bas de
|
|
2347
|
+
// la colonne resterait vide et la fonctionnalité disparaîtrait sans retour.
|
|
2348
|
+
if (tab.isTweak) document.getElementById("tweakCta").hidden = false;
|
|
2333
2349
|
tab.btnEl.remove();
|
|
2334
2350
|
tab.transcriptEl.remove();
|
|
2335
2351
|
tabs = tabs.filter((t) => t !== tab);
|
|
@@ -2383,6 +2399,9 @@
|
|
|
2383
2399
|
nt.draft = savedDrafts[c.sessionId] || "";
|
|
2384
2400
|
if (c.branch) nt.branch = c.branch;
|
|
2385
2401
|
nt.profile = c.profile || null;
|
|
2402
|
+
// Un canal tweak né ailleurs (autre appareil, Telegram) reprend sa place
|
|
2403
|
+
// en bas plutôt que d'apparaître comme un agent ordinaire de la liste.
|
|
2404
|
+
if (nt.profile === "Shadok-Tweak" && !tweakTab()) markTweakTab(nt);
|
|
2386
2405
|
setCrons(nt, c.crons);
|
|
2387
2406
|
if (c.muted) setMuted(nt, true, false);
|
|
2388
2407
|
if (typeof c.mirror === "boolean" ? c.mirror : !!c.telegram) setMirror(nt, true, false);
|
|
@@ -3139,7 +3158,12 @@
|
|
|
3139
3158
|
* of stranding the tab on "link lost". */
|
|
3140
3159
|
function openLink(t) {
|
|
3141
3160
|
setTabState(t, "connecting", t.reconnectAttempts ? "reconnecting…" : "connecting…");
|
|
3142
|
-
|
|
3161
|
+
// The scheme MUST follow the page's. On an HTTPS page — the cockpit behind an
|
|
3162
|
+
// nginx/TLS reverse proxy — a `ws://` socket is blocked as mixed content, and
|
|
3163
|
+
// the failure is invisible: the page is static HTML so it paints normally,
|
|
3164
|
+
// while every channel stays stuck "connecting…" forever. Never hardcode `ws://`.
|
|
3165
|
+
const scheme = location.protocol === "https:" ? "wss:" : "ws:";
|
|
3166
|
+
const ws = new WebSocket(`${scheme}//${location.host}/ws`);
|
|
3143
3167
|
t.ws = ws;
|
|
3144
3168
|
ws.addEventListener("open", () => {
|
|
3145
3169
|
t.reconnectAttempts = 0;
|
|
@@ -4598,6 +4622,13 @@
|
|
|
4598
4622
|
// Un clic démarre un agent sur le code source de shadok-ai lui-même : le
|
|
4599
4623
|
// dossier, le worktree et le rôle sont tous déjà décidés, donc un formulaire
|
|
4600
4624
|
// ne ferait que demander d'approuver des choix qu'on ne peut pas juger.
|
|
4625
|
+
//
|
|
4626
|
+
// Le canal tweak est UNIQUE et vit dans son emplacement en bas de colonne : le
|
|
4627
|
+
// CTA n'est visible que tant qu'il n'existe pas. Une fois lancé, son onglet
|
|
4628
|
+
// prend la place du CTA — un `.tab` ordinaire simplement déplacé là, donc il
|
|
4629
|
+
// hérite sans code en double des états, du menu contextuel, du rattachement
|
|
4630
|
+
// Telegram, du renommage et de la fermeture.
|
|
4631
|
+
const tweakSlot = $("tweakSlot");
|
|
4601
4632
|
const tweakCta = $("tweakCta");
|
|
4602
4633
|
const tweakTitle = tweakCta.querySelector(".tweak-title");
|
|
4603
4634
|
const tweakSub = tweakCta.querySelector(".tweak-sub");
|
|
@@ -4619,8 +4650,26 @@
|
|
|
4619
4650
|
}
|
|
4620
4651
|
}
|
|
4621
4652
|
|
|
4653
|
+
/** L'onglet du canal tweak, s'il existe. */
|
|
4654
|
+
function tweakTab() { return tabs.find((t) => t.isTweak); }
|
|
4655
|
+
|
|
4656
|
+
/** Déplace l'onglet dans l'emplacement du bas et masque le CTA : à partir de
|
|
4657
|
+
* là, c'est LUI qu'on voit et qu'on clique. `appendChild` déplace le nœud,
|
|
4658
|
+
* donc l'onglet quitte la liste de gauche sans être recréé. */
|
|
4659
|
+
function markTweakTab(t) {
|
|
4660
|
+
t.isTweak = true;
|
|
4661
|
+
t.btnEl.classList.add("tweak");
|
|
4662
|
+
t.btnEl.draggable = false; // il n'a pas à finir dans un groupe
|
|
4663
|
+
tweakSlot.appendChild(t.btnEl);
|
|
4664
|
+
tweakCta.hidden = true;
|
|
4665
|
+
}
|
|
4666
|
+
|
|
4622
4667
|
tweakCta.addEventListener("click", async () => {
|
|
4623
4668
|
if (tweakBusy) return;
|
|
4669
|
+
// Ceinture : le CTA est masqué dès qu'un canal tweak existe, mais s'il
|
|
4670
|
+
// réapparaissait on active l'existant plutôt que d'en ouvrir un second.
|
|
4671
|
+
const already = tweakTab();
|
|
4672
|
+
if (already) return activate(already);
|
|
4624
4673
|
tweakBusy = true;
|
|
4625
4674
|
setTweakCta("busy");
|
|
4626
4675
|
let data;
|
|
@@ -4637,6 +4686,7 @@
|
|
|
4637
4686
|
t.customName = true; // launchTab ne nomme qu'un onglet qui n'a pas de nom
|
|
4638
4687
|
t.nameEl.textContent = "tweak";
|
|
4639
4688
|
t.profile = "Shadok-Tweak";
|
|
4689
|
+
markTweakTab(t);
|
|
4640
4690
|
launchTab(t, { cwd: data.cwd, worktree: true, profile: "Shadok-Tweak" });
|
|
4641
4691
|
});
|
|
4642
4692
|
newGroupBtn.addEventListener("click", () => {
|
|
@@ -4775,6 +4825,8 @@
|
|
|
4775
4825
|
t.draft = savedDrafts[s.sessionId] || "";
|
|
4776
4826
|
if (s.branch) t.branch = s.branch;
|
|
4777
4827
|
t.profile = s.profile || null;
|
|
4828
|
+
// Au rechargement, le canal tweak retrouve son emplacement du bas.
|
|
4829
|
+
if (t.profile === "Shadok-Tweak" && !tweakTab()) markTweakTab(t);
|
|
4778
4830
|
setCrons(t, s.crons);
|
|
4779
4831
|
if (s.muted) setMuted(t, true, false); // restoring: don't push back
|
|
4780
4832
|
// The mirror TOO, otherwise the first persist pushes `mirror:false`
|