@viraatdas/rudder 3.9.13 → 4.0.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -12,11 +12,18 @@
12
12
  [![Website](https://img.shields.io/badge/site-rudder.exla.ai-111111.svg)](https://rudder.exla.ai)
13
13
 
14
14
  Rudder is a TUI for managing coding agents. Run Claude Code, Codex and opencode
15
- side by side in one native dashboard: every task you type gets its own isolated
16
- jj workspace, so agents never step on each other. See which agent needs you,
15
+ side by side in one native dashboard. Implementation workers get isolated
16
+ jj workspaces. See which agent needs you,
17
17
  approve prompts, steer any agent mid-turn, and merge the results back with one
18
- key. `/main` puts an agent in the main checkout when you want one there, and
19
- `/plan` turns a bigger goal into a reviewed multi-agent DAG.
18
+ key. Type a goal and Rudder chooses discussion, one worker, or a proposed
19
+ multi-agent plan. `/main` explicitly selects the main checkout.
20
+
21
+ The [v4 architecture and rollout](https://github.com/exla-ai/rudder/blob/main/PLAN.md) covers the distributed workspace,
22
+ build-cache and persistent-engine rewrite. Early milestones do not imply that
23
+ the complete storage/engine migration has shipped.
24
+
25
+ This branch contains v4 alpha foundations. Install the prerelease with
26
+ `npm install -g @exla/rudder@next`; the stable package remains on `latest`.
20
27
 
21
28
  > **AI agents and contributors:** this README is for using Rudder. If you are
22
29
  > an AI agent (or a human) working on the Rudder codebase itself, read
@@ -76,12 +83,19 @@ rudder
76
83
  ```
77
84
 
78
85
  With no arguments, `rudder` opens the dashboard. Type a task in the bottom input
79
- and press `Enter`: Rudder starts one worker for it in its own jj workspace. It
80
- lands in Review when done and merges back with `m`. Use `/main <task>` for an
81
- agent that works directly in the main checkout (for release, push and deploy
82
- work), `/gam <task>` to pair a builder with a reviewer from another model,
83
- `/plan <task>` when you want a reviewed multi-agent DAG, or `/swarm [n] <task>`
84
- to run many competing experiments at one goal and keep the ones that work.
86
+ and press `Enter`. A narrow implementation starts one isolated worker; a broad
87
+ architectural implementation proposes a plan for approval. Questions and
88
+ plan-only requests open one discussion session without implementation workers.
89
+ An isolated worker lands in Review and merges back with `m`. Use `/main <task>`
90
+ for an agent working directly in the checkout, including release work.
91
+ `/swarm [n] <task>` remains available for competing experiments.
92
+
93
+ Routing works locally without an API key. To opt into bounded model-assisted
94
+ routing, set `RUDDER_TASK_ROUTING=decisions` and `OPENAI_API_KEY`; a failed or
95
+ slow classifier falls back to local rules. The classifier chooses a workflow;
96
+ your selected agent still writes the plan. `/plan` remains a compatibility
97
+ shortcut. Standalone `/gam` and `/nudge` have been retired; send a follow-up to
98
+ the agent's pane and resume a stopped agent explicitly.
85
99
 
86
100
  If a task needs shared local context like API tokens, private URLs, account ids,
87
101
  or environment values, save it with `/share <text>` in the task input. Rudder
@@ -121,7 +135,7 @@ to `~/.rudder/config.json`.
121
135
  │ task list │ live Claude Code or Codex terminal │
122
136
  │ status/model │ DAG-over-orchestrator, scrollback, review │
123
137
  ├───────────────┴────────────────────────────────────────────┤
124
- │ task input: main owner by default, /plan DAG, /run isolated │
138
+ │ task input: discuss, work, or propose a plan automatically │
125
139
  └──────────────────────────────────────────────────────────────┘
126
140
  ```
127
141
 
@@ -179,8 +193,8 @@ your browser. The board is a second control surface, not just a monitor: open a
179
193
  task to send an update while it runs, or request changes from Review to resume
180
194
  the same agent in the same workspace. The task's update thread records your
181
195
  directions alongside the latest worker activity. `+ Task` follows the same
182
- end-to-end main-owner path as typing in the dashboard task pane; use `/plan` in
183
- the native dashboard for an explicit DAG.
196
+ task routing as the native dashboard when that dashboard owns the project.
197
+ The standalone board retains its detached-worker workflow.
184
198
 
185
199
  The board also works on its own with `rudder board`: in that mode updates
186
200
  redirect or resume the detached worker directly and move revised work back
@@ -309,9 +323,7 @@ running, Rudder also exposes matching project skills; the orchestrator can write
309
323
  | --- | --- |
310
324
  | `/model` | Pick provider, then model, then effort |
311
325
  | `/fast` | Fast mode for new agents: flagship model at low effort (Claude opus / Codex gpt-6.1-sol); `/model` switches back |
312
- | `/plan <text>` | Start the orchestrator / DAG planner |
313
326
  | `/swarm [n] <task>` | Run `n` competing experiments at one goal (leave `n` out and the swarm decides). A conversational planner proposes the experiment list; talk to it to change the list, then press `Enter` to launch. Every experiment runs on the current model in its own workspace, at most 18 at a time (`swarm.maxParallel`), and none merges on its own |
314
- | `/gam [main] [<model>] <task>` | Generator + adversarial reviewer pair: split panes, the reviewer questions every round, steers the generator mid-turn, and can stop the run; only the generator edits. `^W t` shows the dialogue between them |
315
327
  | `/share <text>` | Save gitignored shared context for all agents in `RUDDER_SHARED.md` |
316
328
  | `/main [task]` or `/m` | Start an agent in the main checkout instead of its own workspace |
317
329
  | `/review-all` | Combine completed workspaces and start a Codex review-all agent |
@@ -360,17 +372,17 @@ disable both capabilities so planning cannot cause external side effects.
360
372
 
361
373
  ## One-Off and Planning
362
374
 
363
- Type a task in the bottom input and Rudder starts one worker for it in its own
364
- jj workspace. The same thing happens no matter what else is on screen: a
365
- standalone worker that touches nothing but its workspace, lands in Review when
366
- done, and merges back with `m` or `/merge-all`. A slash command Rudder does not
375
+ Type a task in the bottom input. Rudder chooses discussion, one isolated worker
376
+ or a proposed plan from the request. A standalone implementation worker touches
377
+ its workspace, lands in Review and merges back with `m` or `/merge-all`.
378
+ A slash command Rudder does not
367
379
  know is reported as unknown instead of being sent to an agent as a task.
368
380
 
369
381
  Use `/main <task>` for an agent in the main checkout itself, for work that has to
370
382
  happen there (release, push, deploy). Several may run there at once; they edit
371
383
  the same tree, so they can overwrite each other, and Rudder says so in the
372
- activity log rather than stopping you. Use `/plan <text>` when you want the
373
- orchestrator / DAG path: the read-only planner decomposes the task, then separate
384
+ activity log rather than stopping you. Larger implementation goals can select
385
+ the DAG path automatically: the read-only planner decomposes the task, then separate
374
386
  isolated workers implement its nodes. To change a running plan, talk to that
375
387
  orchestrator's own pane.
376
388
 
@@ -448,7 +460,7 @@ session, main checkout) instead of forking it into a worker.
448
460
 
449
461
  ## Worktrees and merging
450
462
 
451
- **Who merges what:** planned `/plan` DAG nodes integrate into your checkout
463
+ **Who merges what:** approved DAG nodes integrate into your checkout
452
464
  **automatically** as soon as they finish — no per-node confirmation — so
453
465
  dependent nodes unblock and the chain keeps flowing (their row shows
454
466
  `done · auto-merging`, then `merged locally`; the final gate runs the repo's
@@ -577,11 +589,21 @@ reach an agent (a menu is open in its pane) says so instead of vanishing. Claude
577
589
  panes in the cloud also run with Remote Control, so they appear in the Claude
578
590
  app too.
579
591
 
580
- **Previews.** Put a Tailscale auth key in the vault
581
- (`rudder cloud secrets set TS_AUTHKEY`) and the home machine joins your tailnet
582
- as `rudder-home`. Any dev server an agent starts there shows up as an HTTPS link
583
- (`https://rudder-home.<tailnet>.ts.net:<port>`) in `rudder cloud home` and the
584
- phone app.
592
+ **Previews.** Enroll the home machine with a Tailscale auth key in the vault
593
+ (`rudder cloud secrets set TS_AUTHKEY`). It reuses its persisted login on later
594
+ boots. Connect your laptop to the same tailnet and run `rudder cloud preview`
595
+ from the project to open its ready dev server, or `rudder cloud preview --list`
596
+ to inspect the links. Pass a port when several servers are running.
597
+
598
+ Ready project previews also appear in the app. Rudder checks the listener and
599
+ tailnet state before advertising it, removes stale links and prefers private
600
+ HTTPS Serve. If tailnet certificates are unavailable, the link identifies a
601
+ private HTTP fallback. This does not create a public Funnel. Discovery polls
602
+ every five seconds; first-time enrollment/certificate setup can take longer.
603
+
604
+ Home project tasks in the web/phone app use automatic planning. Legacy task
605
+ sails retain direct execution in this initial v4 track; they refuse discussion
606
+ or decomposition requests and point you to the home dashboard instead.
585
607
 
586
608
  Inside the dashboard, `/login` starts browser auth and `/cloud` toggles cloud
587
609
  mode: while it is on, tasks you type start as workers in Rudder Cloud and keep
package/dist/cloud.js CHANGED
@@ -14,6 +14,7 @@ import { runLaptopOnboarding } from "./onboarding.js";
14
14
  import { renderQrForTerminal } from "./qr.js";
15
15
  import { describeSync, exportWork, importWork, syncCheckout } from "./checkout-sync.js";
16
16
  import { pullFromHome } from "./cloud-pull.js";
17
+ import { cloudPreviews, localTailnetStatus, previewAccess, previewReachable } from "./tailscale-preview.js";
17
18
  import { ensureDir, commandExists, expandHome, isTty, newRunId, nowIso, pathExists, promptConfirm, promptText, promptSelect, promptSecret, readJson, runCommand, shortenHome, shellQuote, writeJson, } from "./util.js";
18
19
  const DEFAULT_LOGIN_INTERVAL_MS = 2000;
19
20
  const DEFAULT_LOGIN_TIMEOUT_MS = 5 * 60 * 1000;
@@ -136,6 +137,9 @@ export async function runCloudCommand(command, args, options = {}) {
136
137
  case "home":
137
138
  await homeCommand(rest, options);
138
139
  return;
140
+ case "preview":
141
+ await cloudPreviewCommand(rest, options);
142
+ return;
139
143
  case "doctor":
140
144
  await cloudDoctor(options);
141
145
  return;
@@ -3734,6 +3738,8 @@ Usage:
3734
3738
  holds every repo) and attach to its dashboard. --snapshot uploads this
3735
3739
  folder to its own machine instead.
3736
3740
  rudder cloud home [status|start|stop|open <owner/repo>]
3741
+ rudder cloud preview [port] [--list] [--json]
3742
+ open this repo's private Tailscale preview; list ready URLs with --list
3737
3743
  rudder cloud move [runId...] [--yes]
3738
3744
  move this repo's unfinished agents (or just the named ones) to the
3739
3745
  home machine: their jj changes and conversations go along, nothing is
@@ -4700,6 +4706,71 @@ async function homeAgentsCommand(client, sub, args, options) {
4700
4706
  ? "The home machine is asleep; it deletes them as soon as it is back."
4701
4707
  : "Deleted on the home machine.");
4702
4708
  }
4709
+ async function cloudPreviewCommand(args, options) {
4710
+ const listOnly = options.json || args.includes("--list");
4711
+ const positional = args.filter((arg) => arg !== "--list");
4712
+ if (positional.length > 1 || (positional[0] && !/^\d{2,5}$/.test(positional[0]))) {
4713
+ throw new Error("Usage: rudder cloud preview [port] [--list] [--json]");
4714
+ }
4715
+ const requestedPort = positional[0] ? Number(positional[0]) : null;
4716
+ if (requestedPort !== null && (requestedPort < 1024 || requestedPort > 65535)) {
4717
+ throw new Error("Preview port must be between 1024 and 65535.");
4718
+ }
4719
+ const root = findRepoRoot();
4720
+ const repo = await githubSlugFromOrigin(root).catch(() => null);
4721
+ const project = repo ? { repo } : { name: path.basename(root) };
4722
+ const client = await cloudClient({ requireToken: true });
4723
+ let state = await client.request("/api/rudder/home", { method: "GET" });
4724
+ if (!listOnly && state.home && (!state.home.connected || state.home.stateLive === false)) {
4725
+ process.stderr.write("Waking the cloud machine for its private preview...\n");
4726
+ await client.request("/api/rudder/home/start", { method: "POST", body: {} });
4727
+ const deadline = Date.now() + 90_000;
4728
+ while ((!state.home?.connected || state.home.stateLive === false) && Date.now() < deadline) {
4729
+ await sleep(1500);
4730
+ state = await client.request("/api/rudder/home", { method: "GET" });
4731
+ }
4732
+ }
4733
+ const ready = () => cloudPreviews(state, project).filter((entry) => requestedPort === null || entry.port === requestedPort);
4734
+ let previews = ready();
4735
+ if (!listOnly && !previews.length && state.home?.connected) {
4736
+ const deadline = Date.now() + 20_000;
4737
+ process.stderr.write("Waiting for this project's private preview...\n");
4738
+ while (!previews.length && state.home?.connected && Date.now() < deadline) {
4739
+ const tailnet = state.home.tailnet;
4740
+ if (tailnet && typeof tailnet === "object" && !Array.isArray(tailnet) && tailnet.error)
4741
+ break;
4742
+ await sleep(1000);
4743
+ state = await client.request("/api/rudder/home", { method: "GET" });
4744
+ previews = ready();
4745
+ }
4746
+ }
4747
+ if (options.json) {
4748
+ printJson({ previews, connected: state.home?.connected === true, access: "tailnet", requiresTailscale: true });
4749
+ return;
4750
+ }
4751
+ if (!previews.length) {
4752
+ const tailnet = state.home?.tailnet;
4753
+ const detail = tailnet && typeof tailnet === "object" && !Array.isArray(tailnet) && typeof tailnet.error === "string"
4754
+ ? ` ${tailnet.error}.` : "";
4755
+ throw new Error(`No ready private preview${requestedPort ? ` on port ${requestedPort}` : ""} for ${repo || project.name}.${detail} Start a dev server in that project's cloud workspace, then retry.`);
4756
+ }
4757
+ if (listOnly || previews.length > 1) {
4758
+ for (const preview of previews)
4759
+ console.log(`:${preview.port} ${preview.label} ${preview.url}`);
4760
+ if (!listOnly)
4761
+ console.log("Open one with: rudder cloud preview <port>");
4762
+ return;
4763
+ }
4764
+ const preview = previews[0];
4765
+ const access = previewAccess(await localTailnetStatus(), new URL(preview.url).hostname);
4766
+ if (!access.ok)
4767
+ throw new Error(`${access.error} Preview: ${preview.url}`);
4768
+ if (!(await previewReachable(preview.url))) {
4769
+ throw new Error(`The preview is not reachable from this computer. Check the dev server and your existing Tailscale access rules. Preview: ${preview.url}`);
4770
+ }
4771
+ console.log(preview.url);
4772
+ openBrowser(preview.url);
4773
+ }
4703
4774
  async function homeCommand(args, options) {
4704
4775
  const client = await cloudClient({ requireToken: true });
4705
4776
  const sub = args[0] ?? "status";