wisp-agents 0.7.0 → 0.8.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.
- package/CHANGELOG.md +38 -1
- package/README.md +76 -1
- package/bin/wisp.js +52 -5
- package/package.json +2 -2
- package/server/adapters/claude.js +4 -0
- package/server/adapters/codex.js +1 -0
- package/server/adapters/common.js +1 -1
- package/server/adapters/index.js +2 -1
- package/server/adapters/opencode.js +4 -0
- package/server/checkpoint.js +348 -0
- package/server/client.js +26 -2
- package/server/git.js +199 -0
- package/server/index.js +409 -21
- package/server/mcp-ask.js +75 -2
- package/server/mcp-wisp.js +7 -1
- package/server/openapi.js +147 -3
- package/server/protocol.js +11 -1
- package/server/recipes.js +64 -0
- package/server/remote.js +279 -0
- package/server/render.js +18 -0
- package/server/runner.js +152 -13
- package/server/store.js +9 -1
- package/server/subagents.js +85 -0
- package/server/triggers.js +268 -0
- package/server/usage.js +79 -0
- package/server/welcome.js +9 -1
- package/skills/wisp/SKILL.md +3 -2
- package/web/css/app.css +557 -16
- package/web/index.html +10 -3
- package/web/js/app.js +1727 -86
- package/web/js/blocks.js +396 -22
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,42 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [0.8.0] - 2026-10-17
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Subagents: give the lead agent a team on other harnesses, e.g. a Claude Code run that hands tests to Codex and research to opencode. The launcher's **Subagents** button opens an editor: up to 6 members, each with its own harness, model and thinking level, a name, a **Use for** note ("image and UI work", "research") that tells the lead when to pick it, and a **×N** copies stepper: `Codex ×3` gives the lead three identical Codex agents (`codex#1`…`codex#3`, 12 agents in all). Several tasks sent to `codex` in one call go to free copies and run side by side; naming a copy continues its conversation. **Save as preset** stores the team; **Presets** loads or deletes one. The lead gets a `delegate` tool on wisp's injected MCP server (Claude Code, Codex, opencode; Cursor and Antigravity can be subagents but not leads). Tasks in one call run in parallel in the lead's folder, and delegating to the same member again resumes its conversation. Feed cards show the lead's logo with its subagents' logos stacked behind it (they fan out on hover and pulse while working). A running card lists each subagent's live status, and a finished turn shows one report card per delegated task that opens into that subagent's own presentation. Subagent edits count as the lead's, so they appear in its files, diffs, rollback and Ship it. API: `subagents` / `preset` on `POST /sessions`, `/teams` for presets, `PATCH /sessions/:id { subagents }`; `wisp_launch` takes them too.
|
|
13
|
+
- Actionable presentation blocks: a new `actions` block (`items: [{label, command, detail, confirm} | {label, reply}]`) renders 1-click command buttons that run in the session's project folder (`POST /api/sessions/:id/exec`), show live exit code, duration and output inline, and offer a **Send failure to agent** follow-up button when a command fails. `terminal` and `tests` blocks with a `command` now have a **Run / Re-run** button, and `diff` and `files` blocks have a per-file **Revert / Re-apply** button (`POST /api/sessions/:id/revert`).
|
|
14
|
+
- Session checkpoints & one-click rollback: before each turn starts, wisp snapshots the git working tree (`git stash create || git rev-parse --verify HEAD`, pinned at `refs/wisp/checkpoints/<session>/<turn>`) along with pre-turn untracked files and per-file before snapshots. Each turn's trail bar, session menu, and command palette include **Restore to before this turn** (and **Re-apply turn** / Undo) via `POST /api/sessions/:id/rollback`.
|
|
15
|
+
- Worktree per task: in a git repo, each launch runs in its own git worktree on a `wisp/<task>-<id>` branch made from `origin/main` (freshly fetched), under `~/.wisp/worktrees/`. Parallel agents never edit the same checkout, and the agent is told where it is. On by default; toggle **Worktree / In place** in the launcher, Settings → *Worktree per task*, `--no-worktree` on the CLI or `worktree: false` in the API. Plan mode and folders that aren't repos run in place. Archiving removes a clean worktree (the branch stays, a follow-up brings it back); deleting the session removes it, and its branch unless it has commits.
|
|
16
|
+
- Ship it (`S`, the card's **Ship it** button, or `wisp ship <id>`): commits the worktree with a conventional message drafted from the presentation, pushes the branch and opens a PR with `gh pr create` (optionally as a draft). The card then shows the PR: CI checks, review state and merges, polled every minute; it comes back to *Needs you* when checks finish, a review lands, or it merges. **Merge** (`wisp merge <id>`) marks a draft ready, squash-merges with `--match-head-commit`, fast-forwards your local base branch and removes the worktree.
|
|
17
|
+
- Compare: the **vs** button in the launcher (or `--compare codex,opencode:model` on the CLI, `compare: [...]` in the API) runs the same task on a second harness, each in its own worktree. **Compare** opens the runs side by side: time, cost, files and lines changed, tests, questions, and each presentation. **Keep this** archives the others and deletes their worktrees.
|
|
18
|
+
- Recipes: saved prompts where `{name}` becomes a field to fill in, e.g. `Prepare release {version}…`. Create them from ⌘K → *Recipes* or *Save as recipe…* on any card; run them from ⌘K → *Run: …*, or `wisp run release 0.8.0` / `wisp recipes`. A recipe can pin a project, harness, model and mode.
|
|
19
|
+
- Triggers start sessions without you, while wisp is open: a cron **schedule** (local time), **CI failed** (a GitHub Actions run fails; polled with `gh run list`), or **issue labelled** (polled with `gh issue list`). Each runs a prompt or a recipe, with the run's or issue's details filled in. The first check only records what already exists. Issue triggers default to Plan mode, since issue text is untrusted. ⌘K → *Triggers*.
|
|
20
|
+
- Usage view (⌘K → *Usage*, Settings → *Usage*): spend per day, and spend, turns, success rate, questions and average time by project and by harness · model, over 7, 30 or 90 days. Harnesses that don't report cost are counted but not priced. `wisp usage`, `GET /api/usage`.
|
|
21
|
+
- Phone (Settings → *Phone*): questions and finished tasks go to [ntfy](https://ntfy.sh) and/or a Telegram bot. Tap an option to answer; on Telegram, reply to a message to answer in your own words or follow up, and tap *Mark done*. wisp polls both, so nothing has to reach your machine. ntfy answers are HMAC-signed; Telegram is linked to one chat with a one-time code.
|
|
22
|
+
- API: `POST /sessions` takes `worktree` and `compare`; new `/sessions/:id/ship`, `/pr`, `/merge`, `/recipes`, `/triggers`, `/remote`, `/usage` (all in `GET /api/openapi.json`). SDK: `ship`, `merge`, `recipes`, `saveRecipe`, `runRecipe`, `triggers`, `usage`. Summaries carry `branch`, `pr`, `groupId` and `origin`.
|
|
23
|
+
- A `graph` presentation block for explaining merges and conflicts: a PR card with the branches as coloured lanes (newest commit first), the merge target on top (conflicts / ready / merged), branch tags on each tip, and the base commits that conflict highlighted and numbered. Under it: each conflict with file, `ours` vs `theirs` and how it was resolved (hover one to light up the commit it came from), checks as chips, and an optional action button (e.g. **Resolve conflicts**) that replies to the agent. Plain-markdown rendering for the CLI, MCP and chat apps too.
|
|
24
|
+
- Back to top from the keyboard: `U` (or `Home`) scrolls the feed to the top. The back-to-top button that appears once you scroll shows the `U` key next to its arrow, and the shortcut is listed under `?`.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- The bar under an open task's title only says how the run was set up: the mode when it's Plan or Full auto, the branch, and what launched it. Agent, model and path, which the card header already shows, moved into its tooltip. Compare stays in the task header and Done next to the composer; Take over is an icon. **Ship it** now also sits next to *Mark done* under the composer, so you can ship right after reading the result.
|
|
29
|
+
- Memory, Automations, Usage and Phone open inside Settings when you reach them from there: they take the main area next to the sidebar, with a **Back** button (or Esc) that returns to the section you came from. Only the X closes Settings. From the top bar or ⌘K they still open on their own.
|
|
30
|
+
- Settings is a sidebar and a pane instead of one long list: **General** (editor, terminal, worktree per task), **Notifications** (desktop, phone), **Agents** (each harness with version or error; a red dot in the sidebar when one is missing), **Memory**, **Automations** (recipes and triggers, each with **Manage**), **Usage** (last 7 days at a glance) and **Privacy**. It reopens on the last section; ↑/↓ move between sections. Phones get the sections as a scrolling tab strip.
|
|
31
|
+
- Recipes and triggers are out in the open instead of only behind Settings and ⌘K: an **Automations** button in the top bar (badge = triggers on, tooltip = what fires next), and a **Recipes** row under the launcher with one-click chips for the picked project's recipes (and those tied to none), **Save as recipe** for whatever is in the box, and **Manage**. Each recipe in Automations has a **Schedule** button that starts a schedule trigger running it.
|
|
32
|
+
- The launcher stays reachable while you scroll: once it passes under the top bar it docks there as a one-line bar (project chip + draft). Click it, or press `⌘L` / `N`, and the full launcher opens in place over the feed, without scrolling you back to the top. `Esc` or clicking away folds it again. Cards selected with `J` / `K` or opened scroll into view below it.
|
|
33
|
+
- The launcher's **vs** (compare) control is part of the harness pill instead of a separate button. Turned on, the second harness joins the same pill (`Claude Code ⌄ vs opencode ⌄`); hover **vs** for × to stop comparing. The bar no longer wraps onto a second line: on narrow widths model names, then labels, fold away and the logos stay.
|
|
34
|
+
- A compare launch is one task in the feed instead of a card per harness: the prompt on top, a row per run (harness, model, live status; each expands in place), and **Compare** for the side-by-side view. The task sits in *Needs you* while any run asks, *Working* while any runs, and *Done* once all are; ✓ marks every finished run done. Compare runs keep the prompt as their title. **Keep this** turns the kept run back into an ordinary card. See [docs/feed.md](docs/feed.md#compare-tasks).
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
- Clicking a recipe no longer runs it straight away when it has no fields to fill in. Its dialog now always stays open until you press **Run** (the button has focus, so Enter works too).
|
|
39
|
+
- `flow` blocks that don't fit across the card wrap onto rows that alternate direction (left to right, then right to left), joined by a short line, instead of squeezing every step into a narrow column. Loop-back lines go around the cards instead of crossing them. Phone width still stacks the steps.
|
|
40
|
+
- The launcher no longer flashes its focus ring when you open the harness pickers or switch between them (e.g. opencode, then Claude Code in a compare). The ring stays on while a picker or the mode menu is open; the rerun dialog behaves the same way.
|
|
41
|
+
- Launching a compare no longer freezes wisp for a few seconds. The runs share one `git fetch` and their worktrees are checked out in parallel. A fresh worktree's first checkpoint is its base commit, so it no longer runs blocking `git` calls that stalled the server and the live feed. With fake harnesses, a 2-way launch went from 1.5 s to 0.7 s and a 3-way launch from 2.3 s to 0.9 s. The send button shows a spinner while the launch sets up. If one worktree fails, the ones already created are removed.
|
|
42
|
+
- Scrolling in the feed stays put. When a card changes section in the background (a task finishes, asks or is removed), the card you're reading no longer jumps; one whose edge just peeks onto the screen no longer drags the view with it. Mark done, Archive and Delete put the next card where the old one was and focus it, so E / A / j triage continues from the same spot. j / k show the header of tall cards, switching project filters returns to the top of the feed, and a task launched from the box scrolls into view when its card lands off-screen. The rules are written down in [docs/feed.md](docs/feed.md).
|
|
43
|
+
|
|
8
44
|
## [0.7.0] - 2026-10-17
|
|
9
45
|
|
|
10
46
|
### Added
|
|
@@ -122,7 +158,8 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
|
|
|
122
158
|
- Session queue with follow-ups, answers to agent questions, terminal takeover and mark-as-done.
|
|
123
159
|
- Older turns are folded, with a floating collapse-all button and back-to-top.
|
|
124
160
|
|
|
125
|
-
[Unreleased]: https://github.com/M0xoo/wisp/compare/v0.
|
|
161
|
+
[Unreleased]: https://github.com/M0xoo/wisp/compare/v0.8.0...HEAD
|
|
162
|
+
[0.8.0]: https://github.com/M0xoo/wisp/compare/v0.7.0...v0.8.0
|
|
126
163
|
[0.7.0]: https://github.com/M0xoo/wisp/compare/v0.6.0...v0.7.0
|
|
127
164
|
[0.6.0]: https://github.com/M0xoo/wisp/compare/v0.5.0...v0.6.0
|
|
128
165
|
[0.5.0]: https://github.com/M0xoo/wisp/compare/v0.4.0...v0.5.0
|
package/README.md
CHANGED
|
@@ -48,6 +48,14 @@ From there you can **follow up**, **answer its questions**, **take it over** in
|
|
|
48
48
|
| 🖥️ **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. |
|
|
49
49
|
| 📎 **Attachments** | Paste screenshots or drop images, video, logs, or PDFs into the launcher. |
|
|
50
50
|
| 🧠 **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). |
|
|
51
|
+
| 🌿 **A worktree per task** | In a git repo each task gets its own worktree and branch from `origin/main`, so parallel agents never collide. |
|
|
52
|
+
| 🚢 **Ship it** | One click commits, pushes and opens the PR. The card follows CI and reviews; **Merge** squash-merges and syncs your local main. |
|
|
53
|
+
| 🤝 **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). |
|
|
54
|
+
| ⏪ **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. |
|
|
55
|
+
| ⚖️ **Compare** | Run one task on two harnesses side by side, and keep the better run. |
|
|
56
|
+
| 🧾 **Recipes & triggers** | Saved prompts with `{params}` (`wisp run release 0.8.0`), run by hand, on a schedule, when CI fails, or when an issue is labelled. |
|
|
57
|
+
| 📊 **Usage** | Spend, success rate and questions per project and per harness · model. |
|
|
58
|
+
| 📱 **Phone** | Questions and finished tasks on ntfy or Telegram; answer from there. |
|
|
51
59
|
| 🔐 **Permission modes** | Choose **Plan** (read-only), **Edits** (file edits auto-accepted), or **Full auto** for each launch. `Shift+Tab` in the prompt cycles modes. |
|
|
52
60
|
| 🔌 **Integrations** | An MCP server (`wisp mcp`), an agent skill, a scriptable CLI, a JS SDK, an OpenAPI-described HTTP API, and webhooks. Hand tasks to wisp from Claude Code, Codex, opencode, Cursor, scripts or CI. |
|
|
53
61
|
| 🪶 **No runtime dependencies** | The server uses only Node built-ins. Electron is needed only for the desktop app. |
|
|
@@ -115,6 +123,11 @@ wisp show <id> # state + latest presentation
|
|
|
115
123
|
wisp wait <id> # until it finishes or asks you something
|
|
116
124
|
wisp reply <id> "also do X" # follow up (answers a pending question)
|
|
117
125
|
wisp stop <id>
|
|
126
|
+
wisp ship <id> [--draft] # commit, push and open a PR for a worktree task
|
|
127
|
+
wisp merge <id> # squash-merge its PR, fast-forward your local main
|
|
128
|
+
wisp run release 0.8.0 # run a recipe; values fill its {params} (or version=0.8.0)
|
|
129
|
+
wisp recipes # list recipes
|
|
130
|
+
wisp usage [--days 7] # spend and outcomes
|
|
118
131
|
wisp mcp # MCP server for other agents and editors
|
|
119
132
|
wisp skill install # install the wisp skill for Claude Code (--agent all for Codex/opencode too)
|
|
120
133
|
```
|
|
@@ -127,6 +140,8 @@ wisp skill install # install the wisp skill for Claude Code (--ag
|
|
|
127
140
|
| `--model` · `--effort` | Model id and thinking level for the launch |
|
|
128
141
|
| `--cwd`, `-C DIR` | Project folder (default: the current one) |
|
|
129
142
|
| `--wait`, `-w` | Wait for the run to finish or ask, then print the presentation |
|
|
143
|
+
| `--no-worktree` | Edit the folder in place instead of a fresh worktree (default in git repos: a worktree) |
|
|
144
|
+
| `--compare a[:model],…` | Also run the task on these harnesses, each in its own worktree |
|
|
130
145
|
| `--json` | Machine-readable output |
|
|
131
146
|
| `--browser`, `-b` | Use the browser instead of the desktop app |
|
|
132
147
|
| `--no-open` | Just run the server; open nothing |
|
|
@@ -169,12 +184,15 @@ console.log((await wisp.wait(s.id)).state, (await wisp.presentation(s.id)).markd
|
|
|
169
184
|
| `/` | Reply to the focused session |
|
|
170
185
|
| `1`–`9` | Pick an option in a pending question |
|
|
171
186
|
| `T` | Take over in your terminal |
|
|
187
|
+
| `S` | Ship it: commit, push and open a PR (worktree tasks) |
|
|
188
|
+
| `⇧ R` | Rerun with another harness, model or thinking level |
|
|
172
189
|
| `E` | Mark done and jump to the next session that needs you |
|
|
173
190
|
| `A` | Archive / unarchive |
|
|
174
191
|
| `M` | Rename |
|
|
175
192
|
| `⌫` | Delete |
|
|
176
193
|
| `R` | Replay the presentation |
|
|
177
194
|
| `C` | Collapse all |
|
|
195
|
+
| `U` | Back to top (also `Home`) |
|
|
178
196
|
| `?` | Show all shortcuts |
|
|
179
197
|
| `Esc` | Close, blur, or collapse |
|
|
180
198
|
|
|
@@ -202,9 +220,15 @@ wisp/
|
|
|
202
220
|
│ ├── mcp-wisp.js # `wisp mcp`: wisp as tools for other agents
|
|
203
221
|
│ ├── render.js # presentation → markdown
|
|
204
222
|
│ ├── runner.js # session lifecycle, follow-ups, event normalization
|
|
223
|
+
│ ├── checkpoint.js # per-turn git checkpoints, file revert, rollback, action exec
|
|
205
224
|
│ ├── protocol.js # system prompt + ```wisp block parser (markdown fallback)
|
|
206
225
|
│ ├── mcp-ask.js # ask_user MCP server
|
|
207
226
|
│ ├── store.js # JSON-file persistence in ~/.wisp
|
|
227
|
+
│ ├── git.js # worktrees, Ship it, PR status and merge (git + gh)
|
|
228
|
+
│ ├── recipes.js # saved prompts with {params}
|
|
229
|
+
│ ├── triggers.js # cron schedules and GitHub watchers
|
|
230
|
+
│ ├── remote.js # phone: ntfy and Telegram, out and back
|
|
231
|
+
│ ├── usage.js # spend and outcomes
|
|
208
232
|
│ ├── terminal.js # "take over" in your terminal
|
|
209
233
|
│ ├── welcome.js # the gallery session
|
|
210
234
|
│ └── adapters/
|
|
@@ -231,7 +255,7 @@ wisp/
|
|
|
231
255
|
|
|
232
256
|
### Presentation blocks
|
|
233
257
|
|
|
234
|
-
`hero` · `markdown` · `steps` · `stats` · `files` · `tree` · `diff` · `code` · `tests` · `commits` · `terminal` · `compare` · `flow` · `chart` · `checklist` · `callout` · `image` · `question` · `next`
|
|
258
|
+
`hero` · `markdown` · `steps` · `stats` · `files` · `tree` · `diff` · `code` · `tests` · `commits` · `graph` · `terminal` · `compare` · `flow` · `chart` · `checklist` · `callout` · `image` · `question` · `actions` · `next`
|
|
235
259
|
|
|
236
260
|
## Adding a harness
|
|
237
261
|
|
|
@@ -251,6 +275,57 @@ If the harness has no system-prompt flag, prepend `systemPrompt` to the first pr
|
|
|
251
275
|
- [x] Cursor CLI adapter
|
|
252
276
|
- [x] Antigravity CLI adapter
|
|
253
277
|
|
|
278
|
+
## Worktrees and shipping
|
|
279
|
+
|
|
280
|
+
In a git repo, every launch (except Plan mode) runs in its own [git worktree](https://git-scm.com/docs/git-worktree) under `~/.wisp/worktrees/`, on a branch named `wisp/<task>-<id>` made from the remote's default branch (`origin/main`, fetched first; `HEAD` when there is no remote). Uncommitted changes in your own checkout are not carried over, and dependencies aren't shared: the agent is told to install what it needs. The card shows the branch, and **Take over** opens the worktree.
|
|
281
|
+
|
|
282
|
+
**Ship it** (`S`) commits whatever is uncommitted (the message is drafted from the presentation as a conventional commit; edit it before shipping), pushes the branch and runs `gh pr create`. You need the [GitHub CLI](https://cli.github.com) signed in. From then on the card follows the PR every minute: checks (✓ / running / failing), the review decision, merged or closed. **Merge** runs `gh pr ready` for drafts and `gh pr merge --squash --match-head-commit <sha>`, so a branch that moved since you looked is never merged, then fast-forwards your local base branch (`git merge --ff-only`, or a ref update when it isn't checked out) and removes the worktree and branch. If a ruleset requires a review, Merge waits for the approval like `gh` does.
|
|
283
|
+
|
|
284
|
+
**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.
|
|
285
|
+
|
|
286
|
+
Turn worktrees off in Settings → *Worktree per task*, with the launcher's **Worktree / In place** toggle, or per launch (`--no-worktree`, `"worktree": false`).
|
|
287
|
+
|
|
288
|
+
## Subagents
|
|
289
|
+
|
|
290
|
+
The launcher's **Subagents** button gives the lead agent a team: up to 6 members, each with its own harness, model, thinking level, name, a **Use for** note that tells the lead when to pick it, and a **×N** copies count (`Codex ×3` → `codex#1`…`codex#3`). The lead gets a `delegate` tool on wisp's MCP server; tasks sent in one call run in parallel in the lead's folder, and delegating to the same member again resumes its conversation. Claude Code, Codex and opencode can lead; Cursor and Antigravity can be members. **Save as preset** stores a team for later. Subagent edits count as the lead's, so they show up in its diffs, rollback and Ship it. API: `subagents` / `preset` on `POST /sessions`, `/teams`.
|
|
291
|
+
|
|
292
|
+
## Checkpoints and rollback
|
|
293
|
+
|
|
294
|
+
Before each turn, wisp snapshots the git working tree (pinned at `refs/wisp/checkpoints/<session>/<turn>`). **Restore to before this turn** (turn bar, session menu or ⌘K) rolls back; **Re-apply turn** undoes that. `diff` and `files` blocks have a per-file **Revert / Re-apply**, and `actions`, `terminal` and `tests` blocks run their command in the project folder with one click.
|
|
295
|
+
|
|
296
|
+
## Automations
|
|
297
|
+
|
|
298
|
+
**Recipes** are prompts you send again and again. `{name}` in a recipe becomes a field you fill in when it runs:
|
|
299
|
+
|
|
300
|
+
```text
|
|
301
|
+
Prepare release {version}: update CHANGELOG, README and docs/, bump package.json, tag v{version}.
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
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.8.0` fills the fields in order, `wisp run release version=0.8.0` by name.
|
|
305
|
+
|
|
306
|
+
**Triggers** launch sessions on their own while wisp is open (⌘K → *Triggers*):
|
|
307
|
+
|
|
308
|
+
| Kind | Fires when | Prompt fields |
|
|
309
|
+
|---|---|---|
|
|
310
|
+
| Schedule | a cron expression matches (local time), e.g. `0 9 * * 1-5` | your recipe's fields |
|
|
311
|
+
| CI failed | a GitHub Actions run fails (optionally on one branch) | `{name} {branch} {url} {id} {sha}` |
|
|
312
|
+
| Issue labelled | an open issue carries a label | `{number} {title} {body} {url} {author}` |
|
|
313
|
+
|
|
314
|
+
GitHub triggers poll with your `gh` login (every 2–60 minutes), so nothing has to reach your machine; the first check only records what already exists. Issue triggers start in Plan mode: issue text comes from whoever can open issues, so think twice before giving it Full auto.
|
|
315
|
+
|
|
316
|
+
## Usage
|
|
317
|
+
|
|
318
|
+
⌘K → *Usage* shows spend per day and, per project and per harness · model: spend, turns, how often the run worked (finished, not partial or blocked), how often the agent had to ask you, and the average time. Claude Code and opencode report cost; Codex, Cursor and Antigravity only report tokens, so their turns are counted but not priced. Also `wisp usage` and `GET /api/usage?days=30`.
|
|
319
|
+
|
|
320
|
+
## Phone
|
|
321
|
+
|
|
322
|
+
Settings → *Phone* sends questions and finished tasks to your phone and takes the answers back. wisp polls both services, so it works from anywhere without opening a port.
|
|
323
|
+
|
|
324
|
+
- **[ntfy](https://ntfy.sh)**: subscribe to the topic in the ntfy app. Notifications carry up to three buttons (the question's options, or *Mark done*); a tap sends a signed answer back. Use a long random topic (wisp suggests one): anyone who knows it can read the notifications, but only wisp can make answers that count.
|
|
325
|
+
- **Telegram**: create a bot with @BotFather, paste its token, then send `/link <code>` to the bot. Tap a button to answer, or reply to a message to answer in your own words or send a follow-up.
|
|
326
|
+
|
|
327
|
+
They receive titles, summaries and questions (with their context), never code or diffs.
|
|
328
|
+
|
|
254
329
|
## Learning
|
|
255
330
|
|
|
256
331
|
After a session has been quiet for 90 seconds (or right away when you mark it done or archive it), wisp runs a short **retro**: the same harness, read-only and without tools (Claude Code runs it on Haiku), reads a digest of the turns it hasn't seen yet. The digest holds your requests and follow-ups, questions and answers, failed tool calls and the commands run around them, and how each run ended. The retro then updates four small lists:
|
package/bin/wisp.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// wisp "fix the tests" launch a wisp in the current folder
|
|
6
6
|
// wisp add [path] add a folder as a project (default: current folder)
|
|
7
7
|
// wisp ls | show | wait | reply | stop script sessions (see --help)
|
|
8
|
+
// wisp run <recipe> [values…] run a saved recipe
|
|
8
9
|
// wisp mcp stdio MCP server, so other agents and editors can use wisp
|
|
9
10
|
// wisp skill [install] the agent skill that teaches other agents to use wisp
|
|
10
11
|
|
|
@@ -25,17 +26,22 @@ const HELP = `usage:
|
|
|
25
26
|
wisp wait <id> block until it finishes or asks you something
|
|
26
27
|
wisp reply <id> "text" follow up (answers a pending question) (--queue)
|
|
27
28
|
wisp stop <id> stop a run
|
|
29
|
+
wisp ship <id> commit, push and open a PR for a worktree session (--draft)
|
|
30
|
+
wisp merge <id> squash-merge its PR and sync your local base branch
|
|
31
|
+
wisp run <recipe> [values…] run a saved recipe; values fill its {params} in order, or key=value
|
|
32
|
+
wisp recipes list recipes
|
|
33
|
+
wisp usage spend and outcomes (--days 7|30|90)
|
|
28
34
|
wisp mcp run the MCP server (stdio) for other agents and editors
|
|
29
35
|
wisp skill [install] print / install the wisp agent skill (--agent claude|codex|opencode|all --project)
|
|
30
36
|
wisp -- "ls the repo" a task that starts with a command name
|
|
31
37
|
|
|
32
38
|
launch options: --mode plan|edits|auto --agent claude|codex|opencode|cursor|antigravity --model ID --effort LEVEL
|
|
33
|
-
--cwd DIR --wait --json
|
|
39
|
+
--cwd DIR --wait --json --no-worktree --compare agent[:model],…
|
|
34
40
|
options: --port N --browser --no-open --json --version`;
|
|
35
41
|
|
|
36
|
-
const COMMANDS = new Set(['add', 'ls', 'list', 'show', 'wait', 'reply', 'stop', 'mcp', 'skill']);
|
|
42
|
+
const COMMANDS = new Set(['add', 'ls', 'list', 'show', 'wait', 'reply', 'stop', 'mcp', 'skill', 'ship', 'merge', 'run', 'recipes', 'usage']);
|
|
37
43
|
const argv = process.argv.slice(2);
|
|
38
|
-
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 };
|
|
44
|
+
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 };
|
|
39
45
|
const words = [];
|
|
40
46
|
let literal = false;
|
|
41
47
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -56,6 +62,11 @@ for (let i = 0; i < argv.length; i++) {
|
|
|
56
62
|
else if (a === '--wait' || a === '-w') flags.wait = true;
|
|
57
63
|
else if (a === '--queue') flags.queue = true;
|
|
58
64
|
else if (a === '--project') flags.project = true;
|
|
65
|
+
else if (a === '--no-worktree') flags.worktree = false;
|
|
66
|
+
else if (a === '--worktree') flags.worktree = true;
|
|
67
|
+
else if (a === '--compare') flags.compare = argv[++i].split(',').filter(Boolean).map((x) => ({ agent: x.split(':')[0], model: x.split(':').slice(1).join(':') || undefined }));
|
|
68
|
+
else if (a === '--draft') flags.draft = true;
|
|
69
|
+
else if (a === '--days') flags.days = Number(argv[++i]);
|
|
59
70
|
else if (a === '--version' || a === '-v') {
|
|
60
71
|
console.log(JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url))).version);
|
|
61
72
|
process.exit(0);
|
|
@@ -64,7 +75,11 @@ for (let i = 0; i < argv.length; i++) {
|
|
|
64
75
|
process.exit(0);
|
|
65
76
|
} else words.push(a);
|
|
66
77
|
}
|
|
67
|
-
|
|
78
|
+
// Newer commands are also common first words of a task ("ship the docs", "usage of X"), so they
|
|
79
|
+
// only count when the rest fits; `wisp run` falls back to a task when no recipe has that name.
|
|
80
|
+
const SESSION_ID = /^[a-f0-9]{12}$/;
|
|
81
|
+
const FITS = { ship: (w) => SESSION_ID.test(w[1] || ''), merge: (w) => SESSION_ID.test(w[1] || ''), usage: (w) => w.length === 1, recipes: (w) => w.length === 1, run: (w) => w.length > 1 };
|
|
82
|
+
const command = literal !== 'task' && COMMANDS.has(words[0]) && (FITS[words[0]]?.(words) ?? true) ? words[0] : null;
|
|
68
83
|
const cwd = flags.cwd || process.cwd();
|
|
69
84
|
const die = (msg) => {
|
|
70
85
|
console.error(` ✗ ${msg}`);
|
|
@@ -98,7 +113,8 @@ async function scripted(cmd, args) {
|
|
|
98
113
|
};
|
|
99
114
|
switch (cmd) {
|
|
100
115
|
case 'launch': {
|
|
101
|
-
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 });
|
|
116
|
+
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 });
|
|
117
|
+
if (s.group && !flags.wait) return out(s, s.group.map((x) => ` ◌ launched on ${x.agent}: ${x.title} (${x.id})`).join('\n'));
|
|
102
118
|
if (!flags.wait) return out(s, ` ◌ launched: ${s.title} (${s.id})`);
|
|
103
119
|
if (!flags.json) console.error(` ◌ launched: ${s.title} (${s.id}) — waiting…`);
|
|
104
120
|
return show(s.id, await wisp.wait(s.id));
|
|
@@ -122,6 +138,37 @@ async function scripted(cmd, args) {
|
|
|
122
138
|
}
|
|
123
139
|
case 'stop':
|
|
124
140
|
return out(await wisp.stop(need(args[0], 'session id')), ' ✓ stopped');
|
|
141
|
+
case 'ship': {
|
|
142
|
+
const pr = await wisp.ship(need(args[0], 'session id'), { draft: flags.draft });
|
|
143
|
+
return out(pr, ` ✓ PR #${pr.number}${pr.draft ? ' (draft)' : ''}: ${pr.url}`);
|
|
144
|
+
}
|
|
145
|
+
case 'merge': {
|
|
146
|
+
const r = await wisp.merge(need(args[0], 'session id'));
|
|
147
|
+
return out(r, ` ✓ merged PR #${r.pr.number}\n ${r.sync.ok ? '✓' : '!'} ${r.sync.text}`);
|
|
148
|
+
}
|
|
149
|
+
case 'recipes': {
|
|
150
|
+
const list = await wisp.recipes();
|
|
151
|
+
return out(list, list.map((r) => `${r.name.padEnd(24)} ${r.params.map((p) => `{${p}}`).join(' ').padEnd(20)} ${r.prompt.replace(/\s+/g, ' ').slice(0, 60)}`).join('\n') || 'No recipes yet. Save one from wisp (⌘K → Recipes).');
|
|
152
|
+
}
|
|
153
|
+
case 'run': {
|
|
154
|
+
const key = need(args[0], 'recipe name');
|
|
155
|
+
const kv = Object.fromEntries(args.slice(1).filter((a) => /^[a-zA-Z][\w-]*=/.test(a)).map((a) => [a.slice(0, a.indexOf('=')), a.slice(a.indexOf('=') + 1)]));
|
|
156
|
+
const words = args.slice(1).filter((a) => !/^[a-zA-Z][\w-]*=/.test(a));
|
|
157
|
+
const s = await wisp.runRecipe(key, { params: kv, words, cwd, agent: flags.agent || undefined, model: flags.model ?? undefined, effort: flags.effort ?? undefined, mode: flags.mode || undefined }).catch((err) => {
|
|
158
|
+
if (err.status !== 404) throw err;
|
|
159
|
+
return wisp.launch({ prompt: ['run', ...args].join(' '), cwd, agent: flags.agent || undefined, mode: flags.mode || undefined, model: flags.model ?? undefined, effort: flags.effort ?? undefined, worktree: flags.worktree });
|
|
160
|
+
});
|
|
161
|
+
if (!flags.wait) return out(s, ` ◌ launched: ${s.title} (${s.id})`);
|
|
162
|
+
if (!flags.json) console.error(` ◌ launched: ${s.title} (${s.id}) — waiting…`);
|
|
163
|
+
return show(s.id, await wisp.wait(s.id));
|
|
164
|
+
}
|
|
165
|
+
case 'usage': {
|
|
166
|
+
const u = await wisp.usage({ days: flags.days || 30 });
|
|
167
|
+
const money = (v) => `$${v.toFixed(2)}`;
|
|
168
|
+
const pct = (v) => (v === null ? '—' : `${Math.round(v * 100)}%`);
|
|
169
|
+
const rows = (list, label) => list.map((x) => ` ${label(x).padEnd(32).slice(0, 32)} ${money(x.cost).padStart(8)} ${String(x.turns).padStart(5)} turns ${pct(x.successRate).padStart(4)} ok`).join('\n');
|
|
170
|
+
return out(u, `Last ${u.days} days: ${money(u.total.cost)} · ${u.total.turns} turns in ${u.total.sessions} sessions · ${pct(u.total.successRate)} worked · ${u.total.questions} questions\n\nBy project\n${rows(u.projects, (x) => x.name)}\n\nBy harness · model\n${rows(u.runners, (x) => `${x.agent} · ${x.model || 'default'}`)}`);
|
|
171
|
+
}
|
|
125
172
|
}
|
|
126
173
|
}
|
|
127
174
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wisp-agents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.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",
|
|
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",
|
|
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
|
},
|
|
@@ -182,6 +182,10 @@ function describeTool(name, input, cwd) {
|
|
|
182
182
|
}
|
|
183
183
|
case 'mcp__wisp__ask_user':
|
|
184
184
|
return { kind: 'wait', label: 'Waiting for your answer' };
|
|
185
|
+
case 'mcp__wisp__delegate':
|
|
186
|
+
return { kind: 'agent', label: `Delegating to ${clip([...new Set((input.tasks || []).map((t) => t.subagent))].join(', ') || 'subagents', 60)}` };
|
|
187
|
+
case 'mcp__wisp__subagent_results':
|
|
188
|
+
return { kind: 'agent', label: 'Waiting on subagents' };
|
|
185
189
|
default:
|
|
186
190
|
return { kind: 'tool', label: name.startsWith('mcp__') ? `Using ${name.split('__').slice(1).join(' · ')}` : `Using ${name}` };
|
|
187
191
|
}
|
package/server/adapters/codex.js
CHANGED
|
@@ -186,5 +186,6 @@ export function unwrapShell(cmd) {
|
|
|
186
186
|
|
|
187
187
|
function describeMcp(item) {
|
|
188
188
|
if (item.server === 'wisp' && item.tool === 'ask_user') return { kind: 'wait', label: 'Waiting for your answer' };
|
|
189
|
+
if (item.server === 'wisp' && (item.tool === 'delegate' || item.tool === 'subagent_results')) return { kind: 'agent', label: 'Waiting on subagents' };
|
|
189
190
|
return { kind: 'tool', label: `Using ${item.server} · ${item.tool}` };
|
|
190
191
|
}
|
|
@@ -169,7 +169,7 @@ export function fileTracker(cwd) {
|
|
|
169
169
|
known.set(r, after);
|
|
170
170
|
announced.delete(r);
|
|
171
171
|
if (before === after) return null;
|
|
172
|
-
return { file: r, op: before === null ? 'create' : 'write', edits: [{ before: before ?? '', after: after ?? '' }] };
|
|
172
|
+
return { file: r, op: before === null ? 'create' : 'write', beforeFile: before, edits: [{ before: before ?? '', after: after ?? '' }] };
|
|
173
173
|
},
|
|
174
174
|
};
|
|
175
175
|
}
|
package/server/adapters/index.js
CHANGED
|
@@ -41,7 +41,8 @@ export async function listAgents() {
|
|
|
41
41
|
const found = await detected.get(a.id);
|
|
42
42
|
// Only a hit is cached: a CLI installed while wisp is running shows up on the next call.
|
|
43
43
|
if (!found.available) detected.delete(a.id);
|
|
44
|
-
|
|
44
|
+
// delegates: can lead a team of subagents (needs wisp's MCP server injected).
|
|
45
|
+
return { id: a.id, name: a.name, models: a.models, efforts: a.efforts || [], delegates: a.askTool !== null, ...found };
|
|
45
46
|
}),
|
|
46
47
|
);
|
|
47
48
|
}
|
|
@@ -169,6 +169,10 @@ function describeTool(name, input, cwd) {
|
|
|
169
169
|
}
|
|
170
170
|
case 'wisp_ask_user':
|
|
171
171
|
return { kind: 'wait', label: 'Waiting for your answer' };
|
|
172
|
+
case 'wisp_delegate':
|
|
173
|
+
return { kind: 'agent', label: `Delegated to ${clip([...new Set((input.tasks || []).map((t) => t.subagent))].join(', ') || 'subagents', 60)}` };
|
|
174
|
+
case 'wisp_subagent_results':
|
|
175
|
+
return { kind: 'agent', label: 'Collected subagent reports' };
|
|
172
176
|
default:
|
|
173
177
|
return { kind: 'tool', label: `Using ${name.replace(/_/g, ' ')}` };
|
|
174
178
|
}
|