@prohost/cli 0.15.0 → 0.16.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.
- package/CHANGELOG.md +68 -0
- package/README.md +265 -1
- package/dist/agent/accounts.d.ts +11 -0
- package/dist/agent/accounts.js +12 -1
- package/dist/agent/api.d.ts +6 -0
- package/dist/agent/api.js +5 -0
- package/dist/agent/claude.d.ts +6 -0
- package/dist/agent/claude.js +2 -2
- package/dist/agent/daemon.d.ts +33 -0
- package/dist/agent/daemon.js +56 -0
- package/dist/agent/prompt.d.ts +12 -0
- package/dist/agent/prompt.js +5 -0
- package/dist/agent/run.d.ts +110 -1
- package/dist/agent/run.js +186 -19
- package/dist/codehost/host.d.ts +25 -0
- package/dist/codehost/host.js +16 -1
- package/dist/codehost/host_stats.d.ts +49 -2
- package/dist/codehost/host_stats.js +153 -2
- package/dist/codehost/relay.d.ts +10 -0
- package/dist/codehost/relay.js +17 -1
- package/dist/host/api.d.ts +82 -0
- package/dist/host/api.js +122 -0
- package/dist/host/capacity.d.ts +41 -0
- package/dist/host/capacity.js +48 -0
- package/dist/host/command.d.ts +59 -0
- package/dist/host/command.js +584 -0
- package/dist/host/computer.d.ts +120 -0
- package/dist/host/computer.js +291 -0
- package/dist/host/events.d.ts +24 -0
- package/dist/host/events.js +47 -0
- package/dist/host/install.d.ts +152 -0
- package/dist/host/install.js +755 -0
- package/dist/host/ledger.d.ts +52 -0
- package/dist/host/ledger.js +131 -0
- package/dist/host/legacy.d.ts +38 -0
- package/dist/host/legacy.js +115 -0
- package/dist/host/logins.d.ts +109 -0
- package/dist/host/logins.js +190 -0
- package/dist/host/migrate.d.ts +165 -0
- package/dist/host/migrate.js +364 -0
- package/dist/host/pair.d.ts +62 -0
- package/dist/host/pair.js +219 -0
- package/dist/host/peak_memory.d.ts +44 -0
- package/dist/host/peak_memory.js +139 -0
- package/dist/host/scheduler.d.ts +86 -0
- package/dist/host/scheduler.js +186 -0
- package/dist/host/service.d.ts +114 -0
- package/dist/host/service.js +1027 -0
- package/dist/host/session_move.d.ts +48 -0
- package/dist/host/session_move.js +106 -0
- package/dist/host/skills.d.ts +29 -0
- package/dist/host/skills.js +135 -0
- package/dist/host/status.d.ts +91 -0
- package/dist/host/status.js +130 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +47 -3
- package/dist/version.d.ts +2 -2
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,74 @@ builds via `prepack`, runs the suite, publishes, and tags `cli-v<version>`.
|
|
|
6
6
|
|
|
7
7
|
## Unreleased
|
|
8
8
|
|
|
9
|
+
## 0.16.0
|
|
10
|
+
|
|
11
|
+
- **`prohost host` — one service per computer, many employees.** A computer is added once
|
|
12
|
+
(`host pair`: device code, or `--token` for the desktop app's self-add; `--name`,
|
|
13
|
+
`--expect-account`, `$PROHOST_PAIR_RESULT_FILE`) and serves every AI employee placed on it on
|
|
14
|
+
one stream authenticated as the computer (`$PROHOST_HOME/host/computer.json`, 0600).
|
|
15
|
+
- Runs are claimed only when a slot is free (`POST /v1/computers/{id}/runs/{run_id}/claim`,
|
|
16
|
+
exactly once; `409` → skipped) and executed like `agent run` runs, as the employee with the
|
|
17
|
+
run's own token (heartbeat, tool events, reply, a per-run MCP config, completion with
|
|
18
|
+
`peak_mem_mb` / `login_moved`). The claim's skills are written into the employee's workspace
|
|
19
|
+
(`.claude/skills/<key>/`; `.codex/skills/<key>/` plus an `AGENTS.md` list). A run claimed
|
|
20
|
+
before a restart is reported, never re-run. The ledger is keyed by employee and conversation.
|
|
21
|
+
- Run order: someone waiting first, then turns between employees, then oldest; conversations
|
|
22
|
+
take turns; per-employee `max_concurrent` and `keep_one_ready`; terminal sessions never wait
|
|
23
|
+
and count against capacity.
|
|
24
|
+
- Capacity recommended from memory, cores and workload (max(8 GB, 25 %) reserve, ~1.5 GB chat /
|
|
25
|
+
~4 GB coding, ≤ half the cores, 1–16), configured in ProhostAI. Holds new work when paused,
|
|
26
|
+
under critical memory pressure or on battery (each configurable), with `hold_started` /
|
|
27
|
+
`hold_ended` events. Each run's peak memory is sampled from its process tree.
|
|
28
|
+
- `POST /v1/computers/{id}/status` every 30 s and on change (host stats, slots, AI logins with
|
|
29
|
+
plan and quota windows — never the email — running work, capabilities, `recommended_capacity`,
|
|
30
|
+
`dedicated`); the answer is the computer's config.
|
|
31
|
+
- Opt-in sharing between AI logins of the same kind: a conversation sticks to its login; when
|
|
32
|
+
that login rests, its Claude session (`projects/<project>/<id>.jsonl`) or Codex rollout
|
|
33
|
+
(including a WSL `CODEX_HOME`) is copied to another login and the run resumes with an
|
|
34
|
+
"interrupted" note (`login_resting`, `login_moved`); otherwise the run waits for the reset.
|
|
35
|
+
Claude Code and Codex are never mixed.
|
|
36
|
+
- Terminal sessions (Code threads) are hosted by the same service with the computer credential.
|
|
37
|
+
- `host status --json`, `host logins --json` (plan and the local-only `email_local`),
|
|
38
|
+
`host pause` / `resume`, `$PROHOST_HOME/host/activity.json`.
|
|
39
|
+
- `host install [--dedicated --service-user <name>]` / `uninstall`: launchd (user agent / system
|
|
40
|
+
daemon with `UserName`), systemd (user / system unit with `User=`), Windows Task Scheduler (at
|
|
41
|
+
logon / at boot as the service user via S4U). Stopping or an update drains: no new work, runs
|
|
42
|
+
going finish, then it exits (75 after an update) for the service manager to restart it.
|
|
43
|
+
- `host migrate` adopts existing agent homes (sessions carried over; served with their own
|
|
44
|
+
credential, in their own `--workdir`, until placed on the computer) and the desktop app's
|
|
45
|
+
terminal-hosting home (`ai.prohost.desktop[.dev].code-host`), stops the old launchd agents it
|
|
46
|
+
adopted (`--keep-services`, `--dry-run`, `--json`), and keeps earlier adoptions on a re-run.
|
|
47
|
+
It refuses while the host service runs (the service owns the ledger).
|
|
48
|
+
- One `host run` per home (`host/service.lock`); each employee's runs share one session ledger.
|
|
49
|
+
- For the desktop app: `host install|uninstall --label <id>` (default derived from the home);
|
|
50
|
+
services run the CLI with the executable that launched it, keeping `ELECTRON_RUN_AS_NODE=1`
|
|
51
|
+
and the launching `PATH` under Electron; re-running install leaves a right service alone.
|
|
52
|
+
`host migrate --from <home>` (repeatable, read-only) and computer adoption through an agent
|
|
53
|
+
key (`/v1/computers/pairing/adopt`). `host logins add --runtime …`, `host unpair`.
|
|
54
|
+
`host status --json` always exits 0 and adds `hardware`, `events` (24 h, stable ids) and
|
|
55
|
+
`service`. `host pause` also stops new terminal sessions (open ones carry on).
|
|
56
|
+
- Dedicated: creates the service account (macOS / Linux), copies this home's `computer.json`,
|
|
57
|
+
installs `--claude-token-file` for `CLAUDE_CODE_OAUTH_TOKEN`, logs to the journal /
|
|
58
|
+
root-owned `/Library/Logs/Prohost` / ACL'd ProgramData (never under the service user's
|
|
59
|
+
home), and publishes its state for the logged-in user's `host status` / `host pause`.
|
|
60
|
+
- `host pair` names the approved workspace and, in a terminal without a matching
|
|
61
|
+
`--expect-account`, asks before saving; a declined or mismatched key is revoked.
|
|
62
|
+
- Terminal sessions use the computer key on `/v1/code-hosts/control`, falling back to the
|
|
63
|
+
adopted agent key on a server that refuses it.
|
|
64
|
+
- The send rule: replies carry the agent's `confidence` / `reply_class` (a trailer line it is
|
|
65
|
+
asked for, stripped before sending — a missing or malformed one sends neither, never a
|
|
66
|
+
default); a reply held as a suggestion, or refused because the thread is muted (`ai_muted`),
|
|
67
|
+
is handled, not failed.
|
|
68
|
+
- `claude auth status --json`'s `subscriptionType` is now read (the email only for local display).
|
|
69
|
+
- Host stats on Linux (`/proc/meminfo`, `/proc/pressure/memory`, `/sys/class/power_supply`) and
|
|
70
|
+
Windows (`typeperf` counters, `Win32_Battery`), beside macOS.
|
|
71
|
+
- `prohost agent run` and every other command are unchanged.
|
|
72
|
+
- **`agent install-daemon` re-install no longer fails while the old agent drains.** After
|
|
73
|
+
`bootout` it waits (bounded at 90 s) for `launchctl print` to say the old job has left launchd
|
|
74
|
+
before `bootstrap`; bootstrapping while the old agent finished its in-flight run failed with
|
|
75
|
+
"Input/output error". A bootstrap that still fails says when the old agent had not stopped.
|
|
76
|
+
|
|
9
77
|
## 0.15.0
|
|
10
78
|
|
|
11
79
|
- **Code threads on this Mac: scroll back locally, no flashing, no permission prompts.**
|
package/README.md
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
# `@prohost/cli`
|
|
2
2
|
|
|
3
|
-
The [ProhostAI](https://prohost.ai) command-line tool.
|
|
3
|
+
The [ProhostAI](https://prohost.ai) command-line tool. Four things it does:
|
|
4
4
|
|
|
5
|
+
- **`prohost host`** — add this computer to your workspace once, and it works
|
|
6
|
+
for every AI employee you place on it: Claude Code or Codex, on the AI logins
|
|
7
|
+
signed in here, several things at once, plus the terminal sessions you open
|
|
8
|
+
on it. See [Computers](#computers-prohost-host).
|
|
5
9
|
- **`prohost agent`** — run your own local agent as a ProhostAI teammate. Someone
|
|
6
10
|
@-mentions it in the app, it runs on your machine, and its answer is posted
|
|
7
11
|
back into the conversation.
|
|
@@ -41,6 +45,266 @@ The rest of this page is detail on each step.
|
|
|
41
45
|
|
|
42
46
|
---
|
|
43
47
|
|
|
48
|
+
## Computers: `prohost host`
|
|
49
|
+
|
|
50
|
+
A computer is added to a workspace once and then serves **every** AI employee
|
|
51
|
+
placed on it — one background service, one connection, many employees, each
|
|
52
|
+
running Claude Code or Codex on the AI logins signed in on this machine. It also
|
|
53
|
+
hosts the terminal sessions people open on it from ProhostAI.
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npx @prohost/cli host pair # prints a code; enter it in ProhostAI → AI Employees → Computers
|
|
57
|
+
npx @prohost/cli host install # keep it running in the background
|
|
58
|
+
npx @prohost/cli host status # what it is doing
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Then pick the computer under **Where it works** when you hire (or edit) an AI
|
|
62
|
+
employee. `prohost agent run` keeps working exactly as before for anything you
|
|
63
|
+
paired that way.
|
|
64
|
+
|
|
65
|
+
### Commands
|
|
66
|
+
|
|
67
|
+
| Command | What it does |
|
|
68
|
+
| --- | --- |
|
|
69
|
+
| `host pair [--name <n>] [--expect-account <id>]` | Device-code pairing: prints a code and the page to enter it on, waits for approval, stores the computer credential in `$PROHOST_HOME/host/computer.json` (0600). |
|
|
70
|
+
| `host pair --token <redeem_token>` | Redeems a one-time token from the signed-in desktop app instead (`--token -` reads it from stdin; `$PROHOST_REDEEM_TOKEN` also works). The token is never printed. |
|
|
71
|
+
| `host run [--machine <n>] [--timeout <s>] [--idle-timeout <s>] [--workload coding\|chat]` | The service, in the foreground. |
|
|
72
|
+
| `host install [--label <id>] [--dedicated --service-user <name> [--service-home <dir>] [--claude-token-file <path>]]` / `host uninstall [--label <id>]` | The background service for this OS (below). Safe to re-run: nothing that is already right is touched. |
|
|
73
|
+
| `host unpair` | Remove this computer from its workspace (`DELETE /v1/computers/{id}` with its own key), then delete `computer.json`. Exits 0 only when the server removed it, or had already. |
|
|
74
|
+
| `host status [--json] [--label <id>]` | Online, paused, capacity, what is running, AI logins, hardware, recent events. Always exits 0. |
|
|
75
|
+
| `host logins [--json]` | The AI logins found on this machine, with their plan and (locally only) email. |
|
|
76
|
+
| `host logins add --runtime claude_code\|codex [--label <l>]` | A new AI login in its own config directory (`~/.prohost-accounts`), signed in right here (`claude auth login` / `codex login`). |
|
|
77
|
+
| `host pause` / `host resume` | Take no new work and open no new terminal sessions / take them again. Work already going finishes. |
|
|
78
|
+
| `host migrate [--from <home>]… [--home <dir,…>] [--keep-services] [--dry-run] [--json]` | Adopt existing agent homes and the desktop app's terminal-hosting home, and become a computer through their agent keys (below). |
|
|
79
|
+
|
|
80
|
+
`--expect-account` works as it does for `agent pair`: a computer approved in any
|
|
81
|
+
other workspace is refused and nothing is stored. `$PROHOST_PAIR_RESULT_FILE`
|
|
82
|
+
gets `{"version": 1, "outcome", "computer_id", "account": {"id", "name"}, "message"}`
|
|
83
|
+
(outcomes: `paired`, `expired`, `token_rejected`, `workspace_unconfirmed`,
|
|
84
|
+
`declined`, `rate_limited`, `unreachable`, `invalid_arguments`, `failed`), written
|
|
85
|
+
0600 and atomically, never holding the code, token or key.
|
|
86
|
+
|
|
87
|
+
Once approved, pairing prints the workspace it was approved in. In a terminal,
|
|
88
|
+
unless `--expect-account` already vouched for it, it asks "Add this computer to
|
|
89
|
+
<workspace>? [y/N]" before saving anything; no (or a workspace that doesn't match
|
|
90
|
+
`--expect-account`) revokes the new key on the server and saves nothing. A
|
|
91
|
+
program (no terminal) relies on `--expect-account`.
|
|
92
|
+
|
|
93
|
+
### How a run works on a computer
|
|
94
|
+
|
|
95
|
+
1. ProhostAI sends `agent.run_requested` on the computer's stream (signed with
|
|
96
|
+
the pairing's secret; an unsigned frame is refused and never run).
|
|
97
|
+
2. The run waits its turn (below). When a slot is free and its AI login has
|
|
98
|
+
room, the computer **claims** it (`POST /v1/computers/{id}/runs/{run_id}/claim`).
|
|
99
|
+
Exactly one computer ever gets a claim; a `409` means another computer took
|
|
100
|
+
it, and it is skipped.
|
|
101
|
+
3. The claim returns the run, a short-lived **run token** and the employee's
|
|
102
|
+
**skills**. The skills are written into the employee's workspace —
|
|
103
|
+
`.claude/skills/<key>/…` for Claude Code; `.codex/skills/<key>/…` plus a
|
|
104
|
+
list in `AGENTS.md` for Codex — and skills no longer granted are removed.
|
|
105
|
+
4. The run executes exactly like an `agent run` run (same prompt, sessions,
|
|
106
|
+
tool events, reply, completion) but **as the employee, with the run token**:
|
|
107
|
+
the heartbeat, tool events, reply, its MCP config (a file of its own,
|
|
108
|
+
removed afterwards) and its completion all carry that token, never the
|
|
109
|
+
computer's key. The completion adds the run's `peak_mem_mb` and, if it moved,
|
|
110
|
+
`login_moved`.
|
|
111
|
+
5. A run claimed but never reported (the service stopped mid-run) is reported
|
|
112
|
+
failed on the next start, with its own token — it is never run again.
|
|
113
|
+
|
|
114
|
+
Employees on a computer **skip permission prompts** (Claude
|
|
115
|
+
`--dangerously-skip-permissions`, Codex `--yolo`); message approval rules still
|
|
116
|
+
apply on the server. Each employee has its own home under
|
|
117
|
+
`$PROHOST_HOME/host/employees/<agent_id>/` (its sessions, workspace and
|
|
118
|
+
`extra-mcp.json` if you add one).
|
|
119
|
+
|
|
120
|
+
### Run order
|
|
121
|
+
|
|
122
|
+
Someone waiting (a mention, a DM, a customer's message) goes first; then the
|
|
123
|
+
employees take turns (the one with the fewest runs going, then the one that
|
|
124
|
+
started longest ago); then oldest first. Runs in one conversation always take
|
|
125
|
+
turns. Terminal sessions never wait — they count against capacity, so they
|
|
126
|
+
shrink what the employees get. Each employee's **Up to N** caps its own runs,
|
|
127
|
+
and **Keep one ready** keeps a slot free for it while it has nothing running.
|
|
128
|
+
|
|
129
|
+
### Capacity and holds
|
|
130
|
+
|
|
131
|
+
Capacity is how many things the computer works on at once. It is set in
|
|
132
|
+
ProhostAI; until it is, the recommendation is used: memory after a reserve of
|
|
133
|
+
max(8 GB, 25 %), divided by ~1.5 GB per chat run or ~4 GB per coding run
|
|
134
|
+
(`--workload`), at most half the cores, between 1 and 16.
|
|
135
|
+
|
|
136
|
+
New work is **held** (work going is never stopped) while the computer is paused
|
|
137
|
+
(`host pause`, or in ProhostAI), under critical memory pressure, or on battery —
|
|
138
|
+
each hold can be switched off in ProhostAI. Holds are reported as they start and
|
|
139
|
+
end. The service reports every 30 seconds and on any change: CPU, memory, memory
|
|
140
|
+
pressure (macOS `kern.memorystatus_vm_pressure_level`; Linux
|
|
141
|
+
`/proc/pressure/memory`; Windows performance counters through `typeperf`),
|
|
142
|
+
battery, slots, AI logins and what is running with each run's peak memory
|
|
143
|
+
(the agent process and everything it started, sampled every 5 s).
|
|
144
|
+
|
|
145
|
+
### AI logins and sharing between them
|
|
146
|
+
|
|
147
|
+
A login is a Claude Code or Codex sign-in: `~/.claude`, `~/.codex`, and every
|
|
148
|
+
account in `~/.prohost-accounts` (`prohost agent account add`). The plan comes
|
|
149
|
+
from `claude auth status --json` (`subscriptionType`, e.g. "Claude Max") or
|
|
150
|
+
Codex's rate-limit read ("ChatGPT Pro"). The email in `claude auth status`
|
|
151
|
+
is shown by `host logins` on this machine only; it is never sent.
|
|
152
|
+
|
|
153
|
+
A conversation sticks to the login holding its session. New conversations go to
|
|
154
|
+
the login with the most room. When a login hits its usage limit it **rests**
|
|
155
|
+
until its reset, and ProhostAI is told (`login_resting`). Then:
|
|
156
|
+
|
|
157
|
+
- **Sharing on** (per computer, per kind, in ProhostAI): the conversation's
|
|
158
|
+
session file is copied to another login **of the same kind** — Claude Code's
|
|
159
|
+
`projects/<project>/<session>.jsonl` under the other `CLAUDE_CONFIG_DIR`, or
|
|
160
|
+
Codex's rollout under the other `CODEX_HOME` (a WSL `CODEX_HOME` given as
|
|
161
|
+
`wsl://<distro>/<path>` is reached through `\\wsl.localhost\<distro>\…`) —
|
|
162
|
+
and the run resumes there, told it was interrupted and to check what is
|
|
163
|
+
already done (`login_moved`). Claude Code and Codex are never mixed.
|
|
164
|
+
- **Sharing off, or no other login has room**: a run going is reported failed
|
|
165
|
+
with the limit and its reset time, so ProhostAI runs it again after the reset;
|
|
166
|
+
runs not started yet wait for the earliest reset.
|
|
167
|
+
|
|
168
|
+
### The background service
|
|
169
|
+
|
|
170
|
+
| | `host install` | `host install --dedicated --service-user <name>` |
|
|
171
|
+
| --- | --- | --- |
|
|
172
|
+
| macOS | LaunchAgent `<label>` (while you are logged in) | LaunchDaemon `/Library/LaunchDaemons/<label>.plist` with `UserName` |
|
|
173
|
+
| Linux | systemd user unit (`loginctl enable-linger` to outlive your session) | system unit in `/etc/systemd/system` with `User=` |
|
|
174
|
+
| Windows | Task Scheduler task at logon | the same task at boot, running as the service user (S4U: no password stored) |
|
|
175
|
+
|
|
176
|
+
The label is `ai.prohost.host` for `~/.prohost` (unit `prohost-host.service`,
|
|
177
|
+
task `\ProhostAI\Host`) and one derived from `$PROHOST_HOME` for any other home,
|
|
178
|
+
so two homes never replace each other's service; `--label` sets it (the desktop
|
|
179
|
+
app passes `ai.prohost.desktop.host` / `ai.prohost.desktop.dev.host`).
|
|
180
|
+
|
|
181
|
+
Dedicated mode keeps working when no one is logged in; install it as root /
|
|
182
|
+
from an administrator prompt. It:
|
|
183
|
+
|
|
184
|
+
- creates the service account if it doesn't exist (macOS: a hidden user, ids
|
|
185
|
+
401–499; Linux: a system user; on Windows the account must already exist —
|
|
186
|
+
creating one there means choosing a password policy);
|
|
187
|
+
- uses that account's `~/.prohost` (`--service-home` sets another), copies your
|
|
188
|
+
`computer.json` into it if it has none (pair as yourself, then install; under
|
|
189
|
+
sudo "yours" is `$SUDO_USER`'s `~/.prohost`, or `--from-home <dir>`), and
|
|
190
|
+
installs `--claude-token-file` (a `claude setup-token` token) there for every
|
|
191
|
+
Claude run (adopted Claude agents too) and Claude login check as
|
|
192
|
+
`CLAUDE_CODE_OAUTH_TOKEN` (never to Codex, never on a command line, so not to
|
|
193
|
+
terminal sessions either: they would carry it in tmux's arguments) — read
|
|
194
|
+
once, never printed, re-read each time so installing a new one needs no
|
|
195
|
+
restart. Everything in the
|
|
196
|
+
account's home is written as the account (`sudo -u` / `runuser`), never by
|
|
197
|
+
root through a path the account controls (a `--service-home` the account
|
|
198
|
+
can't create is made by root only under a root-owned folder, then handed
|
|
199
|
+
over). On Windows the ProgramData folders are made in one step or, if they
|
|
200
|
+
were there, kept only when Administrators / SYSTEM own them; each is taken over
|
|
201
|
+
top-down (owner Administrators, `/reset`, exactly our grants), checked again
|
|
202
|
+
afterwards (owner, no junction, no other principal), and the token is always a
|
|
203
|
+
new file (`wx`) in
|
|
204
|
+
`C:\ProgramData\Prohost\secrets\<label>` (the account may only read it), and
|
|
205
|
+
the account's home is made private and given the credential only when the
|
|
206
|
+
install creates it. If your own (personal) service runs on the same computer
|
|
207
|
+
key, install says so and how to remove it;
|
|
208
|
+
- logs where the service account can't redirect it: the journal on Linux,
|
|
209
|
+
`/Library/Logs/Prohost/<label>.log` (in a root-owned folder) on macOS,
|
|
210
|
+
`C:\ProgramData\Prohost\Logs` (ACL: SYSTEM, administrators, the service
|
|
211
|
+
account) on Windows;
|
|
212
|
+
- publishes its state for anyone logged in on the computer, without login
|
|
213
|
+
emails, in a root-owned `/Library/Application Support/Prohost/hosts/<label>`,
|
|
214
|
+
`/var/lib/prohost/hosts/<label>` or `C:\ProgramData\Prohost\hosts\<label>`: the service owns only
|
|
215
|
+
`state.json`, `activity.json` and `events.json` there, and `control.json` is
|
|
216
|
+
the pause switch anyone on the computer may flip. `host status` and
|
|
217
|
+
`host pause` run as the logged-in user find it there, and
|
|
218
|
+
`sudo prohost host uninstall` finds the service by it (install prints the exact
|
|
219
|
+
command with its `--label`).
|
|
220
|
+
|
|
221
|
+
On Windows it is a boot-start task rather than a Windows service, because Node
|
|
222
|
+
cannot answer the Service Control Manager without a native wrapper.
|
|
223
|
+
|
|
224
|
+
Every variant runs `<executable> <cli> host run` with the `PATH` it was installed
|
|
225
|
+
from: Node, or — under the desktop app — the app's Electron binary, with
|
|
226
|
+
`ELECTRON_RUN_AS_NODE=1` (and `PROHOST_CLI_RUN=1`) kept in the service's
|
|
227
|
+
environment so it runs the CLI rather than opening the app. Personal services
|
|
228
|
+
log to `$PROHOST_HOME/host/host.log`. Re-running `host install` changes nothing
|
|
229
|
+
that is already right (the desktop app runs it after every update); a changed
|
|
230
|
+
definition is loaded after the old service drains. Only one `host run`
|
|
231
|
+
runs per `PROHOST_HOME` (`host/service.lock`); a second one refuses to start.
|
|
232
|
+
The systemd unit uses `KillMode=mixed`, so a stop reaches the host first and its
|
|
233
|
+
runs can finish. Re-installing on macOS waits for the previous service to finish
|
|
234
|
+
draining (up to its 10-minute stop timeout) before loading the new one. A dedicated install hands the service user's `PROHOST_HOME` to
|
|
235
|
+
that user (`chown`); on Windows it checks for an elevated prompt first. **Updates drain:**
|
|
236
|
+
when a newer CLI is installed over the running one (or the service is told to
|
|
237
|
+
stop), it takes no new work, lets the runs going finish, then exits (code 75
|
|
238
|
+
after an update) for the service manager to start the new one. A second
|
|
239
|
+
Ctrl-C (or signal) stops the runs going too.
|
|
240
|
+
|
|
241
|
+
### Migrating an existing setup: `host migrate`
|
|
242
|
+
|
|
243
|
+
`host migrate` brings agents you already run under the host service without
|
|
244
|
+
pairing anything again. When this home has no computer yet, it becomes one
|
|
245
|
+
through an adopted home's agent key (`POST /v1/computers/pairing/adopt`): the
|
|
246
|
+
pairing's creator owns it, no code to enter. `--from <home>` (repeatable) adopts
|
|
247
|
+
only those homes — nothing is discovered, nothing is stopped, the homes are only
|
|
248
|
+
read — which is how the desktop app hands over its own; exit 0 means adopted, or
|
|
249
|
+
nothing to adopt.
|
|
250
|
+
|
|
251
|
+
- **Agent homes** — every home a launchd agent `ai.prohost.agent.*` runs (its
|
|
252
|
+
`--exec`, `--workdir`, `--concurrency`, `--repos`, timeouts and `--safe-tools`
|
|
253
|
+
are read from the plist), `$PROHOST_HOME`, and each `--home` (the desktop
|
|
254
|
+
app passes the homes of the agents it supervises). Their
|
|
255
|
+
conversation sessions are copied to the employee's host home. Until
|
|
256
|
+
ProhostAI places that employee on this computer, `host run` keeps serving it
|
|
257
|
+
with **its own credential**, as `agent run` did; once it is placed, the
|
|
258
|
+
computer serves it and the old connection is stopped.
|
|
259
|
+
- **The terminal-hosting home** — a `--host-only` home (including the desktop
|
|
260
|
+
app's `ai.prohost.desktop[.dev].code-host` agent), or one with Code-thread
|
|
261
|
+
hosting on. The service hosts terminal sessions from it, so open terminals,
|
|
262
|
+
opened folders and the remote switches carry over.
|
|
263
|
+
- **Old launchd agents** it adopted are stopped and removed (`--keep-services`
|
|
264
|
+
keeps them; then stop them yourself, or each employee is answered twice).
|
|
265
|
+
|
|
266
|
+
It cannot adopt: a systemd unit or Windows task written by hand from
|
|
267
|
+
`agent install-daemon` (pass `--home` and stop the unit yourself); an agent the
|
|
268
|
+
desktop app supervises (the app stops it); an `--exec` that is neither Claude
|
|
269
|
+
Code nor Codex (it keeps running on its own credential and is never served as
|
|
270
|
+
the computer); a home whose `agent.json` is missing, unreadable or revoked; the
|
|
271
|
+
sessions of a home that ran in a custom `--workdir`, once the computer serves it
|
|
272
|
+
(Claude Code files sessions by directory, and the host never runs an employee in
|
|
273
|
+
a checkout — those conversations start fresh; until then its own connection
|
|
274
|
+
keeps running in that `--workdir`); `prohost listen` and `prohost usage`
|
|
275
|
+
daemons. Running `host migrate` again keeps what it adopted before, including
|
|
276
|
+
settings read from plists it has since removed. It refuses while the host
|
|
277
|
+
service is running (the service owns the run ledger): stop it, migrate, then
|
|
278
|
+
start it again. `--dry-run` shows what it would do; `--json` prints the report.
|
|
279
|
+
|
|
280
|
+
### For the desktop app
|
|
281
|
+
|
|
282
|
+
- `host status --json` (always exits 0) →
|
|
283
|
+
`{paired, computer: {id, name, account: {id, name}}, online, paused, slots: {total, busy, held, held_reason}, running: [{run_id, agent: {id, name, avatar_url}, title, started_at, kind}], logins: [{account_key, label, runtime, subscription_type, plan_label, email_local, auth_state, windows}], capacity, recommended_capacity, hardware: {memory_gb, cpu_cores, os, chip}, events: [{id, kind, at, …}], service: {running, pid, adopted, dedicated, activity_file?}, version}`.
|
|
284
|
+
`online` is false when the service has not written its state for 2 minutes.
|
|
285
|
+
`events` are the last 24 h of what the service sent (`login_resting`,
|
|
286
|
+
`login_moved`, `hold_started`, `hold_ended`, with the `/events` fields plus
|
|
287
|
+
`computer_id` / `computer_name`), newest last, each with a stable `id`.
|
|
288
|
+
`service` says whether a service runs for this home (`host/service.lock`), how
|
|
289
|
+
many homes it adopted, and — run as the logged-in user on a dedicated computer —
|
|
290
|
+
that it is the dedicated service (`activity_file` is then its shared copy).
|
|
291
|
+
- `host logins --json` → `{logins: [...same shape as status...]}`.
|
|
292
|
+
- `$PROHOST_HOME/host/activity.json` → `{v: 1, busy, running: [...as in status...], updatedAt}`.
|
|
293
|
+
- `$PROHOST_HOME/host/state.json` is the service's own state (0600); read
|
|
294
|
+
`status --json` rather than this file.
|
|
295
|
+
- `host pause` / `resume` write `$PROHOST_HOME/host/control.json` (and a
|
|
296
|
+
dedicated service's shared `control.json`); the running service picks it up
|
|
297
|
+
within 2 seconds. Paused, it also starts no new terminal session (the start
|
|
298
|
+
is reported failed, "this computer is paused") and lets no new viewer dial
|
|
299
|
+
in; terminals already open, their replies and approvals carry on.
|
|
300
|
+
- `prohost --help` names what this build can do: `ELECTRON_RUN_AS_NODE`
|
|
301
|
+
(services written for the desktop app), `--claude-token-file`, `host unpair`.
|
|
302
|
+
- Terminal sessions authenticate to `/v1/code-hosts/control` with the computer
|
|
303
|
+
key; a server from before computer keys (`not_paired`) is asked again with the
|
|
304
|
+
adopted terminal-hosting home's agent key. A revoked or invalid key never
|
|
305
|
+
falls back.
|
|
306
|
+
|
|
307
|
+
|
|
44
308
|
## Bring your own agent
|
|
45
309
|
|
|
46
310
|
Any agent that reads a prompt on stdin and writes its answer to stdout works —
|
package/dist/agent/accounts.d.ts
CHANGED
|
@@ -155,6 +155,16 @@ export interface AuthCheck {
|
|
|
155
155
|
state: AuthState;
|
|
156
156
|
/** What the login itself says it bills to, when it says. */
|
|
157
157
|
billing?: AccountBilling;
|
|
158
|
+
/**
|
|
159
|
+
* Claude Code's `subscriptionType` (`max`, `pro`, …), lowercased, when the
|
|
160
|
+
* login says. Safe to send: it names a plan, not a person.
|
|
161
|
+
*/
|
|
162
|
+
subscriptionType?: string;
|
|
163
|
+
/**
|
|
164
|
+
* The login's email, only when the caller asked for it (`includeEmail`).
|
|
165
|
+
* Local display only (`prohost host logins`): it is never sent anywhere.
|
|
166
|
+
*/
|
|
167
|
+
email?: string;
|
|
158
168
|
}
|
|
159
169
|
/** Runs one short command with extra env; resolves exit code and stdout. Never rejects. */
|
|
160
170
|
export type CommandRunner = (command: string, args: string[], env: NodeJS.ProcessEnv) => Promise<{
|
|
@@ -174,6 +184,7 @@ export declare const defaultCommandRunner: CommandRunner;
|
|
|
174
184
|
export declare function checkAuth(record: AccountRecord, options?: {
|
|
175
185
|
binary?: string;
|
|
176
186
|
run?: CommandRunner;
|
|
187
|
+
includeEmail?: boolean;
|
|
177
188
|
}): Promise<AuthCheck>;
|
|
178
189
|
/**
|
|
179
190
|
* Poll until the operator has finished signing in, or until the deadline.
|
package/dist/agent/accounts.js
CHANGED
|
@@ -520,7 +520,13 @@ export async function checkAuth(record, options = {}) {
|
|
|
520
520
|
let parsed;
|
|
521
521
|
try {
|
|
522
522
|
const json = JSON.parse(stdout.trim());
|
|
523
|
-
|
|
523
|
+
// The org is never read; the email only when the caller shows it locally.
|
|
524
|
+
parsed = {
|
|
525
|
+
loggedIn: json.loggedIn,
|
|
526
|
+
authMethod: json.authMethod,
|
|
527
|
+
subscriptionType: json.subscriptionType,
|
|
528
|
+
email: options.includeEmail ? json.email : undefined,
|
|
529
|
+
};
|
|
524
530
|
}
|
|
525
531
|
catch {
|
|
526
532
|
return { state: 'unknown' };
|
|
@@ -530,9 +536,14 @@ export async function checkAuth(record, options = {}) {
|
|
|
530
536
|
if (parsed.loggedIn !== true)
|
|
531
537
|
return { state: 'unknown' };
|
|
532
538
|
const method = typeof parsed.authMethod === 'string' ? parsed.authMethod : '';
|
|
539
|
+
const plan = typeof parsed.subscriptionType === 'string' && /^[A-Za-z0-9_-]{1,32}$/.test(parsed.subscriptionType)
|
|
540
|
+
? parsed.subscriptionType.toLowerCase()
|
|
541
|
+
: undefined;
|
|
533
542
|
return {
|
|
534
543
|
state: 'ok',
|
|
535
544
|
billing: method === 'claude.ai' ? 'subscription' : method ? 'api_key' : undefined,
|
|
545
|
+
...(plan ? { subscriptionType: plan } : {}),
|
|
546
|
+
...(typeof parsed.email === 'string' && parsed.email ? { email: parsed.email } : {}),
|
|
536
547
|
};
|
|
537
548
|
}
|
|
538
549
|
const { code, stdout } = await run(options.binary ?? 'codex', ['login', 'status'], env);
|
package/dist/agent/api.d.ts
CHANGED
|
@@ -20,6 +20,12 @@ export interface ApiOptions {
|
|
|
20
20
|
* {@link SEND_TIMEOUT_MS} for the reply and the completion.
|
|
21
21
|
*/
|
|
22
22
|
timeoutMs?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Fields a completion carries beside the terminal state, read when it is
|
|
25
|
+
* sent. `prohost host` adds the run's peak memory and any login move here;
|
|
26
|
+
* `agent run` sets none, so its completions are unchanged.
|
|
27
|
+
*/
|
|
28
|
+
completionExtra?: () => Record<string, unknown>;
|
|
23
29
|
}
|
|
24
30
|
export interface ApiResult {
|
|
25
31
|
ok: boolean;
|
package/dist/agent/api.js
CHANGED
|
@@ -191,6 +191,11 @@ export async function completeRun(options, path, outcome, model) {
|
|
|
191
191
|
}
|
|
192
192
|
if (model)
|
|
193
193
|
body.model = model.slice(0, MAX_MODEL_CHARS);
|
|
194
|
+
// Never over the fields above: the terminal state is what the call is for.
|
|
195
|
+
for (const [key, value] of Object.entries(options.completionExtra?.() ?? {})) {
|
|
196
|
+
if (!(key in body) && value !== undefined)
|
|
197
|
+
body[key] = value;
|
|
198
|
+
}
|
|
194
199
|
const result = await postJson(options, path, body, undefined, false, SEND_TIMEOUT_MS);
|
|
195
200
|
if (result.status === 409)
|
|
196
201
|
return { ...result, ok: true };
|
package/dist/agent/claude.d.ts
CHANGED
|
@@ -314,6 +314,12 @@ export interface McpConfigOptions {
|
|
|
314
314
|
*/
|
|
315
315
|
extraServers?: Record<string, Record<string, unknown>>;
|
|
316
316
|
env?: NodeJS.ProcessEnv;
|
|
317
|
+
/**
|
|
318
|
+
* Write here instead of `$PROHOST_HOME/mcp.json`. `prohost host` gives each
|
|
319
|
+
* run its own file: the key in it is that run's token, and two runs of one
|
|
320
|
+
* employee must never read each other's.
|
|
321
|
+
*/
|
|
322
|
+
file?: string;
|
|
317
323
|
}
|
|
318
324
|
/**
|
|
319
325
|
* The config object handed to `--mcp-config`.
|
package/dist/agent/claude.js
CHANGED
|
@@ -857,8 +857,8 @@ export function buildMcpConfig(options) {
|
|
|
857
857
|
*/
|
|
858
858
|
export function writeMcpConfig(options) {
|
|
859
859
|
const env = options.env ?? process.env;
|
|
860
|
-
const
|
|
861
|
-
const
|
|
860
|
+
const file = options.file ?? mcpConfigPath(env);
|
|
861
|
+
const dir = path.dirname(file);
|
|
862
862
|
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
863
863
|
chmodSync(dir, 0o700);
|
|
864
864
|
// Written beside the target and renamed over it. Every run rewrites this
|
package/dist/agent/daemon.d.ts
CHANGED
|
@@ -30,6 +30,10 @@ export interface DaemonSpec {
|
|
|
30
30
|
logPath: string;
|
|
31
31
|
/** Extra environment launchd must set — launchd inherits almost nothing. */
|
|
32
32
|
env?: Record<string, string>;
|
|
33
|
+
/** A system daemon only: the user it runs as (`UserName`). */
|
|
34
|
+
userName?: string;
|
|
35
|
+
/** Seconds launchd waits after SIGTERM before SIGKILL (`ExitTimeOut`). */
|
|
36
|
+
exitTimeoutSeconds?: number;
|
|
33
37
|
}
|
|
34
38
|
/**
|
|
35
39
|
* Label for the daemon serving one `$PROHOST_HOME`.
|
|
@@ -114,17 +118,46 @@ type Runner = (command: string, args: string[]) => {
|
|
|
114
118
|
status: number | null;
|
|
115
119
|
stderr: string;
|
|
116
120
|
};
|
|
121
|
+
/**
|
|
122
|
+
* How long a re-install waits for the old job to leave launchd after its
|
|
123
|
+
* `bootout`. Bootout only sends SIGTERM: `agent run` then finishes its
|
|
124
|
+
* in-flight runs ("Finishing the in-flight run…"), and launchd keeps the label
|
|
125
|
+
* until the process exits — SIGKILL after the job's `ExitTimeOut`, 20 s by
|
|
126
|
+
* default (this plist sets none). A `bootstrap` before then fails with
|
|
127
|
+
* "Input/output error". 90 s covers that with room for a busy Mac; the
|
|
128
|
+
* desktop app waits the same way (`desktop/src/main/code-host/launchd.ts`).
|
|
129
|
+
*/
|
|
130
|
+
export declare const OLD_JOB_EXIT_TIMEOUT_MS = 90000;
|
|
131
|
+
/**
|
|
132
|
+
* Ask `launchctl print` until the job is gone (any exit but 0) or the bound
|
|
133
|
+
* passes: whether it went. No answer (`status` null) is never "gone". Says
|
|
134
|
+
* once that it is waiting, and boots the job out once more half-way through
|
|
135
|
+
* (a first bootout that failed outright rather than drained).
|
|
136
|
+
*/
|
|
137
|
+
export declare function waitUntilUnloaded(run: Runner, target: string, clock: {
|
|
138
|
+
sleep: (ms: number) => void;
|
|
139
|
+
now: () => number;
|
|
140
|
+
timeoutMs: number;
|
|
141
|
+
onWaiting: (line: string) => void;
|
|
142
|
+
}): boolean;
|
|
117
143
|
/**
|
|
118
144
|
* Write the plist and hand it to launchd.
|
|
119
145
|
*
|
|
120
146
|
* `bootout` first, ignoring its result: bootstrapping a label that is already
|
|
121
147
|
* loaded fails outright, so a plain re-install of an agent whose `--exec`
|
|
122
148
|
* changed would otherwise keep running the old command and report success.
|
|
149
|
+
* Then wait (bounded) until launchd has let the old job go: it drains on
|
|
150
|
+
* SIGTERM, and bootstrapping while it does fails.
|
|
123
151
|
*/
|
|
124
152
|
export declare function installLaunchdDaemon(spec: DaemonSpec, options?: {
|
|
125
153
|
home?: string;
|
|
126
154
|
uid?: number;
|
|
127
155
|
run?: Runner;
|
|
156
|
+
sleep?: (ms: number) => void;
|
|
157
|
+
now?: () => number;
|
|
158
|
+
exitTimeoutMs?: number;
|
|
159
|
+
/** Progress while the old agent stops (default: stderr). */
|
|
160
|
+
onWaiting?: (line: string) => void;
|
|
128
161
|
}): InstallResult;
|
|
129
162
|
/**
|
|
130
163
|
* Unload the daemon and delete its plist. Missing pieces are not an error.
|
package/dist/agent/daemon.js
CHANGED
|
@@ -116,6 +116,11 @@ export function buildLaunchdPlist(spec) {
|
|
|
116
116
|
' <key>StandardErrorPath</key>',
|
|
117
117
|
` <string>${xmlEscape(spec.logPath)}</string>`,
|
|
118
118
|
];
|
|
119
|
+
if (spec.userName)
|
|
120
|
+
lines.push(' <key>UserName</key>', ` <string>${xmlEscape(spec.userName)}</string>`);
|
|
121
|
+
if (spec.exitTimeoutSeconds !== undefined) {
|
|
122
|
+
lines.push(' <key>ExitTimeOut</key>', ` <integer>${Math.max(0, Math.floor(spec.exitTimeoutSeconds))}</integer>`);
|
|
123
|
+
}
|
|
119
124
|
if (spec.env && Object.keys(spec.env).length > 0) {
|
|
120
125
|
lines.push(' <key>EnvironmentVariables</key>', ' <dict>', ...stringEntries(spec.env, ' '), ' </dict>');
|
|
121
126
|
}
|
|
@@ -205,12 +210,56 @@ const defaultRunner = (command, args) => {
|
|
|
205
210
|
const result = spawnSync(command, args, { encoding: 'utf8' });
|
|
206
211
|
return { status: result.status, stderr: result.stderr ?? '' };
|
|
207
212
|
};
|
|
213
|
+
/**
|
|
214
|
+
* How long a re-install waits for the old job to leave launchd after its
|
|
215
|
+
* `bootout`. Bootout only sends SIGTERM: `agent run` then finishes its
|
|
216
|
+
* in-flight runs ("Finishing the in-flight run…"), and launchd keeps the label
|
|
217
|
+
* until the process exits — SIGKILL after the job's `ExitTimeOut`, 20 s by
|
|
218
|
+
* default (this plist sets none). A `bootstrap` before then fails with
|
|
219
|
+
* "Input/output error". 90 s covers that with room for a busy Mac; the
|
|
220
|
+
* desktop app waits the same way (`desktop/src/main/code-host/launchd.ts`).
|
|
221
|
+
*/
|
|
222
|
+
export const OLD_JOB_EXIT_TIMEOUT_MS = 90_000;
|
|
223
|
+
const OLD_JOB_POLL_MS = 500;
|
|
224
|
+
const sleepSync = (ms) => {
|
|
225
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
226
|
+
};
|
|
227
|
+
/**
|
|
228
|
+
* Ask `launchctl print` until the job is gone (any exit but 0) or the bound
|
|
229
|
+
* passes: whether it went. No answer (`status` null) is never "gone". Says
|
|
230
|
+
* once that it is waiting, and boots the job out once more half-way through
|
|
231
|
+
* (a first bootout that failed outright rather than drained).
|
|
232
|
+
*/
|
|
233
|
+
export function waitUntilUnloaded(run, target, clock) {
|
|
234
|
+
const startedAt = clock.now();
|
|
235
|
+
const deadline = startedAt + clock.timeoutMs;
|
|
236
|
+
let said = false;
|
|
237
|
+
let resent = false;
|
|
238
|
+
for (;;) {
|
|
239
|
+
const printed = run('launchctl', ['print', target]);
|
|
240
|
+
if (printed.status !== null && printed.status !== 0)
|
|
241
|
+
return true;
|
|
242
|
+
if (clock.now() >= deadline)
|
|
243
|
+
return false;
|
|
244
|
+
if (!said) {
|
|
245
|
+
said = true;
|
|
246
|
+
clock.onWaiting('Waiting for the previous agent to stop (it finishes what it was doing first)…');
|
|
247
|
+
}
|
|
248
|
+
if (!resent && clock.now() - startedAt >= clock.timeoutMs / 2) {
|
|
249
|
+
resent = true;
|
|
250
|
+
run('launchctl', ['bootout', target]);
|
|
251
|
+
}
|
|
252
|
+
clock.sleep(OLD_JOB_POLL_MS);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
208
255
|
/**
|
|
209
256
|
* Write the plist and hand it to launchd.
|
|
210
257
|
*
|
|
211
258
|
* `bootout` first, ignoring its result: bootstrapping a label that is already
|
|
212
259
|
* loaded fails outright, so a plain re-install of an agent whose `--exec`
|
|
213
260
|
* changed would otherwise keep running the old command and report success.
|
|
261
|
+
* Then wait (bounded) until launchd has let the old job go: it drains on
|
|
262
|
+
* SIGTERM, and bootstrapping while it does fails.
|
|
214
263
|
*/
|
|
215
264
|
export function installLaunchdDaemon(spec, options = {}) {
|
|
216
265
|
const home = options.home ?? os.homedir();
|
|
@@ -227,6 +276,12 @@ export function installLaunchdDaemon(spec, options = {}) {
|
|
|
227
276
|
writeFileSync(plistPath, buildLaunchdPlist(spec), { mode: 0o600 });
|
|
228
277
|
chmodSync(plistPath, 0o600);
|
|
229
278
|
run('launchctl', ['bootout', `${domain}/${spec.label}`]);
|
|
279
|
+
const oldGone = waitUntilUnloaded(run, `${domain}/${spec.label}`, {
|
|
280
|
+
sleep: options.sleep ?? sleepSync,
|
|
281
|
+
now: options.now ?? Date.now,
|
|
282
|
+
timeoutMs: options.exitTimeoutMs ?? OLD_JOB_EXIT_TIMEOUT_MS,
|
|
283
|
+
onWaiting: options.onWaiting ?? ((line) => process.stderr.write(`${line}\n`)),
|
|
284
|
+
});
|
|
230
285
|
const bootstrap = run('launchctl', ['bootstrap', domain, plistPath]);
|
|
231
286
|
if (bootstrap.status !== 0) {
|
|
232
287
|
return {
|
|
@@ -234,6 +289,7 @@ export function installLaunchdDaemon(spec, options = {}) {
|
|
|
234
289
|
plistPath,
|
|
235
290
|
message: `Wrote ${plistPath}, but \`launchctl bootstrap ${domain}\` failed` +
|
|
236
291
|
`${bootstrap.stderr.trim() ? `: ${bootstrap.stderr.trim()}` : '.'}\n` +
|
|
292
|
+
(oldGone ? '' : ` The previous agent was still stopping after ${Math.round((options.exitTimeoutMs ?? OLD_JOB_EXIT_TIMEOUT_MS) / 1000)}s.\n`) +
|
|
237
293
|
`Load it by hand with: launchctl bootstrap ${domain} ${plistPath}`,
|
|
238
294
|
};
|
|
239
295
|
}
|
package/dist/agent/prompt.d.ts
CHANGED
|
@@ -99,6 +99,18 @@ export interface PromptContext {
|
|
|
99
99
|
source: string;
|
|
100
100
|
reason: string;
|
|
101
101
|
}>;
|
|
102
|
+
/**
|
|
103
|
+
* Said right after the opening line: `prohost host` resumed this run on
|
|
104
|
+
* another AI login after the first one hit its usage limit, so the turn may
|
|
105
|
+
* be half done.
|
|
106
|
+
*/
|
|
107
|
+
resumeNote?: string;
|
|
108
|
+
/**
|
|
109
|
+
* Ask the agent to end a customer-visible reply with its confidence line
|
|
110
|
+
* (`[[confidence=0.9 reply_class=general]]`): the server sends it only when
|
|
111
|
+
* confident enough and its class needs no approval, else saves a draft.
|
|
112
|
+
*/
|
|
113
|
+
askConfidence?: boolean;
|
|
102
114
|
}
|
|
103
115
|
/** Build the stdin prompt for one run. */
|
|
104
116
|
export declare function buildPrompt(run: AgentRunRequest, context: PromptContext): string;
|
package/dist/agent/prompt.js
CHANGED
|
@@ -175,6 +175,11 @@ export function buildPrompt(run, context) {
|
|
|
175
175
|
: `You have been given work on a ${run.reply_surface}. There is nowhere to post a reply.`,
|
|
176
176
|
'',
|
|
177
177
|
];
|
|
178
|
+
if (context.resumeNote)
|
|
179
|
+
lines.push(context.resumeNote, '');
|
|
180
|
+
if (context.askConfidence) {
|
|
181
|
+
lines.push('End your reply with one last line, exactly: [[confidence=<0 to 1> reply_class=<class>]] — how sure you are', 'the reply is right and safe to send as is, and its class: general, pricing_action, billing_action,', 'legal_contractual or bug_in_our_code. That line is removed before sending; below the bar, or for a class', 'that needs approval, your reply is saved as a draft for the team instead of sent.', '');
|
|
182
|
+
}
|
|
178
183
|
// The operator's own configuration comes first, above everything the server
|
|
179
184
|
// generated. It is who this agent *is* — persona, house rules, escalation
|
|
180
185
|
// policy — and an agent that meets its standing instructions only after four
|