@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 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://www.prohost.ai) command-line tool. Three things it does:
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.
@@ -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);
@@ -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,
@@ -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.
@@ -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`). */
@@ -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
- ok: false,
766
- reason: `Claude usage limit reached on ${options.machine ?? os.hostname()}`,
767
- detail: [said, stderr.includes(said) ? '' : stderr].filter((part) => part.length > 0).join('\n'),
768
- durationMs: elapsed(),
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, { ok: false, error: detail ? `${reason}: ${detail}` : reason }, log, sleep, runtime.model());
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.1";
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.1";
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.1';
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.1",
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://www.prohost.ai",
7
+ "homepage": "https://prohost.ai",
8
8
  "bugs": {
9
9
  "email": "support@prohost.ai"
10
10
  },