rubino-agent 0.5.1 → 0.5.2.1

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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +71 -0
  3. data/docs/agents.md +11 -19
  4. data/docs/api/v1.md +2 -0
  5. data/docs/commands.md +3 -6
  6. data/docs/configuration.md +13 -6
  7. data/docs/oauth-providers.md +21 -0
  8. data/lib/rubino/agent/prompts/build.txt +7 -0
  9. data/lib/rubino/api/operations/tasks/stop_operation.rb +0 -3
  10. data/lib/rubino/attachments/classify.rb +0 -1
  11. data/lib/rubino/cli/chat/completion_builder.rb +0 -8
  12. data/lib/rubino/cli/chat_command.rb +208 -157
  13. data/lib/rubino/commands/built_ins.rb +0 -1
  14. data/lib/rubino/commands/executor.rb +1 -7
  15. data/lib/rubino/commands/handlers/agents.rb +28 -247
  16. data/lib/rubino/compression/line_skeleton.rb +1 -1
  17. data/lib/rubino/compression/python_code_skeleton.rb +1 -1
  18. data/lib/rubino/compression/ruby_code_skeleton.rb +1 -1
  19. data/lib/rubino/compression/tree_sitter_code_skeleton.rb +1 -1
  20. data/lib/rubino/config/configuration.rb +22 -10
  21. data/lib/rubino/config/defaults.rb +42 -20
  22. data/lib/rubino/context/token_budget.rb +0 -5
  23. data/lib/rubino/errors.rb +2 -2
  24. data/lib/rubino/llm/anthropic_role_merge.rb +75 -0
  25. data/lib/rubino/llm/error_classifier.rb +34 -1
  26. data/lib/rubino/llm/fake_provider.rb +0 -4
  27. data/lib/rubino/llm/ruby_llm_adapter.rb +44 -39
  28. data/lib/rubino/llm/stream_tool_call_recovery.rb +91 -0
  29. data/lib/rubino/llm/tool_call_recovery.rb +177 -0
  30. data/lib/rubino/memory/sqlite_extraction_prompt.rb +0 -2
  31. data/lib/rubino/memory/store.rb +0 -19
  32. data/lib/rubino/security/pattern_matcher.rb +0 -2
  33. data/lib/rubino/security/secret_path.rb +16 -4
  34. data/lib/rubino/skills/registry.rb +16 -2
  35. data/lib/rubino/tools/background_tasks.rb +25 -216
  36. data/lib/rubino/tools/base.rb +0 -16
  37. data/lib/rubino/tools/grep_tool.rb +13 -1
  38. data/lib/rubino/tools/question_tool.rb +3 -4
  39. data/lib/rubino/tools/steer_tool.rb +3 -4
  40. data/lib/rubino/tools/task_stop_tool.rb +5 -7
  41. data/lib/rubino/tools/task_tool.rb +7 -24
  42. data/lib/rubino/tools/write_tool.rb +22 -2
  43. data/lib/rubino/ui/agent_menu.rb +0 -2
  44. data/lib/rubino/ui/bottom_composer.rb +216 -363
  45. data/lib/rubino/ui/cli.rb +129 -142
  46. data/lib/rubino/ui/input_history.rb +0 -5
  47. data/lib/rubino/ui/live_region.rb +18 -1
  48. data/lib/rubino/ui/markdown_renderer.rb +47 -3
  49. data/lib/rubino/ui/markdown_repair.rb +114 -0
  50. data/lib/rubino/ui/notifier.rb +4 -10
  51. data/lib/rubino/ui/streaming_markdown.rb +12 -0
  52. data/lib/rubino/ui/subagent_cards.rb +10 -37
  53. data/lib/rubino/util/ignore_rules.rb +18 -2
  54. data/lib/rubino/util/secrets_mask.rb +0 -9
  55. data/lib/rubino/version.rb +1 -1
  56. data/lib/rubino.rb +33 -7
  57. data/rubino-agent.gemspec +1 -0
  58. metadata +20 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c1debe685b923c625e0dc4dcf95da3c9fc12fcd6c73bdff71e35164279e62b06
4
- data.tar.gz: 5451e122fc13bfdd4ffeba0e680cad9fb6b976dfabe8f9dcfd894e5215ac9688
3
+ metadata.gz: 45e009503b875320e560be8ce46459d7c2a70b0c5744043b70f2121c5e747ab2
4
+ data.tar.gz: c26d1f586ae8578ed3d7298d1f7f8765d16ed5328b6d063d77e0df41d32a710a
5
5
  SHA512:
6
- metadata.gz: bf657914d128053ffa39d7911a5c2c12e491ff45b1907e8bc78a694b8f8d7540a1e8d1182ee5831670af17e5e64b16148202d987ebc9e2c75604a88f78148d36
7
- data.tar.gz: eefe6fbbcd977bff1cf8b7a189fdaf73daee9ca6b12ca55b82876a99c55ede529dc78ba3f5146f8b34a7e6e6b6b38dab454c9d6cdcf3baca8e754187f49c6829
6
+ metadata.gz: dc1cfd93fb51e6236daf8e839a8df248fdcd299aef3028c258fefc93c585bf15e322b5d41ebf4d1b504795a9d24d6e8668f5dece2089b9454f6432dabcace482
7
+ data.tar.gz: 5495d4862d7b082826205ca4ecb30701df4a103ce801233df6a3dc91c59ce423ea4cb6cab4e41b9b0327f72e911ad310bf9433aaf6d218d8803bb0ffd56bec65
data/CHANGELOG.md CHANGED
@@ -1,5 +1,76 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.5.2.1] - 2026-06-26
4
+
5
+ ### Fixed
6
+
7
+ - **Symlinked workspace roots broke three path checks.** Several modules compared
8
+ a symlink-resolved path against a NON-resolved root, so a workspace reached
9
+ through a symlink (macOS `/etc` → `/private/etc`, `/var` → `/private/var`, or
10
+ any symlinked checkout) defeated the match:
11
+ - **SecretPath** — `secret?("/etc/sudoers")` returned `false` and the
12
+ `~/.ssh`/`~/.aws`/… credential read-gate classified nothing, silently
13
+ no-op'ing the write-approval gate and read-block for those paths.
14
+ - **IgnoreRules** — `git rev-parse --show-toplevel` returns the realpath, so
15
+ the allowed-set rebase dropped *every* file and the whole tree read as
16
+ git-ignored; `grep`/`glob` then returned nothing under a symlinked checkout.
17
+ - **Skills::Registry** — an untrusted repo's project-local `.rubino/skills` was
18
+ not recognised as project-local, so the trust gate failed to drop it (hostile
19
+ project skills could load in an untrusted directory).
20
+
21
+ All three now resolve both sides of the comparison through `realpath` /
22
+ `canonical_path`. Defense-in-depth — not security boundaries.
23
+ - **`grep` Ruby fallback now matches dotfiles.** Without ripgrep on PATH, the
24
+ fallback globbed `**/<include>` without `FNM_DOTMATCH`, so an include like
25
+ `*.env` never matched `.env`/`.envrc` — exactly the secret-bearing files. The
26
+ include glob now matches dotfiles, mirroring `rg --glob`.
27
+
28
+ ## [0.5.2] - 2026-06-26
29
+
30
+ ### Added
31
+
32
+ - **Live formatted-markdown streaming.** The in-flight model stream now renders
33
+ as formatted markdown while it arrives (Stage 1), painted as atomic frames via
34
+ DEC-2026 synchronized output so a fast stream never tears mid-update (Stage 2),
35
+ with committed code blocks syntax-highlighted through Rouge (Stage 3). (#592,
36
+ #593, #594)
37
+ - **Leaked tool-call recovery.** Models that emit a tool call as plain text or
38
+ garbled XML/JSON markup instead of a structured call (MiniMax-M3 and other
39
+ tool-loop models) now have those calls re-parsed into real `tool_calls` at the
40
+ transport layer so they actually execute, including a garbled `<invoke">`
41
+ variant.
42
+ - **`write` content preview.** The `write` tool box now shows a preview of the
43
+ content being written.
44
+
45
+ ### Changed
46
+
47
+ - Raise the default `max_tool_iterations` from 25 to 90 (Hermes-aligned), so long
48
+ tool-driven turns no longer hit the ceiling mid-task.
49
+ - Teach the agent (via the build prompt) to read the compressed tool-output
50
+ markers introduced in 0.5.1.
51
+
52
+ ### Fixed
53
+
54
+ - Render any unterminated code fence as a code box, matching CommonMark's
55
+ end-of-file fence auto-close, instead of leaking the raw backticks. (#595)
56
+ - Merge consecutive same-role messages on the Anthropic-family wire so the
57
+ request shape stays valid. (#597)
58
+ - Give MiniMax its full output ceiling so a long thinking block no longer starves
59
+ the visible output (a root cause of heavy-turn "invalid params" death).
60
+ - Keep a 5xx-wrapped "invalid params" response on the retryable path.
61
+ - Multi-line `ask()` prompts no longer erase terminal scrollback.
62
+ - Exclude synthetic `[harness control]` injections from the rewind picker.
63
+ - Fix an installed-gem launch crash (`uninitialized constant Rubino::TAGLINE`).
64
+
65
+ ### Removed
66
+
67
+ - Drop the dead `server.*` config section, the orphaned `ask_parent` takeover and
68
+ ask/reply substrate, and dead code surfaced by the post-removal audit.
69
+
70
+ ### Docs
71
+
72
+ - Mark native OAuth as not wired end-to-end (WIP).
73
+
3
74
  ## [0.5.1] - 2026-06-25
4
75
 
5
76
  ### Added
data/docs/agents.md CHANGED
@@ -4,7 +4,7 @@ rubino has two distinct multi-agent surfaces, and **both ship today**:
4
4
 
5
5
  1. **Background subagents** (✅ shipping) — the agent delegates bounded sub-tasks
6
6
  to isolated subagent runs via its `task` tool, and you supervise them with
7
- `/agents` and `/reply`.
7
+ `/agents`.
8
8
  2. **Primary-agent switching** (✅ shipping) — pick the primary agent that
9
9
  handles your turns: `/agent <name>` (or a bare `/<name>` for a primary)
10
10
  pins it for the session, **Tab** cycles through the primaries, and a one-shot
@@ -68,9 +68,7 @@ message instead of fanning out unbounded work:
68
68
  | Glyph | Status | Meaning | You act via |
69
69
  |---|---|---|---|
70
70
  | `●` | `running` | Working (last activity shown) | — |
71
- | `●` | `needs_approval` | A child tool needs your approval (or a budget request) | `/agents <id>` or `/reply <id>` |
72
- | `⛔` | `blocked_on_human` | Vocabulary glyph for a child parked on the human (not raised in normal operation now that subagents are non-blocking) | `/reply <id> <answer>` |
73
- | `◷` | `blocked_on_parent` | Vocabulary glyph for a child parked on its agent-parent (likewise not raised now that subagents are non-blocking) | (optional) `/reply <id>` |
71
+ | `●` | `needs_approval` | A child tool needs your approval (or a budget request) | `/agents <id>` |
74
72
  | `◌` | `stopping` | Stop requested; unwinding at its next checkpoint | — |
75
73
  | `✓` | `done` | Finished; result available | `/agents <id>` |
76
74
  | `✗` | `failed` | Errored; error available | `/agents <id>` |
@@ -79,25 +77,20 @@ message instead of fanning out unbounded work:
79
77
  Subagents are **non-blocking** background workers: they never pause to ask you a
80
78
  mid-task question. The one way a child waits on you is an **approval** — its next
81
79
  tool needs your go-ahead, so it parks as `needs_approval` and a marker persists
82
- until you resolve it (via `/agents <id>` or `/reply <id>`). The `⛔
83
- blocked_on_human` / `◷ blocked_on_parent` glyphs remain in the status vocabulary
84
- the `/agents` surface can render, but with the child→parent ask channel removed
85
- they are no longer raised in normal operation.
80
+ until you resolve it (via `/agents <id>`).
86
81
 
87
- ### Supervising from the CLI: `/agents` and `/reply`
82
+ ### Supervising from the CLI: `/agents`
88
83
 
89
84
  ```
90
85
  /agents # list background subagents (status, tools run, activity)
91
86
  /agents <id> # drill in: live watch while running, result/error when done
92
- /agents <id> --stop # cancel a running subagent (blocked descendants unwind too)
87
+ /agents <id> --stop # cancel a running subagent
93
88
  /agents <id> steer "note" # park a note folded into the child's context at its next turn
94
89
  /agents <id> probe "question" # ephemeral read-only peek — nothing is saved to the child
95
- /reply <id> <answer> # answer a child blocked on you (e.g. an approval)
96
- /reply # bare: list the subagents currently blocked on you
97
90
  ```
98
91
 
99
- `/tasks` is an alias for `/agents`. Stopping a node cancels its descendants'
100
- approval gates too, so anything parked anywhere in the subtree unwinds at once.
92
+ `/tasks` is an alias for `/agents`. Stopping a node cancels its approval gate so
93
+ a parked child unwinds at once.
101
94
 
102
95
  #### Attach to a subagent (agent-view)
103
96
 
@@ -109,12 +102,11 @@ subagent picker, arrow to one, and `Enter`:
109
102
  what it said, replayed from its session (not the bounded activity snapshot the
110
103
  picker used to show);
111
104
  - the prompt becomes **scoped** to it: `sa_xxxx ❯`;
112
- - while attached, just **type** to steer the running child (or answer it if it's
113
- blocked on you) — no id needed; `←` on the empty prompt (or `/detach`) returns
114
- to the main timeline.
105
+ - while attached, just **type** to steer the running child no id needed; `←` on
106
+ the empty prompt (or `/detach`) returns to the main timeline.
115
107
 
116
- So attaching makes `/agents <id> steer/probe` and `/reply <id>` redundant for the
117
- focused child — they're the same operations, just addressed by id. Attach is a
108
+ So attaching makes `/agents <id> steer/probe` redundant for the focused child
109
+ they're the same operations, just addressed by id. Attach is a
118
110
  between-turns action (it owns the screen): while a parent turn is still streaming
119
111
  the picker's `Enter` toasts "attach when the turn ends" — attach once it's idle.
120
112
 
data/docs/api/v1.md CHANGED
@@ -350,6 +350,8 @@ Cooperative cancel of a running task (descendant ask-gates are cancelled too). R
350
350
 
351
351
  ## OAuth
352
352
 
353
+ > **WIP — not wired end-to-end.** These endpoints work and store encrypted tokens, but **no tool consumes a connection's token yet** and there is no CLI surface. See the status banner in [`docs/oauth-providers.md`](../oauth-providers.md) (and issue #590: native vs MCP-delegated).
354
+
353
355
  See [`docs/oauth-providers.md`](../oauth-providers.md) for the full PKCE flow, encryption key requirements, and per-provider setup. The HTTP surface:
354
356
 
355
357
  ### `GET /v1/oauth/providers` → 200
data/docs/commands.md CHANGED
@@ -179,7 +179,6 @@ Type these inside `rubino chat`. Generated from `BuiltIns::DESCRIPTIONS` (drift-
179
179
  | `/agent` | Switch the primary agent (/agent <name>; a bare /<name> or Tab cycles) |
180
180
  | `/agents` | List background subagents; ↓+Enter to attach & steer one live, or steer/probe/view by id |
181
181
  | `/tasks` | Alias for /agents |
182
- | `/reply` | Answer a subagent that is blocked waiting on you (e.g. an approval) |
183
182
  | `/stop` | Stop a running subagent (/stop <id>; alias for /agents <id> --stop) |
184
183
  | `/jobs` | List the background job queue (status counts); /jobs <id> for detail |
185
184
  | `/skills` | List skills; activate one ('none' clears), or enable/disable NAME |
@@ -317,7 +316,7 @@ Read (and set) configuration without leaving the REPL, over the same **effective
317
316
 
318
317
  Gets resolve default-valued keys (not just what's in the file), and secret-named keys (`api_key`, tokens, …) render masked — exactly like `rubino config show`. A set writes through `Config::Writer` (the same persist path `/reasoning` and `/think` use) **and** updates the live configuration, so it survives the session and applies from the next turn; consumers that memoize their config (e.g. the memory backend) still need a restart. Typing `/config ` opens a dropdown with the verbs plus the known config keys flattened from the defaults tree; after `get`/`set` the keys complete again.
319
318
 
320
- ### Background subagents: `/agents` and `/reply`
319
+ ### Background subagents: `/agents`
321
320
 
322
321
  The agent spawns background subagents with its `task` tool; these commands are the human surface over them (full model in [agents.md](agents.md)):
323
322
 
@@ -327,8 +326,6 @@ The agent spawns background subagents with its `task` tool; these commands are t
327
326
  /agents <id> --stop # cancel a running subagent (blocked descendants unwind too)
328
327
  /agents <id> steer "note" # park a note folded into the child's context at its next turn
329
328
  /agents <id> probe "question" # ephemeral read-only peek — nothing is saved to the child
330
- /reply <id> <answer> # answer a subagent blocked on you (e.g. an approval)
331
- /reply # bare: list the subagents currently blocked on you
332
329
  ```
333
330
 
334
331
  `/tasks` is an alias for `/agents`.
@@ -337,9 +334,9 @@ The agent spawns background subagents with its `task` tool; these commands are t
337
334
  idle prompt to open the subagent picker, arrow to one, and `Enter` to **attach**:
338
335
  the screen switches to that agent's own full timeline (its tool calls and what it
339
336
  said, replayed) and the prompt becomes scoped — `sa_xxxx ❯`. While attached, just
340
- type to steer the running child (or answer it if it's blocked on you); `←` on the
337
+ type to steer the running child; `←` on the
341
338
  empty prompt (or `/detach`) returns to the main timeline. The scoped prompt makes
342
- the global `/agents <id> steer/probe` and `/reply <id>` forms redundant — they're
339
+ the global `/agents <id> steer/probe` forms redundant — they're
343
340
  the same operations, by id, from anywhere.
344
341
 
345
342
  ### Workspace roots: `/add-dir` and `/dirs`
@@ -227,6 +227,9 @@ display:
227
227
  statusbar: true # the model + context bar under the chat input
228
228
  tool_output_preview_lines: 3 # head lines of tool output shown in the transcript (0 = full dump)
229
229
  input_max_rows: 8 # chat input grows up to this many rows, then scrolls
230
+ live_markdown: true # format the in-flight streamed block live (false = raw live tail)
231
+ synchronized_output: true # atomic frames via DEC-2026 BSU/ESU (false = legacy per-write frames)
232
+ code_highlight: true # syntax-highlight committed code blocks (Rouge); false = plain
230
233
 
231
234
  paste:
232
235
  collapse_lines: 5 # pastes longer than this collapse to a placeholder
@@ -246,6 +249,10 @@ context:
246
249
  - `display.statusbar` (default `true`) pins a dim one-line bar UNDER the chat input — the session mode first (plus the branch/skill tokens when set), then the resolved model id and context saturation, e.g. `default · MiniMax-M3 · ctx ~8.4k/64k (13%)` (the percentage is omitted below 1%). The mode token is the live mode indicator (the prompt itself is a constant `▍❯ `): dim `default`, yellow `plan`, red `yolo`. Saturation uses the REAL usage the provider reported for the last response when available (the full assembled prompt, recorded by the agent loop), else the same chars/4 estimate compaction runs on (`Context::TokenBudget`); the window comes from `model.context_length` / `context.max_tokens`. It refreshes at turn boundaries (after each turn footer, and on session resume), never per stream delta. The percentage turns yellow at 70% and red at 90%; with no usable window only the token count shows. The bar is omitted off a TTY or on terminals narrower than 40 columns.
247
250
  - `display.tool_output_preview_lines` (default `3`) caps how many head lines of each tool's output the transcript shows before a dim `… +N lines (full output → context)` marker. DISPLAY-ONLY: the model always receives the full output (subject to the `tool_output` truncation caps) — only the scrollback rendering collapses. Set `0` to restore the old full dump.
248
251
  - `display.input_max_rows` (default `8`) caps how many visual rows the chat input grows to as a long or multi-line prompt wraps; past the cap the input scrolls vertically, keeping the caret row in view.
252
+ - `display.live_markdown` (default `true`) renders the still-streaming (in-flight) block as FORMATTED markdown in the live region — bold, headings, lists and code style as the tokens arrive, with syntax left open by the partial stream repaired (an open code fence shows as a code block, a dangling `**`/`` ` `` span is closed) so no raw marker leaks. Set `false` for the legacy raw rolling-tail that only snaps to styled when the block commits. Display-only; the committed scrollback render is identical either way.
253
+ - `display.synchronized_output` (default `true`) wraps each live-region frame in DEC private mode 2026 (BSU/ESU synchronized output) so a supporting terminal (kitty, WezTerm, tmux ≥3.4, recent xterm.js) buffers the whole clear→commit→redraw sequence and swaps it in one atomic update — no flicker or tearing on multi-step repaints. Terminals without support silently ignore the mode (it degrades cleanly); the escapes are emitted only to a real TTY. Set `false` for the legacy per-write frames.
254
+ - `display.code_highlight` (default `true`) syntax-highlights fenced code blocks by language (via Rouge) in the COMMITTED render — the live tail stays unstyled, so highlighting never blocks the stream (code shows instantly, colours arrive a beat later when the block commits, like Claude Code). Unknown languages, language-less fences, and any failure fall back to the plain code body. Set `false` for plain (uncoloured) code blocks.
255
+ - An **unterminated** code fence at end-of-stream — a fence the model never closed, or closed with a too-short bare run of backticks (e.g. MiniMax-M3 emitting `` against a ``` opener) — is rendered as a code box, matching CommonMark's end-of-document auto-close (§4.5) that every other renderer relies on. The CLI synthesises the close at the opener length (never relaxing the "close ≥ opener" rule), because kramdown does not auto-close an open fence.
249
256
  - `paste.collapse_lines` (default `5`) — the file-backed paste pipeline's first tier. Pasting MORE than this many lines into the chat input inserts a single cyan `[Pasted text #N +M lines]` placeholder instead of flooding the composer; the placeholder is one editable token (backspace deletes it whole, you can type around it, it survives ↑ draft recall and Alt+Enter queueing) and expands to the full pasted body when the message is sent — the model sees everything, while the transcript echo keeps the compact placeholder. Pastes at or under the threshold inline as real rows, exactly as before.
250
257
  - `paste.file_threshold_tokens` (default `8000`) — the second tier. A paste estimated above this many tokens (chars/4, the same rule compaction uses) is written to `<RUBINO_HOME>/sessions/<session-id>/paste_N.txt` instead of being held inline, and the sent message carries `[Pasted text #N saved to <path> — too large to inline; read it with the read tool]` so the model reads just the parts it needs. The files persist for the session; `/clear-images` does not touch them (it only drops staged image attachments).
251
258
 
@@ -303,7 +310,6 @@ tasks:
303
310
  max_children_per_node: 3 # max LIVE direct children per node
304
311
  max_concurrent_total: 8 # hard ceiling on total LIVE subagents across the tree
305
312
  max_live_probes_per_child: 5 # per-child budget for billed live probes (probe(live: true))
306
- ask_parent_timeout: 900 # vestigial: governed the removed child→parent ask channel; no effect now
307
313
  ```
308
314
 
309
315
  ### tools
@@ -657,13 +663,14 @@ agents:
657
663
  mcp_servers: []
658
664
  ```
659
665
 
660
- ### server / api
666
+ ### api
661
667
 
662
- ```yaml
663
- server:
664
- port: 4820
665
- auth: false
668
+ The API server's listen port and bind host come from the CLI, not config:
669
+ `rubino server --port <n>` (or `RUBINO_API_PORT`, default `4820`) and `--host`
670
+ (or `RUBINO_API_HOST`). The bearer token is `RUBINO_API_KEY`. The `api` block
671
+ configures payload caps, rate limiting, and the public-bind gate:
666
672
 
673
+ ```yaml
667
674
  api:
668
675
  max_body_bytes: 5242880 # 5 MB cap on JSON request bodies (413 past this)
669
676
  max_upload_bytes: 52428800 # 50 MB cap on multipart uploads
@@ -1,5 +1,26 @@
1
1
  # OAuth provider connectors
2
2
 
3
+ > **Status: NOT WIRED END-TO-END (WIP).** The pieces below exist and the HTTP
4
+ > surface works — the `/v1/oauth/...` API endpoints perform the PKCE flow and
5
+ > store **encrypted** tokens in the `oauth_connections` table. But the subsystem
6
+ > is **API-only and not yet consumed**:
7
+ > - **No tool uses the stored tokens.** Nothing reads `ConnectionRepository`
8
+ > outside the API operations — there is no `GithubTool`/`GoogleTool` etc. that
9
+ > pulls a connection's token to call a provider, so a connected account is not
10
+ > actually actionable by the agent yet.
11
+ > - **No CLI surface.** There is no `rubino oauth` command; the connect/callback
12
+ > flow needs a browser redirect, so it lives only on the API. The CLI treats
13
+ > `RUBINO_ENCRYPTION_KEY` as optional (`doctor`: "only needed for the
14
+ > API/OAuth server").
15
+ > - **Token sharing, when consumption lands:** tokens are not "passed" between
16
+ > CLI and API — both read the **same SQLite DB** (same `RUBINO_HOME`) and
17
+ > decrypt with the **same `RUBINO_ENCRYPTION_KEY`**. So wiring CLI consumption
18
+ > = read `ConnectionRepository` + require the key on the CLI too.
19
+ >
20
+ > Open design question (issue #590): finish the native subsystem, or deprecate
21
+ > it and delegate third-party connections to an MCP server (which does its own
22
+ > OAuth and holds its own tokens). Don't depend on native OAuth in production yet.
23
+
3
24
  Built-in OAuth integration lets users connect third-party accounts (Github, Google, etc.) so tools running inside rubino can act on their behalf.
4
25
 
5
26
  ## Design
@@ -38,6 +38,13 @@ assume or default to one.
38
38
  map-reduces the file in a separate context and returns only the summary,
39
39
  so the raw text never fills this conversation. Reach for `read` (with
40
40
  offset/limit) or `grep` only when you need exact lines, not an overview.
41
+ - Tool output may be COMPRESSED to save context — it is lossless to YOU: a
42
+ `# … N lines elided — read <path> offset=.. limit=..` pointer in a file read
43
+ means that exact body is one targeted `read` away, verbatim (so issue that
44
+ read before editing it). `[… N lines hidden by log compression …]` in command
45
+ output means only passing/info noise was dropped — every error/failure and the
46
+ final summary are kept. `{"_elided": N}` / `"<elided N chars>"` mark trimmed
47
+ JSON. These markers are NOT part of the file; never match or edit against them.
41
48
  - The `ruby` tool runs sandboxed Ruby for quick computation/scripting —
42
49
  reach for it when Ruby fits the project. Otherwise use `shell` for the
43
50
  host's binaries and the project's own toolchain (its interpreter, package
@@ -31,9 +31,6 @@ module Rubino
31
31
  raise ConflictError, "task #{id} already #{entry.status} — nothing to stop" unless entry.status == :running
32
32
 
33
33
  entry.runner&.cancel!
34
- # Stop-cascade (S5a): wake any descendant parked on a blocking
35
- # ask_parent so the whole subtree unwinds at once.
36
- @registry.cancel_descendant_ask_gates(id)
37
34
  [202, Serializer.detail(entry)]
38
35
  end
39
36
  end
@@ -33,7 +33,6 @@ module Rubino
33
33
  application/x-7z-compressed application/x-rar-compressed application/vnd.rar
34
34
  application/x-bzip2 application/x-xz
35
35
  ].freeze
36
- IMAGE_EXTS = %w[.png .jpg .jpeg .gif .webp .bmp .tiff .tif].freeze
37
36
 
38
37
  # Leading magic bytes per recognised image/document MIME (WebP is
39
38
  # special-cased: RIFF container + WEBP tag). Marcel lets the file NAME
@@ -62,7 +62,6 @@ module Rubino
62
62
  # * /agents (alias /tasks) — the live subagent ids, then the
63
63
  # steer/probe/--stop subcommand grammar, so the comm surface is
64
64
  # discoverable from the composer (#39).
65
- # * /reply — the ids of children blocked waiting on the human.
66
65
  # * /mcp — the configured server names (+ reload), then on/off for a
67
66
  # named server (#182), same grammar shape as /agents.
68
67
  # * /mode, /reasoning, /think — the closed enums (#185), via the
@@ -95,7 +94,6 @@ module Rubino
95
94
  "agents" => ->(args) { agents_arg_candidates(args) },
96
95
  "tasks" => ->(args) { agents_arg_candidates(args) },
97
96
  "agent" => ->(args) { args.empty? ? primary_agent_names : [] },
98
- "reply" => ->(args) { args.empty? ? blocked_subagent_ids : [] },
99
97
  "mcp" => ->(args) { mcp_arg_candidates(args) },
100
98
  "mode" => ->(args) { args.empty? ? Rubino::Modes::ALL.map(&:to_s) : [] },
101
99
  "model" => ->(args) { args.empty? ? model_arg_candidates : [] },
@@ -148,12 +146,6 @@ module Rubino
148
146
  end
149
147
  end
150
148
 
151
- # Children parked on an ask_parent waiting for the human — the ids /reply
152
- # answers.
153
- def blocked_subagent_ids
154
- Tools::BackgroundTasks.instance.awaiting_human.map(&:id)
155
- end
156
-
157
149
  # The /model candidates: the registry's model ids for the provider the
158
150
  # next turn would route through. Resolved lazily on each dropdown open so
159
151
  # a /model or /config provider switch is reflected immediately.