claude-yes 1.36.4 → 1.37.1
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/cli.js +683 -756
- package/dist/index.js +603 -484
- package/package.json +4 -21
- package/ts/index.ts +77 -325
- package/ts/parseCliArgs.ts +1 -1
- package/ts/pidStore.ts +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-yes",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.37.1",
|
|
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",
|
|
@@ -66,6 +66,8 @@
|
|
|
66
66
|
"postbuild": "bun ./ts/postbuild.ts",
|
|
67
67
|
"demo": "bun run build && bun link && claude-yes -- demo",
|
|
68
68
|
"dev": "bun ts/index.ts",
|
|
69
|
+
"dev:tsgo": "tsgo ts/cli.ts",
|
|
70
|
+
"test:tsgo": "tsgo --test",
|
|
69
71
|
"fmt": "oxlint --fix --fix-suggestions && oxfmt",
|
|
70
72
|
"_prepack": "bun run build",
|
|
71
73
|
"prepare": "husky",
|
|
@@ -91,6 +93,7 @@
|
|
|
91
93
|
"@types/ms": "^2.1.0",
|
|
92
94
|
"@types/node": "^25.0.10",
|
|
93
95
|
"@types/yargs": "^17.0.35",
|
|
96
|
+
"@typescript/native-preview": "^7.0.0-dev.20260124.1",
|
|
94
97
|
"cpu-wait": "^0.0.10",
|
|
95
98
|
"execa": "^9.6.1",
|
|
96
99
|
"husky": "^9.1.7",
|
|
@@ -125,25 +128,5 @@
|
|
|
125
128
|
},
|
|
126
129
|
"engines": {
|
|
127
130
|
"node": ">=22.0.0"
|
|
128
|
-
},
|
|
129
|
-
"_release": {
|
|
130
|
-
"branches": [
|
|
131
|
-
"main"
|
|
132
|
-
],
|
|
133
|
-
"plugins": [
|
|
134
|
-
"@semantic-release/commit-analyzer",
|
|
135
|
-
"@semantic-release/release-notes-generator",
|
|
136
|
-
"@semantic-release/changelog",
|
|
137
|
-
"@semantic-release/npm",
|
|
138
|
-
[
|
|
139
|
-
"@semantic-release/exec",
|
|
140
|
-
{
|
|
141
|
-
"publishCmd": "npm pkg set name=claude-yes"
|
|
142
|
-
}
|
|
143
|
-
],
|
|
144
|
-
"@semantic-release/npm",
|
|
145
|
-
"@semantic-release/git",
|
|
146
|
-
"@semantic-release/github"
|
|
147
|
-
]
|
|
148
131
|
}
|
|
149
132
|
}
|
package/ts/index.ts
CHANGED
|
@@ -21,6 +21,22 @@ import { createFifoStream } from "./beta/fifo.ts";
|
|
|
21
21
|
import { PidStore } from "./pidStore.ts";
|
|
22
22
|
import { SUPPORTED_CLIS } from "./SUPPORTED_CLIS.ts";
|
|
23
23
|
import winston from "winston";
|
|
24
|
+
import { sendEnter, sendMessage, type MessageContext } from "./core/messaging.ts";
|
|
25
|
+
import {
|
|
26
|
+
initializeLogPaths,
|
|
27
|
+
setupDebugLogging,
|
|
28
|
+
saveLogFile,
|
|
29
|
+
saveDeprecatedLogFile,
|
|
30
|
+
type LogPaths,
|
|
31
|
+
} from "./core/logging.ts";
|
|
32
|
+
import { spawnAgent, getTerminalDimensions } from "./core/spawner.ts";
|
|
33
|
+
import { AgentContext } from "./core/context.ts";
|
|
34
|
+
import { createAutoResponseHandler } from "./core/responders.ts";
|
|
35
|
+
import {
|
|
36
|
+
handleConsoleControlCodes,
|
|
37
|
+
createTerminateSignalHandler,
|
|
38
|
+
createTerminatorStream,
|
|
39
|
+
} from "./core/streamHelpers.ts";
|
|
24
40
|
|
|
25
41
|
export { removeControlCharacters };
|
|
26
42
|
|
|
@@ -175,44 +191,16 @@ export default async function agentYes({
|
|
|
175
191
|
|
|
176
192
|
process.stdin.setRawMode?.(true); // must be called any stdout/stdin usage
|
|
177
193
|
|
|
178
|
-
let isFatal = false; // when true, do not restart on crash, and exit agent
|
|
179
|
-
let shouldRestartWithoutContinue = false; // when true, restart without continue args
|
|
180
|
-
|
|
181
|
-
const stdinReady = new ReadyManager();
|
|
182
|
-
const stdinFirstReady = new ReadyManager(); // if user send ctrl+c before
|
|
183
|
-
|
|
184
|
-
// force ready after 10s to avoid stuck forever if the ready-word mismatched
|
|
185
|
-
sleep(10e3).then(() => {
|
|
186
|
-
if (!stdinReady.isReady) stdinReady.ready();
|
|
187
|
-
if (!stdinFirstReady.isReady) stdinFirstReady.ready();
|
|
188
|
-
});
|
|
189
|
-
const nextStdout = new ReadyManager();
|
|
190
|
-
|
|
191
194
|
const shellOutputStream = new TransformStream<string, string>();
|
|
192
195
|
const outputWriter = shellOutputStream.writable.getWriter();
|
|
193
196
|
|
|
194
197
|
logger.debug(`Using ${ptyPackage} for pseudo terminal management.`);
|
|
195
198
|
|
|
196
|
-
let logPath: string | false = false;
|
|
197
|
-
let rawLogPath: string | false = false;
|
|
198
|
-
let rawLinesLogPath: string | false = false;
|
|
199
|
-
let debuggingLogsPath: string | false = false;
|
|
200
|
-
|
|
201
199
|
// Detect if running as sub-agent
|
|
202
200
|
const isSubAgent = !!process.env.CLAUDE_PPID;
|
|
203
201
|
if (isSubAgent)
|
|
204
202
|
logger.info(`[${cli}-yes] Running as sub-agent (CLAUDE_PPID=${process.env.CLAUDE_PPID})`);
|
|
205
203
|
|
|
206
|
-
const getPtyOptions = () => {
|
|
207
|
-
const ptyEnv = { ...(env ?? (process.env as Record<string, string>)) };
|
|
208
|
-
return {
|
|
209
|
-
name: "xterm-color",
|
|
210
|
-
...getTerminalDimensions(),
|
|
211
|
-
cwd: cwd ?? process.cwd(),
|
|
212
|
-
env: ptyEnv,
|
|
213
|
-
};
|
|
214
|
-
};
|
|
215
|
-
|
|
216
204
|
// Apply CLI specific configurations (moved to CLI_CONFIGURES)
|
|
217
205
|
const cliConf = (CLIS_CONFIG as Record<string, AgentCliConfig>)[cli] || {};
|
|
218
206
|
cliArgs = cliConf.defaultArgs ? [...cliConf.defaultArgs, ...cliArgs] : cliArgs;
|
|
@@ -327,118 +315,46 @@ export default async function agentYes({
|
|
|
327
315
|
logger.warn(`Unknown promptArg format: ${cliConf.promptArg}`);
|
|
328
316
|
}
|
|
329
317
|
}
|
|
330
|
-
// Determine the actual cli command to run
|
|
331
|
-
|
|
332
|
-
// Helper function to get install command based on platform/availability
|
|
333
|
-
const getInstallCommand = (
|
|
334
|
-
installConfig:
|
|
335
|
-
| string
|
|
336
|
-
| { powershell?: string; bash?: string; npm?: string; unix?: string; windows?: string },
|
|
337
|
-
): string | null => {
|
|
338
|
-
if (typeof installConfig === "string") {
|
|
339
|
-
return installConfig;
|
|
340
|
-
}
|
|
341
318
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
// Try shell-specific commands
|
|
351
|
-
if (isWindows && installConfig.powershell) {
|
|
352
|
-
return installConfig.powershell;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
if (!isWindows && installConfig.bash) {
|
|
356
|
-
return installConfig.bash;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// Fallback to npm if available
|
|
360
|
-
if (installConfig.npm) {
|
|
361
|
-
return installConfig.npm;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
return null;
|
|
365
|
-
};
|
|
366
|
-
|
|
367
|
-
const spawn = () => {
|
|
368
|
-
const cliCommand = cliConf?.binary || cli;
|
|
369
|
-
let [bin, ...args] = [...parseCommandString(cliCommand), ...cliArgs];
|
|
370
|
-
if (verbose) logger.info(`Spawning ${bin} with args: ${JSON.stringify(args)}`);
|
|
371
|
-
logger.info(`Spawning ${bin} with args: ${JSON.stringify(args)}`);
|
|
372
|
-
// throw new Error(JSON.stringify([bin!, args, getPtyOptions()]))
|
|
373
|
-
const spawned = pty.spawn(bin!, args, getPtyOptions());
|
|
374
|
-
logger.info(`[${cli}-yes] Spawned ${bin} with PID ${spawned.pid}`);
|
|
375
|
-
// if (globalThis.Bun)
|
|
376
|
-
// args = args.map((arg) => `'${arg.replace(/'/g, "\\'")}'`);
|
|
377
|
-
return spawned;
|
|
319
|
+
// Spawn the agent CLI process
|
|
320
|
+
const ptyEnv = { ...(env ?? (process.env as Record<string, string>)) };
|
|
321
|
+
const ptyOptions = {
|
|
322
|
+
name: "xterm-color",
|
|
323
|
+
...getTerminalDimensions(),
|
|
324
|
+
cwd: cwd ?? process.cwd(),
|
|
325
|
+
env: ptyEnv,
|
|
378
326
|
};
|
|
379
327
|
|
|
380
|
-
let shell =
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
if (!installCmd) {
|
|
389
|
-
logger.error(`No suitable install command found for ${cli} on this platform`);
|
|
390
|
-
throw error;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
logger.info(`Please install the cli by run ${installCmd}`);
|
|
394
|
-
|
|
395
|
-
if (install) {
|
|
396
|
-
logger.info(`Attempting to install ${cli}...`);
|
|
397
|
-
execaCommandSync(installCmd, { stdio: "inherit" });
|
|
398
|
-
logger.info(`${cli} installed successfully. Please rerun the command.`);
|
|
399
|
-
return spawn();
|
|
400
|
-
} else {
|
|
401
|
-
logger.error(`If you did not installed it yet, Please install it first: ${installCmd}`);
|
|
402
|
-
throw error;
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
if (globalThis.Bun && error instanceof Error && error.stack?.includes("bun-pty")) {
|
|
407
|
-
// try to fix bun-pty issues
|
|
408
|
-
logger.error(`Detected bun-pty issue, attempted to fix it. Please try again.`);
|
|
409
|
-
require("./pty-fix");
|
|
410
|
-
// unable to retry with same process, so exit here.
|
|
411
|
-
}
|
|
412
|
-
throw error;
|
|
413
|
-
|
|
414
|
-
function isCommandNotFoundError(e: unknown) {
|
|
415
|
-
if (e instanceof Error) {
|
|
416
|
-
return (
|
|
417
|
-
e.message.includes("command not found") || // unix
|
|
418
|
-
e.message.includes("ENOENT") || // unix
|
|
419
|
-
e.message.includes("spawn") // windows
|
|
420
|
-
);
|
|
421
|
-
}
|
|
422
|
-
return false;
|
|
423
|
-
}
|
|
424
|
-
},
|
|
425
|
-
spawn,
|
|
426
|
-
)();
|
|
328
|
+
let shell = spawnAgent({
|
|
329
|
+
cli,
|
|
330
|
+
cliConf,
|
|
331
|
+
cliArgs,
|
|
332
|
+
verbose,
|
|
333
|
+
install,
|
|
334
|
+
ptyOptions,
|
|
335
|
+
});
|
|
427
336
|
|
|
428
337
|
// Register process in pidStore and compute log paths
|
|
429
338
|
await pidStore.registerProcess({ pid: shell.pid, cli, args: cliArgs, prompt });
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
339
|
+
const logPaths = initializeLogPaths(pidStore, shell.pid);
|
|
340
|
+
setupDebugLogging(logPaths.debuggingLogsPath);
|
|
341
|
+
|
|
342
|
+
// Create agent context
|
|
343
|
+
const ctx = new AgentContext({
|
|
344
|
+
shell,
|
|
345
|
+
pidStore,
|
|
346
|
+
logPaths,
|
|
347
|
+
cli,
|
|
348
|
+
cliConf,
|
|
349
|
+
verbose,
|
|
350
|
+
robust,
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
// force ready after 10s to avoid stuck forever if the ready-word mismatched
|
|
354
|
+
sleep(10e3).then(() => {
|
|
355
|
+
if (!ctx.stdinReady.isReady) ctx.stdinReady.ready();
|
|
356
|
+
if (!ctx.stdinFirstReady.isReady) ctx.stdinFirstReady.ready();
|
|
357
|
+
});
|
|
442
358
|
|
|
443
359
|
const pendingExitCode = Promise.withResolvers<number | null>();
|
|
444
360
|
|
|
@@ -449,18 +365,18 @@ export default async function agentYes({
|
|
|
449
365
|
|
|
450
366
|
shell.onData(onData);
|
|
451
367
|
shell.onExit(async function onExit({ exitCode }) {
|
|
452
|
-
stdinReady.unready(); // start buffer stdin
|
|
368
|
+
ctx.stdinReady.unready(); // start buffer stdin
|
|
453
369
|
const agentCrashed = exitCode !== 0;
|
|
454
370
|
|
|
455
371
|
// Handle restart without continue args (e.g., "No conversation found to continue")
|
|
456
372
|
// logger.debug(``, { shouldRestartWithoutContinue, robust })
|
|
457
|
-
if (shouldRestartWithoutContinue) {
|
|
373
|
+
if (ctx.shouldRestartWithoutContinue) {
|
|
458
374
|
await pidStore.updateStatus(shell.pid, "exited", {
|
|
459
375
|
exitReason: "restarted",
|
|
460
376
|
exitCode: exitCode ?? undefined,
|
|
461
377
|
});
|
|
462
|
-
shouldRestartWithoutContinue = false; // reset flag
|
|
463
|
-
isFatal = false; // reset fatal flag to allow restart
|
|
378
|
+
ctx.shouldRestartWithoutContinue = false; // reset flag
|
|
379
|
+
ctx.isFatal = false; // reset fatal flag to allow restart
|
|
464
380
|
|
|
465
381
|
// Restart without continue args - use original cliArgs without restoreArgs
|
|
466
382
|
const cliCommand = cliConf?.binary || cli;
|
|
@@ -487,7 +403,7 @@ export default async function agentYes({
|
|
|
487
403
|
);
|
|
488
404
|
return;
|
|
489
405
|
}
|
|
490
|
-
if (isFatal) {
|
|
406
|
+
if (ctx.isFatal) {
|
|
491
407
|
await pidStore.updateStatus(shell.pid, "exited", {
|
|
492
408
|
exitReason: "fatal",
|
|
493
409
|
exitCode: exitCode ?? undefined,
|
|
@@ -541,9 +457,8 @@ export default async function agentYes({
|
|
|
541
457
|
.replace(/\s+/g, " ")
|
|
542
458
|
.match(/esc to interrupt|to run in background/);
|
|
543
459
|
|
|
544
|
-
const idleWaiter = new IdleWaiter();
|
|
545
460
|
if (exitOnIdle)
|
|
546
|
-
idleWaiter.wait(exitOnIdle).then(async () => {
|
|
461
|
+
ctx.idleWaiter.wait(exitOnIdle).then(async () => {
|
|
547
462
|
await pidStore.updateStatus(shell.pid, "idle").catch(() => null);
|
|
548
463
|
if (isStillWorkingQ()) {
|
|
549
464
|
logger.warn("[${cli}-yes] ${cli} is idle, but seems still working, not exiting yet");
|
|
@@ -562,22 +477,11 @@ export default async function agentYes({
|
|
|
562
477
|
.map((buffer) => buffer.toString())
|
|
563
478
|
|
|
564
479
|
.by(function handleTerminateSignals(s) {
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
if (!aborted && chunk === "\u001A") {
|
|
569
|
-
return "";
|
|
570
|
-
}
|
|
571
|
-
// handle CTRL+C, when stdin is not ready (no response from agent yet, usually this is when agent loading)
|
|
572
|
-
if (!aborted && !stdinReady.isReady && chunk === "\u0003") {
|
|
573
|
-
logger.error("User aborted: SIGINT");
|
|
574
|
-
shell.kill("SIGINT");
|
|
575
|
-
pendingExitCode.resolve(130); // SIGINT
|
|
576
|
-
aborted = true;
|
|
577
|
-
return chunk; // still pass into agent, but they prob be killed XD
|
|
578
|
-
}
|
|
579
|
-
return chunk; // normal inputs
|
|
480
|
+
const handler = createTerminateSignalHandler(ctx.stdinReady, (exitCode) => {
|
|
481
|
+
shell.kill("SIGINT");
|
|
482
|
+
pendingExitCode.resolve(exitCode);
|
|
580
483
|
});
|
|
484
|
+
return s.map(handler);
|
|
581
485
|
})
|
|
582
486
|
|
|
583
487
|
// read from IPC stream if available (FIFO on Linux, Named Pipes on Windows)
|
|
@@ -596,14 +500,14 @@ export default async function agentYes({
|
|
|
596
500
|
.onStart(async function promptOnStart() {
|
|
597
501
|
// send prompt when start
|
|
598
502
|
logger.debug("Sending prompt message: " + JSON.stringify(prompt));
|
|
599
|
-
if (prompt) await sendMessage(prompt);
|
|
503
|
+
if (prompt) await sendMessage(ctx.messageContext, prompt);
|
|
600
504
|
})
|
|
601
505
|
|
|
602
506
|
// pipe content by shell
|
|
603
507
|
.by({
|
|
604
508
|
writable: new WritableStream<string>({
|
|
605
509
|
write: async (data) => {
|
|
606
|
-
await stdinReady.wait();
|
|
510
|
+
await ctx.stdinReady.wait();
|
|
607
511
|
shell.write(data);
|
|
608
512
|
},
|
|
609
513
|
}),
|
|
@@ -611,21 +515,21 @@ export default async function agentYes({
|
|
|
611
515
|
})
|
|
612
516
|
|
|
613
517
|
.forEach(() => {
|
|
614
|
-
idleWaiter.ping();
|
|
518
|
+
ctx.idleWaiter.ping();
|
|
615
519
|
pidStore.updateStatus(shell.pid, "active").catch(() => null);
|
|
616
520
|
})
|
|
617
|
-
.forEach(() => nextStdout.ready())
|
|
521
|
+
.forEach(() => ctx.nextStdout.ready())
|
|
618
522
|
|
|
619
523
|
.forkTo(async function rawLogger(f) {
|
|
620
|
-
if (!rawLogPath) return f.run(); // no stream
|
|
524
|
+
if (!ctx.logPaths.rawLogPath) return f.run(); // no stream
|
|
621
525
|
|
|
622
526
|
// try stream the raw log for realtime debugging, including control chars, note: it will be a huge file
|
|
623
|
-
return await mkdir(path.dirname(rawLogPath), { recursive: true })
|
|
527
|
+
return await mkdir(path.dirname(ctx.logPaths.rawLogPath), { recursive: true })
|
|
624
528
|
.then(() => {
|
|
625
|
-
logger.debug(`[${cli}-yes] raw logs streaming to ${rawLogPath}`);
|
|
529
|
+
logger.debug(`[${cli}-yes] raw logs streaming to ${ctx.logPaths.rawLogPath}`);
|
|
626
530
|
return f
|
|
627
531
|
.forEach(async (chars) => {
|
|
628
|
-
await writeFile(rawLogPath
|
|
532
|
+
await writeFile(ctx.logPaths.rawLogPath!, chars, { flag: "a" }).catch(() => null);
|
|
629
533
|
})
|
|
630
534
|
.run();
|
|
631
535
|
})
|
|
@@ -636,36 +540,7 @@ export default async function agentYes({
|
|
|
636
540
|
.by(function consoleResponder(e) {
|
|
637
541
|
// wait for cli ready and send prompt if provided
|
|
638
542
|
if (cli === "codex") shell.write(`\u001b[1;1R`); // send cursor position response when stdin is not tty
|
|
639
|
-
return e.forEach((text) =>
|
|
640
|
-
// render terminal output for log file
|
|
641
|
-
terminalRender.write(text);
|
|
642
|
-
|
|
643
|
-
// Handle Device Attributes query (DA) - ESC[c or ESC[0c
|
|
644
|
-
// This must be handled regardless of TTY status
|
|
645
|
-
if (text.includes("\u001b[c") || text.includes("\u001b[0c")) {
|
|
646
|
-
// Respond shell with VT100 with Advanced Video Option
|
|
647
|
-
shell.write("\u001b[?1;2c");
|
|
648
|
-
if (verbose) {
|
|
649
|
-
logger.debug("device|respond DA: VT100 with Advanced Video Option");
|
|
650
|
-
}
|
|
651
|
-
return;
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
// todo: .onStatus((msg)=> shell.write(msg))
|
|
655
|
-
if (process.stdin.isTTY) return; // only handle it when stdin is not tty, because tty already handled this
|
|
656
|
-
|
|
657
|
-
if (!text.includes("\u001b[6n")) return; // only asked for cursor position
|
|
658
|
-
// todo: use terminalRender API to get cursor position when new version is available
|
|
659
|
-
// xterm replies CSI row; column R if asked cursor position
|
|
660
|
-
// https://en.wikipedia.org/wiki/ANSI_escape_code#:~:text=citation%20needed%5D-,xterm%20replies,-CSI%20row%C2%A0%3B
|
|
661
|
-
// when agent asking position, respond with row; col
|
|
662
|
-
// const rendered = terminalRender.render();
|
|
663
|
-
const { col, row } = terminalRender.getCursorPosition();
|
|
664
|
-
shell.write(`\u001b[${row};${col}R`); // reply cli when getting cursor position
|
|
665
|
-
logger.debug(`cursor|respond position: row=${String(row)}, col=${String(col)}`);
|
|
666
|
-
// const row = rendered.split('\n').length + 1;
|
|
667
|
-
// const col = (rendered.split('\n').slice(-1)[0]?.length || 0) + 1;
|
|
668
|
-
});
|
|
543
|
+
return e.forEach((text) => handleConsoleControlCodes(text, shell, terminalRender, cli, verbose));
|
|
669
544
|
})
|
|
670
545
|
|
|
671
546
|
// auto-response
|
|
@@ -695,75 +570,19 @@ export default async function agentYes({
|
|
|
695
570
|
// })
|
|
696
571
|
|
|
697
572
|
// Generic auto-response handler driven by CLI_CONFIGURES
|
|
698
|
-
.forEach(async
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
if (conf.ready?.some((rx: RegExp) => e.match(rx))) {
|
|
702
|
-
logger.debug(`ready |${e}`);
|
|
703
|
-
if (cli === "gemini" && i <= 80) return; // gemini initial noise, only after many lines
|
|
704
|
-
stdinReady.ready();
|
|
705
|
-
stdinFirstReady.ready();
|
|
706
|
-
}
|
|
707
|
-
// enter matchers: send Enter when any enter regex matches
|
|
708
|
-
|
|
709
|
-
if (conf.enter?.some((rx: RegExp) => e.match(rx))) {
|
|
710
|
-
logger.debug(`enter |${e}`);
|
|
711
|
-
return await sendEnter(400); // wait for idle for a short while and then send Enter
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
// typingRespond matcher: if matched, send the specified message
|
|
715
|
-
const typingResponded = await sflow(Object.entries(conf.typingRespond ?? {}))
|
|
716
|
-
.filter(([_sendString, onThePatterns]) => onThePatterns.some((rx) => e.match(rx)))
|
|
717
|
-
.map(async ([sendString]) => await sendMessage(sendString, { waitForReady: false }))
|
|
718
|
-
.toCount();
|
|
719
|
-
if (typingResponded) return;
|
|
720
|
-
|
|
721
|
-
// fatal matchers: set isFatal flag when matched
|
|
722
|
-
if (conf.fatal?.some((rx: RegExp) => e.match(rx))) {
|
|
723
|
-
logger.debug(`fatal |${e}`);
|
|
724
|
-
isFatal = true;
|
|
725
|
-
await exitAgent();
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
// restartWithoutContinueArg matchers: set flag to restart without continue args
|
|
729
|
-
if (conf.restartWithoutContinueArg?.some((rx: RegExp) => e.match(rx))) {
|
|
730
|
-
await logger.debug(`restart-without-continue|${e}`);
|
|
731
|
-
shouldRestartWithoutContinue = true;
|
|
732
|
-
isFatal = true; // also set fatal to trigger exit
|
|
733
|
-
await exitAgent();
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
// session ID capture for codex
|
|
737
|
-
if (cli === "codex") {
|
|
738
|
-
const sessionId = extractSessionId(e);
|
|
739
|
-
if (sessionId) {
|
|
740
|
-
await logger.debug(`session|captured session ID: ${sessionId}`);
|
|
741
|
-
await storeSessionForCwd(workingDir, sessionId);
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
})
|
|
573
|
+
.forEach(async (line, lineIndex) =>
|
|
574
|
+
createAutoResponseHandler(line, lineIndex, { ctx, conf, cli, workingDir, exitAgent }),
|
|
575
|
+
)
|
|
745
576
|
.run()
|
|
746
577
|
);
|
|
747
578
|
})
|
|
748
579
|
.by((s) => (removeControlCharactersFromStdout ? s.map((e) => removeControlCharacters(e)) : s))
|
|
749
580
|
|
|
750
581
|
// terminate whole stream when shell did exited (already crash-handled)
|
|
751
|
-
.by(
|
|
752
|
-
new TransformStream({
|
|
753
|
-
start: function terminator(ctrl) {
|
|
754
|
-
pendingExitCode.promise.then(() => ctrl.terminate());
|
|
755
|
-
},
|
|
756
|
-
transform: (e, ctrl) => ctrl.enqueue(e),
|
|
757
|
-
flush: (ctrl) => ctrl.terminate(),
|
|
758
|
-
}),
|
|
759
|
-
)
|
|
582
|
+
.by(createTerminatorStream(pendingExitCode.promise))
|
|
760
583
|
.to(fromWritable(process.stdout));
|
|
761
584
|
|
|
762
|
-
|
|
763
|
-
await mkdir(path.dirname(logPath), { recursive: true }).catch(() => null);
|
|
764
|
-
await writeFile(logPath, terminalRender.render()).catch(() => null);
|
|
765
|
-
logger.info(`[${cli}-yes] Full logs saved to ${logPath}`);
|
|
766
|
-
}
|
|
585
|
+
await saveLogFile(ctx.logPaths.logPath, terminalRender.render());
|
|
767
586
|
|
|
768
587
|
// and then get its exitcode
|
|
769
588
|
const exitCode = await pendingExitCode.promise;
|
|
@@ -776,72 +595,15 @@ export default async function agentYes({
|
|
|
776
595
|
await outputWriter.close();
|
|
777
596
|
|
|
778
597
|
// deprecated logFile option, we have logPath now, but keep for backward compatibility
|
|
779
|
-
|
|
780
|
-
if (verbose) logger.info(`[${cli}-yes] Writing rendered logs to ${logFile}`);
|
|
781
|
-
const logFilePath = path.resolve(logFile);
|
|
782
|
-
await mkdir(path.dirname(logFilePath), { recursive: true }).catch(() => null);
|
|
783
|
-
await writeFile(logFilePath, terminalRender.render());
|
|
784
|
-
}
|
|
598
|
+
await saveDeprecatedLogFile(logFile, terminalRender.render(), verbose);
|
|
785
599
|
|
|
786
600
|
return { exitCode, logs: terminalRender.render() };
|
|
787
601
|
|
|
788
|
-
async function sendEnter(waitms = 1000) {
|
|
789
|
-
// wait for idle for a bit to let agent cli finish rendering
|
|
790
|
-
const st = Date.now();
|
|
791
|
-
await idleWaiter.wait(waitms); // wait for idle a while
|
|
792
|
-
const et = Date.now();
|
|
793
|
-
// process.stdout.write(`\ridleWaiter.wait(${waitms}) took ${et - st}ms\r`);
|
|
794
|
-
logger.debug(`sendEn| idleWaiter.wait(${String(waitms)}) took ${String(et - st)}ms`);
|
|
795
|
-
nextStdout.unready();
|
|
796
|
-
// send the enter key
|
|
797
|
-
shell.write("\r");
|
|
798
|
-
|
|
799
|
-
// retry once if not received any output in 1 second after sending Enter
|
|
800
|
-
await Promise.race([
|
|
801
|
-
nextStdout.wait(),
|
|
802
|
-
new Promise<void>((resolve) =>
|
|
803
|
-
setTimeout(() => {
|
|
804
|
-
if (!nextStdout.ready) {
|
|
805
|
-
shell.write("\r");
|
|
806
|
-
}
|
|
807
|
-
resolve();
|
|
808
|
-
}, 1000),
|
|
809
|
-
),
|
|
810
|
-
]);
|
|
811
|
-
|
|
812
|
-
// retry the second time if not received any output in 3 second after sending Enter
|
|
813
|
-
await Promise.race([
|
|
814
|
-
nextStdout.wait(),
|
|
815
|
-
new Promise<void>((resolve) =>
|
|
816
|
-
setTimeout(() => {
|
|
817
|
-
if (!nextStdout.ready) {
|
|
818
|
-
shell.write("\r");
|
|
819
|
-
}
|
|
820
|
-
resolve();
|
|
821
|
-
}, 3000),
|
|
822
|
-
),
|
|
823
|
-
]);
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
async function sendMessage(message: string, { waitForReady = true } = {}) {
|
|
827
|
-
if (waitForReady) await stdinReady.wait();
|
|
828
|
-
// show in-place message: write msg and move cursor back start
|
|
829
|
-
logger.debug(`send |${message}`);
|
|
830
|
-
nextStdout.unready();
|
|
831
|
-
shell.write(message);
|
|
832
|
-
idleWaiter.ping(); // just sent a message, wait for echo
|
|
833
|
-
logger.debug(`waiting next stdout|${message}`);
|
|
834
|
-
await nextStdout.wait();
|
|
835
|
-
logger.debug(`sending enter`);
|
|
836
|
-
await sendEnter(1000);
|
|
837
|
-
logger.debug(`sent enter`);
|
|
838
|
-
}
|
|
839
|
-
|
|
840
602
|
async function exitAgent() {
|
|
841
|
-
robust = false; // disable robust to avoid auto restart
|
|
603
|
+
ctx.robust = false; // disable robust to avoid auto restart
|
|
842
604
|
|
|
843
605
|
// send exit command to the shell, must sleep a bit to avoid claude treat it as pasted input
|
|
844
|
-
for (const cmd of cliConf.exitCommands ?? ["/exit"]) await sendMessage(cmd);
|
|
606
|
+
for (const cmd of cliConf.exitCommands ?? ["/exit"]) await sendMessage(ctx.messageContext, cmd);
|
|
845
607
|
|
|
846
608
|
// wait for shell to exit or kill it with a timeout
|
|
847
609
|
let exited = false;
|
|
@@ -858,16 +620,6 @@ export default async function agentYes({
|
|
|
858
620
|
), // 5 seconds timeout
|
|
859
621
|
]);
|
|
860
622
|
}
|
|
861
|
-
|
|
862
|
-
function getTerminalDimensions() {
|
|
863
|
-
if (!process.stdout.isTTY) return { cols: 80, rows: 30 }; // default size when not tty
|
|
864
|
-
return {
|
|
865
|
-
// TODO: enforce minimum cols/rows to avoid layout issues
|
|
866
|
-
// cols: Math.max(process.stdout.columns, 80),
|
|
867
|
-
cols: Math.min(Math.max(20, process.stdout.columns), 80),
|
|
868
|
-
rows: process.stdout.rows,
|
|
869
|
-
};
|
|
870
|
-
}
|
|
871
623
|
}
|
|
872
624
|
|
|
873
625
|
function sleep(ms: number) {
|
package/ts/parseCliArgs.ts
CHANGED
|
@@ -196,6 +196,6 @@ export function parseCliArgs(argv: string[]) {
|
|
|
196
196
|
resume: parsedArgv.continue, // Note: intentional use resume here to avoid preserved keyword (continue)
|
|
197
197
|
useSkills: parsedArgv.useSkills,
|
|
198
198
|
appendPrompt: parsedArgv.appendPrompt,
|
|
199
|
-
useFifo: parsedArgv.stdpush || parsedArgv.ipc || parsedArgv.fifo, // Support --stdpush, --ipc, and --fifo (backward compatibility)
|
|
199
|
+
useFifo: Boolean(parsedArgv.stdpush || parsedArgv.ipc || parsedArgv.fifo), // Support --stdpush, --ipc, and --fifo (backward compatibility)
|
|
200
200
|
};
|
|
201
201
|
}
|