agent-yes 1.206.0 → 1.208.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-DQSgURGW.js → SUPPORTED_CLIS-C38pi6qs.js} +2 -2
- package/dist/SUPPORTED_CLIS-DTqtsVC5.js +9 -0
- package/dist/{agentShare-k8224qiZ.js → agentShare-sjB7JIAq.js} +2 -2
- package/dist/cli.js +4 -4
- package/dist/index.js +2 -2
- package/dist/{notifyDaemon-BbVnNvlo.js → notifyDaemon-CyEnGsPI.js} +2 -2
- package/dist/{rustBinary-BjRvvEs8.js → rustBinary-Cy1JDX9Q.js} +2 -2
- package/dist/{schedule-DJ-v4yVa.js → schedule-Co1vB9EZ.js} +4 -4
- package/dist/{serve-crLypY_c.js → serve-CMUfHfqa.js} +108 -14
- package/dist/{setup-BPNkeSmY.js → setup-aMt_-16G.js} +2 -2
- package/dist/{subcommands-DCGZjr4t.js → subcommands-DVHfue6K.js} +1 -1
- package/dist/{subcommands-DkrMRVNh.js → subcommands-Dvg-RHy6.js} +7 -7
- package/dist/{ts-DnqRreF4.js → ts-_opGnkmz.js} +2 -2
- package/dist/{versionChecker-d5c6_vgS.js → versionChecker-BSmUzVSM.js} +2 -2
- package/dist/{ws-BmHpWaG_.js → ws-CKoSZ9CN.js} +2 -2
- package/lab/ui/console-logic.js +20 -0
- package/lab/ui/index.html +190 -76
- package/package.json +1 -1
- package/ts/serve.ts +109 -6
- package/ts/serveLock.spec.ts +15 -14
- package/ts/sizeNego.spec.ts +56 -0
- package/ts/sizeNego.ts +59 -0
- package/ts/ws.spec.ts +8 -10
- package/dist/SUPPORTED_CLIS-CQ6e94ln.js +0 -9
package/ts/serveLock.spec.ts
CHANGED
|
@@ -140,7 +140,8 @@ describe("acquireWebrtcHostLock — held-lock lifecycle", () => {
|
|
|
140
140
|
});
|
|
141
141
|
|
|
142
142
|
const ownerFile = () => path.join(home, "webrtc-host.lock", "owner.json");
|
|
143
|
-
const readOwnerFile = async () =>
|
|
143
|
+
const readOwnerFile = async () =>
|
|
144
|
+
JSON.parse(await readFile(ownerFile(), "utf-8")) as ServeLockOwner;
|
|
144
145
|
|
|
145
146
|
it("heartbeats refresh beat_at while held", async () => {
|
|
146
147
|
const got = await acquireWebrtcHostLock({ graceMs: 0, beatMs: 40 });
|
|
@@ -190,19 +191,19 @@ describe("acquireWebrtcHostLock — held-lock lifecycle", () => {
|
|
|
190
191
|
it.skipIf(process.platform === "win32")(
|
|
191
192
|
"escalates to SIGKILL when the owner ignores SIGTERM",
|
|
192
193
|
async () => {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
194
|
+
const { spawn } = await import("node:child_process");
|
|
195
|
+
const stubborn = spawn("/bin/sh", ["-c", "trap '' TERM; sleep 30"], { stdio: "ignore" });
|
|
196
|
+
const pid = stubborn.pid!;
|
|
197
|
+
await new Promise((r) => setTimeout(r, 100)); // let the trap install
|
|
198
|
+
const { mkdir: mkd, writeFile: wf } = await import("fs/promises");
|
|
199
|
+
await mkd(path.join(home, "webrtc-host.lock"), { recursive: true });
|
|
200
|
+
await wf(ownerFile(), JSON.stringify({ pid, started_at: Date.now(), beat_at: Date.now() }));
|
|
201
|
+
|
|
202
|
+
const got = await acquireWebrtcHostLock({ takeover: true, graceMs: 0, takeoverWaitMs: 200 });
|
|
203
|
+
expect(got.ok).toBe(true);
|
|
204
|
+
if (got.ok) releases.push(got.release);
|
|
205
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
206
|
+
expect(stubborn.signalCode).toBe("SIGKILL");
|
|
206
207
|
},
|
|
207
208
|
);
|
|
208
209
|
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { NEGO_FLOOR_COLS, NEGO_FLOOR_ROWS, negotiateSize, sanitizeCap } from "./sizeNego.ts";
|
|
3
|
+
|
|
4
|
+
describe("sanitizeCap", () => {
|
|
5
|
+
it("accepts a normal viewer capacity", () => {
|
|
6
|
+
expect(sanitizeCap({ cols: 80, rows: 24 })).toEqual({ cols: 80, rows: 24 });
|
|
7
|
+
});
|
|
8
|
+
it("floors fractional values", () => {
|
|
9
|
+
expect(sanitizeCap({ cols: 80.9, rows: 24.7 })).toEqual({ cols: 80, rows: 24 });
|
|
10
|
+
});
|
|
11
|
+
it("rejects null / non-object / missing fields", () => {
|
|
12
|
+
expect(sanitizeCap(null)).toBeNull();
|
|
13
|
+
expect(sanitizeCap(undefined)).toBeNull();
|
|
14
|
+
expect(sanitizeCap("80x24")).toBeNull();
|
|
15
|
+
expect(sanitizeCap({})).toBeNull();
|
|
16
|
+
expect(sanitizeCap({ cols: 80 })).toBeNull();
|
|
17
|
+
});
|
|
18
|
+
it("rejects out-of-range junk (mid-layout 0x0, absurd sizes)", () => {
|
|
19
|
+
expect(sanitizeCap({ cols: 0, rows: 0 })).toBeNull();
|
|
20
|
+
expect(sanitizeCap({ cols: 10, rows: 24 })).toBeNull(); // below min cols
|
|
21
|
+
expect(sanitizeCap({ cols: 80, rows: 2 })).toBeNull(); // below min rows
|
|
22
|
+
expect(sanitizeCap({ cols: 9999, rows: 24 })).toBeNull();
|
|
23
|
+
expect(sanitizeCap({ cols: 80, rows: 9999 })).toBeNull();
|
|
24
|
+
expect(sanitizeCap({ cols: NaN, rows: 24 })).toBeNull();
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe("negotiateSize", () => {
|
|
29
|
+
it("returns null with no caps (withdraw → tty size rules)", () => {
|
|
30
|
+
expect(negotiateSize([])).toBeNull();
|
|
31
|
+
});
|
|
32
|
+
it("single viewer: adopts its capacity", () => {
|
|
33
|
+
expect(negotiateSize([{ cols: 133, rows: 40 }])).toEqual({ cols: 133, rows: 40 });
|
|
34
|
+
});
|
|
35
|
+
it("phone + desktop: elementwise min (the tmux rule)", () => {
|
|
36
|
+
// phone is narrow but tall, desktop is wide but short → min of each axis
|
|
37
|
+
const phone = { cols: 51, rows: 60 };
|
|
38
|
+
const desktop = { cols: 200, rows: 50 };
|
|
39
|
+
expect(negotiateSize([phone, desktop])).toEqual({ cols: 51, rows: 50 });
|
|
40
|
+
});
|
|
41
|
+
it("clamps to the floor so a tiny viewer can't wedge the agent", () => {
|
|
42
|
+
expect(negotiateSize([{ cols: 20, rows: 5 }])).toEqual({
|
|
43
|
+
cols: NEGO_FLOOR_COLS,
|
|
44
|
+
rows: NEGO_FLOOR_ROWS,
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
it("three viewers: min wins per axis", () => {
|
|
48
|
+
expect(
|
|
49
|
+
negotiateSize([
|
|
50
|
+
{ cols: 100, rows: 30 },
|
|
51
|
+
{ cols: 80, rows: 50 },
|
|
52
|
+
{ cols: 120, rows: 24 },
|
|
53
|
+
]),
|
|
54
|
+
).toEqual({ cols: 80, rows: 24 });
|
|
55
|
+
});
|
|
56
|
+
});
|
package/ts/sizeNego.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-viewer PTY size negotiation — the tmux "smallest client wins" rule.
|
|
3
|
+
*
|
|
4
|
+
* Every writable web viewer reports its readable capacity (the cols×rows its
|
|
5
|
+
* pane can render at its chosen font size) via /api/presence. The host resizes
|
|
6
|
+
* the agent's PTY to the elementwise minimum across live viewers, so the
|
|
7
|
+
* narrowest screen (a phone) gets a grid it can actually read while wider
|
|
8
|
+
* viewers simply render fewer columns. When the last capacity-reporting viewer
|
|
9
|
+
* leaves, the negotiated size is withdrawn and the PTY falls back to the real
|
|
10
|
+
* terminal's size (the wrapper re-reads the tty once the winsize file is gone).
|
|
11
|
+
*
|
|
12
|
+
* Pure logic only — the serve layer owns presence bookkeeping, the winsize
|
|
13
|
+
* file, and SIGWINCH delivery.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export interface SizeCap {
|
|
17
|
+
cols: number;
|
|
18
|
+
rows: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Reject junk caps (a viewer mid-layout can report 0×0 or absurd numbers). */
|
|
22
|
+
const CAP_MIN_COLS = 20;
|
|
23
|
+
const CAP_MIN_ROWS = 5;
|
|
24
|
+
const CAP_MAX_COLS = 500;
|
|
25
|
+
const CAP_MAX_ROWS = 200;
|
|
26
|
+
|
|
27
|
+
/** Never negotiate below this — TUIs (claude, codex) break down when the grid
|
|
28
|
+
* gets absurdly narrow, so a tiny watch-sized viewer can't wedge the agent. */
|
|
29
|
+
export const NEGO_FLOOR_COLS = 40;
|
|
30
|
+
export const NEGO_FLOOR_ROWS = 10;
|
|
31
|
+
|
|
32
|
+
/** Parse a presence-reported cap into a sane SizeCap, or null if unusable. */
|
|
33
|
+
export function sanitizeCap(cap: unknown): SizeCap | null {
|
|
34
|
+
if (typeof cap !== "object" || cap === null) return null;
|
|
35
|
+
const c = Math.floor(Number((cap as SizeCap).cols) || 0);
|
|
36
|
+
const r = Math.floor(Number((cap as SizeCap).rows) || 0);
|
|
37
|
+
if (c < CAP_MIN_COLS || c > CAP_MAX_COLS) return null;
|
|
38
|
+
if (r < CAP_MIN_ROWS || r > CAP_MAX_ROWS) return null;
|
|
39
|
+
return { cols: c, rows: r };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Elementwise minimum over the live viewers' capacities, clamped to the floor.
|
|
44
|
+
* Returns null when no viewer reports a capacity — meaning "withdraw the
|
|
45
|
+
* negotiated size, let the real tty size rule again".
|
|
46
|
+
*/
|
|
47
|
+
export function negotiateSize(caps: ReadonlyArray<SizeCap>): SizeCap | null {
|
|
48
|
+
let cols = Infinity;
|
|
49
|
+
let rows = Infinity;
|
|
50
|
+
for (const cap of caps) {
|
|
51
|
+
if (cap.cols < cols) cols = cap.cols;
|
|
52
|
+
if (cap.rows < rows) rows = cap.rows;
|
|
53
|
+
}
|
|
54
|
+
if (!Number.isFinite(cols) || !Number.isFinite(rows)) return null;
|
|
55
|
+
return {
|
|
56
|
+
cols: Math.max(NEGO_FLOOR_COLS, cols),
|
|
57
|
+
rows: Math.max(NEGO_FLOOR_ROWS, rows),
|
|
58
|
+
};
|
|
59
|
+
}
|
package/ts/ws.spec.ts
CHANGED
|
@@ -468,16 +468,14 @@ describe("collectWorkspaces / workspaceStatus (mocked provision)", () => {
|
|
|
468
468
|
beforeEach(() => {
|
|
469
469
|
root = realpathSync(mkdtempSync(path.join(os.tmpdir(), "ay-ws-api-")));
|
|
470
470
|
prov.wsRoot = root;
|
|
471
|
-
prov.readStatus
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
hasUpstream: true,
|
|
480
|
-
});
|
|
471
|
+
prov.readStatus.mockReset().mockResolvedValue({
|
|
472
|
+
branch: "main",
|
|
473
|
+
head: "abc123",
|
|
474
|
+
ahead: 1,
|
|
475
|
+
behind: 0,
|
|
476
|
+
dirty: true,
|
|
477
|
+
hasUpstream: true,
|
|
478
|
+
});
|
|
481
479
|
homeBackup = process.env.AGENT_YES_HOME;
|
|
482
480
|
process.env.AGENT_YES_HOME = path.join(root, ".ay-home");
|
|
483
481
|
});
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import "./ts-DnqRreF4.js";
|
|
2
|
-
import "./logger-CDIsZ-Pp.js";
|
|
3
|
-
import "./versionChecker-d5c6_vgS.js";
|
|
4
|
-
import "./pidStore-BIvsBQ8X.js";
|
|
5
|
-
import "./globalPidIndex-CoNr7tS8.js";
|
|
6
|
-
import "./messageLog-CxrKJj77.js";
|
|
7
|
-
import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DQSgURGW.js";
|
|
8
|
-
|
|
9
|
-
export { SUPPORTED_CLIS };
|