shadok-ai 0.2.17 → 0.2.18

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
@@ -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 click
67
- clones shadok-ai's own source into `~/.shadok-ai/self/shadok-ai`, starts an
68
- agent on it in its own worktree, and that agent delivers its change as a
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. Desktop only the agents column is
74
- hidden on phones.
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)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shadok-ai",
3
- "version": "0.2.17",
3
+ "version": "0.2.18",
4
4
  "main": "dist/session.js",
5
5
  "scripts": {
6
6
  "test": "node --import tsx --test test/*.ts .claude/skills/shadok-ai-agents/test/*.test.mjs",
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
- #tweakCta {
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
- /* Empilé sur le fond de la colonne, PAS `var(--amber-soft)` seul : la carte
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
- <button id="tweakCta" type="button"
1415
- title="Change shadok-ai itself — the agent works on a branch and opens a pull request">
1416
- <span class="tweak-title">🛠 Tweak Shadok-AI <span class="tweak-arrow">›</span></span>
1417
- <span class="tweak-sub">change the cockpit itself</span>
1418
- </button>
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
- if (overTab && overTab !== dragTabEl) {
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);
@@ -4598,6 +4617,13 @@
4598
4617
  // Un clic démarre un agent sur le code source de shadok-ai lui-même : le
4599
4618
  // dossier, le worktree et le rôle sont tous déjà décidés, donc un formulaire
4600
4619
  // ne ferait que demander d'approuver des choix qu'on ne peut pas juger.
4620
+ //
4621
+ // Le canal tweak est UNIQUE et vit dans son emplacement en bas de colonne : le
4622
+ // CTA n'est visible que tant qu'il n'existe pas. Une fois lancé, son onglet
4623
+ // prend la place du CTA — un `.tab` ordinaire simplement déplacé là, donc il
4624
+ // hérite sans code en double des états, du menu contextuel, du rattachement
4625
+ // Telegram, du renommage et de la fermeture.
4626
+ const tweakSlot = $("tweakSlot");
4601
4627
  const tweakCta = $("tweakCta");
4602
4628
  const tweakTitle = tweakCta.querySelector(".tweak-title");
4603
4629
  const tweakSub = tweakCta.querySelector(".tweak-sub");
@@ -4619,8 +4645,26 @@
4619
4645
  }
4620
4646
  }
4621
4647
 
4648
+ /** L'onglet du canal tweak, s'il existe. */
4649
+ function tweakTab() { return tabs.find((t) => t.isTweak); }
4650
+
4651
+ /** Déplace l'onglet dans l'emplacement du bas et masque le CTA : à partir de
4652
+ * là, c'est LUI qu'on voit et qu'on clique. `appendChild` déplace le nœud,
4653
+ * donc l'onglet quitte la liste de gauche sans être recréé. */
4654
+ function markTweakTab(t) {
4655
+ t.isTweak = true;
4656
+ t.btnEl.classList.add("tweak");
4657
+ t.btnEl.draggable = false; // il n'a pas à finir dans un groupe
4658
+ tweakSlot.appendChild(t.btnEl);
4659
+ tweakCta.hidden = true;
4660
+ }
4661
+
4622
4662
  tweakCta.addEventListener("click", async () => {
4623
4663
  if (tweakBusy) return;
4664
+ // Ceinture : le CTA est masqué dès qu'un canal tweak existe, mais s'il
4665
+ // réapparaissait on active l'existant plutôt que d'en ouvrir un second.
4666
+ const already = tweakTab();
4667
+ if (already) return activate(already);
4624
4668
  tweakBusy = true;
4625
4669
  setTweakCta("busy");
4626
4670
  let data;
@@ -4637,6 +4681,7 @@
4637
4681
  t.customName = true; // launchTab ne nomme qu'un onglet qui n'a pas de nom
4638
4682
  t.nameEl.textContent = "tweak";
4639
4683
  t.profile = "Shadok-Tweak";
4684
+ markTweakTab(t);
4640
4685
  launchTab(t, { cwd: data.cwd, worktree: true, profile: "Shadok-Tweak" });
4641
4686
  });
4642
4687
  newGroupBtn.addEventListener("click", () => {
@@ -4775,6 +4820,8 @@
4775
4820
  t.draft = savedDrafts[s.sessionId] || "";
4776
4821
  if (s.branch) t.branch = s.branch;
4777
4822
  t.profile = s.profile || null;
4823
+ // Au rechargement, le canal tweak retrouve son emplacement du bas.
4824
+ if (t.profile === "Shadok-Tweak" && !tweakTab()) markTweakTab(t);
4778
4825
  setCrons(t, s.crons);
4779
4826
  if (s.muted) setMuted(t, true, false); // restoring: don't push back
4780
4827
  // The mirror TOO, otherwise the first persist pushes `mirror:false`