@prohost/cli 0.8.1 → 0.8.3

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,37 @@ 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.8.3
8
+
9
+ A paired agent's shell steps read as what they do, not "Running a command".
10
+
11
+ - **Shell calls report their description.** A `Bash` call's tool event now
12
+ carries Claude Code's own one-line `description` of the call beside the
13
+ command in `args_preview` (`{"command": …, "description": "Find the
14
+ handler"}`). The server uses it as the step's label in the live run view and
15
+ keeps "Running a command" when it is absent. Nothing else in the event
16
+ changes.
17
+
18
+ ## 0.8.2
19
+
20
+ A ProhostAI back-office agent is told its cross-account reads are allowed.
21
+
22
+ - **Back-office read tools change the credential sentence.** Before each run
23
+ whose ProhostAI tools are wired, the harness asks the MCP server's own
24
+ `tools/list` (cached 10 minutes; a failure reads as "absent" and is retried
25
+ after a minute). When it lists both `backoffice_describe_readonly_schema` and
26
+ `backoffice_query_readonly` — shown only to an agent-bound key on ProhostAI's
27
+ internal account holding `backoffice:read` — and a read-only call to the
28
+ schema tool goes through (its rollout flag is enforced only at call time),
29
+ the prompt replaces "your
30
+ credential is deliberately narrow" with: read-only cross-account
31
+ investigation is pre-authorized, look before answering (schema, then a query
32
+ filtered to the customer's account), never ask a human before a read-only
33
+ query, writes and customer-facing sends still follow approval discipline,
34
+ and summarize rather than paste rows. Ungated runs are also told that
35
+ read-only tools on the machine (operator-added MCP servers, CLIs) serve the
36
+ same purpose. Every other agent's prompt is byte-for-byte unchanged.
37
+
7
38
  ## 0.8.1
8
39
 
9
40
  A long message no longer reaches a paired agent as a fragment it can't recover.
package/README.md CHANGED
@@ -172,6 +172,12 @@ reservations or listings — those tools return a permission error. The prompt
172
172
  tells the agent to say what it couldn't reach rather than answer as if it had,
173
173
  and refused tools are printed in the run log.
174
174
 
175
+ The one exception is a ProhostAI back-office agent. Before a run, the CLI asks
176
+ the MCP server's `tools/list` (cached for ten minutes). If the list includes the
177
+ back-office read tools, which only an internal key with `backoffice:read` sees,
178
+ and a read-only call to the schema tool is allowed, the prompt tells the agent that read-only cross-account investigation is
179
+ pre-authorized. Writes and customer-facing sends still go through approval.
180
+
175
181
  **It reads the reply out of JSON.** The run is invoked with
176
182
  `--output-format json`, and the `result` field is what gets posted. If that
177
183
  output isn't JSON — a login prompt, a crash banner — the run is reported failed
@@ -0,0 +1,59 @@
1
+ /**
2
+ * What the paired credential can reach, as far as the prompt needs to know.
3
+ *
4
+ * Today that is one question: does this agent hold ProhostAI's back-office
5
+ * read tools (`backoffice_describe_readonly_schema` + `backoffice_query_readonly`)?
6
+ * The answer changes what the prompt says — an agent told its credential is
7
+ * "deliberately narrow" refuses or hedges on exactly the cross-account
8
+ * investigations those tools exist for.
9
+ *
10
+ * Nothing the CLI stores can answer it. The pairing response carries no scopes,
11
+ * and the gate is not a scope alone: the server shows the tools only to an
12
+ * agent-bound key on ProhostAI's internal account, holding the literal
13
+ * `backoffice:read` scope, with a flag on — any of which can change after
14
+ * pairing. So the server is asked: `tools/list` first, and — because its
15
+ * visibility check leaves out the rollout flag, which is enforced only when a
16
+ * tool is called — then the read-only, side-effect-free
17
+ * `backoffice_describe_readonly_schema`, which runs the whole gate. Two small
18
+ * requests, cached, and every failure reads as "absent": the prompt then says
19
+ * what it has always said.
20
+ */
21
+ /** Both have to be listed — the schema call is what makes the query usable. */
22
+ export declare const BACKOFFICE_READ_TOOLS: readonly ["backoffice_describe_readonly_schema", "backoffice_query_readonly"];
23
+ export interface RunCapabilities {
24
+ /** The back-office read tools are in this credential's `tools/list`. */
25
+ backofficeRead: boolean;
26
+ }
27
+ export declare const NO_CAPABILITIES: RunCapabilities;
28
+ /** Short: a run waits on this before its agent starts. */
29
+ export declare const CAPABILITY_PROBE_TIMEOUT_MS = 5000;
30
+ /** How long an answer is reused. A grant or revoke shows up within this. */
31
+ export declare const CAPABILITY_CACHE_MS: number;
32
+ /** How long a failed probe is reused, so an outage costs one timeout a minute, not one per run. */
33
+ export declare const CAPABILITY_FAILURE_CACHE_MS = 60000;
34
+ export interface ToolListOptions {
35
+ mcpUrl: string;
36
+ apiKey: string;
37
+ fetchImpl?: typeof fetch;
38
+ timeoutMs?: number;
39
+ }
40
+ /** The tool names the server lists for this key, or `null` when it could not be asked. */
41
+ export declare function listMcpToolNames(options: ToolListOptions): Promise<string[] | null>;
42
+ /**
43
+ * Whether a back-office read actually goes through for this key: `true` when
44
+ * the schema call answers with its schema, `false` when the server refuses it
45
+ * (`backoffice_forbidden` — e.g. the rollout flag is off), `null` when it could
46
+ * not be asked.
47
+ */
48
+ export declare function backofficeReadAllowed(options: ToolListOptions): Promise<boolean | null>;
49
+ export declare function capabilitiesFromToolNames(names: readonly string[]): RunCapabilities;
50
+ /**
51
+ * A cached `tools/list` probe for one daemon's credential.
52
+ *
53
+ * A daemon is long-lived, so the answer is re-asked every
54
+ * {@link CAPABILITY_CACHE_MS} rather than fixed at start-up: a scope granted or
55
+ * revoked on the server reaches the prompt without a restart.
56
+ */
57
+ export declare function capabilityProbe(options: ToolListOptions & {
58
+ now?: () => number;
59
+ }): () => Promise<RunCapabilities>;
@@ -0,0 +1,158 @@
1
+ /**
2
+ * What the paired credential can reach, as far as the prompt needs to know.
3
+ *
4
+ * Today that is one question: does this agent hold ProhostAI's back-office
5
+ * read tools (`backoffice_describe_readonly_schema` + `backoffice_query_readonly`)?
6
+ * The answer changes what the prompt says — an agent told its credential is
7
+ * "deliberately narrow" refuses or hedges on exactly the cross-account
8
+ * investigations those tools exist for.
9
+ *
10
+ * Nothing the CLI stores can answer it. The pairing response carries no scopes,
11
+ * and the gate is not a scope alone: the server shows the tools only to an
12
+ * agent-bound key on ProhostAI's internal account, holding the literal
13
+ * `backoffice:read` scope, with a flag on — any of which can change after
14
+ * pairing. So the server is asked: `tools/list` first, and — because its
15
+ * visibility check leaves out the rollout flag, which is enforced only when a
16
+ * tool is called — then the read-only, side-effect-free
17
+ * `backoffice_describe_readonly_schema`, which runs the whole gate. Two small
18
+ * requests, cached, and every failure reads as "absent": the prompt then says
19
+ * what it has always said.
20
+ */
21
+ import { USER_AGENT } from '../version.js';
22
+ /** Both have to be listed — the schema call is what makes the query usable. */
23
+ export const BACKOFFICE_READ_TOOLS = ['backoffice_describe_readonly_schema', 'backoffice_query_readonly'];
24
+ export const NO_CAPABILITIES = { backofficeRead: false };
25
+ /** Short: a run waits on this before its agent starts. */
26
+ export const CAPABILITY_PROBE_TIMEOUT_MS = 5_000;
27
+ /** How long an answer is reused. A grant or revoke shows up within this. */
28
+ export const CAPABILITY_CACHE_MS = 10 * 60_000;
29
+ /** How long a failed probe is reused, so an outage costs one timeout a minute, not one per run. */
30
+ export const CAPABILITY_FAILURE_CACHE_MS = 60_000;
31
+ /** Cap on the body read — a full catalog is well under this. */
32
+ const MAX_BODY_CHARS = 2_000_000;
33
+ /**
34
+ * Pull the JSON-RPC result out of a response that may be plain JSON or a
35
+ * streamable-HTTP SSE stream (`data: {...}` lines). `undefined` when neither
36
+ * shape holds a result.
37
+ */
38
+ function jsonRpcResult(body) {
39
+ const candidates = body.trimStart().startsWith('{')
40
+ ? [body]
41
+ : body
42
+ .split(/\r?\n/)
43
+ .filter((line) => line.startsWith('data:'))
44
+ .map((line) => line.slice('data:'.length).trim());
45
+ for (const candidate of candidates) {
46
+ try {
47
+ const parsed = JSON.parse(candidate);
48
+ if (parsed && typeof parsed.result === 'object' && parsed.result !== null) {
49
+ return parsed.result;
50
+ }
51
+ }
52
+ catch {
53
+ // Not JSON — keep looking.
54
+ }
55
+ }
56
+ return undefined;
57
+ }
58
+ /**
59
+ * One JSON-RPC request to the ProhostAI MCP server; its `result`, or `null`
60
+ * when it could not be asked (network, timeout, non-2xx, unparseable, error).
61
+ *
62
+ * Authenticates exactly as the brain's own MCP config does (`X-API-Key`), so
63
+ * the answer is what the agent itself will see. The server runs stateless
64
+ * HTTP, so no `initialize` round-trip is needed first.
65
+ */
66
+ async function mcpRequest(options, method, params) {
67
+ const fetchImpl = options.fetchImpl ?? fetch;
68
+ const controller = new AbortController();
69
+ const timer = setTimeout(() => controller.abort(), options.timeoutMs ?? CAPABILITY_PROBE_TIMEOUT_MS);
70
+ try {
71
+ const response = await fetchImpl(options.mcpUrl, {
72
+ method: 'POST',
73
+ headers: {
74
+ 'Content-Type': 'application/json',
75
+ Accept: 'application/json, text/event-stream',
76
+ 'X-API-Key': options.apiKey,
77
+ 'User-Agent': USER_AGENT,
78
+ },
79
+ body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }),
80
+ signal: controller.signal,
81
+ });
82
+ if (response.status < 200 || response.status >= 300)
83
+ return null;
84
+ return jsonRpcResult((await response.text()).slice(0, MAX_BODY_CHARS)) ?? null;
85
+ }
86
+ catch {
87
+ return null;
88
+ }
89
+ finally {
90
+ clearTimeout(timer);
91
+ }
92
+ }
93
+ /** The tool names the server lists for this key, or `null` when it could not be asked. */
94
+ export async function listMcpToolNames(options) {
95
+ const tools = (await mcpRequest(options, 'tools/list', {}))?.tools;
96
+ if (!Array.isArray(tools))
97
+ return null;
98
+ return tools
99
+ .map((tool) => (tool && typeof tool === 'object' ? tool.name : undefined))
100
+ .filter((name) => typeof name === 'string');
101
+ }
102
+ /**
103
+ * Whether a back-office read actually goes through for this key: `true` when
104
+ * the schema call answers with its schema, `false` when the server refuses it
105
+ * (`backoffice_forbidden` — e.g. the rollout flag is off), `null` when it could
106
+ * not be asked.
107
+ */
108
+ export async function backofficeReadAllowed(options) {
109
+ const result = await mcpRequest(options, 'tools/call', {
110
+ name: 'backoffice_describe_readonly_schema',
111
+ arguments: {},
112
+ });
113
+ if (!result)
114
+ return null;
115
+ if (result.isError === true)
116
+ return false;
117
+ const content = Array.isArray(result.content) ? result.content : [];
118
+ const text = content
119
+ .map((part) => (part && typeof part === 'object' ? part.text : undefined))
120
+ .filter((part) => typeof part === 'string')
121
+ .join('');
122
+ try {
123
+ const parsed = JSON.parse(text);
124
+ return parsed.error === undefined && parsed.tables !== undefined;
125
+ }
126
+ catch {
127
+ return false;
128
+ }
129
+ }
130
+ export function capabilitiesFromToolNames(names) {
131
+ return { backofficeRead: BACKOFFICE_READ_TOOLS.every((tool) => names.includes(tool)) };
132
+ }
133
+ /**
134
+ * A cached `tools/list` probe for one daemon's credential.
135
+ *
136
+ * A daemon is long-lived, so the answer is re-asked every
137
+ * {@link CAPABILITY_CACHE_MS} rather than fixed at start-up: a scope granted or
138
+ * revoked on the server reaches the prompt without a restart.
139
+ */
140
+ export function capabilityProbe(options) {
141
+ const now = options.now ?? Date.now;
142
+ let cached;
143
+ return async () => {
144
+ if (cached && now() < cached.until)
145
+ return cached.value;
146
+ const names = await listMcpToolNames(options);
147
+ let value = names ? capabilitiesFromToolNames(names) : NO_CAPABILITIES;
148
+ let answered = names !== null;
149
+ if (value.backofficeRead) {
150
+ // Listed is not the same as allowed: confirm the gate end to end.
151
+ const allowed = await backofficeReadAllowed(options);
152
+ answered = allowed !== null;
153
+ value = { backofficeRead: allowed === true };
154
+ }
155
+ cached = { value, until: now() + (answered ? CAPABILITY_CACHE_MS : CAPABILITY_FAILURE_CACHE_MS) };
156
+ return value;
157
+ };
158
+ }
@@ -358,8 +358,16 @@ export function toolArgsPreview(name, input) {
358
358
  for (const key of headline) {
359
359
  const value = args[key];
360
360
  const text = typeof value === 'string' ? preview(value) : undefined;
361
- if (text)
362
- return JSON.stringify({ [key]: text });
361
+ if (!text)
362
+ continue;
363
+ // The tool's own one-line summary of the call ("Find the failing test"),
364
+ // which the server prefers over the generic "Running a command" label.
365
+ // Capped shorter than the headline so the object stays under the
366
+ // server's 500-char preview bound and still parses there.
367
+ const description = DESCRIBED_TOOLS.has(name) && typeof args.description === 'string'
368
+ ? preview(args.description)?.slice(0, MAX_PREVIEW_VALUE_CHARS)
369
+ : undefined;
370
+ return JSON.stringify(description ? { [key]: text, description } : { [key]: text });
363
371
  }
364
372
  // A headline-only tool with no headline shows nothing rather than its
365
373
  // whole input — that input is the file content this field exists to keep
@@ -410,6 +418,8 @@ const HEADLINE_FIELDS = {
410
418
  Task: ['description'],
411
419
  Agent: ['description'],
412
420
  };
421
+ /** Tools whose input carries a human `description` of the call alongside the headline. */
422
+ const DESCRIBED_TOOLS = new Set(['Bash', 'shell']);
413
423
  /** The text of a `tool_result` block — a string, or its `text` parts joined. */
414
424
  function toolResultText(content) {
415
425
  if (typeof content === 'string')
@@ -325,6 +325,8 @@ export interface AgentRunRequest {
325
325
  recent_messages?: RunMessage[];
326
326
  /** What the account remembers that bears on this run. Absent on older servers. */
327
327
  memories?: RunMemory[];
328
+ /** Selected records omitted whole by the server's inline context budget. */
329
+ memories_omitted?: number;
328
330
  }
329
331
  /** Reply destinations the server can bind a run to, including future surfaces. */
330
332
  export type ReplySurface = 'conversation' | 'task' | 'cleaning' | 'none' | (string & {});
@@ -280,6 +280,11 @@ function parseMemory(entry) {
280
280
  return null;
281
281
  return { content, scope: str(entry.scope) };
282
282
  }
283
+ function omittedMemoryCount(value) {
284
+ return typeof value === 'number' && Number.isSafeInteger(value) && value > 0
285
+ ? value
286
+ : undefined;
287
+ }
283
288
  /**
284
289
  * Extract the event body from a websocket frame's payload.
285
290
  *
@@ -368,5 +373,6 @@ export function parseRunRequest(data) {
368
373
  // CLI cannot be redeployed to follow.
369
374
  recent_messages: contextList(data.recent_messages ?? context.recent_messages, parseMessage),
370
375
  memories: contextList(data.memories ?? context.memories, parseMemory),
376
+ memories_omitted: omittedMemoryCount(data.memories_omitted ?? context.memories_omitted),
371
377
  };
372
378
  }
@@ -30,6 +30,17 @@ export interface PromptContext {
30
30
  * to guess at it.
31
31
  */
32
32
  mcpToolPattern?: string;
33
+ /**
34
+ * The ProhostAI tools include the back-office read pair
35
+ * (`backoffice_describe_readonly_schema` → `backoffice_query_readonly`).
36
+ *
37
+ * Taken from the server's own `tools/list` for this key (see
38
+ * `capabilities.ts`), never assumed. It changes what the prompt says about
39
+ * the credential: an agent told its access is "deliberately narrow" refused
40
+ * or asked permission for exactly the cross-account reads these tools
41
+ * pre-authorize. Unset or false, the prompt is byte-for-byte what it was.
42
+ */
43
+ backofficeRead?: boolean;
33
44
  /**
34
45
  * Tools are behind permission prompts this run (`--safe-tools`).
35
46
  *
@@ -193,6 +193,9 @@ export function buildPrompt(run, context) {
193
193
  if (run.memories?.length) {
194
194
  lines.push('Things you remember (from earlier work in this workspace):', ...run.memories.map(renderMemory), '');
195
195
  }
196
+ if (run.memories_omitted) {
197
+ lines.push(`Incomplete memory recall: selected records omitted whole to fit the inline budget: ${run.memories_omitted}.`, 'If your tools and permissions allow, use full-memory reads or POST /v1/memories/search (memories:read) to recover relevant context. Check each result’s scope and target before applying it; exact recovery is not guaranteed. Omitted context is not evidence that no rule applies.', '');
198
+ }
196
199
  if (run.recent_messages?.length) {
197
200
  lines.push('Conversation so far (oldest first):', ...run.recent_messages.map(renderMessage), '--- end of conversation history ---', '');
198
201
  }
@@ -328,10 +331,13 @@ export function buildPrompt(run, context) {
328
331
  context.mcpToolPattern
329
332
  ? ` (${context.mcpToolPattern})`
330
333
  : `, from the MCP server registered as "${context.mcpServerName}"`}. They are the sanctioned ` +
331
- 'way to look up workspace data, so use them rather than guessing. Your credential is ' +
332
- 'deliberately narrow and does not reach every part of the workspace: if a tool returns ' +
333
- 'a permission error, that is a real limit rather than a glitch — say what you could not ' +
334
- 'look up instead of retrying it or answering as if you had.'
334
+ 'way to look up workspace data, so use them rather than guessing. ' +
335
+ (context.backofficeRead
336
+ ? backofficeGuidance(Boolean(context.toolsGated))
337
+ : 'Your credential is ' +
338
+ 'deliberately narrow and does not reach every part of the workspace: if a tool returns ' +
339
+ 'a permission error, that is a real limit rather than a glitch — say what you could not ' +
340
+ 'look up instead of retrying it or answering as if you had.')
335
341
  : '- You have no ProhostAI tools in this run. Everything you know about this ' +
336
342
  'workspace is what is written above.', '');
337
343
  // Postability is a separate question from what the run is about: a task run
@@ -351,6 +357,29 @@ export function buildPrompt(run, context) {
351
357
  : 'stdout is recorded but not posted anywhere — act through your own tools.');
352
358
  return `${lines.join('\n')}\n`;
353
359
  }
360
+ /**
361
+ * What replaces the "deliberately narrow" sentence for a back-office agent.
362
+ *
363
+ * Field-observed 2026-10-01: an internal support agent holding these tools
364
+ * answered "I couldn't read the workflow" and asked a human before a read-only
365
+ * query, because the only thing the prompt said about its access was that it
366
+ * was narrow. The read is pre-authorized, so the prompt says so — and keeps
367
+ * writes, customer-facing sends and personal data exactly as guarded as before.
368
+ */
369
+ function backofficeGuidance(toolsGated) {
370
+ return ('You are a ProhostAI back-office agent: read-only cross-account investigation is ' +
371
+ 'pre-authorized and expected. When a support or engineering question is about a ' +
372
+ 'specific customer account, look before you answer — backoffice_describe_readonly_schema ' +
373
+ 'for the tables, then backoffice_query_readonly filtered to that customer\'s account. ' +
374
+ 'Never ask a human for permission to run a read-only query. ' +
375
+ (toolsGated
376
+ ? ''
377
+ : 'Read-only investigation tools on this machine — MCP servers your operator added, ' +
378
+ 'CLIs such as `gh` — are usable for the same purpose. ') +
379
+ 'Writes and anything sent to a customer still follow approval discipline. Keep personal ' +
380
+ 'data and secrets out of your reply: summarize what you found, do not paste rows. If a ' +
381
+ 'tool returns a permission error, that is a real limit — say what you could not look up.');
382
+ }
354
383
  /**
355
384
  * Normalize an agent's stdout into a postable message.
356
385
  *
@@ -26,6 +26,7 @@ import type { spawn } from 'node:child_process';
26
26
  import WebSocket from 'ws';
27
27
  import type { CommandRunner } from './accounts.js';
28
28
  import type { AgentCredentials } from './credentials.js';
29
+ import type { RunCapabilities } from './capabilities.js';
29
30
  import type { QuotaProvider } from './runtime_status.js';
30
31
  /**
31
32
  * Default ceiling on one agent invocation: none.
@@ -144,6 +145,12 @@ export interface AgentRunOptions {
144
145
  * `codex login status`). Tests inject a fake so no real CLI is asked.
145
146
  */
146
147
  accountCommandRunner?: CommandRunner;
148
+ /**
149
+ * Asks what the paired credential can reach (see `capabilities.ts`).
150
+ * Defaults to a cached `tools/list` against the pairing's MCP endpoint; tests
151
+ * inject a fake so no extra request lands in their recorded calls.
152
+ */
153
+ capabilityProbe?: () => Promise<RunCapabilities>;
147
154
  }
148
155
  export interface AgentRunMetrics {
149
156
  connections: number;
package/dist/agent/run.js CHANGED
@@ -38,6 +38,7 @@ import { ensureWorkspace, streamUrlFor, workspacePath } from './credentials.js';
38
38
  import { execAgent } from './exec.js';
39
39
  import { HeartbeatLoop } from './heartbeat.js';
40
40
  import { buildPrompt, replyFromStdout } from './prompt.js';
41
+ import { NO_CAPABILITIES, capabilityProbe } from './capabilities.js';
41
42
  import { RunStore } from './runstore.js';
42
43
  import { RUNTIME_STATUS_FEATURE, RuntimeStatusReporter, claudeQuotaProvider, codexConfiguredModel, codexQuotaProvider, modelFromExec, parseVersion, readCodexObservation, } from './runtime_status.js';
43
44
  import { SessionStore, sessionKeyFor } from './sessionstore.js';
@@ -255,6 +256,7 @@ function buildRuntime(options, log) {
255
256
  brain,
256
257
  skipPermissions,
257
258
  prepareMcp: () => ({ mcpWired: false }),
259
+ capabilities: async () => NO_CAPABILITIES,
258
260
  runtimeName,
259
261
  observe,
260
262
  model,
@@ -336,6 +338,10 @@ function buildRuntime(options, log) {
336
338
  sessions,
337
339
  brain,
338
340
  prepareMcp: prepareWithAccount,
341
+ capabilities: options.capabilityProbe ??
342
+ (mcpUrl
343
+ ? capabilityProbe({ mcpUrl, apiKey: options.credentials.api_key, fetchImpl: options.fetchImpl })
344
+ : async () => NO_CAPABILITIES),
339
345
  skipPermissions,
340
346
  runtimeName,
341
347
  observe,
@@ -841,6 +847,8 @@ async function handleRun(run, options, runtime, api, log, sleep, control) {
841
847
  const surface = run.conversation_id ?? run.kind ?? 'no conversation';
842
848
  log(`${timestamp()} ▶ ${label} started (${surface})`);
843
849
  const mcp = runtime.prepareMcp();
850
+ // Only worth asking when the tools it describes are wired into this run.
851
+ const capabilities = mcp.mcpWired ? await runtime.capabilities() : NO_CAPABILITIES;
844
852
  const prompt = buildPrompt(run, {
845
853
  agentName: options.credentials.agent?.name ?? 'your agent',
846
854
  // Named only when the tools are actually reachable from this invocation.
@@ -852,6 +860,7 @@ async function handleRun(run, options, runtime, api, log, sleep, control) {
852
860
  // `plain` command is still told the server name only: we have no idea what
853
861
  // it is. See PromptContext.mcpToolPattern.
854
862
  mcpToolPattern: runtime.brain !== 'plain' && mcp.mcpWired ? MCP_TOOL_PATTERN : undefined,
863
+ backofficeRead: capabilities.backofficeRead,
855
864
  toolsGated: !runtime.skipPermissions,
856
865
  // The real numbers, not a vague "there is a limit" — see PromptContext.
857
866
  timeoutMs: options.timeoutMs ?? DEFAULT_EXEC_TIMEOUT_MS,
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.8.1";
10
+ export declare const CLI_VERSION = "0.8.3";
11
11
  /** Sent on every HTTP request the CLI makes back into ProhostAI. */
12
- export declare const USER_AGENT = "prohost-cli/0.8.1";
12
+ export declare const USER_AGENT = "prohost-cli/0.8.3";
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.8.1';
10
+ export const CLI_VERSION = '0.8.3';
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.8.1",
3
+ "version": "0.8.3",
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",