@double_geste/agent-standup 1.25.1 → 1.25.2
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 +35 -12
- package/public/locales/da.json +4 -1
- package/public/locales/de.json +4 -1
- package/public/locales/en.json +3 -0
- package/public/locales/es.json +4 -1
- package/public/locales/ja.json +4 -1
- package/public/locales/pt-BR.json +4 -1
- package/public/locales/zh-CN.json +4 -1
- package/public/locales/zh-TW.json +4 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@double_geste/agent-standup",
|
|
3
|
-
"version": "1.25.
|
|
3
|
+
"version": "1.25.2",
|
|
4
4
|
"description": "Your Claude Code agents as a live video call, from the terminal: a local page in your browser, plus one-line status for status bars. Free, open source, zero token cost.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"agent-standup": "bin/agent-standup.js"
|
package/public/index.html
CHANGED
|
@@ -713,7 +713,7 @@ const TTS = {
|
|
|
713
713
|
for (const l of ['fr', 'en']) this.pool[l] = rankVoices(d.voices.filter((v) => v.locale.startsWith(l + '_')).map((v) => v.name)); } catch {}
|
|
714
714
|
},
|
|
715
715
|
voiceFor(p, fr = FR()) { const pool = this.pool[fr ? 'fr' : 'en']; return pool.length ? (p.id === 'main' ? pool[0] : pool[(p.vi >>> 2) % pool.length]) : null; },
|
|
716
|
-
rate() { return Math.round(Math.max(120, Math.min(330, 178 * (S.live ? 1.12 * lagFactor() : +$('#speed').value))) / 10) * 10; },
|
|
716
|
+
rate() { return Math.round(Math.max(120, Math.min(330, 178 * (S.live ? 1.12 * lagFactor() * (S.boost > 1 ? 1.25 : 1) : +$('#speed').value))) / 10) * 10; },
|
|
717
717
|
buf(text, voice, rate) {
|
|
718
718
|
const key = this.engine + '|' + voice + '|' + rate + '|' + text;
|
|
719
719
|
if (!this.cache.has(key)) {
|
|
@@ -1250,6 +1250,7 @@ setInterval(() => { // now and then a busy agent has a little thought; never w
|
|
|
1250
1250
|
}, 7000);
|
|
1251
1251
|
function oops(id) { if (!prefs.banter || !S.playing || S.recomputing) return; const t = $('#t-' + id); if (!t || t.style.display === 'none' || (t._oops && Date.now() - t._oops < 4000)) return; t._oops = Date.now(); t.classList.remove('oops'); void t.offsetWidth; t.classList.add('oops'); thought(id, '😬'); }
|
|
1252
1252
|
function confetti() {
|
|
1253
|
+
S.wrap = true; // the lead is done: if the end of its last message was never read, it is read now (see wrapUp)
|
|
1253
1254
|
if (!prefs.banter || document.hidden) return; const box = $('#center'), cols = ['#f5c04a', '#7c9cff', '#ff7a6b', '#5ad19a', '#c58bff'];
|
|
1254
1255
|
for (let i = 0; i < 26; i++) { const c = document.createElement('i'); c.className = 'confetti'; c.style.left = Math.random() * 100 + '%'; c.style.background = cols[i % cols.length]; c.style.animationDelay = Math.random() * 0.6 + 's'; c.style.animationDuration = 1.8 + Math.random() * 1.4 + 's'; box.appendChild(c); setTimeout(() => c.remove(), 4200); }
|
|
1255
1256
|
}
|
|
@@ -1531,7 +1532,7 @@ async function react(ev, token) {
|
|
|
1531
1532
|
if ((!prefs.epi && !prefs.banter) || S.boost > 1 || S.hurry || !caughtUp()) return; const seed = hash(String(ev.ts) + ev.agent), R = REACT[FR() ? 'fr' : 'en']; let who, line, thanks = false;
|
|
1532
1533
|
if (ev.kind === 'order') { if (!prefs.banter && seed % 100 > 55) return; who = ev.to; line = prefs.banter ? ackLine(ev) : R.ack[seed % R.ack.length]; }
|
|
1533
1534
|
else if (ev.kind === 'report') {
|
|
1534
|
-
const r = seed % 100, joke = prefs.epi ? 60 : 35; // a remark from someone else, or (a little less often) a word of thanks from whoever receives the report
|
|
1535
|
+
const r = seed % 100, joke = prefs.epi ? 60 : prefs.chat === 'quiet' ? -1 : 35; // a remark from someone else, or (a little less often) a word of thanks from whoever receives the report
|
|
1535
1536
|
if (r <= joke) { const others = Object.keys(S.status).concat(['main']).filter((x) => x !== ev.agent && S.agents[x] && !S.gone.has(x)); who = others[(seed >>> 3) % others.length]; line = quipMem.get(baseText(ev, 160)) || R.done[seed % R.done.length]; }
|
|
1536
1537
|
else if (prefs.banter && r <= joke + 25 && ev.to !== 'user' && S.agents[ev.to] && !S.gone.has(ev.to)) { who = ev.to; line = varied('thanks', seed >>> 3); thanks = true; }
|
|
1537
1538
|
else return;
|
|
@@ -1560,10 +1561,18 @@ function varied(kind, seed) { const P = TALK[FR() ? 'fr' : 'en'][kind]; let line
|
|
|
1560
1561
|
const thinkerOf = (e) => (e.kind === 'results' ? e.agent : e.kind === 'user' ? 'main' : e.kind === 'report' || e.kind === 'finish' || e.kind === 'order' ? e.to : null);
|
|
1561
1562
|
const before = (i) => { let k = i - 1; while (k >= 0 && (S.events[k].kind === 'usage' || S.events[k].kind === 'compact')) k--; return S.events[k]; };
|
|
1562
1563
|
async function ponder(i, token) { // replay: an agent that really took a long time (40 s or more) before this line or call says so first, about every other time
|
|
1563
|
-
const ev = S.events[i], p = before(i); if (!prefs.banter || S.boost > 1 || S.hurry || !p || !S.agents[ev.agent] || thinkerOf(p) !== ev.agent || ev.ts - p.ts < 40000 || hash('t' + ev.ts) % 100 > 55) return;
|
|
1564
|
+
const ev = S.events[i], p = before(i); if (!prefs.banter || prefs.chat === 'quiet' || S.boost > 1 || S.hurry || !p || !S.agents[ev.agent] || thinkerOf(p) !== ev.agent || ev.ts - p.ts < 40000 || hash('t' + ev.ts) % 100 > 55) return;
|
|
1564
1565
|
const line = varied('think', hash(String(ev.ts))); startTalk(ev.agent, line, '💭'); await speakLine(line, S.agents[ev.agent], true, token); stopTalk(ev.agent);
|
|
1565
1566
|
}
|
|
1567
|
+
async function wrapUp(token) { // live, once the lead is done: the part of its last message that the clipped reading left out, so you know what was done
|
|
1568
|
+
if (!S.wrap || S.speakers.size || !prefs.voice && !prefs.banter) return false; S.wrap = false;
|
|
1569
|
+
let k = S.events.length - 1; while (k >= 0 && !(S.events[k].agent === 'main' && S.events[k].kind === 'say')) k--; if (k < 0) return false; const e = S.events[k], m = S.mainSaid;
|
|
1570
|
+
const full = plain(e.text); let rest = m && m.ev === e ? full.slice(m.said).trim() : full; if (m && m.ev === e && m.full) return false; if (!m || m.ev !== e) rest = full;
|
|
1571
|
+
rest = rest.replace(/^[^\w\p{L}]+/u, ''); if (rest.length < 25) return false; const line = (m && m.ev === e ? L('Pour résumer : ', 'To sum up: ') : '') + short(rest, 520);
|
|
1572
|
+
startTalk('main', line, '🎉'); await speakLine(line, S.agents.main, true, token); stopTalk('main'); return true;
|
|
1573
|
+
}
|
|
1566
1574
|
async function smallTalk(token) {
|
|
1575
|
+
if (prefs.chat === 'quiet') return; // quiet: nothing to fill the silences, only the real exchanges
|
|
1567
1576
|
const n = S.fill || 0, last = S.events[S.events.length - 1]; if (!prefs.banter || n > 2 || !last || S.speakers.size || S.hurry || S.boost > 1 || (document.hidden && !S.exporting)) return;
|
|
1568
1577
|
const quiet = performance.now() - S.spokeAt, here = Object.keys(S.agents).filter((id) => id !== 'user' && $('#t-' + id) && $('#t-' + id).style.display !== 'none'), pick = (a) => a[Math.floor(Math.random() * a.length)];
|
|
1569
1578
|
const p = before(S.events.length), muser = S.live && !n && !S.mused && p ? thinkerOf(p) : null, think = !!muser && here.includes(muser) && quiet > 6000 && Date.now() - p.ts > 20000 && Date.now() - p.ts < 180000; // live: 20 s without a word from whoever is thinking, once per silence
|
|
@@ -1585,11 +1594,11 @@ const SPK = new Set(['say', 'report', 'order', 'user']);
|
|
|
1585
1594
|
function aheadSpeakable(i, limit) { let n = 0; for (let k = i, m = Math.min(S.events.length, i + 800); k < m; k++) if (SPK.has(S.events[k].kind) && ++n > limit) break; return n; }
|
|
1586
1595
|
const isSub = (id) => id !== 'main' && id !== 'user';
|
|
1587
1596
|
async function run(i) {
|
|
1588
|
-
const token = ++S.token; S.playing = true; S.idle = false; S.hurry = false; S.boost = 1; S.spokeAt = performance.now(); S.fill = 0; S.exQ = []; S.errRun = {}; S.syncTs = (S.events[i] && S.events[i].ts) || Date.now(); syncUI(); AUD.init();
|
|
1597
|
+
const token = ++S.token; S.playing = true; S.idle = false; S.hurry = false; S.boost = 1; S.spokeAt = performance.now(); S.fill = 0; S.exQ = []; S.errRun = {}; S.wrap = false; S.mainSaid = null; S.syncTs = (S.events[i] && S.events[i].ts) || Date.now(); syncUI(); AUD.init();
|
|
1589
1598
|
if (i === 0 && prefs.epi && !S.live) { await card(L('ÉPISODE 1', 'EPISODE 1'), S.title); if (token !== S.token) return; }
|
|
1590
1599
|
while (token === S.token) {
|
|
1591
1600
|
const lim = S.stopAt || S.events.length;
|
|
1592
|
-
if (i >= lim) { if (S.live && !S.stopAt) { if (!S.idle) { S.idle = true; syncUI(); } await smallTalk(token); await sleep(350); continue; } break; }
|
|
1601
|
+
if (i >= lim) { if (S.live && !S.stopAt) { if (!S.idle) { S.idle = true; syncUI(); } if (await wrapUp(token)) continue; await smallTalk(token); await sleep(350); continue; } break; }
|
|
1593
1602
|
if (S.idle) { S.idle = false; syncUI(); }
|
|
1594
1603
|
if (S.live) { // way behind: skip ahead to stay current, but only count what is actually said: sub-agents produce thousands of silent tool events
|
|
1595
1604
|
const rem = S.events.length - i;
|
|
@@ -1605,10 +1614,15 @@ async function run(i) {
|
|
|
1605
1614
|
if (!S.live && (ev.kind === 'say' || ev.kind === 'report' || ev.kind === 'order' || ev.kind === 'tools')) { await ponder(i, token); if (token !== S.token) return; }
|
|
1606
1615
|
if (ev.kind === 'usage' || ev.kind === 'compact') { if (ev.kind === 'compact' && S.live && prefs.banter) toast(`${nameOf(ev.agent)}: ${L('contexte compacté 🗜', 'context compacted 🗜')}`); i++; continue; }
|
|
1607
1616
|
if (ev.kind === 'finish') { applyStatus(ev); paintStatus(); i++; continue; }
|
|
1608
|
-
|
|
1609
|
-
if (ev.kind === '
|
|
1617
|
+
const backlog = S.live ? S.events.length - i : 0, quick = S.live ? Math.min(1, 40 / Math.max(1, backlog)) : 1; // live: hundreds of tool events from parallel sub-agents must not take minutes to scroll past
|
|
1618
|
+
if (ev.kind === 'results') { applyResults(ev, S.boost > 1); await sleep((S.boost === 1 && S.present === ev.agent && ev.items.some((it) => it.img) ? (S.live ? 900 : 1800) : S.demo ? 900 : S.live ? 30 * quick : 80) / spd()); i++; continue; } // a picture gets a moment on screen
|
|
1619
|
+
if (ev.kind === 'tools') { applyStatus(ev); tickTools(ev); AUD.click(); OFFICE.kick(ev.tools[ev.tools.length - 1].name); paintStatus(); if (quick === 1) await smallTalk(token); await sleep((S.demo ? 1100 : S.live ? 110 * quick : 250) / spd()); i++; continue; }
|
|
1610
1620
|
const group = [ev]; let j = i + 1; // sub-agents talking back-to-back were running in parallel: they talk over each other
|
|
1611
|
-
if (
|
|
1621
|
+
if (ev.kind === 'order') { // several orders in a row (a parallel launch): the new agents all appear now, the first order is read, the others are named in one line
|
|
1622
|
+
while (j < Math.min(S.events.length, i + 40) && group.length < 8) { const e2 = S.events[j]; if (e2.kind === 'usage' || e2.kind === 'compact') { tally(e2); j++; continue; } if (e2.kind === 'order' && e2.agent === ev.agent) { group.push(e2); j++; } else break; }
|
|
1623
|
+
if (group.length > 1) { for (const g of group) applyStatus(g); paintStatus(); }
|
|
1624
|
+
}
|
|
1625
|
+
else if (isSub(ev.agent) && ev.kind !== 'order') {
|
|
1612
1626
|
while (j < Math.min(S.events.length, i + 28) && group.length < 3) {
|
|
1613
1627
|
const e2 = S.events[j];
|
|
1614
1628
|
if (e2.kind === 'usage' || e2.kind === 'compact') { tally(e2); j++; continue; }
|
|
@@ -1626,11 +1640,18 @@ async function run(i) {
|
|
|
1626
1640
|
startTalk(g.agent, line, label);
|
|
1627
1641
|
if (g.kind === 'order') { S.status[g.to] = 'working'; AUD.join(); setTimeout(() => { paintStatus(); fly(g.agent, g.to, '📨'); popTile(g.to); }, 30); }
|
|
1628
1642
|
if (g.kind === 'report') { AUD.ding(); setTimeout(() => { fly(g.agent, g.to, '✅'); popTile(g.agent); }, 30); }
|
|
1629
|
-
|
|
1643
|
+
const said = clipSpeech(line, S.pace); if (g.agent === 'main' && g.kind === 'say') { const full = plain(g.text), key = said.replace(/…$/, '').trim().slice(-30), pos = key ? full.indexOf(key) : -1, n = pos >= 0 ? pos + key.length : said.length; S.mainSaid = { ev: g, said: n, full: n >= full.length - 5 }; }
|
|
1644
|
+
return speakLine(said, S.agents[g.agent], true, token, FR() && trMem.has(baseText(g))).then(() => stopTalk(g.agent));
|
|
1630
1645
|
};
|
|
1631
|
-
let plays;
|
|
1646
|
+
let plays;
|
|
1647
|
+
if (ev.kind === 'order' && group.length > 1) { // the first order in full, then "Also Remy on X, Iris on Y"
|
|
1648
|
+
const rest = group.slice(1), line = `${L('Et aussi ', 'Also ')}${rest.map((o) => `${nameOf(o.to)}${L(' sur ', ' on ')}${(o.title || short(o.text, 60)).replace(/[.。]\s*$/, '')}`).join(', ')}.`;
|
|
1649
|
+
setTimeout(() => rest.forEach((o, k) => setTimeout(() => { fly(o.agent, o.to, '📨'); popTile(o.to); }, k * 120)), 30);
|
|
1650
|
+
plays = [playOne(ev).then(() => { if (token !== S.token) return; startTalk(ev.agent, line, ''); return speakLine(clipSpeech(line, S.pace), S.agents[ev.agent], true, token).then(() => stopTalk(ev.agent)); })];
|
|
1651
|
+
}
|
|
1652
|
+
else if (S.live) { plays = []; let chain = Promise.resolve(); for (const g of group) { chain = chain.then(() => (token === S.token ? playOne(g) : null)); plays.push(chain); } } else plays = group.map(playOne); // live: one voice at a time so each can be heard; replay keeps the overlapping chatter
|
|
1632
1653
|
S.idx = j - 1; paintStatus(); await Promise.all(plays); if (token !== S.token) return;
|
|
1633
|
-
for (const g of group) { await converse(g, token); if (token !== S.token) return; }
|
|
1654
|
+
for (const g of (ev.kind === 'order' && group.length > 1 ? [ev] : group)) { await converse(g, token); if (token !== S.token) return; }
|
|
1634
1655
|
i = j;
|
|
1635
1656
|
}
|
|
1636
1657
|
if (token === S.token) {
|
|
@@ -1701,7 +1722,9 @@ setInterval(async () => {
|
|
|
1701
1722
|
if (d.error) return; if (d.total < S.events.length) { S.cur = null; return; } // transcript was rewritten: reload next tick
|
|
1702
1723
|
if (!d.events || !d.events.length) return;
|
|
1703
1724
|
for (const a of d.agents) if (!S.agents[a.id]) { S.agents[a.id] = persona(a); addTile(S.agents[a.id]); }
|
|
1704
|
-
const from = S.events.length; S.events = S.events.concat(d.events); $('#seek').max = S.events.length - 1; trNew(from); warmTTS(from); renderLog(false);
|
|
1725
|
+
const from = S.events.length; S.events = S.events.concat(d.events); $('#seek').max = S.events.length - 1; trNew(from); warmTTS(from); renderLog(false);
|
|
1726
|
+
if (S.playing && !S.recomputing) for (const e of d.events) if (e.kind === 'order' && !S.status[e.to]) { S.status[e.to] = 'working'; S.orderTs[e.to] = e.ts || 0; S.gone.delete(e.to); } // a new sub-agent shows up now, while the previous line is still being read
|
|
1727
|
+
paintStatus();
|
|
1705
1728
|
} catch { if (!S.offline) { S.offline = true; syncUI(); } } finally { polling = false; }
|
|
1706
1729
|
}, 400);
|
|
1707
1730
|
|
package/public/locales/da.json
CHANGED
|
@@ -206,5 +206,8 @@
|
|
|
206
206
|
"Normal": "Normal",
|
|
207
207
|
"nothing to report": "intet at rapportere",
|
|
208
208
|
"Quiet": "Stille",
|
|
209
|
-
"Laptop in front of your own characters": "Bærbar foran dine egne figurer"
|
|
209
|
+
"Laptop in front of your own characters": "Bærbar foran dine egne figurer",
|
|
210
|
+
"Also ": "Også ",
|
|
211
|
+
" on ": " på ",
|
|
212
|
+
"To sum up: ": "Kort sagt: "
|
|
210
213
|
}
|
package/public/locales/de.json
CHANGED
|
@@ -207,5 +207,8 @@
|
|
|
207
207
|
"Normal": "Normal",
|
|
208
208
|
"nothing to report": "nichts zu melden",
|
|
209
209
|
"Quiet": "Ruhig",
|
|
210
|
-
"Laptop in front of your own characters": "Laptop vor deinen eigenen Figuren"
|
|
210
|
+
"Laptop in front of your own characters": "Laptop vor deinen eigenen Figuren",
|
|
211
|
+
"Also ": "Außerdem ",
|
|
212
|
+
" on ": " an ",
|
|
213
|
+
"To sum up: ": "Zusammengefasst: "
|
|
211
214
|
}
|
package/public/locales/en.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
" full — compaction soon": " full — compaction soon",
|
|
3
|
+
" on ": " on ",
|
|
3
4
|
"”": "”",
|
|
4
5
|
"↗ Open in browser": "↗ Open in browser",
|
|
5
6
|
"⌨ Keyboard shortcuts (?)": "⌨ Keyboard shortcuts (?)",
|
|
@@ -14,6 +15,7 @@
|
|
|
14
15
|
"Agents": "Agents",
|
|
15
16
|
"Agents list (left)": "Agents list (left)",
|
|
16
17
|
"All projects (most recent)": "All projects (most recent)",
|
|
18
|
+
"Also ": "Also ",
|
|
17
19
|
"Ambience volume": "Ambience volume",
|
|
18
20
|
"An agent may be waiting for you ✋": "An agent may be waiting for you ✋",
|
|
19
21
|
"Animal Crossing (babble)": "Animal Crossing (babble)",
|
|
@@ -170,6 +172,7 @@
|
|
|
170
172
|
"This help": "This help",
|
|
171
173
|
"Time": "Time",
|
|
172
174
|
"to": "to",
|
|
175
|
+
"To sum up: ": "To sum up: ",
|
|
173
176
|
"tokens": "tokens",
|
|
174
177
|
"Tokens": "Tokens",
|
|
175
178
|
"Tokens recorded by Claude Code (counted once per message, not a bill)": "Tokens recorded by Claude Code (counted once per message, not a bill)",
|
package/public/locales/es.json
CHANGED
|
@@ -207,5 +207,8 @@
|
|
|
207
207
|
"Normal": "Normal",
|
|
208
208
|
"nothing to report": "nada que informar",
|
|
209
209
|
"Quiet": "Tranquilo",
|
|
210
|
-
"Laptop in front of your own characters": "Portátil delante de tus propios personajes"
|
|
210
|
+
"Laptop in front of your own characters": "Portátil delante de tus propios personajes",
|
|
211
|
+
"Also ": "También ",
|
|
212
|
+
" on ": " en ",
|
|
213
|
+
"To sum up: ": "En resumen: "
|
|
211
214
|
}
|
package/public/locales/ja.json
CHANGED
|
@@ -207,5 +207,8 @@
|
|
|
207
207
|
"Normal": "ふつう",
|
|
208
208
|
"nothing to report": "報告事項なし",
|
|
209
209
|
"Quiet": "静か",
|
|
210
|
-
"Laptop in front of your own characters": "自作キャラクターの前にノートパソコン"
|
|
210
|
+
"Laptop in front of your own characters": "自作キャラクターの前にノートパソコン",
|
|
211
|
+
"Also ": "それから ",
|
|
212
|
+
" on ": " は ",
|
|
213
|
+
"To sum up: ": "まとめると: "
|
|
211
214
|
}
|
|
@@ -207,5 +207,8 @@
|
|
|
207
207
|
"Normal": "Normal",
|
|
208
208
|
"nothing to report": "nada a reportar",
|
|
209
209
|
"Quiet": "Quieto",
|
|
210
|
-
"Laptop in front of your own characters": "Notebook na frente dos seus personagens"
|
|
210
|
+
"Laptop in front of your own characters": "Notebook na frente dos seus personagens",
|
|
211
|
+
"Also ": "Também ",
|
|
212
|
+
" on ": " em ",
|
|
213
|
+
"To sum up: ": "Resumindo: "
|
|
211
214
|
}
|
|
@@ -207,5 +207,8 @@
|
|
|
207
207
|
"Normal": "正常",
|
|
208
208
|
"nothing to report": "没什么可报告的",
|
|
209
209
|
"Quiet": "安静",
|
|
210
|
-
"Laptop in front of your own characters": "在自定义角色前显示笔记本电脑"
|
|
210
|
+
"Laptop in front of your own characters": "在自定义角色前显示笔记本电脑",
|
|
211
|
+
"Also ": "另外 ",
|
|
212
|
+
" on ": " 负责 ",
|
|
213
|
+
"To sum up: ": "总结一下: "
|
|
211
214
|
}
|
|
@@ -207,5 +207,8 @@
|
|
|
207
207
|
"Normal": "正常",
|
|
208
208
|
"nothing to report": "沒什麼好回報的",
|
|
209
209
|
"Quiet": "安靜",
|
|
210
|
-
"Laptop in front of your own characters": "在自訂角色前顯示筆記型電腦"
|
|
210
|
+
"Laptop in front of your own characters": "在自訂角色前顯示筆記型電腦",
|
|
211
|
+
"Also ": "另外 ",
|
|
212
|
+
" on ": " 負責 ",
|
|
213
|
+
"To sum up: ": "總結一下: "
|
|
211
214
|
}
|