@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
package/CHANGELOG.md CHANGED
@@ -4,6 +4,81 @@ Versions follow [semver](https://semver.org/). Publishing is automated: merging
4
4
  a version bump to `main` triggers `.github/workflows/npm-publish-cli.yml`, which
5
5
  builds via `prepack`, runs the suite, publishes, and tags `cli-v<version>`.
6
6
 
7
+ ## Unreleased
8
+
9
+ ## 0.14.0
10
+
11
+ - **Code threads on this Mac: installed agents, honest shells, one prompt, who sizes.**
12
+ The host declares `code_thread_agent_claude_code` / `code_thread_agent_codex` for each
13
+ agent whose binary resolves on the daemon's `PATH` (re-checked per status frame), so a
14
+ `--host-only` Mac offers Claude Code and Codex, not only Shell; a start for one that is
15
+ missing fails saying which binary. A reply into a shell window is refused as the new
16
+ `shell_window` (was `terminal_gone`). New windows start at the size they'll be viewed at
17
+ (`resize-window` right after `new-window`), and a shell's first prompt waits for its first viewer's size to settle
18
+ (`tmux wait-for`), so it shows one prompt instead of one per resize. Each window is
19
+ sized by `resize-window` (so `window-size manual` per window; tmux 3.4 exits at start with
20
+ it in a config) to one viewer: whoever attaches, types, focuses or sends
21
+ `{"type":"claim"}` (never a watch viewer), applied once quiet 300 ms and never for a size it
22
+ already has, since Claude Code reprints its transcript on every width change. A viewer that
23
+ sent `{"type":"hello","client":{"kind","label"}}` is told who that is and whether the pane is
24
+ in copy mode (`{"type":"sizer",...,"scrolled"}`). Copy mode is the pane's, so it is left when
25
+ the sizing viewer changes and when the viewer that scrolled detaches. `scroll-on-clear off`:
26
+ a full-screen redraw no longer leaves a copy of the old screen in scrollback (Claude Code's
27
+ header repeated once per resize). No copy-mode position box and no coloured border at a
28
+ window's edge; typed text and relayed replies leave copy mode first (q / Esc / Ctrl+C there
29
+ still only leave it).
30
+
31
+ - **`runtime_status` reports the machine's short hostname** (`hostname`, up to its first dot). The
32
+ server keeps it against the socket and on a run started while this is the pairing's only socket,
33
+ for ProhostAI staff only. With `--machine`, that name is sent as given (`build.eu` stays whole)
34
+ instead of the real hostname, and one the server can't use as a hostname (`Front desk`) means no machine is recorded. An older server
35
+ ignores it.
36
+ - **`Claude Ultra` plan label.** `get_usage`'s `subscription_type: "ultra"` now reads
37
+ `Claude Ultra`. The value is inferred from `pro` / `max`; no real response has confirmed it yet.
38
+ - **Host Code threads on this Mac (off by default).** `prohost agent
39
+ code-host enable` lets `agent run` take `code_thread.start_requested`,
40
+ `code_thread.input_requested` and `code_thread.approval_decided`: one worktree and one window in a private tmux
41
+ server per thread, running Claude Code, Codex or a shell, re-adopted after a
42
+ restart. Claude Code / Codex hooks are passed on their command lines (never
43
+ installed globally) and post the thread's prompts, turns, permission prompts
44
+ and interrupts with a narrow, expiring token. `prohost agent code-host remote
45
+ on` additionally lets the machine's owner attach and type from ProhostAI
46
+ through the relay (`/v1/code-hosts/*`), or from the desktop app over
47
+ loopback. While hosting, the heartbeat carries optional `host` stats. New
48
+ optional dependency: `node-pty` 1.1.0.
49
+ - **`agent run --host-only`.** Hosts Code threads with no `--exec`: every
50
+ `agent.run_requested` is closed at once as failed ("This computer only hosts
51
+ Code threads; it doesn't answer messages."), nothing is executed, and the
52
+ harness declares `host_only`. Mutually exclusive with `--exec`;
53
+ `install-daemon --host-only` passes it through; listed in `--help`.
54
+ - **`code-host/activity.json` for keep-awake.** While hosting, the daemon
55
+ writes `{"v":1,"busy":bool,"updatedAt":ISO}` (0600, atomic, at least every
56
+ 30 s; removed when hosting stops): busy while any hosted agent is mid-turn
57
+ (Claude Code `UserPromptSubmit` → `Stop`; a goal or typed reply → Codex's
58
+ turn-complete; Esc/Ctrl+C from an attached viewer ends it; 6 h cap per
59
+ turn). Readers treat `updatedAt` older than ~3 min as not busy.
60
+ - **Folder Code threads.** A `code_thread.start_requested` with `folder` runs
61
+ in that folder in place (no worktree, no branch), or with `separateCopy` in a
62
+ worktree and branch of the folder's git checkout. Only a folder this Mac has
63
+ opened, or one inside it (after realpath), starts; anything else is refused.
64
+ `prohost agent code-host folders add|list|remove` keeps the opened folders
65
+ (`code-host/folders.json`); the status frame reports them as
66
+ `code_host_folders` with `code_host_home`, and the host declares
67
+ `code_thread_folders`. A shell in an opened folder starts with `remote off`.
68
+ - **Folder threads, follow-ups.** A separate copy refuses a subfolder its
69
+ branch doesn't have, and one that resolves outside the new worktree (a
70
+ symlink on that branch), instead of starting in the worktree's root or in
71
+ the owner's files. Changes to `folders.json` (`folders add`/`remove`, a
72
+ start moving its folder to the front) take an inter-process lock, so two at
73
+ once never drop a folder. The tmux server and every pane no longer inherit
74
+ `ELECTRON_RUN_AS_NODE` / `PROHOST_CLI_RUN` from a daemon the desktop app runs
75
+ on Electron; the hook shim sets `ELECTRON_RUN_AS_NODE=1` for itself only.
76
+ - **The desktop app attaches locally with `remote off`.** The 127.0.0.1 socket
77
+ and `local.json` are served whenever hosting is on; with the remote switch
78
+ off they admit only a viewer token the relay vouches for as `local`, and
79
+ `remote off` ends every other desktop viewer. `code-host disable` closes the
80
+ socket at once.
81
+
7
82
  ## 0.13.3
8
83
 
9
84
  - **Every turn the agent answers in reaches the thread.** When a command the
package/README.md CHANGED
@@ -632,8 +632,137 @@ workspace's `.claude/`: a skill that points it at the repo's PR protocol
632
632
  the `gh pr create` gate hook the fleet uses. Nothing else about the default
633
633
  workspace changes.
634
634
 
635
+ ### Hosting Code threads on this Mac (off by default)
636
+
637
+ ProhostAI can show agent terminals — Claude Code, Codex or a plain shell — as
638
+ **Code** threads in the inbox. This machine can host them, but only once its
639
+ owner says so, here, on this machine. Nothing the server sends can turn it on.
640
+
641
+ ```bash
642
+ prohost agent code-host enable # host Code threads (needs --repos on agent run)
643
+ prohost agent code-host remote on # also let *you* attach and type from ProhostAI
644
+ prohost agent code-host status
645
+ prohost agent code-host remote off # refuses the next viewer at once
646
+ prohost agent code-host disable
647
+
648
+ prohost agent code-host folders add ~/code/app # open a folder for Code threads (the desktop app does this)
649
+ prohost agent code-host folders list [--json]
650
+ prohost agent code-host folders remove ~/code/app
651
+ ```
652
+
653
+ Restart `agent run` (or the daemon) after `enable` / `disable`.
654
+
655
+ **Host only: Code threads without an agent that answers.** `prohost agent run
656
+ --host-only` (or `install-daemon --host-only`) takes no `--exec`. It keeps
657
+ everything hosting needs — the connection, presence, status, the desktop
658
+ app's local socket, `activity.json` — and executes nothing anyone sends: every
659
+ run requested of this agent (a mention, a DM) is closed at once as failed with
660
+ "This computer only hosts Code threads; it doesn't answer messages." It
661
+ declares the `host_only` capability. `--host-only` and `--exec` can't be used
662
+ together, and `prohost --help` lists `--host-only` on builds that have it. A
663
+ host-only agent runs no agent of its own, so it has no account: its Code
664
+ threads run Claude Code or Codex on this Mac's default login, as a terminal you
665
+ opened would (`--account` is refused with `--host-only`, and an account already
666
+ on the pairing is reported as unused at start). The
667
+ desktop app's "This Mac" daemon runs this way.
668
+
669
+ - **Folder threads, in place.** A thread can also run in a folder instead of a
670
+ repository: in that folder itself, with no worktree and no branch, the way a
671
+ terminal you open there would. The desktop app's folder picker adds what you
672
+ pick to this Mac's **opened folders** (`folders add`;
673
+ `$PROHOST_HOME/code-host/folders.json`, `0600`, the 50 most recent, each
674
+ stored as its realpath). A thread started from anywhere else — your phone,
675
+ the web — may only run in one of those folders or a folder inside one,
676
+ checked after resolving symlinks, so a link that points out of an opened
677
+ folder does not count as inside it. Anything else is refused with "This
678
+ folder hasn't been opened on this Mac." A thread that starts moves its
679
+ folder to the front of the list. The list (with whether each is a git
680
+ repository, and your home directory for showing `~/…`) is reported to
681
+ ProhostAI so your phone can offer it; `--repos` is not needed for folders.
682
+ - **Work in a separate copy.** For a folder in a git repository, a thread can
683
+ instead get its own worktree and branch of that checkout, exactly as a
684
+ repository thread does, opened in the same subfolder. Your checkout is never
685
+ switched. A folder outside git can't, and says so; neither can a subfolder
686
+ the branch doesn't have (untracked or ignored) or one that is a link out of
687
+ the copy on that branch — the thread is refused, never started elsewhere.
688
+ - **The goal is optional.** A thread with no goal opens Claude Code or Codex
689
+ with no prompt, or just the shell.
690
+
691
+ - **One worktree and one tmux window per repository thread.** The repository is matched
692
+ against `--repos` by its `origin` remote (else by directory name), checked out
693
+ under `$PROHOST_HOME/worktrees/`, and switched to the thread's branch. The
694
+ windows live in a private tmux server (`tmux -L prohost-<id>`; your own tmux
695
+ is untouched), so they survive the agent restarting and are re-adopted. You
696
+ need `tmux` installed (`brew install tmux`).
697
+ - **Hooks without touching your config.** Claude Code is started with
698
+ `--settings` and Codex with `-c notify=…`, naming a shim that does nothing
699
+ unless `$PROHOST_TERMINAL_ID` is set — your own `claude` sessions never see
700
+ them, and `~/.claude` / `~/.codex` are never written. They report the prompt,
701
+ the end of each turn, permission prompts and Esc/Ctrl+C to the thread, using a
702
+ narrow token the relay issues (`$PROHOST_HOME/code-host/hook.json`, `0600`,
703
+ expires in a day) — never your API key, never a Claude or Codex credential.
704
+ ProhostAI saves the conversation, not your terminal. An Allow / Deny chosen in
705
+ ProhostAI answers the prompt in the terminal (Enter, Down+Enter, Escape) —
706
+ only if that prompt is still the one on screen.
707
+ - **Reply recovery after a permission menu.** New Claude Code windows check
708
+ the installed version before choosing their hooks. Claude Code 2.1.295 or
709
+ newer records tool invocation IDs before permission processing; replies can
710
+ resume when the transcript confirms those calls finished. Recording failures
711
+ block the request. Older or unverifiable versions, already-running windows,
712
+ and subagent permission prompts wait for a new user prompt or a completed
713
+ turn when the menu's outcome is uncertain. Start a new window after upgrading
714
+ to use the invocation hooks.
715
+ Keep the daemon running: it reports verified completion back to the thread,
716
+ including after the waiting hook exits, and retries reports after reconnect.
717
+ - **Replies from ProhostAI go to the agent, never to a shell.** Typing a reply
718
+ or an Allow / Deny into a terminal from ProhostAI needs `remote on`, read at
719
+ every event. It reaches only a running Claude Code or Codex: never a shell
720
+ thread, and never the shell an agent leaves behind when it exits. Starting a
721
+ shell thread for a repository also needs `remote on`; a shell in an opened
722
+ folder starts with it off, where only the desktop app on this Mac can type
723
+ into it. A reply or decision that is redelivered
724
+ after a reconnect is never typed twice. A goal is always passed after `--`,
725
+ so it can't become an option.
726
+ - **A remote terminal is remote code execution on this Mac.** With `remote on`,
727
+ the machine's owner (whoever paired it) — and nobody else — can attach from the
728
+ web, phone or desktop app and type. Off, the relay refuses every viewer and
729
+ this machine refuses every dial. Terminal bytes are relayed, never stored.
730
+ - **The desktop app on this Mac attaches over `127.0.0.1`**, not the relay,
731
+ whenever hosting is on — with `remote off` too. The socket's port and token
732
+ are in `$PROHOST_HOME/code-host/local.json` (`0600`, new every start; removed
733
+ by `disable` at once). Any request with an `Origin` header — every browser —
734
+ is refused, and every attach's viewer token is checked with the relay, which
735
+ vouches for who it is. With `remote off` it admits only a token ProhostAI
736
+ minted for this Mac's own desktop app, which the relay refuses for any
737
+ relayed attach; `remote off` ends every other viewer at once.
738
+ - **Whether an agent is working, for keep-awake.** While hosting, the daemon
739
+ keeps `$PROHOST_HOME/code-host/activity.json` (`0600`, written atomically):
740
+ `{"v":1,"busy":true|false,"updatedAt":"<ISO>"}`. `busy` is true while any
741
+ thread's agent is mid-turn: from Claude Code's prompt to its `Stop`, from a
742
+ goal or a reply (not a `/` command) this Mac types to Codex's turn-complete, ended early by Esc
743
+ or Ctrl+C in an attached terminal, and never for longer than 6 hours per
744
+ turn. The file is rewritten at least every 30 s and removed when hosting
745
+ stops. **A reader must treat an `updatedAt` older than about 3 minutes as
746
+ not busy**, so a daemon that died never keeps the Mac awake. The desktop
747
+ app's "Keep this Mac awake while an agent is working" reads it. A prompt
748
+ typed straight into a Codex terminal isn't seen until that turn ends.
749
+ - **Attaching needs `node-pty`**, an optional dependency with prebuilt macOS
750
+ binaries. Where it doesn't load, threads still run; only attaching answers
751
+ `pty_unavailable`.
752
+ - While hosting, the heartbeat also reports this machine's CPU, memory,
753
+ memory pressure, battery and model (`vm_stat`, `sysctl`, `pmset`) for the
754
+ thread's Machine card.
755
+
635
756
  ### The trust model — read this once
636
757
 
758
+ **`--host-only` is the exception: it runs nothing anyone sends.** No message,
759
+ mention or DM executes a command on a host-only machine; each requested run is
760
+ refused. What it does run are Code-thread terminals, and only as described
761
+ under "Hosting Code threads on this Mac": a thread starts only in a repository
762
+ from `--repos` or a folder this Mac has opened; nothing from ProhostAI types
763
+ into one without `remote on`; with `remote off` only the desktop app on this
764
+ Mac attaches; and only the pairing's owner can attach at all.
765
+
637
766
  `agent run` is a standing instruction to execute a command on your machine when
638
767
  someone in your ProhostAI workspace asks. Be clear-eyed about what that means:
639
768
 
@@ -57,4 +57,12 @@ export declare function parseConcurrency(raw: string | boolean | undefined): num
57
57
  export declare function daemonEnvironment(): Record<string, string>;
58
58
  /** Dispatch an ``agent`` subcommand. Returns the process exit code. */
59
59
  export declare function runAgent(subcommand: string | undefined, flags: Flags, printHelp: () => void, args?: string[]): Promise<number>;
60
+ /**
61
+ * `--exec <command>` or `--host-only`, exactly one: what this harness does with a run. `null`
62
+ * after explaining why not. Host-only runs nothing anyone sends; it only hosts Code threads.
63
+ */
64
+ export declare function execOrHostOnly(flags: Flags, verb: 'run' | 'install-daemon'): {
65
+ exec: string;
66
+ hostOnly: boolean;
67
+ } | null;
60
68
  export {};
@@ -20,6 +20,8 @@ import { CredentialsMissingError, ensureWorkspace, loadCredentials, prohostHome,
20
20
  import { DEFAULT_EXEC_TIMEOUT_MS, DEFAULT_IDLE_TIMEOUT_MS, runAgentHarness } from './run.js';
21
21
  import { MAX_CONCURRENCY } from './scheduler.js';
22
22
  import { ReposFlagError, isCheckout, parseReposFlag } from './worktrees.js';
23
+ import { codeHostCommand } from '../codehost/command.js';
24
+ import { loadSettings as loadCodeHostSettings } from '../codehost/settings.js';
23
25
  import { announceUpgradeIfAvailable } from '../upgrade.js';
24
26
  import { DEFAULT_WS_URL } from '../listen.js';
25
27
  function flagString(flags, name) {
@@ -223,10 +225,12 @@ export async function runAgent(subcommand, flags, printHelp, args = []) {
223
225
  return listCommand();
224
226
  if (subcommand === 'workspace')
225
227
  return workspaceCommand(args, flags);
228
+ if (subcommand === 'code-host')
229
+ return codeHostCommand(args, flags);
226
230
  process.stderr.write(subcommand
227
231
  ? `Unknown agent subcommand: ${subcommand}\n\n`
228
232
  : 'Missing agent subcommand — expected `pair`, `run`, `install-daemon`, `uninstall-daemon`, ' +
229
- '`account`, `list`, or `workspace`.\n\n');
233
+ '`account`, `list`, `workspace`, or `code-host`.\n\n');
230
234
  printHelp();
231
235
  return 1;
232
236
  }
@@ -256,12 +260,35 @@ function loadOrExplain(streamOverride) {
256
260
  }
257
261
  return credentials;
258
262
  }
259
- async function installDaemonCommand(flags) {
263
+ /**
264
+ * `--exec <command>` or `--host-only`, exactly one: what this harness does with a run. `null`
265
+ * after explaining why not. Host-only runs nothing anyone sends; it only hosts Code threads.
266
+ */
267
+ export function execOrHostOnly(flags, verb) {
260
268
  const exec = flagString(flags, 'exec');
269
+ const hostOnly = flags['host-only'] === true;
270
+ if (hostOnly && flags.exec !== undefined) {
271
+ process.stderr.write('--host-only and --exec can\'t be used together: a host-only agent runs no command.\n');
272
+ return null;
273
+ }
274
+ if (hostOnly && flags.account !== undefined) {
275
+ process.stderr.write("--host-only and --account can't be used together: a host-only agent runs no agent of its own, " +
276
+ "so its Code threads use this Mac's default Claude Code / Codex login.\n");
277
+ return null;
278
+ }
279
+ if (hostOnly)
280
+ return { exec: '', hostOnly: true };
261
281
  if (!exec) {
262
- process.stderr.write("Missing required flag: --exec. Example: prohost agent install-daemon --exec 'claude -p'\n");
263
- return 1;
282
+ process.stderr.write(`Missing required flag: --exec (or --host-only). Example: prohost agent ${verb} --exec 'claude -p'\n`);
283
+ return null;
264
284
  }
285
+ return { exec, hostOnly: false };
286
+ }
287
+ async function installDaemonCommand(flags) {
288
+ const mode = execOrHostOnly(flags, 'install-daemon');
289
+ if (!mode)
290
+ return 1;
291
+ const { exec } = mode;
265
292
  const credentials = loadOrExplain(flagString(flags, 'url'));
266
293
  if (!credentials)
267
294
  return 1;
@@ -299,6 +326,7 @@ async function installDaemonCommand(flags) {
299
326
  execPath: process.execPath,
300
327
  scriptPath: cliEntrypoint(),
301
328
  exec,
329
+ hostOnly: mode.hostOnly,
302
330
  workdir,
303
331
  timeoutSeconds,
304
332
  idleTimeoutSeconds,
@@ -420,10 +448,14 @@ async function pairCommand(flags) {
420
448
  return 0;
421
449
  }
422
450
  async function runCommand(flags) {
423
- const exec = flagString(flags, 'exec');
424
- if (!exec) {
425
- process.stderr.write("Missing required flag: --exec. Example: prohost agent run --exec 'claude -p'\n");
451
+ const mode = execOrHostOnly(flags, 'run');
452
+ if (!mode)
426
453
  return 1;
454
+ const { exec, hostOnly } = mode;
455
+ if (hostOnly && !loadCodeHostSettings().enabled) {
456
+ // Not fatal: under a supervisor an exit would only restart into the same state.
457
+ process.stderr.write('! --host-only: Code-thread hosting is off on this machine (prohost agent code-host enable), ' +
458
+ 'so this agent will refuse every run and host nothing until it is turned on and the agent restarts.\n');
427
459
  }
428
460
  const credentials = loadOrExplain(flagString(flags, 'url') ?? process.env.PROHOST_WS_URL);
429
461
  if (!credentials)
@@ -465,6 +497,7 @@ async function runCommand(flags) {
465
497
  try {
466
498
  const metrics = await runAgentHarness({
467
499
  exec,
500
+ hostOnly,
468
501
  credentials,
469
502
  url: flagString(flags, 'url') ?? process.env.PROHOST_WS_URL,
470
503
  machine: flagString(flags, 'machine'),
@@ -79,8 +79,10 @@ export interface HarnessInvocation {
79
79
  execPath: string;
80
80
  /** Absolute path of the CLI entrypoint script. */
81
81
  scriptPath: string;
82
- /** The `--exec` command, verbatim. */
82
+ /** The `--exec` command, verbatim. Unused with {@link hostOnly}. */
83
83
  exec: string;
84
+ /** `--host-only` instead of `--exec`: the daemon hosts Code threads and runs nothing. */
85
+ hostOnly?: boolean;
84
86
  workdir: string;
85
87
  timeoutSeconds?: number;
86
88
  /** Seconds of total silence before a run is treated as stalled. */
@@ -171,8 +171,7 @@ export function harnessArguments(invocation) {
171
171
  invocation.scriptPath,
172
172
  'agent',
173
173
  'run',
174
- '--exec',
175
- invocation.exec,
174
+ ...(invocation.hostOnly ? ['--host-only'] : ['--exec', invocation.exec]),
176
175
  '--workdir',
177
176
  invocation.workdir,
178
177
  ];
@@ -28,10 +28,13 @@
28
28
  */
29
29
  import type { spawn } from 'node:child_process';
30
30
  import WebSocket from 'ws';
31
+ import { HarnessAccount } from './account_runtime.js';
31
32
  import type { CommandRunner } from './accounts.js';
32
33
  import type { AgentCredentials } from './credentials.js';
33
34
  import type { RunCapabilities } from './capabilities.js';
34
35
  import type { QuotaProvider } from './runtime_status.js';
36
+ import type { CodeHostLike } from '../codehost/host.js';
37
+ import type { AccountEnvResult, CodeAgent } from '../codehost/terminals.js';
35
38
  /**
36
39
  * Default ceiling on one agent invocation: none.
37
40
  *
@@ -72,9 +75,20 @@ export declare const DEFAULT_IDLE_TIMEOUT_MS = 900000;
72
75
  * uses it to stop a run from sitting on CI after the PR is open.
73
76
  */
74
77
  export declare const PAIRED_RUN_ENV_VAR = "PROHOST_PAIRED_RUN";
78
+ /** What a run sent to a `--host-only` harness is answered with: it never executes. */
79
+ export declare const HOST_ONLY_REFUSAL = "This computer only hosts Code threads; it doesn't answer messages.";
80
+ /** Declared by a `--host-only` harness: it executes no runs (the server may say so, or stop sending them). */
81
+ export declare const HOST_ONLY_CAPABILITY = "host_only";
75
82
  export interface AgentRunOptions {
76
- /** Shell command that runs the agent, e.g. ``claude -p``. */
83
+ /** Shell command that runs the agent, e.g. ``claude -p``. Empty with {@link hostOnly}. */
77
84
  exec: string;
85
+ /**
86
+ * `--host-only`: this harness hosts Code threads (control socket, presence,
87
+ * status, the local server, activity) and executes nothing anyone sends. Every
88
+ * `agent.run_requested` it receives is closed at once as failed with
89
+ * {@link HOST_ONLY_REFUSAL}, so a mention never hangs.
90
+ */
91
+ hostOnly?: boolean;
78
92
  credentials: AgentCredentials;
79
93
  /** Websocket endpoint override. Defaults to the paired value. */
80
94
  url?: string;
@@ -176,6 +190,12 @@ export interface AgentRunOptions {
176
190
  * inject a fake so no extra request lands in their recorded calls.
177
191
  */
178
192
  capabilityProbe?: () => Promise<RunCapabilities>;
193
+ /**
194
+ * The Code-thread host (`codehost/host.ts`). Unset, one is built only when
195
+ * this machine's owner turned hosting on (`prohost agent code-host enable`);
196
+ * `null` never hosts. Tests inject a fake.
197
+ */
198
+ codeHost?: CodeHostLike | null;
179
199
  }
180
200
  export interface AgentRunMetrics {
181
201
  connections: number;
@@ -228,5 +248,14 @@ export declare function codexMcpEntryProvider(options: AgentRunOptions, log: (li
228
248
  * Reconnects on any drop with jittered exponential backoff; an ``auth_error``
229
249
  * is fatal, because retrying a rejected credential just hammers the server.
230
250
  */
251
+ /**
252
+ * The login a Code thread's agent runs on: the account picked for this
253
+ * agent, as its ordinary runs get it. A thread for the other CLI, or an
254
+ * account that is no longer on this machine, is refused rather than run on
255
+ * some other login — it would spend a subscription the agent doesn't report.
256
+ * A harness without accounts (a plain `--exec`) and shell threads use the
257
+ * operator's default login, exactly as everything else they run does.
258
+ */
259
+ export declare function codeThreadAccountEnv(account: HarnessAccount | undefined, agent: CodeAgent): AccountEnvResult;
231
260
  export declare function runAgentHarness(options: AgentRunOptions): Promise<AgentRunMetrics>;
232
261
  export {};