@robota-sdk/agent-cli 3.0.0-beta.79 → 3.0.0-beta.81

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.
Files changed (56) hide show
  1. package/README.md +740 -55
  2. package/dist/node/bin.js +3 -406
  3. package/dist/node/bounded-output-DpjpS98g-DFgAppgl.js +2 -0
  4. package/dist/node/bounded-output-DpjpS98g-DFgAppgl.js.map +1 -0
  5. package/dist/node/bounded-output-DpjpS98g-DoRuRGhV.js +2 -0
  6. package/dist/node/bounded-output-DpjpS98g-yPybHprY.cjs +1 -0
  7. package/dist/node/cli-core-Bah38oug.js +2795 -0
  8. package/dist/node/command-executor-DV-H2oD6-BfKoGoFw.js +2 -0
  9. package/dist/node/command-executor-DV-H2oD6-OKy8pwYH.js +2 -0
  10. package/dist/node/command-executor-DV-H2oD6-OKy8pwYH.js.map +1 -0
  11. package/dist/node/command-executor-DV-H2oD6-iQAXtTLY.cjs +1 -0
  12. package/dist/node/headless.js +4 -0
  13. package/dist/node/http-executor-cWzI2svn-C7F17ZGz.cjs +1 -0
  14. package/dist/node/http-executor-cWzI2svn-TG_TMYKh.js +2 -0
  15. package/dist/node/http-executor-cWzI2svn-TG_TMYKh.js.map +1 -0
  16. package/dist/node/http-executor-cWzI2svn-Ype0naht.js +2 -0
  17. package/dist/node/index.cjs +2641 -251
  18. package/dist/node/index.d.cts +3421 -0
  19. package/dist/node/index.d.cts.map +1 -0
  20. package/dist/node/index.d.ts +3412 -5
  21. package/dist/node/index.d.ts.map +1 -1
  22. package/dist/node/index.js +2641 -251
  23. package/dist/node/index.js.map +1 -1
  24. package/dist/node/node-2-nrieFn.js +2 -0
  25. package/dist/node/node-2-nrieFn.js.map +1 -0
  26. package/dist/node/node-C-hnFnOP.js +67 -0
  27. package/dist/node/node-CFTax5Wl.cjs +1 -0
  28. package/dist/node/node-CJnrbkAS.js +14 -0
  29. package/dist/node/node-CJnrbkAS.js.map +1 -0
  30. package/dist/node/node-ChG9Tg1I.js +2 -0
  31. package/dist/node/node-DDl02brx.cjs +66 -0
  32. package/dist/node/node-DIRGC_1P.js +67 -0
  33. package/dist/node/node-DIRGC_1P.js.map +1 -0
  34. package/dist/node/node-OBnZskvR.cjs +1 -0
  35. package/dist/node/node-bJ0lEuNF.js +2 -0
  36. package/dist/node/node-bJ0lEuNF.js.map +1 -0
  37. package/dist/node/node-tf433Aey.cjs +13 -0
  38. package/dist/node/node-uwhEt6sT.js +14 -0
  39. package/dist/node/rolldown-runtime-C9xYM0wc.js +1 -0
  40. package/dist/node/rolldown-runtime-DK3Fl9T5-BIInC43l.cjs +1 -0
  41. package/dist/node/rolldown-runtime-DK3Fl9T5-BeX3Bzvn.js +2 -0
  42. package/dist/node/rolldown-runtime-DK3Fl9T5-DK3Fl9T5.js +2 -0
  43. package/dist/node/rolldown-runtime-DK3Fl9T5-DK3Fl9T5.js.map +1 -0
  44. package/dist/node/rolldown-runtime-rs7quPpn.js +2 -0
  45. package/dist/node/verdict-decoder-Dk_w2NG9-BKWMtxp8.js +2 -0
  46. package/dist/node/verdict-decoder-Dk_w2NG9-Bka7Cldj.js +2 -0
  47. package/dist/node/verdict-decoder-Dk_w2NG9-Bka7Cldj.js.map +1 -0
  48. package/dist/node/verdict-decoder-Dk_w2NG9-DNTeVdQ8.cjs +1 -0
  49. package/dist/web/assets/index-8_Slbxe7.css +1 -0
  50. package/dist/web/assets/index-Cgu5C1kn.js +77 -0
  51. package/dist/web/index.html +3 -3
  52. package/package.json +106 -39
  53. package/dist/node/bin.d.ts +0 -2
  54. package/dist/node/bin.js.map +0 -1
  55. package/dist/web/assets/index-DiahfakU.css +0 -2
  56. package/dist/web/assets/index-LMW0IDmj.js +0 -37
package/README.md CHANGED
@@ -21,16 +21,28 @@ AI coding assistant CLI built on Robota SDK. Loads AGENTS.md/CLAUDE.md for proje
21
21
 
22
22
  ```typescript
23
23
  import { createAgentRuntime } from '@robota-sdk/agent-framework';
24
- import { createAnthropicProvider } from '@robota-sdk/agent-provider';
24
+ import { createAnthropicProvider } from '@robota-sdk/agent-provider-anthropic';
25
25
 
26
26
  const runtime = createAgentRuntime({
27
27
  cwd: process.cwd(),
28
28
  provider: createAnthropicProvider({ apiKey: process.env.ANTHROPIC_API_KEY }),
29
+ // projectAccess: hostWorkspaceDecision,
29
30
  });
30
31
  const session = runtime.createSession({ permissionMode: 'bypassPermissions' });
31
- const response = await session.submit('Explain this codebase');
32
+
33
+ // submit() is event-driven and resolves to void — drive output via listeners
34
+ session.on('text_delta', (delta) => process.stdout.write(delta));
35
+ session.on('complete', (result) => console.log(result.response));
36
+
37
+ await session.submit('Explain this codebase');
32
38
  ```
33
39
 
40
+ A bare `cwd` is provenance, not project authority. Without a host-issued
41
+ `TWorkspaceProjectAccess` decision the runtime is observably Restricted and does not load project
42
+ context, settings, memory, sessions, or logs. Embedders establish that decision through the
43
+ framework `WorkspaceTrustService`; a path, boolean, or generic filesystem cannot substitute for it.
44
+ Trusted composition is rejected when `cwd` resolves outside that authority's frozen workspace root.
45
+
34
46
  ## Prerequisites
35
47
 
36
48
  Node.js **22 or higher** is required. The TUI renderer ([ink 7.x](https://github.com/vadimdemedes/ink)) requires Node.js 22+.
@@ -51,10 +63,11 @@ volta install node@22
51
63
 
52
64
  ## Demo
53
65
 
54
- <!-- TODO: Add demo GIF here -->
55
- <!-- Run `pnpm demo:record` to capture a demo recording — see docs/DEMO-SCRIPT.md for instructions -->
66
+ ![robota reading a project file and explaining its entry point in the terminal](./docs/demo.gif)
56
67
 
57
- ![Demo](./docs/demo.gif)
68
+ Recorded from the real CLI with `pnpm --filter @robota-sdk/agent-cli demo:record`. The tools run for
69
+ real; the model turns are replayed from a recorded session log so the recording needs no API key —
70
+ see [docs/DEMO-SCRIPT.md](./docs/DEMO-SCRIPT.md).
58
71
 
59
72
  ## Installation
60
73
 
@@ -96,11 +109,19 @@ export ANTHROPIC_API_KEY=sk-ant-...
96
109
  ## Development Setup (Monorepo)
97
110
 
98
111
  ```bash
99
- # Build dependencies and CLI
100
- pnpm build:deps
101
- pnpm --filter @robota-sdk/agent-cli build
112
+ # Build all packages, including the CLI and its web monitor
113
+ pnpm build
102
114
  ```
103
115
 
116
+ `pnpm build` runs each package's own build in dependency order. The CLI build runs `tsdown` and then
117
+ copies the web monitor (`agent-cli-web/dist`) into `dist/web`. To see the published tarball, run
118
+ `pnpm --filter @robota-sdk/agent-cli pack`.
119
+
120
+ Standalone Bun binaries are written to `dist-bun` (`pnpm --filter @robota-sdk/agent-cli build:bun`) and
121
+ `dist-bun-headless` (`build:bun:headless`). Native dependencies make these builds exact-host: each
122
+ supported Linux x64/arm64, macOS x64/arm64, or Windows x64 binary must be compiled on its matching host,
123
+ and a mismatched target is refused.
124
+
104
125
  ## Usage (Monorepo)
105
126
 
106
127
  ```bash
@@ -133,17 +154,401 @@ robota --max-turns <n> # Limit agentic turns per interaction
133
154
  robota --goal "<objective>" # Pursue an autonomous goal headlessly until satisfied or a bound
134
155
  robota --goal-max-iterations <n> # Per-goal turn budget (default 25)
135
156
  robota --output-format <fmt> # text | json | stream-json (print mode)
157
+ robota --effort <level> # auto | none | minimal | low | medium | high | xhigh | max
136
158
  robota --system-prompt <text> # Replace system prompt (print mode)
137
159
  robota --append-system-prompt <text> # Append to system prompt (print mode)
138
160
  robota --model claude-sonnet-4-6 # Override provider model for this session
139
161
  robota --allowed-tools "Bash,Read" # Whitelist specific tools
140
162
  robota --denied-tools "Bash,Write" # Blacklist specific tools (denied > allowed)
163
+ robota --screen-reader # Screen-reader mode: no chrome, no motion, numbered menus, role labels
164
+ robota --no-screen-reader # Force it off for this run, whatever the env or settings say
165
+ # Pacing (ms): ROBOTA_SCREEN_READER_STARTUP_QUIET_MS=900 ROBOTA_SCREEN_READER_PREPARK_MS=50 (0 disables either)
166
+ robota --serve # Run as a headless runtime host over a loopback WS sidecar (used by the desktop GUI)
167
+ robota mcp serve # Serve one session to a local MCP client over stdio
168
+ robota mcp serve --http-token-file /absolute/private/path/mcp-token --http-port 8765
169
+ # Serve Streamable HTTP on 127.0.0.1; the token file must not exist
170
+ robota mcp serve --http-public-url https://agents.example.com/robota/mcp --oauth-issuer https://auth.example.com \
171
+ --oauth-scopes mcp:use --oauth-allowed-subjects alice@example.com
172
+ # Serve remote HTTP behind a proxy, admitting OAuth access tokens
173
+ robota trust status # Inspect canonical workspace trust
174
+ robota trust --yes # Grant trust for the current Git workspace
175
+ robota trust revoke --yes # Revoke the current workspace grant
176
+ robota usage # Show the last 7 days of personal usage from local session history
177
+ robota usage --period 30d # Show complete buckets for the last 30 calendar days
178
+ robota usage --timezone UTC --format json # Emit the versioned JSON projection
179
+ robota usage export --endpoint http://127.0.0.1:4318 # Send stored-usage Gauges to a loopback OTLP collector
180
+ robota usage export --signal traces --endpoint http://127.0.0.1:4318 # Send recorded prompt/provider/tool spans
181
+ robota usage export --signal logs --endpoint http://127.0.0.1:4318 # Send content-free completion events
182
+ robota --safe-mode # Every customization off, to rule one out
141
183
  robota --reset # Delete user settings and exit
142
184
  robota --check-update # Check npm for a newer CLI version and exit
143
185
  robota --disable-update-check # Skip interactive startup update check for this run
144
186
  robota --version # Show version
187
+ robota --reduced-motion # Suppress animation for this run (colour is unaffected)
188
+ robota --no-reduced-motion # Allow animation, overriding a persisted reducedMotion
189
+ ```
190
+
191
+ ### Personal Usage
192
+
193
+ `robota usage` reads local user and trusted-project session stores without starting a provider or
194
+ requiring network access. It reports sessions, started turns, tokens, cost confidence, model/provider/
195
+ surface/source breakdowns, privacy-safe activity counts, and coverage diagnostics. The project copy
196
+ wins when the same session ID exists in both stores. Stored prompts, responses, paths, and tool
197
+ payloads are never printed.
198
+
199
+ Use `--period 7d` (the default) or `--period 30d`, choose an IANA timezone with `--timezone`, and use
200
+ `--format json` for the external `schemaVersion: 1` projection. An empty store produces an empty
201
+ report; a supplied store set containing no readable records exits with an error instead of silently
202
+ reporting zero usage.
203
+
204
+ `robota usage export` is a separate, explicit, one-shot network action over OTLP/HTTP JSON to a
205
+ loopback collector (`127.0.0.1` or `[::1]`) only. The default `metrics` signal sends a current
206
+ **Gauge snapshot** of stored session/turn counts, tokens, estimated known USD cost, and unknown-cost
207
+ counts to `/v1/metrics`. Repeated exports are snapshots, not new usage to add together. Select
208
+ `--signal traces` to send recorded prompt-root, provider-call, and tool spans to `/v1/traces`, or
209
+ `--signal logs` to send recorded content-free completion events to `/v1/logs`. Repeating a logs export
210
+ can resend the same events; a collector may retain duplicates. These signals use stored records;
211
+ they are not live tracing, and legacy records may lack span/event coverage. No signal
212
+ exports prompt or tool bodies, credentials, or a remote destination. An unreadable stored session,
213
+ collector rejection, or network error fails the command without reporting success.
214
+
215
+ Live prompt telemetry is a separate, opt-in Node CLI feature (interactive, print, serve, and MCP serve). Set
216
+ `ROBOTA_TELEMETRY_ENABLED=1`, `ROBOTA_TELEMETRY_TRACES=otlp`,
217
+ `ROBOTA_TELEMETRY_OTLP_PROTOCOL=http/protobuf`, and
218
+ `ROBOTA_TELEMETRY_OTLP_ENDPOINT=https://collector.example` to send content-free prompt/provider/tool
219
+ spans to the base URL's `/v1/traces`. `ROBOTA_TELEMETRY_OTLP_TRACES_ENDPOINT` overrides the base with
220
+ an exact traces URL. Select `ROBOTA_TELEMETRY_METRICS=otlp` independently to send per-invoked-call
221
+ delta counts, complete-usage token totals and price-table-estimated USD cost, plus observed prompt and
222
+ tool-completion counts, to `/v1/metrics`;
223
+ `ROBOTA_TELEMETRY_OTLP_METRICS_ENDPOINT` overrides that destination. Missing usage or prices are
224
+ counted separately, never treated as zero cost. A truncated provider-event batch reports omissions
225
+ and does not claim a complete usage/cost total. Metric datapoints omit session, turn, provider and
226
+ model labels by default; set `ROBOTA_TELEMETRY_METRIC_ATTRIBUTES` to a comma list drawn from
227
+ `session`, `provider` and `model` (canonical lower case, no duplicates) to add them, and only when
228
+ metrics export over `otlp` or `console` — the setting is refused otherwise, and startup is refused
229
+ for any token that is not exactly one of the three. `session` adds `robota.session.id` (the same key
230
+ the trace spans use) to every metric datapoint of the batch. `provider`/`model` add
231
+ `robota.provider.id`/`robota.model.id` only to provider-derived metrics (calls, tokens, cost, and the
232
+ unpriced/usage-unavailable counts), splitting them into one datapoint per distinct id (or pair); a
233
+ call with no id gets its own datapoint without that attribute. In `--serve`/`robota mcp serve`, how
234
+ many distinct session ids appear is set by the connecting clients, not by the CLI; provider and model
235
+ values come from whatever the host's provider configuration reports, not from a fixed catalog. Select
236
+ `ROBOTA_TELEMETRY_LOGS=otlp` independently for content-free
237
+ prompt/provider/tool completion events, plus a tool's own permission decision (allowed, denied, or
238
+ hook-blocked), at `/v1/logs`; `ROBOTA_TELEMETRY_OTLP_LOGS_ENDPOINT`
239
+ overrides that destination. Tool spans and logs carry a validated opaque call ID when available, and an
240
+ invoked provider-call span and its completion log carry the provider's own request ID the same way when
241
+ the adapter attested one; metric datapoints never use either as a label, though a permission-decision
242
+ count by decision value is still reported on `/v1/metrics`. A permission decision has no duration of
243
+ its own and never produces a trace span. Only confirmed invocations produce provider-completion events;
244
+ omitted child counts remain visible on the prompt event. Plain HTTP is allowed only for loopback; URL credentials and query parameters
245
+ are rejected. Export is bounded, best-effort, and does not delay or fail a turn; delivery failures
246
+ produce a content-free stderr warning. These switches do not enable content capture,
247
+ additional event kinds, or replay of stored traces. Ambient `OTEL_*` values alone do not enable them.
248
+
249
+ Prompt, response and tool content is a separate opt-in on top of `ROBOTA_TELEMETRY_LOGS=otlp`.
250
+ `ROBOTA_TELEMETRY_LOG_USER_PROMPTS=1` sends what you typed (never the expanded model input, such as
251
+ `@file` contents), and `ROBOTA_TELEMETRY_LOG_ASSISTANT_RESPONSES=1` sends the assistant's final answer
252
+ for the turn. `ROBOTA_TELEMETRY_LOG_TOOL_ARGUMENTS=1` sends the arguments of the turn's tool calls,
253
+ allowed or denied, and `ROBOTA_TELEMETRY_LOG_TOOL_OUTPUT=1` sends the output of its allowed calls
254
+ (empty for a tool that crashed). Tool arguments carry whatever the model passed: a `Write` or `Edit`
255
+ call's arguments are the file content it writes. Each setting accepts exactly `0` or `1`.
256
+ `ROBOTA_TELEMETRY_LOG_CONTENT_MAX_BYTES` bounds each item (an integer from 256 to 16384, default 2048)
257
+ and is refused unless one of them is `1`. Content is captured only in the interactive terminal and
258
+ only for turns you type yourself, the same turns prompt history records: goal and loop wakeups, peer
259
+ and external messages, remote co-drivers, subagents and background work are never captured, and
260
+ tool content covers only the calls that turn made itself — never a subagent's, a forked skill's or
261
+ background work's, even through a tool they share. A hook-blocked call, an unknown tool, or a call
262
+ stopped before its tool ran sends nothing. Print (`-p`, `--goal`), `--serve` and
263
+ `robota mcp serve` refuse to start with a content setting at `1` rather than ignore it. It goes only to OTLP log records (`robota.content.captured`, joined to the prompt's trace and
264
+ root span, with `robota.content.kind`, `robota.content.truncated`, `robota.content.original_bytes` and,
265
+ for an interrupted turn's response, `robota.content.partial`) — never to spans, metrics or console
266
+ output, so a content setting with `ROBOTA_TELEMETRY_LOGS=console` is refused. A tool item is joined to
267
+ the call's tool span when the trace kept one (else the root span) and adds `robota.tool.call_id`,
268
+ `robota.tool.name` and `robota.tool.outcome` (`success`, `failure` or `denied`). Arguments are
269
+ rendered with values under secret-looking keys (`password`, `apiKey`, `accessTokens`, …) masked
270
+ whole, and binary or base64 payloads replaced by their size. It is sent to the logs
271
+ destination with its headers but in its own requests and queue: a content failure never delays or
272
+ drops the content-free logs, and is reported on stderr like any other delivery failure. One turn's
273
+ content is bounded in item count and total size, with room kept for the prompt and response, and is
274
+ sent as a few requests of bounded size; what does not fit is dropped and counted by kind in a
275
+ content-free `robota.content.omitted` record, sent last. When a request fails, the rest of that turn's
276
+ content and every queued turn are dropped, so that count is lost too. Queued content can hold about
277
+ 12 MB in the worst case. Before sending, the CLI masks known credential shapes (vendor API keys, AWS keys, private-key blocks,
278
+ JWTs, GitHub, Stripe, npm and GitLab tokens, bearer tokens, URL and `-u user:pass` credentials,
279
+ `*_KEY`/`*_TOKEN`/`*_SECRET`/`*_PASSWORD=` values, JSON values whose name looks secret, and
280
+ `Authorization`, `Proxy-Authorization`, `Cookie`, `Set-Cookie` and `X-…-Token`/`-Key`/`-Secret`/`-Auth`
281
+ header lines, including indented ones and the `> ` lines `curl -v` prints), the literal secrets it knows of (settings keys and
282
+ `env` values, every resolved provider key including one switched to mid-session, and collector header
283
+ values), your workspace path (`<workspace>`, also inside `file://` URLs) and home directory (`~`),
284
+ control characters, and a partial token left at the size cut. This
285
+ masking is best effort: anything it does not recognise as a secret is sent as written — file
286
+ contents, command output and a response that repeats them included. If the secrets cannot be
287
+ read, that request carries no content at all.
288
+
289
+ Static collector headers (for example an `Authorization` token) use
290
+ `ROBOTA_TELEMETRY_OTLP_HEADERS` for the generic endpoint and `ROBOTA_TELEMETRY_OTLP_TRACES_HEADERS`,
291
+ `ROBOTA_TELEMETRY_OTLP_METRICS_HEADERS` or `ROBOTA_TELEMETRY_OTLP_LOGS_HEADERS` for one signal, in
292
+ OpenTelemetry's `name=value,name2=value2` form with percent-encoded values
293
+ (`Authorization=Bearer%20abc123`). Headers are scoped to their destination: a signal that uses
294
+ `ROBOTA_TELEMETRY_OTLP_ENDPOINT` sends the generic headers merged with its own, its own winning on the
295
+ same name, while a signal with its own `ROBOTA_TELEMETRY_OTLP_<SIGNAL>_ENDPOINT` sends only its own
296
+ headers. Unlike OpenTelemetry, generic headers are never sent to a per-signal endpoint. Startup is
297
+ refused for malformed entries, empty names or values, duplicate names, reserved transport,
298
+ content-negotiation, proxy, `sec-` or trace-propagation names, control characters other than tab, non-ASCII characters,
299
+ oversized settings, headers no OTLP signal would send, and a per-signal endpoint without its own
300
+ headers while another signal sends the generic ones. Header helpers and refresh are not supported.
301
+ Console output, logs and resource attributes never contain headers, and errors name only the setting
302
+ and entry position. The CLI removes every `ROBOTA_TELEMETRY_*` setting from its environment at
303
+ startup, so shells, hooks, subagents and other child processes do not inherit them; the only
304
+ handover is the supervised runtime that `session start` or `session view` launches, which receives
305
+ them in its spawn environment. While telemetry is enabled, any other `ROBOTA_TELEMETRY_*` setting
306
+ (for example client certificates, a locked destination or other content capture) stops startup with an
307
+ error that names the setting but never prints its value, rather than exporting without it.
308
+ Each signal also accepts `console` instead of `otlp` to write a content-free JSON diagnostic to stderr;
309
+ console needs neither an endpoint nor a protocol and never includes collector credentials. Signals
310
+ remain independent, and the Robota enable switch is still required.
311
+ All enabled signals use the same per-process `service.instance.id` and the CLI's version and
312
+ presentation mode as resource attributes. Ambient `OTEL_SERVICE_NAME` and other `OTEL_*` values
313
+ cannot replace these fields.
314
+
315
+ Trace context propagation is a further opt-in. `ROBOTA_TELEMETRY_PROPAGATE_TO` is a comma list of
316
+ exact origins (`https://api.anthropic.com,https://gateway.example.com:8443`) that may receive a
317
+ W3C `traceparent` on provider requests: `00-<prompt trace id>-<provider-call span id>-01`, where the
318
+ span ID is the one the exported `robota.provider_call` span carries. The same list covers MCP servers
319
+ reached over Streamable HTTP: a tool call to a server whose URL has a listed origin carries
320
+ `00-<prompt trace id>-<tool span id>-01`, where the span ID is the one the exported tool span
321
+ carries. Only the `tools/call` request and its cancellation carry it — never initialization,
322
+ listing, list refreshes, notifications or the server's event stream — and stdio MCP servers receive
323
+ nothing. It needs
324
+ `ROBOTA_TELEMETRY_ENABLED=1` and `ROBOTA_TELEMETRY_TRACES=otlp` or `console`, and is inert while
325
+ telemetry is off. Each entry must be exactly its own origin: `https`, or `http` only on loopback, with no
326
+ path, trailing slash, query, credentials, wildcard or spelled-out default port; a scheme, port or
327
+ subdomain difference is a different origin and gets nothing. An internationalized host must be listed
328
+ in its punycode (`xn--`) form, and an origin with a trailing dot never matches; both fail closed
329
+ (refused at startup or sent nothing). Malformed, duplicate or too many entries
330
+ stop startup with an error that names only the setting and entry position. `tracestate` and `baggage`
331
+ are never sent, the collector's origin is never trusted implicitly, and collector headers are never
332
+ reused for provider requests. The Anthropic and OpenAI (Responses and Chat Completions) adapters
333
+ propagate to their client's effective base URL, as do the OpenAI-compatible DeepSeek, Qwen (both its
334
+ Chat Completions and Responses surfaces), and Gemma adapters, and Gemini propagates to
335
+ `https://generativelanguage.googleapis.com` only — not with `GOOGLE_GEMINI_BASE_URL`,
336
+ `GOOGLE_VERTEX_BASE_URL` or Vertex mode. Nothing is sent through a provider executor. When
337
+ propagation is configured but the round's provider cannot propagate — an executor, or a client
338
+ whose base URL cannot be read — the CLI writes one stderr line per provider naming only that
339
+ provider. Only a prompt's own provider calls carry it: subagents, workers and background
340
+ tasks do not inherit it.
341
+
342
+ Listing a vendor's origin lets that vendor link its own request logs to your trace ID. A redirect
343
+ followed by the SDK carries the header to the redirect target. A provider call whose span was omitted or
344
+ dropped from export still sent its `traceparent`, so the vendor's parent span may be missing from your
345
+ trace; `robota.omitted.provider_count` on the prompt span shows when that happened. Ambient
346
+ `TRACEPARENT` and `OTEL_*` values are never adopted.
347
+
348
+ `ROBOTA_TELEMETRY_PROPAGATE_TO_SUBPROCESSES` hands the prompt's trace to child processes through the
349
+ `TRACEPARENT` environment variable. It is a comma list drawn from exactly `shell` and `hooks`, each at
350
+ most once; it needs the same `ROBOTA_TELEMETRY_ENABLED=1` and exported traces, is inert while
351
+ telemetry is off, works with or without `ROBOTA_TELEMETRY_PROPAGATE_TO`, and a malformed entry stops
352
+ startup naming only the setting and entry position. With `shell`, each foreground `Bash`/`Shell`
353
+ command runs with `00-<prompt trace id>-<tool span id>-01`, where the span ID is the one that call's
354
+ exported tool span carries. With `hooks`, command hooks fired during a prompt — `UserPromptSubmit`,
355
+ `PreToolUse`, `PostToolUse`, `PermissionDecision`, the model-call hooks, `Stop`, `StopFailure` and
356
+ the `PreCompact` and `PostCompact` of an automatic compaction — run with `00-<prompt trace id>-<prompt span id>-01`, so
357
+ their spans sit beside the provider and tool spans; a hook fired outside a prompt (`SessionStart`,
358
+ `SessionEnd`, both hooks of `/compact`, background tasks, subagent worktrees) gets nothing. The value
359
+ is only ever in the child's environment, never in a hook's stdin JSON. A `TRACEPARENT` that a hook
360
+ group's own `env` sets wins, and the child then sees its environment unchanged; otherwise the ambient
361
+ `TRACESTATE` is removed, because it belonged to a different parent. The `!` shell passthrough,
362
+ background, managed and scheduled shells, the monitor UI launcher, a sandboxed shell, stdio MCP
363
+ servers, and HTTP, prompt and agent hooks never receive it. Robota never modifies its own process
364
+ environment, so while the setting is off every child sees exactly the ambient `TRACEPARENT` and
365
+ `TRACESTATE` it would have seen anyway.
366
+
367
+ Upgrading: an origin already listed for a provider now also sends `traceparent` to an MCP HTTP server
368
+ at that exact origin. Remove the origin, or move the MCP server to a different origin, if that server
369
+ should not link its logs to your trace.
370
+
371
+ ### Doctor
372
+
373
+ `robota doctor` (aliases: `checkup`, `diagnose`) diagnoses configuration and runtime readiness
374
+ before any session exists, so a broken configuration cannot make the diagnostic unreachable. It reports
375
+ every settings layer in precedence order with its state and cause, the merged keys with the layer that
376
+ contributed each, provider resolution and endpoint reachability, workspace trust, storage, plugins,
377
+ skills, hooks and MCP declarations — naming the exact file and cause, never a credential. Exit code
378
+ `0` means no check failed (warnings allowed); `1` means at least one did.
379
+
380
+ ```bash
381
+ robota doctor # full report
382
+ robota doctor --repair settings.user.robota # one allowlisted repair, asks [y/N] first
383
+ robota doctor --repair storage.user --yes # no prompt (required in a non-interactive shell)
384
+ ```
385
+
386
+ Repairs are limited to an empty user settings file (rewritten as `{}`) and a missing or too-open
387
+ user storage directory; everything else is reported with the path to fix. `/doctor` runs the same
388
+ report inside a session, and `/doctor repair <check-id>` asks before writing.
389
+
390
+ ### MCP Servers
391
+
392
+ #### Serve Robota to an MCP host
393
+
394
+ Install `@robota-sdk/agent-cli`, configure a provider with `robota --configure`, and grant the
395
+ intended project with `robota trust --yes` before starting a headless server. Resolve the actual
396
+ executable (`command -v robota`) and use its **absolute path** in the host configuration. For an
397
+ MCP client that supports a child-process working directory, configure:
398
+
399
+ ```json
400
+ {
401
+ "mcpServers": {
402
+ "robota": {
403
+ "command": "/absolute/path/to/robota",
404
+ "args": ["mcp", "serve"],
405
+ "cwd": "/absolute/path/to/trusted/project"
406
+ }
407
+ }
408
+ }
409
+ ```
410
+
411
+ The host must launch the process in the intended project directory. Robota uses that inherited
412
+ directory for its normal project-root, access and trust decision; an MCP `roots/list` value or an
413
+ environment variable does not silently change it. For Claude Code, whose stdio server environment
414
+ includes `CLAUDE_PROJECT_DIR`, a project-scoped launch command can select that directory explicitly:
415
+
416
+ ```sh
417
+ claude mcp add --scope project --transport stdio robota -- \
418
+ /bin/sh -c 'cd "$CLAUDE_PROJECT_DIR" && exec /absolute/path/to/robota mcp serve'
419
+ ```
420
+
421
+ The host receives the canonical runtime tool catalog plus `robota_submit`. Model-invocable commands
422
+ appear only through their canonical `robota_command_*` names. Tools run through the session's normal
423
+ permission and hook policy; denied or approval-requiring calls return MCP tool errors, never an
424
+ interactive prompt on the protocol stream. The peer is a local process started by the user and can
425
+ request actions within that session's admitted workspace and permissions. This mode starts no web
426
+ server, WebSocket sidecar or TUI. Stdout carries only MCP messages; startup notices and failures go
427
+ to stderr. Closing the host's stdin or sending SIGINT/SIGTERM shuts down the carrier and session.
428
+ If the host reports a connection failure, run `robota trust status` and `robota doctor` separately in
429
+ the same project directory, then inspect the host's captured stderr for configuration errors.
430
+
431
+ The same session can also use an admitted external MCP tool while it serves the host. Its connected
432
+ client tools appear in the served catalog under canonical names such as `probe__echo`; they still
433
+ run through the session's normal permission policy. The server carrier and outbound client close
434
+ independently. An embedding host can supply its own `IMCPActivationApprovalStore` through
435
+ `startCli({ mcpApprovalStore })` before startup, along with an explicitly approved
436
+ `mcpHttpTransportDeps` egress policy when needed. Neither capability comes from MCP settings or the
437
+ remote caller. The ordinary `robota` executable supplies neither automatically.
438
+
439
+ #### Serve Robota to a remote MCP client
440
+
441
+ `--http-token-file` is for clients on the same machine: it binds only `127.0.0.1`, and its bearer
442
+ is never accepted beyond loopback. To serve a client elsewhere, run Robota as an OAuth resource
443
+ server behind an HTTPS reverse proxy. An authorization server you already run issues the access
444
+ tokens; Robota only verifies them.
445
+
446
+ ```sh
447
+ robota mcp serve \
448
+ --http-public-url https://agents.example.com/robota/mcp \
449
+ --oauth-issuer https://auth.example.com \
450
+ --oauth-scopes mcp:use \
451
+ --oauth-allowed-subjects alice@example.com \
452
+ --http-host 127.0.0.1 --http-port 8765 \
453
+ --trusted-proxy 127.0.0.1
454
+ ```
455
+
456
+ - `--http-public-url` is the `https` address clients use. It is also the token audience and the
457
+ `resource` Robota advertises. Robota serves MCP at its path (`/robota/mcp` above) and the RFC 9728
458
+ protected-resource metadata at `/.well-known/oauth-protected-resource` followed by that path. The
459
+ proxy must forward both paths unchanged (do not strip the prefix) and must preserve the client's
460
+ `Host` header. Robota checks `Host` and `Origin` against the public URL, not the address it binds.
461
+ - `--oauth-issuer`, `--oauth-scopes` and `--oauth-allowed-subjects` are all required. A token must
462
+ be an RFC 9068 access token from that issuer, addressed to the public URL, carrying every listed
463
+ scope, and issued to a listed subject. Every admitted client drives the same session, so name only
464
+ the people you would hand this terminal to.
465
+ - `--http-host` defaults to `127.0.0.1`, which suits a proxy on the same machine. Robota binds any
466
+ other address only when all the flags above are present.
467
+ - A request without a valid token receives `401` with
468
+ `WWW-Authenticate: Bearer resource_metadata="…"`, and a token missing a scope receives `403`
469
+ with `insufficient_scope`. The body is always empty. MCP clients that support authorization
470
+ use that challenge to discover the authorization server.
471
+ - Failed requests are counted per client address. After too many failures in a minute, that
472
+ address receives `429`; a valid token is never throttled. The client address is read from
473
+ `X-Forwarded-For` only when the connection comes from a `--trusted-proxy` address (repeatable).
474
+ - Each refusal is logged on stderr as a reason and an address class (`loopback`, `private`,
475
+ `public`), never the token or the address itself.
476
+ - The server is stateless: it issues no `Mcp-Session-Id`, so there are no sessions to enumerate or
477
+ hijack.
478
+
479
+ The settings described below configure Robota as an MCP **client**.
480
+
481
+ Declare remote MCP servers under an `mcpServers` key in any layered settings file (managed, user, or
482
+ project `.robota`/`.claude` settings) — the same precedence order every other setting uses. Each
483
+ entry names a `"type": "http"` transport and a `url`; `${VAR}`/`${VAR:-default}` references in `url`,
484
+ `headers`, and `env` are resolved from the process environment. Every declared server is
485
+ **deny-by-default**: a server must be explicitly approved with `/mcp approve <serverId>` before its
486
+ tools are connected. Use `/mcp` (or `/mcp list`) to see every declared server's admission status,
487
+ and `/mcp reject`/`/mcp revoke` to withdraw approval. Only approved servers already known at startup
488
+ are connected. Approval is in-memory for the ordinary executable, so `/mcp approve` mid-session
489
+ records a decision for that session but does not connect the server in the running session or
490
+ persist it across a restart. An embedding host can preserve approval state across starts by
491
+ supplying the same store; it remains responsible for when to reconnect approved definitions.
492
+
493
+ A remote server that declares `"oauth": {}` (optionally with `clientId`, `callbackPort`,
494
+ `authServerMetadataUrl` and `scopes`) needs its own sign-in before a session can use it. Sign-in is
495
+ per server, naming the server — inside a session with `/mcp login <server>`, or from a terminal:
496
+
497
+ ```bash
498
+ robota mcp login files # opens your browser; tokens kept owner-only in ~/.robota/mcp-credentials
499
+ robota mcp login files --no-browser # prints the URL; paste back the address your browser was sent to
500
+ robota mcp login files --client-secret # a pre-registered confidential client: asks for the secret
501
+ robota mcp logout files # deletes the stored tokens, then revokes them where the server allows
502
+ ```
503
+
504
+ Use `--no-browser` when the browser runs on another machine (for example over SSH). After you
505
+ approve, the browser is sent to a `http://127.0.0.1:<port>/callback` address that may not load; copy
506
+ that full address and paste it at the prompt (the input is not echoed). It is accepted only if it is
507
+ this sign-in's redirect address and carries its `state`, and only within the same five minutes the
508
+ browser flow allows.
509
+
510
+ `robota mcp logout <server>` always deletes that server's local credential, even when the
511
+ authorization server cannot be reached or refuses to revoke the tokens; it then says, per token,
512
+ whether it was revoked, and otherwise why not (by a short reason) or that the server offers no
513
+ revocation. `/mcp` shows each OAuth server's sign-in state — `signed in`, `token expired, will
514
+ refresh`, `sign-in required` or `signed out` — never a token, and for a server that needs a sign-in
515
+ it names `/mcp login <server>` and `robota mcp login <server>` (the server's name appears in them
516
+ only when it is a plain name that is safe to paste into any shell). `/mcp logout <serverId>` signs
517
+ out of that one server from inside a session and stops the session sending the token it holds.
518
+
519
+ `/mcp login <server>` signs in without leaving the session: it first shows the authorization URL,
520
+ where you can choose to open your browser, paste the redirect instead, or cancel. When no browser
521
+ can be opened — or with `/mcp login <server> --no-browser` — it asks for the redirect address in the
522
+ session's own prompt (the input is not shown). Once signed
523
+ in, a server that could not connect at startup is admitted as usual (it must be approved) and
524
+ connected, and its tools are available from your next message; a server whose sign-in lapsed
525
+ mid-session works again. A failed or cancelled sign-in changes nothing. A client secret is never
526
+ typed into a session: for a pre-registered client that needs one, run
527
+ `robota mcp login <server> --client-secret` in a terminal.
528
+
529
+ ### MCP Background Handoff
530
+
531
+ A slow MCP tool call can be handed to a background task instead of blocking the turn. Configure it
532
+ under an `mcp` key (beside `mcpServers`, in the same layered settings files):
533
+
534
+ ```json
535
+ {
536
+ "mcp": {
537
+ "autoBackgroundMs": 120000,
538
+ "callTimeoutMs": 600000
539
+ }
540
+ }
145
541
  ```
146
542
 
543
+ - `autoBackgroundMs` (default 120000 ms): a tool call still running at this point is handed to a
544
+ `tool-invocation` background task; `/tasks` shows it like any other background task. `0` disables
545
+ the handoff.
546
+ - `callTimeoutMs` (default 600000 ms): the tool call's own budget, enforced by the MCP client in
547
+ every mode — including print. `autoBackgroundMs` must be less than `callTimeoutMs`, or the handoff
548
+ is disabled with a warning.
549
+ - The handoff applies to the interactive TUI and `robota --serve`. In print mode (`-p`) a slow MCP call always runs to completion in the
550
+ foreground, bounded by `callTimeoutMs`; a positive `autoBackgroundMs` is reported as ignored there.
551
+
147
552
  ### CLI Updates
148
553
 
149
554
  Robota can check npm for a newer `@robota-sdk/agent-cli` version:
@@ -214,18 +619,19 @@ Non-interactive/headless mode never prompts. Configure a provider ahead of time
214
619
 
215
620
  ## Built-in Tools
216
621
 
217
- The AI agent can invoke 8 local tools:
622
+ The AI agent can invoke 9 distinct local tools (the runtime registers 10 tool names — `Bash` is the model-familiar alias of `Shell`):
218
623
 
219
- | Tool | Description | Primary Argument |
220
- | ----------- | ---------------------------------------------- | ---------------- |
221
- | `Bash` | Execute shell commands | `command` |
222
- | `Read` | Read file contents with line numbers | `filePath` |
223
- | `Write` | Write content to a file | `filePath` |
224
- | `Edit` | Replace a string in a file | `filePath` |
225
- | `Glob` | Find files matching a pattern | `pattern` |
226
- | `Grep` | Search file contents with regex | `pattern` |
227
- | `WebFetch` | Fetch URL content as text | `url` |
228
- | `WebSearch` | Search the internet (requires `BRAVE_API_KEY`) | `query` |
624
+ | Tool | Description | Primary Argument |
625
+ | ----------------- | ---------------------------------------------- | ---------------- |
626
+ | `Bash` | Execute shell commands | `command` |
627
+ | `Read` | Read file contents with line numbers | `filePath` |
628
+ | `Write` | Write content to a file | `filePath` |
629
+ | `Edit` | Replace a string in a file | `filePath` |
630
+ | `Glob` | Find files matching a pattern | `pattern` |
631
+ | `Grep` | Search file contents with regex | `pattern` |
632
+ | `WebFetch` | Fetch URL content as text | `url` |
633
+ | `WebSearch` | Search the internet (requires `BRAVE_API_KEY`) | `query` |
634
+ | `AskUserQuestion` | Ask the user structured questions mid-task | `questions` |
229
635
 
230
636
  > **WebSearch** requires a `BRAVE_API_KEY` environment variable. Without it, the tool returns a setup message instead of results. Get a free key at [brave.com/search/api](https://brave.com/search/api/) (2,000 queries/month free tier).
231
637
 
@@ -240,6 +646,95 @@ The AI agent can invoke 8 local tools:
240
646
  - Background subagents are real runtime jobs with transcripts and resumable task snapshots.
241
647
  - Explicit multi-agent requests use the `/agent` command module batch path through the SDK runtime.
242
648
 
649
+ ### Recap when you come back
650
+
651
+ The TUI notices when you leave the terminal — by focus, where the terminal reports it (iTerm2,
652
+ Kitty, WezTerm, Alacritty, Ghostty, VS Code, Windows Terminal, tmux with `focus-events on`), or
653
+ after five minutes without a keystroke elsewhere — and on your return prints one line for the
654
+ interval, or nothing if nothing happened:
655
+
656
+ ```
657
+ While away 12m: 2 turns finished (1 wake) · 1 needs input · 1 failed
658
+ ```
659
+
660
+ Every background row carries its state word beside the glyph (`working`, `needs-input`,
661
+ `completed`, `failed`, `stopped`), the one-line headline, and for a sleeping `/schedule` a live
662
+ `in 59s` countdown. `ROBOTA_FOCUS_EVENTS=0` turns focus reporting off (idle detection remains);
663
+ `=1` requests it even where the TUI would not.
664
+
665
+ ### Prompt history
666
+
667
+ `Ctrl+R` searches every prompt you have typed — in this session, in this project, or anywhere —
668
+ the way a shell's reverse search does. Type to narrow the list (newest first, matches highlighted),
669
+ `Ctrl+S` cycles the scope `all → session → project`, `Enter` or `Tab` puts the highlighted prompt back
670
+ in the input, `Ctrl+E` runs it, `Esc` returns you to exactly the draft you had. Every key is
671
+ rebindable in `~/.robota/keybindings.json` (context `history-search`, and `chat-input.history-search`
672
+ for the opener).
673
+
674
+ What is written, where, and how to turn it off: the interactive TUI appends each prompt you submit
675
+ to `~/.robota/history.jsonl` (one JSON line — timestamp, session id, project root, text; readable by
676
+ you only). Prompts are already kept verbatim in the session record; this file is a searchable index
677
+ of them across sessions. `--serve` and print mode never write it. Set `"promptHistory": false` in
678
+ `~/.robota/settings.json` to turn it off, or `ROBOTA_PROMPT_HISTORY=0` for one run (`=1` overrides
679
+ the setting). Delete the file to forget everything.
680
+
681
+ Searching the conversation itself needs no viewer: the TUI never switches to the alternate screen,
682
+ so every message of a resumed session is in your terminal's own scrollback and search.
683
+
684
+ ### Themes
685
+
686
+ `/theme` opens a picker: moving the highlight previews that theme in the live region — the input
687
+ frame, the status bar and the overlay itself — so you judge a colour scheme against the thing it
688
+ applies to rather than a swatch. `Enter` applies it, `Esc` leaves the previous one in place. The
689
+ transcript above keeps the colours it was written in, because the terminal owns those lines once
690
+ they are printed.
691
+
692
+ Four built-ins ship: `dark` (what Robota has always looked like), `light`, and `dark-daltonized` /
693
+ `light-daltonized`, which avoid the red/green distinction entirely — blue for "good", orange for
694
+ "bad" — for the roughly 1 in 12 men with a colour-vision deficiency. The daltonized pair is checked
695
+ mechanically: a test simulates protanopia and deuteranopia over the pairs whose difference in colour
696
+ carries meaning and fails if any of them come too close.
697
+
698
+ Without the picker: `/theme list` shows what is installed and what is active, `/theme <id>` switches,
699
+ `/theme syntax on|off` toggles code-block highlighting, and `/theme motion on|off` toggles animation.
700
+ All three persist to `~/.robota/settings.json` as the flat keys `theme`, `syntaxHighlighting` and
701
+ `reducedMotion`.
702
+
703
+ #### Writing your own
704
+
705
+ Drop a `.json` file in `~/.robota/themes/` and it appears in the list as `custom:<file-name>`. The
706
+ name becomes part of an id you type, so it may use up to 24 characters from letters, digits, `.`,
707
+ `-` and `_` — a file named anything else is skipped with a line saying so, rather than listed as a theme no command can apply:
708
+
709
+ ```json
710
+ {
711
+ "name": "Mine",
712
+ "base": "light",
713
+ "overrides": { "colors": { "text": { "accent": "#56b4e9" } } }
714
+ }
715
+ ```
716
+
717
+ `base` is any built-in and `overrides` is a sparse map over the same token paths the built-ins use —
718
+ `colors`, `markdown`, `syntax` and `motion` — so you change the colours you care about and inherit
719
+ the rest. Values use Ink's colour grammar: a chalk colour name, `#rgb`, `#rrggbb`, `ansi256(n)` or
720
+ `rgb(r,g,b)`. A raw escape sequence is not in that grammar, so it cannot enter through a theme.
721
+
722
+ A plugin ships themes the same way, in its own `themes/` directory; they are listed as
723
+ `custom:<plugin>:<file-name>`, and the plugin's own name has to satisfy the same rule for the same
724
+ reason. Both namespaces start with `custom:`, so a file can never take a
725
+ built-in's name whatever it is called.
726
+
727
+ A file is applied whole or not at all. An unknown token, a value that is not a colour, or JSON that
728
+ does not parse skips the WHOLE file with the path that refused it — printed once at startup as
729
+ `Skipped theme "mine.json": $.overrides.colors.text.accent: "nope" is not a colour …`, and shown in
730
+ the picker as a row that carries the same reason and cannot be chosen. Its neighbours still load.
731
+
732
+ Motion can also be decided per run: `--reduced-motion` / `--no-reduced-motion` beat
733
+ `ROBOTA_REDUCED_MOTION=1|0`, which beats the setting. A run that pins it says so — `/theme motion on`
734
+ reports that it saved the setting and that this run keeps what pinned it, rather than appearing to
735
+ do nothing. `NO_COLOR`, `FORCE_COLOR=0`, a non-TTY stdout and screen-reader mode still win over
736
+ every theme: no colour and no animation, exactly as before.
737
+
243
738
  ## Permission System
244
739
 
245
740
  Every tool call passes through a three-step permission gate:
@@ -262,7 +757,7 @@ Every tool call passes through a three-step permission gate:
262
757
  Use the `/permissions` slash command:
263
758
 
264
759
  ```
265
- > /permissions # Show current mode and session-approved tools
760
+ > /permissions # Show mode, rules by settings file, approvals, recent denials
266
761
  > /permissions plan # Switch to plan (read-only)
267
762
  > /permissions bypassPermissions # Skip all prompts
268
763
  ```
@@ -293,6 +788,7 @@ Pattern syntax: `ToolName` matches any invocation; `ToolName(pattern)` matches o
293
788
  | Key | Action |
294
789
  | ---------- | ----------------------------------------------------------- |
295
790
  | Enter | Submit input |
791
+ | Ctrl+R | Search prompt history (see "Prompt history") |
296
792
  | ESC | Abort current execution (graceful — saves partial response) |
297
793
  | Ctrl+C | Exit process immediately |
298
794
  | Up/Down | Navigate visual lines in wrapped multi-line input |
@@ -337,6 +833,20 @@ The CLI supports continuing, resuming, forking, and naming sessions.
337
833
  | `/resume` | List recent sessions and resume one |
338
834
  | `/rename <name>` | Rename the current session |
339
835
 
836
+ ### Moving to another directory (`/cd`)
837
+
838
+ `/cd <directory>` continues the conversation in another directory. Robota starts again there, as if
839
+ launched in that directory: its settings, trust decision, tools, skills and `AGENTS.md` apply, and the
840
+ conversation resumes. The system prompt is kept as it was, so a provider's prompt cache survives. One
841
+ message tells the model about the new directory and its project instructions.
842
+
843
+ - `/cd` is refused while a turn is running or a background task is still running, and in a session
844
+ started with `--no-session-persistence`, which has no saved conversation to carry.
845
+ - A restricted (untrusted) session stays restricted after a move. A trusted session takes the target
846
+ directory's own trust decision.
847
+ - A `Cd(...)` deny rule keeps sessions out of a directory, for example
848
+ `"deny": ["Cd(/secrets/**)"]`.
849
+
340
850
  ### Session Name Display
341
851
 
342
852
  When a session has a name, it appears in three places:
@@ -345,31 +855,71 @@ When a session has a name, it appears in three places:
345
855
  - **Terminal title** — updated via ANSI escape sequences
346
856
  - **StatusBar** — displayed alongside activity, model, and context usage
347
857
 
858
+ ## Deep Links
859
+
860
+ A `robota://open` link starts a session in a directory you have already trusted, with a prompt
861
+ already in the composer and **not** submitted — you read it and press Enter, or clear it.
862
+
863
+ ```bash
864
+ robota open 'robota://open?v=1&prompt=Summarize%20the%20README&cwd=/absolute/path/to/repo'
865
+ ```
866
+
867
+ | Key | Meaning |
868
+ | -------- | -------------------------------------------------------------------------------------------------------------------- |
869
+ | `v` | Contract version. Required, and must be `1`. |
870
+ | `prompt` | The text to prefill. At most 5,000 characters; it may not begin with `/`. |
871
+ | `cwd` | Absolute path of the directory to open. |
872
+ | `repo` | `owner/name` of an already-trusted local clone, when you do not want to name a path. `cwd` wins if both are present. |
873
+
874
+ Everything else is refused, and a refusal discards the whole link, says which rule it broke, writes
875
+ to stderr and exits non-zero without starting a session: an unknown key (so a link cannot carry
876
+ `provider=`, `permission-mode=`, `plugin=` or any other configuration), a duplicate key, a missing
877
+ or different `v`, a link over 8,192 characters, a prompt over 5,000, a prompt beginning with `/`
878
+ (one Enter would otherwise run it as a command), a relative, UNC or `..`-bearing path, and a second
879
+ link appended after the first (the argv shape a desktop handler can be made to produce). Your own
880
+ flags still apply after the link; a trailing token that is neither a link nor a flag is currently
881
+ discarded rather than refused — a CLI-wide gap tracked separately, not specific to links.
882
+
883
+ The target must already be trusted — `robota trust --yes` in that directory — for `cwd=` exactly as
884
+ for `repo=`. A link opens only what you have already approved; it never clones, never fetches, and
885
+ never reads a repository you have not trusted. While the composer still holds exactly what the link
886
+ supplied, the line `Prompt from an external link` sits below the input, and above 1,000 characters it
887
+ adds the character count and asks you to read the whole thing before sending. Edit that text and the
888
+ line goes: what is in the composer is then yours, and the label would be claiming otherwise.
889
+
890
+ **Known limitations.** No URL scheme is registered with the operating system yet, so a browser
891
+ cannot hand the link over: pass it to `robota open` yourself, or point your own handler at that
892
+ command. Registering the scheme on macOS, Linux and Windows, and the HTTPS launcher that works
893
+ around chat clients stripping custom schemes, are tracked separately.
894
+
348
895
  ## Slash Commands
349
896
 
350
897
  Typing `/` in the TUI opens an autocomplete popup. Arrow keys navigate, Tab inserts without executing, Enter executes. Subcommands (e.g., `/provider list`) show a nested submenu.
351
898
 
352
899
  ### Session & Context
353
900
 
354
- | Command | Description |
355
- | ------------------------- | ---------------------------------------------------------------------- |
356
- | `/clear` | Clear conversation history |
357
- | `/compact [instructions]` | Compress context window |
358
- | `/context` | Context window details, reference inventory, and auto-compact controls |
359
- | `/cost` | Show session token usage and cost |
360
- | `/resume` | List recent sessions and resume one |
361
- | `/rename <name>` | Rename the current session |
362
- | `/rewind` | List, inspect, restore, or rollback edit checkpoints |
901
+ | Command | Description |
902
+ | ------------------------- | ------------------------------------------------------------------------------------------------ |
903
+ | `/clear` | Clear conversation history |
904
+ | `/compact [instructions]` | Compress context window |
905
+ | `/context` | Context window details, reference inventory, and auto-compact controls |
906
+ | `/cost` | Show session token usage and cost |
907
+ | `/effort [level]` | Show or change model effort (`auto`, `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`) |
908
+ | `/resume` | List recent sessions and resume one |
909
+ | `/rename <name>` | Rename the current session |
910
+ | `/cd <directory>` | Move this conversation to another directory (see below) |
911
+ | `/rewind` | List, inspect, restore, or rollback edit checkpoints |
363
912
 
364
913
  ### Providers & Settings
365
914
 
366
- | Command | Description |
915
+ | Command | Description |
367
916
  | ------------------------ | -------------------------------------------------------------------- | ------- | -------------------------------------------------- |
368
917
  | `/provider [subcommand]` | Manage provider profiles: `list`, `switch`, `add`, `test`, `current` |
369
- | `/mode [mode]` | Show or switch permission mode |
370
- | `/permissions [mode]` | Show permission rules or change permission mode |
371
- | `/settings` | Open transport settings (enable/disable transports) |
372
- | `/language [lang]` | Set response language (ko, en, ja, zh), saves and restarts |
918
+ | `/mode [mode]` | Show or switch permission mode |
919
+ | `/permissions [mode]` | Show permission rules or change permission mode |
920
+ | `/sandbox [mode]` | Show or change how shell commands are confined |
921
+ | `/settings` | Open transport settings (enable/disable transports) |
922
+ | `/language [lang]` | Set response language (ko, en, ja, zh), saves and restarts |
373
923
  | `/statusline [on | off | reset]` | Configure status-line fields (model, context, git) |
374
924
 
375
925
  ### Tools & Memory
@@ -383,6 +933,30 @@ Typing `/` in the TUI opens an autocomplete popup. Arrow keys navigate, Tab inse
383
933
  | `/skills [name]` | List registered skills or activate one by name |
384
934
  | `/plugin [subcommand]` | Plugin management |
385
935
 
936
+ ### Git
937
+
938
+ | Command | Description |
939
+ | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
940
+ | `/git status` | Branch plus the staged, unstaged and untracked paths |
941
+ | `/git diff [--staged \| <rev> \| <a>..<b>] [-- <path> ...]` | Unstaged diff, staged diff, or a diff against one or two revisions (each revision is verified first) |
942
+ | `/git commit [<subject>]` | Commit the staged changes after a confirmation listing the message and the staged files |
943
+
944
+ `/git commit` operates on the staged set only — no `-a`, no paths. The subject must follow the
945
+ Conventional Commits form `<type>[(scope)][!]: <description>`; the type list, the 72-character limit
946
+ and a trailing period are warnings, not refusals. With nothing staged it says so, with the unstaged and
947
+ untracked counts. Headless (`-p`) runs cancel the commit because no confirmation can be asked for.
948
+ Other git flags are not accepted — `/shell git ...` remains the way to run arbitrary git.
949
+
950
+ ### Sessions on this host
951
+
952
+ | Command | Description |
953
+ | ------------------------------------ | ----------------------------------------- |
954
+ | `/peers` | List the other live sessions on this host |
955
+ | `/peers send <session-id> <message>` | Send a message to one of them |
956
+
957
+ See [Talking to another session](#talking-to-another-session) for the flow and what happens when the
958
+ other session is busy.
959
+
386
960
  ### Utility
387
961
 
388
962
  | Command | Description |
@@ -391,7 +965,97 @@ Typing `/` in the TUI opens an autocomplete popup. Arrow keys navigate, Tab inse
391
965
  | `/reset` | Delete user settings and return to first-run state |
392
966
  | `/exit` | Exit CLI |
393
967
 
394
- Skill commands discovered from `.agents/skills/` and `.claude/commands/` appear alongside built-in commands.
968
+ Skill commands from the CLI's ordered `.robota/skills/`, `.claude/skills/`, `.claude/commands/`, and
969
+ `.agents/skills/` roots appear alongside built-in commands.
970
+
971
+ ## Talking to another session
972
+
973
+ Two `robota` sessions running on the same host, as the same user, can see and address each other.
974
+ Nothing crosses a machine boundary and nothing is configured — a session becomes discoverable by
975
+ being alive and stops being discoverable when it exits.
976
+
977
+ ### Seeing who is there
978
+
979
+ ```
980
+ /peers
981
+ ```
982
+
983
+ With nothing else running:
984
+
985
+ ```
986
+ No other live session is announced. Start a second session on this host, as this user,
987
+ and it appears here.
988
+ ```
989
+
990
+ That is a sentence rather than an empty list on purpose: "no one is there" and "discovery is not
991
+ working" are different answers, and an empty list cannot tell you which one you got.
992
+
993
+ With a second session up:
994
+
995
+ ```
996
+ Live sessions:
997
+ b8319b98-bb7b-486c-a499-cf1585b39e61 (this session)
998
+ 97ffafe3-f770-4877-90a4-bd86df6be010
999
+
1000
+ Send to one: /peers send <session-id> <message>
1001
+ ```
1002
+
1003
+ ### Sending
1004
+
1005
+ ```
1006
+ /peers send 97ffafe3-f770-4877-90a4-bd86df6be010 rerun the failing suite
1007
+ ```
1008
+
1009
+ The message becomes a **turn** in the other session — the agent there answers it as if the operator
1010
+ had typed it. The sender is told which of four things happened, as a sentence:
1011
+
1012
+ | Outcome | What it means |
1013
+ | ------------------------------------------------------------------------ | --------------------------------------------------------------------------------- |
1014
+ | `Delivered to <id>.` | It arrived and started. |
1015
+ | `<id> has the message; it is waiting behind work already running there.` | It arrived and is queued. Deliberately **not** reported as delivered. |
1016
+ | `<id> had already seen that message.` | A retry that the receiver recognised; it does not run twice. |
1017
+ | `Not delivered to <id>. <reason>` | Nothing ran. The reason names the target, e.g. that no such session is announced. |
1018
+
1019
+ The second row is the one worth knowing. Reporting a queued message as delivered would hide a wait
1020
+ the operator can otherwise see and act on.
1021
+
1022
+ ### What the receiving operator sees
1023
+
1024
+ The message is attributed to the sender, not to whoever is sitting at the receiving terminal:
1025
+
1026
+ ```
1027
+ peer:b8319b98-bb7b-486c-a499-cf1585b39e61:
1028
+ rerun the failing suite
1029
+ Robota:
1030
+ …
1031
+ ```
1032
+
1033
+ Your own turns still read `You:`. The name in the label is **derived from the peer's session id** —
1034
+ it is not a display name the sender chose, because a name the transcript's reader trusts must not be
1035
+ picked by the party being named. It is display attribution only: nothing anywhere uses it to decide
1036
+ what a turn is allowed to do.
1037
+
1038
+ ### When the other session is busy
1039
+
1040
+ A message arriving mid-turn joins that session's existing pending queue rather than interrupting or
1041
+ opening a second one. Three things follow from how that queue works:
1042
+
1043
+ - **Consecutive messages from the same sender coalesce**, last one wins. That is right for a person
1044
+ retyping and wrong for a peer saying two separate things. The replaced one is not swallowed: it
1045
+ settles as refused with the reason `coalesced`, so the sender learns it never ran rather than
1046
+ assuming both did. Tracked as `PEER-003`.
1047
+ - **Messages from different senders do not coalesce** — they queue in arrival order.
1048
+ - **The queue holds 32.** Beyond that a message settles as refused with the reason `dropped`.
1049
+ - **A cleared queue** — abort, cancel, or shutdown — settles the waiting entries with `cancelled`.
1050
+
1051
+ In every case the submission that never became a turn says so. A message that arrived and was then
1052
+ displaced is a different thing from one that ran, and the sender is told which it got.
1053
+
1054
+ ### Limits
1055
+
1056
+ - Same host, same user. There is no network path here.
1057
+ - A session that exits removes its own entry; a crashed one is reaped by the next session that looks.
1058
+ - Session ids are what you address. There are no aliases.
395
1059
 
396
1060
  ## Plugin Management
397
1061
 
@@ -420,6 +1084,29 @@ Settings are merged in this order, from lowest to highest priority:
420
1084
  5. `.claude/settings.json` (project, Claude Code compatible)
421
1085
  6. `.claude/settings.local.json` (local, gitignored, Claude Code compatible)
422
1086
 
1087
+ The two user layers are always host-owned. The four project layers participate only when the CLI host
1088
+ supplies trusted project access; Restricted composition does not probe them. Project writes require a
1089
+ separately approved settings writer for the same authority.
1090
+
1091
+ ### Workspace trust
1092
+
1093
+ Robota admits project-controlled settings and executable contributions only after a host-owned grant
1094
+ for the canonical Git workspace identity. In a new or revoked workspace, interactive startup remains
1095
+ usable with project settings, hooks, plugins, skills, and provider overrides disabled. Headless startup
1096
+ fails closed until trust is granted:
1097
+
1098
+ ```bash
1099
+ robota trust status
1100
+ robota trust --yes
1101
+ robota trust revoke --yes
1102
+ ```
1103
+
1104
+ The grant survives process restart and is invalidated by repository replacement, revocation, or a
1105
+ trust-store error. Symlink aliases resolve to the canonical workspace; a different repository at the
1106
+ same textual path does not inherit the grant. `robota doctor` reports trust and endpoint provenance
1107
+ without printing credentials. If a lower-trust settings layer changes a provider endpoint without
1108
+ providing its own key, Robota removes the inherited key and reports `provider endpoint quarantined`.
1109
+
423
1110
  ```json
424
1111
  {
425
1112
  "defaultMode": "default",
@@ -480,17 +1167,17 @@ persisted default. The legacy single-provider shape remains supported:
480
1167
 
481
1168
  ## Context Discovery
482
1169
 
483
- The CLI automatically discovers and loads:
1170
+ With trusted project access, the CLI discovers and loads:
484
1171
 
485
- - **AGENTS.md** — walking up from cwd to filesystem root
486
- - **CLAUDE.md** — same walk-up discovery
1172
+ - **AGENTS.md** — walking only within the authenticated worktree root
1173
+ - **CLAUDE.md** — the same root-bounded discovery
487
1174
  - **Project metadata** — from `package.json`, `tsconfig.json`
488
1175
 
489
1176
  All context is assembled into the system prompt.
490
1177
 
491
1178
  Ordinary prompts may also reference workspace-local files with path-like `@file` tokens, for
492
1179
  example `@AGENTS.md` or `@docs/SPEC.md`. The CLI passes those prompts through unchanged; the SDK
493
- resolves bounded file content under the active `cwd`, sends the enriched prompt to the model, and
1180
+ resolves bounded file content through the accepted project reader, sends the enriched prompt to the model, and
494
1181
  records a structured file-reference event in the session history.
495
1182
 
496
1183
  ## Memory Management
@@ -501,9 +1188,11 @@ records a structured file-reference event in the session history.
501
1188
 
502
1189
  ## Session Logging
503
1190
 
504
- Session logs are written to `.robota/logs/{sessionId}.jsonl` in JSONL format by default, capturing structured events for diagnostics and replay. Background task lifecycle/progress events are logged there as they happen. Child-process subagents also write append-only transcripts to `.robota/logs/{sessionId}/subagents/{agentId}.jsonl`, including streaming text deltas while the local provider request is still running.
505
-
506
- Resumable session JSON is written to `.robota/sessions/{sessionId}.json` for the current project and includes messages, UI history, the exact system prompt, registered tool schemas, and background task snapshots. High-frequency streaming chunks stay in JSONL transcript files; the session JSON stores task state and transcript paths.
1191
+ Trusted composition writes project session logs and resumable records through authority-backed
1192
+ `session-logs` and `sessions` state facets. Restricted composition opens no project log path and uses
1193
+ the user session store instead. Session records include messages, UI history, the exact system prompt,
1194
+ registered tool schemas, and background task snapshots; high-frequency streaming chunks remain in
1195
+ the separately injected JSONL sink.
507
1196
 
508
1197
  ## Architecture
509
1198
 
@@ -535,19 +1224,15 @@ bin.ts → cli.ts (arg parsing)
535
1224
 
536
1225
  ## Dependencies
537
1226
 
538
- | Package | Purpose |
539
- | -------------------------------------------- | ------------------------------------------ |
540
- | `@robota-sdk/agent-framework` | Session factory, query, config, context |
541
- | `@robota-sdk/agent-core` | Types (TPermissionMode, TToolArgs) |
542
- | `@robota-sdk/agent-transport` (`./headless`) | Headless runner for print mode (`-p`) |
543
- | `ink` 7, `react` 19.2+ | TUI rendering |
544
- | `ink-select-input` | Arrow-key selection (permission prompt) |
545
- | `ink-spinner` | Loading spinner |
546
- | `chalk` | Terminal colors |
547
- | `ink-text-input` | Base text input (extended by CjkTextInput) |
548
- | `marked`, `marked-terminal` | Markdown parsing and terminal rendering |
549
- | `cli-highlight` | Syntax highlighting for code blocks |
550
- | `string-width` | Unicode-aware string width (CJK support) |
1227
+ | Package | Purpose |
1228
+ | ----------------------------- | ----------------------------------------------------------------------------- |
1229
+ | `@robota-sdk/agent-framework` | Session factory, query, config, context |
1230
+ | `@robota-sdk/agent-core` | Types (TPermissionMode, TToolArgs) |
1231
+ | `@robota-sdk/agent-framework` | Headless runner and registry for print mode (`-p`); terminal I/O is CLI-local |
1232
+ | `ink` 7, `react` 19.2+ | TUI rendering |
1233
+ | `chalk` | Terminal colors |
1234
+ | `marked`, `marked-terminal` | Markdown parsing and terminal rendering |
1235
+ | `string-width` | Unicode-aware string width (CJK support) |
551
1236
 
552
1237
  ## Documentation
553
1238