shadok-ai 0.2.75 → 0.2.76

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shadok-ai",
3
- "version": "0.2.75",
3
+ "version": "0.2.76",
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
@@ -1655,6 +1655,7 @@
1655
1655
  <button id="profSwitchClose" title="Close">✕</button>
1656
1656
  </div>
1657
1657
  <p class="sec-note">Applying re-spawns the agent in place. Its history is kept and the conversation continues.</p>
1658
+ <p class="sec-note warn" id="profSwitchManaged" hidden></p>
1658
1659
  <div id="profSwitchGrid" role="radiogroup" aria-label="Agent profile"></div>
1659
1660
  </div>
1660
1661
  </div>
@@ -1904,8 +1905,7 @@
1904
1905
  profileBadges,
1905
1906
  defaultAgentName,
1906
1907
  hasReadonlyPreset,
1907
- applyReadonlyPreset,
1908
- } from "/profile-card.js";
1908
+ applyReadonlyPreset, isManagedProfile } from "/profile-card.js";
1909
1909
  import { notifyState, BLINK_MS } from "/notify.js";
1910
1910
  import { echoAuthor } from "/echo-author.js";
1911
1911
  import { dialPos, dialColor, arcSegments, dialTitle, SWEEP_DEG } from "/gauge-dial.js";
@@ -1918,6 +1918,7 @@
1918
1918
  window.profileBlurb = profileBlurb;
1919
1919
  window.profileBadges = profileBadges;
1920
1920
  window.defaultAgentName = defaultAgentName;
1921
+ window.isManagedProfile = isManagedProfile;
1921
1922
  window.hasReadonlyPreset = hasReadonlyPreset;
1922
1923
  window.applyReadonlyPreset = applyReadonlyPreset;
1923
1924
  window.echoAuthor = echoAuthor;
@@ -2294,12 +2295,18 @@
2294
2295
  profSwitchTab = tab;
2295
2296
  $("profSwitchWho").textContent = tab.nameEl ? tab.nameEl.textContent : "agent";
2296
2297
  await loadProfilesInto(); // la liste a pu changer depuis le dernier passage
2298
+ const onManaged = managedProfile(tab.profile);
2297
2299
  renderProfileCards(
2298
2300
  $("profSwitchGrid"),
2299
- tab.profile || "",
2301
+ onManaged ? null : tab.profile || "", // null : aucune carte ne ment
2300
2302
  (name) => askProfileSwitch(tab, name),
2301
2303
  false,
2302
2304
  );
2305
+ // Un agent qui TOURNE sous un rôle managé n'a plus de carte : sans ce mot,
2306
+ // rien n'indiquerait ce qui tourne réellement.
2307
+ const note = $("profSwitchManaged");
2308
+ note.hidden = !onManaged;
2309
+ if (onManaged) note.textContent = "Currently running " + tab.profile + " — a managed role, not offered as a choice. Picking another one replaces it.";
2303
2310
  $("profSwitchOverlay").hidden = false;
2304
2311
  }
2305
2312
 
@@ -5284,13 +5291,18 @@
5284
5291
  // carte ici serait une deuxième porte, subtilement différente, vers le même
5285
5292
  // agent. Il reste listé dans le panneau Profils, pour qu'on puisse tout de
5286
5293
  // même lui attacher un secret ou un modèle.
5287
- renderProfileCards(grid, selectedProfile, (n) => selectProfile(n, true), failed, "Shadok-Tweak");
5294
+ renderProfileCards(grid, selectedProfile, (n) => selectProfile(n, true), failed);
5288
5295
  }
5289
5296
 
5297
+ /** Garde alignée sur invariant 10 : le pont ESM s'exécute après le parsing.
5298
+ * S'il n'est pas encore là, on ne filtre pas — un rôle de trop est moins
5299
+ * grave qu'une exception qui viderait la liste en silence. */
5300
+ const managedProfile = (name) => (window.isManagedProfile ? window.isManagedProfile(name) : false);
5301
+
5290
5302
  /** Peint les cartes de profil dans `container`. Générique : la box de création
5291
5303
  * ET le sélecteur du menu d'onglet s'en servent — la logique de carte (blurb,
5292
5304
  * badges, crayon, clavier) ne vit qu'ici, en un seul exemplaire. */
5293
- function renderProfileCards(container, selected, onPick, failed, exclude) {
5305
+ function renderProfileCards(container, selected, onPick, failed) {
5294
5306
  container.innerHTML = "";
5295
5307
  if (failed) {
5296
5308
  const note = document.createElement("div");
@@ -5298,11 +5310,13 @@
5298
5310
  note.textContent = "couldn't load profiles";
5299
5311
  container.appendChild(note);
5300
5312
  }
5301
- // `exclude` sert à la box de création, qui masque le rôle « tweak » (il a
5302
- // son propre CTA). Surtout PAS ici pour tout le monde : le sélecteur du menu
5303
- // d'onglet montre le profil COURANT comme sélectionné, et un agent tweak n'y
5304
- // trouverait plus son propre rôle.
5305
- const list = profileCache.filter((p) => p && p.name !== exclude);
5313
+ // Les rôles pilotés par le serveur ne sont JAMAIS un choix : leur prompt est
5314
+ // réécrit à chaque boot, les proposer promet une personnalisation qui
5315
+ // s'effacera. Filtré ici pour toutes les listes, plutôt que par un paramètre
5316
+ // qu'un futur appelant oublierait — c'est ce qui était arrivé au sélecteur
5317
+ // du menu d'onglet. L'agent qui TOURNE sous un tel rôle est annoncé à part
5318
+ // (voir openProfileSwitcher), sinon la grille mentirait sur ce qui tourne.
5319
+ const list = profileCache.filter((p) => p && !managedProfile(p.name));
5306
5320
  // Zéro profil : on montre la porte d'entrée plutôt qu'un « (none) » muet.
5307
5321
  // Pas de role="radio" → ignorée par la sélection et par les flèches.
5308
5322
  if (!failed && !list.length) {
@@ -5383,6 +5397,19 @@
5383
5397
  * nom réellement retenu — "" si le profil visé n'existe plus. */
5384
5398
  function markProfileSelection(container, selected) {
5385
5399
  const cards = [...container.querySelectorAll('.profile-card[role="radio"]')];
5400
+ // `null` = ne rien cocher, délibérément. Un agent qui tourne sous un rôle
5401
+ // managé n'a pas de carte : retomber sur "" cocherait « No profile » et la
5402
+ // grille affirmerait qu'il tourne sans profil. Une chaîne, elle, garde le
5403
+ // repli sur « No profile » — c'est le défaut légitime de la box.
5404
+ if (selected === null) {
5405
+ for (const c of cards) {
5406
+ c.classList.remove("selected");
5407
+ c.setAttribute("aria-checked", "false");
5408
+ c.tabIndex = -1;
5409
+ }
5410
+ if (cards.length) cards[0].tabIndex = 0; // le groupe reste atteignable au clavier
5411
+ return null;
5412
+ }
5386
5413
  let on = selected || "";
5387
5414
  if (!cards.some((c) => c.dataset.profile === on)) on = "";
5388
5415
  for (const c of cards) {
@@ -5441,8 +5468,12 @@
5441
5468
  function renderProfilesList() {
5442
5469
  const ul = $("profilesList");
5443
5470
  ul.innerHTML = "";
5444
- if (!profileCache.length) ul.innerHTML = '<li><span style="opacity:.6">no profiles yet</span></li>';
5445
- for (const p of profileCache) {
5471
+ // Masqués ici aussi : leur prompt est repris du dépôt à chaque boot, donc
5472
+ // « edit » y perdrait la saisie sans un mot, et « delete » ne tiendrait pas
5473
+ // le redémarrage.
5474
+ const listed = profileCache.filter((p) => p && !managedProfile(p.name));
5475
+ if (!listed.length) ul.innerHTML = '<li><span style="opacity:.6">no profiles yet</span></li>';
5476
+ for (const p of listed) {
5446
5477
  const li = document.createElement("li");
5447
5478
  const meta = document.createElement("div");
5448
5479
  // Nom et modèle en textContent : un profil peut être créé par un agent
@@ -107,3 +107,16 @@ export function applyReadonlyPreset(deny, on, preset) {
107
107
  for (const x of p) if (!have.has(x)) list.push(x);
108
108
  return list;
109
109
  }
110
+
111
+ /**
112
+ * Rôles pilotés par le SERVEUR, qui n'ont rien à faire dans une liste où l'on
113
+ * choisit un profil. `Shadok-Tweak` reprend son prompt de
114
+ * `context/tweak-prompt.md` à chaque boot et possède son propre CTA : l'offrir
115
+ * comme un rôle ordinaire promet une personnalisation que le prochain
116
+ * redémarrage effacera.
117
+ */
118
+ export const MANAGED_PROFILES = ["Shadok-Tweak"];
119
+
120
+ export function isManagedProfile(name) {
121
+ return MANAGED_PROFILES.includes(String(name ?? "").trim());
122
+ }