@prohost/cli 0.16.6 → 0.16.8

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.
@@ -89,6 +89,16 @@ const SAFE_ID = /^[A-Za-z0-9-]{1,64}$/;
89
89
  export function busyBackoffMs(waits) {
90
90
  return Math.min(30_000 * 2 ** waits, 120_000);
91
91
  }
92
+ /** A refused status is logged again at most this often while refusals continue. */
93
+ export const STATUS_FAILURE_LOG_MS = 10 * 60_000;
94
+ /** A server's error text with anything shaped like a key or token blanked out. */
95
+ export function redactSecrets(text) {
96
+ return text
97
+ .replace(/\b(pk|sk|rk|whsec)_[A-Za-z0-9_-]{8,}/g, '$1_…')
98
+ .replace(/\bsk-ant-[A-Za-z0-9_-]{8,}/g, 'sk-ant-…')
99
+ .replace(/\bgh[pousr]_[A-Za-z0-9]{8,}/g, 'gh…')
100
+ .replace(/\b(Bearer|token)\s+[A-Za-z0-9._-]{12,}/gi, '$1 …');
101
+ }
92
102
  function timestamp() {
93
103
  return new Date().toISOString().slice(11, 19);
94
104
  }
@@ -260,12 +270,15 @@ async function serveHost(options) {
260
270
  // ── What is running ──────────────────────────────────────────────────────
261
271
  function runningEntries() {
262
272
  const runs = [...active.values()].map((run) => ({ ...run.entry, peak_mem_mb: peak.peakMb(run.entry.run_id) }));
273
+ // A terminal session is no employee's: no employee id (its runtime instead), and no start time
274
+ // this host knows (`started_at: null`, never '' — the server reads both as typed values).
263
275
  const terms = (codeHost?.liveTerminals?.() ?? []).map((t) => ({
264
276
  run_id: t.terminalId,
265
- agent_id: t.agent,
277
+ agent_id: null,
278
+ runtime: t.agent,
266
279
  conversation_key: `code_thread:${t.codeThreadId}`,
267
280
  kind: 'terminal',
268
- started_at: '',
281
+ started_at: null,
269
282
  }));
270
283
  return [...runs, ...terms];
271
284
  }
@@ -326,6 +339,8 @@ async function serveHost(options) {
326
339
  let statusTimer;
327
340
  let statusInFlight;
328
341
  let statusFailures = 0;
342
+ /** When a refused status was last logged: a repeat is logged again (with its count) at most this often. */
343
+ let statusFailureLoggedAt = 0;
329
344
  function statusChanged() {
330
345
  writeLocalState();
331
346
  if (statusTimer)
@@ -374,8 +389,14 @@ async function serveHost(options) {
374
389
  return;
375
390
  }
376
391
  statusFailures += 1;
377
- if (statusFailures === 1)
378
- log(`${timestamp()} ! status not accepted (${response.status || response.error})`);
392
+ // The server's reason (a 422 names the field), at the first refusal and then every 10 minutes
393
+ // while it lasts, with how many there were — never only once and then silence.
394
+ if (statusFailures === 1 || now() - statusFailureLoggedAt >= STATUS_FAILURE_LOG_MS) {
395
+ statusFailureLoggedAt = now();
396
+ const why = redactSecrets(response.error ?? '').replace(/\s+/g, ' ').trim().slice(0, 500);
397
+ log(`${timestamp()} ! status not accepted (${response.status || 'no answer'}${statusFailures > 1 ? `, ${statusFailures} times in a row` : ''})` +
398
+ (why ? `: ${why}` : ''));
399
+ }
379
400
  })().finally(() => {
380
401
  statusInFlight = undefined;
381
402
  });
@@ -34,13 +34,17 @@ export declare function parseConfig(body: Record<string, unknown> | undefined, p
34
34
  /** One run going on this computer. */
35
35
  export interface RunningEntry {
36
36
  run_id: string;
37
- agent_id: string;
37
+ /** The employee's id (a UUID); null for a terminal session, which is no employee's. */
38
+ agent_id: string | null;
39
+ /** A terminal session's agent runtime (`claude_code`, `codex`, …). */
40
+ runtime?: string;
38
41
  agent_name?: string;
39
42
  agent_avatar_url?: string;
40
43
  title?: string;
41
44
  conversation_key?: string;
42
45
  kind: 'employee' | 'terminal';
43
- started_at: string;
46
+ /** ISO-8601, or null when it isn't known (a terminal session). */
47
+ started_at: string | null;
44
48
  peak_mem_mb?: number;
45
49
  started_by_user_id?: string;
46
50
  }
@@ -69,16 +69,32 @@ export function hostStatsBody(stats) {
69
69
  battery_pct: stats?.batteryPct ?? null,
70
70
  };
71
71
  }
72
+ function clip(value, max) {
73
+ if (value === null || value === undefined)
74
+ return null;
75
+ // By code point, as the server counts (`len()` of a Python str): never half a surrogate pair,
76
+ // which it would refuse as invalid Unicode.
77
+ const points = Array.from(value);
78
+ return points.length > max ? points.slice(0, max).join('') : value;
79
+ }
72
80
  /** A login as the status body names it: no email, no directory. */
73
81
  export function loginBody(login, conversations) {
74
82
  return {
75
83
  account_key: login.account_key,
76
- label: login.label,
77
- runtime: login.runtime,
78
- subscription_type: login.subscription_type,
79
- plan_label: login.plan_label,
84
+ label: clip(login.label, 120) || 'login',
85
+ runtime: clip(login.runtime, 40),
86
+ subscription_type: clip(login.subscription_type, 40),
87
+ plan_label: clip(login.plan_label, 80),
80
88
  auth_state: login.auth_state,
81
- quota_windows: login.quota_windows.map((w) => ({ ...w })),
89
+ quota_windows: login.quota_windows
90
+ .filter((w) => typeof w.kind === 'string' && w.kind.length > 0)
91
+ .slice(0, 16)
92
+ .map((w) => ({
93
+ kind: clip(w.kind, 40),
94
+ used_percent: Math.min(100, Math.max(0, w.used_percent)),
95
+ resets_at: w.resets_at,
96
+ ...(w.model_label ? { model_label: clip(w.model_label, 40) } : {}),
97
+ })),
82
98
  conversations,
83
99
  };
84
100
  }
@@ -89,11 +105,13 @@ export function buildStatusBody(inputs) {
89
105
  slots: { ...inputs.slots },
90
106
  logins: inputs.logins.map((login) => loginBody(login, inputs.conversationsPerLogin.get(login.account_key) ?? 0)),
91
107
  running: inputs.running.map((entry) => ({
92
- run_id: entry.run_id,
108
+ run_id: clip(entry.run_id, 128),
93
109
  agent_id: entry.agent_id,
94
- conversation_key: entry.conversation_key ?? null,
110
+ ...(entry.runtime ? { runtime: clip(entry.runtime, 40) } : {}),
111
+ conversation_key: clip(entry.conversation_key, 256),
95
112
  kind: entry.kind,
96
- started_at: entry.started_at,
113
+ // Never '' (the server reads it as a date): a time, or null.
114
+ started_at: entry.started_at || null,
97
115
  peak_mem_mb: entry.peak_mem_mb ?? null,
98
116
  ...(entry.started_by_user_id ? { started_by_user_id: entry.started_by_user_id } : {}),
99
117
  })),
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.16.6";
10
+ export declare const CLI_VERSION = "0.16.8";
11
11
  /** Sent on every HTTP request the CLI makes back into ProhostAI. */
12
- export declare const USER_AGENT = "prohost-cli/0.16.6";
12
+ export declare const USER_AGENT = "prohost-cli/0.16.8";
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.16.6';
10
+ export const CLI_VERSION = '0.16.8';
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@prohost/cli",
3
- "version": "0.16.6",
3
+ "version": "0.16.8",
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",