@robota-sdk/agent-cli 3.0.0-beta.10 → 3.0.0-beta.101

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 (156) hide show
  1. package/CHANGELOG.md +3008 -0
  2. package/LICENSE +661 -21
  3. package/README.md +527 -170
  4. package/bin/agent.cjs +39 -0
  5. package/dist/node/artifact-DrPhOb_v.d.ts +749 -0
  6. package/dist/node/artifact-DrPhOb_v.d.ts.map +1 -0
  7. package/dist/node/bin.js +35 -19
  8. package/dist/node/bounded-output-BdWv9SX3-28em7iHq.js +2 -0
  9. package/dist/node/bounded-output-BdWv9SX3-28em7iHq.js.map +1 -0
  10. package/dist/node/bounded-output-BdWv9SX3-C4ucXwLR.js +2 -0
  11. package/dist/node/cli-core-CIzUq54B.js +2920 -0
  12. package/dist/node/command-executor-D_UBZHxh-CVZipoOV.js +2 -0
  13. package/dist/node/command-executor-D_UBZHxh-CVZipoOV.js.map +1 -0
  14. package/dist/node/command-executor-D_UBZHxh-D7UrdAlN.js +2 -0
  15. package/dist/node/desktop-host.d.ts +53 -0
  16. package/dist/node/desktop-host.d.ts.map +1 -0
  17. package/dist/node/desktop-host.js +2 -0
  18. package/dist/node/desktop-host.js.map +1 -0
  19. package/dist/node/headless.js +4 -0
  20. package/dist/node/host.d.ts +3 -0
  21. package/dist/node/host.js +1 -0
  22. package/dist/node/http-executor-ADn4locH-D-8crzF8.js +2 -0
  23. package/dist/node/http-executor-ADn4locH-D-8crzF8.js.map +1 -0
  24. package/dist/node/http-executor-ADn4locH-uz5KV6N-.js +2 -0
  25. package/dist/node/index.d.ts +790 -49
  26. package/dist/node/index.d.ts.map +1 -0
  27. package/dist/node/index.js +6 -13
  28. package/dist/node/index.js.map +1 -0
  29. package/dist/node/node-AQEjsK5o.js +2 -0
  30. package/dist/node/node-AQEjsK5o.js.map +1 -0
  31. package/dist/node/node-B5wKOmx1.js +165 -0
  32. package/dist/node/node-B5wKOmx1.js.map +1 -0
  33. package/dist/node/node-BQfSVkbA.js +2 -0
  34. package/dist/node/node-BRrU0Q8C.js +165 -0
  35. package/dist/node/node-CvUY6TbD.js +15 -0
  36. package/dist/node/node-DQ4XoykT.js +15 -0
  37. package/dist/node/node-DQ4XoykT.js.map +1 -0
  38. package/dist/node/product-host-Btm4B-p5.js +2954 -0
  39. package/dist/node/product-host-Btm4B-p5.js.map +1 -0
  40. package/dist/node/product-host-Cle0fPIc.d.ts +4691 -0
  41. package/dist/node/product-host-Cle0fPIc.d.ts.map +1 -0
  42. package/dist/node/rolldown-runtime-C9xYM0wc.js +1 -0
  43. package/dist/node/rolldown-runtime-DK3Fl9T5-BeX3Bzvn.js +2 -0
  44. package/dist/node/rolldown-runtime-DK3Fl9T5-DK3Fl9T5.js +2 -0
  45. package/dist/node/rolldown-runtime-DK3Fl9T5-DK3Fl9T5.js.map +1 -0
  46. package/dist/node/rolldown-runtime-rs7quPpn.js +2 -0
  47. package/dist/node/verdict-decoder-Dz_rjoIZ-DA8BKB83.js +2 -0
  48. package/dist/node/verdict-decoder-Dz_rjoIZ-DA8BKB83.js.map +1 -0
  49. package/dist/node/verdict-decoder-Dz_rjoIZ-qM0jJVsB.js +2 -0
  50. package/dist/web/app-sandbox.html +145 -0
  51. package/dist/web/assets/PretendardVariable.subset.0-BHUkWNFR.woff2 +0 -0
  52. package/dist/web/assets/PretendardVariable.subset.1-C-__qv6_.woff2 +0 -0
  53. package/dist/web/assets/PretendardVariable.subset.10-DzSWztS8.woff2 +0 -0
  54. package/dist/web/assets/PretendardVariable.subset.11-CqVmlKJn.woff2 +0 -0
  55. package/dist/web/assets/PretendardVariable.subset.12-BHuZSgT0.woff2 +0 -0
  56. package/dist/web/assets/PretendardVariable.subset.13-C42mj_j2.woff2 +0 -0
  57. package/dist/web/assets/PretendardVariable.subset.14-Bl512uUX.woff2 +0 -0
  58. package/dist/web/assets/PretendardVariable.subset.15-D04iXIE3.woff2 +0 -0
  59. package/dist/web/assets/PretendardVariable.subset.16-BQUnS2GX.woff2 +0 -0
  60. package/dist/web/assets/PretendardVariable.subset.17-BfZSA-Xc.woff2 +0 -0
  61. package/dist/web/assets/PretendardVariable.subset.18-CwAxMC3C.woff2 +0 -0
  62. package/dist/web/assets/PretendardVariable.subset.19-CJu4Zcdo.woff2 +0 -0
  63. package/dist/web/assets/PretendardVariable.subset.2-dCZkyKLw.woff2 +0 -0
  64. package/dist/web/assets/PretendardVariable.subset.20-Ig1-z3n5.woff2 +0 -0
  65. package/dist/web/assets/PretendardVariable.subset.21-yKPEdLXC.woff2 +0 -0
  66. package/dist/web/assets/PretendardVariable.subset.22-CSqxKoOs.woff2 +0 -0
  67. package/dist/web/assets/PretendardVariable.subset.23-DK80wi0t.woff2 +0 -0
  68. package/dist/web/assets/PretendardVariable.subset.24-CmkE8Q8D.woff2 +0 -0
  69. package/dist/web/assets/PretendardVariable.subset.25-CsoWBIZB.woff2 +0 -0
  70. package/dist/web/assets/PretendardVariable.subset.26-Sozl8dw8.woff2 +0 -0
  71. package/dist/web/assets/PretendardVariable.subset.27-CT6nuW9L.woff2 +0 -0
  72. package/dist/web/assets/PretendardVariable.subset.28-CpO0Y96p.woff2 +0 -0
  73. package/dist/web/assets/PretendardVariable.subset.29-D6hjrUWm.woff2 +0 -0
  74. package/dist/web/assets/PretendardVariable.subset.3-Dqw33sf4.woff2 +0 -0
  75. package/dist/web/assets/PretendardVariable.subset.30-CWDM1c0J.woff2 +0 -0
  76. package/dist/web/assets/PretendardVariable.subset.31-CdmyZ5mm.woff2 +0 -0
  77. package/dist/web/assets/PretendardVariable.subset.32-CGnFWD2i.woff2 +0 -0
  78. package/dist/web/assets/PretendardVariable.subset.33--0OT__YQ.woff2 +0 -0
  79. package/dist/web/assets/PretendardVariable.subset.34-CaCS33Md.woff2 +0 -0
  80. package/dist/web/assets/PretendardVariable.subset.35-DWFYRGLp.woff2 +0 -0
  81. package/dist/web/assets/PretendardVariable.subset.36-Dn5IBRQB.woff2 +0 -0
  82. package/dist/web/assets/PretendardVariable.subset.37-BD6FyOtY.woff2 +0 -0
  83. package/dist/web/assets/PretendardVariable.subset.38-D4hu443z.woff2 +0 -0
  84. package/dist/web/assets/PretendardVariable.subset.39-B_7wfth9.woff2 +0 -0
  85. package/dist/web/assets/PretendardVariable.subset.4-Bvh2YGoc.woff2 +0 -0
  86. package/dist/web/assets/PretendardVariable.subset.40-BDaOfdUe.woff2 +0 -0
  87. package/dist/web/assets/PretendardVariable.subset.41-BUACvzZC.woff2 +0 -0
  88. package/dist/web/assets/PretendardVariable.subset.42-Dp-5mnyL.woff2 +0 -0
  89. package/dist/web/assets/PretendardVariable.subset.43-DHdpry7N.woff2 +0 -0
  90. package/dist/web/assets/PretendardVariable.subset.44-qHopVhdd.woff2 +0 -0
  91. package/dist/web/assets/PretendardVariable.subset.45-BniyRFfm.woff2 +0 -0
  92. package/dist/web/assets/PretendardVariable.subset.46-BMRq7xC-.woff2 +0 -0
  93. package/dist/web/assets/PretendardVariable.subset.47-B-cWO2pw.woff2 +0 -0
  94. package/dist/web/assets/PretendardVariable.subset.48-Ct-fWrPO.woff2 +0 -0
  95. package/dist/web/assets/PretendardVariable.subset.49-BblQVys9.woff2 +0 -0
  96. package/dist/web/assets/PretendardVariable.subset.5-K_MNGNCe.woff2 +0 -0
  97. package/dist/web/assets/PretendardVariable.subset.50-C8IyFH7L.woff2 +0 -0
  98. package/dist/web/assets/PretendardVariable.subset.51-Bxd0gTAs.woff2 +0 -0
  99. package/dist/web/assets/PretendardVariable.subset.52-CNgqKOOJ.woff2 +0 -0
  100. package/dist/web/assets/PretendardVariable.subset.53-BSRnyb-u.woff2 +0 -0
  101. package/dist/web/assets/PretendardVariable.subset.54-Dt2-cQkx.woff2 +0 -0
  102. package/dist/web/assets/PretendardVariable.subset.55-jFgflYjX.woff2 +0 -0
  103. package/dist/web/assets/PretendardVariable.subset.56-BwZdvJZQ.woff2 +0 -0
  104. package/dist/web/assets/PretendardVariable.subset.57-BwFDg-Fs.woff2 +0 -0
  105. package/dist/web/assets/PretendardVariable.subset.58-DlucQts_.woff2 +0 -0
  106. package/dist/web/assets/PretendardVariable.subset.59-CMkWjhdo.woff2 +0 -0
  107. package/dist/web/assets/PretendardVariable.subset.6-Bxhohlcm.woff2 +0 -0
  108. package/dist/web/assets/PretendardVariable.subset.60-CeHezjjf.woff2 +0 -0
  109. package/dist/web/assets/PretendardVariable.subset.61-PUuTnod4.woff2 +0 -0
  110. package/dist/web/assets/PretendardVariable.subset.62-DGSAWCfb.woff2 +0 -0
  111. package/dist/web/assets/PretendardVariable.subset.63-B35xsm4O.woff2 +0 -0
  112. package/dist/web/assets/PretendardVariable.subset.64-CTbrgYF9.woff2 +0 -0
  113. package/dist/web/assets/PretendardVariable.subset.65-B66rjuyf.woff2 +0 -0
  114. package/dist/web/assets/PretendardVariable.subset.66-C3HqaDeY.woff2 +0 -0
  115. package/dist/web/assets/PretendardVariable.subset.67-BmuXdlDy.woff2 +0 -0
  116. package/dist/web/assets/PretendardVariable.subset.68-DS9B48d0.woff2 +0 -0
  117. package/dist/web/assets/PretendardVariable.subset.69-YT16ymcp.woff2 +0 -0
  118. package/dist/web/assets/PretendardVariable.subset.7-E2HaA55t.woff2 +0 -0
  119. package/dist/web/assets/PretendardVariable.subset.70-BUXiAGMT.woff2 +0 -0
  120. package/dist/web/assets/PretendardVariable.subset.71-DuPZj8us.woff2 +0 -0
  121. package/dist/web/assets/PretendardVariable.subset.72-pYYGrEQR.woff2 +0 -0
  122. package/dist/web/assets/PretendardVariable.subset.73-DMrK970F.woff2 +0 -0
  123. package/dist/web/assets/PretendardVariable.subset.74-D4tQnymK.woff2 +0 -0
  124. package/dist/web/assets/PretendardVariable.subset.75-CxKdrRNf.woff2 +0 -0
  125. package/dist/web/assets/PretendardVariable.subset.76-DhPm2b_q.woff2 +0 -0
  126. package/dist/web/assets/PretendardVariable.subset.77-DwaxqOC8.woff2 +0 -0
  127. package/dist/web/assets/PretendardVariable.subset.78-DhqRbBzT.woff2 +0 -0
  128. package/dist/web/assets/PretendardVariable.subset.79-XpoyPP38.woff2 +0 -0
  129. package/dist/web/assets/PretendardVariable.subset.8-CRbJhhyA.woff2 +0 -0
  130. package/dist/web/assets/PretendardVariable.subset.80-DsV9Qp_h.woff2 +0 -0
  131. package/dist/web/assets/PretendardVariable.subset.81-BZzF9Hb3.woff2 +0 -0
  132. package/dist/web/assets/PretendardVariable.subset.82-BgAHe30u.woff2 +0 -0
  133. package/dist/web/assets/PretendardVariable.subset.83-DF-zBLLe.woff2 +0 -0
  134. package/dist/web/assets/PretendardVariable.subset.84-Brb8EsYQ.woff2 +0 -0
  135. package/dist/web/assets/PretendardVariable.subset.85-Byo_x2hf.woff2 +0 -0
  136. package/dist/web/assets/PretendardVariable.subset.86-XG7lTN_6.woff2 +0 -0
  137. package/dist/web/assets/PretendardVariable.subset.87-Lzui2vbK.woff2 +0 -0
  138. package/dist/web/assets/PretendardVariable.subset.88-CqX6JSgh.woff2 +0 -0
  139. package/dist/web/assets/PretendardVariable.subset.89-DOzqWPpX.woff2 +0 -0
  140. package/dist/web/assets/PretendardVariable.subset.9-Btb3bmS6.woff2 +0 -0
  141. package/dist/web/assets/PretendardVariable.subset.90-BF7RiZjm.woff2 +0 -0
  142. package/dist/web/assets/PretendardVariable.subset.91-Csm0YNoH.woff2 +0 -0
  143. package/dist/web/assets/index-2GhVGsHN.js +314 -0
  144. package/dist/web/assets/index-BSxTg2Py.css +1 -0
  145. package/dist/web/index.html +15 -0
  146. package/package.json +203 -34
  147. package/scripts/koffi-bun-plugin.mjs +116 -0
  148. package/scripts/native-build.d.mts +19 -0
  149. package/scripts/native-build.mjs +72 -0
  150. package/scripts/qualify-koffi-gc.mjs +28 -0
  151. package/dist/node/bin.cjs +0 -1368
  152. package/dist/node/bin.d.cts +0 -1
  153. package/dist/node/bin.d.ts +0 -1
  154. package/dist/node/chunk-UT4RQDTI.js +0 -1337
  155. package/dist/node/index.cjs +0 -1375
  156. package/dist/node/index.d.cts +0 -49
package/README.md CHANGED
@@ -1,254 +1,611 @@
1
+ **Language:** [English](README.md) | [Korean](docs/README-KO.md)
2
+
1
3
  # @robota-sdk/agent-cli
2
4
 
3
- AI coding assistant CLI built on Robota SDK. Loads AGENTS.md/CLAUDE.md for project context and provides tool-calling REPL with Claude Code-compatible permission modes.
5
+ `robota` is the configurable terminal interface for Robota, assembled from the
6
+ same TypeScript libraries you can embed. It is a coding-capable agent within the environment whose
7
+ direction is [our agents developing and advancing our agents](../../VISION.md). Autonomous
8
+ self-evolution remains an ambition; the behavior below describes the current CLI. It reads your
9
+ project, edits files and runs commands under a permission system you control, and works with
10
+ Anthropic, OpenAI, Gemini, DeepSeek, Qwen and local OpenAI-compatible models.
4
11
 
5
- ## Installation
12
+ The CLI is assembled from the same packages you can use in your own app: `@robota-sdk/agent-framework`
13
+ for the session, `@robota-sdk/agent-ui-terminal` for the terminal UI, and one package per model
14
+ provider. To build your own agent rather than use this one, start with the
15
+ [SDK guide](../../content/guide/sdk.md).
6
16
 
7
- ```bash
8
- # Global install
9
- npm install -g @robota-sdk/agent-cli
17
+ > **Beta.** Behavior may change before the stable release. Please
18
+ > [report issues](https://github.com/woojubb/robota/issues).
10
19
 
11
- # Or run directly with npx
12
- npx @robota-sdk/agent-cli
13
- ```
20
+ ## Install
14
21
 
15
- After installing globally, the `robota` command is available system-wide:
22
+ Requires Node.js 22.12.0 or later.
16
23
 
17
24
  ```bash
18
- robota # Interactive REPL
19
- robota "prompt" # REPL with initial prompt
20
- robota -p "List all files" # Print mode (one-shot, exit after response)
25
+ npm install -g @robota-sdk/agent-cli # installs the `robota` command
26
+ npx @robota-sdk/agent-cli # or run it once without installing
21
27
  ```
22
28
 
23
- ### Environment Variables
29
+ On macOS, Korean and other CJK input methods can crash inside Terminal.app; use a terminal such as
30
+ [iTerm2](https://iterm2.com/) instead.
24
31
 
25
- | Variable | Description | Required |
26
- | ------------------- | ----------------- | -------- |
27
- | `ANTHROPIC_API_KEY` | Anthropic API key | Yes |
32
+ ## First run
28
33
 
29
- Set your key before running:
34
+ Run `robota` inside a Git repository. It first asks whether to trust the folder: only a trusted
35
+ workspace can load the project's own settings, hooks, skills, plugins and MCP servers. Answer no and
36
+ the session starts **Restricted**, with your user settings and the built-in tools only. The first
37
+ time, it then walks you through choosing a provider and filling in its fields (model, base URL, API
38
+ key), and saves the profile to `<configured-user-state-dir>/settings.json`.
30
39
 
31
40
  ```bash
32
- export ANTHROPIC_API_KEY=sk-ant-...
41
+ cd my-project
42
+ robota
33
43
  ```
34
44
 
35
- ## Development Setup (Monorepo)
45
+ To set up without prompts (for example on a server), create the profile and trust the workspace
46
+ with flags. The profile stores a reference to the environment variable, not the key itself, and the
47
+ variable must be set when you run the command:
36
48
 
37
49
  ```bash
38
- # 1. Copy .env.example and add your Anthropic API key
39
- cp packages/agent-cli/.env.example packages/agent-cli/.env
40
- # Edit .env and set ANTHROPIC_API_KEY=sk-ant-...
41
-
42
- # 2. Build dependencies and CLI
43
- pnpm build:deps
44
- pnpm --filter @robota-sdk/agent-cli build
50
+ export ANTHROPIC_API_KEY=sk-ant-...
51
+ robota --configure-provider anthropic --type anthropic --model claude-sonnet-4-6 \
52
+ --api-key-env ANTHROPIC_API_KEY --set-current
53
+ robota trust --yes
45
54
  ```
46
55
 
47
- ## Usage (Monorepo)
48
-
49
- ```bash
50
- # From monorepo root
51
- cd packages/agent-cli
56
+ `robota init` writes a starter `AGENTS.md` and `<configured-project-state-dir>/settings.json` for the current project.
57
+ `robota --configure` reruns the interactive provider setup, and `robota --reset` deletes
58
+ `<configured-user-state-dir>/settings.json`.
52
59
 
53
- # Development mode (no build needed, auto-loads .env)
54
- pnpm dev
60
+ ## What you can do
55
61
 
56
- # Production mode (requires build)
57
- pnpm start
62
+ ### Work in the terminal UI
58
63
 
59
- # With arguments
60
- pnpm dev -- --version
61
- pnpm dev -- --permission-mode plan
62
- pnpm dev -- -p "List all TypeScript files in src/"
63
- ```
64
+ `robota` starts an interactive session. Type a request, or `/` to open the command menu (`/help`
65
+ lists every command). `Esc` stops the current response, `Ctrl+R` searches the prompts you have typed
66
+ before, and every key can be rebound in `<configured-user-state-dir>/keybindings.json` (see the
67
+ [keybindings guide](../../content/guide/keybindings.md)).
64
68
 
65
- ## CLI Flags
69
+ The agent works with file and shell tools (`Read`, `Write`, `Edit`, `Glob`, `Grep`, `Bash`),
70
+ `WebFetch`, `WebSearch` (needs a `BRAVE_API_KEY`) and `AskUserQuestion`, and can hand work to
71
+ subagents and background tasks. Your project's `AGENTS.md` and `CLAUDE.md` are loaded as context
72
+ in a trusted workspace, and `@path` in a prompt attaches a project file.
66
73
 
74
+ ```bash
75
+ robota # new session
76
+ robota --permission-mode acceptEdits
77
+ robota --screen-reader # plain-text mode for screen readers
67
78
  ```
68
- robota # Interactive REPL (default mode)
69
- robota "prompt" # REPL with initial prompt
70
- robota -p "prompt" # Print mode (one-shot, exit after response)
71
- robota -c # Continue last session
72
- robota -r <session-id> # Resume session by ID
73
- robota --model <model> # Model override (e.g., claude-sonnet-4-6)
74
- robota --permission-mode <mode> # plan | default | acceptEdits | bypassPermissions
75
- robota --max-turns <n> # Limit agentic turns per interaction
76
- robota --version # Show version
77
- ```
78
-
79
- ## Built-in Tools
80
79
 
81
- The CLI provides 6 tools that the AI agent can invoke:
80
+ ### Run one prompt from a script
82
81
 
83
- | Tool | Description | Primary Argument |
84
- | ------- | ------------------------------------ | ---------------- |
85
- | `Bash` | Execute shell commands | `command` |
86
- | `Read` | Read file contents with line numbers | `filePath` |
87
- | `Write` | Write content to a file | `filePath` |
88
- | `Edit` | Replace a string in a file | `filePath` |
89
- | `Glob` | Find files matching a pattern | `pattern` |
90
- | `Grep` | Search file contents with regex | `pattern` |
82
+ Print mode (`-p`) runs a single prompt without the terminal UI and exits. With no prompt argument it
83
+ reads the prompt from piped stdin.
91
84
 
92
- ## Permission System
85
+ ```bash
86
+ robota -p "List the TypeScript files in src/"
87
+ robota -p "Summarize this repository" --output-format json # one JSON object: result, session_id
88
+ cat task.md | robota -p # prompt from stdin
89
+ robota -p "Review this diff" --bare # without AGENTS.md, CLAUDE.md or plugins
90
+ robota --goal "make the failing tests pass" # work toward a goal over several turns
91
+ ```
93
92
 
94
- Every tool call passes through a three-step permission gate before execution:
93
+ `--output-format` is `text` (default), `json` or `stream-json`. `--json-schema` asks for JSON matching
94
+ a schema, and `--system-prompt` / `--append-system-prompt` change the system prompt for the run. The
95
+ exit code is `0` on success and `1` on an error; `-p` exits `3` when no usable provider is
96
+ configured, and a `--goal` run that stops without reaching its goal exits `2`. In a Git repository
97
+ you have not trusted, print mode, `--goal`, `--serve`, `robota mcp serve`, `robota daemon start` and
98
+ `robota session start` refuse to start; with `--safe-mode` the first four run Restricted instead, and
99
+ `robota daemon start --restricted-workspace` starts the daemon Restricted.
95
100
 
96
- 1. **Deny list** — if any deny pattern matches, the action is blocked immediately
97
- 2. **Allow list** — if any allow pattern matches, the action is auto-approved
98
- 3. **Mode policy** — the active permission mode determines the decision
101
+ ### Keep sessions, run them in the background, share a daemon
99
102
 
100
- When a tool requires approval, the user sees an interactive prompt:
103
+ Sessions are saved, so you can come back to them.
101
104
 
102
- ```
103
- [Permission Required] Tool: Bash
104
- Arguments: command: rm -rf dist
105
- Allow? [y/N]
105
+ ```bash
106
+ robota -c # continue the most recent session
107
+ robota -r <id-or-name> # resume a session
108
+ robota -r <id> --fork-session # continue a copy, leaving the original as it was
109
+ robota -n "refactor auth" # name a new session
106
110
  ```
107
111
 
108
- - Type `y` or `yes` to approve
109
- - Press Enter or type anything else to deny
112
+ Inside a session, `/resume` switches sessions, `/rename` names this one, `/fork` copies the
113
+ conversation into a background session, and `/cd <directory>` moves the conversation to another
114
+ directory.
110
115
 
111
- If denied, the AI agent receives a "Permission denied" error and can adjust its approach.
116
+ A supervised session keeps running after you close the terminal, and a workspace daemon is one
117
+ long-lived runtime that terminals and the desktop app share. In a folder not trusted yet, the desktop
118
+ app asks in its window whether to trust it, start Restricted, or quit before it starts the daemon:
112
119
 
113
- ### Permission Modes
120
+ ```bash
121
+ robota session start --background --name nightly
122
+ robota session list
123
+ robota session attach <supervised-id> # add --observe to watch read-only
124
+ robota daemon start # then: robota --attach
125
+ robota daemon stop
126
+ ```
114
127
 
115
- | Mode | Alias | Read/Glob/Grep | Write/Edit | Bash |
116
- | ------------------- | -------- | :------------: | :--------: | :-----: |
117
- | `plan` | safe | auto | deny | deny |
118
- | `default` | moderate | auto | approve | approve |
119
- | `acceptEdits` | full | auto | auto | approve |
120
- | `bypassPermissions` | — | auto | auto | auto |
128
+ See [Sessions and the daemon](../../content/guide/sessions-and-daemon.md).
121
129
 
122
- - **auto** — tool executes without prompting
123
- - **approve** — user is prompted to allow or deny
124
- - **deny** — tool is blocked silently (no prompt shown)
130
+ ### Control what the agent may do
125
131
 
126
- Unknown tools default to `approve` in most modes, `deny` in `plan` mode.
132
+ Every tool call passes through deny rules, ask rules, allow rules and then the permission mode.
127
133
 
128
- ### Changing Mode at Runtime
134
+ | Mode | Reads | File edits | Shell commands |
135
+ | ------------------- | ----- | ---------- | ---------------------------------------- |
136
+ | `plan` | yes | refused | refused (except built-in read-only ones) |
137
+ | `default` | yes | asks | asks |
138
+ | `acceptEdits` | yes | yes | asks |
139
+ | `auto` | yes | yes | decided by a model classifier |
140
+ | `bypassPermissions` | yes | yes | yes |
129
141
 
130
- Use the `/mode` slash command in the REPL:
142
+ Set the mode with `--permission-mode <mode>` (`--dry-run` is `plan`) or change it in a session with
143
+ `/permissions <mode>`. `/permissions` alone lists the rules in force and recent denials. Rules live in
144
+ any settings file:
131
145
 
146
+ ```json
147
+ {
148
+ "permissions": {
149
+ "allow": ["Bash(pnpm *)", "Bash(git status)"],
150
+ "ask": ["Bash(git push *)"],
151
+ "deny": ["Bash(rm -rf *)", "Write(.env)"]
152
+ }
153
+ }
132
154
  ```
133
- > /mode # Show current mode
134
- Current permission mode: default
135
155
 
136
- > /mode plan # Switch to plan (read-only)
137
- Permission mode set to: plan
156
+ An `ask` rule asks even in `bypassPermissions`, and a few actions are never approved automatically
157
+ in any mode: `rm` on the root, home or working directory, and writes into `.git`, `<configured-project-state-dir>`,
158
+ `.claude`, `.agents` or shell and tool configuration files.
138
159
 
139
- > /mode bypassPermissions # Skip all prompts
140
- Permission mode set to: bypassPermissions
141
- ```
160
+ Shell commands can also run inside an OS sandbox (bubblewrap on Linux, Seatbelt on macOS): `/sandbox`
161
+ switches between `auto-allow`, `regular` and `off`, and the `sandbox` settings key configures it.
162
+ When something misbehaves, `robota --safe-mode` starts with instruction files, skills, plugins, hooks
163
+ and MCP servers all off.
142
164
 
143
- Or set it at startup:
165
+ Workspace trust is granted per Git worktree and kept in `<configured-user-state-dir>/workspace-trust.json`:
144
166
 
145
167
  ```bash
146
- robota --permission-mode plan
168
+ robota trust status # is this workspace trusted, and what would trust load? (--json: one line)
169
+ robota trust --yes # trust the Git workspace you are in
170
+ robota trust revoke --yes
147
171
  ```
148
172
 
149
- ### Permission Patterns (allow/deny lists)
173
+ See [Permissions and hooks](../../content/guide/permissions-and-hooks.md).
174
+
175
+ ### Choose providers and models
150
176
 
151
- Configure in `.robota/settings.json` or `.robota/settings.local.json`:
177
+ Each provider profile in `providers` names a `type` (`anthropic`, `openai`, `gemini`, `deepseek`,
178
+ `qwen`, `gemma`), a model, and optionally a base URL and API key; `currentProvider` picks the one to
179
+ use. Setup fills the key in as a reference to an environment variable:
180
+
181
+ | Provider type | Default key variable | Notes |
182
+ | ------------- | -------------------- | ------------------------------------------------------------------------------------ |
183
+ | `anthropic` | `ANTHROPIC_API_KEY` | |
184
+ | `openai` | `OPENAI_API_KEY` | also other OpenAI-compatible endpoints, through `baseURL` |
185
+ | `gemini` | `GEMINI_API_KEY` | |
186
+ | `deepseek` | `DEEPSEEK_API_KEY` | |
187
+ | `qwen` | `DASHSCOPE_API_KEY` | Alibaba Cloud Model Studio |
188
+ | `gemma` | none | a local Gemma model; the base URL defaults to LM Studio's `http://localhost:1234/v1` |
152
189
 
153
190
  ```json
154
191
  {
155
- "permissions": {
156
- "allow": ["Bash(pnpm *)", "Bash(git status)", "Read(/src/**)"],
157
- "deny": ["Bash(rm -rf *)", "Write(.env)"]
192
+ "currentProvider": "claude",
193
+ "providers": {
194
+ "claude": {
195
+ "type": "anthropic",
196
+ "model": "claude-sonnet-4-6",
197
+ "apiKey": "$ENV:ANTHROPIC_API_KEY"
198
+ }
158
199
  }
159
200
  }
160
201
  ```
161
202
 
162
- **Pattern syntax:**
163
-
164
- - `ToolName` — match any invocation of that tool (e.g., `Bash`)
165
- - `ToolName(pattern)` — match when the primary argument matches the glob (e.g., `Bash(pnpm *)`)
166
- - `*` — zero or more characters (shell-style)
167
- - `**` — one or more characters (recursive path matching)
168
-
169
- **Evaluation order:** deny patterns are checked first, then allow patterns, then the mode policy. Deny always wins.
203
+ In a session, `/provider list`, `/provider switch <profile>`, `/provider add` and `/provider test`
204
+ manage profiles. For one run, `--provider <profile>` picks a profile (add `--set-current` to make it
205
+ the default), `--model` overrides the model, `--fallback-model a,b` continues a turn on another model
206
+ when the first is overloaded, `--effort <level>` sets model effort, and `--advisor <profile[:model]>`
207
+ lets the model consult a second model. See [Providers](../../content/guide/providers.md) and
208
+ [Local LLM setup](../../content/guide/local-llm.md).
170
209
 
171
- ## Slash Commands
210
+ ### Connect MCP servers, or serve the session over MCP
172
211
 
173
- | Command | Description |
174
- | -------------- | ------------------------------- |
175
- | `/help` | Show help |
176
- | `/clear` | Clear conversation history |
177
- | `/mode [mode]` | Show or change permission mode |
178
- | `/resume` | List and resume a saved session |
179
- | `/cost` | Show token usage |
180
- | `/model` | Show current model |
181
- | `/exit` | Exit CLI |
212
+ Declare remote MCP servers under `mcpServers` in a settings file. A declared server is not connected
213
+ until you approve it: `/mcp` shows each server's state, and `/mcp approve <server>` connects it in
214
+ the running session and keeps the approval in `<configured-user-state-dir>/mcp-approvals.json` for later starts. A
215
+ server that uses OAuth also needs a sign-in — `/mcp login <server>` in a session, or
216
+ `robota mcp login <server>` in a terminal — and connects once you sign in.
182
217
 
183
- ## Configuration
184
-
185
- Settings are loaded from (highest priority first):
218
+ ```json
219
+ {
220
+ "mcpServers": {
221
+ "docs": { "type": "http", "url": "https://mcp.example.com/mcp", "oauth": {} }
222
+ }
223
+ }
224
+ ```
186
225
 
187
- 1. `.robota/settings.local.json` (local, gitignored)
188
- 2. `.robota/settings.json` (project, shared)
189
- 3. `~/.robota/settings.json` (user global)
226
+ An HTTP or stdio definition can explicitly select `"protocolVersion": "2026-07-28"` for a stateless
227
+ peer. Omit that field for the legacy handshake. Changing it requires approval again; unsupported values
228
+ are refused rather than silently ignored. The selected version is visible in the definition details.
229
+ Startup reports unavailable client-input, subscription and extension capabilities. Selecting this
230
+ version does not enable distributed Skills or give the server any additional authority. Add
231
+ `"skills": true` to explicitly opt that definition into a peer's advertised Skills extension;
232
+ changing this selection requires fresh server approval. `/mcp skill-list <server>` reads metadata
233
+ only. A local terminal or app user can review `/mcp skill-inspect <server> <uri>`, then copy its
234
+ `/mcp skill-approve <server> <uri> <fingerprint>` command to consent to those exact instructions and
235
+ frontmatter. `/mcp skill-withdraw <server> <uri>` withdraws that consent. JSON string arrays also
236
+ accept opaque names and URIs containing spaces. Content decisions persist separately in
237
+ `<configured-user-state-dir>/mcp-skill-approvals.json`, bound to the host origin, workspace and URI.
238
+ Observed changed or removed manifests withdraw consent even if the peer later reverts them.
239
+ Discovery returns an exact invocation name for each supported skill, or a reason its profile is
240
+ unavailable. Invoke that name through the session's skill command. Instructions load only when
241
+ the actual turn starts, under current content consent; ending or cancelling that turn closes its
242
+ activation. A queued or cancelled request does not load instructions into another turn. Remote
243
+ shell expressions remain instruction text and do not execute as local preprocessing. During an
244
+ active execution, `/skill-read ["<invocation-name>", "<resource-uri>"]` reads a listed supporting
245
+ file with fresh verification. A fork can read only files from its own activations; a nested SKILL.md remains
246
+ supporting data until separately approved and activated.
247
+
248
+ MCP calls recheck approval and the current source immediately before dispatch. Removing or
249
+ replacing a definition, revoking approval, changing required workspace trust, or closing the
250
+ composition refuses new calls through retained tools. Calls already dispatched keep their real
251
+ outcome; revocation is not rollback. A changed configuration needs a fresh startup and approval
252
+ rather than silently substituting a new connection into an existing tool chain. The startup host
253
+ also exposes declarative contribution descriptors bound to configuration fingerprints, without
254
+ credential values or an invented server version.
255
+
256
+ | Surface | Verified coverage | Limit |
257
+ | ------------------------------------------ | -------------------------------------------------------------------------------------- | ------------------------------------------------------- |
258
+ | Linux interactive, serve and print startup | Real admitted stdio calls; changed settings refused before another call | Shared startup composition; not a rendered app UI trial |
259
+ | Installed Linux bundle in serve startup | Disable, uninstall and selected-revision update; durable approval rechecked at restart | Already dispatched calls settle; no rollback claim |
260
+ | Remote HTTP | Composition, transport admission and call routing regressions | No live account benchmark |
261
+ | macOS and Windows | Repository CI build routes | No external-plugin pilot on those OSes |
262
+
263
+ `robota mcp serve` does the reverse: it serves one agent session to an MCP host over stdio (or over
264
+ authenticated HTTP with the `--http-*` and `--oauth-*` flags). Trust the project first, and give the
265
+ host the absolute path of `robota` and the project directory:
190
266
 
191
267
  ```json
192
268
  {
193
- "defaultMode": "default",
194
- "provider": {
195
- "name": "anthropic",
196
- "model": "claude-sonnet-4-6",
197
- "apiKey": "$ENV:ANTHROPIC_API_KEY"
198
- },
199
- "permissions": {
200
- "allow": ["Bash(pnpm *)"],
201
- "deny": ["Bash(rm -rf *)"]
269
+ "mcpServers": {
270
+ "robota": {
271
+ "command": "/absolute/path/to/robota",
272
+ "args": ["mcp", "serve"],
273
+ "cwd": "/absolute/path/to/trusted/project"
274
+ }
202
275
  }
203
276
  }
204
277
  ```
205
278
 
206
- ### Environment Variables
279
+ See [MCP](../../content/guide/mcp.md).
280
+
281
+ ### Add skills, commands, agents and plugins
207
282
 
208
- | Variable | Description | Required |
209
- | ------------------- | ----------------- | -------- |
210
- | `ANTHROPIC_API_KEY` | Anthropic API key | Yes |
283
+ Skills and commands are Markdown files the CLI finds in `<configured-project-state-dir>/skills/`, `.claude/skills/`,
284
+ `.claude/commands/` and `.agents/skills/` — in a trusted project and under your home directory. Each
285
+ one becomes a slash command (`/<name>`), and `/skills` lists them. Agent definitions are read from
286
+ `<configured-project-state-dir>/agents/`, `.agents/agents/` and `.claude/agents/`. Plugins bundle these together with hooks,
287
+ themes and MCP servers:
211
288
 
212
- Copy `.env.example` to `.env` and set your key. The CLI reads `.env` automatically in dev mode.
289
+ ```text
290
+ /plugin marketplace add <source>
291
+ /plugin install <name>@<marketplace>
292
+ /plugin # open the plugin manager
293
+ ```
294
+
295
+ See the [CLI guide](../../content/guide/cli.md) for skill frontmatter and plugin management.
213
296
 
214
- ## Context Discovery
297
+ ### Use the graphical interface
215
298
 
216
- The CLI automatically discovers and loads:
299
+ `robota --serve --open` starts a headless runtime for the current workspace, serves the configured GUI
300
+ on `127.0.0.1` and opens it in your browser. The Electron desktop app in this repository
301
+ ([`apps/agent-app`](../../apps/agent-app/docs/README.md)) shows the same GUI over the workspace
302
+ daemon; it is not published to npm. See [The GUI and the Desktop App](../../content/guide/gui.md).
217
303
 
218
- - **AGENTS.md** — walking up from cwd to filesystem root
219
- - **CLAUDE.md** — same walk-up discovery
220
- - **Project metadata** — from `package.json`, `tsconfig.json`
304
+ ### Let other apps and services use the agent
221
305
 
222
- All context is assembled into the system prompt for the AI assistant.
306
+ `robota --serve --http-port <port>` also serves the agent over HTTP on `127.0.0.1:<port>`, so another
307
+ application or service can use it without a Robota client library. Every request presents the
308
+ bearer you choose in `PRODUCT_HTTP_TOKEN` (at least 32 characters); the runtime removes it from its
309
+ environment, so the commands it runs do not inherit it. A command running as the same OS user can
310
+ still read the server's launch environment unless the OS sandbox confines it (or the server runs
311
+ as a separate user), so enable the sandbox on a server. With the bearer it binds loopback only.
223
312
 
224
- ## Architecture
313
+ To reach it from elsewhere, either put your own reverse proxy or tunnel in front, or serve it as an
314
+ OAuth resource server, the same way `robota mcp serve` does: give `--http-public-url`, `--oauth-issuer`,
315
+ `--oauth-scopes` and `--oauth-allowed-subjects` (optionally `--http-host <ip>` and `--trusted-proxy`)
316
+ and no `PRODUCT_HTTP_TOKEN`. Clients then present access tokens from that issuer whose audience is the
317
+ public URL, which carry the scopes and name an allowed subject. The routes below are served under the
318
+ public URL's path, and its RFC 9728 metadata at `/.well-known/oauth-protected-resource<path>`.
225
319
 
320
+ ```bash
321
+ PRODUCT_HTTP_TOKEN="$(openssl rand -hex 32)" robota --serve --http-port 8787
322
+ robota --serve --http-port 8787 --http-host 0.0.0.0 \
323
+ --http-public-url https://agents.example.com/agent --oauth-issuer https://auth.example.com \
324
+ --oauth-scopes agent.run --oauth-allowed-subjects client-a
226
325
  ```
227
- bin.ts → cli.ts (parseArgs, load config/context, create Session)
228
- ├── config/config-loader.ts — settings file discovery + Zod validation
229
- ├── context/context-loader.ts — AGENTS.md/CLAUDE.md walk-up discovery
230
- ├── context/project-detector.ts — package.json/tsconfig detection
231
- ├── context/system-prompt-builder.ts — system message assembly
232
- ├── session.ts — Robota agent wrapper + permission enforcement
233
- │ └── permissions/
234
- │ ├── permission-gate.ts — 3-step evaluation (deny → allow → mode)
235
- │ ├── permission-mode.ts — mode × tool policy matrix
236
- │ └── permission-prompt.ts — interactive [y/N] prompt
237
- ├── tools/ — 6 built-in tools (Bash, Read, Write, Edit, Glob, Grep)
238
- ├── session-store.ts — JSON file-based session persistence
239
- └── ui/ — Ink TUI components (App, MessageList, InputArea, etc.)
326
+
327
+ | Request | Does |
328
+ | --- | --- |
329
+ | `POST /submit` `{"prompt": "...", "receivePrompts"?: true}` | Runs a turn and streams it as SSE (`text_delta`, `complete`, `error`; with `receivePrompts`, also `permission_request`, `ask_request`, `prompt_resolved`); `409` while a turn is running |
330
+ | `GET /prompts` · `POST /prompts/<id>` `{"result": true}` | Lists the open permission/ask prompts · answers one (`{"response": ...}` for an ask) |
331
+ | `POST /command` `{"name": "help", "args": ""}` | Runs a slash command |
332
+ | `POST /abort` · `POST /cancel-queue` | Aborts the running turn · drops the queued prompt |
333
+ | `GET /messages` · `/context` · `/executing` · `/pending` | Reads the conversation and its state |
334
+
335
+ A client that submits with `receivePrompts: true` answers the permission and ask prompts its turn
336
+ raises; without it, a question no other client answers is denied, so choose the permission mode and
337
+ rules the server runs under.
338
+
339
+ `robota acp` serves the agent to an editor (Zed, JetBrains and other
340
+ [Agent Client Protocol](https://agentclientprotocol.com/) clients) over stdio. Configure the editor to
341
+ run `robota acp` with the project folder as its working directory. Workspace trust, permissions and
342
+ the sandbox apply as in the terminal, and the editor asks you to approve each tool call the terminal
343
+ would. See [Editors](../../content/guide/cli.md#editors-agent-client-protocol).
344
+
345
+ ### Reach other sessions and your other devices
346
+
347
+ `/peers` lists the other live `robota` sessions on this machine, and `/peers send <session-id>
348
+ <message>` sends one a message; the receiving session handles it under its own permissions, like
349
+ its own work. `/handoff <session-id>` moves this conversation to another session once both sides
350
+ confirm. To reach your other devices too, create a device identity with `/devices init`, link each
351
+ new device with `/devices add` and `/devices join`, and set `transports.mesh.enabled` to `true` in
352
+ your user settings. `/remote-control enable` pairs a browser to co-drive the current session.
353
+
354
+ See [Devices and remote control](../../content/guide/devices-and-remote.md).
355
+
356
+ ### Check your setup and usage
357
+
358
+ ```bash
359
+ robota doctor # settings layers, provider, trust, storage, plugins, hooks, MCP
360
+ robota usage # sessions, turns, tokens and cost for the last 7 days (--period 30d)
361
+ robota --check-update # is a newer version on npm?
362
+ robota eval <definition> # run an evals-as-code definition; exits 1 on a metric breach
240
363
  ```
241
364
 
242
- Tool calls flow through the permission system:
365
+ ## Configuration files
366
+
367
+ Settings are merged from these files, lowest priority first. The two user files always apply; the
368
+ four project files apply only in a trusted workspace.
369
+
370
+ | File | Scope |
371
+ | ---------------------------------------------------- | --------------------------------------- |
372
+ | `<configured-user-state-dir>/settings.json` | user |
373
+ | `~/.claude/settings.json` | user (Claude Code-compatible) |
374
+ | `<configured-project-state-dir>/settings.json` | project, committed |
375
+ | `<configured-project-state-dir>/settings.local.json` | project, local to this machine |
376
+ | `.claude/settings.json` | project (Claude Code-compatible) |
377
+ | `.claude/settings.local.json` | project, local (Claude Code-compatible) |
378
+
379
+ Other files the CLI keeps under `<configured-user-state-dir>/`:
380
+
381
+ | Path | Contents |
382
+ | ---------------------- | ---------------------------------------------------------------------------------------------- |
383
+ | `workspace-trust.json` | workspaces you have trusted |
384
+ | `sessions/` | saved sessions (a trusted project may keep them in `<configured-project-state-dir>/sessions/`) |
385
+ | `history.jsonl` | prompts you typed, for `Ctrl+R` (`"promptHistory": false` turns it off) |
386
+ | `keybindings.json` | key bindings |
387
+ | `themes/` | your own `/theme` themes |
388
+ | `plugins/` | installed plugins (a project may have its own `<configured-project-state-dir>/plugins/`) |
389
+ | `mcp-credentials/` | OAuth tokens for MCP servers, readable by you only |
390
+
391
+ ## Use it from code
392
+
393
+ The package also exports `startCli`, the function the `robota` executable runs, and its options
394
+ type `IStartCliOptions`. The package is ESM-only: load it with `import`, not `require()`.
395
+
396
+ A subagent runs in a child process that starts your entry script again with a worker flag; when
397
+ `startCli()` sees that flag it runs the subagent instead of the CLI, and the returned promise never
398
+ settles (the worker ends the process). So call `startCli()` from the script Node started, and put
399
+ nothing before it that must not run once per subagent.
400
+
401
+ ### Hosted task-worker execution
402
+
403
+ The library also exports `HostedOrganizationControl` and `createHostedOrganizationGateway` for
404
+ the owner-hosted company backend. This composition requires an anchored `OrganizationLedger`,
405
+ an `OrganizationAudit`, provider-backed worker inventory and named detection, containment,
406
+ assessment and recovery owners. The gateway serves the configured admission endpoints and
407
+ OpenAI model endpoints; its worker ingress has no policy, approval or recovery routes.
408
+ Each model-only grant has a distinct owner-issued task token and runtime binding. The logical task
409
+ root survives grant rotation; provider inventory and gateway admission pin its current binding. Issuer signing
410
+ capabilities, upstream credentials, policy, audit and private payload storage stay outside workers.
411
+ Sign task access with the exported `hostedWorkerAccessBytes` and provision E2B workers with an
412
+ `organization` binding; `createE2BOrganizationInventory` enumerates all pages, including paused
413
+ workers, and rechecks ownership before deletion. Owner stop methods persist withdrawal first,
414
+ abort affected broker calls, and wait for descendant provider cleanup; unresolved cleanup is
415
+ reported to the incident owners.
416
+
417
+ `HostedOrganizationPayloads` stores bounded request/response bytes in an owner-only directory;
418
+ the durable journal and audit retain digests rather than these bodies. Install
419
+ `createHostedOrganizationModelAction` with a fixed provider endpoint/model, owner-selected
420
+ reservation, input/output bounds and explicit upstream idempotency support. A mandatory trusted
421
+ `countInputTokens` port counts the exact immutable upstream input before dispatch; input plus the
422
+ owner-selected output cap must fit the token and conservative cost reservation.
423
+ `createHostedOrganizationInputTokenCounter` connects a fixed private counting endpoint through an
424
+ owner-selected encoder. A Responses counter does not establish exact Chat token counts; select a
425
+ verified counter for that surface. The counting service must be read-only and covered by the
426
+ owner's pricing assumptions. The gateway buffers
427
+ the upstream result until durable settlement, then returns JSON or OpenAI stream events.
428
+ Configure the worker's OpenAI profile with `options: { durableOperations: true }`: one operation
429
+ identity survives SDK retries, and missing identities are refused. Unknown provider outcomes
430
+ retain reservations and require asset-owner reconciliation; this adapter never retries upstream.
431
+ The private payload store retains digest-bound operation claims for recovery. Owner code may
432
+ load those claims with `operation` and call `reconcileExternalOperation` with confirmed asset-owner
433
+ evidence. Reconciliation charges the entire retained reservation and cannot restore revoked
434
+ authority; the worker gateway exposes no reconciliation route.
435
+ Broker supervisory token/cost observations report settled usage; pending reservations remain
436
+ enforced by the hierarchical ledger. Local process and wire tests establish these software paths,
437
+ while physical containment, provider billing and independent remote custody require deployment
438
+ measurements.
439
+
440
+ Hosted posture requires current signed admission for a separate task worker and company broker.
441
+ The installed E2B executor runs the stock CLI inside that worker. The runtime forwards arguments,
442
+ input and output and owns cancellation, credential expiry and provider deletion; it does not run
443
+ task tools against its own filesystem. Direct `--api-key` configuration is refused.
444
+
445
+ `PRODUCT_HOSTED_RUNTIME_CONFIG` selects an owner-controlled version-3 deployment file. It retains
446
+ `identity`, `epoch`, `worker`, `broker`, `snapshot`, `lifetimeMs` and `probeTimeoutMs`, and requires
447
+ `limits: { sessions, modelCalls, modelTokens, costMicros }` with nonnegative safe integer ceilings.
448
+ Version-2 deployments and proofs are refused because they cannot attest current accounting.
449
+ The worker and broker answer version-3 admission challenges with the configured limits; worker
450
+ proofs carry `usage: null`, while broker proofs carry cumulative usage in that same four-counter
451
+ shape. The broker measures registered sessions and provider calls outside the worker for the
452
+ current identity and epoch. Counts and micro-cost units come from its trusted instrumentation,
453
+ not CLI output, model messages or a checkpoint. Both attestations sign the fixed tuple returned by
454
+ the exported `hostedAdmissionBytes`, binding limits and accounting to the current challenge,
455
+ identity, root, epoch and checkpoint. The issuer must validate requested limits against owner policy
456
+ before attesting them.
457
+
458
+ `HostedRuntimeController.status()` exposes the latest verified observations to the owner. The
459
+ controller checks them before executor allocation, during admission refresh and after short
460
+ executions before reporting success. Invalid, regressing, unavailable or over-limit observations
461
+ refuse admission or stop and release owned execution. Runtime ceilings supervise observed totals;
462
+ the broker's per-request reservations must bound new model calls and their maximum token/cost
463
+ usage before dispatch. Supervisory polling does not replace those reservations or establish an
464
+ invoice hard cap. The broker must retain cumulative accounting across controller restart/resume;
465
+ recovered task content cannot reset it. Actual provider charges and cleanup remain separately
466
+ measured operational evidence.
467
+
468
+ The operator supplies `PRODUCT_E2B_API_KEY` to the runtime and sets
469
+ `PRODUCT_HOSTED_WORKER_EXECUTION_CONFIG` to an absolute owner-only JSON file. Its version-1 shape is:
243
470
 
471
+ ```json
472
+ {
473
+ "version": 1,
474
+ "templateId": "operator-pinned-template",
475
+ "nodeExecutable": "/usr/local/bin/node",
476
+ "workspaceRoot": "/workspace",
477
+ "entrypoint": { "path": "/opt/agent/cli.mjs", "digest": "<sha256>" },
478
+ "productConfig": { "path": "/opt/agent/product.env", "digest": "<sha256>" },
479
+ "access": {
480
+ "version": 1,
481
+ "identity": {
482
+ "tenant": "...",
483
+ "task": "...",
484
+ "rootTask": "...",
485
+ "actor": "...",
486
+ "runtime": "..."
487
+ },
488
+ "worker": "...",
489
+ "broker": "...",
490
+ "epoch": 1,
491
+ "endpoint": "https://company-broker.example/v1",
492
+ "token": "<task-scoped-broker-token>",
493
+ "issuedAt": 0,
494
+ "expiresAt": 0,
495
+ "signature": "<broker-ed25519-base64url-signature>"
496
+ }
497
+ }
244
498
  ```
245
- AI agent requests tool call
246
- → Session.wrapToolWithPermission() intercepts execute()
247
- → evaluatePermission(toolName, args, mode, allow/deny lists)
248
- → deny list match? → blocked
249
- → allow list match? → auto-approved
250
- → mode policy lookup → auto | approve | deny
251
- → if 'approve': promptForApproval() → user types y/N
252
- → if allowed: original tool.execute() runs
253
- → if denied: returns error result to AI agent
499
+
500
+ The broker signs the fixed tuple encoded by `hostedWorkerAccessBytes` in the implementation.
501
+ Access must match the admission's identity, resources and epoch, use the admitted broker origin,
502
+ and expire within both admission and sixty seconds of issuance. This executor ends execution at
503
+ expiry; it does not renew credentials. The template's CLI and product environment artifacts must
504
+ match their pinned digests. The product artifact accepts only CLI product metadata variables.
505
+ Provider metadata must match task ownership and the template, with public traffic disabled and
506
+ outbound access limited to the broker hostname and no attached volumes. The resource must already
507
+ be running, with timeout deletion and auto-resume disabled; connecting a paused memory image is
508
+ refused. These checks do not establish measured cloud containment.
509
+
510
+ An owner backend can call `provisionE2BTaskWorker` before issuing admission. It creates the
511
+ operator-selected clean template with closed networking, no worker credentials or volumes, and
512
+ an exclusive new workspace. It returns the resource, workspace root, operation ID, checkpoint
513
+ reference, optional conversation artifact receipt and an idempotent `release()` operation. The management capability stays in this
514
+ owner-side SDK composition. After the receipt, the owner signs current worker/broker admission
515
+ and task access for the returned resource and configures that workspace in the execution input.
516
+
517
+ For recovery, supply an owner-approved `{ id, digest }` reference and the matching UTF-8 JSON bytes
518
+ in `checkpoint`. The manifest has `version: 1`, `id`, the source `identity`, `epoch`, `worker`, and
519
+ `files: [{ path, base64 }]`. Paths are relative regular workspace files; private HOME/TMPDIR and link or memory records
520
+ are not accepted. Owner authority journals stay outside this workspace. The source must belong
521
+ to the same tenant/task/root and precede the current epoch and runtime. File bytes are read back
522
+ and compared before the owner receives a successful result. Treat recovered project content as
523
+ untrusted; current profile, policy and credentials come from the clean operator composition.
524
+ For conversation recovery, use manifest `version: 2` with the same fields and `session` containing
525
+ an existing versioned session-record envelope (`{ schemaVersion, record }`). Only the conversation,
526
+ name, selector and timestamps survive. Saved system messages/prompts, message metadata, tool schemas,
527
+ VM state, goals, background jobs, loops, memory references and branch pointers do not restore;
528
+ current composition rebuilds execution and authority. Display history is rebuilt from the projected
529
+ conversation; saved events are not imported. The record's cwd becomes the fresh workspace.
530
+ Pass the returned `resumeSession: { id, path, digest }` in the private worker execution input and
531
+ invoke `--resume <id>` (or `--continue`, optionally with `--fork-session`). The default executor
532
+ verifies the receipt and copies the projected record into the current product artifact's selected
533
+ private user session store before CLI startup. It refuses an existing destination, mismatched
534
+ selector/digest, malformed record or a user state root inside recovered project files. No saved
535
+ settings, trust decision or credential is imported. A resume without this receipt refuses execution.
536
+
537
+ The default executor accepts this filesystem checkpoint only when signed current admission and
538
+ provider metadata agree on its digest, fresh identity/epoch and source resource. It does not
539
+ restore provider memory snapshots or reconnect the checkpoint source. Setup failures delete
540
+ verified allocations; unknown creation/ownership and unresolved deletion return
541
+ `E2BWorkerProvisioningError` with reconciliation identifiers. The external owner must retain and
542
+ reconcile those outcomes rather than retry or report cleanup as successful.
543
+
544
+ The worker receives a closed environment with private HOME/TMPDIR, product configuration and the
545
+ scoped broker token through the OpenAI-compatible provider interface. Management and upstream
546
+ credentials stay outside the worker. Interactive input uses a PTY; input EOF requests the stock
547
+ CLI's Ctrl-C shutdown. For `--serve`, include `serve: { port, workerPort, token }` in the private
548
+ execution input. `port` selects the runtime's IPv4 loopback ingress (`0` assigns a free port);
549
+ `workerPort` selects the worker daemon port. Generate a fresh, unpredictable owner client `token`
550
+ of 32–256 URL-safe characters and connect to the printed runtime URL with `?token=<token>`.
551
+ The runtime validates the client token, Host and Origin, then carries binary WebSocket traffic
552
+ through an owned provider command to the pinned worker loopback listener. It replaces client
553
+ headers and credentials with a separate per-launch worker token. The owner token and provider
554
+ management capability stay in the runtime. Stopping execution closes the listener and clients
555
+ and deletes the worker. This local ingress does not provide remote desktop pairing or TLS.
556
+ Credential renewal requires broker integration; this executor still ends at its signed access expiry.
557
+
558
+ Embedders can override the installed executor with `hostedRuntimeExecutorFactory`. It receives
559
+ `(admission, signal, invocation)` with immutable captured arguments and returns `run`, `stop` and
560
+ `release`. The adapter owns authenticated communication, input/output and trusted usage reporting;
561
+ stop and release settle only after cleanup. Missing or invalid authority refuses execution without
562
+ entering local host adapters. CLI flags request behavior and never confer company authority.
563
+
564
+ ## Remote desktop owner composition
565
+
566
+ Use `HostedDesktopWorkerPort.connect({ endpoint, token, worker, control })` with the owner's private
567
+ runtime loopback ingress. It discovers and pins the actual worker session; retain this carrier across
568
+ desktop reconnects. Create `HostedDesktopAuthorization` with the public HTTPS URL, pinned issuer,
569
+ current `HostedOrganizationControl`, and owner-selected bindings containing `id`, `user`, `client`,
570
+ the discovered `session`, and the same `worker`. Give `HostedDesktopGateway` the owner's HTTPS server,
571
+ that authorization, a `HostedDesktopAuthorityStore` and a binding-ID-to-worker-port map. The store uses
572
+ a private existing directory and an independently held organization ledger anchor; `create: true` is only
573
+ for first creation. Store loss, rollback or an uncertain anchor acknowledgment refuses access.
574
+
575
+ The issuer signs a short-lived asymmetric JWT with exact `sub`, `client_id`, `tenant`, `task`, `session`,
576
+ `workload` and `epoch` binding claims, unique `jti`, `iat`, and `exp`. Ordinary credentials have the public
577
+ URL as audience and `desktop:pair desktop:drive` scopes. A separate operator credential has
578
+ `<publicUrl>/approval` as audience and `desktop:approve` scope. Credentials expire within two minutes;
579
+ pairing grants at most one minute and never extends current company authority. Reconnect uses new JWTs,
580
+ withdraws the previous connection and preserves the pinned worker session. Issuer/JWK outages and current
581
+ grant withdrawal close active access. `gateway.revoke(bindingId)` is an owner-only withdrawal API.
582
+
583
+ Remote traffic requires TLS and the pinned Host/Origin. TLS termination requires explicitly selected
584
+ proxy IPs with matching HTTPS forwarding headers; arbitrary proxy headers are refused. The renderer
585
+ cannot switch tasks/sessions, execute slash commands, change settings, or approve permissions. The
586
+ main-process operator channel binds Allow once/Deny to the currently pending operation digest and
587
+ rejects replay. Run all authority stores and gateway/control composition outside the task worker.
588
+ These local integration checks do not establish deployed cloud containment or operational latency.
589
+
590
+ ## Work on the CLI in this repository
591
+
592
+ ```bash
593
+ pnpm install && pnpm build
594
+ pnpm cli:dev # run the CLI from source
595
+ pnpm cli:trust # trust this repository for the source CLI
254
596
  ```
597
+
598
+ ## Documentation
599
+
600
+ - [CLI guide](../../content/guide/cli.md) — every command, flag and setting
601
+ - [Sessions and the daemon](../../content/guide/sessions-and-daemon.md)
602
+ - [Permissions and hooks](../../content/guide/permissions-and-hooks.md)
603
+ - [Providers](../../content/guide/providers.md) and [Local LLM setup](../../content/guide/local-llm.md)
604
+ - [MCP](../../content/guide/mcp.md)
605
+ - [Devices and remote control](../../content/guide/devices-and-remote.md)
606
+ - [SPEC.md](./docs/SPEC.md) — what this package owns and guarantees
607
+
608
+ ## License
609
+
610
+ This package is dual-licensed under the [GNU AGPL-3.0](../../LICENSE) or a
611
+ [commercial license](../../COMMERCIAL.md). See [LICENSING.md](../../LICENSING.md).