agent-yes 1.287.0 → 1.288.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-DKdFELZL.js → SUPPORTED_CLIS-BHowMgMY.js} +2 -2
- package/dist/{SUPPORTED_CLIS-5YPYQ8Iw.js → SUPPORTED_CLIS-ChhJyq4h.js} +3 -3
- package/dist/{agentShare-B525ly6v.js → agentShare-T3RskvDT.js} +2 -2
- package/dist/{callback-C-awM4qn.js → callback-CuohbCJA.js} +3 -3
- package/dist/{callback-BLTW4Mgp.js → callback-DeaETuHt.js} +2 -2
- package/dist/cli.js +5 -5
- package/dist/{cmdPs-BRtmnpsM.js → cmdPs-Bm4JG7hi.js} +2 -2
- package/dist/index.js +2 -2
- package/dist/{notifyDaemon-DshQvO-4.js → notifyDaemon-BORqPeAl.js} +2 -2
- package/dist/{parentPingLoop-Bjw6hKoZ.js → parentPingLoop-COePRYr9.js} +2 -2
- package/dist/{rustBinary-C57KWEtX.js → rustBinary-BmXzqj5z.js} +2 -2
- package/dist/{rustBinary-BXhp8yBp.js → rustBinary-D12k7iOD.js} +2 -2
- package/dist/{schedule-Boa0nE0V.js → schedule-DLoUmyJh.js} +4 -4
- package/dist/{serve-7mcHebep.js → serve-DaDlOtss.js} +17 -17
- package/dist/{setup-DXRL-Mdo.js → setup-Cqy8nRrq.js} +2 -2
- package/dist/{subcommands-BftthBK7.js → subcommands-BfiOyfz7.js} +15 -15
- package/dist/{subcommands-BQJ-Uqtl.js → subcommands-DKTa3E6O.js} +1 -1
- package/dist/{terminal-DrYf3HP0.js → terminal-C0tnGnrZ.js} +2 -2
- package/dist/trayApp-BrAMlCY7.js +5 -0
- package/dist/{trayApp-DG2fQnhx.js → trayApp-DvL9qW6V.js} +2 -2
- package/dist/{ts-LDnoqCV2.js → ts-DJLp2_bv.js} +3 -3
- package/dist/{versionChecker-CGmyt2qE.js → versionChecker-CkO7wYyV.js} +2 -2
- package/dist/{widget-B2SluyG1.js → widget-CJqpScCG.js} +3 -3
- package/dist/{ws-DWA90-uE.js → ws-DGENGNrr.js} +2 -2
- package/dist/{ws-CTOnVXg8.js → ws-DzCKvzWD.js} +2 -2
- package/lab/ui/index.html +95 -67
- package/package.json +2 -1
- package/ts/JsonlStore.bun.spec.ts +204 -0
- package/ts/ReadyManager.bun.spec.ts +72 -0
- package/ts/agentNice.bun.spec.ts +24 -0
- package/ts/agentPermissions.bun.spec.ts +117 -0
- package/ts/agentShare.bun.spec.ts +240 -0
- package/ts/agentTree.bun.spec.ts +92 -0
- package/ts/agentYesHome.bun.spec.ts +25 -0
- package/ts/askApi.bun.spec.ts +358 -0
- package/ts/askCli.bun.spec.ts +304 -0
- package/ts/autoRetry.bun.spec.ts +123 -0
- package/ts/badges.bun.spec.ts +187 -0
- package/ts/buildRustArgs.bun.spec.ts +351 -0
- package/ts/callbackCore.bun.spec.ts +139 -0
- package/ts/channels/hlc.bun.spec.ts +56 -0
- package/ts/channels/link.bun.spec.ts +72 -0
- package/ts/channels/op.bun.spec.ts +78 -0
- package/ts/channels/store.browser.bun.spec.ts +60 -0
- package/ts/channels/store.bun.spec.ts +145 -0
- package/ts/channels/store.node.bun.spec.ts +76 -0
- package/ts/channels/trust.bun.spec.ts +86 -0
- package/ts/channels.bun.spec.ts +212 -0
- package/ts/cli-idle.bun.spec.ts +20 -0
- package/ts/cmdDsh.bun.spec.ts +90 -0
- package/ts/configLoader.bun.spec.ts +273 -0
- package/ts/cwdConflictWarn.bun.spec.ts +60 -0
- package/ts/cwdPassthroughHint.bun.spec.ts +74 -0
- package/ts/e2e-crypto.bun.spec.ts +235 -0
- package/ts/forkNested.bun.spec.ts +240 -0
- package/ts/histStore.bun.spec.ts +355 -0
- package/ts/identity.bun.spec.ts +190 -0
- package/ts/idleWaiter.bun.spec.ts +91 -0
- package/ts/initMsg.bun.spec.ts +132 -0
- package/ts/invokedCli.bun.spec.ts +30 -0
- package/ts/ipcLock.bun.spec.ts +107 -0
- package/ts/logger.bun.spec.ts +27 -0
- package/ts/lsWatch.bun.spec.ts +61 -0
- package/ts/messageLog.bun.spec.ts +145 -0
- package/ts/needsInput.bun.spec.ts +157 -0
- package/ts/nodeRuntime.bun.spec.ts +99 -0
- package/ts/notifyDaemon.bun.spec.ts +297 -0
- package/ts/notifyInbox.bun.spec.ts +277 -0
- package/ts/notifyRouter.bun.spec.ts +351 -0
- package/ts/notifyStore.bun.spec.ts +403 -0
- package/ts/ownerHeartbeat.bun.spec.ts +175 -0
- package/ts/parentLink.bun.spec.ts +64 -0
- package/ts/parentPing.bun.spec.ts +176 -0
- package/ts/parentPingLoop.bun.spec.ts +79 -0
- package/ts/parentPingSend.bun.spec.ts +195 -0
- package/ts/parentWatching.bun.spec.ts +61 -0
- package/ts/pidStore.bun.spec.ts +372 -0
- package/ts/procStartTime.bun.spec.ts +117 -0
- package/ts/reaper.bun.spec.ts +86 -0
- package/ts/removeControlCharacters.bun.spec.ts +94 -0
- package/ts/resolveBinary.bun.spec.ts +133 -0
- package/ts/resultEnvelope.bun.spec.ts +46 -0
- package/ts/runningLock.bun.spec.ts +485 -0
- package/ts/rustBinary.bun.spec.ts +81 -0
- package/ts/schedule.bun.spec.ts +30 -0
- package/ts/serve.bun.spec.ts +166 -0
- package/ts/serveLock.bun.spec.ts +220 -0
- package/ts/session-integration.bun.spec.ts +93 -0
- package/ts/setup.bun.spec.ts +42 -0
- package/ts/share.bun.spec.ts +36 -0
- package/ts/sizeNego.bun.spec.ts +56 -0
- package/ts/statusText.bun.spec.ts +17 -0
- package/ts/strayServe.bun.spec.ts +50 -0
- package/ts/subcommandMirror.bun.spec.ts +76 -0
- package/ts/systemPathLink.bun.spec.ts +79 -0
- package/ts/termToken.bun.spec.ts +74 -0
- package/ts/terminal.bun.spec.ts +50 -0
- package/ts/terminalReply.bun.spec.ts +36 -0
- package/ts/titleScanner.bun.spec.ts +65 -0
- package/ts/todoAutomation.bun.spec.ts +253 -0
- package/ts/todoBlock.bun.spec.ts +73 -0
- package/ts/todoDigest.bun.spec.ts +184 -0
- package/ts/todoIdentity.bun.spec.ts +85 -0
- package/ts/todoLifecycle.bun.spec.ts +79 -0
- package/ts/todoParse.bun.spec.ts +98 -0
- package/ts/todoRoot.bun.spec.ts +117 -0
- package/ts/todoStore.bun.spec.ts +727 -0
- package/ts/tryCatch.bun.spec.ts +275 -0
- package/ts/webrtcLink.bun.spec.ts +45 -0
- package/ts/workspaceConfig.bun.spec.ts +319 -0
- package/dist/trayApp-1AQ6X2V9.js +0 -5
package/lab/ui/index.html
CHANGED
|
@@ -3622,12 +3622,6 @@
|
|
|
3622
3622
|
// Always-on header-badge traffic ring (independent of the opt-in HUD below).
|
|
3623
3623
|
if (kind === "in") traf.cin += n || 0;
|
|
3624
3624
|
else if (kind === "out") traf.cout += n || 0;
|
|
3625
|
-
// Also accumulate into the selected agent's per-agent ring (left-panel heatmap).
|
|
3626
|
-
if (sel) {
|
|
3627
|
-
const r = ioRing(sel);
|
|
3628
|
-
if (kind === "in") r.i += n || 0;
|
|
3629
|
-
else if (kind === "out") r.o += n || 0;
|
|
3630
|
-
}
|
|
3631
3625
|
if (!perfOn) return;
|
|
3632
3626
|
if (kind === "in") {
|
|
3633
3627
|
perf.inEvents++;
|
|
@@ -3725,28 +3719,9 @@
|
|
|
3725
3719
|
traf.cin = traf.cout = 0;
|
|
3726
3720
|
traf.hist = [];
|
|
3727
3721
|
}
|
|
3728
|
-
//
|
|
3729
|
-
//
|
|
3730
|
-
|
|
3731
|
-
// rows keep their last-known window (empty → transparent). Mirrors traf's
|
|
3732
|
-
// shape but is keyed by composite _key, sampled on the same 1s tick below.
|
|
3733
|
-
const IO_N = 60;
|
|
3734
|
-
const ioByKey = new Map();
|
|
3735
|
-
function ioRing(key) {
|
|
3736
|
-
let r = ioByKey.get(key);
|
|
3737
|
-
if (!r) {
|
|
3738
|
-
r = { i: 0, o: 0, hist: [] };
|
|
3739
|
-
ioByKey.set(key, r);
|
|
3740
|
-
}
|
|
3741
|
-
return r;
|
|
3742
|
-
}
|
|
3743
|
-
function ioReset(key) {
|
|
3744
|
-
const r = ioByKey.get(key);
|
|
3745
|
-
if (r) {
|
|
3746
|
-
r.i = r.o = 0;
|
|
3747
|
-
r.hist = [];
|
|
3748
|
-
}
|
|
3749
|
-
}
|
|
3722
|
+
// Cached /api/host response (system load + managed/unattributed split) for
|
|
3723
|
+
// the room header line. Refreshed every list tick.
|
|
3724
|
+
let hostRes = null;
|
|
3750
3725
|
// The last winsize the agent's PTY reported (from /api/size), so the tooltip
|
|
3751
3726
|
// can show OUR viewport vs the agent's render size. Reset on agent switch.
|
|
3752
3727
|
let agentSize = null;
|
|
@@ -3881,15 +3856,9 @@
|
|
|
3881
3856
|
traf.hist.push({ i: traf.cin, o: traf.cout });
|
|
3882
3857
|
if (traf.hist.length > TRAF_N) traf.hist.shift();
|
|
3883
3858
|
traf.cin = traf.cout = 0;
|
|
3884
|
-
//
|
|
3885
|
-
//
|
|
3886
|
-
|
|
3887
|
-
if (r) {
|
|
3888
|
-
r.hist.push({ i: r.i, o: r.o });
|
|
3889
|
-
if (r.hist.length > IO_N) r.hist.shift();
|
|
3890
|
-
r.i = r.o = 0;
|
|
3891
|
-
renderList();
|
|
3892
|
-
}
|
|
3859
|
+
// The per-agent heatmap now comes from the server sampler (/api/ls res
|
|
3860
|
+
// field), refreshed on the 3s list poll — no front-end per-second
|
|
3861
|
+
// sampling needed. Repaint the header badge (traffic tooltip) only.
|
|
3893
3862
|
if (sel) paintHeaderBadge();
|
|
3894
3863
|
}, 1000);
|
|
3895
3864
|
|
|
@@ -5934,6 +5903,28 @@ my.translate = async (text, lang) => {
|
|
|
5934
5903
|
// as agent rows so the hierarchy reads as one tree. The room/peer layers
|
|
5935
5904
|
// only appear here when there are ≥2 of them (single ones fold away in
|
|
5936
5905
|
// layeredRows), so a header always disambiguates something real.
|
|
5906
|
+
// Resource summary for the LOCAL room header line, sourced from /api/host:
|
|
5907
|
+
// XX%/YY% XXG/YYG of ZZG
|
|
5908
|
+
// where XX = agent-yes managed, YY = unattributed (rest of the box). Only
|
|
5909
|
+
// renders when hostRes has been fetched and the sampler has data.
|
|
5910
|
+
function resLineHtml() {
|
|
5911
|
+
if (!hostRes || !hostRes.resources) return "";
|
|
5912
|
+
const r = hostRes.resources;
|
|
5913
|
+
const memTotal = hostRes.mem && hostRes.mem.total ? hostRes.mem.total : null;
|
|
5914
|
+
const managed = hsize(r.managed_rss);
|
|
5915
|
+
const unattributed = hsize(r.unattributed_rss);
|
|
5916
|
+
const total = memTotal ? hsize(memTotal) : null;
|
|
5917
|
+
if (!total) return "";
|
|
5918
|
+
return ` — <span title="agent-yes managed / unattributed">${managed}/${unattributed}</span> of ${total}`;
|
|
5919
|
+
}
|
|
5920
|
+
function hsize(bytes) {
|
|
5921
|
+
if (bytes === null || !Number.isFinite(bytes)) return "-";
|
|
5922
|
+
if (bytes >= 1073741824) return (bytes / 1073741824).toFixed(1) + "G";
|
|
5923
|
+
if (bytes >= 1048576) return Math.round(bytes / 1048576) + "M";
|
|
5924
|
+
if (bytes >= 1024) return Math.round(bytes / 1024) + "K";
|
|
5925
|
+
return bytes + "B";
|
|
5926
|
+
}
|
|
5927
|
+
|
|
5937
5928
|
function headerHtml(r) {
|
|
5938
5929
|
const icon = r.kind === "room" ? "▤" : "▣";
|
|
5939
5930
|
// Peer headers carry their host's connection type (lan/wan/relay), so a
|
|
@@ -5942,7 +5933,8 @@ my.translate = async (text, lang) => {
|
|
|
5942
5933
|
r.kind === "peer"
|
|
5943
5934
|
? connTagHtml(hostKinds.get((r.room || "") + " " + (r.host || "")))
|
|
5944
5935
|
: "";
|
|
5945
|
-
|
|
5936
|
+
const resSuffix = r.kind === "room" ? resLineHtml() : "";
|
|
5937
|
+
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 || "")}${resSuffix}</span>${ctag}<span class="gkind">${r.kind}</span></div>`;
|
|
5946
5938
|
}
|
|
5947
5939
|
|
|
5948
5940
|
// Extra row classes for collaborative presence: a yellow bar when a human
|
|
@@ -5982,29 +5974,42 @@ my.translate = async (text, lang) => {
|
|
|
5982
5974
|
: "";
|
|
5983
5975
|
}
|
|
5984
5976
|
|
|
5985
|
-
// Build the low-contrast oklch heatmap backdrop for one agent row
|
|
5986
|
-
//
|
|
5987
|
-
//
|
|
5988
|
-
//
|
|
5989
|
-
//
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
const
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
const
|
|
5997
|
-
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
const
|
|
6001
|
-
|
|
6002
|
-
const
|
|
6003
|
-
const
|
|
6004
|
-
|
|
6005
|
-
|
|
6006
|
-
|
|
6007
|
-
|
|
5977
|
+
// Build the low-contrast oklch heatmap backdrop for one agent row, from the
|
|
5978
|
+
// SERVER-side per-agent resource window (`entry.res`, fed by ayrs' sampler —
|
|
5979
|
+
// CPU-seconds, RSS and process count per bucket, one point per minute-ish).
|
|
5980
|
+
// Every row gets its own window (no selection required), painted BEHIND the
|
|
5981
|
+
// text (absolute, low opacity). Two stacked rows: CPU (top, warm) and RSS
|
|
5982
|
+
// (bottom, cool), each a per-bucket grid so a busy/fat burst reads as a solid
|
|
5983
|
+
// block and idle as near-transparent. Right edge is the newest bucket.
|
|
5984
|
+
function rowIoHeat(e) {
|
|
5985
|
+
const res = e && e.res;
|
|
5986
|
+
if (!res || !Array.isArray(res.cpu_seconds) || !res.cpu_seconds.length) return "";
|
|
5987
|
+
const n = res.cpu_seconds.length;
|
|
5988
|
+
const bucketSecs = res.bucket_secs || 60;
|
|
5989
|
+
// CPU: normalize cpu_seconds (summed across the tree) to "fraction of one
|
|
5990
|
+
// core" by dividing by the bucket width. Log-scaled so a 1% vs 100% spread
|
|
5991
|
+
// reads better than a linear 0..1.
|
|
5992
|
+
const cpuMax = Math.max(1e-9, ...res.cpu_seconds.map((s) => Math.abs(s) / bucketSecs));
|
|
5993
|
+
const rssMax = Math.max(1, ...(res.rss || []).map((b) => b || 0));
|
|
5994
|
+
const log1p = (v) => (v <= 0 ? 0 : Math.min(1, Math.log1p(v) / Math.log1p(cpuMax)));
|
|
5995
|
+
const logRss = (v) =>
|
|
5996
|
+
v <= 0 ? 0 : Math.min(1, Math.log1p(v / 1e6) / Math.log1p(rssMax / 1e6));
|
|
5997
|
+
const w = 96,
|
|
5998
|
+
h = 22,
|
|
5999
|
+
colW = w / n,
|
|
6000
|
+
rowH = (h - 2) / 2;
|
|
6001
|
+
const cells = [];
|
|
6002
|
+
for (let k = 0; k < n; k++) {
|
|
6003
|
+
const x = k * colW;
|
|
6004
|
+
const aCpu = log1p((res.cpu_seconds[k] || 0) / bucketSecs);
|
|
6005
|
+
const aRss = logRss(res.rss ? res.rss[k] || 0 : 0);
|
|
6006
|
+
// Low-contrast caps so the agent name / status stay crisp on top.
|
|
6007
|
+
cells.push(
|
|
6008
|
+
`<rect x="${x.toFixed(2)}" y="0" width="${(colW - 0.5).toFixed(2)}" height="${rowH.toFixed(2)}" fill="oklch(62% 0.15 35)" opacity="${(aCpu * 0.3).toFixed(2)}"/>`,
|
|
6009
|
+
`<rect x="${x.toFixed(2)}" y="${(rowH + 2).toFixed(2)}" width="${(colW - 0.5).toFixed(2)}" height="${rowH.toFixed(2)}" fill="oklch(65% 0.14 262)" opacity="${(aRss * 0.3).toFixed(2)}"/>`,
|
|
6010
|
+
);
|
|
6011
|
+
}
|
|
6012
|
+
return `<div class="ioheat"><svg width="${w}" height="${h}" viewBox="0 0 ${w} ${h}" preserveAspectRatio="none" shape-rendering="crispEdges" style="position:absolute;inset:0;width:100%;height:100%">${cells.join("")}</svg></div>`;
|
|
6008
6013
|
}
|
|
6009
6014
|
function renderList() {
|
|
6010
6015
|
// The graph view (/r/) only exists on the hosted origin — a local
|
|
@@ -6049,7 +6054,7 @@ my.translate = async (text, lang) => {
|
|
|
6049
6054
|
const t = e.title || e.status_text || e.prompt || "";
|
|
6050
6055
|
const id = compactIdent(e, ctx, 3, r.parentEntry);
|
|
6051
6056
|
const cli = cliLabel(e);
|
|
6052
|
-
return `<div class="row crow ${e._key === sel ? "sel" : ""}${rowFlags(e)}" data-key="${esc(e._key)}">${rowIoHeat(e
|
|
6057
|
+
return `<div class="row crow ${e._key === sel ? "sel" : ""}${rowFlags(e)}" data-key="${esc(e._key)}">${rowIoHeat(e)}
|
|
6053
6058
|
${r.branch ? `<span class="tbranch">${esc(r.branch)}</span>` : ""}
|
|
6054
6059
|
${pinMarkHtml(e)}
|
|
6055
6060
|
<span class="dot ${esc(e.status)}"></span>
|
|
@@ -6081,7 +6086,7 @@ my.translate = async (text, lang) => {
|
|
|
6081
6086
|
`<span class="rtag" data-k="${k}"><span style="opacity:.55">${k}:</span>${esc(v)}</span>`,
|
|
6082
6087
|
)
|
|
6083
6088
|
.join("");
|
|
6084
|
-
return `<div class="row ${e._key === sel ? "sel" : ""}${rowFlags(e)}" data-key="${esc(e._key)}">${rowIoHeat(e
|
|
6089
|
+
return `<div class="row ${e._key === sel ? "sel" : ""}${rowFlags(e)}" data-key="${esc(e._key)}">${rowIoHeat(e)}
|
|
6085
6090
|
<div class="r1">${r.branch ? `<span class="tbranch">${esc(r.branch)}</span>` : ""}${pinMarkHtml(e)}<span class="dot ${esc(e.status)}"></span>
|
|
6086
6091
|
<span class="name">${esc(cliLabel(e) || ident(e) || "agent")}</span>
|
|
6087
6092
|
<span class="badge">pid ${e.pid}</span>
|
|
@@ -6157,6 +6162,14 @@ my.translate = async (text, lang) => {
|
|
|
6157
6162
|
// so this stays a no-op on the wire for a modern fleet — it still re-renders
|
|
6158
6163
|
// every tick, which keeps the relative "age" column fresh.
|
|
6159
6164
|
async function loadList() {
|
|
6165
|
+
// Refresh the local-host system snapshot for the room header line.
|
|
6166
|
+
localTx
|
|
6167
|
+
.fetchJSON("/api/host")
|
|
6168
|
+
.then((h) => {
|
|
6169
|
+
hostRes = h;
|
|
6170
|
+
})
|
|
6171
|
+
.catch(() => {});
|
|
6172
|
+
|
|
6160
6173
|
const srcs = [...sources.values()];
|
|
6161
6174
|
await Promise.all(srcs.filter((s) => !s.streaming).map(listSource));
|
|
6162
6175
|
mergeRender();
|
|
@@ -9430,9 +9443,18 @@ my.translate = async (text, lang) => {
|
|
|
9430
9443
|
// The handle itself lives inside the tip; hit-test against it directly.
|
|
9431
9444
|
if (!tip.contains(e.target)) return;
|
|
9432
9445
|
if (e.clientY - tr.top > HANDLE) return;
|
|
9433
|
-
drag = {
|
|
9446
|
+
drag = {
|
|
9447
|
+
id: e.pointerId,
|
|
9448
|
+
x: e.clientX,
|
|
9449
|
+
y: e.clientY,
|
|
9450
|
+
ox: tr.left,
|
|
9451
|
+
oy: tr.top,
|
|
9452
|
+
moved: false,
|
|
9453
|
+
};
|
|
9434
9454
|
suppressClick = false;
|
|
9435
|
-
try {
|
|
9455
|
+
try {
|
|
9456
|
+
tip.setPointerCapture(e.pointerId);
|
|
9457
|
+
} catch {}
|
|
9436
9458
|
e.preventDefault();
|
|
9437
9459
|
});
|
|
9438
9460
|
pill.addEventListener("pointermove", (e) => {
|
|
@@ -9456,7 +9478,11 @@ my.translate = async (text, lang) => {
|
|
|
9456
9478
|
const tip = tipEl();
|
|
9457
9479
|
drag = null;
|
|
9458
9480
|
if (tip) tip.classList.remove("dragging");
|
|
9459
|
-
if (tip) {
|
|
9481
|
+
if (tip) {
|
|
9482
|
+
try {
|
|
9483
|
+
tip.releasePointerCapture(e.pointerId);
|
|
9484
|
+
} catch {}
|
|
9485
|
+
}
|
|
9460
9486
|
if (!moved) return;
|
|
9461
9487
|
if (tip) {
|
|
9462
9488
|
pos = {
|
|
@@ -9464,7 +9490,9 @@ my.translate = async (text, lang) => {
|
|
|
9464
9490
|
y: parseFloat(tip.style.top) || 8,
|
|
9465
9491
|
};
|
|
9466
9492
|
}
|
|
9467
|
-
try {
|
|
9493
|
+
try {
|
|
9494
|
+
localStorage.setItem(KEY, JSON.stringify(pos));
|
|
9495
|
+
} catch {}
|
|
9468
9496
|
suppressClick = true;
|
|
9469
9497
|
setTimeout(() => (suppressClick = false), 0);
|
|
9470
9498
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-yes",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.288.0",
|
|
4
4
|
"description": "A wrapper tool that automates interactions with various AI CLI tools by automatically handling common prompts and responses.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -127,6 +127,7 @@
|
|
|
127
127
|
"release": "standard-version && npm publish",
|
|
128
128
|
"release:beta": "standard-version && npm publish --tag beta",
|
|
129
129
|
"test": "vitest run",
|
|
130
|
+
"test:bun": "bun test --timeout 30000 .bun.",
|
|
130
131
|
"test:coverage": "vitest run --coverage",
|
|
131
132
|
"test:ui": "vitest run --config tests/ui-test/vitest.config.ts",
|
|
132
133
|
"test:ui-dom": "vitest run --config tests/ui-dom/vitest.config.ts",
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { describe, expect, it, beforeEach, afterEach } from "bun:test";
|
|
2
|
+
import { JsonlStore } from "./JsonlStore";
|
|
3
|
+
import { rm, readFile, writeFile, mkdir } from "fs/promises";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import os from "os";
|
|
6
|
+
|
|
7
|
+
const TEST_DIR = path.join(os.tmpdir(), "jsonlstore-test-" + process.pid);
|
|
8
|
+
const TEST_FILE = path.join(TEST_DIR, "test.jsonl");
|
|
9
|
+
|
|
10
|
+
describe("JsonlStore", () => {
|
|
11
|
+
let store: JsonlStore;
|
|
12
|
+
|
|
13
|
+
beforeEach(async () => {
|
|
14
|
+
try {
|
|
15
|
+
await rm(TEST_DIR, { recursive: true, force: true });
|
|
16
|
+
} catch {
|
|
17
|
+
// ignore cleanup failures (e.g. Windows lock files from previous test)
|
|
18
|
+
}
|
|
19
|
+
await mkdir(TEST_DIR, { recursive: true });
|
|
20
|
+
store = new JsonlStore(TEST_FILE);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterEach(async () => {
|
|
24
|
+
try {
|
|
25
|
+
await rm(TEST_DIR, { recursive: true, force: true });
|
|
26
|
+
} catch {
|
|
27
|
+
// ignore cleanup failures
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe("load", () => {
|
|
32
|
+
it("should return empty map for new file", async () => {
|
|
33
|
+
const docs = await store.load();
|
|
34
|
+
expect(docs.size).toBe(0);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("should load existing JSONL data", async () => {
|
|
38
|
+
await writeFile(TEST_FILE, '{"_id":"1","name":"Alice"}\n{"_id":"2","name":"Bob"}\n');
|
|
39
|
+
const docs = await store.load();
|
|
40
|
+
expect(docs.size).toBe(2);
|
|
41
|
+
expect(docs.get("1")).toEqual({ _id: "1", name: "Alice" });
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("should merge duplicate IDs (last wins)", async () => {
|
|
45
|
+
await writeFile(
|
|
46
|
+
TEST_FILE,
|
|
47
|
+
'{"_id":"1","name":"Alice","age":30}\n{"_id":"1","name":"Alice Updated"}\n',
|
|
48
|
+
);
|
|
49
|
+
const docs = await store.load();
|
|
50
|
+
expect(docs.size).toBe(1);
|
|
51
|
+
expect(docs.get("1")).toEqual({ _id: "1", name: "Alice Updated", age: 30 });
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("should handle $$deleted tombstones", async () => {
|
|
55
|
+
await writeFile(TEST_FILE, '{"_id":"1","name":"Alice"}\n{"_id":"1","$$deleted":true}\n');
|
|
56
|
+
const docs = await store.load();
|
|
57
|
+
expect(docs.size).toBe(0);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("should skip lines without _id", async () => {
|
|
61
|
+
await writeFile(TEST_FILE, '{"name":"no id"}\n{"_id":"1","name":"valid"}\n');
|
|
62
|
+
const docs = await store.load();
|
|
63
|
+
expect(docs.size).toBe(1);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("should skip corrupt lines gracefully", async () => {
|
|
67
|
+
await writeFile(
|
|
68
|
+
TEST_FILE,
|
|
69
|
+
'{"_id":"1","name":"ok"}\n{corrupt json\n{"_id":"2","name":"also ok"}\n',
|
|
70
|
+
);
|
|
71
|
+
const docs = await store.load();
|
|
72
|
+
expect(docs.size).toBe(2);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("should recover from temp file when main file missing", async () => {
|
|
76
|
+
const tempFile = TEST_FILE + "~";
|
|
77
|
+
await writeFile(tempFile, '{"_id":"1","name":"recovered"}\n');
|
|
78
|
+
const docs = await store.load();
|
|
79
|
+
expect(docs.size).toBe(1);
|
|
80
|
+
expect(docs.get("1")!.name).toBe("recovered");
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe("getAll / getById / find / findOne", () => {
|
|
85
|
+
beforeEach(async () => {
|
|
86
|
+
await writeFile(
|
|
87
|
+
TEST_FILE,
|
|
88
|
+
'{"_id":"1","name":"Alice","age":30}\n{"_id":"2","name":"Bob","age":25}\n{"_id":"3","name":"Charlie","age":35}\n',
|
|
89
|
+
);
|
|
90
|
+
await store.load();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("getAll returns all documents", () => {
|
|
94
|
+
expect(store.getAll()).toHaveLength(3);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("getById returns correct doc", () => {
|
|
98
|
+
expect(store.getById("2")).toEqual({ _id: "2", name: "Bob", age: 25 });
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("getById returns undefined for missing id", () => {
|
|
102
|
+
expect(store.getById("999")).toBeUndefined();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("find returns matching docs", () => {
|
|
106
|
+
const result = store.find((d) => d.age > 28);
|
|
107
|
+
expect(result).toHaveLength(2);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("findOne returns first match", () => {
|
|
111
|
+
const result = store.findOne((d) => d.age > 28);
|
|
112
|
+
expect(result).toBeDefined();
|
|
113
|
+
expect(result!.age).toBeGreaterThan(28);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("findOne returns undefined when no match", () => {
|
|
117
|
+
expect(store.findOne((d) => d.age > 100)).toBeUndefined();
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe("append", () => {
|
|
122
|
+
it("should append and return doc with generated id", async () => {
|
|
123
|
+
await store.load();
|
|
124
|
+
const doc = await store.append({ name: "test" } as any);
|
|
125
|
+
expect(doc._id).toBeDefined();
|
|
126
|
+
expect(doc.name).toBe("test");
|
|
127
|
+
expect(store.getAll()).toHaveLength(1);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("should append with provided _id", async () => {
|
|
131
|
+
await store.load();
|
|
132
|
+
const doc = await store.append({ _id: "custom-id", name: "test" } as any);
|
|
133
|
+
expect(doc._id).toBe("custom-id");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("should merge with existing doc of same _id", async () => {
|
|
137
|
+
await store.load();
|
|
138
|
+
await store.append({ _id: "x", name: "first", value: 1 } as any);
|
|
139
|
+
await store.append({ _id: "x", name: "second" } as any);
|
|
140
|
+
const doc = store.getById("x")!;
|
|
141
|
+
expect(doc.name).toBe("second");
|
|
142
|
+
expect(doc.value).toBe(1);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe("updateById", () => {
|
|
147
|
+
it("should update existing doc", async () => {
|
|
148
|
+
await store.load();
|
|
149
|
+
await store.append({ _id: "u1", name: "original", status: "active" } as any);
|
|
150
|
+
await store.updateById("u1", { status: "done" });
|
|
151
|
+
expect(store.getById("u1")!.status).toBe("done");
|
|
152
|
+
expect(store.getById("u1")!.name).toBe("original");
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("should no-op for non-existent id", async () => {
|
|
156
|
+
await store.load();
|
|
157
|
+
await store.updateById("nonexistent", { status: "done" });
|
|
158
|
+
expect(store.getAll()).toHaveLength(0);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe("deleteById", () => {
|
|
163
|
+
it("should remove doc from memory and write tombstone", async () => {
|
|
164
|
+
await store.load();
|
|
165
|
+
await store.append({ _id: "d1", name: "to delete" } as any);
|
|
166
|
+
expect(store.getAll()).toHaveLength(1);
|
|
167
|
+
|
|
168
|
+
await store.deleteById("d1");
|
|
169
|
+
expect(store.getAll()).toHaveLength(0);
|
|
170
|
+
expect(store.getById("d1")).toBeUndefined();
|
|
171
|
+
|
|
172
|
+
// Tombstone should be written to file
|
|
173
|
+
const content = await readFile(TEST_FILE, "utf-8");
|
|
174
|
+
expect(content).toContain('"$$deleted":true');
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe("compact", () => {
|
|
179
|
+
it("should deduplicate entries", async () => {
|
|
180
|
+
await store.load();
|
|
181
|
+
await store.append({ _id: "c1", name: "v1" } as any);
|
|
182
|
+
await store.updateById("c1", { name: "v2" });
|
|
183
|
+
await store.updateById("c1", { name: "v3" });
|
|
184
|
+
|
|
185
|
+
// Before compact: 3 lines
|
|
186
|
+
const before = (await readFile(TEST_FILE, "utf-8")).trim().split("\n");
|
|
187
|
+
expect(before).toHaveLength(3);
|
|
188
|
+
|
|
189
|
+
await store.compact();
|
|
190
|
+
|
|
191
|
+
// After compact: 1 line
|
|
192
|
+
const after = (await readFile(TEST_FILE, "utf-8")).trim().split("\n");
|
|
193
|
+
expect(after).toHaveLength(1);
|
|
194
|
+
expect(JSON.parse(after[0]!).name).toBe("v3");
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("should handle empty store", async () => {
|
|
198
|
+
await store.load();
|
|
199
|
+
await store.compact();
|
|
200
|
+
const content = await readFile(TEST_FILE, "utf-8");
|
|
201
|
+
expect(content).toBe("");
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { ReadyManager } from "./ReadyManager";
|
|
3
|
+
|
|
4
|
+
describe("ReadyManager", () => {
|
|
5
|
+
it("should start in not ready state", () => {
|
|
6
|
+
const manager = new ReadyManager();
|
|
7
|
+
expect(manager.wait()).toBeInstanceOf(Promise);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("should resolve wait when ready is called", async () => {
|
|
11
|
+
const manager = new ReadyManager();
|
|
12
|
+
const waitPromise = manager.wait();
|
|
13
|
+
|
|
14
|
+
manager.ready();
|
|
15
|
+
|
|
16
|
+
await expect(waitPromise).resolves.toBeUndefined();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("should resolve immediately if already ready", async () => {
|
|
20
|
+
const manager = new ReadyManager();
|
|
21
|
+
manager.ready();
|
|
22
|
+
|
|
23
|
+
const result = manager.wait();
|
|
24
|
+
expect(result).toBeUndefined();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("should handle multiple waiters", async () => {
|
|
28
|
+
const manager = new ReadyManager();
|
|
29
|
+
const wait1 = manager.wait();
|
|
30
|
+
const wait2 = manager.wait();
|
|
31
|
+
const wait3 = manager.wait();
|
|
32
|
+
|
|
33
|
+
manager.ready();
|
|
34
|
+
|
|
35
|
+
await Promise.all([
|
|
36
|
+
expect(wait1).resolves.toBeUndefined(),
|
|
37
|
+
expect(wait2).resolves.toBeUndefined(),
|
|
38
|
+
expect(wait3).resolves.toBeUndefined(),
|
|
39
|
+
]);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("should reset to not ready when unready is called", async () => {
|
|
43
|
+
const manager = new ReadyManager();
|
|
44
|
+
manager.ready();
|
|
45
|
+
manager.unready();
|
|
46
|
+
|
|
47
|
+
expect(manager.wait()).toBeInstanceOf(Promise);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("should handle ready with no waiting queue", () => {
|
|
51
|
+
const manager = new ReadyManager();
|
|
52
|
+
manager.ready(); // Should not throw even if no one is waiting
|
|
53
|
+
expect(manager.wait()).toBeUndefined(); // Should be ready now
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("should handle multiple ready/unready cycles", async () => {
|
|
57
|
+
const manager = new ReadyManager();
|
|
58
|
+
|
|
59
|
+
// First cycle
|
|
60
|
+
const wait1 = manager.wait();
|
|
61
|
+
manager.ready();
|
|
62
|
+
await wait1;
|
|
63
|
+
|
|
64
|
+
// Reset
|
|
65
|
+
manager.unready();
|
|
66
|
+
|
|
67
|
+
// Second cycle
|
|
68
|
+
const wait2 = manager.wait();
|
|
69
|
+
manager.ready();
|
|
70
|
+
await expect(wait2).resolves.toBeUndefined();
|
|
71
|
+
});
|
|
72
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { agentNiceValue } from "./agentNice.ts";
|
|
3
|
+
|
|
4
|
+
describe("agentNice.agentNiceValue", () => {
|
|
5
|
+
it("defaults to 5 when unset or empty", () => {
|
|
6
|
+
expect(agentNiceValue({})).toBe(5);
|
|
7
|
+
expect(agentNiceValue({ AGENT_YES_AGENT_NICE: "" })).toBe(5);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("honors a valid positive value", () => {
|
|
11
|
+
expect(agentNiceValue({ AGENT_YES_AGENT_NICE: "10" })).toBe(10);
|
|
12
|
+
expect(agentNiceValue({ AGENT_YES_AGENT_NICE: "0" })).toBe(0); // 0 = disabled
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("clamps into the 0..19 nice range (never elevates)", () => {
|
|
16
|
+
expect(agentNiceValue({ AGENT_YES_AGENT_NICE: "-5" })).toBe(0); // no negative (needs CAP_SYS_NICE)
|
|
17
|
+
expect(agentNiceValue({ AGENT_YES_AGENT_NICE: "40" })).toBe(19);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("falls back to the default on garbage", () => {
|
|
21
|
+
expect(agentNiceValue({ AGENT_YES_AGENT_NICE: "abc" })).toBe(5);
|
|
22
|
+
expect(agentNiceValue({ AGENT_YES_AGENT_NICE: "3.9" })).toBe(3); // truncates
|
|
23
|
+
});
|
|
24
|
+
});
|