@prohost/cli 0.13.3 → 0.14.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.
Files changed (59) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/README.md +129 -0
  3. package/dist/agent/command.d.ts +8 -0
  4. package/dist/agent/command.js +40 -7
  5. package/dist/agent/daemon.d.ts +3 -1
  6. package/dist/agent/daemon.js +1 -2
  7. package/dist/agent/run.d.ts +30 -1
  8. package/dist/agent/run.js +221 -9
  9. package/dist/agent/runtime_status.d.ts +99 -3
  10. package/dist/agent/runtime_status.js +173 -5
  11. package/dist/agent/worktrees.d.ts +2 -0
  12. package/dist/agent/worktrees.js +3 -2
  13. package/dist/codehost/activity.d.ts +64 -0
  14. package/dist/codehost/activity.js +135 -0
  15. package/dist/codehost/agents.d.ts +31 -0
  16. package/dist/codehost/agents.js +57 -0
  17. package/dist/codehost/checkouts.d.ts +130 -0
  18. package/dist/codehost/checkouts.js +270 -0
  19. package/dist/codehost/claude_trust.d.ts +40 -0
  20. package/dist/codehost/claude_trust.js +129 -0
  21. package/dist/codehost/command.d.ts +25 -0
  22. package/dist/codehost/command.js +136 -0
  23. package/dist/codehost/decision.d.ts +156 -0
  24. package/dist/codehost/decision.js +553 -0
  25. package/dist/codehost/events.d.ts +49 -0
  26. package/dist/codehost/events.js +87 -0
  27. package/dist/codehost/folders.d.ts +122 -0
  28. package/dist/codehost/folders.js +317 -0
  29. package/dist/codehost/hooks.d.ts +178 -0
  30. package/dist/codehost/hooks.js +726 -0
  31. package/dist/codehost/host.d.ts +290 -0
  32. package/dist/codehost/host.js +625 -0
  33. package/dist/codehost/host_stats.d.ts +71 -0
  34. package/dist/codehost/host_stats.js +166 -0
  35. package/dist/codehost/invocations.d.ts +12 -0
  36. package/dist/codehost/invocations.js +82 -0
  37. package/dist/codehost/ledger.d.ts +35 -0
  38. package/dist/codehost/ledger.js +91 -0
  39. package/dist/codehost/local_server.d.ts +106 -0
  40. package/dist/codehost/local_server.js +339 -0
  41. package/dist/codehost/pty.d.ts +38 -0
  42. package/dist/codehost/pty.js +47 -0
  43. package/dist/codehost/ready.d.ts +31 -0
  44. package/dist/codehost/ready.js +109 -0
  45. package/dist/codehost/relay.d.ts +170 -0
  46. package/dist/codehost/relay.js +548 -0
  47. package/dist/codehost/settings.d.ts +52 -0
  48. package/dist/codehost/settings.js +114 -0
  49. package/dist/codehost/sizing.d.ts +163 -0
  50. package/dist/codehost/sizing.js +394 -0
  51. package/dist/codehost/terminals.d.ts +390 -0
  52. package/dist/codehost/terminals.js +965 -0
  53. package/dist/codehost/tmux.d.ts +196 -0
  54. package/dist/codehost/tmux.js +391 -0
  55. package/dist/index.d.ts +5 -0
  56. package/dist/index.js +21 -0
  57. package/dist/version.d.ts +2 -2
  58. package/dist/version.js +1 -1
  59. package/package.json +4 -1
@@ -0,0 +1,290 @@
1
+ /**
2
+ * The Code-thread host inside `prohost agent run`: one object the harness
3
+ * starts when this machine's owner has turned hosting on
4
+ * (`prohost agent code-host enable`), and hands the three events it serves —
5
+ * `code_thread.start_requested`, `.input_requested` and `.approval_decided`.
6
+ *
7
+ * Off — the default — the harness never builds one: no tmux, no relay socket,
8
+ * no local server, no host stats, and the events are acknowledged as ignored.
9
+ */
10
+ import type { CodeThreadEventSink } from './events.js';
11
+ import type { ReportedFolder } from './folders.js';
12
+ import type { HostStats } from './host_stats.js';
13
+ import type { PtySpawn } from './pty.js';
14
+ import type { PairingIdentity } from './relay.js';
15
+ import type { CodeHostSettings } from './settings.js';
16
+ import { CodeTerminals } from './terminals.js';
17
+ import type { AccountEnvResult, ApprovalRequest, CodeAgent, InputRequest, StartRequest, TmuxRunner } from './terminals.js';
18
+ export declare const EVENT_CODE_THREAD_START_REQUESTED = "code_thread.start_requested";
19
+ export declare const EVENT_CODE_THREAD_INPUT_REQUESTED = "code_thread.input_requested";
20
+ export declare const EVENT_CODE_THREAD_APPROVAL_DECIDED = "code_thread.approval_decided";
21
+ /**
22
+ * Declared in `runtime_status.capabilities` while this harness hosts threads.
23
+ * Versioned with the contract the server relies on (`remote_terminal`, 409
24
+ * refusals; a prompt's own `hook_decision` capability says how its decision is
25
+ * answered): an older CLI's `code_threads` is not listed.
26
+ */
27
+ export declare const CODE_THREADS_CAPABILITY = "code_threads_v2";
28
+ /**
29
+ * Declared beside {@link CODE_THREADS_CAPABILITY} while the owner allows remote
30
+ * terminals. It rides the `/ws/webhooks` socket into the pairing's row in
31
+ * Postgres, where the backend API reads it (hosts listing, shell create,
32
+ * terminal-token) whatever fleet and Redis it runs against.
33
+ */
34
+ export declare const REMOTE_TERMINAL_CAPABILITY = "remote_terminal";
35
+ /**
36
+ * Declared with the instance id appended (`host_instance_<32 hex>`) beside
37
+ * {@link CODE_THREADS_CAPABILITY}: the server binds each thread to the
38
+ * instance that accepted its start and routes its frames only there.
39
+ */
40
+ export declare const HOST_INSTANCE_CAPABILITY_PREFIX = "host_instance_";
41
+ /**
42
+ * Declared beside {@link CODE_THREADS_CAPABILITY} by a host that starts threads
43
+ * in a folder (`folder` / `separateCopy` in `code_thread.start_requested`, the
44
+ * opened-folders list, `code_host_folders` on the status frame). The server
45
+ * sends a folder start only to a socket that declared it.
46
+ */
47
+ export declare const CODE_THREAD_FOLDERS_CAPABILITY = "code_thread_folders";
48
+ /** `code_thread.start_requested` data (camelCase per the contract), or `null`. */
49
+ export declare function parseStartRequest(data: Record<string, unknown>): StartRequest | null;
50
+ /** `code_thread.input_requested` data, or `null`. */
51
+ export declare function parseInputRequest(data: Record<string, unknown>): InputRequest | null;
52
+ /** What the harness needs from a host; tests fake it. */
53
+ export interface CodeHostLike {
54
+ start(): Promise<void>;
55
+ stop(): Promise<void>;
56
+ /** Whether this Mac takes the event now (the owner's switches); a refusal is acked 204. */
57
+ admits(event: string, data: Record<string, unknown>): boolean;
58
+ /** Why `event` is refused when it is only the owner's remote switch: the server hands it back. */
59
+ refusal(event: string): RefusedDeliveryError['reason'] | undefined;
60
+ handleStart(data: Record<string, unknown>): Promise<boolean>;
61
+ /** `deliveryId`: a redelivery of one already acted on is acknowledged without typing again. */
62
+ handleInput(data: Record<string, unknown>, deliveryId?: string): Promise<boolean>;
63
+ handleApproval(data: Record<string, unknown>, deliveryId?: string): Promise<boolean>;
64
+ latestStats(): HostStats | undefined;
65
+ /** The owner's remote-terminal switch as it is on disk now. */
66
+ remoteTerminalAllowed(): boolean;
67
+ /** This home's instance capability (`host_instance_<id>`). */
68
+ instanceCapability(): string;
69
+ /**
70
+ * The repositories this Mac offers Code threads (`--repos`, by `origin`), once
71
+ * known; `undefined` until then. Declared on every status frame.
72
+ */
73
+ offeredRepos?(): string[] | undefined;
74
+ /**
75
+ * The folders this Mac has opened (`folders.ts`) and its home directory, for the status frame
76
+ * (`code_host_folders`, `code_host_home`). Read for every frame.
77
+ */
78
+ openedFolders?(): {
79
+ folders: ReportedFolder[];
80
+ home: string;
81
+ } | undefined;
82
+ /** Whether this host may act for ProhostAI now (the relay hasn't refused the pairing). */
83
+ authorized(): boolean;
84
+ /**
85
+ * `code_thread_agent_<agent>` for each agent whose binary resolves on this daemon's `PATH`
86
+ * (`agents.ts`), re-checked on every status frame: what the server offers for this Mac.
87
+ */
88
+ agentCapabilities?(): string[];
89
+ }
90
+ /** `code_thread.approval_decided` data, or `null`. */
91
+ export declare function parseApprovalDecision(data: Record<string, unknown>): ApprovalRequest | null;
92
+ /** A start ran (or failed) but ProhostAI hasn't heard: redeliver, don't ack. */
93
+ /**
94
+ * A reply or decision this Mac refuses before typing anything, with the reason
95
+ * the server shows on the thread (the harness acks it 409 + `reason`, and the
96
+ * server hands the reply or decision back instead of counting it delivered).
97
+ */
98
+ export declare class RefusedDeliveryError extends Error {
99
+ readonly reason: 'remote_terminal_disabled' | 'reply_interrupted' | 'terminal_gone' | 'shell_window' | 'decision_unconfirmed' | 'trust_prompt' | 'prompt_local' | 'prompt_open';
100
+ constructor(reason: 'remote_terminal_disabled' | 'reply_interrupted' | 'terminal_gone' | 'shell_window' | 'decision_unconfirmed' | 'trust_prompt' | 'prompt_local' | 'prompt_open');
101
+ }
102
+ export declare class UnreportedOutcomeError extends Error {
103
+ constructor(codeThreadId: string);
104
+ }
105
+ export interface CodeHostOptions {
106
+ env?: NodeJS.ProcessEnv;
107
+ baseUrl: string;
108
+ apiKey: string;
109
+ machineId: string;
110
+ machine: string;
111
+ clientVersion: string;
112
+ /** The ProhostAI workspace this machine is paired with (`agent.json` `prohost_account.id`). */
113
+ accountId?: string;
114
+ repos: string[];
115
+ /** Absolute path of this CLI's entrypoint, for the hook shim. */
116
+ cliEntrypoint: string;
117
+ nodePath?: string;
118
+ log?: (line: string) => void;
119
+ slots?: () => {
120
+ busy: number;
121
+ total: number;
122
+ } | undefined;
123
+ /**
124
+ * What this host declares changed (the remote switch flipped, the relay
125
+ * authorized or refused the pairing, or the opened folders changed): the
126
+ * harness re-declares its capabilities now, and its status frame as soon as
127
+ * the server takes one, not in five minutes.
128
+ */
129
+ onRemoteChanged?: () => void;
130
+ /** The opened folders changed (`folders.json`): the status frame goes out as soon as the server takes one. */
131
+ onCodeHostChanged?: () => void;
132
+ /** The selected account's environment (see `CodeTerminalsOptions.accountEnv`). */
133
+ accountEnv?: (agent: CodeAgent) => AccountEnvResult;
134
+ tmux?: TmuxRunner;
135
+ spawnPty?: () => PtySpawn | undefined;
136
+ events?: CodeThreadEventSink;
137
+ relay?: boolean;
138
+ localServer?: boolean;
139
+ /** How often the owner's switches are re-read to close what they no longer allow. */
140
+ settingsPollMs?: number;
141
+ /** Seams for tests: whether a hook's process still runs, and the wait between looks. */
142
+ alive?: (pid: number) => boolean;
143
+ sleep?: (ms: number) => Promise<void>;
144
+ }
145
+ /**
146
+ * How long a reply waits for a hook to apply the decision it was handed (it prints it and exits
147
+ * within a poll, 100 ms) before it is sent again later instead of typed.
148
+ */
149
+ export declare const DECISION_APPLY_WAIT_MS = 2000;
150
+ /** A `remote off` ends open terminals within this long. */
151
+ export declare const SETTINGS_POLL_MS = 2000;
152
+ /** How often live terminals' worktree lanes are marked in use (pruning is after 14 idle days). */
153
+ export declare const LANE_REFRESH_MS = 3600000;
154
+ export declare class CodeHost implements CodeHostLike {
155
+ private readonly options;
156
+ readonly terminals: CodeTerminals;
157
+ private readonly env;
158
+ private readonly stats;
159
+ private readonly deliveries;
160
+ private relay;
161
+ private local;
162
+ private watcher;
163
+ private laneKeeper;
164
+ private remoteAllowed;
165
+ private recoveringPermissions;
166
+ private readonly events;
167
+ /** Set while the relay refuses this pairing (revoked key, inactive pairing). */
168
+ private pairingRefused;
169
+ /** Whose pairing this is, from the relay's last `auth_ok`; the desktop socket is bound to it. */
170
+ private identity;
171
+ private stopped;
172
+ private readonly instanceId;
173
+ private readonly log;
174
+ private readonly checkouts;
175
+ /** What {@link offeredRepos} answers, read at {@link start}. */
176
+ private offered;
177
+ /** `folders.json` as last seen by the settings poll: a change re-sends the status frame. */
178
+ private foldersSeen;
179
+ /** Hosting (`enabled`) as last seen on disk: `code-host disable` closes the desktop socket at once. */
180
+ private enabledSeen;
181
+ /** `code-host/activity.json`, for the desktop app's keep-awake (`activity.ts`). */
182
+ private readonly activity;
183
+ constructor(options: CodeHostOptions);
184
+ settings(): CodeHostSettings;
185
+ remoteTerminalAllowed(): boolean;
186
+ instanceCapability(): string;
187
+ /**
188
+ * Only the agents a start would accept: installed here, and runnable on this harness's login
189
+ * (`accountEnv`: a Claude Code harness hosts no Codex thread). A host-only daemon has no login of
190
+ * its own and runs whichever is installed.
191
+ */
192
+ agentCapabilities(): string[];
193
+ offeredRepos(): string[] | undefined;
194
+ openedFolders(): {
195
+ folders: ReportedFolder[];
196
+ home: string;
197
+ };
198
+ /** The latest host stats, for the heartbeat. Sampled by the relay loop and on demand. */
199
+ latestStats(): HostStats | undefined;
200
+ sampleStats(): Promise<HostStats>;
201
+ start(): Promise<void>;
202
+ /**
203
+ * Whether an agent here is mid-turn, written for the desktop app's keep-awake (`activity.ts`) —
204
+ * only while hosting is on; turned off, the file goes.
205
+ */
206
+ reportActivity(): void;
207
+ /** The daemon outlives timed-out/killed hooks and retries recovery after reconnect or restart. */
208
+ recoverCompletedPermissions(): Promise<void>;
209
+ /**
210
+ * The switches as the desktop socket must see them: no remote terminal while
211
+ * the relay refuses this pairing, whatever the file says — a revoked
212
+ * pairing closes every way in, not only the relay's.
213
+ */
214
+ private desktopSettings;
215
+ /**
216
+ * Whether this host may act for ProhostAI now: running, and the relay has
217
+ * not refused the pairing. A refusal holds until the next `auth_ok`, and
218
+ * gates everything the event socket asks for as well as every attach.
219
+ */
220
+ authorized(): boolean;
221
+ /** The relay authorized or refused this machine (see `CodeHostRelayOptions.onAuthorization`). */
222
+ onAuthorization(authorized: boolean, reason?: string, identity?: PairingIdentity): Promise<void>;
223
+ /**
224
+ * The relay went away. Whether this pairing is still authorized can't be
225
+ * known until it answers again, so the desktop socket closes now (its
226
+ * identity is forgotten) and reopens only on a fresh `auth_ok`. The event
227
+ * socket's work is not paused: that socket authenticates on its own, and
228
+ * an explicit refusal (`auth_error`) is what gates it.
229
+ */
230
+ onRelayDisconnected(): Promise<void>;
231
+ private closeLocal;
232
+ /**
233
+ * The desktop app's socket on 127.0.0.1, served whenever hosting is on — with
234
+ * the remote switch off as well: then it admits only attaches the relay
235
+ * vouches for as local (`local_server.ts`).
236
+ */
237
+ private startLocal;
238
+ /**
239
+ * The owner's switches, re-read: a terminal attached while remote access was
240
+ * allowed must not outlive it. Turning it off closes every relay viewer and
241
+ * every desktop viewer that was admitted as remote; the desktop socket stays
242
+ * up for local attaches. Turning hosting off (`code-host disable`) closes the
243
+ * desktop socket (and its `local.json`) too. The opened folders changing
244
+ * re-sends the status frame.
245
+ */
246
+ applySettings(): Promise<void>;
247
+ /** Every closer is attempted, whatever the one before it did; never rejects. */
248
+ stop(): Promise<void>;
249
+ /**
250
+ * Whether this Mac takes `event` at all, read from the owner's switches on
251
+ * disk now — never cached. Anything that types into a terminal from
252
+ * ProhostAI (a reply, an approval) or opens a raw shell is remote control of
253
+ * this machine, so it needs the remote-terminal switch as well as hosting.
254
+ * The harness answers a refusal 204, as for a machine that doesn't host.
255
+ *
256
+ * Hosting itself (`enabled`) is latched for the process: this host exists only
257
+ * because it was on at startup, and `code-host disable` takes effect when the
258
+ * agent restarts, as the command says. Never re-read here: the runtime keeps
259
+ * advertising `code_threads_v2` until then, so a start refused for it would be
260
+ * acked 204 and leave its thread `starting` for good.
261
+ */
262
+ admits(event: string, data: Record<string, unknown>): boolean;
263
+ refusal(event: string): RefusedDeliveryError['reason'] | undefined;
264
+ /**
265
+ * Throws {@link UnreportedOutcomeError} when the outcome never reached the
266
+ * server, so the harness asks for the delivery again rather than ack it.
267
+ */
268
+ handleStart(data: Record<string, unknown>): Promise<boolean>;
269
+ handleInput(data: Record<string, unknown>, deliveryId?: string): Promise<boolean>;
270
+ handleApproval(data: Record<string, unknown>, deliveryId?: string): Promise<boolean>;
271
+ /** The terminal's menu state (`menuState`) once a hook that is still deciding has had a moment to finish. */
272
+ private menuSettled;
273
+ /**
274
+ * A refusal the server hands back. Nothing typed (the switch): the claim is
275
+ * released, so a redelivery after a lost ack is judged afresh rather than
276
+ * answered "done". Possibly typed (`reply_interrupted`): the claim stays, so it
277
+ * is never typed twice, and a redelivery is refused the same way.
278
+ */
279
+ private refuse;
280
+ /**
281
+ * A local failure before anything reached the pane: the claim is released and
282
+ * the harness acks 503, so the outbox sends it again and it is typed once.
283
+ */
284
+ private retryLater;
285
+ /**
286
+ * A delivery already acted on: done, unless it ended in a refusal, which is said again
287
+ * (`decision_unconfirmed`: a decision an older CLI pressed keys for, which may have landed).
288
+ */
289
+ private repeat;
290
+ }