agent-yes 1.169.1 → 1.171.0
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/dist/SUPPORTED_CLIS-BMExykB1.js +8 -0
- package/dist/{SUPPORTED_CLIS-Bwg9VqLI.js → SUPPORTED_CLIS-D6MArV7A.js} +2 -2
- package/dist/{agent-yes.config-Bl08kMpl.js → agent-yes.config-ugPYTPOd.js} +2 -2
- package/dist/{agentYesHome-_eJa3DaX.js → agentYesHome-CtHb5b71.js} +1 -1
- package/dist/cli.js +10 -212
- package/dist/{configShared-CEyhl0WH.js → configShared-0MnIQ652.js} +1 -1
- package/dist/{e2e-BeKjLhmO.js → e2e-jb0Hp43q.js} +1 -1
- package/dist/{forkNested-UCKPEgSI.js → forkNested-DhJxa4q4.js} +2 -2
- package/dist/{globalPidIndex-DlmmJlO8.js → globalPidIndex-CoNr7tS8.js} +1 -1
- package/dist/index.js +4 -4
- package/dist/notifyDaemon-BlGV0y4j.js +591 -0
- package/dist/{openBrowser-CCF1iuQK.js → openBrowser-ChR4llYa.js} +1 -1
- package/dist/{pidStore-BfoBWUjc.js → pidStore-BIvsBQ8X.js} +3 -3
- package/dist/pidStore-CLxwp0qI.js +5 -0
- package/dist/{reaper-C-eWAxIj.js → reaper-CWF2_ATd.js} +27 -3
- package/dist/{reaper-BQgDb85W.js → reaper-jfSmog15.js} +1 -1
- package/dist/{remotes-cx_GDFPj.js → remotes-BQMr4_En.js} +3 -3
- package/dist/{remotes-BmAPylU_.js → remotes-CC-4GuJb.js} +3 -3
- package/dist/{runningLock-BobVW1_A.js → runningLock-CNMl13dC.js} +1 -1
- package/dist/rustBinary-mYbyV0SD.js +228 -0
- package/dist/{schedule-CtVvSyaN.js → schedule-C5nUV0GO.js} +7 -7
- package/dist/{serve-CLmk5ujF.js → serve-ChcayCC3.js} +122 -26
- package/dist/{setup-CYbXOcpH.js → setup-CCQZ9tPB.js} +3 -3
- package/dist/{share-D_e2Fwiy.js → share-QregR8a_.js} +2 -2
- package/dist/spawnGate-BFhva-2F.js +5 -0
- package/dist/{spawnGate-DzPfa1PZ.js → spawnGate-IJDByl2U.js} +3 -3
- package/dist/subcommands-CIk1SSdW.js +9 -0
- package/dist/{subcommands--EUush_X.js → subcommands-CtWxwydR.js} +1049 -44
- package/dist/{tray-CWUpaZF4.js → tray-DXr7iK3E.js} +2 -2
- package/dist/{ts-Bbhf5JbN.js → ts-WHYKp13w.js} +8 -8
- package/dist/{versionChecker-BmWkWhcJ.js → versionChecker-BoPKPL9B.js} +2 -2
- package/dist/{webrtcLink-BG0Xc4-W.js → webrtcLink-B7REGtK2.js} +2 -2
- package/dist/{webrtcRemote-SybKurg9.js → webrtcRemote-Bx-eD_0I.js} +3 -3
- package/dist/{workspaceConfig-BC03X4Y1.js → workspaceConfig-BgqK-31W.js} +37 -6
- package/lab/ui/console-logic.js +125 -10
- package/lab/ui/index.html +340 -11
- package/lab/ui/landing.html +11 -1
- package/package.json +1 -1
- package/scripts/build-rs.ts +9 -5
- package/ts/badges.spec.ts +51 -1
- package/ts/badges.ts +35 -1
- package/ts/notifyDaemon.spec.ts +276 -0
- package/ts/notifyDaemon.ts +698 -0
- package/ts/notifyInbox.spec.ts +192 -0
- package/ts/notifyInbox.ts +294 -0
- package/ts/notifyRouter.spec.ts +355 -0
- package/ts/notifyRouter.ts +288 -0
- package/ts/notifyStore.spec.ts +308 -0
- package/ts/notifyStore.ts +439 -0
- package/ts/reaper.spec.ts +18 -1
- package/ts/reaper.ts +33 -1
- package/ts/rustBinary.ts +30 -0
- package/ts/serve.ts +194 -14
- package/ts/subcommands.spec.ts +341 -0
- package/ts/subcommands.ts +764 -29
- package/ts/workspaceConfig.spec.ts +48 -0
- package/ts/workspaceConfig.ts +46 -3
- package/dist/SUPPORTED_CLIS-DiL99G3j.js +0 -8
- package/dist/pidStore-5qyLX3Fh.js +0 -5
- package/dist/spawnGate-CQ1Il3Xk.js +0 -5
- package/dist/subcommands-1ZcaHYkb.js +0 -9
package/lab/ui/index.html
CHANGED
|
@@ -37,6 +37,9 @@
|
|
|
37
37
|
--purple: #bc8cff;
|
|
38
38
|
--pink: #f778ba;
|
|
39
39
|
--red: #f85149;
|
|
40
|
+
/* Peer-focus yellow: a human collaborator (not you) is on this agent.
|
|
41
|
+
Distinct from your own blue (--accent) and the idle-dot amber. */
|
|
42
|
+
--peer: #e3b341;
|
|
40
43
|
--mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
|
|
41
44
|
/* Safe-area insets — non-zero only in a standalone PWA on a notched /
|
|
42
45
|
gesture-bar device (else 0, so these are harmless on desktop). Consumed
|
|
@@ -70,6 +73,7 @@
|
|
|
70
73
|
--purple: #8250df;
|
|
71
74
|
--pink: #bf3989;
|
|
72
75
|
--red: #cf222e;
|
|
76
|
+
--peer: #bf8700;
|
|
73
77
|
}
|
|
74
78
|
}
|
|
75
79
|
* {
|
|
@@ -420,6 +424,21 @@
|
|
|
420
424
|
font-size: 11.5px;
|
|
421
425
|
margin-top: 8px;
|
|
422
426
|
}
|
|
427
|
+
.metaleft {
|
|
428
|
+
display: inline-flex;
|
|
429
|
+
align-items: center;
|
|
430
|
+
gap: 8px;
|
|
431
|
+
}
|
|
432
|
+
/* Peers badge beside the agent counter — how many other humans are in the
|
|
433
|
+
fleet right now. Yellow ("here") when a peer shares the agent you have
|
|
434
|
+
open, muted otherwise. Hidden entirely when you're alone. */
|
|
435
|
+
.peers {
|
|
436
|
+
color: var(--muted);
|
|
437
|
+
cursor: default;
|
|
438
|
+
}
|
|
439
|
+
.peers.here {
|
|
440
|
+
color: var(--peer);
|
|
441
|
+
}
|
|
423
442
|
.connbadge {
|
|
424
443
|
cursor: pointer;
|
|
425
444
|
user-select: none;
|
|
@@ -630,6 +649,10 @@
|
|
|
630
649
|
.omni-spawn .omni-title {
|
|
631
650
|
color: var(--green);
|
|
632
651
|
}
|
|
652
|
+
/* Destructive slash command (/kill) — red title, matching the ⋯-menu Force-kill. */
|
|
653
|
+
.omni-spawn.danger .omni-title {
|
|
654
|
+
color: var(--red);
|
|
655
|
+
}
|
|
633
656
|
.omni-empty {
|
|
634
657
|
padding: 14px;
|
|
635
658
|
color: var(--muted);
|
|
@@ -814,6 +837,39 @@
|
|
|
814
837
|
background: var(--panel);
|
|
815
838
|
box-shadow: inset 3px 0 0 var(--accent);
|
|
816
839
|
}
|
|
840
|
+
/* A human peer (not you) is watching this agent: yellow, mirrored on the
|
|
841
|
+
RIGHT edge so it can coexist with your own blue selection bar on the
|
|
842
|
+
left — a row you BOTH have open shows blue-left + yellow-right. */
|
|
843
|
+
.row.peerfocus {
|
|
844
|
+
box-shadow: inset -3px 0 0 var(--peer);
|
|
845
|
+
background: color-mix(in srgb, var(--peer) 7%, transparent);
|
|
846
|
+
}
|
|
847
|
+
.row.sel.peerfocus {
|
|
848
|
+
background: var(--panel);
|
|
849
|
+
box-shadow: inset 3px 0 0 var(--accent), inset -3px 0 0 var(--peer);
|
|
850
|
+
}
|
|
851
|
+
/* stdin just landed here (someone typed, or `ay send` pushed a command) —
|
|
852
|
+
pulse the row regardless of who (if anyone) is focused on it. Runs while
|
|
853
|
+
the .stdinflash class is present; index.html removes it after the window. */
|
|
854
|
+
.row.stdinflash {
|
|
855
|
+
animation: stdinpulse 0.6s ease-in-out infinite;
|
|
856
|
+
}
|
|
857
|
+
@keyframes stdinpulse {
|
|
858
|
+
0%,
|
|
859
|
+
100% {
|
|
860
|
+
background-color: transparent;
|
|
861
|
+
}
|
|
862
|
+
50% {
|
|
863
|
+
background-color: color-mix(in srgb, var(--peer) 24%, transparent);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
/* Per-row chip: how many peers are watching THIS agent (yellow, mono). */
|
|
867
|
+
.peerchip {
|
|
868
|
+
font-family: var(--mono);
|
|
869
|
+
font-size: 11px;
|
|
870
|
+
color: var(--peer);
|
|
871
|
+
flex: none;
|
|
872
|
+
}
|
|
817
873
|
.r1 {
|
|
818
874
|
display: flex;
|
|
819
875
|
align-items: center;
|
|
@@ -891,6 +947,26 @@
|
|
|
891
947
|
color: var(--green);
|
|
892
948
|
border-color: #2a3a2a;
|
|
893
949
|
}
|
|
950
|
+
/* Folded-subagent summary chip ("⊞ 3/5" = 3 working of 5 hidden subagents),
|
|
951
|
+
shown on a root agent row when the subagent trees are collapsed. Same pill
|
|
952
|
+
shape as .tasks; the ⊞ glyph echoes the fold toolbar button. Turns accent
|
|
953
|
+
when any hidden subagent is still working, so a collapsed tree with live
|
|
954
|
+
children stands out from a quiet one. Hover for the count + last-active. */
|
|
955
|
+
.subs {
|
|
956
|
+
font-family: var(--mono);
|
|
957
|
+
font-size: 10.5px;
|
|
958
|
+
color: var(--muted);
|
|
959
|
+
white-space: nowrap;
|
|
960
|
+
flex: none;
|
|
961
|
+
padding: 0 5px;
|
|
962
|
+
border: 1px solid var(--line);
|
|
963
|
+
border-radius: 8px;
|
|
964
|
+
cursor: pointer;
|
|
965
|
+
}
|
|
966
|
+
.subs.active {
|
|
967
|
+
color: var(--accent);
|
|
968
|
+
border-color: var(--accent);
|
|
969
|
+
}
|
|
894
970
|
/* Status-flag chip ("badge") matched server-side against the agent's
|
|
895
971
|
screen — e.g. an active /goal loop (ts/badges.ts). Same pill shape as
|
|
896
972
|
.tasks but accent-colored so a matched flag stands out as a distinct
|
|
@@ -1368,8 +1444,12 @@
|
|
|
1368
1444
|
/>
|
|
1369
1445
|
</div>
|
|
1370
1446
|
<div class="meta">
|
|
1371
|
-
<span
|
|
1447
|
+
<span class="metaleft">
|
|
1448
|
+
<span id="count"></span>
|
|
1449
|
+
<span id="peers" class="peers" hidden></span>
|
|
1450
|
+
</span>
|
|
1372
1451
|
<span class="metaright">
|
|
1452
|
+
<button id="foldbtn" class="viewbtn" title="fold subagent trees">⊞ subs</button>
|
|
1373
1453
|
<button id="sortbtn" class="viewbtn" title="cycle sort order">⇅ state</button>
|
|
1374
1454
|
<button id="viewbtn" class="viewbtn" title="toggle compact list">☰</button>
|
|
1375
1455
|
<button id="newbtn" class="newbtn" title="spawn a new agent on this fleet">
|
|
@@ -1554,10 +1634,13 @@
|
|
|
1554
1634
|
hasIdent,
|
|
1555
1635
|
deviceCount,
|
|
1556
1636
|
layeredRows,
|
|
1637
|
+
foldSummaries,
|
|
1557
1638
|
sortEntries,
|
|
1558
1639
|
SORT_MODES,
|
|
1559
1640
|
taskLabel,
|
|
1560
1641
|
badgesFor,
|
|
1642
|
+
advanceStdinFlashes,
|
|
1643
|
+
focusSummary,
|
|
1561
1644
|
hashHue,
|
|
1562
1645
|
selFromBottom,
|
|
1563
1646
|
parseSel,
|
|
@@ -1584,6 +1667,17 @@
|
|
|
1584
1667
|
|
|
1585
1668
|
let entries = [];
|
|
1586
1669
|
let sel = null; // selected agent's composite key (room#pid)
|
|
1670
|
+
// Collaborative presence, fleet-wide: focusByKey maps an agent's _key ->
|
|
1671
|
+
// how many OTHER human peers are watching it (yellow bars + per-row chip);
|
|
1672
|
+
// peerTotal is the distinct-peer count for the header badge. stdinSeen +
|
|
1673
|
+
// flashUntil drive the stdin pulse: stdinSeen remembers each agent's last
|
|
1674
|
+
// last_stdin_at so we can spot a bump, flashUntil holds the deadline the
|
|
1675
|
+
// row keeps pulsing to. See advanceStdinFlashes/focusSummary (console-logic).
|
|
1676
|
+
let focusByKey = new Map();
|
|
1677
|
+
let peerTotal = 0;
|
|
1678
|
+
const stdinSeen = new Map();
|
|
1679
|
+
const flashUntil = new Map();
|
|
1680
|
+
const STDIN_FLASH_MS = 1200;
|
|
1587
1681
|
let es = null; // live-tail subscription closer
|
|
1588
1682
|
let term = null; // xterm.js Terminal rendering the raw PTY stream
|
|
1589
1683
|
let fit = null;
|
|
@@ -2687,10 +2781,65 @@
|
|
|
2687
2781
|
}
|
|
2688
2782
|
renderPeerSelections();
|
|
2689
2783
|
}
|
|
2784
|
+
// Fleet-wide presence: unlike pollPresence (only the SELECTED agent's host,
|
|
2785
|
+
// for the terminal overlay), this reads EVERY live source's blackboard so
|
|
2786
|
+
// the left panel can colour any agent a peer is watching. Each viewer
|
|
2787
|
+
// self-reports the pid it's on; we map that back to the source's composite
|
|
2788
|
+
// _key. Best-effort — a source that 404s (older host) contributes nothing.
|
|
2789
|
+
async function pollFleetPresence() {
|
|
2790
|
+
const recs = [];
|
|
2791
|
+
await Promise.all(
|
|
2792
|
+
[...sources.values()].map(async (s) => {
|
|
2793
|
+
if (!s.tx || !s.live) return;
|
|
2794
|
+
let all;
|
|
2795
|
+
try {
|
|
2796
|
+
all = await s.tx.fetchJSON("/api/presence");
|
|
2797
|
+
} catch {
|
|
2798
|
+
return;
|
|
2799
|
+
}
|
|
2800
|
+
if (!Array.isArray(all)) return;
|
|
2801
|
+
const keyOfPid = new Map((s.agents || []).map((e) => [String(e.pid), e._key]));
|
|
2802
|
+
for (const v of all) {
|
|
2803
|
+
if (!v || v.viewer === myViewerId) continue;
|
|
2804
|
+
const key = keyOfPid.get(String(v.agent));
|
|
2805
|
+
// Prefix the viewer id with its source so the same id on two hosts
|
|
2806
|
+
// still counts as two distinct people in the fleet total.
|
|
2807
|
+
if (key) recs.push({ key, viewer: s.id + ":" + v.viewer });
|
|
2808
|
+
}
|
|
2809
|
+
}),
|
|
2810
|
+
);
|
|
2811
|
+
const sum = focusSummary(recs);
|
|
2812
|
+
focusByKey = sum.byKey;
|
|
2813
|
+
peerTotal = sum.total;
|
|
2814
|
+
paintPeersBadge();
|
|
2815
|
+
renderList();
|
|
2816
|
+
}
|
|
2817
|
+
// "N peers" badge beside the agent counter. Yellow when a peer shares the
|
|
2818
|
+
// agent you have open ("same agent"); its tooltip breaks the count down by
|
|
2819
|
+
// agent so you can see who's where. Hidden when you're the only one here.
|
|
2820
|
+
function paintPeersBadge() {
|
|
2821
|
+
const el = $("peers");
|
|
2822
|
+
if (!el) return;
|
|
2823
|
+
if (!peerTotal) {
|
|
2824
|
+
el.hidden = true;
|
|
2825
|
+
el.removeAttribute("title");
|
|
2826
|
+
return;
|
|
2827
|
+
}
|
|
2828
|
+
el.hidden = false;
|
|
2829
|
+
el.textContent = `👤 ${peerTotal} peer${peerTotal === 1 ? "" : "s"}`;
|
|
2830
|
+
el.classList.toggle("here", !!(sel && focusByKey.get(sel)));
|
|
2831
|
+
const lines = [...focusByKey.entries()].map(([k, n]) => {
|
|
2832
|
+
const e = entries.find((x) => x._key === k);
|
|
2833
|
+
const name = e ? e.title || cliLabel(e) || ident(e) || `pid ${e.pid}` : k;
|
|
2834
|
+
return ` ${n} on ${name}`;
|
|
2835
|
+
});
|
|
2836
|
+
el.title = "peers watching agents in this fleet:\n" + lines.join("\n");
|
|
2837
|
+
}
|
|
2690
2838
|
// 3s heartbeat (< the host's 12s TTL): refresh our presence + read others',
|
|
2691
2839
|
// and follow the canonical grid size (resize+scale, never reflow) so all
|
|
2692
2840
|
// viewers stay on one shared canvas and selections line up.
|
|
2693
2841
|
setInterval(() => {
|
|
2842
|
+
pollFleetPresence(); // fleet-wide peer focus (independent of what's selected)
|
|
2694
2843
|
if (!sel) return;
|
|
2695
2844
|
sendPresence();
|
|
2696
2845
|
pollPresence();
|
|
@@ -2701,6 +2850,20 @@
|
|
|
2701
2850
|
.then(followAgentSize)
|
|
2702
2851
|
.catch(() => {});
|
|
2703
2852
|
}, 3000);
|
|
2853
|
+
// Fast pulse loop: drive the stdin flash on the STABLE list DOM (toggle a
|
|
2854
|
+
// class, no re-render) so a pulse begins/ends promptly regardless of the
|
|
2855
|
+
// list's own render cadence. Cheap — a no-op until something is flashing.
|
|
2856
|
+
setInterval(() => {
|
|
2857
|
+
if (!flashUntil.size) return;
|
|
2858
|
+
const now = Date.now();
|
|
2859
|
+
for (const [k, until] of [...flashUntil.entries()]) if (until <= now) flashUntil.delete(k);
|
|
2860
|
+
const listEl = $("list");
|
|
2861
|
+
if (!listEl) return;
|
|
2862
|
+
for (const el of listEl.querySelectorAll(".row[data-key]")) {
|
|
2863
|
+
const on = (flashUntil.get(el.getAttribute("data-key")) || 0) > now;
|
|
2864
|
+
el.classList.toggle("stdinflash", on);
|
|
2865
|
+
}
|
|
2866
|
+
}, 200);
|
|
2704
2867
|
|
|
2705
2868
|
// Peer-selection overlay. The coordinate math (parseSel / selSegments /
|
|
2706
2869
|
// hashHue) lives in console-logic.js (unit-tested); here we just measure the
|
|
@@ -2819,8 +2982,10 @@
|
|
|
2819
2982
|
// Force — escalation a stuck agent can't ignore: a real SIGKILL of its process
|
|
2820
2983
|
// GROUP, server-side via POST /api/kill. Use when /exit does nothing.
|
|
2821
2984
|
const GRACEFUL_EXIT = { claude: "/exit", codex: "/exit", gemini: "/quit" };
|
|
2822
|
-
|
|
2823
|
-
|
|
2985
|
+
// `ent` targets a specific agent (the Cmd+K /kill · /restart commands pass the
|
|
2986
|
+
// omnibox anchor); the ⋯-menu buttons omit it and act on the selected agent.
|
|
2987
|
+
async function stopAgent(force, ent) {
|
|
2988
|
+
const e = ent || (sel ? entries.find((x) => x._key === sel) : null);
|
|
2824
2989
|
if (!e) return;
|
|
2825
2990
|
const who = `pid ${e.pid} ${cliLabel(e) || ident(e) || ""}`.trim();
|
|
2826
2991
|
const tx = txFor(e),
|
|
@@ -2843,8 +3008,8 @@
|
|
|
2843
3008
|
// Restart — stop (if live) + relaunch resuming the session, server-side via
|
|
2844
3009
|
// POST /api/restart (the `ay restart` flow). The server runs it detached, so
|
|
2845
3010
|
// it's decoupled from this agent's lifecycle — no prompt typed into the agent.
|
|
2846
|
-
async function restartAgent() {
|
|
2847
|
-
const e = sel ? entries.find((x) => x._key === sel) : null;
|
|
3011
|
+
async function restartAgent(ent) {
|
|
3012
|
+
const e = ent || (sel ? entries.find((x) => x._key === sel) : null);
|
|
2848
3013
|
if (!e) return;
|
|
2849
3014
|
const who = `pid ${e.pid} ${cliLabel(e) || ident(e) || ""}`.trim();
|
|
2850
3015
|
if (!confirm(`Restart ${who}?\nStops it (if live) and relaunches resuming the session.`))
|
|
@@ -3191,6 +3356,14 @@
|
|
|
3191
3356
|
// Compact list: one line per agent (dot + cli + title), persisted per device.
|
|
3192
3357
|
let compactList = localStorage.getItem("ay.compactList") === "1";
|
|
3193
3358
|
|
|
3359
|
+
// Fold subagent trees: hide nested (sub)agent rows and roll each root's
|
|
3360
|
+
// hidden descendants into a summary chip. Folded BY DEFAULT (only "0" opts
|
|
3361
|
+
// out) so a busy fleet reads as a flat list of top-level agents at a glance.
|
|
3362
|
+
let foldSubs = localStorage.getItem("ay.foldSubs") !== "0";
|
|
3363
|
+
// Per-render Map(rootEntry → { total, working, lastActive }); rebuilt by
|
|
3364
|
+
// renderList whenever folding is on, consumed by foldChipHtml.
|
|
3365
|
+
let foldSum = null;
|
|
3366
|
+
|
|
3194
3367
|
// Display sort order, cycled by the sort button and persisted per device.
|
|
3195
3368
|
// Default "state" (attention-first state, then git busyness). Guard against
|
|
3196
3369
|
// a stale/garbage stored value so we never start in an unknown mode.
|
|
@@ -3234,6 +3407,21 @@
|
|
|
3234
3407
|
return `<span class="tasks${allDone ? " done" : ""}" title="${esc(tip)}">${esc(label)}</span>`;
|
|
3235
3408
|
}
|
|
3236
3409
|
|
|
3410
|
+
// Folded-subagent summary chip ("⊞ 3/5") for a root agent whose subagent
|
|
3411
|
+
// tree is collapsed — working/total of its hidden descendants, from the
|
|
3412
|
+
// per-render foldSum map. "" when folding is off or this row has no hidden
|
|
3413
|
+
// subagents. Clicking it expands the trees (see the list click handler).
|
|
3414
|
+
// The tooltip carries the recency the badge face omits ("last active 5s ago").
|
|
3415
|
+
function foldChipHtml(e) {
|
|
3416
|
+
const s = foldSum && foldSum.get(e);
|
|
3417
|
+
if (!s) return "";
|
|
3418
|
+
const rel = s.lastActive != null ? age({ last_active_at: s.lastActive }) : "";
|
|
3419
|
+
const tip =
|
|
3420
|
+
`${s.total} subagent${s.total === 1 ? "" : "s"} folded · ${s.working} working` +
|
|
3421
|
+
(rel ? ` · last active ${rel} ago` : "");
|
|
3422
|
+
return `<span class="subs${s.working ? " active" : ""}" data-fold-chip="1" title="${esc(tip)}">⊞ ${s.working}/${s.total}</span>`;
|
|
3423
|
+
}
|
|
3424
|
+
|
|
3237
3425
|
// Status-flag chips ("badges") matched server-side against the agent's
|
|
3238
3426
|
// screen (ts/badges.ts) — e.g. an active /goal loop. One small pill per
|
|
3239
3427
|
// matched flag; "" when none matched. CSS class is "flag" (not "badge")
|
|
@@ -3260,13 +3448,39 @@
|
|
|
3260
3448
|
return `<div class="ghead g-${r.kind}">${r.branch ? `<span class="tbranch">${esc(r.branch)}</span>` : ""}<span class="gicon">${icon}</span><span class="glabel">${esc(r.label || "")}</span>${ctag}<span class="gkind">${r.kind}</span></div>`;
|
|
3261
3449
|
}
|
|
3262
3450
|
|
|
3451
|
+
// Extra row classes for collaborative presence: a yellow bar when a human
|
|
3452
|
+
// peer is watching this agent, plus the stdin pulse while its input just
|
|
3453
|
+
// advanced. Empty in the solo case, so a single-user list is unchanged.
|
|
3454
|
+
function rowFlags(e) {
|
|
3455
|
+
const peers = focusByKey.get(e._key) || 0;
|
|
3456
|
+
const flash = (flashUntil.get(e._key) || 0) > Date.now();
|
|
3457
|
+
return (peers ? " peerfocus" : "") + (flash ? " stdinflash" : "");
|
|
3458
|
+
}
|
|
3459
|
+
// A yellow chip showing how many peers are watching this agent ("" if none).
|
|
3460
|
+
function peerChipHtml(e) {
|
|
3461
|
+
const n = focusByKey.get(e._key) || 0;
|
|
3462
|
+
return n
|
|
3463
|
+
? `<span class="peerchip" title="${n} peer${n === 1 ? "" : "s"} watching this agent">👤${n}</span>`
|
|
3464
|
+
: "";
|
|
3465
|
+
}
|
|
3466
|
+
|
|
3263
3467
|
function renderList() {
|
|
3264
3468
|
const toks = $("q").value.trim().split(/\s+/).filter(Boolean);
|
|
3265
3469
|
// Build the full signalling-server > rooms > peers > agents > subagents
|
|
3266
3470
|
// tree. Single-node room/peer layers fold away; ≥2 become ├ └ │ branches.
|
|
3267
|
-
const
|
|
3268
|
-
const agentRows =
|
|
3471
|
+
const fullRows = layeredRows(orderedEntries(toks));
|
|
3472
|
+
const agentRows = fullRows.filter((r) => r.kind === "agent");
|
|
3473
|
+
// Fold: summarize each root's hidden descendants, then drop subagent rows
|
|
3474
|
+
// (any agent row with a parent agent). The count still reports every
|
|
3475
|
+
// matching agent, so folding never makes the fleet look smaller.
|
|
3476
|
+
foldSum = foldSubs ? foldSummaries(fullRows) : null;
|
|
3477
|
+
const rows = foldSubs
|
|
3478
|
+
? fullRows.filter((r) => !(r.kind === "agent" && r.parentEntry))
|
|
3479
|
+
: fullRows;
|
|
3269
3480
|
$("count").textContent = `${agentRows.length} / ${entries.length} agents`;
|
|
3481
|
+
paintPeersBadge(); // keep the peers badge in step with sel / the entry list
|
|
3482
|
+
$("foldbtn").textContent = foldSubs ? "⊞ subs" : "⊟ subs";
|
|
3483
|
+
$("foldbtn").classList.toggle("on", !foldSubs);
|
|
3270
3484
|
$("viewbtn").classList.toggle("on", compactList);
|
|
3271
3485
|
// Show the active sort mode on the button so it's self-documenting; the
|
|
3272
3486
|
// default ("state") is also highlighted to hint it's the implicit order.
|
|
@@ -3287,15 +3501,17 @@
|
|
|
3287
3501
|
const t = e.title || e.prompt || "";
|
|
3288
3502
|
const id = compactIdent(e, ctx, 3, r.parentEntry);
|
|
3289
3503
|
const cli = cliLabel(e);
|
|
3290
|
-
return `<div class="row crow ${e._key === sel ? "sel" : ""}" data-key="${esc(e._key)}">
|
|
3504
|
+
return `<div class="row crow ${e._key === sel ? "sel" : ""}${rowFlags(e)}" data-key="${esc(e._key)}">
|
|
3291
3505
|
${r.branch ? `<span class="tbranch">${esc(r.branch)}</span>` : ""}
|
|
3292
3506
|
<span class="dot ${esc(e.status)}"></span>
|
|
3293
3507
|
${hasIdent(id) ? `<span class="cident" title="${esc(fullIdent(e))}">${esc(id)}</span>` : ""}
|
|
3294
3508
|
${cli ? `<span class="cname">${esc(cli)}</span>` : ""}
|
|
3295
3509
|
<span class="ctitle ${e.title ? "" : "dim"}" title="${esc(t)}">${esc(t)}</span>
|
|
3510
|
+
${foldChipHtml(e)}
|
|
3296
3511
|
${taskChipHtml(e)}
|
|
3297
3512
|
${badgeChipsHtml(e)}
|
|
3298
3513
|
${gitChipHtml(e)}
|
|
3514
|
+
${peerChipHtml(e)}
|
|
3299
3515
|
<span class="age">${age(e)}</span></div>`;
|
|
3300
3516
|
})
|
|
3301
3517
|
.join("") || `<div class="empty">no match</div>`;
|
|
@@ -3315,13 +3531,15 @@
|
|
|
3315
3531
|
`<span class="rtag" data-k="${k}"><span style="opacity:.55">${k}:</span>${esc(v)}</span>`,
|
|
3316
3532
|
)
|
|
3317
3533
|
.join("");
|
|
3318
|
-
return `<div class="row ${e._key === sel ? "sel" : ""}" data-key="${esc(e._key)}">
|
|
3534
|
+
return `<div class="row ${e._key === sel ? "sel" : ""}${rowFlags(e)}" data-key="${esc(e._key)}">
|
|
3319
3535
|
<div class="r1">${r.branch ? `<span class="tbranch">${esc(r.branch)}</span>` : ""}<span class="dot ${esc(e.status)}"></span>
|
|
3320
3536
|
<span class="name">${esc(cliLabel(e) || ident(e) || "agent")}</span>
|
|
3321
3537
|
<span class="badge">pid ${e.pid}</span>
|
|
3538
|
+
${foldChipHtml(e)}
|
|
3322
3539
|
${taskChipHtml(e)}
|
|
3323
3540
|
${badgeChipsHtml(e)}
|
|
3324
3541
|
${gitChipHtml(e)}
|
|
3542
|
+
${peerChipHtml(e)}
|
|
3325
3543
|
<span class="age">${age(e)}</span></div>
|
|
3326
3544
|
${e.title ? `<div class="rowtitle" title="${esc(e.title)}">${esc(e.title)}</div>` : ""}
|
|
3327
3545
|
${
|
|
@@ -3383,6 +3601,13 @@
|
|
|
3383
3601
|
function mergeRender() {
|
|
3384
3602
|
const srcs = [...sources.values()];
|
|
3385
3603
|
entries = srcs.flatMap((s) => s.agents || []);
|
|
3604
|
+
// Spot agents whose stdin just advanced (last_stdin_at bumped since we
|
|
3605
|
+
// last saw them — someone typed, or `ay send` pushed input) and arm the
|
|
3606
|
+
// pulse. Runs before renderList so the flashing rows render pulsing; the
|
|
3607
|
+
// fast loop above stops them when the window lapses. Streamed deltas carry
|
|
3608
|
+
// the new mtime, so a silent `ay send` still trips the flash within ~1s.
|
|
3609
|
+
const until = Date.now() + STDIN_FLASH_MS;
|
|
3610
|
+
for (const k of advanceStdinFlashes(entries, stdinSeen)) flashUntil.set(k, until);
|
|
3386
3611
|
// Badge: total agents + how many rooms are live. Red only when nothing
|
|
3387
3612
|
// at all is reachable (no source answered).
|
|
3388
3613
|
const roomSrcs = srcs.filter((s) => s.id !== LOCAL);
|
|
@@ -3680,6 +3905,14 @@
|
|
|
3680
3905
|
}
|
|
3681
3906
|
|
|
3682
3907
|
$("list").addEventListener("click", (ev) => {
|
|
3908
|
+
// Clicking a folded-subagent chip expands the trees instead of selecting
|
|
3909
|
+
// the row, so the hidden children it summarizes become reachable.
|
|
3910
|
+
if (ev.target.closest("[data-fold-chip]")) {
|
|
3911
|
+
foldSubs = false;
|
|
3912
|
+
localStorage.setItem("ay.foldSubs", "0");
|
|
3913
|
+
renderList();
|
|
3914
|
+
return;
|
|
3915
|
+
}
|
|
3683
3916
|
const row = ev.target.closest(".row");
|
|
3684
3917
|
if (row) select(row.dataset.key);
|
|
3685
3918
|
});
|
|
@@ -3739,6 +3972,12 @@
|
|
|
3739
3972
|
localStorage.setItem("ay.compactList", compactList ? "1" : "0");
|
|
3740
3973
|
renderList();
|
|
3741
3974
|
});
|
|
3975
|
+
// Fold / unfold every subagent tree (global toggle, persisted per device).
|
|
3976
|
+
$("foldbtn").addEventListener("click", () => {
|
|
3977
|
+
foldSubs = !foldSubs;
|
|
3978
|
+
localStorage.setItem("ay.foldSubs", foldSubs ? "1" : "0");
|
|
3979
|
+
renderList();
|
|
3980
|
+
});
|
|
3742
3981
|
// Cycle the sort order: state → created → identity → state.
|
|
3743
3982
|
$("sortbtn").addEventListener("click", () => {
|
|
3744
3983
|
sortMode = SORT_MODES[(SORT_MODES.indexOf(sortMode) + 1) % SORT_MODES.length];
|
|
@@ -3781,9 +4020,10 @@
|
|
|
3781
4020
|
// renders. Clamps at the ends, scrolls the row into view.
|
|
3782
4021
|
function stepSelection(dir) {
|
|
3783
4022
|
const toks = $("q").value.trim().split(/\s+/).filter(Boolean);
|
|
3784
|
-
// Must match renderList's order; step only over agent rows (skip headers)
|
|
4023
|
+
// Must match renderList's order; step only over agent rows (skip headers),
|
|
4024
|
+
// and skip folded-away subagents so nav lands only on visible rows.
|
|
3785
4025
|
const shown = layeredRows(orderedEntries(toks))
|
|
3786
|
-
.filter((r) => r.kind === "agent")
|
|
4026
|
+
.filter((r) => r.kind === "agent" && !(foldSubs && r.parentEntry))
|
|
3787
4027
|
.map((r) => r.entry);
|
|
3788
4028
|
if (!shown.length) return;
|
|
3789
4029
|
const cur = shown.findIndex((e) => e._key === sel);
|
|
@@ -3889,6 +4129,66 @@
|
|
|
3889
4129
|
return cur && cur.cwd && cur.git && cur.git.branch ? cur : null;
|
|
3890
4130
|
}
|
|
3891
4131
|
|
|
4132
|
+
// Slash commands that act on the OPEN agent (the omnibox anchor), mirroring the
|
|
4133
|
+
// ⋯-menu recovery buttons. Typing "/" in the omnibox lists them; "/k" / "/r"
|
|
4134
|
+
// narrows. Each runs the very same handler the dropdown button does, so the
|
|
4135
|
+
// confirm prompt and server call are identical — /kill = Force-kill (SIGKILL),
|
|
4136
|
+
// /restart = Restart (resume). "/fork [topic]" is also offered here (built in
|
|
4137
|
+
// omniCommandRows, not this list, since it spawns rather than acts in place).
|
|
4138
|
+
const OMNI_COMMANDS = [
|
|
4139
|
+
{
|
|
4140
|
+
cmd: "/restart",
|
|
4141
|
+
icon: "↻",
|
|
4142
|
+
title: "Restart (resume)",
|
|
4143
|
+
desc: "stops it (if live) and relaunches resuming the session",
|
|
4144
|
+
run: (e) => restartAgent(e),
|
|
4145
|
+
},
|
|
4146
|
+
{
|
|
4147
|
+
cmd: "/kill",
|
|
4148
|
+
icon: "✕",
|
|
4149
|
+
title: "Force-kill (SIGKILL)",
|
|
4150
|
+
desc: "terminates the process immediately",
|
|
4151
|
+
danger: true,
|
|
4152
|
+
run: (e) => stopAgent(true, e),
|
|
4153
|
+
},
|
|
4154
|
+
];
|
|
4155
|
+
// Build the command rows for a "/…"-prefixed query, targeting the anchor agent.
|
|
4156
|
+
// Empty when nothing is open (a command with no agent to act on is a no-op) so
|
|
4157
|
+
// the omnibox falls through to its normal search/spawn behaviour.
|
|
4158
|
+
function omniCommandRows(q) {
|
|
4159
|
+
const target = omniAnchor();
|
|
4160
|
+
if (!target) return [];
|
|
4161
|
+
// Match on the FIRST token so a command can carry args after it
|
|
4162
|
+
// (e.g. "/fork fix the auth bug" → cmd "fork", topic "fix the auth bug").
|
|
4163
|
+
const parts = q.slice(1).trim().split(/\s+/).filter(Boolean);
|
|
4164
|
+
const cmdTerm = (parts[0] || "").toLowerCase(); // command token after "/"
|
|
4165
|
+
const rows = OMNI_COMMANDS.filter((c) => c.cmd.slice(1).startsWith(cmdTerm)).map((c) => ({
|
|
4166
|
+
kind: "cmd",
|
|
4167
|
+
cmd: c,
|
|
4168
|
+
target,
|
|
4169
|
+
}));
|
|
4170
|
+
// "/fork [topic]" — a slash alias for the fork spawn row so fork is reachable
|
|
4171
|
+
// straight from the command palette (⌘K → "/fork"), not only via the row that
|
|
4172
|
+
// auto-appends to a search. It's a spawn (kind:"fork"), not a kind:"cmd" that
|
|
4173
|
+
// acts on an existing agent, so it's built here rather than in OMNI_COMMANDS —
|
|
4174
|
+
// omniActivate's fork branch (branch prompt → /api/spawn {fork}) handles it.
|
|
4175
|
+
// Needs a real fork source (the open agent with a live git branch); when the
|
|
4176
|
+
// anchor has none, forking is meaningless so the row is omitted.
|
|
4177
|
+
if ("fork".startsWith(cmdTerm)) {
|
|
4178
|
+
const src = omniForkSource();
|
|
4179
|
+
if (src)
|
|
4180
|
+
rows.push({
|
|
4181
|
+
kind: "fork",
|
|
4182
|
+
branch: forkSlug(parts.slice(1).join(" "), src.git.branch),
|
|
4183
|
+
srcBranch: src.git.branch,
|
|
4184
|
+
fromCwd: src.cwd,
|
|
4185
|
+
cli: src.cli,
|
|
4186
|
+
room: src._room,
|
|
4187
|
+
});
|
|
4188
|
+
}
|
|
4189
|
+
return rows;
|
|
4190
|
+
}
|
|
4191
|
+
|
|
3892
4192
|
// Which agent the highlighted candidate should preview in the background: an
|
|
3893
4193
|
// agent row → that agent; the spawn row / an empty list → the launch-time
|
|
3894
4194
|
// agent (so the backdrop shows the spawn target's cwd).
|
|
@@ -3975,6 +4275,16 @@
|
|
|
3975
4275
|
<div class="omni-sub">⏎ to choose a working dir · prompt: ${esc(q || "(empty)")}</div>
|
|
3976
4276
|
</div></div>`;
|
|
3977
4277
|
}
|
|
4278
|
+
if (r.kind === "cmd") {
|
|
4279
|
+
const t = r.target;
|
|
4280
|
+
const dz = r.cmd.danger ? " danger" : "";
|
|
4281
|
+
return `<div class="omni-row omni-spawn${dz}${sc}" data-i="${i}">
|
|
4282
|
+
<span class="dot ${esc(t.status)}"></span>
|
|
4283
|
+
<div class="omni-main">
|
|
4284
|
+
<div class="omni-title">${r.cmd.icon} ${esc(r.cmd.title)} <span style="opacity:.55">${esc(r.cmd.cmd)}</span></div>
|
|
4285
|
+
<div class="omni-sub">pid ${esc(String(t.pid))} ${esc(cliLabel(t) || ident(t) || "")} · ${esc(r.cmd.desc)}</div>
|
|
4286
|
+
</div></div>`;
|
|
4287
|
+
}
|
|
3978
4288
|
const e = r.entry;
|
|
3979
4289
|
const snip = r.snippet ? `<div class="omni-snip">…${r.snippet}…</div>` : "";
|
|
3980
4290
|
return `<div class="omni-row${sc}" data-i="${i}">
|
|
@@ -3993,6 +4303,18 @@
|
|
|
3993
4303
|
|
|
3994
4304
|
function runOmni() {
|
|
3995
4305
|
const q = $("omni-input").value.trim();
|
|
4306
|
+
// A "/…" query is a slash command against the open agent, not a search —
|
|
4307
|
+
// show the matching command rows and skip the agent/spawn/tail machinery.
|
|
4308
|
+
if (q.startsWith("/")) {
|
|
4309
|
+
const cmds = omniCommandRows(q);
|
|
4310
|
+
if (cmds.length) {
|
|
4311
|
+
if (omniTailTimer) clearTimeout(omniTailTimer);
|
|
4312
|
+
omniRows = cmds;
|
|
4313
|
+
omniIdx = 0;
|
|
4314
|
+
renderOmni();
|
|
4315
|
+
return;
|
|
4316
|
+
}
|
|
4317
|
+
}
|
|
3996
4318
|
const toks = q.split(/\s+/).filter(Boolean);
|
|
3997
4319
|
// Tier 1 — instant title/identity match, ranked title-first then by recency.
|
|
3998
4320
|
const hits = entries
|
|
@@ -4085,6 +4407,13 @@
|
|
|
4085
4407
|
const q = $("omni-input").value.trim();
|
|
4086
4408
|
const row = omniRows[omniIdx];
|
|
4087
4409
|
const anchor = omniAnchor();
|
|
4410
|
+
// A slash command runs its ⋯-menu handler against the pinned target. Handled
|
|
4411
|
+
// before the spawn fallthrough so ⌘⏎ on a "/kill" row can't spawn instead.
|
|
4412
|
+
if (row && row.kind === "cmd") {
|
|
4413
|
+
closeOmni(false);
|
|
4414
|
+
await row.cmd.run(row.target);
|
|
4415
|
+
return;
|
|
4416
|
+
}
|
|
4088
4417
|
// ⌘⏎ always = spawn-here (the fast default), regardless of the selected row.
|
|
4089
4418
|
if (forceSpawn || (row && row.kind === "spawn")) {
|
|
4090
4419
|
closeOmni(false); // committing — keep the bg as-is, spawnAndSelect takes over
|
package/lab/ui/landing.html
CHANGED
|
@@ -214,7 +214,7 @@
|
|
|
214
214
|
spawn agents from anywhere.
|
|
215
215
|
</p>
|
|
216
216
|
<div class="cta">
|
|
217
|
-
<a class="btn" href="/w/">Open the console →</a>
|
|
217
|
+
<a class="btn" href="/w/" autofocus>Open the console →</a>
|
|
218
218
|
<a class="btn ghost" href="https://github.com/snomiao/agent-yes">Star on GitHub</a>
|
|
219
219
|
</div>
|
|
220
220
|
<div class="install">
|
|
@@ -273,5 +273,15 @@ powershell -c "irm https://agent-yes.com/setup.ps1 | iex"</code></pre>
|
|
|
273
273
|
</div>
|
|
274
274
|
</footer>
|
|
275
275
|
</div>
|
|
276
|
+
<script>
|
|
277
|
+
// Focus the "Open the console" CTA on load so keyboard/Enter users land
|
|
278
|
+
// straight on it. `autofocus` on <a> isn't honored everywhere, so back it
|
|
279
|
+
// up here — but skip on touch devices where focusing scroll-jumps the page.
|
|
280
|
+
(function () {
|
|
281
|
+
if (matchMedia("(hover: none)").matches) return;
|
|
282
|
+
var cta = document.querySelector(".hero .cta a.btn");
|
|
283
|
+
if (cta) cta.focus({ preventScroll: true });
|
|
284
|
+
})();
|
|
285
|
+
</script>
|
|
276
286
|
</body>
|
|
277
287
|
</html>
|
package/package.json
CHANGED
package/scripts/build-rs.ts
CHANGED
|
@@ -18,11 +18,15 @@ import path from "path";
|
|
|
18
18
|
const repoRoot = path.resolve(import.meta.dirname, "..");
|
|
19
19
|
const cargoHome = process.env.CARGO_HOME || path.join(os.homedir(), ".cargo");
|
|
20
20
|
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
];
|
|
21
|
+
// Every binary cargo touches: the build output and the installed copy, for
|
|
22
|
+
// each [[bin]] in the package. Any of these can be locked by a live process,
|
|
23
|
+
// and cargo install refuses to overwrite an existing installed binary (even
|
|
24
|
+
// with --force it can't delete a locked one), so each must be moved aside.
|
|
25
|
+
const exeNames = ["agent-yes.exe", "ay-spawn-hidden.exe"];
|
|
26
|
+
const targets = exeNames.flatMap((exe) => [
|
|
27
|
+
path.join(repoRoot, "rs", "target", "release", exe),
|
|
28
|
+
path.join(cargoHome, "bin", exe),
|
|
29
|
+
]);
|
|
26
30
|
|
|
27
31
|
function moveLockedBinariesAside(): Array<{ original: string; aside: string }> {
|
|
28
32
|
const moved: Array<{ original: string; aside: string }> = [];
|