claude-yes 1.153.2 → 1.154.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/README.md +1 -0
- package/agent-yes.config.schema.json +4 -0
- package/default.config.yaml +27 -0
- package/dist/SUPPORTED_CLIS-CmM_zBrM.js +8 -0
- package/dist/{SUPPORTED_CLIS-DsOUGcZj.js → SUPPORTED_CLIS-Dam11bMz.js} +2 -2
- package/dist/{agent-yes.config-BKtCmmy9.js → agent-yes.config-Bl08kMpl.js} +2 -2
- package/dist/cli.js +5 -5
- package/dist/{configShared-C1C04bbq.js → configShared-CEyhl0WH.js} +3 -2
- package/dist/{e2e-ClOI_aqV.js → e2e-Bfw7qL9O.js} +1 -1
- package/dist/index.js +2 -2
- package/dist/openBrowser-CuOlFtow.js +71 -0
- package/dist/{remotes-qK6uozO4.js → remotes-DBCvpp3B.js} +3 -3
- package/dist/{remotes-5lHLAAqn.js → remotes-DkNuq417.js} +3 -3
- package/dist/{schedule-Bxei036B.js → schedule-BPFBe7R5.js} +5 -5
- package/dist/{serve-CoUlCHxT.js → serve-BYc2J-2F.js} +25 -18
- package/dist/{setup-CSgScgPD.js → setup-D9emVJYz.js} +3 -3
- package/dist/{share-ShLKJTUE.js → share-Bq_tDXQU.js} +2 -2
- package/dist/{subcommands-CapfwHNa.js → subcommands-C4tMi9NX.js} +102 -18
- package/dist/subcommands-DLvC6kto.js +9 -0
- package/dist/{tray-5Hezw9uj.js → tray-BMzpUSfa.js} +1 -1
- package/dist/{ts-C4ZmPc5P.js → ts-U3AE4u72.js} +3 -3
- package/dist/{versionChecker-CqRHax_g.js → versionChecker-B-ToHoJ2.js} +2 -2
- package/dist/{webrtcLink-BWhuA74k.js → webrtcLink-CBZkZ-LT.js} +2 -2
- package/dist/{webrtcRemote-jGM3ZHK3.js → webrtcRemote-Ccdzmuc-.js} +3 -3
- package/dist/{workspaceConfig-B9VX8tVO.js → workspaceConfig-DT3cCBZK.js} +1 -1
- package/lab/ui/index.html +1 -1
- package/package.json +1 -1
- package/ts/buildRustArgs.spec.ts +5 -1
- package/ts/configShared.ts +4 -0
- package/ts/globalPidIndex.ts +5 -0
- package/ts/index.ts +1 -0
- package/ts/openBrowser.spec.ts +130 -0
- package/ts/openBrowser.ts +67 -0
- package/ts/serve.ts +19 -6
- package/ts/subcommands.spec.ts +119 -2
- package/ts/subcommands.ts +104 -7
- package/dist/SUPPORTED_CLIS-BsfuhkaH.js +0 -8
- package/dist/subcommands-6M1qZllB.js +0 -9
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { t as invokedCliName } from "./invokedCli-uqM2YYA7.js";
|
|
2
2
|
import { t as agentYesHome } from "./agentYesHome-_eJa3DaX.js";
|
|
3
3
|
import { a as updateGlobalPidStatus, i as readGlobalPids } from "./globalPidIndex-DlmmJlO8.js";
|
|
4
|
-
import { t as loadSharedCliDefaults } from "./configShared-
|
|
5
|
-
import { n as isWebrtcSpec } from "./webrtcLink-
|
|
6
|
-
import { a as resolveRemoteSpec, i as readRemotes } from "./remotes-
|
|
4
|
+
import { t as loadSharedCliDefaults } from "./configShared-CEyhl0WH.js";
|
|
5
|
+
import { n as isWebrtcSpec } from "./webrtcLink-CBZkZ-LT.js";
|
|
6
|
+
import { a as resolveRemoteSpec, i as readRemotes } from "./remotes-DBCvpp3B.js";
|
|
7
7
|
import ms from "ms";
|
|
8
8
|
import yargs from "yargs";
|
|
9
9
|
import { appendFile, mkdir, open, readFile, stat, writeFile } from "fs/promises";
|
|
@@ -554,19 +554,19 @@ async function runSubcommand(argv) {
|
|
|
554
554
|
case "restart": return await cmdRestart(rest);
|
|
555
555
|
case "note": return await cmdNote(rest);
|
|
556
556
|
case "serve": {
|
|
557
|
-
const { cmdServe } = await import("./serve-
|
|
557
|
+
const { cmdServe } = await import("./serve-BYc2J-2F.js");
|
|
558
558
|
return cmdServe(rest);
|
|
559
559
|
}
|
|
560
560
|
case "setup": {
|
|
561
|
-
const { cmdSetup } = await import("./setup-
|
|
561
|
+
const { cmdSetup } = await import("./setup-D9emVJYz.js");
|
|
562
562
|
return cmdSetup(rest);
|
|
563
563
|
}
|
|
564
564
|
case "schedule": {
|
|
565
|
-
const { cmdSchedule } = await import("./schedule-
|
|
565
|
+
const { cmdSchedule } = await import("./schedule-BPFBe7R5.js");
|
|
566
566
|
return cmdSchedule(rest);
|
|
567
567
|
}
|
|
568
568
|
case "remote": {
|
|
569
|
-
const { cmdRemote } = await import("./remotes-
|
|
569
|
+
const { cmdRemote } = await import("./remotes-DkNuq417.js");
|
|
570
570
|
return cmdRemote(rest);
|
|
571
571
|
}
|
|
572
572
|
case "reap":
|
|
@@ -583,7 +583,7 @@ async function runSubcommand(argv) {
|
|
|
583
583
|
}
|
|
584
584
|
function cmdHelp(managerCommands = true) {
|
|
585
585
|
const setupLine = managerCommands ? ` ay setup guided setup: pick a workspace, share to agent-yes.com\n` : ``;
|
|
586
|
-
process.stdout.write("ay - agent-yes CLI\n\nManagement:\n ay ls [keyword] list running agents\n ay tail [-f] [-n N] <keyword> last N lines (96), -f to follow\n ay read <keyword> [page opts] paginate: --last/--head N, --range A:B,\n --before-line L [--limit N]\n ay cat <keyword> full log\n ay head <keyword> first N lines\n ay send <keyword> <msg> send a message\n ay attach <keyword> interactive attach (detach: Ctrl-\\)\n ay stop <keyword> graceful shutdown (/exit for claude/codex)\n ay exit <keyword> [reason] graceful shutdown, recording who/why (= 'ay send <kw> exit')\n ay status <keyword> agent status snapshot\n ay result <keyword> [--wait] pull an agent's structured result envelope\n ay result set '<json>' (inside an agent) deposit your result envelope\n ay reap kill process groups leaked by dead agents\n\nRemote:\n" + setupLine + " ay schedule <when> <cli> -- <msg> run an agent on a schedule (HH:MM or cron)\n ay serve [--port N] start HTTP API server (prints token)\n ay serve status show serve daemon/server status\n ay remote add <alias> http://<token>@<host>:<port>\n ay remote ls / rm <alias> manage saved remotes\n ay ls <token>@<host>:<port> connect inline (no alias needed)\n ay send <token>@<host>:<port>:<kw> <msg>\n\nRun an agent:\n ay [claude|codex|gemini|...] [options] -- [prompt]\n ay claude -- \"fix the bug in auth.ts\"\n ay claude --help full agent-runner options\n\nLabs (examples at https://github.com/snomiao/agent-yes/tree/main/lab):\n local-role-play/ designer + builder on one machine\n http-remote/ ay serve remote access demo\n p2p-pairing/ libp2p P2P (needs: cargo build --features swarm)\n");
|
|
586
|
+
process.stdout.write("ay - agent-yes CLI\n\nManagement:\n ay ls [keyword] list running agents\n ay tail [-f] [-n N] <keyword> last N lines (96), -f to follow\n ay read <keyword> [page opts] paginate: --last/--head N, --range A:B,\n --before-line L [--limit N]\n ay cat <keyword> full log\n ay head <keyword> first N lines\n ay send <keyword> <msg> send a message\n ay attach <keyword> interactive attach (detach: Ctrl-\\)\n ay stop <keyword> graceful shutdown (/exit for claude/codex)\n ay exit <keyword> [reason] graceful shutdown, recording who/why (= 'ay send <kw> exit')\n ay restart <keyword> [--fresh] stop (if live) + relaunch resuming the session; --fresh replays the prompt\n ay status <keyword> agent status snapshot\n ay result <keyword> [--wait] pull an agent's structured result envelope\n ay result set '<json>' (inside an agent) deposit your result envelope\n ay reap kill process groups leaked by dead agents\n\nRemote:\n" + setupLine + " ay schedule <when> <cli> -- <msg> run an agent on a schedule (HH:MM or cron)\n ay serve [--port N] start HTTP API server (prints token)\n ay serve status show serve daemon/server status\n ay remote add <alias> http://<token>@<host>:<port>\n ay remote ls / rm <alias> manage saved remotes\n ay ls <token>@<host>:<port> connect inline (no alias needed)\n ay send <token>@<host>:<port>:<kw> <msg>\n\nRun an agent:\n ay [claude|codex|gemini|...] [options] -- [prompt]\n ay claude -- \"fix the bug in auth.ts\"\n ay claude --help full agent-runner options\n\nLabs (examples at https://github.com/snomiao/agent-yes/tree/main/lab):\n local-role-play/ designer + builder on one machine\n http-remote/ ay serve remote access demo\n p2p-pairing/ libp2p P2P (needs: cargo build --features swarm)\n");
|
|
587
587
|
return 0;
|
|
588
588
|
}
|
|
589
589
|
function matchKeyword(record, keyword) {
|
|
@@ -899,7 +899,7 @@ async function fetchRemoteRecordsRaw(url, token, opts) {
|
|
|
899
899
|
let base = url;
|
|
900
900
|
let bearer = token;
|
|
901
901
|
if (isWebrtcSpec(url)) {
|
|
902
|
-
const { startWebrtcBridge } = await import("./webrtcRemote-
|
|
902
|
+
const { startWebrtcBridge } = await import("./webrtcRemote-Ccdzmuc-.js");
|
|
903
903
|
bridge = await startWebrtcBridge(url);
|
|
904
904
|
base = bridge.baseUrl;
|
|
905
905
|
bearer = bridge.token;
|
|
@@ -1002,6 +1002,10 @@ async function deriveLiveState(r) {
|
|
|
1002
1002
|
state: "stopped",
|
|
1003
1003
|
question: null
|
|
1004
1004
|
};
|
|
1005
|
+
if (r.unresponsive) return {
|
|
1006
|
+
state: "stuck",
|
|
1007
|
+
question: null
|
|
1008
|
+
};
|
|
1005
1009
|
if (r.log_file) {
|
|
1006
1010
|
const ni = await extractNeedsInput(r.log_file, r.cli);
|
|
1007
1011
|
if (ni) return {
|
|
@@ -2283,14 +2287,74 @@ async function cmdAttach(rest) {
|
|
|
2283
2287
|
});
|
|
2284
2288
|
return 0;
|
|
2285
2289
|
}
|
|
2290
|
+
/**
|
|
2291
|
+
* Decide how to relaunch an agent on `ay restart`. Pure (no I/O) so it's unit
|
|
2292
|
+
* testable. Precedence:
|
|
2293
|
+
* - `fresh`: replay the original prompt (the old behaviour), no resume.
|
|
2294
|
+
* - else if the CLI printed a resume command its `resumeCommand` regex matches
|
|
2295
|
+
* in the captured log (capture group 1 = the arg string), relaunch with those
|
|
2296
|
+
* whitespace-split args.
|
|
2297
|
+
* - else fall back to `restoreArgs` (e.g. `--continue`) so the wrapper's own
|
|
2298
|
+
* resume plumbing (claude --continue, codex stored-session) kicks in.
|
|
2299
|
+
*/
|
|
2300
|
+
function resolveResumeArgs(conf, logText, opts) {
|
|
2301
|
+
if (opts.fresh) return opts.prompt ? {
|
|
2302
|
+
args: [opts.prompt],
|
|
2303
|
+
strategy: "fresh (replay original prompt)"
|
|
2304
|
+
} : {
|
|
2305
|
+
args: [],
|
|
2306
|
+
strategy: "fresh (no prompt)"
|
|
2307
|
+
};
|
|
2308
|
+
const re = conf?.resumeCommand;
|
|
2309
|
+
if (re) {
|
|
2310
|
+
const captured = (re.global ? new RegExp(re.source, re.flags.replace(/g/g, "")) : re).exec(logText)?.[1]?.trim();
|
|
2311
|
+
if (captured) {
|
|
2312
|
+
const parts = captured.split(/\s+/).filter(Boolean);
|
|
2313
|
+
if (parts.length) return {
|
|
2314
|
+
args: parts,
|
|
2315
|
+
strategy: `printed resume command: ${captured}`
|
|
2316
|
+
};
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
const restore = conf?.restoreArgs;
|
|
2320
|
+
if (restore && restore.length) return {
|
|
2321
|
+
args: [...restore],
|
|
2322
|
+
strategy: `restoreArgs (${restore.join(" ")})`
|
|
2323
|
+
};
|
|
2324
|
+
return {
|
|
2325
|
+
args: ["--continue"],
|
|
2326
|
+
strategy: "--continue (fallback)"
|
|
2327
|
+
};
|
|
2328
|
+
}
|
|
2329
|
+
/**
|
|
2330
|
+
* Wait for a pid to exit. No cross-process exit event exists (the agent is owned
|
|
2331
|
+
* by its own wrapper), so poll `isPidAlive` — checked once immediately, then with
|
|
2332
|
+
* golden-ratio backoff (1.0, 1.6, 2.6…s, capped) up to `timeoutMs`. Returns true
|
|
2333
|
+
* once the pid is gone.
|
|
2334
|
+
*/
|
|
2335
|
+
async function waitForExit(pid, timeoutMs) {
|
|
2336
|
+
if (!isPidAlive(pid)) return true;
|
|
2337
|
+
const start = Date.now();
|
|
2338
|
+
let delay = 1e3;
|
|
2339
|
+
while (Date.now() - start < timeoutMs) {
|
|
2340
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
2341
|
+
if (!isPidAlive(pid)) return true;
|
|
2342
|
+
delay = Math.min(Math.round(delay * 1.618), 8e3);
|
|
2343
|
+
}
|
|
2344
|
+
return !isPidAlive(pid);
|
|
2345
|
+
}
|
|
2286
2346
|
async function cmdRestart(rest) {
|
|
2287
|
-
const argv = await yargs(rest).usage("Usage: ay restart <keyword>").option("latest", {
|
|
2347
|
+
const argv = await yargs(rest).usage("Usage: ay restart <keyword> [--fresh]").option("latest", {
|
|
2288
2348
|
type: "boolean",
|
|
2289
2349
|
default: false,
|
|
2290
2350
|
description: "Use most recent match"
|
|
2291
2351
|
}).option("cwd", {
|
|
2292
2352
|
type: "string",
|
|
2293
2353
|
description: "Restrict to agents under this dir"
|
|
2354
|
+
}).option("fresh", {
|
|
2355
|
+
type: "boolean",
|
|
2356
|
+
default: false,
|
|
2357
|
+
description: "Replay the original prompt instead of resuming the session"
|
|
2294
2358
|
}).help(false).version(false).exitProcess(false).parseAsync();
|
|
2295
2359
|
const opts = {
|
|
2296
2360
|
all: true,
|
|
@@ -2300,13 +2364,32 @@ async function cmdRestart(rest) {
|
|
|
2300
2364
|
cwdScope: typeof argv.cwd === "string" ? path.resolve(argv.cwd) : null
|
|
2301
2365
|
};
|
|
2302
2366
|
const record = await resolveOne(argv._[0] !== void 0 ? String(argv._[0]) : void 0, opts);
|
|
2367
|
+
const fresh = Boolean(argv.fresh);
|
|
2303
2368
|
if (isPidAlive(record.pid)) {
|
|
2304
|
-
|
|
2305
|
-
|
|
2369
|
+
await gracefulExitAgent(record, "restart");
|
|
2370
|
+
process.stdout.write(`stopping pid ${record.pid} (${record.cli}) before restart…\n`);
|
|
2371
|
+
let exited = await waitForExit(record.pid, 3e4);
|
|
2372
|
+
if (!exited) {
|
|
2373
|
+
try {
|
|
2374
|
+
process.kill(record.pid, "SIGKILL");
|
|
2375
|
+
} catch {}
|
|
2376
|
+
exited = await waitForExit(record.pid, 5e3);
|
|
2377
|
+
}
|
|
2378
|
+
if (!exited) {
|
|
2379
|
+
process.stderr.write(`pid ${record.pid} did not exit — aborting restart (try: ay stop ${record.pid} --method=double-ctrl-c)\n`);
|
|
2380
|
+
return 1;
|
|
2381
|
+
}
|
|
2306
2382
|
}
|
|
2307
|
-
const
|
|
2308
|
-
|
|
2309
|
-
|
|
2383
|
+
const conf = (await cliDefaults())[record.cli];
|
|
2384
|
+
const { args: resumeArgs, strategy } = resolveResumeArgs(conf, !fresh && record.log_file ? await readFile(record.log_file, "utf8").catch(() => "") : "", {
|
|
2385
|
+
fresh,
|
|
2386
|
+
prompt: record.prompt
|
|
2387
|
+
});
|
|
2388
|
+
const proc = Bun.spawn([
|
|
2389
|
+
"agent-yes",
|
|
2390
|
+
"--cli=" + record.cli,
|
|
2391
|
+
...resumeArgs
|
|
2392
|
+
], {
|
|
2310
2393
|
cwd: record.cwd,
|
|
2311
2394
|
detached: true,
|
|
2312
2395
|
stdio: [
|
|
@@ -2315,7 +2398,7 @@ async function cmdRestart(rest) {
|
|
|
2315
2398
|
"ignore"
|
|
2316
2399
|
]
|
|
2317
2400
|
});
|
|
2318
|
-
process.stdout.write(`restarted ${record.cli} in ${shortenPath(record.cwd)} (new pid: ${proc.pid})\n`);
|
|
2401
|
+
process.stdout.write(`restarted ${record.cli} in ${shortenPath(record.cwd)} via ${strategy} (new pid: ${proc.pid})\n`);
|
|
2319
2402
|
process.stderr.write(`\n ay tail ${proc.pid} # watch output\n ay ls # list all agents\n`);
|
|
2320
2403
|
return 0;
|
|
2321
2404
|
}
|
|
@@ -2360,6 +2443,7 @@ async function snapshotStatus(record) {
|
|
|
2360
2443
|
question = ni.question;
|
|
2361
2444
|
} else if (state === "idle" && await isAgentStuck(record, logMtimeMs)) state = "stuck";
|
|
2362
2445
|
}
|
|
2446
|
+
if (alive && record.unresponsive) state = "stuck";
|
|
2363
2447
|
const note = (await readNotes()).get(record.pid) ?? null;
|
|
2364
2448
|
return {
|
|
2365
2449
|
pid: record.pid,
|
|
@@ -2625,5 +2709,5 @@ async function cmdResultSet(rest) {
|
|
|
2625
2709
|
}
|
|
2626
2710
|
|
|
2627
2711
|
//#endregion
|
|
2628
|
-
export {
|
|
2629
|
-
//# sourceMappingURL=subcommands-
|
|
2712
|
+
export { runSubcommand as C, writeToIpc as E, resolveResumeArgs as S, stopTipForCli as T, readPtysize as _, cursorAbs as a, resolveOne as b, extractTaskCounts as c, isExitRequest as d, isPidAlive as f, readNotes as g, matchKeyword as h, controlCodeFromName as i, finalizedLines as l, listRecords as m, READ_PAGE_DEFAULT as n, deriveLiveStatus as o, isSubcommand as p, cmdHelp as r, extractNeedsInput as s, GRACEFUL_EXIT_COMMANDS as t, isAgentStuck as u, renderRawLog as v, snapshotStatus as w, resolveReadWindow as x, renderRawLogLines as y };
|
|
2713
|
+
//# sourceMappingURL=subcommands-C4tMi9NX.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import "./logger-CDIsZ-Pp.js";
|
|
2
|
+
import "./globalPidIndex-DlmmJlO8.js";
|
|
3
|
+
import "./configShared-CEyhl0WH.js";
|
|
4
|
+
import "./e2e-Bfw7qL9O.js";
|
|
5
|
+
import "./webrtcLink-CBZkZ-LT.js";
|
|
6
|
+
import "./remotes-DBCvpp3B.js";
|
|
7
|
+
import { C as runSubcommand, E as writeToIpc, S as resolveResumeArgs, T as stopTipForCli, _ as readPtysize, a as cursorAbs, b as resolveOne, c as extractTaskCounts, d as isExitRequest, f as isPidAlive, g as readNotes, h as matchKeyword, i as controlCodeFromName, l as finalizedLines, m as listRecords, n as READ_PAGE_DEFAULT, o as deriveLiveStatus, p as isSubcommand, r as cmdHelp, s as extractNeedsInput, t as GRACEFUL_EXIT_COMMANDS, u as isAgentStuck, v as renderRawLog, w as snapshotStatus, x as resolveReadWindow, y as renderRawLogLines } from "./subcommands-C4tMi9NX.js";
|
|
8
|
+
|
|
9
|
+
export { cmdHelp, isSubcommand, runSubcommand };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as logger, t as addTransport } from "./logger-CDIsZ-Pp.js";
|
|
2
|
-
import { r as getInstalledPackage } from "./versionChecker-
|
|
2
|
+
import { r as getInstalledPackage } from "./versionChecker-B-ToHoJ2.js";
|
|
3
3
|
import { t as agentYesHome } from "./agentYesHome-_eJa3DaX.js";
|
|
4
4
|
import { i as shouldUseLock, r as releaseLock, t as acquireLock } from "./runningLock-V4qvXgAw.js";
|
|
5
5
|
import { t as PidStore } from "./pidStore-fqXqTKkh.js";
|
|
@@ -1060,7 +1060,7 @@ async function notifyWebhook(status, details, cwd = process.cwd()) {
|
|
|
1060
1060
|
|
|
1061
1061
|
//#endregion
|
|
1062
1062
|
//#region ts/index.ts
|
|
1063
|
-
const config = await import("./agent-yes.config-
|
|
1063
|
+
const config = await import("./agent-yes.config-Bl08kMpl.js").then((mod) => mod.default || mod);
|
|
1064
1064
|
const CLIS_CONFIG = config.clis;
|
|
1065
1065
|
/**
|
|
1066
1066
|
* Main function to run agent-cli with automatic yes/no responses
|
|
@@ -1802,4 +1802,4 @@ function sleep(ms) {
|
|
|
1802
1802
|
|
|
1803
1803
|
//#endregion
|
|
1804
1804
|
export { removeControlCharacters as a, AgentContext as i, agentYes as n, config as r, CLIS_CONFIG as t };
|
|
1805
|
-
//# sourceMappingURL=ts-
|
|
1805
|
+
//# sourceMappingURL=ts-U3AE4u72.js.map
|
|
@@ -7,7 +7,7 @@ import { fileURLToPath } from "url";
|
|
|
7
7
|
|
|
8
8
|
//#region package.json
|
|
9
9
|
var name = "claude-yes";
|
|
10
|
-
var version = "1.
|
|
10
|
+
var version = "1.154.0";
|
|
11
11
|
|
|
12
12
|
//#endregion
|
|
13
13
|
//#region ts/versionChecker.ts
|
|
@@ -215,4 +215,4 @@ async function displayVersion() {
|
|
|
215
215
|
|
|
216
216
|
//#endregion
|
|
217
217
|
export { versionString as i, displayVersion as n, getInstalledPackage as r, checkAndAutoUpdate as t };
|
|
218
|
-
//# sourceMappingURL=versionChecker-
|
|
218
|
+
//# sourceMappingURL=versionChecker-B-ToHoJ2.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { u as parseSecret } from "./e2e-
|
|
1
|
+
import { u as parseSecret } from "./e2e-Bfw7qL9O.js";
|
|
2
2
|
|
|
3
3
|
//#region ts/webrtcLink.ts
|
|
4
4
|
const SIGNAL_SUBPROTOCOL = "ay-signal-1";
|
|
@@ -42,4 +42,4 @@ function isWebrtcSpec(spec) {
|
|
|
42
42
|
|
|
43
43
|
//#endregion
|
|
44
44
|
export { isWebrtcSpec as n, parseWebrtcLink as r, SIGNAL_SUBPROTOCOL as t };
|
|
45
|
-
//# sourceMappingURL=webrtcLink-
|
|
45
|
+
//# sourceMappingURL=webrtcLink-CBZkZ-LT.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as computeTranscriptHash, c as open, d as randomHex, f as seal, l as packEnvelope, n as FLAG_CONFIRM, o as deriveAuthToken, p as unpackEnvelope, s as deriveDirKeys } from "./e2e-
|
|
2
|
-
import { n as isWebrtcSpec, r as parseWebrtcLink, t as SIGNAL_SUBPROTOCOL } from "./webrtcLink-
|
|
1
|
+
import { a as computeTranscriptHash, c as open, d as randomHex, f as seal, l as packEnvelope, n as FLAG_CONFIRM, o as deriveAuthToken, p as unpackEnvelope, s as deriveDirKeys } from "./e2e-Bfw7qL9O.js";
|
|
2
|
+
import { n as isWebrtcSpec, r as parseWebrtcLink, t as SIGNAL_SUBPROTOCOL } from "./webrtcLink-CBZkZ-LT.js";
|
|
3
3
|
import { RTCPeerConnection } from "node-datachannel/polyfill";
|
|
4
4
|
|
|
5
5
|
//#region ts/webrtcRemote.ts
|
|
@@ -256,4 +256,4 @@ async function startWebrtcBridge(link) {
|
|
|
256
256
|
|
|
257
257
|
//#endregion
|
|
258
258
|
export { startWebrtcBridge };
|
|
259
|
-
//# sourceMappingURL=webrtcRemote-
|
|
259
|
+
//# sourceMappingURL=webrtcRemote-Ccdzmuc-.js.map
|
|
@@ -88,4 +88,4 @@ function resolveSpawnCwd(input) {
|
|
|
88
88
|
|
|
89
89
|
//#endregion
|
|
90
90
|
export { setWorkspaceRoot as a, resolveSpawnCwd as i, getWorkspaceRoot as n, isProvisionAllowed as r, getProvisionRoot as t };
|
|
91
|
-
//# sourceMappingURL=workspaceConfig-
|
|
91
|
+
//# sourceMappingURL=workspaceConfig-DT3cCBZK.js.map
|
package/lab/ui/index.html
CHANGED
package/package.json
CHANGED
package/ts/buildRustArgs.spec.ts
CHANGED
|
@@ -167,7 +167,11 @@ describe("buildRustArgs", () => {
|
|
|
167
167
|
});
|
|
168
168
|
|
|
169
169
|
it("does not treat a supported-CLI word after -- as the CLI selector", () => {
|
|
170
|
-
const result = buildRustArgs(
|
|
170
|
+
const result = buildRustArgs(
|
|
171
|
+
argv("--cwd", "/tmp/x", "--", "ask", "claude"),
|
|
172
|
+
"claude",
|
|
173
|
+
SUPPORTED_CLIS,
|
|
174
|
+
);
|
|
171
175
|
expect(result).toEqual(["--cli=claude", "--cwd", "/tmp/x", "--", "ask", "claude"]);
|
|
172
176
|
});
|
|
173
177
|
});
|
package/ts/configShared.ts
CHANGED
|
@@ -19,6 +19,7 @@ type RawCliConfig = Omit<
|
|
|
19
19
|
| "exitCommands"
|
|
20
20
|
| "autoRetry"
|
|
21
21
|
| "needsInput"
|
|
22
|
+
| "resumeCommand"
|
|
22
23
|
> & {
|
|
23
24
|
ready?: RegexSource[];
|
|
24
25
|
fatal?: RegexSource[];
|
|
@@ -30,6 +31,7 @@ type RawCliConfig = Omit<
|
|
|
30
31
|
updateAvailable?: RegexSource[];
|
|
31
32
|
autoRetry?: RegexSource[];
|
|
32
33
|
needsInput?: RegexSource[];
|
|
34
|
+
resumeCommand?: RegexSource;
|
|
33
35
|
exitCommands?: string[];
|
|
34
36
|
exitCommand?: string[];
|
|
35
37
|
};
|
|
@@ -84,6 +86,7 @@ export function normalizeCliConfig(raw: RawCliConfig): AgentCliConfig {
|
|
|
84
86
|
updateAvailable,
|
|
85
87
|
autoRetry,
|
|
86
88
|
needsInput,
|
|
89
|
+
resumeCommand,
|
|
87
90
|
exitCommands,
|
|
88
91
|
exitCommand,
|
|
89
92
|
...rest
|
|
@@ -101,6 +104,7 @@ export function normalizeCliConfig(raw: RawCliConfig): AgentCliConfig {
|
|
|
101
104
|
updateAvailable: compileRegexList(updateAvailable),
|
|
102
105
|
autoRetry: compileRegexList(autoRetry),
|
|
103
106
|
needsInput: compileRegexList(needsInput),
|
|
107
|
+
resumeCommand: resumeCommand === undefined ? undefined : compileRegexSource(resumeCommand),
|
|
104
108
|
exitCommands: exitCommands ?? exitCommand,
|
|
105
109
|
};
|
|
106
110
|
}
|
package/ts/globalPidIndex.ts
CHANGED
|
@@ -33,6 +33,11 @@ export interface GlobalPidRecord {
|
|
|
33
33
|
log_file: string | null;
|
|
34
34
|
fifo_file?: string | null;
|
|
35
35
|
status: "active" | "idle" | "exited";
|
|
36
|
+
// Set by the Rust supervisor when the agent produced no PTY output after a
|
|
37
|
+
// high-signal poke / while a "working" spinner is frozen — i.e. it looks
|
|
38
|
+
// wedged. Orthogonal to `status` (which stays "active"); cleared on recovery
|
|
39
|
+
// and on exit. The ls/status live-state derivation surfaces it as `stuck`.
|
|
40
|
+
unresponsive?: boolean;
|
|
36
41
|
exit_code: number | null;
|
|
37
42
|
exit_reason: string | null;
|
|
38
43
|
started_at: number;
|
package/ts/index.ts
CHANGED
|
@@ -75,6 +75,7 @@ export type AgentCliConfig = {
|
|
|
75
75
|
// crash/resuming-session behaviour
|
|
76
76
|
restoreArgs?: string[]; // arguments to continue the session when crashed
|
|
77
77
|
restartWithoutContinueArg?: RegExp[]; // array of regex to match for errors that require restart without continue args
|
|
78
|
+
resumeCommand?: RegExp; // optional: scraped from the agent's log on `ay restart`. Capture group 1 is the argument string (whitespace-split) to relaunch with — i.e. a resume command the CLI printed, like "--resume <id>". When absent or unmatched, restart falls back to restoreArgs (e.g. --continue).
|
|
78
79
|
};
|
|
79
80
|
export type AgentYesConfig = {
|
|
80
81
|
configDir?: string; // directory to store agent-yes config files, e.g. session store
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
|
|
3
|
+
const spawn = vi.fn();
|
|
4
|
+
const createInterface = vi.fn();
|
|
5
|
+
vi.mock("node:child_process", () => ({ spawn: (...a: unknown[]) => spawn(...a) }));
|
|
6
|
+
vi.mock("node:readline", () => ({
|
|
7
|
+
createInterface: (...a: unknown[]) => createInterface(...a),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
import { resolveOpener, openInBrowser, offerOpenInBrowser } from "./openBrowser.ts";
|
|
11
|
+
|
|
12
|
+
const URL = "https://agent-yes.com/w/#room:tok";
|
|
13
|
+
|
|
14
|
+
// A fake child whose .on()/.unref() are inert — enough for openInBrowser's
|
|
15
|
+
// fire-and-forget launch.
|
|
16
|
+
const fakeChild = () => ({ on: vi.fn(), unref: vi.fn() });
|
|
17
|
+
// A fake readline that answers `reply` to the next question() call.
|
|
18
|
+
const fakeRl = (reply: string) => ({
|
|
19
|
+
question: (_q: string, cb: (a: string) => void) => cb(reply),
|
|
20
|
+
close: vi.fn(),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Force a resolvable opener regardless of the host platform: on Linux runners
|
|
24
|
+
// (incl. headless CI) resolveOpener needs a display, so pin one. macOS/Windows
|
|
25
|
+
// resolve an opener anyway, so this is harmless there.
|
|
26
|
+
let savedDisplay: string | undefined;
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
spawn.mockReset();
|
|
29
|
+
createInterface.mockReset();
|
|
30
|
+
savedDisplay = process.env.DISPLAY;
|
|
31
|
+
process.env.DISPLAY = ":99";
|
|
32
|
+
});
|
|
33
|
+
afterEach(() => {
|
|
34
|
+
if (savedDisplay === undefined) delete process.env.DISPLAY;
|
|
35
|
+
else process.env.DISPLAY = savedDisplay;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// resolveOpener is the pure resolution table — the only branching worth pinning.
|
|
39
|
+
describe("resolveOpener", () => {
|
|
40
|
+
it("uses `open` on macOS", () => {
|
|
41
|
+
expect(resolveOpener(URL, "darwin", {})).toEqual({ cmd: "open", args: [URL] });
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("uses cmd /c start with an empty title arg on Windows", () => {
|
|
45
|
+
expect(resolveOpener(URL, "win32", {})).toEqual({
|
|
46
|
+
cmd: "cmd",
|
|
47
|
+
args: ["/c", "start", "", URL],
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("uses xdg-open on Linux when a display is present (X11 or Wayland)", () => {
|
|
52
|
+
expect(resolveOpener(URL, "linux", { DISPLAY: ":0" })).toEqual({
|
|
53
|
+
cmd: "xdg-open",
|
|
54
|
+
args: [URL],
|
|
55
|
+
});
|
|
56
|
+
expect(resolveOpener(URL, "linux", { WAYLAND_DISPLAY: "wayland-0" })).toEqual({
|
|
57
|
+
cmd: "xdg-open",
|
|
58
|
+
args: [URL],
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("returns null on a headless Linux box (no display → nothing to open)", () => {
|
|
63
|
+
expect(resolveOpener(URL, "linux", {})).toBeNull();
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe("openInBrowser", () => {
|
|
68
|
+
it("spawns the platform opener detached and returns true when one exists", () => {
|
|
69
|
+
spawn.mockReturnValue(fakeChild());
|
|
70
|
+
expect(openInBrowser(URL)).toBe(true);
|
|
71
|
+
expect(spawn).toHaveBeenCalledOnce();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("returns false (and never spawns) when spawn throws", () => {
|
|
75
|
+
spawn.mockImplementation(() => {
|
|
76
|
+
throw new Error("ENOENT");
|
|
77
|
+
});
|
|
78
|
+
expect(openInBrowser(URL)).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("offerOpenInBrowser", () => {
|
|
83
|
+
const withTTY = async (fn: () => Promise<void>) => {
|
|
84
|
+
const [si, so] = [process.stdin.isTTY, process.stdout.isTTY];
|
|
85
|
+
process.stdin.isTTY = true;
|
|
86
|
+
process.stdout.isTTY = true;
|
|
87
|
+
try {
|
|
88
|
+
await fn();
|
|
89
|
+
} finally {
|
|
90
|
+
process.stdin.isTTY = si;
|
|
91
|
+
process.stdout.isTTY = so;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
it("no-ops without prompting when stdin is not a TTY (curl|sh / CI / daemon)", async () => {
|
|
96
|
+
const orig = process.stdin.isTTY;
|
|
97
|
+
process.stdin.isTTY = false;
|
|
98
|
+
try {
|
|
99
|
+
expect(await offerOpenInBrowser(URL)).toBe(false);
|
|
100
|
+
expect(createInterface).not.toHaveBeenCalled();
|
|
101
|
+
} finally {
|
|
102
|
+
process.stdin.isTTY = orig;
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("opens on an empty answer (default yes)", async () => {
|
|
107
|
+
await withTTY(async () => {
|
|
108
|
+
createInterface.mockReturnValue(fakeRl(""));
|
|
109
|
+
spawn.mockReturnValue(fakeChild());
|
|
110
|
+
expect(await offerOpenInBrowser(URL)).toBe(true);
|
|
111
|
+
expect(spawn).toHaveBeenCalledOnce();
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("opens on an explicit yes", async () => {
|
|
116
|
+
await withTTY(async () => {
|
|
117
|
+
createInterface.mockReturnValue(fakeRl("Y"));
|
|
118
|
+
spawn.mockReturnValue(fakeChild());
|
|
119
|
+
expect(await offerOpenInBrowser(URL)).toBe(true);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("does not open when the operator declines", async () => {
|
|
124
|
+
await withTTY(async () => {
|
|
125
|
+
createInterface.mockReturnValue(fakeRl("n"));
|
|
126
|
+
expect(await offerOpenInBrowser(URL)).toBe(false);
|
|
127
|
+
expect(spawn).not.toHaveBeenCalled();
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Open a URL in the operator's default browser, gated behind an interactive
|
|
2
|
+
// prompt. Used after `ay serve --share` prints a WebRTC console link so the
|
|
3
|
+
// operator can jump straight into the console — but only when there's a real
|
|
4
|
+
// terminal to ask on AND a graphical session to open into, so SSH/CI/daemon
|
|
5
|
+
// runs never try to launch a browser they can't show (they just print the link).
|
|
6
|
+
import { spawn } from "node:child_process";
|
|
7
|
+
import { createInterface } from "node:readline";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The platform command that opens `url` in the default browser, or null when we
|
|
11
|
+
* have no way to (a headless Linux box with no display). Pure — platform/env are
|
|
12
|
+
* injectable so the resolution table is unit-testable.
|
|
13
|
+
*/
|
|
14
|
+
export function resolveOpener(
|
|
15
|
+
url: string,
|
|
16
|
+
platform: NodeJS.Platform = process.platform,
|
|
17
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
18
|
+
): { cmd: string; args: string[] } | null {
|
|
19
|
+
if (platform === "darwin") return { cmd: "open", args: [url] };
|
|
20
|
+
// `start` is a cmd builtin; the empty "" is its title arg so a URL with spaces
|
|
21
|
+
// isn't mistaken for the window title.
|
|
22
|
+
if (platform === "win32") return { cmd: "cmd", args: ["/c", "start", "", url] };
|
|
23
|
+
// Linux/BSD: opening a browser only makes sense inside a graphical session.
|
|
24
|
+
if (!env.DISPLAY && !env.WAYLAND_DISPLAY) return null;
|
|
25
|
+
return { cmd: "xdg-open", args: [url] };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Fire-and-forget launch of the browser. Returns false when we can't open. */
|
|
29
|
+
export function openInBrowser(url: string): boolean {
|
|
30
|
+
const opener = resolveOpener(url);
|
|
31
|
+
if (!opener) return false;
|
|
32
|
+
try {
|
|
33
|
+
const child = spawn(opener.cmd, opener.args, { stdio: "ignore", detached: true });
|
|
34
|
+
child.on("error", () => {}); // missing opener binary → swallow, never crash the caller
|
|
35
|
+
child.unref();
|
|
36
|
+
return true;
|
|
37
|
+
} catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Offer to open `url` in the browser, defaulting to yes. No-ops silently unless
|
|
44
|
+
* BOTH stdin/stdout are a TTY (so we can ask) AND a browser opener exists for
|
|
45
|
+
* this platform (so "yes" can actually do something). Non-interactive contexts
|
|
46
|
+
* — `curl | sh`, SSH, CI, the daemon — fall through and just leave the link
|
|
47
|
+
* printed, as the operator chose.
|
|
48
|
+
*/
|
|
49
|
+
export async function offerOpenInBrowser(
|
|
50
|
+
url: string,
|
|
51
|
+
prompt = "Open in browser now? [Y/n] ",
|
|
52
|
+
): Promise<boolean> {
|
|
53
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
|
|
54
|
+
if (!resolveOpener(url)) return false;
|
|
55
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
56
|
+
try {
|
|
57
|
+
const answer = (
|
|
58
|
+
await new Promise<string>((resolve) => rl.question(prompt, resolve))
|
|
59
|
+
)
|
|
60
|
+
.trim()
|
|
61
|
+
.toLowerCase();
|
|
62
|
+
if (answer !== "" && answer !== "y" && answer !== "yes") return false;
|
|
63
|
+
} finally {
|
|
64
|
+
rl.close();
|
|
65
|
+
}
|
|
66
|
+
return openInBrowser(url);
|
|
67
|
+
}
|
package/ts/serve.ts
CHANGED
|
@@ -388,9 +388,9 @@ async function cmdServeDaemon(sub: string, args: string[]): Promise<number> {
|
|
|
388
388
|
/* best effort — fall back to the .share-link file hint in emitShareLink */
|
|
389
389
|
}
|
|
390
390
|
}
|
|
391
|
-
const emitShareLink = () => {
|
|
391
|
+
const emitShareLink = async () => {
|
|
392
392
|
if (!webrtcDaemon) return;
|
|
393
|
-
if (shareLink)
|
|
393
|
+
if (shareLink) {
|
|
394
394
|
process.stdout.write(
|
|
395
395
|
`\nshared over WebRTC — open this link (the token is eaten from the URL on open):\n` +
|
|
396
396
|
` ${shareLink}\n` +
|
|
@@ -398,7 +398,11 @@ async function cmdServeDaemon(sub: string, args: string[]): Promise<number> {
|
|
|
398
398
|
? ` (persistent room — same link across restarts; delete ~/.agent-yes/.share-room to rotate)\n`
|
|
399
399
|
: ``),
|
|
400
400
|
);
|
|
401
|
-
|
|
401
|
+
// Offer to jump straight into the console (default yes); no-ops on a
|
|
402
|
+
// non-TTY or headless box, leaving the link printed above.
|
|
403
|
+
const { offerOpenInBrowser } = await import("./openBrowser.ts");
|
|
404
|
+
await offerOpenInBrowser(shareLink);
|
|
405
|
+
} else
|
|
402
406
|
process.stdout.write(
|
|
403
407
|
`\nthe WebRTC share link carries a secret, so the daemon does NOT log it —\n` +
|
|
404
408
|
`read it from ~/.agent-yes/.share-link (mode 0600). The room persists in\n` +
|
|
@@ -418,7 +422,7 @@ async function cmdServeDaemon(sub: string, args: string[]): Promise<number> {
|
|
|
418
422
|
if (runningVer === current && sameConfig) {
|
|
419
423
|
await ensureBootAutostart(mgr);
|
|
420
424
|
process.stdout.write(`'${DAEMON_NAME}' already running v${current} (up to date)\n`);
|
|
421
|
-
emitShareLink();
|
|
425
|
+
await emitShareLink();
|
|
422
426
|
return 0;
|
|
423
427
|
}
|
|
424
428
|
// Outdated, unreachable, or reconfigured → graceful roll-forward. `stop` sends
|
|
@@ -528,7 +532,7 @@ async function cmdServeDaemon(sub: string, args: string[]): Promise<number> {
|
|
|
528
532
|
}
|
|
529
533
|
process.stdout.write(` ay serve logs # view server logs\n`);
|
|
530
534
|
process.stdout.write(` ay serve uninstall # remove daemon\n`);
|
|
531
|
-
emitShareLink();
|
|
535
|
+
await emitShareLink();
|
|
532
536
|
}
|
|
533
537
|
return code ?? 1;
|
|
534
538
|
}
|
|
@@ -983,7 +987,10 @@ export async function cmdServe(rest: string[]): Promise<number> {
|
|
|
983
987
|
const status = await deriveLiveStatus(r);
|
|
984
988
|
return {
|
|
985
989
|
...r,
|
|
986
|
-
|
|
990
|
+
// The Rust supervisor's unresponsive flag is an authoritative wedge signal —
|
|
991
|
+
// surface it as `stuck` so the console's dot matches `ay ls`. (A dead agent
|
|
992
|
+
// is never unresponsive — Rust clears the flag on exit.)
|
|
993
|
+
status: status !== "exited" && r.unresponsive ? "stuck" : status,
|
|
987
994
|
title: await logTitle(r.log_file),
|
|
988
995
|
git: status === "exited" ? null : await gitStatus(r.cwd),
|
|
989
996
|
// Task progress from the rendered todo block (null when none detected → no
|
|
@@ -1770,6 +1777,12 @@ export async function cmdServe(rest: string[]): Promise<number> {
|
|
|
1770
1777
|
? "\n"
|
|
1771
1778
|
: ` (persistent room — same link across restarts; delete ~/.agent-yes/.share-room to rotate)\n\n`;
|
|
1772
1779
|
process.stdout.write(`${wantHttp ? "\n" : ""}${lead}:\n ${link}\n` + persistNote);
|
|
1780
|
+
// Offer to open the console (default yes) on the FIRST share only —
|
|
1781
|
+
// an auto-rotation shouldn't pop a fresh tab from under the operator.
|
|
1782
|
+
if (!rotated) {
|
|
1783
|
+
const { offerOpenInBrowser } = await import("./openBrowser.ts");
|
|
1784
|
+
await offerOpenInBrowser(link);
|
|
1785
|
+
}
|
|
1773
1786
|
} else {
|
|
1774
1787
|
// Non-TTY (daemon/journal/CI): the link embeds the room secret S, so never
|
|
1775
1788
|
// write it to a log stream. Stash it in a 0600 file and point there instead.
|