wisp-agents 0.18.0 → 0.21.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 (52) hide show
  1. package/CHANGELOG.md +62 -1
  2. package/README.md +23 -7
  3. package/bin/wisp.js +4 -3
  4. package/package.json +2 -2
  5. package/server/adapters/antigravity.js +2 -1
  6. package/server/adapters/claude.js +7 -5
  7. package/server/adapters/codex.js +7 -3
  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/client.js +3 -2
  14. package/server/index.js +2 -1
  15. package/server/judge.js +78 -0
  16. package/server/mcp-wisp.js +2 -1
  17. package/server/openapi.js +4 -3
  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/sessions.js +15 -8
  23. package/server/routes/settings.js +2 -0
  24. package/server/routes/ship.js +27 -27
  25. package/server/runner.js +83 -17
  26. package/server/scout.js +71 -0
  27. package/server/services/auto.js +111 -14
  28. package/server/services/launch.js +45 -8
  29. package/server/services/ship.js +132 -23
  30. package/skills/wisp/SKILL.md +2 -1
  31. package/web/css/auto.css +22 -0
  32. package/web/css/feed.css +3 -0
  33. package/web/css/launcher.css +12 -0
  34. package/web/css/palette.css +6 -0
  35. package/web/css/ship.css +17 -1
  36. package/web/index.html +1 -0
  37. package/web/js/blocks.js +1 -1
  38. package/web/js/core/state.js +4 -2
  39. package/web/js/features/actions.js +3 -5
  40. package/web/js/features/auto.js +38 -7
  41. package/web/js/features/composer.js +2 -4
  42. package/web/js/features/feed.js +40 -10
  43. package/web/js/features/launcher.js +62 -18
  44. package/web/js/features/live.js +2 -7
  45. package/web/js/features/places.js +10 -0
  46. package/web/js/features/project-list.js +20 -5
  47. package/web/js/features/projects.js +6 -8
  48. package/web/js/features/runners.js +7 -6
  49. package/web/js/features/settings.js +34 -8
  50. package/web/js/features/ship.js +95 -31
  51. package/web/js/features/turns.js +6 -4
  52. package/web/js/ui/popover.js +10 -0
package/CHANGELOG.md CHANGED
@@ -5,6 +5,63 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.21.0] - 2026-10-20
9
+
10
+ ### Added
11
+
12
+ - **One task across several folders.** A task can now work in more than one project at a time, for example an API and the web app that uses it. In the launcher, ⌘-click (Ctrl- or Shift-click) a second project chip, or click the **+** on its corner or next to it in the project list. One agent starts in the first folder and can read and edit the others: Claude Code and Codex get them as `--add-dir`, and every harness is told the paths. Each git repo gets its own worktree. The card reads `api + web` and shows up under either project's filter. **Ship it** opens a pull request in each repo that has changes, and **Merge all** merges them together and syncs each local base branch. CLI: `wisp "…" --also ~/code/web` (repeatable). API: `"also": [folders]` on `POST /sessions`. MCP: `also` on `wisp_launch`.
13
+
14
+ ### Fixed
15
+
16
+ - **The launcher's project row no longer throws while it isn't laid out.** Refitting the chips of a launcher that had just been painted over (or folded into the dock) read the position of a chip that wasn't there.
17
+
18
+ ## [0.20.0] - 2026-10-19
19
+
20
+ ### Added
21
+
22
+ - **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.
23
+
24
+ ### Changed
25
+
26
+ - **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**.
27
+ - **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.
28
+ - **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.
29
+ - **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.
30
+ - **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.
31
+
32
+ ### Fixed
33
+
34
+ - **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.
35
+
36
+ ## [0.19.0] - 2026-10-19
37
+
38
+ ### Added
39
+
40
+ - **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.
41
+
42
+ ### Changed
43
+
44
+ - **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.
45
+
46
+ ## [0.18.1] - 2026-10-19
47
+
48
+ ### Fixed
49
+
50
+ - **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.
51
+
52
+ ### Added
53
+
54
+ - **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.
55
+ - **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**.
56
+ - **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.
57
+ - **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.
58
+ - **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).
59
+ - **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.
60
+ - **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.
61
+ - 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**.
62
+ - Cards show **Tests** / **N failing**, **Pick** and **Handed on**.
63
+ - 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`.
64
+
8
65
  ## [0.18.0] - 2026-10-19
9
66
 
10
67
  ### Added
@@ -334,7 +391,11 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
334
391
  - Session queue with follow-ups, answers to agent questions, terminal takeover and mark-as-done.
335
392
  - Older turns are folded, with a floating collapse-all button and back-to-top.
336
393
 
337
- [Unreleased]: https://github.com/M0xoo/wisp/compare/v0.18.0...HEAD
394
+ [Unreleased]: https://github.com/M0xoo/wisp/compare/v0.21.0...HEAD
395
+ [0.21.0]: https://github.com/M0xoo/wisp/compare/v0.20.0...v0.21.0
396
+ [0.20.0]: https://github.com/M0xoo/wisp/compare/v0.19.0...v0.20.0
397
+ [0.19.0]: https://github.com/M0xoo/wisp/compare/v0.18.1...v0.19.0
398
+ [0.18.1]: https://github.com/M0xoo/wisp/compare/v0.18.0...v0.18.1
338
399
  [0.18.0]: https://github.com/M0xoo/wisp/compare/v0.17.0...v0.18.0
339
400
  [0.17.0]: https://github.com/M0xoo/wisp/compare/v0.16.0...v0.17.0
340
401
  [0.16.0]: https://github.com/M0xoo/wisp/compare/v0.15.0...v0.16.0
package/README.md CHANGED
@@ -44,17 +44,18 @@ 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. |
51
51
  | 🧠 **Learns as you go** | After a session goes quiet, a background retro records your preferences and the agent's lessons (a mistake and the way that worked), globally and per project. Later runs get them in their prompt. See [Learning](#learning). |
52
+ | 🗂️ **One task, several folders** | ⌘-click a second project in the launcher (or its **+**) and one agent works in both, e.g. the API and the web app that uses it. Each git repo gets its own worktree, and **Ship it** opens a PR in each. |
52
53
  | 🌿 **A worktree per task** | In a git repo each task gets its own worktree and branch from `origin/main`, so parallel agents never collide. |
53
54
  | 🚢 **Ship it** | One click commits, pushes and opens the PR. The card follows CI and reviews; **Merge** squash-merges and syncs your local main. |
54
55
  | 🤝 **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
56
  | ⏪ **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
57
  | ⚖️ **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.18.0`), run by hand, on a schedule, when CI fails, or when an issue is labelled. |
58
+ | 🧾 **Recipes & triggers** | Saved prompts with `{params}` (`wisp run release 0.21.0`), run by hand, on a schedule, when CI fails, or when an issue is labelled. |
58
59
  | 📊 **Usage** | Spend, success rate and questions per project and per harness · model. |
59
60
  | 📱 **Phone** | Questions and finished tasks on ntfy or Telegram; answer from there, or start a task with `/new`. |
60
61
  | 🔐 **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 +84,7 @@ brew install m0xoo/tap/wisp-agents # Homebrew: CLI and browser mode,
83
84
  wisp "fix the flaky auth test"
84
85
  ```
85
86
 
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.18.0 bash`).
87
+ 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.21.0 bash`).
87
88
 
88
89
  From source:
89
90
 
@@ -132,7 +133,7 @@ wisp reply <id> "also do X" # follow up (answers a pending question; --mod
132
133
  wisp stop <id>
133
134
  wisp ship <id> [--draft] # commit, push and open a PR for a worktree task
134
135
  wisp merge <id> # squash-merge its PR, fast-forward your local main
135
- wisp run release 0.18.0 # run a recipe; values fill its {params} (or version=0.18.0)
136
+ wisp run release 0.21.0 # run a recipe; values fill its {params} (or version=0.21.0)
136
137
  wisp recipes # list recipes
137
138
  wisp usage [--days 7] # spend and outcomes
138
139
  wisp mcp # MCP server for other agents and editors
@@ -146,6 +147,7 @@ wisp skill install # install the wisp skill for Claude Code (--ag
146
147
  | `--agent`, `-a` | `claude` · `codex` · `opencode` · `cursor` · `antigravity` · `grok` (default: the agent last picked in the launcher) |
147
148
  | `--model` · `--effort` | Model id and thinking level for the launch |
148
149
  | `--cwd`, `-C DIR` | Project folder (default: the current one) |
150
+ | `--also DIR` | Another folder the same task works in; repeat for more (each git repo gets its own worktree and PR) |
149
151
  | `--wait`, `-w` | Wait for the run to finish or ask, then print the presentation |
150
152
  | `--no-worktree` | Edit the folder in place instead of a fresh worktree (default in git repos: a worktree) |
151
153
  | `--compare a[:model],…` | Also run the task on these harnesses, each in its own worktree |
@@ -299,14 +301,26 @@ If the harness has no system-prompt flag, prepend `systemPrompt` to the first pr
299
301
 
300
302
  ## Auto
301
303
 
302
- **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**.
304
+ **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.
303
305
 
304
- 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.
306
+ 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.
305
307
 
306
308
  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.
307
309
 
308
310
  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.
309
311
 
312
+ After the run, Auto checks the work before it lands in your queue:
313
+
314
+ 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.
315
+ 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.
316
+ 3. **Repair**: new failures go back to the same agent (1 round by default, up to 2).
317
+ 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.
318
+
319
+ 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.
320
+ 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**.
321
+
322
+ 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.
323
+
310
324
  The benchmark behind the priors is in [`bench/`](bench/README.md).
311
325
 
312
326
  ## Worktrees and shipping
@@ -317,6 +331,8 @@ In a git repo, every launch (except Plan mode) runs in its own [git worktree](ht
317
331
 
318
332
  **Compare** (the **vs** button next to the harness picker) launches the same task on a second harness. Both get their own worktree and show up as one task in the feed, with a row per run; **Compare** on the task shows them side by side, and **Keep this** archives the others and deletes their worktrees.
319
333
 
334
+ **Several folders.** A task can span projects: ⌘/Ctrl-click (or Shift-click) another project chip in the launcher, or use the **+** on its corner or in the project list. The agent starts in the first folder and gets the others as extra working directories (`--add-dir` for Claude Code and Codex; the rest are told the paths). Each git repo gets its own worktree on the task's branch name, the card reads `api + web` and lists under either project's filter, and **Ship it** commits, pushes and opens a PR per repo with changes, each noting the others. **Merge all** squash-merges them in order and syncs each local base branch. From the CLI or API: `wisp "…" --also ~/code/web`, `"also": ["~/code/web"]` on `POST /sessions`, or `also` on the `wisp_launch` MCP tool. wisp fixes merge conflicts only on the first repo's PR, and checkpoints and rollback cover the first folder.
335
+
320
336
  Turn worktrees off in Settings → *Worktree per task*, with the launcher's **Worktree / In place** toggle, or per launch (`--no-worktree`, `"worktree": false`).
321
337
 
322
338
  ## Subagents
@@ -335,7 +351,7 @@ Before each turn, wisp snapshots the git working tree (pinned at `refs/wisp/chec
335
351
  Prepare release {version}: update CHANGELOG, README and docs/, bump package.json, tag v{version}.
336
352
  ```
337
353
 
338
- 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.18.0` fills the fields in order, `wisp run release version=0.18.0` by name.
354
+ 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.21.0` fills the fields in order, `wisp run release version=0.21.0` by name.
339
355
 
340
356
  **Triggers** launch sessions on their own while wisp is open (⌘K → *Triggers*):
341
357
 
package/bin/wisp.js CHANGED
@@ -37,12 +37,12 @@ const HELP = `usage:
37
37
  wisp -- "ls the repo" a task that starts with a command name
38
38
 
39
39
  launch options: --mode plan|edits|auto --agent claude|codex|opencode|cursor|antigravity|grok --model ID --effort LEVEL
40
- --cwd DIR --wait --json --no-worktree --compare agent[:model],…
40
+ --cwd DIR --also DIR (repeatable) --wait --json --no-worktree --compare agent[:model],…
41
41
  options: --port N --browser --no-open --json --version`;
42
42
 
43
43
  const COMMANDS = new Set(['add', 'ls', 'list', 'show', 'wait', 'reply', 'stop', 'mcp', 'skill', 'ship', 'merge', 'run', 'recipes', 'usage']);
44
44
  const argv = process.argv.slice(2);
45
- const flags = { open: true, browser: false, port: Number(process.env.WISP_PORT) || 4477, mode: null, agent: null, model: null, effort: null, cwd: null, json: false, wait: false, queue: undefined, state: null, limit: null, project: false, worktree: undefined, compare: null, draft: false, days: null };
45
+ const flags = { open: true, browser: false, port: Number(process.env.WISP_PORT) || 4477, mode: null, agent: null, model: null, effort: null, cwd: null, also: [], json: false, wait: false, queue: undefined, state: null, limit: null, project: false, worktree: undefined, compare: null, draft: false, days: null };
46
46
  const words = [];
47
47
  let literal = false;
48
48
  for (let i = 0; i < argv.length; i++) {
@@ -57,6 +57,7 @@ for (let i = 0; i < argv.length; i++) {
57
57
  else if (a === '--model') flags.model = argv[++i];
58
58
  else if (a === '--effort') flags.effort = argv[++i];
59
59
  else if (a === '--cwd' || a === '-C') flags.cwd = path.resolve(argv[++i].replace(/^~(?=$|[\\/])/, os.homedir()));
60
+ else if (a === '--also') flags.also.push(path.resolve(argv[++i].replace(/^~(?=$|[\\/])/, os.homedir())));
60
61
  else if (a === '--state') flags.state = argv[++i];
61
62
  else if (a === '--limit') flags.limit = Number(argv[++i]);
62
63
  else if (a === '--json') flags.json = true;
@@ -114,7 +115,7 @@ async function scripted(cmd, args) {
114
115
  };
115
116
  switch (cmd) {
116
117
  case 'launch': {
117
- const s = await wisp.launch({ prompt: args.join(' '), cwd, agent: flags.agent || undefined, mode: flags.mode || undefined, model: flags.model ?? undefined, effort: flags.effort ?? undefined, worktree: flags.worktree, compare: flags.compare || undefined });
118
+ const s = await wisp.launch({ prompt: args.join(' '), cwd, also: flags.also, agent: flags.agent || undefined, mode: flags.mode || undefined, model: flags.model ?? undefined, effort: flags.effort ?? undefined, worktree: flags.worktree, compare: flags.compare || undefined });
118
119
  if (s.group && !flags.wait) return out(s, s.group.map((x) => ` ◌ launched on ${x.agent}: ${x.title} (${x.id})`).join('\n'));
119
120
  if (!flags.wait) return out(s, ` ◌ launched: ${s.title} (${s.id})`);
120
121
  if (!flags.json) console.error(` ◌ launched: ${s.title} (${s.id}) — waiting…`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wisp-agents",
3
- "version": "0.18.0",
3
+ "version": "0.21.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 test/clone.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
 
@@ -40,8 +41,8 @@ export default {
40
41
  efforts: ['low', 'medium', 'high', 'xhigh', 'max'],
41
42
 
42
43
  // Interactive command to take over a session in a terminal.
43
- resumeCommand({ resumeId, model, effort }) {
44
- return [BIN, ...(resumeId ? ['--resume', resumeId] : []), ...(model ? ['--model', model] : []), ...(effort ? ['--effort', effort] : [])];
44
+ resumeCommand({ resumeId, model, effort, dirs = [] }) {
45
+ return [BIN, ...(resumeId ? ['--resume', resumeId] : []), ...(model ? ['--model', model] : []), ...(effort ? ['--effort', effort] : []), ...dirs.flatMap((d) => ['--add-dir', d])];
45
46
  },
46
47
 
47
48
  detect() {
@@ -55,7 +56,8 @@ export default {
55
56
 
56
57
  // Returns { stop(), done: Promise<void> }. Everything else is reported through onEvent.
57
58
  // grants: --allowedTools rules (Bash(npm publish)) for what the user allowed after a denial.
58
- run({ prompt, attachments = [], cwd, resumeId, mode, model, effort, systemPrompt, mcpServers = {}, delegating = false, grants = [], onEvent }) {
59
+ // dirs: the task's other folders, which it may read and edit like cwd.
60
+ run({ prompt, attachments = [], cwd, dirs: extra = [], resumeId, mode, model, effort, systemPrompt, mcpServers = {}, delegating = false, grants = [], onEvent }) {
59
61
  const args = [
60
62
  '-p',
61
63
  '--output-format', 'stream-json',
@@ -73,7 +75,7 @@ export default {
73
75
  if (allowed.length) args.push('--allowedTools', ...allowed);
74
76
  // Its built-in subagents (Task, or Agent in newer versions) would bypass the user's team.
75
77
  if (delegating) args.push('--disallowedTools', 'Task', 'Agent', 'Workflow');
76
- const dirs = [...new Set(attachments.map((a) => path.dirname(a.path)))];
78
+ const dirs = [...new Set([...extra, ...attachments.map((a) => path.dirname(a.path))])];
77
79
  if (dirs.length) args.push('--add-dir', ...dirs);
78
80
  args.push('--verbose'); // terminates the variadic flags above
79
81
  prompt += attachmentNote(attachments, 'Read images and PDFs with the Read tool');
@@ -199,7 +201,7 @@ function describeTool(name, input, cwd) {
199
201
  case 'NotebookEdit':
200
202
  return { kind: 'edit', label: `Editing notebook ${f}`, file: f };
201
203
  case 'Bash':
202
- return { kind: 'run', label: input.description ? clip(input.description) : `Running ${clip(input.command, 60)}`, detail: input.command };
204
+ return { kind: 'run', label: input.description ? clip(input.description) : describeCommand(input.command, cwd), detail: input.command };
203
205
  case 'Grep':
204
206
  return { kind: 'search', label: `Searching for “${clip(input.pattern, 40)}”` };
205
207
  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)$/;
@@ -31,11 +32,14 @@ export default {
31
32
  return detectVersion(BIN);
32
33
  },
33
34
 
34
- run({ prompt, attachments = [], cwd, resumeId, mode, model, effort, systemPrompt, mcpServers = {}, onEvent }) {
35
+ // dirs: the task's other folders, writable like cwd.
36
+ run({ prompt, attachments = [], cwd, dirs: extra = [], resumeId, mode, model, effort, systemPrompt, mcpServers = {}, onEvent }) {
35
37
  const args = ['exec', '--json', '--skip-git-repo-check'];
36
38
  // `resume` doesn't take --sandbox, so set it through config for both paths.
37
39
  if (mode === 'auto') args.push('--dangerously-bypass-approvals-and-sandbox');
38
40
  else args.push('-c', `sandbox_mode=${toml(SANDBOX[mode] || 'workspace-write')}`);
41
+ // `resume` doesn't take --add-dir either: the other folders are writable roots through config.
42
+ if (extra.length && mode !== 'auto') args.push('-c', `sandbox_workspace_write.writable_roots=${toml(extra)}`);
39
43
  if (model) args.push('-m', model);
40
44
  if (effort) args.push('-c', `model_reasoning_effort=${toml(effort)}`);
41
45
  for (const [name, srv] of Object.entries(mcpServers)) {
@@ -47,7 +51,7 @@ export default {
47
51
  const images = attachments.filter((a) => IMAGE.test(a.mime));
48
52
  for (const img of images) args.push('-i', img.path);
49
53
  if (!resumeId) {
50
- const dirs = [...new Set(attachments.map((a) => path.dirname(a.path)))];
54
+ const dirs = [...new Set([...extra, ...attachments.map((a) => path.dirname(a.path))])];
51
55
  for (const d of dirs) args.push('--add-dir', d);
52
56
  }
53
57
  if (resumeId) args.push('resume', resumeId);
@@ -69,7 +73,7 @@ export default {
69
73
  const type = item.type || item.item_type;
70
74
  if (phase === 'started') {
71
75
  if (type === 'command_execution') {
72
- onEvent({ type: 'tool', kind: 'run', label: `Running ${clip(unwrapShell(item.command), 60)}`, detail: item.command });
76
+ onEvent({ type: 'tool', kind: 'run', label: describeCommand(unwrapShell(item.command), cwd), detail: item.command });
73
77
  } else if (type === 'mcp_tool_call') {
74
78
  onEvent({ type: 'tool', ...describeMcp(item) });
75
79
  } else if (type === 'web_search') {