@suzuke/agend 1.0.2 → 1.1.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/backend/claude-code.d.ts +5 -1
- package/dist/backend/claude-code.js +19 -5
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex.d.ts +3 -1
- package/dist/backend/codex.js +5 -1
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/gemini-cli.d.ts +3 -1
- package/dist/backend/gemini-cli.js +5 -1
- package/dist/backend/gemini-cli.js.map +1 -1
- package/dist/backend/opencode.d.ts +3 -1
- package/dist/backend/opencode.js +5 -1
- package/dist/backend/opencode.js.map +1 -1
- package/dist/backend/types.d.ts +8 -0
- package/dist/backend/types.js +14 -1
- package/dist/backend/types.js.map +1 -1
- package/dist/daemon.d.ts +20 -2
- package/dist/daemon.js +121 -22
- package/dist/daemon.js.map +1 -1
- package/dist/fleet-manager.d.ts +1 -0
- package/dist/fleet-manager.js +10 -1
- package/dist/fleet-manager.js.map +1 -1
- package/dist/tmux-control.d.ts +49 -0
- package/dist/tmux-control.js +184 -0
- package/dist/tmux-control.js.map +1 -0
- package/package.json +1 -1
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type CliBackend, type CliBackendConfig } from "./types.js";
|
|
2
2
|
export declare class ClaudeCodeBackend implements CliBackend {
|
|
3
3
|
private instanceDir;
|
|
4
|
+
readonly binaryName = "claude";
|
|
5
|
+
private binaryPath;
|
|
4
6
|
constructor(instanceDir: string);
|
|
5
7
|
buildCommand(config: CliBackendConfig): string;
|
|
6
8
|
writeConfig(config: CliBackendConfig): void;
|
|
@@ -9,5 +11,7 @@ export declare class ClaudeCodeBackend implements CliBackend {
|
|
|
9
11
|
cleanup(_config: CliBackendConfig): void;
|
|
10
12
|
/** Pre-approve ANTHROPIC_API_KEY in ~/.claude.json to skip the interactive prompt */
|
|
11
13
|
private preApproveApiKey;
|
|
14
|
+
/** Check if user has an active OAuth session in ~/.claude.json */
|
|
15
|
+
private hasOAuthSession;
|
|
12
16
|
private writeStatusLineScript;
|
|
13
17
|
}
|
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
|
+
import { resolveBinary } from "./types.js";
|
|
4
5
|
export class ClaudeCodeBackend {
|
|
5
6
|
instanceDir;
|
|
7
|
+
binaryName = "claude";
|
|
8
|
+
binaryPath;
|
|
6
9
|
constructor(instanceDir) {
|
|
7
10
|
this.instanceDir = instanceDir;
|
|
11
|
+
this.binaryPath = resolveBinary("claude");
|
|
8
12
|
}
|
|
9
13
|
buildCommand(config) {
|
|
10
14
|
const settingsPath = join(this.instanceDir, "claude-settings.json");
|
|
11
15
|
const mcpConfigPath = join(this.instanceDir, "mcp-config.json");
|
|
12
|
-
// Forward Anthropic env vars to the CLI process (tmux shell doesn't inherit daemon's env)
|
|
13
16
|
const envPrefix = ["CMUX_CLAUDE_HOOKS_DISABLED=1", "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1"];
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
if (process.env.ANTHROPIC_BASE_URL)
|
|
18
|
+
envPrefix.push(`ANTHROPIC_BASE_URL=${process.env.ANTHROPIC_BASE_URL}`);
|
|
19
|
+
if (process.env.ANTHROPIC_API_KEY && !this.hasOAuthSession()) {
|
|
20
|
+
envPrefix.push(`ANTHROPIC_API_KEY=${process.env.ANTHROPIC_API_KEY}`);
|
|
17
21
|
}
|
|
18
|
-
let cmd = `${envPrefix.join(" ")}
|
|
22
|
+
let cmd = `${envPrefix.join(" ")} ${this.binaryPath} --settings ${settingsPath} --mcp-config ${mcpConfigPath} --dangerously-skip-permissions`;
|
|
19
23
|
const sessionIdFile = join(this.instanceDir, "session-id");
|
|
20
24
|
if (existsSync(sessionIdFile)) {
|
|
21
25
|
const sid = readFileSync(sessionIdFile, "utf-8").trim();
|
|
@@ -96,6 +100,16 @@ export class ClaudeCodeBackend {
|
|
|
96
100
|
writeFileSync(claudeJsonPath, JSON.stringify(claudeCfg, null, 2));
|
|
97
101
|
}
|
|
98
102
|
}
|
|
103
|
+
/** Check if user has an active OAuth session in ~/.claude.json */
|
|
104
|
+
hasOAuthSession() {
|
|
105
|
+
try {
|
|
106
|
+
const cfg = JSON.parse(readFileSync(join(homedir(), ".claude.json"), "utf-8"));
|
|
107
|
+
return !!cfg.oauthAccount?.accountUuid;
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
99
113
|
writeStatusLineScript() {
|
|
100
114
|
const statusFile = join(this.instanceDir, "statusline.json");
|
|
101
115
|
// Use a Node.js script instead of bash to avoid shell injection via statusFile path
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude-code.js","sourceRoot":"","sources":["../../src/backend/claude-code.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAClE,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"claude-code.js","sourceRoot":"","sources":["../../src/backend/claude-code.ts"],"names":[],"mappings":"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"}
|
package/dist/backend/codex.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type CliBackend, type CliBackendConfig } from "./types.js";
|
|
2
2
|
export declare class CodexBackend implements CliBackend {
|
|
3
3
|
private instanceDir;
|
|
4
|
+
readonly binaryName = "codex";
|
|
5
|
+
private binaryPath;
|
|
4
6
|
constructor(instanceDir: string);
|
|
5
7
|
buildCommand(config: CliBackendConfig): string;
|
|
6
8
|
writeConfig(config: CliBackendConfig): void;
|
package/dist/backend/codex.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { resolveBinary } from "./types.js";
|
|
3
4
|
export class CodexBackend {
|
|
4
5
|
instanceDir;
|
|
6
|
+
binaryName = "codex";
|
|
7
|
+
binaryPath;
|
|
5
8
|
constructor(instanceDir) {
|
|
6
9
|
this.instanceDir = instanceDir;
|
|
10
|
+
this.binaryPath = resolveBinary("codex");
|
|
7
11
|
}
|
|
8
12
|
buildCommand(config) {
|
|
9
|
-
let cmd =
|
|
13
|
+
let cmd = `${this.binaryPath} --full-auto`;
|
|
10
14
|
if (config.model) {
|
|
11
15
|
cmd += ` -c model="${config.model}"`;
|
|
12
16
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codex.js","sourceRoot":"","sources":["../../src/backend/codex.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAc,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"codex.js","sourceRoot":"","sources":["../../src/backend/codex.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type CliBackend, type CliBackendConfig } from "./types.js";
|
|
2
2
|
export declare class GeminiCliBackend implements CliBackend {
|
|
3
3
|
private instanceDir;
|
|
4
|
+
readonly binaryName = "gemini";
|
|
5
|
+
private binaryPath;
|
|
4
6
|
constructor(instanceDir: string);
|
|
5
7
|
buildCommand(config: CliBackendConfig): string;
|
|
6
8
|
writeConfig(config: CliBackendConfig): void;
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
3
|
+
import { resolveBinary } from "./types.js";
|
|
3
4
|
export class GeminiCliBackend {
|
|
4
5
|
instanceDir;
|
|
6
|
+
binaryName = "gemini";
|
|
7
|
+
binaryPath;
|
|
5
8
|
constructor(instanceDir) {
|
|
6
9
|
this.instanceDir = instanceDir;
|
|
10
|
+
this.binaryPath = resolveBinary("gemini");
|
|
7
11
|
}
|
|
8
12
|
buildCommand(config) {
|
|
9
|
-
let cmd =
|
|
13
|
+
let cmd = `${this.binaryPath} --yolo`;
|
|
10
14
|
const sessionIdFile = join(this.instanceDir, "session-id");
|
|
11
15
|
if (existsSync(sessionIdFile)) {
|
|
12
16
|
const sid = readFileSync(sessionIdFile, "utf-8").trim();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gemini-cli.js","sourceRoot":"","sources":["../../src/backend/gemini-cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"gemini-cli.js","sourceRoot":"","sources":["../../src/backend/gemini-cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type CliBackend, type CliBackendConfig } from "./types.js";
|
|
2
2
|
export declare class OpenCodeBackend implements CliBackend {
|
|
3
3
|
private instanceDir;
|
|
4
|
+
readonly binaryName = "opencode";
|
|
5
|
+
private binaryPath;
|
|
4
6
|
constructor(instanceDir: string);
|
|
5
7
|
buildCommand(_config: CliBackendConfig): string;
|
|
6
8
|
writeConfig(config: CliBackendConfig): void;
|
package/dist/backend/opencode.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { resolveBinary } from "./types.js";
|
|
3
4
|
export class OpenCodeBackend {
|
|
4
5
|
instanceDir;
|
|
6
|
+
binaryName = "opencode";
|
|
7
|
+
binaryPath;
|
|
5
8
|
constructor(instanceDir) {
|
|
6
9
|
this.instanceDir = instanceDir;
|
|
10
|
+
this.binaryPath = resolveBinary("opencode");
|
|
7
11
|
}
|
|
8
12
|
buildCommand(_config) {
|
|
9
|
-
return
|
|
13
|
+
return this.binaryPath;
|
|
10
14
|
}
|
|
11
15
|
writeConfig(config) {
|
|
12
16
|
// OpenCode uses opencode.json in the working directory
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opencode.js","sourceRoot":"","sources":["../../src/backend/opencode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"opencode.js","sourceRoot":"","sources":["../../src/backend/opencode.ts"],"names":[],"mappings":"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"}
|
package/dist/backend/types.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ export interface CliBackendConfig {
|
|
|
13
13
|
model?: string;
|
|
14
14
|
}
|
|
15
15
|
export interface CliBackend {
|
|
16
|
+
/** The CLI binary name (e.g. "claude", "gemini", "codex") */
|
|
17
|
+
readonly binaryName: string;
|
|
16
18
|
/** Build the shell command string to launch the CLI in a tmux window. */
|
|
17
19
|
buildCommand(config: CliBackendConfig): string;
|
|
18
20
|
/** Write all config files the CLI needs before launch. */
|
|
@@ -24,3 +26,9 @@ export interface CliBackend {
|
|
|
24
26
|
/** Clean up config files on shutdown. */
|
|
25
27
|
cleanup?(config: CliBackendConfig): void;
|
|
26
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Resolve the full path to a CLI binary.
|
|
31
|
+
* tmux new-window runs commands in a minimal shell without user PATH,
|
|
32
|
+
* so we resolve at daemon startup time when the full PATH is available.
|
|
33
|
+
*/
|
|
34
|
+
export declare function resolveBinary(name: string): string;
|
package/dist/backend/types.js
CHANGED
|
@@ -1,2 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
/**
|
|
3
|
+
* Resolve the full path to a CLI binary.
|
|
4
|
+
* tmux new-window runs commands in a minimal shell without user PATH,
|
|
5
|
+
* so we resolve at daemon startup time when the full PATH is available.
|
|
6
|
+
*/
|
|
7
|
+
export function resolveBinary(name) {
|
|
8
|
+
try {
|
|
9
|
+
return execFileSync("which", [name], { encoding: "utf-8" }).trim();
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return name; // fallback to bare name
|
|
13
|
+
}
|
|
14
|
+
}
|
|
2
15
|
//# sourceMappingURL=types.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/backend/types.ts"],"names":[],"mappings":""}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/backend/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAsClD;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACrE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC,CAAC,wBAAwB;IACvC,CAAC;AACH,CAAC"}
|
package/dist/daemon.d.ts
CHANGED
|
@@ -3,12 +3,14 @@ import type { InstanceConfig, RotationSnapshot } from "./types.js";
|
|
|
3
3
|
import { MessageBus } from "./channel/message-bus.js";
|
|
4
4
|
import type { CliBackend } from "./backend/types.js";
|
|
5
5
|
import { HangDetector } from "./hang-detector.js";
|
|
6
|
+
import type { TmuxControlClient } from "./tmux-control.js";
|
|
6
7
|
export declare class Daemon extends EventEmitter {
|
|
7
8
|
private name;
|
|
8
9
|
private config;
|
|
9
10
|
private instanceDir;
|
|
10
11
|
private topicMode;
|
|
11
12
|
private backend?;
|
|
13
|
+
private controlClient?;
|
|
12
14
|
private logger;
|
|
13
15
|
private tmux;
|
|
14
16
|
private ipcServer;
|
|
@@ -39,7 +41,8 @@ export declare class Daemon extends EventEmitter {
|
|
|
39
41
|
private recentUserMessages;
|
|
40
42
|
private recentEvents;
|
|
41
43
|
private recentToolActivity;
|
|
42
|
-
|
|
44
|
+
private pasteLock;
|
|
45
|
+
constructor(name: string, config: InstanceConfig, instanceDir: string, topicMode?: boolean, backend?: CliBackend | undefined, controlClient?: TmuxControlClient | undefined);
|
|
43
46
|
start(): Promise<void>;
|
|
44
47
|
private startHealthCheck;
|
|
45
48
|
stop(): Promise<void>;
|
|
@@ -58,6 +61,9 @@ export declare class Daemon extends EventEmitter {
|
|
|
58
61
|
* Otherwise send to the instance's own session (this.name).
|
|
59
62
|
*/
|
|
60
63
|
pushChannelMessage(content: string, meta: Record<string, string>, _targetSession?: string): void;
|
|
64
|
+
/** Deliver a single message: wait for idle, then paste */
|
|
65
|
+
private deliverMessage;
|
|
66
|
+
private getWindowId;
|
|
61
67
|
/** Find the IPC socket for a given sessionName */
|
|
62
68
|
private findSocketBySession;
|
|
63
69
|
/**
|
|
@@ -69,8 +75,20 @@ export declare class Daemon extends EventEmitter {
|
|
|
69
75
|
private buildBackendConfig;
|
|
70
76
|
/** Combine fleet context with user-configured system prompt + previous session snapshot */
|
|
71
77
|
private buildSystemPrompt;
|
|
72
|
-
/** Spawn (or respawn) a
|
|
78
|
+
/** Spawn (or respawn) a CLI window in tmux */
|
|
73
79
|
private spawnClaudeWindow;
|
|
80
|
+
/**
|
|
81
|
+
* Spawn a CLI window and verify it reaches a ready state.
|
|
82
|
+
* Uses control mode to wait for output, then checks pane content.
|
|
83
|
+
* Handles confirmation dialogs (trust folder, bypass permissions).
|
|
84
|
+
* Returns true if CLI is ready, false if it failed or got stuck.
|
|
85
|
+
*/
|
|
86
|
+
private trySpawn;
|
|
87
|
+
/**
|
|
88
|
+
* Repeatedly check pane content, dismiss any confirmation dialogs,
|
|
89
|
+
* and return true once CLI reaches a ready prompt.
|
|
90
|
+
*/
|
|
91
|
+
private dismissDialogsUntilReady;
|
|
74
92
|
private saveSessionId;
|
|
75
93
|
private readContextPercentage;
|
|
76
94
|
/** Set a model override for next spawn (used by failover logic) */
|
package/dist/daemon.js
CHANGED
|
@@ -19,6 +19,7 @@ export class Daemon extends EventEmitter {
|
|
|
19
19
|
instanceDir;
|
|
20
20
|
topicMode;
|
|
21
21
|
backend;
|
|
22
|
+
controlClient;
|
|
22
23
|
logger;
|
|
23
24
|
tmux = null;
|
|
24
25
|
ipcServer = null;
|
|
@@ -57,13 +58,15 @@ export class Daemon extends EventEmitter {
|
|
|
57
58
|
recentUserMessages = [];
|
|
58
59
|
recentEvents = [];
|
|
59
60
|
recentToolActivity = [];
|
|
60
|
-
|
|
61
|
+
pasteLock = Promise.resolve();
|
|
62
|
+
constructor(name, config, instanceDir, topicMode = false, backend, controlClient) {
|
|
61
63
|
super();
|
|
62
64
|
this.name = name;
|
|
63
65
|
this.config = config;
|
|
64
66
|
this.instanceDir = instanceDir;
|
|
65
67
|
this.topicMode = topicMode;
|
|
66
68
|
this.backend = backend;
|
|
69
|
+
this.controlClient = controlClient;
|
|
67
70
|
this.logger = createLogger(config.log_level);
|
|
68
71
|
this.messageBus = new MessageBus();
|
|
69
72
|
this.messageBus.setLogger(this.logger);
|
|
@@ -472,15 +475,31 @@ export class Daemon extends EventEmitter {
|
|
|
472
475
|
const threadId = meta.thread_id || "";
|
|
473
476
|
formatted = `[user:${user} chat_id:${chatId} thread_id:${threadId}] ${content}\n(Reply using the reply tool with chat_id="${chatId}" — do NOT respond with direct text)`;
|
|
474
477
|
}
|
|
475
|
-
|
|
478
|
+
// Serialize deliveries: each message waits for the previous to complete,
|
|
479
|
+
// and each waits for the CLI to be idle before pasting.
|
|
480
|
+
this.pasteLock = this.pasteLock.then(() => this.deliverMessage(formatted));
|
|
481
|
+
this.logger.debug({ user: meta.user, text: content.slice(0, 100) }, "Queued channel message for delivery");
|
|
482
|
+
}
|
|
483
|
+
/** Deliver a single message: wait for idle, then paste */
|
|
484
|
+
async deliverMessage(formatted) {
|
|
485
|
+
const windowId = this.getWindowId();
|
|
486
|
+
if (windowId && this.controlClient) {
|
|
487
|
+
const idle = await this.controlClient.waitForIdle(windowId);
|
|
488
|
+
if (!idle) {
|
|
489
|
+
this.logger.warn("Delivering message after idle timeout (CLI may be busy)");
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
const ok = await this.tmux.pasteText(formatted);
|
|
493
|
+
if (!ok) {
|
|
476
494
|
// Window ID may be stale after crash/respawn — try to find by name
|
|
477
|
-
this.logger.warn(
|
|
495
|
+
this.logger.warn("pasteText failed, looking up window by name");
|
|
478
496
|
try {
|
|
479
497
|
const windows = await TmuxManager.listWindows("agend");
|
|
480
498
|
const match = windows.find(w => w.name === this.name);
|
|
481
499
|
if (match) {
|
|
482
500
|
this.tmux = new TmuxManager("agend", match.id);
|
|
483
501
|
writeFileSync(join(this.instanceDir, "window-id"), match.id);
|
|
502
|
+
await this.controlClient?.registerWindow(match.id);
|
|
484
503
|
await this.tmux.pasteText(formatted);
|
|
485
504
|
this.logger.info({ windowId: match.id }, "Recovered window ID and delivered message");
|
|
486
505
|
}
|
|
@@ -488,8 +507,15 @@ export class Daemon extends EventEmitter {
|
|
|
488
507
|
catch (retryErr) {
|
|
489
508
|
this.logger.error({ err: retryErr }, "Failed to recover window for message delivery");
|
|
490
509
|
}
|
|
491
|
-
}
|
|
492
|
-
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
getWindowId() {
|
|
513
|
+
try {
|
|
514
|
+
return readFileSync(join(this.instanceDir, "window-id"), "utf-8").trim() || undefined;
|
|
515
|
+
}
|
|
516
|
+
catch {
|
|
517
|
+
return undefined;
|
|
518
|
+
}
|
|
493
519
|
}
|
|
494
520
|
/** Find the IPC socket for a given sessionName */
|
|
495
521
|
findSocketBySession(sessionName) {
|
|
@@ -636,37 +662,110 @@ export class Daemon extends EventEmitter {
|
|
|
636
662
|
}
|
|
637
663
|
return prompt;
|
|
638
664
|
}
|
|
639
|
-
/** Spawn (or respawn) a
|
|
665
|
+
/** Spawn (or respawn) a CLI window in tmux */
|
|
640
666
|
async spawnClaudeWindow() {
|
|
641
667
|
this.spawning = true;
|
|
642
668
|
try {
|
|
643
|
-
// Clear tool status from previous session
|
|
644
669
|
this.toolStatusLines = [];
|
|
645
670
|
this.toolStatusMessageId = null;
|
|
646
671
|
if (!this.backend) {
|
|
647
|
-
throw new Error("No backend configured — cannot spawn
|
|
672
|
+
throw new Error("No backend configured — cannot spawn CLI window");
|
|
648
673
|
}
|
|
649
|
-
const
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
674
|
+
const alive = await this.trySpawn();
|
|
675
|
+
if (!alive) {
|
|
676
|
+
// First attempt failed (stale --resume, crash, rate limit, etc.)
|
|
677
|
+
// Clean slate: clear session-id and retry once.
|
|
678
|
+
this.logger.warn("CLI startup failed — clearing session-id and retrying");
|
|
679
|
+
const sidFile = join(this.instanceDir, "session-id");
|
|
680
|
+
try {
|
|
681
|
+
unlinkSync(sidFile);
|
|
682
|
+
}
|
|
683
|
+
catch { /* may not exist */ }
|
|
684
|
+
await this.tmux.killWindow();
|
|
685
|
+
const retryAlive = await this.trySpawn();
|
|
686
|
+
if (!retryAlive) {
|
|
687
|
+
await this.tmux.killWindow();
|
|
688
|
+
throw new Error("CLI failed to start after retry");
|
|
689
|
+
}
|
|
662
690
|
}
|
|
663
|
-
catch { /* window may have exited */ }
|
|
664
691
|
this.lastSpawnAt = Date.now();
|
|
665
692
|
}
|
|
666
693
|
finally {
|
|
667
694
|
this.spawning = false;
|
|
668
695
|
}
|
|
669
696
|
}
|
|
697
|
+
/**
|
|
698
|
+
* Spawn a CLI window and verify it reaches a ready state.
|
|
699
|
+
* Uses control mode to wait for output, then checks pane content.
|
|
700
|
+
* Handles confirmation dialogs (trust folder, bypass permissions).
|
|
701
|
+
* Returns true if CLI is ready, false if it failed or got stuck.
|
|
702
|
+
*/
|
|
703
|
+
async trySpawn() {
|
|
704
|
+
const backendConfig = this.buildBackendConfig();
|
|
705
|
+
this.backend.writeConfig(backendConfig);
|
|
706
|
+
const cmd = `AGEND_INSTANCE_NAME=${this.name} ` + this.backend.buildCommand(backendConfig);
|
|
707
|
+
const windowId = await this.tmux.createWindow(cmd, this.config.working_directory, this.name);
|
|
708
|
+
writeFileSync(join(this.instanceDir, "window-id"), windowId);
|
|
709
|
+
// Register with control client and wait for output + idle
|
|
710
|
+
await this.controlClient?.registerWindow(windowId);
|
|
711
|
+
if (this.controlClient) {
|
|
712
|
+
const hasOutput = await this.controlClient.waitForOutput(windowId, 15_000);
|
|
713
|
+
if (!hasOutput)
|
|
714
|
+
return false;
|
|
715
|
+
await this.controlClient.waitForIdle(windowId, 10_000);
|
|
716
|
+
}
|
|
717
|
+
else {
|
|
718
|
+
await new Promise(r => setTimeout(r, 10_000));
|
|
719
|
+
}
|
|
720
|
+
// Dismiss confirmation dialogs and verify CLI reached prompt
|
|
721
|
+
if (!await this.tmux.isWindowAlive())
|
|
722
|
+
return false;
|
|
723
|
+
return this.dismissDialogsUntilReady(3);
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Repeatedly check pane content, dismiss any confirmation dialogs,
|
|
727
|
+
* and return true once CLI reaches a ready prompt.
|
|
728
|
+
*/
|
|
729
|
+
async dismissDialogsUntilReady(maxAttempts) {
|
|
730
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
731
|
+
try {
|
|
732
|
+
const pane = await this.tmux.capturePane();
|
|
733
|
+
// CLI is ready
|
|
734
|
+
if (/❯|bypass permissions|ok\s*$/m.test(pane))
|
|
735
|
+
return true;
|
|
736
|
+
// Confirmation dialog: "Yes, I accept" / "Yes, I trust this folder"
|
|
737
|
+
// Navigate to the "Yes" option and confirm
|
|
738
|
+
if (/No, exit|I accept|I trust/i.test(pane)) {
|
|
739
|
+
this.logger.debug("Dismissing confirmation dialog");
|
|
740
|
+
// If "No" is selected (❯ on No), press Down to select Yes
|
|
741
|
+
if (/❯\s*\d+\.\s*No/m.test(pane)) {
|
|
742
|
+
await this.tmux.sendSpecialKey("Down");
|
|
743
|
+
await new Promise(r => setTimeout(r, 200));
|
|
744
|
+
}
|
|
745
|
+
await this.tmux.sendSpecialKey("Enter");
|
|
746
|
+
// Wait for next screen to render
|
|
747
|
+
if (this.controlClient) {
|
|
748
|
+
const wid = readFileSync(join(this.instanceDir, "window-id"), "utf-8").trim();
|
|
749
|
+
await this.controlClient.waitForIdle(wid, 10_000);
|
|
750
|
+
}
|
|
751
|
+
else {
|
|
752
|
+
await new Promise(r => setTimeout(r, 3_000));
|
|
753
|
+
}
|
|
754
|
+
if (!await this.tmux.isWindowAlive())
|
|
755
|
+
return false;
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
// Resume Session picker or command not found
|
|
759
|
+
if (/Resume Session|command not found|not found/i.test(pane))
|
|
760
|
+
return false;
|
|
761
|
+
}
|
|
762
|
+
catch {
|
|
763
|
+
return false;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
// Exhausted attempts — assume ok for unknown CLI prompts
|
|
767
|
+
return true;
|
|
768
|
+
}
|
|
670
769
|
saveSessionId() {
|
|
671
770
|
const sid = this.backend?.getSessionId();
|
|
672
771
|
if (sid) {
|