@prohost/cli 0.16.3 → 0.16.5
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/CHANGELOG.md +53 -0
- package/README.md +21 -4
- package/dist/agent/daemon.js +12 -1
- package/dist/agent/run.js +12 -4
- package/dist/codehost/hooks.d.ts +2 -0
- package/dist/codehost/hooks.js +15 -1
- package/dist/codehost/host.d.ts +24 -5
- package/dist/codehost/host.js +85 -3
- package/dist/codehost/ledger.d.ts +16 -2
- package/dist/codehost/ledger.js +43 -6
- package/dist/codehost/ready.d.ts +8 -9
- package/dist/codehost/ready.js +41 -9
- package/dist/codehost/session.d.ts +25 -0
- package/dist/codehost/session.js +61 -0
- package/dist/codehost/terminals.d.ts +119 -1
- package/dist/codehost/terminals.js +369 -5
- package/dist/codehost/tmux.d.ts +6 -0
- package/dist/codehost/tmux.js +9 -0
- package/dist/host/api.d.ts +7 -1
- package/dist/host/api.js +15 -1
- package/dist/host/command.js +334 -9
- package/dist/host/computer.d.ts +31 -0
- package/dist/host/computer.js +36 -0
- package/dist/host/install.d.ts +64 -0
- package/dist/host/install.js +317 -7
- package/dist/host/migrate.js +5 -1
- package/dist/host/pair.d.ts +1 -1
- package/dist/host/pair.js +2 -0
- package/dist/host/scheduler.d.ts +5 -1
- package/dist/host/scheduler.js +7 -1
- package/dist/host/service.d.ts +5 -0
- package/dist/host/service.js +49 -7
- package/dist/index.js +5 -1
- package/dist/version.d.ts +2 -2
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/codehost/ready.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* up or about to be drawn, and an Enter would answer it. The mark is a file,
|
|
10
10
|
* so it outlives a daemon restart.
|
|
11
11
|
*/
|
|
12
|
-
import { closeSync, openSync, readdirSync, readSync, realpathSync, statSync } from 'node:fs';
|
|
12
|
+
import { closeSync, openSync, readdirSync, readSync, realpathSync, rmSync, statSync } from 'node:fs';
|
|
13
13
|
import path from 'node:path';
|
|
14
14
|
import { codeHostDir, writePrivateJson } from './settings.js';
|
|
15
15
|
import { isValidId } from './tmux.js';
|
|
@@ -35,6 +35,21 @@ export function markReady(terminalId, env = process.env) {
|
|
|
35
35
|
return false;
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Forget the mark: the window's Claude Code was replaced (a restart), and the new one is past its
|
|
40
|
+
* dialog only once its own first hook runs. Returns whether no mark is left.
|
|
41
|
+
*/
|
|
42
|
+
export function clearReady(terminalId, env = process.env) {
|
|
43
|
+
if (!isValidId(terminalId))
|
|
44
|
+
return false;
|
|
45
|
+
try {
|
|
46
|
+
rmSync(readyPath(terminalId, env), { force: true });
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
38
53
|
/** How much of a transcript is read looking for its folder (its first entries carry `cwd`). */
|
|
39
54
|
const TRANSCRIPT_HEAD_BYTES = 256 * 1024;
|
|
40
55
|
/**
|
|
@@ -48,22 +63,39 @@ const TRANSCRIPT_HEAD_BYTES = 256 * 1024;
|
|
|
48
63
|
* owns, that this window's Claude Code got past its dialog. Used only for a window an older build
|
|
49
64
|
* started, which never had a ready mark.
|
|
50
65
|
*/
|
|
51
|
-
|
|
52
|
-
|
|
66
|
+
/** Claude Code's transcripts dir for `cwd` under `configDir` (`projects/<dash-encoded real path>`), or `undefined`. */
|
|
67
|
+
function projectDir(cwd, configDir) {
|
|
53
68
|
try {
|
|
54
|
-
real = realpathSync(cwd);
|
|
69
|
+
const real = realpathSync(cwd);
|
|
70
|
+
return { real, dir: path.join(configDir, 'projects', real.replace(/[^a-zA-Z0-9]/g, '-')) };
|
|
55
71
|
}
|
|
56
72
|
catch {
|
|
57
|
-
return
|
|
73
|
+
return undefined;
|
|
58
74
|
}
|
|
59
|
-
|
|
60
|
-
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The transcript files (`<session>.jsonl`) Claude Code keeps for `cwd` under `configDir`: one exists
|
|
78
|
+
* for a session only once its first prompt ran. Empty when there are none or they can't be read.
|
|
79
|
+
*/
|
|
80
|
+
export function claudeTranscripts(cwd, configDir) {
|
|
81
|
+
const project = projectDir(cwd, configDir);
|
|
82
|
+
if (!project)
|
|
83
|
+
return [];
|
|
61
84
|
try {
|
|
62
|
-
|
|
85
|
+
return readdirSync(project.dir).filter((name) => name.endsWith('.jsonl'));
|
|
63
86
|
}
|
|
64
87
|
catch {
|
|
65
|
-
return
|
|
88
|
+
return [];
|
|
66
89
|
}
|
|
90
|
+
}
|
|
91
|
+
export function claudeSessionSince(cwd, configDir, sinceMs) {
|
|
92
|
+
const project = projectDir(cwd, configDir);
|
|
93
|
+
if (!project)
|
|
94
|
+
return false;
|
|
95
|
+
const { real, dir } = project;
|
|
96
|
+
const names = claudeTranscripts(cwd, configDir);
|
|
97
|
+
if (names.length === 0)
|
|
98
|
+
return false;
|
|
67
99
|
return names.some((name) => {
|
|
68
100
|
const file = path.join(dir, name);
|
|
69
101
|
let fd;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which agent session a terminal's window runs, and on which login: Claude Code's `session_id`
|
|
3
|
+
* (every hook's input) or Codex's `thread-id` (its `notify` payload), with the window's
|
|
4
|
+
* `CLAUDE_CONFIG_DIR` / `CODEX_HOME` (`null`: the agent's default login), written by the window's own
|
|
5
|
+
* hooks to `<state>/<terminal>.session`. A restart (`CodeTerminals.restart`) resumes exactly that
|
|
6
|
+
* session on that login: a folder thread runs in place in a folder of the owner's, where other
|
|
7
|
+
* sessions of the same agent may be newer, so "the folder's latest" is not this terminal's.
|
|
8
|
+
*/
|
|
9
|
+
/** What a recorded id may be: a UUID or a similar token, never anything a shell would read. */
|
|
10
|
+
export declare const SESSION_ID_PATTERN: RegExp;
|
|
11
|
+
export interface RecordedSession {
|
|
12
|
+
id: string;
|
|
13
|
+
/** The login's config dir the session lives under; `null` for the agent's default. */
|
|
14
|
+
login: string | null;
|
|
15
|
+
}
|
|
16
|
+
export declare function sessionPath(terminalId: string, env?: NodeJS.ProcessEnv): string;
|
|
17
|
+
/** The login dir an agent's window runs on, from its environment (`null`: the default login). */
|
|
18
|
+
export declare function loginOf(agent: string, env: NodeJS.ProcessEnv): string | null;
|
|
19
|
+
/** The session the terminal's agent last reported, or `undefined`. */
|
|
20
|
+
export declare function readSession(terminalId: string, env?: NodeJS.ProcessEnv): RecordedSession | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* Record `sessionId` for the terminal, on the login its hook's environment names (best effort: a hook
|
|
23
|
+
* never fails for it). `env` is the hook's own environment, which its window gave it.
|
|
24
|
+
*/
|
|
25
|
+
export declare function recordSession(terminalId: string | undefined, agent: string, sessionId: unknown, env?: NodeJS.ProcessEnv): void;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which agent session a terminal's window runs, and on which login: Claude Code's `session_id`
|
|
3
|
+
* (every hook's input) or Codex's `thread-id` (its `notify` payload), with the window's
|
|
4
|
+
* `CLAUDE_CONFIG_DIR` / `CODEX_HOME` (`null`: the agent's default login), written by the window's own
|
|
5
|
+
* hooks to `<state>/<terminal>.session`. A restart (`CodeTerminals.restart`) resumes exactly that
|
|
6
|
+
* session on that login: a folder thread runs in place in a folder of the owner's, where other
|
|
7
|
+
* sessions of the same agent may be newer, so "the folder's latest" is not this terminal's.
|
|
8
|
+
*/
|
|
9
|
+
import { readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { codeHostDir, ensurePrivateDir } from './settings.js';
|
|
12
|
+
import { isValidId } from './tmux.js';
|
|
13
|
+
/** What a recorded id may be: a UUID or a similar token, never anything a shell would read. */
|
|
14
|
+
export const SESSION_ID_PATTERN = /^[A-Za-z0-9_-]{8,128}$/;
|
|
15
|
+
export function sessionPath(terminalId, env = process.env) {
|
|
16
|
+
if (!isValidId(terminalId))
|
|
17
|
+
throw new Error('invalid terminal id');
|
|
18
|
+
return path.join(codeHostDir(env), 'state', `${terminalId}.session`);
|
|
19
|
+
}
|
|
20
|
+
/** The login dir an agent's window runs on, from its environment (`null`: the default login). */
|
|
21
|
+
export function loginOf(agent, env) {
|
|
22
|
+
const value = agent === 'codex' ? env.CODEX_HOME : env.CLAUDE_CONFIG_DIR;
|
|
23
|
+
return value?.trim() ? path.resolve(value.trim()) : null;
|
|
24
|
+
}
|
|
25
|
+
/** The session the terminal's agent last reported, or `undefined`. */
|
|
26
|
+
export function readSession(terminalId, env = process.env) {
|
|
27
|
+
if (!isValidId(terminalId))
|
|
28
|
+
return undefined;
|
|
29
|
+
try {
|
|
30
|
+
const raw = JSON.parse(readFileSync(sessionPath(terminalId, env), 'utf8'));
|
|
31
|
+
if (typeof raw.id !== 'string' || !SESSION_ID_PATTERN.test(raw.id))
|
|
32
|
+
return undefined;
|
|
33
|
+
return { id: raw.id, login: typeof raw.login === 'string' && raw.login ? raw.login : null };
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Record `sessionId` for the terminal, on the login its hook's environment names (best effort: a hook
|
|
41
|
+
* never fails for it). `env` is the hook's own environment, which its window gave it.
|
|
42
|
+
*/
|
|
43
|
+
export function recordSession(terminalId, agent, sessionId, env = process.env) {
|
|
44
|
+
if (!isValidId(terminalId) || typeof sessionId !== 'string' || !SESSION_ID_PATTERN.test(sessionId))
|
|
45
|
+
return;
|
|
46
|
+
const next = { id: sessionId, login: loginOf(agent, env) };
|
|
47
|
+
try {
|
|
48
|
+
const current = readSession(terminalId, env);
|
|
49
|
+
if (current?.id === next.id && current.login === next.login)
|
|
50
|
+
return;
|
|
51
|
+
const file = sessionPath(terminalId, env);
|
|
52
|
+
ensurePrivateDir(path.dirname(file));
|
|
53
|
+
// A temp file and a rename: a restart never reads half a record.
|
|
54
|
+
const temp = `${file}.${process.pid}.tmp`;
|
|
55
|
+
writeFileSync(temp, JSON.stringify(next), { mode: 0o600 });
|
|
56
|
+
renameSync(temp, file);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
/* the restart then says it can't tell which session */
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -33,6 +33,8 @@ export type CodeAgent = (typeof CODE_AGENTS)[number];
|
|
|
33
33
|
export declare const GOAL_ENV = "PROHOST_CODE_THREAD_GOAL";
|
|
34
34
|
/** Window option set when the agent exits and its window becomes a shell. */
|
|
35
35
|
export declare const EXITED_OPTION = "@prohost_exited";
|
|
36
|
+
/** Window option a relaunched pane stamps with its relaunch's token (`CodeTerminals.restart`). */
|
|
37
|
+
export declare const RELAUNCH_OPTION = "@prohost_relaunch";
|
|
36
38
|
/**
|
|
37
39
|
* Window option that marks a relayed reply as possibly sitting in the agent's
|
|
38
40
|
* input unsubmitted. It is set before the reply's first keystroke reaches the
|
|
@@ -101,7 +103,7 @@ export interface TypingOutcome {
|
|
|
101
103
|
* `shell_window`: the thread's window is a shell (a shell thread, or an agent that exited into
|
|
102
104
|
* one), which composer text is never typed into: it would run as a command.
|
|
103
105
|
*/
|
|
104
|
-
refused?: 'remote_terminal_disabled' | 'reply_interrupted' | 'terminal_gone' | 'trust_prompt' | 'shell_window';
|
|
106
|
+
refused?: 'remote_terminal_disabled' | 'reply_interrupted' | 'terminal_gone' | 'trust_prompt' | 'shell_window' | 'session_unknown' | 'login_changed' | 'login_unavailable' | 'restart_expired';
|
|
105
107
|
/**
|
|
106
108
|
* Nothing reached the pane, and the failure was local and may pass (a tmux
|
|
107
109
|
* command failed): the delivery is worth sending again, not handing back.
|
|
@@ -113,6 +115,53 @@ export interface InputRequest {
|
|
|
113
115
|
terminalId: string;
|
|
114
116
|
text: string;
|
|
115
117
|
}
|
|
118
|
+
/** What a restart that found no conversation to resume says on its thread. */
|
|
119
|
+
export declare function freshConversationNote(agent: CodeAgent): string;
|
|
120
|
+
/** What a restart's caller ties to it (`CodeHost.handleRestart`: its delivery ledger). */
|
|
121
|
+
export interface RestartHooks {
|
|
122
|
+
/**
|
|
123
|
+
* Called right before the relaunch, past every refusal; `false`: an earlier attempt has this one;
|
|
124
|
+
* `'unsaved'`: the claim could not be recorded, so nothing is touched and it is retried later.
|
|
125
|
+
*/
|
|
126
|
+
claim?: () => boolean | 'unsaved';
|
|
127
|
+
/** Called once the window is seen running the new process. */
|
|
128
|
+
confirmed?: () => void;
|
|
129
|
+
}
|
|
130
|
+
/** What a restart says back beyond {@link TypingOutcome}. */
|
|
131
|
+
export interface RestartOutcome extends Omit<TypingOutcome, 'refused'> {
|
|
132
|
+
refused?: NonNullable<TypingOutcome['refused']> | 'restart_unconfirmed' | 'agent_exited';
|
|
133
|
+
/** Its delivery was claimed by an earlier attempt: answered as that one was. */
|
|
134
|
+
repeat?: boolean;
|
|
135
|
+
/** Claimed, then nothing was replaced (a clean tmux failure): the claim goes, so it runs when sent again. */
|
|
136
|
+
release?: boolean;
|
|
137
|
+
}
|
|
138
|
+
/** Whether a pane's current command is a shell (a login shell's `-zsh` too). */
|
|
139
|
+
export declare function isShellCommand(command: string): boolean;
|
|
140
|
+
/**
|
|
141
|
+
* How long a relaunched agent is watched for quitting at once before its restart is confirmed: a
|
|
142
|
+
* missing binary or a failed resume exits within a second or two.
|
|
143
|
+
*/
|
|
144
|
+
export declare const RESTART_SETTLE_POLLS = 6;
|
|
145
|
+
/** How long a relaunched pane may take to stamp its token (wall clock, from the relaunch). */
|
|
146
|
+
export declare const RESTART_STAMP_WAIT_MS = 10000;
|
|
147
|
+
export declare const RESTART_SETTLE_POLL_MS = 500;
|
|
148
|
+
/** How many times a restart's `stop` is posted, and the first pause between tries (doubling). */
|
|
149
|
+
export declare const RESTART_STOP_ATTEMPTS = 3;
|
|
150
|
+
export declare const RESTART_STOP_BACKOFF_MS = 500;
|
|
151
|
+
/** Then, in the background, this many more rounds, the n-th after n × the pause. */
|
|
152
|
+
export declare const RESTART_STOP_LATER_ATTEMPTS = 4;
|
|
153
|
+
export declare const RESTART_STOP_LATER_PAUSE_MS = 15000;
|
|
154
|
+
/** How old a restart may be when it reaches this Mac (the server's `RESTART_MAX_AGE`). */
|
|
155
|
+
export declare const RESTART_MAX_AGE_MS: number;
|
|
156
|
+
/** `code_thread.restart_requested`: relaunch the thread's agent, continuing its conversation. */
|
|
157
|
+
export interface RestartRequest {
|
|
158
|
+
codeThreadId: string;
|
|
159
|
+
terminalId: string;
|
|
160
|
+
/** When the owner asked (server clock): refused past {@link RESTART_MAX_AGE_MS}. Absent: never stale. */
|
|
161
|
+
requestedAtMs?: number;
|
|
162
|
+
/** Relaunched without its permission prompts (only an explicit `true`; see {@link agentCommandLine}). */
|
|
163
|
+
skipPermissions: boolean;
|
|
164
|
+
}
|
|
116
165
|
export interface ApprovalRequest {
|
|
117
166
|
codeThreadId: string;
|
|
118
167
|
terminalId: string;
|
|
@@ -190,6 +239,13 @@ export declare const CLAUDE_SKIP_PERMISSIONS_FLAG = "--dangerously-skip-permissi
|
|
|
190
239
|
* `-a never` alone would keep the sandbox, where a command needing the network fails instead of asking.
|
|
191
240
|
*/
|
|
192
241
|
export declare const CODEX_SKIP_PERMISSIONS_FLAG = "--dangerously-bypass-approvals-and-sandbox";
|
|
242
|
+
/** Claude Code's (`claude --help`, 2.1.318): "Continue the most recent conversation in the current directory". */
|
|
243
|
+
export declare const CLAUDE_CONTINUE_FLAG = "--continue";
|
|
244
|
+
/**
|
|
245
|
+
* Codex's (`codex resume --help`, 0.146): the most recent session without the picker, filtered to
|
|
246
|
+
* the current folder (`--all` would lift that). Its own options (`-c`, the skip flag) follow it.
|
|
247
|
+
*/
|
|
248
|
+
export declare const CODEX_RESUME_ARGS = "resume --last";
|
|
193
249
|
/** The shell command line a thread's agent window runs. */
|
|
194
250
|
export declare function agentCommandLine(agent: CodeAgent, options: {
|
|
195
251
|
claudeSettings: string;
|
|
@@ -205,6 +261,13 @@ export declare function agentCommandLine(agent: CodeAgent, options: {
|
|
|
205
261
|
hasGoal: boolean;
|
|
206
262
|
/** Start the agent without its permission prompts (a shell has none). */
|
|
207
263
|
skipPermissions?: boolean;
|
|
264
|
+
/**
|
|
265
|
+
* Continue a conversation instead of starting one (a restart: {@link CodeTerminals.restart}):
|
|
266
|
+
* a session id resumes exactly it (Claude Code `--resume <id>`, Codex `resume <id>`); `'latest'`
|
|
267
|
+
* the window folder's most recent one (`--continue`, `resume --last`), only for a folder that
|
|
268
|
+
* is the thread's own. Never with a goal.
|
|
269
|
+
*/
|
|
270
|
+
resume?: string;
|
|
208
271
|
}): string;
|
|
209
272
|
export interface CodeTerminalsOptions {
|
|
210
273
|
/** `$PROHOST_HOME` for this host; exported to every window. */
|
|
@@ -296,6 +359,10 @@ export declare class CodeTerminals {
|
|
|
296
359
|
/** Per terminal: the tail of its typing queue (see {@link serialize}). */
|
|
297
360
|
private readonly typing;
|
|
298
361
|
private openPtys;
|
|
362
|
+
/** The last `adopt` look timed out (`TmuxResult.uncertain`): records were kept, not re-read. */
|
|
363
|
+
private lastListUncertain;
|
|
364
|
+
/** Each agent window's pane process at the last `adopt`: a restart is confirmed by its changing. */
|
|
365
|
+
private panePids;
|
|
299
366
|
/** Who sizes each window, and what its viewers are told (`sizing.ts`). */
|
|
300
367
|
readonly sizing: TerminalSizing;
|
|
301
368
|
private readonly log;
|
|
@@ -378,6 +445,57 @@ export declare class CodeTerminals {
|
|
|
378
445
|
* reply types: the ready mark does.
|
|
379
446
|
*/
|
|
380
447
|
private watchTrustPrompt;
|
|
448
|
+
/**
|
|
449
|
+
* Relaunch a thread's Claude Code or Codex in its own window and folder, continuing its
|
|
450
|
+
* conversation (`code_thread.restart_requested`): the process in the agent window is replaced
|
|
451
|
+
* (`respawn-pane -k`), so the window, its viewers and its size stay. Run with
|
|
452
|
+
* {@link RestartRequest.skipPermissions}, it no longer asks before running commands, as a start
|
|
453
|
+
* that skips prompts (the same flags and `--settings` documents). Whatever the old one was doing
|
|
454
|
+
* stops: the person asked for that, from the thread they own.
|
|
455
|
+
*
|
|
456
|
+
* It resumes the session the window's hooks recorded (`session.ts`); a worktree of the thread's
|
|
457
|
+
* own may fall back to its latest session.
|
|
458
|
+
*
|
|
459
|
+
* Refused (nothing is touched, the server says so on the thread) with remote control off
|
|
460
|
+
* (`remote_terminal_disabled`: as for a start, nothing from ProhostAI starts an agent that runs
|
|
461
|
+
* commands unasked then), for a thread whose window is gone (`terminal_gone`), for a shell
|
|
462
|
+
* thread (`shell_window`), or while its session isn't known in a folder it shares
|
|
463
|
+
* (`session_unknown`). An agent that exited into its shell is relaunched all the same.
|
|
464
|
+
* Serialized with the thread's replies, so it never lands between a paste and its Enter.
|
|
465
|
+
*/
|
|
466
|
+
restart(request: RestartRequest, outcome?: RestartOutcome, hooks?: RestartHooks): Promise<boolean>;
|
|
467
|
+
/** Once every typing job and restart already queued for the terminal has finished (never rejects). */
|
|
468
|
+
settled(terminalId: string): Promise<void>;
|
|
469
|
+
/** The agent window's pane process as tmux says now; `undefined` when it didn't answer or has none. */
|
|
470
|
+
private panePidNow;
|
|
471
|
+
/** Whether the thread's folder is a worktree this host made for threads (never one of the owner's own). */
|
|
472
|
+
private ownsFolder;
|
|
473
|
+
private restartNow;
|
|
474
|
+
/**
|
|
475
|
+
* Whether a just-relaunched agent quits within a moment. First waits (until {@link RESTART_STAMP_WAIT_MS}
|
|
476
|
+
* have passed) for the window to carry this relaunch's `token` ({@link RELAUNCH_OPTION}, stamped by the new
|
|
477
|
+
* process's own pane after it lifted the old exited mark); only then is {@link EXITED_OPTION} read as
|
|
478
|
+
* the new agent's, over {@link RESTART_SETTLE_POLLS} more looks from that moment. `exited` only when that mark and the pane running a shell (`#{pane_current_command}`)
|
|
479
|
+
* agree; `running` only when the token was seen and the last look was answered without the mark;
|
|
480
|
+
* anything else (never seen, a look unanswered, the signals disagreeing) is `unclear`.
|
|
481
|
+
*/
|
|
482
|
+
private exitsAtOnce;
|
|
483
|
+
/**
|
|
484
|
+
* The restart's `stop` (the person's turn), tried {@link RESTART_STOP_ATTEMPTS} times with a
|
|
485
|
+
* growing pause: whether ProhostAI took it (2xx). A restart is acknowledged done only after that.
|
|
486
|
+
*/
|
|
487
|
+
private postRestartStop;
|
|
488
|
+
/** The same `stop`, sent again a few times over a few minutes, in the background; never throws. */
|
|
489
|
+
private postRestartStopLater;
|
|
490
|
+
/**
|
|
491
|
+
* What a restart resumes (`agentCommandLine`'s `resume`): the recorded session when its
|
|
492
|
+
* conversation exists; `'latest'` in a worktree of the thread's own; `undefined` (a fresh start)
|
|
493
|
+
* when there is no conversation yet; `null` when it can't be told (a folder of the owner's, its
|
|
494
|
+
* session never reported). Claude Code writes a session's transcript only once its first prompt
|
|
495
|
+
* runs (`ready.ts`), so a session id from `SessionStart` alone has nothing to resume. Codex reports
|
|
496
|
+
* its id only after a turn (`notify`), so a recorded one has.
|
|
497
|
+
*/
|
|
498
|
+
private resumeTarget;
|
|
381
499
|
private known;
|
|
382
500
|
/**
|
|
383
501
|
* Whether the thread's agent window runs an agent that composer text and
|