shadok-ai 0.2.30 → 0.2.32
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 +1 -1
- package/public/index.html +48 -12
- package/public/notify.js +17 -9
package/package.json
CHANGED
package/public/index.html
CHANGED
|
@@ -1308,10 +1308,6 @@
|
|
|
1308
1308
|
<span class="label">Name</span>
|
|
1309
1309
|
<input type="text" id="nameInput" placeholder="agent" autocomplete="off" spellcheck="false">
|
|
1310
1310
|
</div>
|
|
1311
|
-
<div class="field">
|
|
1312
|
-
<span class="label">Working directory</span>
|
|
1313
|
-
<input type="text" id="cwdInput" placeholder="/path/to/project">
|
|
1314
|
-
</div>
|
|
1315
1311
|
<label class="check" id="worktreeField">
|
|
1316
1312
|
<input type="checkbox" id="worktreeInput" checked>
|
|
1317
1313
|
<span>Isolate in a git worktree <span class="check-hint">— the agent edits a fresh branch; review & merge from the Diff panel. Auto-removed on close if unused, kept if it has changes. (new sessions only)</span></span>
|
|
@@ -1321,7 +1317,11 @@
|
|
|
1321
1317
|
<span>Mirror to Telegram <span class="check-hint">— gives this agent its own topic in the board group, so you can follow it and reply from your phone. Off by default; switchable later from the channel menu.</span></span>
|
|
1322
1318
|
</label>
|
|
1323
1319
|
<details class="fold" id="advancedField">
|
|
1324
|
-
<summary>Advanced — resume an existing session</summary>
|
|
1320
|
+
<summary>Advanced — working directory, resume an existing session</summary>
|
|
1321
|
+
<div class="field">
|
|
1322
|
+
<span class="label">Working directory <span class="check-hint">— defaults to where shadok-ai runs; point at another repo for a multi-project agent.</span></span>
|
|
1323
|
+
<input type="text" id="cwdInput" placeholder="/path/to/project">
|
|
1324
|
+
</div>
|
|
1325
1325
|
<div class="field">
|
|
1326
1326
|
<div class="radio-row">
|
|
1327
1327
|
<label><input type="radio" name="mode" value="new" checked> new session</label>
|
|
@@ -2495,11 +2495,22 @@
|
|
|
2495
2495
|
pip + `</svg>`;
|
|
2496
2496
|
return "data:image/svg+xml," + encodeURIComponent(svg);
|
|
2497
2497
|
}
|
|
2498
|
+
/** Favicon « en train de bosser » : la marque + un arc qui tourne (angle animé). */
|
|
2499
|
+
function faviconWorkingSVG(angle) {
|
|
2500
|
+
const svg =
|
|
2501
|
+
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">` +
|
|
2502
|
+
`<rect width="32" height="32" rx="7" fill="#14161d"/>` +
|
|
2503
|
+
`<text x="15" y="23" font-family="monospace" font-size="20" fill="#f0a848" text-anchor="middle">›</text>` +
|
|
2504
|
+
`<g transform="rotate(${angle} 24 8)"><circle cx="24" cy="8" r="6" fill="none" stroke="#f0a848" stroke-width="2.5" stroke-linecap="round" stroke-dasharray="26 40"/></g>` +
|
|
2505
|
+
`</svg>`;
|
|
2506
|
+
return "data:image/svg+xml," + encodeURIComponent(svg);
|
|
2507
|
+
}
|
|
2498
2508
|
/** What each channel is asking for, as the pure module wants it. */
|
|
2499
2509
|
function attentionInput() {
|
|
2500
2510
|
return tabs.map((t) => ({
|
|
2501
2511
|
mood: t.btnEl.classList.contains("needs-answer") ? "needs-answer"
|
|
2502
|
-
: t.btnEl.classList.contains("unread") ? "unread"
|
|
2512
|
+
: t.btnEl.classList.contains("unread") ? "unread"
|
|
2513
|
+
: t.btnEl.classList.contains("working") ? "working" : null,
|
|
2503
2514
|
muted: !!t.muted,
|
|
2504
2515
|
}));
|
|
2505
2516
|
}
|
|
@@ -2508,10 +2519,14 @@
|
|
|
2508
2519
|
// costs no timer at all.
|
|
2509
2520
|
let blinkTimer = null;
|
|
2510
2521
|
let blinkPhase = 0;
|
|
2522
|
+
let workTimer = null; // anime le favicon « working » (spinner)
|
|
2523
|
+
let workAngle = 0;
|
|
2524
|
+
function stopWork() { if (workTimer) { clearInterval(workTimer); workTimer = null; } }
|
|
2511
2525
|
function paint() {
|
|
2512
2526
|
// The ESM bridge lands after the document is parsed (gotcha #10): until it
|
|
2513
2527
|
// does, keep the plain badge rather than throwing on the first paint.
|
|
2514
2528
|
if (!window.notifyState) {
|
|
2529
|
+
stopWork();
|
|
2515
2530
|
favicon.href = faviconSVG(null);
|
|
2516
2531
|
document.title = titleBase;
|
|
2517
2532
|
return;
|
|
@@ -2524,8 +2539,21 @@
|
|
|
2524
2539
|
away: document.hidden || !document.hasFocus(),
|
|
2525
2540
|
phase: blinkPhase,
|
|
2526
2541
|
});
|
|
2527
|
-
favicon.href = faviconSVG(s.color);
|
|
2528
2542
|
document.title = s.badge + titleBase;
|
|
2543
|
+
// « working » = favicon animé (spinner). Le module ne renvoie ce mode que si
|
|
2544
|
+
// RIEN de plus prioritaire (bloqué / non-lu) n'attend — la priorité est donc
|
|
2545
|
+
// gérée en amont : un agent qui attend gagne toujours sur un agent qui bosse.
|
|
2546
|
+
if (s.mode === "working") {
|
|
2547
|
+
if (blinkTimer) { clearInterval(blinkTimer); blinkTimer = null; blinkPhase = 0; }
|
|
2548
|
+
if (!workTimer) {
|
|
2549
|
+
const tick = () => { workAngle = (workAngle + 30) % 360; favicon.href = faviconWorkingSVG(workAngle); };
|
|
2550
|
+
tick();
|
|
2551
|
+
workTimer = setInterval(tick, 120);
|
|
2552
|
+
}
|
|
2553
|
+
return;
|
|
2554
|
+
}
|
|
2555
|
+
stopWork();
|
|
2556
|
+
favicon.href = faviconSVG(s.color);
|
|
2529
2557
|
if (s.blink && !blinkTimer) {
|
|
2530
2558
|
blinkTimer = setInterval(() => { blinkPhase ^= 1; paint(); }, window.BLINK_MS || 900);
|
|
2531
2559
|
} else if (!s.blink && blinkTimer) {
|
|
@@ -3323,10 +3351,16 @@
|
|
|
3323
3351
|
* à la place de la conversation qu'on regardait.
|
|
3324
3352
|
*/
|
|
3325
3353
|
function openSetup() {
|
|
3326
|
-
//
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3354
|
+
// Défauts PRÉVISIBLES à chaque ouverture. Le navigateur restaure l'état des
|
|
3355
|
+
// champs de formulaire (un mode "continue"/"resume" choisi une fois, une case
|
|
3356
|
+
// worktree décochée, un cwd de worktree) — c'est ce qui faisait retomber
|
|
3357
|
+
// « créer un agent » dans le répertoire de base, en silence. On réaffirme le
|
|
3358
|
+
// défaut : session NEUVE, worktree isolé, depuis la base.
|
|
3359
|
+
const newRadio = document.querySelector('input[name="mode"][value="new"]');
|
|
3360
|
+
newRadio.checked = true;
|
|
3361
|
+
newRadio.dispatchEvent(new Event("change")); // réactive worktree/profil, cache le resume
|
|
3362
|
+
$("worktreeInput").checked = true;
|
|
3363
|
+
$("cwdInput").value = serverCwd || "";
|
|
3330
3364
|
const last = localStorage.getItem("cp.session");
|
|
3331
3365
|
if (last && !$("resumeInput").value) $("resumeInput").value = last;
|
|
3332
3366
|
$("startBtn").disabled = false;
|
|
@@ -3341,7 +3375,9 @@
|
|
|
3341
3375
|
refreshRecoverList();
|
|
3342
3376
|
refreshLiveList();
|
|
3343
3377
|
$("setupOverlay").hidden = false;
|
|
3344
|
-
|
|
3378
|
+
// Le cwd est désormais dans le fold « Advanced » (replié) : on focus le nom,
|
|
3379
|
+
// premier champ du flux principal.
|
|
3380
|
+
$("nameInput").focus();
|
|
3345
3381
|
}
|
|
3346
3382
|
function closeSetup() { $("setupOverlay").hidden = true; }
|
|
3347
3383
|
|
package/public/notify.js
CHANGED
|
@@ -46,19 +46,27 @@ export function notifyState(channels, view) {
|
|
|
46
46
|
|
|
47
47
|
let blocked = false;
|
|
48
48
|
let unread = false;
|
|
49
|
+
let working = false;
|
|
49
50
|
for (const c of channels || []) {
|
|
50
51
|
if (!c || c.muted) continue; // un canal muté ne remonte aucun signal global
|
|
51
52
|
if (c.mood === "needs-answer") blocked = true;
|
|
52
53
|
else if (c.mood === "unread") unread = true;
|
|
54
|
+
else if (c.mood === "working") working = true;
|
|
53
55
|
}
|
|
54
56
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
57
|
+
// Priorité : « il y a quelque chose à faire » (bloqué) prime sur « quelque
|
|
58
|
+
// chose est arrivé » (non-lu), qui prime sur « ça bosse ». `mode` dit à
|
|
59
|
+
// l'appelant quel favicon dessiner (le mode "working" est ANIMÉ côté DOM).
|
|
60
|
+
if (blocked) {
|
|
61
|
+
// Seul un agent bloqué justifie de clignoter, et seulement si tu es ailleurs.
|
|
62
|
+
if (!away) return { color: RED, badge: "● ", blink: false, mode: "blocked" };
|
|
63
|
+
return phase
|
|
64
|
+
? { color: RED_DIM, badge: "◉ ", blink: true, mode: "blocked" }
|
|
65
|
+
: { color: RED, badge: "● ", blink: true, mode: "blocked" };
|
|
66
|
+
}
|
|
67
|
+
if (unread) return { color: AMBER, badge: "● ", blink: false, mode: "unread" };
|
|
68
|
+
// « working » n'a ni couleur de pip ni badge : le favicon est animé (spinner)
|
|
69
|
+
// par l'appelant tant que le mode vaut "working".
|
|
70
|
+
if (working) return { color: null, badge: "", blink: false, mode: "working" };
|
|
71
|
+
return { color: null, badge: "", blink: false, mode: null };
|
|
64
72
|
}
|