wisp-agents 0.17.0 → 0.20.0

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 (55) hide show
  1. package/CHANGELOG.md +72 -1
  2. package/README.md +20 -7
  3. package/bin/wisp.js +4 -2
  4. package/package.json +2 -2
  5. package/server/adapters/antigravity.js +2 -1
  6. package/server/adapters/claude.js +2 -1
  7. package/server/adapters/codex.js +2 -1
  8. package/server/adapters/commands.js +808 -0
  9. package/server/adapters/cursor.js +2 -1
  10. package/server/adapters/grok.js +2 -1
  11. package/server/adapters/opencode.js +2 -1
  12. package/server/check.js +205 -0
  13. package/server/git.js +114 -2
  14. package/server/index.js +2 -1
  15. package/server/judge.js +78 -0
  16. package/server/machines.js +2 -2
  17. package/server/openapi.js +19 -2
  18. package/server/pipeline.js +217 -0
  19. package/server/quick.js +76 -0
  20. package/server/router.js +201 -28
  21. package/server/routes/auto.js +8 -1
  22. package/server/routes/meta.js +1 -1
  23. package/server/routes/settings.js +53 -4
  24. package/server/routes/ship.js +29 -2
  25. package/server/runner.js +65 -15
  26. package/server/scout.js +71 -0
  27. package/server/services/auto.js +115 -15
  28. package/server/services/launch.js +22 -13
  29. package/server/services/ship.js +107 -6
  30. package/server/store.js +15 -0
  31. package/web/css/auto.css +22 -0
  32. package/web/css/launcher.css +17 -0
  33. package/web/css/layout.css +10 -2
  34. package/web/css/modals.css +8 -0
  35. package/web/css/palette.css +22 -0
  36. package/web/css/ship.css +44 -0
  37. package/web/index.html +16 -5
  38. package/web/js/app.js +14 -3
  39. package/web/js/blocks.js +1 -1
  40. package/web/js/core/state.js +2 -1
  41. package/web/js/features/actions.js +2 -4
  42. package/web/js/features/auto.js +38 -7
  43. package/web/js/features/clones.js +79 -0
  44. package/web/js/features/composer.js +2 -4
  45. package/web/js/features/feed.js +34 -4
  46. package/web/js/features/launcher.js +139 -19
  47. package/web/js/features/live.js +5 -10
  48. package/web/js/features/project-list.js +99 -0
  49. package/web/js/features/projects.js +220 -39
  50. package/web/js/features/runners.js +7 -6
  51. package/web/js/features/settings.js +47 -7
  52. package/web/js/features/setup.js +3 -3
  53. package/web/js/features/ship.js +128 -25
  54. package/web/js/features/turns.js +4 -3
  55. package/web/js/ui/popover.js +10 -0
package/CHANGELOG.md CHANGED
@@ -5,6 +5,73 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.20.0] - 2026-10-19
9
+
10
+ ### Added
11
+
12
+ - **Fix conflicts button on every conflicting PR.** When a PR has merge conflicts, its row on the card now has a **Fix conflicts** button (with a merge icon) that sends the task a follow-up to merge the base in and resolve them; wisp pushes the result to the PR. The row switches to "Fixing merge conflicts" as soon as you click, and the follow-up starts without waiting on a fetch first.
13
+
14
+ ### Changed
15
+
16
+ - **PR conflicts are no longer fixed without asking.** wisp used to send the merge follow-up on its own as soon as a PR conflicted. Now it waits for your click on Fix conflicts. To get the old behaviour back, turn on Settings → **Auto-fix PR conflicts**.
17
+ - **PR status updates faster.** A PR whose checks are running, whose mergeability GitHub is still working out, or that was just pushed is now checked every 10 seconds instead of every minute, so a passing check or a cleared conflict shows up quickly. The refresh button on the PR row spins while it checks.
18
+ - **Auto no longer runs Best tasks twice by default.** Best of two (two picks side by side, the better one marked) is now off unless you turn it on in Settings → Auto → Best of two, or with `PATCH /api/router` `{ "best2": true }`. With the Best goal, a task now runs once, on a single pick. Your manual **vs** Compare in the launcher is unchanged.
19
+ - **Auto's Best goal runs frontier models only.** Best now always picks a frontier model (Claude Opus, Codex's default, Grok 4, Gemini Argon), never an everyday or small one like Sonnet or Haiku, however well those did on your past tasks. Before, that was only true for your first 20 outcomes. If every frontier model is busy or at its limit, Best waits instead of stepping down: best-of-2 runs a single pick, and a move at a usage limit stops with a note. Only when you have no frontier model installed (or allowed) does Best use the strongest one you have.
20
+ - **An open card stays open when its task finishes.** It used to fold shut if you had scrolled away or switched windows while it worked; now it lands in "Needs you" still open, with the result ready to read.
21
+
22
+ ### Fixed
23
+
24
+ - **Right-click menus no longer get cut off at the bottom of the window.** The task, project and queued-message menus measured themselves mid pop-in animation, while still scaled down, so near the bottom edge the last item ("Delete session") ended up below the fold. They now measure their real size and stay fully on-screen.
25
+
26
+ ## [0.19.0] - 2026-10-19
27
+
28
+ ### Added
29
+
30
+ - **Auto picks the thinking level too.** Auto's options are now harness · model · thinking level (`claude:opus:high`), and the goal sets the range of levels: **Cheap** runs low (or minimal), **Balanced** the harness's default or medium, **Best** high or x-high. Within that range Auto learns which level works for each kind of task, the same way it learns models. A model's record at one level counts half at its other levels. Harder thinking starts out a little more likely to work and costs more, and then your outcomes decide. Compare runs and hand-overs use a different model, not just a different level. A rate limit rests the model at every level. A follow-up that changes the level counts as a miss for Auto's pick. Settings → Auto lists the levels it has a record for. Harnesses and models without levels run at their default, as before.
31
+
32
+ ### Changed
33
+
34
+ - **Shell commands read as English in the live ticker.** Instead of `Running sed -n 15,50p server/a.js`, a task now shows "Reading lines 15–50 of server/a.js". wisp knows the common file, search, git, gh, npm/pnpm/yarn, Python, Node, cargo, go, make and docker commands, follows `&&` chains and pipes (`git status && git diff --stat` → "Checking git status, then summarising changes"), and looks inside `bash -lc '…'`. Works for every harness; a command it doesn't know still shows as `Running …`, and the exact command stays in the activity detail.
35
+
36
+ ## [0.18.1] - 2026-10-19
37
+
38
+ ### Fixed
39
+
40
+ - **The published 0.18.0 package was missing the Auto checks.** It was built before they merged, so `npm i -g wisp-agents` got none of the entry below. 0.18.1 ships them.
41
+
42
+ ### Added
43
+
44
+ - **Auto checks the work before it reaches you.** When an Auto run says it's done, wisp runs the project's tests itself: `npm test`, pytest, `go test`, `cargo test`, `make test`, and so on. You can set the command, or turn the check off, per project in **Settings → Auto → Open**. A failure only counts if the test passed before: wisp compares with the base commit and runs a failing suite twice.
45
+ - **Repair:** tests that used to pass now fail, so they go back to the same agent. One round by default; the turn shows as **Sent by wisp**.
46
+ - **Hand over:** still failing, so the task runs again on a stronger tier (small → everyday → frontier) in its own worktree. Both runs show as one task.
47
+ - **Usage limits don't count against a model.** When a harness says it's at its limit (Claude's session limit, a rate limit, no credit left), the run doesn't count as a failure. That harness, or just that model for a rate limit, rests until its limit resets, and the task moves straight to the best pick that can run now (**Moved on**). Settings → Auto → Open shows what's resting and until when.
48
+ - **Quality first until Auto knows you.** Until Auto has 20 of your own outcomes, Balanced only picks everyday and frontier models, and Best only frontier ones. The shipped starting estimates count for less (at most 4 tasks each).
49
+ - **Best of two:** with the **Best** goal, two picks work side by side and wisp marks the better one. Tests decide first, then a cheap model reads both diffs.
50
+ - **Scout:** before a bigger task starts, a cheap model looks around the repo and tells the agent which files matter and how wisp will test.
51
+ - Each turn gets wisp's own tests block (**Run by wisp**). If the agent said "done" and wisp's run disagrees, the turn says so and the card shows **Partial**.
52
+ - Cards show **Tests** / **N failing**, **Pick** and **Handed on**.
53
+ - Your own follow-up or Stop cancels whatever Auto was about to do next. Settings → Auto turns each step on or off. API: `PATCH /api/router` with `check`, `repairs`, `escalate`, `best2`, `scout`; `PATCH /api/projects/:id` with `testCommand`.
54
+
55
+ ## [0.18.0] - 2026-10-19
56
+
57
+ ### Added
58
+
59
+ - **Merge conflicts on shipped PRs fix themselves.** When something lands on `main` and your shipped PR no longer merges, the task used to sit there until you noticed on GitHub. Now wisp sees it on its PR check (every minute, or when you press refresh): GitHub reports the PR as conflicting, and wisp confirms it locally with `git merge-tree` against a fresh fetch of the base, so a stale GitHub answer can't start a run. The task then gets a follow-up asking it to merge `origin/main` in (no rebase, since the branch is pushed), resolve each conflict keeping both sides' intent, run the cheap checks and commit. When that run ends, wisp checks the merge is complete: no unmerged files, the base is in the branch, no leftover `<<<<<<<` markers. Then it pushes to the PR, and the card shows the usual Push → PR track. While it works, the PR row reads *Fixing merge conflicts* and hides Merge. If the agent stops halfway, nothing is pushed: the card comes back unread with *Conflicts not fixed* and the reason, plus a **Fix conflicts** button to try again. wisp tries once per PR head and at most 3 times per task, so a base that keeps moving can't run up the bill. On by default; turn it off in Settings → General → **Fix PR conflicts** (`defaults.fixConflicts: "off"`). API: `conflict` on the PR, `conflictFix` on the session summary, `POST /api/sessions/:id/fix-conflicts`.
60
+
61
+ - **Tasks without a folder.** The launcher has a **No folder** chip next to your projects, for general asks that have nothing to do with a repo (explain a concept, draft a message, research something). The agent runs in an empty scratch directory under `~/.wisp/scratch`, with no worktree, and the task shows under *General*. The folder is not added to your projects. The Worktree toggle hides while No folder is picked, and deleting the task deletes its scratch folder. API: `POST /api/sessions {"prompt","scratch":true}`.
62
+
63
+ - **Add a project from a GitHub URL.** Paste `https://github.com/owner/repo` (or `git@github.com:owner/repo.git`, a link to any page inside the repo, or any other git URL) into Projects and the button turns into **Clone**. Click it and the dialog closes: the launcher already points at the repo (a chip with a spinner, *cloning…*) and the prompt has focus, so you write the task while it clones. Send it before the clone finishes and the task card shows *Cloning owner/repo*, then starts in the clone. If the clone fails, a toast says why, the launcher goes back to the project you had picked and your task comes back into the box. A feed filtered to another project switches to All, so the launcher isn't pulled back to that project. GitHub repos go through `gh repo clone` when gh is installed, so private repos work with your gh login; otherwise plain `git clone`. Repos land in `~/code` (or the first of `~/dev`, `~/Developer`, `~/projects`, `~/src`, `~/repos`… that exists). Click the path in the hint to change it (`defaults.cloneDir`). If the folder there already is that repo, wisp reuses it; another folder with that name gets a `-2` suffix. When gh is signed in, the dialog also lists **Your GitHub repos**, newest push first and minus ones already added, so cloning one is a single click. On a machine tab the clone happens on that machine. CLI: `wisp add https://github.com/owner/repo`. API: `POST /api/projects {"repo":"…"}`; a URL in `path` works too.
64
+
65
+ ### Changed
66
+
67
+ - **Project chips stay on one row with tens of projects.** The launcher's *in* row used to show every project as a chip (30 projects meant 6 rows on desktop and 14 on a phone), and the top bar cut off whatever didn't fit with no way to scroll to it. Now both show your most recent projects as chips, and the rest go behind a **+N** chip. Fewer chips show when the space is narrow, and a chip is never cut in half. The project you picked, the one you're filtering by, and projects with tasks that need you always keep their chip. **+N** opens a searchable list of every project: type to filter by name or path, use ↑/↓ and Enter to pick. It also has *No folder* and *Other folder…* in the launcher, and *All projects* and *Manage projects…* in the top bar. In the top bar, **+N** shows a badge counting the tasks that need you in the projects it hides. When a phone is too narrow even for *All*, the top bar shows one chip with the current filter that opens the list.
68
+
69
+ - **Ship it no longer holds you in its dialog.** Clicking *Commit, push & open PR* used to leave the dialog open with a disabled *Shipping…* button until the push and `gh pr create` were done. Now the dialog closes right away, and the card's PR row (just above the composer) shows a live track: **Commit → Push → Open PR**, with a spinner on the step that's running and a check on each one that's done. The server reports each step as `shipping` on the session summary, so other tabs see it too, and the card head shows a *Shipping* pill while the card is collapsed. When the PR is up, the row turns into the PR row with a short highlight, and a toast links to it. If a step fails, the row turns red and says which step failed and why, with **Retry**, which reopens the dialog with your commit message and description still filled in. A second ship while one is running is refused with 409.
70
+
71
+ ### Fixed
72
+
73
+ - **The launcher is there from the first frame.** On start, the launcher used to sit as an empty strip for one to five seconds, because the page waited for harness detection (`GET /api/agents` runs every CLI, and opencode lists its models) before drawing it. Now `index.html` draws the launcher's shape with a prompt box that already has focus, so you can start typing before the scripts load. The full launcher fills in about 50 ms later, at the same height, and keeps what you typed. Harnesses come from the last list seen (kept in the browser) and refresh when detection answers. `GET /api/agents` now answers from the last detection right away and checks again in the background, sending an `agents` event if anything changed. Warm calls dropped from about 1.2 s to 2 ms. `?fresh=1` and Re-check still wait for a full detection. Empty feed sections no longer flash their headings before the feed loads.
74
+
8
75
  ## [0.17.0] - 2026-10-19
9
76
 
10
77
  ### Added
@@ -314,7 +381,11 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
314
381
  - Session queue with follow-ups, answers to agent questions, terminal takeover and mark-as-done.
315
382
  - Older turns are folded, with a floating collapse-all button and back-to-top.
316
383
 
317
- [Unreleased]: https://github.com/M0xoo/wisp/compare/v0.17.0...HEAD
384
+ [Unreleased]: https://github.com/M0xoo/wisp/compare/v0.20.0...HEAD
385
+ [0.20.0]: https://github.com/M0xoo/wisp/compare/v0.19.0...v0.20.0
386
+ [0.19.0]: https://github.com/M0xoo/wisp/compare/v0.18.1...v0.19.0
387
+ [0.18.1]: https://github.com/M0xoo/wisp/compare/v0.18.0...v0.18.1
388
+ [0.18.0]: https://github.com/M0xoo/wisp/compare/v0.17.0...v0.18.0
318
389
  [0.17.0]: https://github.com/M0xoo/wisp/compare/v0.16.0...v0.17.0
319
390
  [0.16.0]: https://github.com/M0xoo/wisp/compare/v0.15.0...v0.16.0
320
391
  [0.15.0]: https://github.com/M0xoo/wisp/compare/v0.14.0...v0.15.0
package/README.md CHANGED
@@ -44,7 +44,7 @@ From there you can **follow up**, **answer its questions**, **take it over** in
44
44
  | 📥 **A queue** | Sessions sort into *Needs you*, *Working* (with a live one-line status), and *Done*. Archive what you don't need to see. |
45
45
  | 💬 **Follow-ups that interrupt** | A follow-up sent mid-run interrupts the agent and resumes it with your message. Turn on **Queue** to wait for the run to finish instead; queued messages can be cancelled. |
46
46
  | 🔎 **Command palette** | `⌘K` / `Ctrl+K` fuzzy-searches sessions, projects, and actions. |
47
- | ✨ **Auto** | The default harness: wisp picks the harness and model per task from what worked before, and learns from merges, 👍 / 👎 and follow-ups. See [Auto](#auto). |
47
+ | ✨ **Auto** | The default harness: wisp picks the harness, model and thinking level per task from what worked before, and learns from merges, 👍 / 👎 and follow-ups. See [Auto](#auto). |
48
48
  | 🤖 **Any harness** | Claude Code, Codex, opencode, Cursor CLI, Antigravity CLI, or Grok Build. Pick the harness, model and thinking level together in the launcher; both are remembered per harness. Each card shows the harness it ran on. |
49
49
  | 🖥️ **Take over** | Opens your terminal with the harness's resume command (`claude --resume <id>`, `codex resume <id>`, …) in the project folder. Works with Terminal, iTerm, Ghostty, Warp, cmux, kitty, WezTerm, GNOME Terminal, Konsole, Alacritty, Windows Terminal, PowerShell, or a custom command. |
50
50
  | 📎 **Attachments** | Paste screenshots or drop images, video, logs, or PDFs into the launcher. |
@@ -54,7 +54,7 @@ From there you can **follow up**, **answer its questions**, **take it over** in
54
54
  | 🤝 **Subagents** | Give the lead agent a team on other harnesses (e.g. Claude Code hands tests to Codex ×3). It delegates through a `delegate` tool; their edits land in its worktree. See [Subagents](#subagents). |
55
55
  | ⏪ **Checkpoints & rollback** | Every turn is snapshotted first. Restore to before any turn, revert a single file, or re-run a command block from the presentation. |
56
56
  | ⚖️ **Compare** | Run one task on two harnesses side by side, and keep the better run. |
57
- | 🧾 **Recipes & triggers** | Saved prompts with `{params}` (`wisp run release 0.17.0`), run by hand, on a schedule, when CI fails, or when an issue is labelled. |
57
+ | 🧾 **Recipes & triggers** | Saved prompts with `{params}` (`wisp run release 0.20.0`), run by hand, on a schedule, when CI fails, or when an issue is labelled. |
58
58
  | 📊 **Usage** | Spend, success rate and questions per project and per harness · model. |
59
59
  | 📱 **Phone** | Questions and finished tasks on ntfy or Telegram; answer from there, or start a task with `/new`. |
60
60
  | 🔐 **Permission modes** | Choose **Plan** (read-only), **Edits** (file edits auto-accepted), or **Full auto** (no permission prompts, the default) for each launch. `Shift+Tab` in the prompt cycles modes. In Edits mode, anything the permissions block comes to you: Claude Code waits for **Allow / Always / Deny**, and the other harnesses show an **Allow & retry** card. |
@@ -83,7 +83,7 @@ brew install m0xoo/tap/wisp-agents # Homebrew: CLI and browser mode,
83
83
  wisp "fix the flaky auth test"
84
84
  ```
85
85
 
86
- The Homebrew formula lives in [M0xoo/homebrew-tap](https://github.com/M0xoo/homebrew-tap). `install.sh` honors `WISP_VERSION` to pin a release (`curl -fsSL https://mokh.xyz/install.sh | WISP_VERSION=0.17.0 bash`).
86
+ The Homebrew formula lives in [M0xoo/homebrew-tap](https://github.com/M0xoo/homebrew-tap). `install.sh` honors `WISP_VERSION` to pin a release (`curl -fsSL https://mokh.xyz/install.sh | WISP_VERSION=0.20.0 bash`).
87
87
 
88
88
  From source:
89
89
 
@@ -116,6 +116,7 @@ The CLI talks to a running wisp, or starts one if none is running. It works from
116
116
  wisp # open wisp (or focus the running one)
117
117
  wisp "fix the flaky auth test" # launch a task in this folder (it becomes a project)
118
118
  wisp add [path] # add a folder as a project (default: cwd)
119
+ wisp add <repo-url> # clone a repo (GitHub URL, git@…, any git URL) into ~/code, then add it
119
120
  wisp --mode plan "audit deps" # permission mode: plan | edits | auto
120
121
  wisp --agent codex "add a test" # harness: claude | codex | opencode | cursor | antigravity | grok
121
122
  ```
@@ -131,7 +132,7 @@ wisp reply <id> "also do X" # follow up (answers a pending question; --mod
131
132
  wisp stop <id>
132
133
  wisp ship <id> [--draft] # commit, push and open a PR for a worktree task
133
134
  wisp merge <id> # squash-merge its PR, fast-forward your local main
134
- wisp run release 0.17.0 # run a recipe; values fill its {params} (or version=0.17.0)
135
+ wisp run release 0.20.0 # run a recipe; values fill its {params} (or version=0.20.0)
135
136
  wisp recipes # list recipes
136
137
  wisp usage [--days 7] # spend and outcomes
137
138
  wisp mcp # MCP server for other agents and editors
@@ -298,14 +299,26 @@ If the harness has no system-prompt flag, prepend `systemPrompt` to the first pr
298
299
 
299
300
  ## Auto
300
301
 
301
- **Auto** is the default harness: wisp picks the harness and model for each task. Its "model" is a goal: **Balanced** (best result per dollar), **Cheap** or **Best**.
302
+ **Auto** is the default harness: wisp picks the harness, model and thinking level for each task. Its "model" is a goal: **Balanced** (best result per dollar), **Cheap** or **Best**. The goal also sets how hard the agent thinks: Cheap runs at low (or minimal), Balanced at the harness's default or medium, Best at high or x-high. A harness or model without levels runs at its default.
302
303
 
303
- How it picks: every harness · model you have installed is an option. For the kind of task (bug, feature, UI, tests, refactor, docs, release, question) Auto keeps an estimate of how often each option does it well. The estimate starts from a small wisp-bench pilot that ships with wisp (`server/router-priors.json`), then counts your own outcomes: merged PR (best), closed PR, 👍 / 👎, rollback, then how the run ended, less a bit for each follow-up you needed. It draws from each estimate (Thompson sampling), so options with little data now and then get a turn, and takes the best value for the goal after expected cost and time. A launch with `important: true` (API, SDK) gets the best-known option, with no trying. Options that keep failing a kind of task are left out of it.
304
+ How it picks: every harness · model · thinking level you have installed is an option (`claude:opus:high`). For the kind of task (bug, feature, UI, tests, refactor, docs, release, question) Auto keeps an estimate of how often each option does it well. The estimate starts from a small wisp-bench pilot that ships with wisp (`server/router-priors.json`), then counts your own outcomes: merged PR (best), closed PR, 👍 / 👎, rollback, then how the run ended, less a bit for each follow-up you needed. It draws from each estimate (Thompson sampling), so options with little data now and then get a turn, and takes the best value for the goal after expected cost and time. A launch with `important: true` (API, SDK) gets the best-known option, with no trying. Options that keep failing a kind of task are left out of it. Thinking harder starts out a little more likely to work and costs more, and then your outcomes decide. A model's record at one level counts half at its other levels.
304
305
 
305
306
  New harnesses and models: when a CLI or model shows up (after an install or a Re-check), it's on probation. It only gets the occasional exploring slot until it has three outcomes, and a new version (say a new Sonnet) starts from its family's record. **Settings → Auto** turns trying new models off, limits which harnesses Auto may use, and shows what it learned; **Forget** clears it.
306
307
 
307
308
  Compare runs get different picks. A subagent set to Auto is picked on its first task and keeps that harness. A launch on a machine is picked here, from the harnesses that machine reported, and that machine's outcomes come back to the same router. All of it stays in `~/.wisp/router.json`; usage stats only add which goal, kind of task and size were used, and whether the pick was exploring.
308
309
 
310
+ After the run, Auto checks the work before it lands in your queue:
311
+
312
+ 1. **Scout** (bigger tasks): a cheap model looks around the repo, read-only, and the agent gets the files that matter and the test command.
313
+ 2. **Check**: wisp runs the project's tests itself. A failure only counts if that test passed on the base commit, and a failing suite runs twice so a flaky test doesn't count. The turn gets a **Run by wisp** tests block. If the agent said "done" and the run disagrees, the turn says so.
314
+ 3. **Repair**: new failures go back to the same agent (1 round by default, up to 2).
315
+ 4. **Hand over**: still failing, or the run errored, so the task runs again on a stronger tier (small → everyday → frontier) in its own worktree, next to the first run.
316
+
317
+ A harness that says it's at its limit (session or usage limit, rate limit, no credit) isn't failing the task. Auto rests it until the limit resets, doesn't count the run against it, and moves the task to the best pick that can run now. Best always runs frontier models only (Opus, not Sonnet or Haiku); it falls back to the strongest you have only when no frontier model is installed. Until Auto has 20 of your own outcomes, Balanced picks only everyday and frontier models. The shipped estimates come from a small pilot, so they count for at most 4 tasks each.
318
+ 5. **Best of two** (goal Best, off by default): two picks work side by side; tests decide, then a cheap model reads both diffs, and the better run is marked **Pick**.
319
+
320
+ Each step's outcome feeds the router: a test pass counts more than the agent's "done", needing a repair counts less, and losing a best-of-2 a little less again. Your own follow-up stops whatever Auto was about to do next. A launch on a machine is checked on that machine, where the files are. Settings → Auto turns each step on or off and sets the test command per project.
321
+
309
322
  The benchmark behind the priors is in [`bench/`](bench/README.md).
310
323
 
311
324
  ## Worktrees and shipping
@@ -334,7 +347,7 @@ Before each turn, wisp snapshots the git working tree (pinned at `refs/wisp/chec
334
347
  Prepare release {version}: update CHANGELOG, README and docs/, bump package.json, tag v{version}.
335
348
  ```
336
349
 
337
- Make one from ⌘K → *Recipes*, or *Save as recipe…* on any card. A recipe may pin a project, harness, model and mode. Run it from ⌘K (*Run: Release*), from the Recipes view, or from a terminal: `wisp run release 0.17.0` fills the fields in order, `wisp run release version=0.17.0` by name.
350
+ Make one from ⌘K → *Recipes*, or *Save as recipe…* on any card. A recipe may pin a project, harness, model and mode. Run it from ⌘K (*Run: Release*), from the Recipes view, or from a terminal: `wisp run release 0.20.0` fills the fields in order, `wisp run release version=0.20.0` by name.
338
351
 
339
352
  **Triggers** launch sessions on their own while wisp is open (⌘K → *Triggers*):
340
353
 
package/bin/wisp.js CHANGED
@@ -236,8 +236,10 @@ async function interactive() {
236
236
 
237
237
  let openPath = '/';
238
238
  if (command === 'add') {
239
- const dir = path.resolve(words[1] || process.cwd());
240
- const p = await api('/projects', { path: dir }).catch((e) => console.error(` ✗ ${e.message}`));
239
+ // A repo URL is cloned by the server (into ~/code unless defaults.cloneDir says otherwise).
240
+ const url = /^(https?:|ssh:|git:|git@|(www\.)?github\.com\/)/i.test(words[1] || '');
241
+ if (url) console.log(` ◌ cloning ${words[1]}…`);
242
+ const p = await api('/projects', url ? { repo: words[1] } : { path: path.resolve(words[1] || process.cwd()) }).catch((e) => console.error(` ✗ ${e.message}`));
241
243
  if (p) console.log(` ✓ project added: ${p.name} (${p.path})`);
242
244
  if (running || electron) process.exit(0);
243
245
  } else if (words.length) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wisp-agents",
3
- "version": "0.17.0",
3
+ "version": "0.20.0",
4
4
  "description": "Launch coding agents, skip the transcript, read the presentation.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -20,7 +20,7 @@
20
20
  "app": "node bin/wisp.js",
21
21
  "dev": "WISP_DEV=1 node bin/wisp.js",
22
22
  "dev:web": "node --watch-path=server bin/wisp.js --no-open",
23
- "test": "node --test test/proc.test.js test/terminal.test.js test/adapters.test.js test/e2e.test.js test/fuzzy.test.js test/integrations.test.js test/update.test.js test/learn.test.js test/ship.test.js test/subagents.test.js test/machines.test.js test/feedback.test.js test/router.test.js test/routes.test.js test/adapters-exit.test.js test/web-pure.test.js test/install.test.js test/install-script.test.js test/mentions.test.js",
23
+ "test": "node --test test/proc.test.js test/terminal.test.js test/adapters.test.js test/e2e.test.js test/fuzzy.test.js test/integrations.test.js test/update.test.js test/learn.test.js test/ship.test.js test/subagents.test.js test/machines.test.js test/feedback.test.js test/router.test.js test/routes.test.js test/adapters-exit.test.js test/web-pure.test.js test/install.test.js test/install-script.test.js test/mentions.test.js test/clone.test.js test/pipeline.test.js test/commands.test.js",
24
24
  "test:real": "node test/real-clis.mjs",
25
25
  "prepublishOnly": "node --check bin/wisp.js && node --check bin/mac-app.js && node -e \"import('./server/index.js').then(() => import('./server/mcp-wisp.js'))\" && node --check electron/main.js"
26
26
  },
@@ -8,6 +8,7 @@
8
8
 
9
9
  import path from 'node:path';
10
10
  import { attachmentNote, clip, detectVersion, exitText, fileTracker, quietOutput, rel, spawnJsonl, withProtocol } from './common.js';
11
+ import { describeCommand } from './commands.js';
11
12
 
12
13
  const BIN = process.env.WISP_ANTIGRAVITY_BIN || 'agy';
13
14
 
@@ -144,7 +145,7 @@ const fileOf = (p) => pick(p, 'TargetFile', 'AbsolutePath', 'FilePath', 'File',
144
145
  function describeTool(name, p, cwd) {
145
146
  const f = rel(fileOf(p), cwd);
146
147
  const n = name.toLowerCase();
147
- if (n === 'run_command') return { kind: 'run', label: `Running ${clip(p.CommandLine, 60)}`, detail: p.CommandLine };
148
+ if (n === 'run_command') return { kind: 'run', label: describeCommand(p.CommandLine, cwd), detail: p.CommandLine };
148
149
  if (EDIT_TOOL.test(n) && f) return { kind: 'edit', label: `${/delete/.test(n) ? 'Deleting' : 'Editing'} ${f}`, file: f };
149
150
  if (/^view_/.test(n) && f) return { kind: 'read', label: `Reading ${f}`, file: f };
150
151
  if (n === 'list_dir') return { kind: 'search', label: `Listing ${rel(p.DirectoryPath, cwd) || '.'}` };
@@ -4,6 +4,7 @@ import fs from 'node:fs';
4
4
  import os from 'node:os';
5
5
  import path from 'node:path';
6
6
  import { ASK_TIMEOUT_MS, MAX_SNAPSHOT, attachmentNote, clip, detectVersion, exitText, flatten, rel, spawnJsonl } from './common.js';
7
+ import { describeCommand } from './commands.js';
7
8
 
8
9
  const BIN = process.env.WISP_CLAUDE_BIN || 'claude';
9
10
 
@@ -199,7 +200,7 @@ function describeTool(name, input, cwd) {
199
200
  case 'NotebookEdit':
200
201
  return { kind: 'edit', label: `Editing notebook ${f}`, file: f };
201
202
  case 'Bash':
202
- return { kind: 'run', label: input.description ? clip(input.description) : `Running ${clip(input.command, 60)}`, detail: input.command };
203
+ return { kind: 'run', label: input.description ? clip(input.description) : describeCommand(input.command, cwd), detail: input.command };
203
204
  case 'Grep':
204
205
  return { kind: 'search', label: `Searching for “${clip(input.pattern, 40)}”` };
205
206
  case 'Glob':
@@ -2,6 +2,7 @@
2
2
 
3
3
  import path from 'node:path';
4
4
  import { ASK_TIMEOUT_MS, attachmentNote, clip, detectVersion, exitText, fileTracker, rel, spawnJsonl, withProtocol } from './common.js';
5
+ import { describeCommand } from './commands.js';
5
6
 
6
7
  const BIN = process.env.WISP_CODEX_BIN || 'codex';
7
8
  const IMAGE = /^image\/(png|jpe?g|gif|webp)$/;
@@ -69,7 +70,7 @@ export default {
69
70
  const type = item.type || item.item_type;
70
71
  if (phase === 'started') {
71
72
  if (type === 'command_execution') {
72
- onEvent({ type: 'tool', kind: 'run', label: `Running ${clip(unwrapShell(item.command), 60)}`, detail: item.command });
73
+ onEvent({ type: 'tool', kind: 'run', label: describeCommand(unwrapShell(item.command), cwd), detail: item.command });
73
74
  } else if (type === 'mcp_tool_call') {
74
75
  onEvent({ type: 'tool', ...describeMcp(item) });
75
76
  } else if (type === 'web_search') {