@prohost/cli 0.12.4 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +44 -0
- package/README.md +21 -0
- package/dist/agent/contract.d.ts +30 -0
- package/dist/agent/contract.js +11 -0
- package/dist/agent/exec.d.ts +6 -0
- package/dist/agent/exec.js +5 -1
- package/dist/agent/heartbeat.d.ts +13 -0
- package/dist/agent/heartbeat.js +9 -1
- package/dist/agent/run.js +390 -143
- package/dist/agent/runtime_status.d.ts +6 -0
- package/dist/agent/runtime_status.js +5 -0
- package/dist/agent/venue.d.ts +163 -0
- package/dist/agent/venue.js +272 -0
- package/dist/version.d.ts +2 -2
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,50 @@ 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
|
+
## 0.13.1
|
|
8
|
+
|
|
9
|
+
- **A connected team's thread is never answered after the run's grant ended.**
|
|
10
|
+
A venue session that has already expired (or has less than 30 seconds left)
|
|
11
|
+
is refused when it opens, and the grant is checked again right before
|
|
12
|
+
Claude Code starts. A run that waited in the queue past its grant is never
|
|
13
|
+
started; it is closed as failed with its own token while the token still
|
|
14
|
+
works, so the thread's thinking indicator clears. A `--dry-run` only logs
|
|
15
|
+
that it would.
|
|
16
|
+
- **The grant is timed on both of this machine's clocks**, from the server's
|
|
17
|
+
own remaining lifetime (`expires_in_seconds`) counted from when the session
|
|
18
|
+
was asked for, and whichever runs out first ends it. A wound-back system
|
|
19
|
+
clock can't lengthen it, and neither can a sleep the monotonic clock doesn't
|
|
20
|
+
count (Linux): a venue run is re-checked at least every 15 seconds.
|
|
21
|
+
- **A venue run whose grant is ending is stopped and closed with its own
|
|
22
|
+
token** while the 30-second margin still leaves it good — queued or already
|
|
23
|
+
working — instead of being dropped locally and left to the server's
|
|
24
|
+
stale-run sweep. Nothing it was saying is posted.
|
|
25
|
+
|
|
26
|
+
## 0.13.0
|
|
27
|
+
|
|
28
|
+
- **An agent can answer in a connected team's thread without its own key.**
|
|
29
|
+
When ProhostAI hands this agent a run in a thread that belongs to a
|
|
30
|
+
connected team's workspace, the event carries none of that thread. The CLI
|
|
31
|
+
opens the run's venue session over its own connection to ProhostAI (there is
|
|
32
|
+
no HTTP route to it), which returns the thread and a token minted for that
|
|
33
|
+
one run. It uses the token, not this machine's key, for every request the
|
|
34
|
+
run makes, and keeps it in memory only. The run's progress line and failure
|
|
35
|
+
report are fixed text, so nothing from that thread lands in this
|
|
36
|
+
workspace's run history or in this machine's log. ProhostAI hands such a
|
|
37
|
+
run only to the one connection that holds this pairing: if a second process
|
|
38
|
+
connects with the same key, the run is declined rather than sent to either.
|
|
39
|
+
- The run is answered by a Claude Code session started from your `--exec`
|
|
40
|
+
binary and model only. It has no tools, no MCP servers, no hooks, plugins,
|
|
41
|
+
settings or `CLAUDE.md` (`--safe-mode --setting-sources '' --settings '{}'`),
|
|
42
|
+
and writes no transcript (`--no-session-persistence`). Its environment
|
|
43
|
+
contains only the shell basics and Claude Code's own login, and it runs in an
|
|
44
|
+
empty directory of its own. The other team's messages never reach a session
|
|
45
|
+
that could act on this workspace.
|
|
46
|
+
- The CLI tells ProhostAI it can do this (`capabilities: ["venue_run_token"]`
|
|
47
|
+
on its status frame) only when it drives a Claude Code that has all of those
|
|
48
|
+
flags, on macOS or Linux. ProhostAI sends such runs to no other agent. Codex
|
|
49
|
+
and plain `--exec` commands are unchanged and never receive one.
|
|
50
|
+
|
|
7
51
|
## 0.12.4
|
|
8
52
|
|
|
9
53
|
- Refuse redirects on credentialed API writes and pairing requests, so a validated endpoint cannot replay keys or pairing codes to another destination. Direct HTTPS and local-loopback requests keep working.
|
package/README.md
CHANGED
|
@@ -663,6 +663,27 @@ Other agents: the CLI shapes no command but Claude Code's and Codex's, so give
|
|
|
663
663
|
yours its own equivalent flag — whatever your runner calls it — and apply the
|
|
664
664
|
same judgement.
|
|
665
665
|
|
|
666
|
+
**Threads in a connected team's workspace are the exception.** When a
|
|
667
|
+
connected team addresses your agent in one of *their* threads, the prompt is
|
|
668
|
+
another workspace's text, so the run is answered without any of the above. The
|
|
669
|
+
event carries none of the thread: the CLI opens the run's venue session over
|
|
670
|
+
its own connection to ProhostAI (no HTTP route, so a process that only holds
|
|
671
|
+
your key cannot ask the same way), which returns the thread and a token of its
|
|
672
|
+
own that can only read and post in that one thread and report on that one run.
|
|
673
|
+
ProhostAI sends such a run only while this connection is the only one holding
|
|
674
|
+
the pairing, and opens its session only on the connection it went to: another
|
|
675
|
+
process connecting with your key makes the run decline, never receive it. The CLI uses it instead of your key and
|
|
676
|
+
keeps it in memory only. Claude Code is started from your `--exec` binary and
|
|
677
|
+
model only, with `--tools ''`, `--safe-mode`, `--setting-sources ''
|
|
678
|
+
--settings '{}'`, an empty `--strict-mcp-config` and
|
|
679
|
+
`--no-session-persistence`: no tools, no MCP servers, no hooks, plugins,
|
|
680
|
+
settings or `CLAUDE.md`, no transcript on disk. Its environment holds only the
|
|
681
|
+
shell basics and Claude Code's own login, and it runs in an empty directory of
|
|
682
|
+
its own. The agent answers from the thread it is sent; the CLI posts the reply.
|
|
683
|
+
Only a Claude Code that has those flags, on macOS or Linux, tells ProhostAI it
|
|
684
|
+
can do this, and ProhostAI sends these runs to no other agent — Codex and plain
|
|
685
|
+
commands never get one.
|
|
686
|
+
|
|
666
687
|
### `agent run` flags
|
|
667
688
|
|
|
668
689
|
| Flag | Default | What it does |
|
package/dist/agent/contract.d.ts
CHANGED
|
@@ -339,6 +339,36 @@ export interface AgentRunRequest {
|
|
|
339
339
|
memories?: RunMemory[];
|
|
340
340
|
/** Selected records omitted whole by the server's inline context budget. */
|
|
341
341
|
memories_omitted?: number;
|
|
342
|
+
/**
|
|
343
|
+
* Set when the conversation belongs to a CONNECTED team's workspace rather
|
|
344
|
+
* than this agent's own (see `venue.ts`). Absent on every other run.
|
|
345
|
+
*/
|
|
346
|
+
venue?: RunVenue;
|
|
347
|
+
/**
|
|
348
|
+
* The run's own credential, set by the harness once it opened the venue
|
|
349
|
+
* session — never read from the event. The one thing that may read and post
|
|
350
|
+
* in that thread and heartbeat, stream and complete the run. Kept in memory
|
|
351
|
+
* only, and never handed to the agent.
|
|
352
|
+
*/
|
|
353
|
+
venue_run_token?: string;
|
|
354
|
+
/**
|
|
355
|
+
* When the venue run's grant ends, on this process's monotonic clock
|
|
356
|
+
* (`performance.now()`): the server's remaining lifetime counted from the
|
|
357
|
+
* moment the session was asked for, so a wrong wall clock cannot extend it.
|
|
358
|
+
*/
|
|
359
|
+
venue_deadline_ms?: number;
|
|
360
|
+
/**
|
|
361
|
+
* The same deadline on the wall clock (`Date.now()`): the server's lifetime
|
|
362
|
+
* counted from the wall-clock moment the session was asked for. It keeps
|
|
363
|
+
* counting through a suspend, which the monotonic clock may not.
|
|
364
|
+
*/
|
|
365
|
+
venue_wall_deadline_ms?: number;
|
|
366
|
+
}
|
|
367
|
+
/** The connected team's thread a venue run answers in. */
|
|
368
|
+
export interface RunVenue {
|
|
369
|
+
account_id?: string;
|
|
370
|
+
conversation_id?: string;
|
|
371
|
+
team_connection_id?: string;
|
|
342
372
|
}
|
|
343
373
|
/** Reply destinations the server can bind a run to, including future surfaces. */
|
|
344
374
|
export type ReplySurface = 'conversation' | 'task' | 'cleaning' | 'none' | (string & {});
|
package/dist/agent/contract.js
CHANGED
|
@@ -386,5 +386,16 @@ export function parseRunRequest(data) {
|
|
|
386
386
|
recent_messages: contextList(data.recent_messages ?? context.recent_messages, parseMessage),
|
|
387
387
|
memories: contextList(data.memories ?? context.memories, parseMemory),
|
|
388
388
|
memories_omitted: omittedMemoryCount(data.memories_omitted ?? context.memories_omitted),
|
|
389
|
+
// Any object counts as a venue, so a run the server marked as one is never
|
|
390
|
+
// mistaken for a run at home — even if a field inside it is malformed.
|
|
391
|
+
...(data.venue && typeof data.venue === 'object' && !Array.isArray(data.venue)
|
|
392
|
+
? {
|
|
393
|
+
venue: {
|
|
394
|
+
account_id: str(obj(data.venue).account_id),
|
|
395
|
+
conversation_id: str(obj(data.venue).conversation_id),
|
|
396
|
+
team_connection_id: str(obj(data.venue).team_connection_id),
|
|
397
|
+
},
|
|
398
|
+
}
|
|
399
|
+
: {}),
|
|
389
400
|
};
|
|
390
401
|
}
|
package/dist/agent/exec.d.ts
CHANGED
|
@@ -43,6 +43,12 @@ export interface ExecOptions {
|
|
|
43
43
|
cwd?: string;
|
|
44
44
|
/** Extra environment for the child, merged over ``process.env``. */
|
|
45
45
|
env?: NodeJS.ProcessEnv;
|
|
46
|
+
/**
|
|
47
|
+
* ``false``: the child gets {@link env} and nothing else — not
|
|
48
|
+
* ``process.env``. A venue session's environment is built by dropping what
|
|
49
|
+
* it must not see, and merging the harness's own back over it would undo that.
|
|
50
|
+
*/
|
|
51
|
+
inheritEnv?: boolean;
|
|
46
52
|
/**
|
|
47
53
|
* Ends the run early — a person pressed stop, or the server closed the run.
|
|
48
54
|
*
|
package/dist/agent/exec.js
CHANGED
|
@@ -103,7 +103,11 @@ export async function execAgent(options) {
|
|
|
103
103
|
const spawnOptions = {
|
|
104
104
|
shell: true,
|
|
105
105
|
cwd: options.cwd,
|
|
106
|
-
env: options.
|
|
106
|
+
env: options.inheritEnv === false
|
|
107
|
+
? { ...options.env }
|
|
108
|
+
: options.env
|
|
109
|
+
? { ...process.env, ...options.env }
|
|
110
|
+
: process.env,
|
|
107
111
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
108
112
|
// Own process group. `--exec 'foo | bar'` runs under a shell, and
|
|
109
113
|
// signalling the shell alone leaves its children holding our stdio
|
|
@@ -44,6 +44,19 @@ export type Scheduler = (run: () => void, ms: number) => ScheduledTick;
|
|
|
44
44
|
export interface HeartbeatTarget {
|
|
45
45
|
/** Where the heartbeat POSTs — see `heartbeatPathFor`. */
|
|
46
46
|
path: string;
|
|
47
|
+
/**
|
|
48
|
+
* The credential this run's beats go out with, when it is not the loop's
|
|
49
|
+
* own: a venue run is heartbeated with its run token (see `venue.ts`).
|
|
50
|
+
*/
|
|
51
|
+
api?: ApiOptions;
|
|
52
|
+
/**
|
|
53
|
+
* A venue run (see `venue.ts`): its token's authority ends with the run's
|
|
54
|
+
* grant, so a refusal that means "this token is no longer good" — 401, 403,
|
|
55
|
+
* 404 — ends the run here as if the server had closed it, rather than
|
|
56
|
+
* leaving the agent working on a connected team's thread it may no longer
|
|
57
|
+
* answer.
|
|
58
|
+
*/
|
|
59
|
+
venue?: boolean;
|
|
47
60
|
/**
|
|
48
61
|
* Latest one-line description of what the agent is doing, read fresh at each
|
|
49
62
|
* beat. Absent for a queued run, and for brains whose output we don't stream.
|
package/dist/agent/heartbeat.js
CHANGED
|
@@ -122,7 +122,7 @@ export class HeartbeatLoop {
|
|
|
122
122
|
*/
|
|
123
123
|
async beat(target) {
|
|
124
124
|
try {
|
|
125
|
-
const outcome = await heartbeatRun(this.options.api, target.path, target.progress?.());
|
|
125
|
+
const outcome = await heartbeatRun(target.api ?? this.options.api, target.path, target.progress?.());
|
|
126
126
|
// The run may have finished while its own heartbeat was in flight; acting
|
|
127
127
|
// on a stale answer would cancel a run that is already reporting itself.
|
|
128
128
|
if (!this.targets.has(target.runId))
|
|
@@ -135,6 +135,14 @@ export class HeartbeatLoop {
|
|
|
135
135
|
await target.onCancelRequested();
|
|
136
136
|
return;
|
|
137
137
|
}
|
|
138
|
+
if (target.venue &&
|
|
139
|
+
(outcome.kind === 'unknown' ||
|
|
140
|
+
(outcome.kind === 'error' && (outcome.status === 401 || outcome.status === 403)))) {
|
|
141
|
+
this.untrack(target.runId);
|
|
142
|
+
this.options.log(` ⏹ run ${target.runId.slice(0, 12)} lost its connected-team grant — stopping it`);
|
|
143
|
+
await target.onResolved();
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
138
146
|
if (outcome.kind === 'unknown') {
|
|
139
147
|
// Said once per run, then silence: on a server that predates
|
|
140
148
|
// heartbeats this is the answer for every run forever, and a line a
|