@prohost/cli 0.13.1 → 0.13.2
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 +10 -0
- package/README.md +6 -3
- package/dist/agent/api.d.ts +15 -7
- package/dist/agent/api.js +6 -1
- package/dist/agent/claude.d.ts +7 -0
- package/dist/agent/claude.js +4 -0
- package/dist/agent/contract.d.ts +7 -0
- package/dist/agent/contract.js +7 -0
- package/dist/agent/run.js +17 -8
- package/dist/version.d.ts +2 -2
- package/dist/version.js +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,16 @@ 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.2
|
|
8
|
+
|
|
9
|
+
- **A run stopped by the Claude usage limit tells ProhostAI when the limit
|
|
10
|
+
lifts.** The completion now carries `failure_kind: "usage_limit"` and, when
|
|
11
|
+
Claude Code's stream said, `retry_after` (the reset time). When the limit
|
|
12
|
+
lifts within a few hours, ProhostAI runs the same request again shortly after,
|
|
13
|
+
unless someone asks again first; either way it alerts the person who asked
|
|
14
|
+
instead of leaving a note nobody is notified of. The thread keeps its session, as before, so the retry continues it. A
|
|
15
|
+
server that predates the fields ignores them.
|
|
16
|
+
|
|
7
17
|
## 0.13.1
|
|
8
18
|
|
|
9
19
|
- **A connected team's thread is never answered after the run's grant ended.**
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# `@prohost/cli`
|
|
2
2
|
|
|
3
|
-
The [ProhostAI](https://
|
|
3
|
+
The [ProhostAI](https://prohost.ai) command-line tool. Three things it does:
|
|
4
4
|
|
|
5
5
|
- **`prohost agent`** — run your own local agent as a ProhostAI teammate. Someone
|
|
6
6
|
@-mentions it in the app, it runs on your machine, and its answer is posted
|
|
@@ -233,9 +233,12 @@ and gets a reply that has forgotten asking. The CLI reads the `session_id` out
|
|
|
233
233
|
of Claude Code's JSON output, records it per conversation in
|
|
234
234
|
`~/.prohost/sessions.json`, and `--resume`s it next time. Sessions don't live
|
|
235
235
|
forever; when one is gone the run is retried once from scratch rather than
|
|
236
|
-
failing. A run stopped by your Claude usage limit is not retried: it fails
|
|
236
|
+
failing. A run stopped by your Claude usage limit is not retried here: it fails
|
|
237
237
|
with Claude Code's own message, which says when the limit resets, and the
|
|
238
|
-
conversation keeps its session for the next run.
|
|
238
|
+
conversation keeps its session for the next run. The failure also tells
|
|
239
|
+
ProhostAI when the limit lifts: if that is within a few hours, ProhostAI asks
|
|
240
|
+
the agent again shortly after (unless someone asks again first), and either way
|
|
241
|
+
it alerts whoever asked.
|
|
239
242
|
|
|
240
243
|
**It lets the agent actually do things.** Runs are invoked with
|
|
241
244
|
`--dangerously-skip-permissions`, so tool calls execute without stopping to ask.
|
package/dist/agent/api.d.ts
CHANGED
|
@@ -129,6 +129,20 @@ export type HeartbeatOutcome =
|
|
|
129
129
|
* work send none either.
|
|
130
130
|
*/
|
|
131
131
|
export declare function heartbeatRun(options: ApiOptions, path: string, progress?: string): Promise<HeartbeatOutcome>;
|
|
132
|
+
/**
|
|
133
|
+
* A run's terminal state as the completion reports it. A failure the brain's
|
|
134
|
+
* usage limit caused says so, with when the limit lifts if the brain said.
|
|
135
|
+
*/
|
|
136
|
+
export type CompletionOutcome = {
|
|
137
|
+
ok: true;
|
|
138
|
+
silent?: boolean;
|
|
139
|
+
} | {
|
|
140
|
+
ok: false;
|
|
141
|
+
error: string;
|
|
142
|
+
usageLimit?: {
|
|
143
|
+
retryAfter?: string;
|
|
144
|
+
};
|
|
145
|
+
};
|
|
132
146
|
/**
|
|
133
147
|
* Report a run's terminal state. Called for every run — including one whose
|
|
134
148
|
* command crashed — so the server never waits on a run this harness abandoned.
|
|
@@ -140,13 +154,7 @@ export declare function heartbeatRun(options: ApiOptions, path: string, progress
|
|
|
140
154
|
* ``model`` is the model id the run used, when the harness knows it. Omitted
|
|
141
155
|
* otherwise, so a plain ``--exec`` command's completion body is unchanged.
|
|
142
156
|
*/
|
|
143
|
-
export declare function completeRun(options: ApiOptions, path: string, outcome:
|
|
144
|
-
ok: true;
|
|
145
|
-
silent?: boolean;
|
|
146
|
-
} | {
|
|
147
|
-
ok: false;
|
|
148
|
-
error: string;
|
|
149
|
-
}, model?: string): Promise<ApiResult>;
|
|
157
|
+
export declare function completeRun(options: ApiOptions, path: string, outcome: CompletionOutcome, model?: string): Promise<ApiResult>;
|
|
150
158
|
/**
|
|
151
159
|
* Report a batch of tool events for a run in flight.
|
|
152
160
|
*
|
package/dist/agent/api.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* `completion.path`), which is why these take a path rather than building one.
|
|
11
11
|
*/
|
|
12
12
|
import { USER_AGENT } from '../version.js';
|
|
13
|
-
import { MAX_ERROR_CHARS, MAX_MODEL_CHARS, MAX_PROGRESS_CHARS, MAX_TOOL_EVENTS_PER_BATCH, RUN_OUTCOME_SILENT, RUN_STATUS_FAILED, RUN_STATUS_SUCCEEDED, } from './contract.js';
|
|
13
|
+
import { MAX_ERROR_CHARS, MAX_MODEL_CHARS, MAX_PROGRESS_CHARS, MAX_TOOL_EVENTS_PER_BATCH, RUN_FAILURE_USAGE_LIMIT, RUN_OUTCOME_SILENT, RUN_STATUS_FAILED, RUN_STATUS_SUCCEEDED, } from './contract.js';
|
|
14
14
|
/** Deadline for the cheap calls — a heartbeat answers in milliseconds. */
|
|
15
15
|
export const DEFAULT_TIMEOUT_MS = 30_000;
|
|
16
16
|
/**
|
|
@@ -184,6 +184,11 @@ export async function completeRun(options, path, outcome, model) {
|
|
|
184
184
|
const body = outcome.ok
|
|
185
185
|
? { status: RUN_STATUS_SUCCEEDED, ...(outcome.silent ? { outcome: RUN_OUTCOME_SILENT } : {}) }
|
|
186
186
|
: { status: RUN_STATUS_FAILED, error: outcome.error.slice(0, MAX_ERROR_CHARS) };
|
|
187
|
+
if (!outcome.ok && outcome.usageLimit) {
|
|
188
|
+
body.failure_kind = RUN_FAILURE_USAGE_LIMIT;
|
|
189
|
+
if (outcome.usageLimit.retryAfter)
|
|
190
|
+
body.retry_after = outcome.usageLimit.retryAfter;
|
|
191
|
+
}
|
|
187
192
|
if (model)
|
|
188
193
|
body.model = model.slice(0, MAX_MODEL_CHARS);
|
|
189
194
|
const result = await postJson(options, path, body, undefined, false, SEND_TIMEOUT_MS);
|
package/dist/agent/claude.d.ts
CHANGED
|
@@ -199,6 +199,13 @@ export interface ClaudeStream {
|
|
|
199
199
|
* help until the limit resets, so a caller must not retry it.
|
|
200
200
|
*/
|
|
201
201
|
usageLimit(): string | undefined;
|
|
202
|
+
/**
|
|
203
|
+
* When the usage limit that stopped the turn lifts, in epoch seconds: the
|
|
204
|
+
* `resetsAt` of the rejecting `rate_limit_event`. `undefined` when no such
|
|
205
|
+
* event arrived, even if {@link usageLimit} recognised the limit from Claude
|
|
206
|
+
* Code's message alone — that message's "resets 11pm" is for people.
|
|
207
|
+
*/
|
|
208
|
+
usageLimitResetsAt(): number | undefined;
|
|
202
209
|
/**
|
|
203
210
|
* The turn's outcome. ``stdout`` is the complete captured output, used only
|
|
204
211
|
* when the stream held no `result` event — a crashed binary, a login prompt,
|
package/dist/agent/claude.js
CHANGED
|
@@ -655,6 +655,10 @@ export function readClaudeStream() {
|
|
|
655
655
|
? 'usage limit reached'
|
|
656
656
|
: `usage limit reached · resets ${new Date(resetsAt * 1000).toISOString()}`;
|
|
657
657
|
},
|
|
658
|
+
usageLimitResetsAt: () => {
|
|
659
|
+
flushTail();
|
|
660
|
+
return limitRejected?.resetsAt;
|
|
661
|
+
},
|
|
658
662
|
finish(stdout) {
|
|
659
663
|
// A binary that exits without flushing a final `\n` must not lose its
|
|
660
664
|
// result.
|
package/dist/agent/contract.d.ts
CHANGED
|
@@ -50,6 +50,13 @@ export declare const RUN_OUTCOME_SILENT = "silent";
|
|
|
50
50
|
export declare const NO_REPLY_TOKEN = "[NO_REPLY]";
|
|
51
51
|
/** Whether the agent's output is the no-reply token and nothing else. */
|
|
52
52
|
export declare function isNoReply(output: string): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Completion `failure_kind` for a run the brain's subscription usage limit
|
|
55
|
+
* stopped. Sent with `status: failed`, plus `retry_after` when the brain said
|
|
56
|
+
* when the limit lifts, so the server can retry the work then instead of
|
|
57
|
+
* leaving the question unanswered. A server that predates the field ignores it.
|
|
58
|
+
*/
|
|
59
|
+
export declare const RUN_FAILURE_USAGE_LIMIT = "usage_limit";
|
|
53
60
|
/** Server-side cap on the `error` field of a completion (`MAX_ERROR_CHARS`). */
|
|
54
61
|
export declare const MAX_ERROR_CHARS = 1000;
|
|
55
62
|
/** Server-side cap on the `model` field of a completion (`MAX_MODEL_CHARS`). */
|
package/dist/agent/contract.js
CHANGED
|
@@ -52,6 +52,13 @@ export const NO_REPLY_TOKEN = '[NO_REPLY]';
|
|
|
52
52
|
export function isNoReply(output) {
|
|
53
53
|
return output.trim().toUpperCase() === NO_REPLY_TOKEN;
|
|
54
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Completion `failure_kind` for a run the brain's subscription usage limit
|
|
57
|
+
* stopped. Sent with `status: failed`, plus `retry_after` when the brain said
|
|
58
|
+
* when the limit lifts, so the server can retry the work then instead of
|
|
59
|
+
* leaving the question unanswered. A server that predates the field ignores it.
|
|
60
|
+
*/
|
|
61
|
+
export const RUN_FAILURE_USAGE_LIMIT = 'usage_limit';
|
|
55
62
|
/** Server-side cap on the `error` field of a completion (`MAX_ERROR_CHARS`). */
|
|
56
63
|
export const MAX_ERROR_CHARS = 1000;
|
|
57
64
|
/** Server-side cap on the `model` field of a completion (`MAX_MODEL_CHARS`). */
|
package/dist/agent/run.js
CHANGED
|
@@ -759,14 +759,19 @@ venueCwd) {
|
|
|
759
759
|
* A turn the Claude subscription's usage limit stopped, worded for the
|
|
760
760
|
* thread: whose limit (this machine's login) and, in Claude Code's own
|
|
761
761
|
* words, when it resets. The only fix is waiting, so the reason says that
|
|
762
|
-
* rather than "exited 1"
|
|
762
|
+
* rather than "exited 1" — and the completion says when, so the server can
|
|
763
|
+
* run the work again then.
|
|
763
764
|
*/
|
|
764
|
-
const usageLimitFailure = (said, stderr = '') =>
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
765
|
+
const usageLimitFailure = (said, stderr = '') => {
|
|
766
|
+
const resetsAt = stream?.usageLimitResetsAt();
|
|
767
|
+
return {
|
|
768
|
+
ok: false,
|
|
769
|
+
reason: `Claude usage limit reached on ${options.machine ?? os.hostname()}`,
|
|
770
|
+
detail: [said, stderr.includes(said) ? '' : stderr].filter((part) => part.length > 0).join('\n'),
|
|
771
|
+
durationMs: elapsed(),
|
|
772
|
+
usageLimit: resetsAt === undefined ? {} : { retryAfter: new Date(resetsAt * 1000).toISOString() },
|
|
773
|
+
};
|
|
774
|
+
};
|
|
770
775
|
const failure = (result) => {
|
|
771
776
|
// A killed run has no reply, but it usually has most of one. Reporting the
|
|
772
777
|
// tail of what the agent was writing turns "no output for 15 minutes" into
|
|
@@ -1089,7 +1094,11 @@ mcp) {
|
|
|
1089
1094
|
log(` [dry-run] would report the run as failed`);
|
|
1090
1095
|
return 'skipped';
|
|
1091
1096
|
}
|
|
1092
|
-
const completed = await completeWithRetry(api, run.completion_path, {
|
|
1097
|
+
const completed = await completeWithRetry(api, run.completion_path, {
|
|
1098
|
+
ok: false,
|
|
1099
|
+
error: detail ? `${reason}: ${detail}` : reason,
|
|
1100
|
+
...(result.usageLimit ? { usageLimit: result.usageLimit } : {}),
|
|
1101
|
+
}, log, sleep, runtime.model());
|
|
1093
1102
|
if (!completed.ok) {
|
|
1094
1103
|
log(` ! could not report failure (${describeFailure(completed)})` +
|
|
1095
1104
|
`${completed.status !== 0 && completed.error ? ` ${completed.error}` : ''}`);
|
package/dist/version.d.ts
CHANGED
|
@@ -7,6 +7,6 @@
|
|
|
7
7
|
* `package.json`, so a release bump that forgets this file fails the suite
|
|
8
8
|
* instead of shipping a `User-Agent` that lies about which build is calling.
|
|
9
9
|
*/
|
|
10
|
-
export declare const CLI_VERSION = "0.13.
|
|
10
|
+
export declare const CLI_VERSION = "0.13.2";
|
|
11
11
|
/** Sent on every HTTP request the CLI makes back into ProhostAI. */
|
|
12
|
-
export declare const USER_AGENT = "prohost-cli/0.13.
|
|
12
|
+
export declare const USER_AGENT = "prohost-cli/0.13.2";
|
package/dist/version.js
CHANGED
|
@@ -7,6 +7,6 @@
|
|
|
7
7
|
* `package.json`, so a release bump that forgets this file fails the suite
|
|
8
8
|
* instead of shipping a `User-Agent` that lies about which build is calling.
|
|
9
9
|
*/
|
|
10
|
-
export const CLI_VERSION = '0.13.
|
|
10
|
+
export const CLI_VERSION = '0.13.2';
|
|
11
11
|
/** Sent on every HTTP request the CLI makes back into ProhostAI. */
|
|
12
12
|
export const USER_AGENT = `prohost-cli/${CLI_VERSION}`;
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prohost/cli",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.2",
|
|
4
4
|
"description": "Run your own AI agent as a ProhostAI teammate, and stream your account's webhooks to your laptop.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
|
-
"homepage": "https://
|
|
7
|
+
"homepage": "https://prohost.ai",
|
|
8
8
|
"bugs": {
|
|
9
9
|
"email": "support@prohost.ai"
|
|
10
10
|
},
|