wisp-agents 0.14.0 → 0.16.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 +50 -1
- package/README.md +29 -13
- package/bin/wisp.js +3 -2
- package/package.json +3 -3
- package/server/adapters/antigravity.js +2 -2
- package/server/adapters/claude.js +31 -50
- package/server/adapters/codex.js +2 -2
- package/server/adapters/common.js +14 -34
- package/server/adapters/cursor.js +2 -2
- package/server/adapters/grok.js +2 -2
- package/server/adapters/index.js +16 -1
- package/server/adapters/opencode.js +47 -6
- package/server/checkpoint.js +12 -68
- package/server/client.js +5 -3
- package/server/git.js +9 -4
- package/server/handoff.js +70 -0
- package/server/http/respond.js +59 -0
- package/server/http/router.js +64 -0
- package/server/http/static.js +36 -0
- package/server/index.js +138 -1216
- package/server/machines/bootstrap.js +56 -0
- package/server/machines/hosts.js +127 -0
- package/server/machines.js +7 -179
- package/server/mcp-wisp.js +17 -5
- package/server/openapi.js +33 -2
- package/server/paths.js +32 -0
- package/server/preferences.js +2 -6
- package/server/proc.js +54 -0
- package/server/recipes.js +2 -4
- package/server/remote.js +14 -14
- package/server/router.js +7 -3
- package/server/routes/auto.js +29 -0
- package/server/routes/automations.js +92 -0
- package/server/routes/internal.js +56 -0
- package/server/routes/machines.js +43 -0
- package/server/routes/meta.js +76 -0
- package/server/routes/proxy.js +47 -0
- package/server/routes/sessions.js +339 -0
- package/server/routes/settings.js +144 -0
- package/server/routes/ship.js +61 -0
- package/server/routes/uploads.js +19 -0
- package/server/runner.js +87 -36
- package/server/services/auto.js +67 -0
- package/server/services/feedback.js +53 -0
- package/server/services/install.js +69 -0
- package/server/services/launch.js +127 -0
- package/server/services/phone.js +65 -0
- package/server/services/ship.js +82 -0
- package/server/services/telemetry.js +34 -0
- package/server/services/uploads.js +59 -0
- package/server/services/webhooks.js +28 -0
- package/server/store.js +5 -3
- package/server/subagents.js +5 -4
- package/server/telemetry.js +1 -1
- package/server/terminal.js +2 -1
- package/server/triggers.js +4 -6
- package/server/update.js +4 -3
- package/server/util.js +42 -0
- package/server/welcome.js +1 -1
- package/web/css/auto.css +29 -0
- package/web/css/automations.css +71 -0
- package/web/css/base.css +83 -0
- package/web/css/blocks.css +461 -0
- package/web/css/composer.css +62 -0
- package/web/css/feed.css +208 -0
- package/web/css/launcher.css +204 -0
- package/web/css/layout.css +61 -0
- package/web/css/machines.css +125 -0
- package/web/css/modals.css +176 -0
- package/web/css/palette.css +79 -0
- package/web/css/responsive.css +35 -0
- package/web/css/ship.css +95 -0
- package/web/css/subagents.css +130 -0
- package/web/css/tokens.css +115 -0
- package/web/index.html +16 -1
- package/web/js/app.js +28 -5232
- package/web/js/core/api.js +25 -0
- package/web/js/core/bus.js +28 -0
- package/web/js/core/dom.js +7 -0
- package/web/js/core/format.js +27 -0
- package/web/js/core/state.js +43 -0
- package/web/js/features/actions.js +339 -0
- package/web/js/features/attachments.js +115 -0
- package/web/js/features/auto.js +54 -0
- package/web/js/features/automations.js +305 -0
- package/web/js/features/commands.js +123 -0
- package/web/js/features/compare.js +104 -0
- package/web/js/features/composer.js +311 -0
- package/web/js/features/feed.js +400 -0
- package/web/js/features/feedback.js +127 -0
- package/web/js/features/keys.js +83 -0
- package/web/js/features/launcher.js +559 -0
- package/web/js/features/live.js +91 -0
- package/web/js/features/machines.js +313 -0
- package/web/js/features/memory.js +154 -0
- package/web/js/features/notify.js +36 -0
- package/web/js/features/phone.js +124 -0
- package/web/js/features/places.js +38 -0
- package/web/js/features/projects.js +284 -0
- package/web/js/features/runner-picker.js +233 -0
- package/web/js/features/runners.js +161 -0
- package/web/js/features/scroll.js +75 -0
- package/web/js/features/settings.js +390 -0
- package/web/js/features/setup.js +94 -0
- package/web/js/features/ship.js +122 -0
- package/web/js/features/team.js +386 -0
- package/web/js/features/turns.js +401 -0
- package/web/js/features/update.js +48 -0
- package/web/js/features/usage.js +75 -0
- package/web/js/ui/kbd.js +46 -0
- package/web/js/ui/overlay.js +38 -0
- package/web/js/ui/popover.js +52 -0
- package/web/js/ui/toast.js +23 -0
- package/web/css/app.css +0 -1901
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,53 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [0.16.0] - 2026-10-19
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Switch harness or model in a follow-up.** The follow-up box has a harness button, a small logo next to the mode button. It opens the same harness · model · thinking picker as the launcher. Pick another model and the follow-up resumes the same conversation on it. Pick another harness, say Codex on a task Claude Code started, and Codex starts its own conversation in the same folder. wisp hands it the turns it hasn't seen: each request, the report it got back and the files that were edited. Long histories keep the newest turns, up to about 24k characters. Each harness keeps its own conversation, so switching back to Claude Code resumes its session and only tells it what happened while it was away. Each turn's badge shows the harness that ran it. A queued follow-up keeps its own pick, so the run already going is not affected. On a session that Auto routed, switching counts as a miss for Auto's pick. API: `POST /api/sessions/:id/reply {"text","agent","model","effort"}`; CLI: `wisp reply <id> "…" --agent codex --model …`; MCP: `wisp_reply` takes `agent`, `model` and `effort`.
|
|
13
|
+
- **No harness yet? Install opencode in one click.** When wisp finds no coding-agent CLI on this computer, the launcher shows one line, *No coding agent here yet*, with an **Install opencode** button. opencode's free models need no account, so a new user can go from `npx wisp-agents` to a running task in a few seconds. wisp installs it with npm into its own folder (`~/.wisp/tools`), so it needs no sudo and leaves your shell profile alone, and finds it there. Pressing Enter with no harness shakes that line and keeps your prompt; before, the launch failed with *Auto found no installed harness*. Settings → Agents has the same **Install** button on the opencode row. API: `POST /api/agents/install {"id":"opencode"}`.
|
|
14
|
+
- wisp also finds opencode in `~/.opencode/bin`, where its official install script puts it, when that folder isn't on PATH (common for the desktop app).
|
|
15
|
+
|
|
16
|
+
- **wisp Cloud preview page** (`cloud.html`, linked from the site's nav and footer, and from a "Let it run while you sleep" section on the home page, between *Also in the box* and the install steps). It introduces a hosted wisp: managed remote machines, persistent queues that keep running while your laptop sleeps, and team dashboards. The local CLI stays free. You can try each part on the page: close the laptop and watch the cloud runs keep going, add machines, drag through one night of the queue, and filter a team dashboard by person. Signups for the waitlist go to PostHog.
|
|
17
|
+
- **One-line install: `curl -fsSL https://mokh.xyz/install.sh | bash`.** The script (`docs/install.sh`, served by the site) checks for macOS or Linux, Node.js 20+ and npm, runs `npm install -g wisp-agents` (set `WISP_VERSION` to pin a release), and confirms that `wisp` is on your PATH. It prints a PATH fix when the npm bin folder isn't in it, and a link to npm's permissions guide when the global install is refused. The site and the README show it next to `npx wisp-agents`.
|
|
18
|
+
- **Homebrew:** `brew install m0xoo/tap/wisp-agents`, from the new [M0xoo/homebrew-tap](https://github.com/M0xoo/homebrew-tap) repo (formula source in `packaging/homebrew/wisp-agents.rb`). It installs the CLI and browser mode; the formula removes the optional Electron package, which would otherwise download its ~300 MB binary into the Homebrew keg on first run.
|
|
19
|
+
- **Install tabs on the site.** The hero command and step 2 of the install section both have tabs (npx, npm, curl, brew), like opencode's install block. Picking a tab in one switches the other, the copy button copies the command shown, and the note under it changes with the tab. Arrow keys, Home and End move between tabs. Type a task into the hero launcher and every tab hands back a command that installs wisp and starts that task (`brew install m0xoo/tap/wisp-agents && wisp "…"`). A command too long for the bar stays on one line and ends in …, and a long typed task no longer makes the page scroll sideways on phones.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- **The website moved to [mokh.xyz](https://mokh.xyz/).** Canonical, Open Graph and structured-data URLs, `sitemap.xml`, `robots.txt`, `llms.txt` and the npm `homepage` now use the new domain. The old `m0xoo.github.io/wisp/` address redirects there.
|
|
24
|
+
- **New website.** The site opens on a working copy of the launcher: it types three tasks and sends them off, and their rows move from Working to Asking (answer it right there) to Done. Type your own task and press ↵ to get the `npx wisp-agents "…"` command that runs it for real. Further down it shows the real app instead of animated mock-ups. A scroll-driven "morning with wisp" walks through five real screens with a ticking clock: three tasks launched, a mid-run question with its desktop notification, a report, Ship it, and the merged PR. A Transcript / wisp report switch shows the same run both ways. Also new: a Compare section, a take-over table with each harness's resume command, and a terminal that replays real `wisp` CLI output. Screenshots follow your light or dark theme, and on phones the story becomes a simple timeline.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- **Claude in Chrome now works in Claude Code tasks.** Claude Code only turns the Chrome extension on by itself in interactive sessions; wisp runs it with `-p`, so its `mcp__claude-in-chrome__*` tools were missing. wisp now passes `--chrome` when you have Claude in Chrome enabled by default (`/chrome` in Claude Code, `claudeInChromeDefaultEnabled` in `~/.claude.json`). Set `WISP_CLAUDE_CHROME=1` or `0` to force it on or off.
|
|
29
|
+
- An opencode task on a rate-limited model (often a free one) no longer sits at "Starting up" forever. opencode retries without printing anything, so wisp now reads its error log. If the rate limit hasn't cleared after 60s, wisp stops the task and says what to do: try again later, pick another model, or run `opencode auth login`.
|
|
30
|
+
- wisp no longer crashes when the `claude` CLI quits before reading its prompt (a bad flag, an auth error, a crash on start). The task now ends with Claude's error message. Before, the unread prompt raised an unhandled `EPIPE` that took the whole server down.
|
|
31
|
+
- `config.json` (projects, recipes, triggers, machines, settings) is now written atomically. Before, a crash or power loss mid-write could leave a cut-off file, which loaded as the defaults and was then saved over. Auto's learned state and the learned preferences are written the same way.
|
|
32
|
+
- Idle API connections stay open longer than clients keep them, so a request sent on a connection the server was just closing no longer fails with `ECONNRESET`.
|
|
33
|
+
- Refused requests (bad input, not found) no longer print a stack trace in the terminal running wisp.
|
|
34
|
+
- **Run** / **Re-run** on a terminal, tests or actions block now shows the new output. Before, the command ran but the block kept its old output and an error toast said it failed.
|
|
35
|
+
- **Escape** closes the menu or popover that is open, wherever focus is, and nothing else. Before, it could collapse the card behind a context menu and leave the menu open.
|
|
36
|
+
- Settings switches, project edits, and deleting a recipe, trigger or subagent preset now show the server's error when it refuses the change, and leave things as they were. Before, nothing happened on screen; deleting a preset even said "Deleted".
|
|
37
|
+
- Expanding cards no longer leaves a hidden file picker behind each time, and the launcher no longer adds another listener on every redraw.
|
|
38
|
+
- `openapi.json` now documents subagent presets (`/teams`) and `GET /uploads/{id}`.
|
|
39
|
+
|
|
40
|
+
### Internal
|
|
41
|
+
|
|
42
|
+
- The server is split by area: `server/index.js` only wires things together. Routes live in `server/routes/` behind a small route table (`server/http/`), and shared logic lives in `server/services/` (launch, Ship it, Auto, uploads, phone, feedback). Helpers that were copied across files now come from `server/util.js`, and git always runs through one helper that honours `WISP_GIT_BIN`. Adapters are checked against their contract at startup, and `claude.js` uses the same spawn plumbing as the other harnesses. `test/routes.test.js` keeps the route table and `openapi.json` in sync. The HTTP API, CLI, SDK exports and the `~/.wisp` files are unchanged.
|
|
43
|
+
- The web UI's 5,300-line `app.js` is now about 40 modules under `web/js/` (`core/`, `ui/`, `features/`), and `app.css` is 15 stylesheets that, concatenated in link order, are byte-for-byte the old file. A small event bus replaces the hooks that live updates used to reach open dialogs through, and one popover helper replaces about ten copies of open, outside-click and close code. No build step was added.
|
|
44
|
+
|
|
45
|
+
## [0.15.0] - 2026-10-18
|
|
46
|
+
|
|
47
|
+
### Changed
|
|
48
|
+
|
|
49
|
+
- **Calmer turn bar.** The bar under each turn now fits on one line: the read / edited / ran / searched counts open the activity log themselves (no separate **Activity** link), and **Restore to before this turn** and **Agent notes** are icon buttons next to 👍 / 👎 that stay faint until you hover the turn. After a restore, **Re-apply** shows as text so the rolled-back state stays visible.
|
|
50
|
+
|
|
51
|
+
### Fixed
|
|
52
|
+
|
|
53
|
+
- Rating a turn with 👍 / 👎 no longer re-renders the whole card and replays its entrance animation. Every refetch re-inserted the turn nodes, which restarts their CSS animations; unchanged turns are now left in place.
|
|
54
|
+
|
|
8
55
|
## [0.14.0] - 2026-10-18
|
|
9
56
|
|
|
10
57
|
### Added
|
|
@@ -252,7 +299,9 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
|
|
|
252
299
|
- Session queue with follow-ups, answers to agent questions, terminal takeover and mark-as-done.
|
|
253
300
|
- Older turns are folded, with a floating collapse-all button and back-to-top.
|
|
254
301
|
|
|
255
|
-
[Unreleased]: https://github.com/M0xoo/wisp/compare/v0.
|
|
302
|
+
[Unreleased]: https://github.com/M0xoo/wisp/compare/v0.16.0...HEAD
|
|
303
|
+
[0.16.0]: https://github.com/M0xoo/wisp/compare/v0.15.0...v0.16.0
|
|
304
|
+
[0.15.0]: https://github.com/M0xoo/wisp/compare/v0.14.0...v0.15.0
|
|
256
305
|
[0.14.0]: https://github.com/M0xoo/wisp/compare/v0.13.0...v0.14.0
|
|
257
306
|
[0.13.0]: https://github.com/M0xoo/wisp/compare/v0.12.0...v0.13.0
|
|
258
307
|
[0.12.0]: https://github.com/M0xoo/wisp/compare/v0.11.0...v0.12.0
|
package/README.md
CHANGED
|
@@ -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.
|
|
57
|
+
| 🧾 **Recipes & triggers** | Saved prompts with `{params}` (`wisp run release 0.16.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** for each launch. `Shift+Tab` in the prompt cycles modes. |
|
|
@@ -73,13 +73,18 @@ npx wisp-agents --browser # or use it in the browser → http://l
|
|
|
73
73
|
|
|
74
74
|
The first run downloads Electron for the desktop app; if that isn't possible, wisp opens in the browser instead.
|
|
75
75
|
|
|
76
|
-
Or install it globally to get the `wisp` command everywhere:
|
|
76
|
+
Or install it globally to get the `wisp` command everywhere. Pick one:
|
|
77
77
|
|
|
78
78
|
```sh
|
|
79
|
-
npm install -g wisp-agents
|
|
79
|
+
curl -fsSL https://mokh.xyz/install.sh | bash # macOS or Linux: checks Node 20+, then npm install -g wisp-agents
|
|
80
|
+
npm install -g wisp-agents # any OS with Node 20+
|
|
81
|
+
brew install m0xoo/tap/wisp-agents # Homebrew: CLI and browser mode, no Electron desktop app
|
|
82
|
+
|
|
80
83
|
wisp "fix the flaky auth test"
|
|
81
84
|
```
|
|
82
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.16.0 bash`).
|
|
87
|
+
|
|
83
88
|
From source:
|
|
84
89
|
|
|
85
90
|
```sh
|
|
@@ -122,11 +127,11 @@ wisp "add a test" --wait # block until it finishes, print the presentat
|
|
|
122
127
|
wisp ls --state running,waiting # list sessions
|
|
123
128
|
wisp show <id> # state + latest presentation
|
|
124
129
|
wisp wait <id> # until it finishes or asks you something
|
|
125
|
-
wisp reply <id> "also do X" # follow up (answers a pending question; --mode to switch)
|
|
130
|
+
wisp reply <id> "also do X" # follow up (answers a pending question; --mode, --agent, --model to switch)
|
|
126
131
|
wisp stop <id>
|
|
127
132
|
wisp ship <id> [--draft] # commit, push and open a PR for a worktree task
|
|
128
133
|
wisp merge <id> # squash-merge its PR, fast-forward your local main
|
|
129
|
-
wisp run release 0.
|
|
134
|
+
wisp run release 0.16.0 # run a recipe; values fill its {params} (or version=0.16.0)
|
|
130
135
|
wisp recipes # list recipes
|
|
131
136
|
wisp usage [--days 7] # spend and outcomes
|
|
132
137
|
wisp mcp # MCP server for other agents and editors
|
|
@@ -216,13 +221,18 @@ wisp/
|
|
|
216
221
|
├── bin/
|
|
217
222
|
│ └── wisp.js # CLI: starts the server or talks to the running one
|
|
218
223
|
├── server/
|
|
219
|
-
│ ├── index.js #
|
|
220
|
-
│ ├──
|
|
224
|
+
│ ├── index.js # startServer: wires the pieces below together
|
|
225
|
+
│ ├── http/ # route table, JSON responses + SSE, static UI
|
|
226
|
+
│ ├── routes/ # one file per API area: sessions, ship, settings, automations, machines, …
|
|
227
|
+
│ ├── services/ # shared by routes and background jobs: launch, ship, auto, uploads, phone, …
|
|
228
|
+
│ ├── util.js # small shared helpers (errors, ids, quoting, atomic JSON writes)
|
|
229
|
+
│ ├── openapi.js # OpenAPI document for the public API (test/routes.test.js keeps it in sync)
|
|
221
230
|
│ ├── client.js # JS SDK (wisp-agents/client), used by the CLI and MCP server
|
|
222
231
|
│ ├── mcp-wisp.js # `wisp mcp`: wisp as tools for other agents
|
|
223
232
|
│ ├── render.js # presentation → markdown
|
|
224
233
|
│ ├── runner.js # session lifecycle, follow-ups, event normalization
|
|
225
|
-
│ ├── checkpoint.js # per-turn git checkpoints, file revert, rollback
|
|
234
|
+
│ ├── checkpoint.js # per-turn git checkpoints, file revert, rollback
|
|
235
|
+
│ ├── proc.js # cross-platform spawn, and running a block's command
|
|
226
236
|
│ ├── protocol.js # system prompt + ```wisp block parser (markdown fallback)
|
|
227
237
|
│ ├── mcp-ask.js # ask_user MCP server
|
|
228
238
|
│ ├── store.js # JSON-file persistence in ~/.wisp
|
|
@@ -231,6 +241,7 @@ wisp/
|
|
|
231
241
|
│ ├── triggers.js # cron schedules and GitHub watchers
|
|
232
242
|
│ ├── remote.js # phone: ntfy and Telegram, out and back
|
|
233
243
|
│ ├── machines.js # other computers: SSH, a tunnel to wisp there, relayed sessions
|
|
244
|
+
│ ├── machines/ # known hosts (ssh config, Tailscale) and the bootstrap script
|
|
234
245
|
│ ├── usage.js # spend and outcomes
|
|
235
246
|
│ ├── terminal.js # "take over" in your terminal
|
|
236
247
|
│ ├── welcome.js # the gallery session
|
|
@@ -244,10 +255,15 @@ wisp/
|
|
|
244
255
|
│ ├── antigravity.js # Antigravity CLI adapter
|
|
245
256
|
│ └── grok.js # Grok Build adapter
|
|
246
257
|
├── skills/wisp/SKILL.md # agent skill: how other agents use wisp
|
|
247
|
-
├── web/ # vanilla JS UI, no build step
|
|
258
|
+
├── web/ # vanilla JS UI, native ES modules, no build step
|
|
248
259
|
│ ├── index.html
|
|
249
|
-
│ ├── css/
|
|
250
|
-
│ └── js/
|
|
260
|
+
│ ├── css/ # one stylesheet per area (tokens, base, feed, blocks, modals, …), linked in order
|
|
261
|
+
│ └── js/
|
|
262
|
+
│ ├── app.js # boot, top bar, routing
|
|
263
|
+
│ ├── core/ # state, api, event bus, DOM helpers
|
|
264
|
+
│ ├── ui/ # toasts, dialogs, popovers, keyboard hints
|
|
265
|
+
│ ├── features/ # feed, turns, composer, launcher, settings, ship, machines, …
|
|
266
|
+
│ └── {blocks,md,diff,fuzzy,palette}.js # presentation blocks and their helpers
|
|
251
267
|
└── electron/ # desktop shell
|
|
252
268
|
├── main.js
|
|
253
269
|
└── preload.cjs
|
|
@@ -308,7 +324,7 @@ The launcher's **Subagents** button gives the lead agent a team: up to 6 members
|
|
|
308
324
|
|
|
309
325
|
## Checkpoints and rollback
|
|
310
326
|
|
|
311
|
-
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
|
|
327
|
+
Before each turn, wisp snapshots the git working tree (pinned at `refs/wisp/checkpoints/<session>/<turn>`). **Restore to before this turn** (the ⏪ icon in the turn bar, session menu or ⌘K) rolls back; **Re-apply** 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.
|
|
312
328
|
|
|
313
329
|
## Automations
|
|
314
330
|
|
|
@@ -318,7 +334,7 @@ Before each turn, wisp snapshots the git working tree (pinned at `refs/wisp/chec
|
|
|
318
334
|
Prepare release {version}: update CHANGELOG, README and docs/, bump package.json, tag v{version}.
|
|
319
335
|
```
|
|
320
336
|
|
|
321
|
-
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.
|
|
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.16.0` fills the fields in order, `wisp run release version=0.16.0` by name.
|
|
322
338
|
|
|
323
339
|
**Triggers** launch sessions on their own while wisp is open (⌘K → *Triggers*):
|
|
324
340
|
|
package/bin/wisp.js
CHANGED
|
@@ -24,7 +24,8 @@ const HELP = `usage:
|
|
|
24
24
|
wisp ls list sessions (--state running,waiting --limit N)
|
|
25
25
|
wisp show <id> state + latest presentation as markdown
|
|
26
26
|
wisp wait <id> block until it finishes or asks you something
|
|
27
|
-
wisp reply <id> "text" follow up (answers a pending question) (--queue --mode plan|edits|auto
|
|
27
|
+
wisp reply <id> "text" follow up (answers a pending question) (--queue --mode plan|edits|auto
|
|
28
|
+
--agent ID --model ID --effort LEVEL: switch harness / model from here on)
|
|
28
29
|
wisp stop <id> stop a run
|
|
29
30
|
wisp ship <id> commit, push and open a PR for a worktree session (--draft)
|
|
30
31
|
wisp merge <id> squash-merge its PR and sync your local base branch
|
|
@@ -132,7 +133,7 @@ async function scripted(cmd, args) {
|
|
|
132
133
|
}
|
|
133
134
|
case 'reply': {
|
|
134
135
|
const id = need(args[0], 'session id');
|
|
135
|
-
const r = await wisp.reply(id, need(args.slice(1).join(' ').trim(), 'text'), { queue: flags.queue, mode: flags.mode || undefined });
|
|
136
|
+
const r = await wisp.reply(id, need(args.slice(1).join(' ').trim(), 'text'), { queue: flags.queue, mode: flags.mode || undefined, agent: flags.agent || undefined, model: flags.model ?? undefined, effort: flags.effort ?? undefined });
|
|
136
137
|
if (!flags.wait) return out(r, r.answered ? ' ✓ answered' : r.interrupted ? ' ✓ interrupted; resuming with your message' : r.queued ? ' ✓ queued' : ' ✓ sent');
|
|
137
138
|
return show(id, await wisp.wait(id));
|
|
138
139
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wisp-agents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.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",
|
|
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",
|
|
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
|
},
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"type": "git",
|
|
45
45
|
"url": "git+https://github.com/M0xoo/wisp.git"
|
|
46
46
|
},
|
|
47
|
-
"homepage": "https://
|
|
47
|
+
"homepage": "https://mokh.xyz/",
|
|
48
48
|
"bugs": {
|
|
49
49
|
"url": "https://github.com/M0xoo/wisp/issues"
|
|
50
50
|
},
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// first message.
|
|
8
8
|
|
|
9
9
|
import path from 'node:path';
|
|
10
|
-
import { attachmentNote, clip, detectVersion, fileTracker, quietOutput, rel, spawnJsonl, withProtocol } from './common.js';
|
|
10
|
+
import { attachmentNote, clip, detectVersion, exitText, fileTracker, quietOutput, rel, spawnJsonl, withProtocol } from './common.js';
|
|
11
11
|
|
|
12
12
|
const BIN = process.env.WISP_ANTIGRAVITY_BIN || 'agy';
|
|
13
13
|
|
|
@@ -125,7 +125,7 @@ export default {
|
|
|
125
125
|
},
|
|
126
126
|
onClose({ code, signal, stderr, stopped }) {
|
|
127
127
|
if (sawResult) return;
|
|
128
|
-
onEvent({ type: 'result', isError: true, stopped, text: stderr ||
|
|
128
|
+
onEvent({ type: 'result', isError: true, stopped, text: stderr || exitText('agy', code, signal) });
|
|
129
129
|
},
|
|
130
130
|
});
|
|
131
131
|
},
|
|
@@ -1,13 +1,25 @@
|
|
|
1
1
|
// Claude Code adapter — drives `claude -p --output-format stream-json`.
|
|
2
2
|
|
|
3
|
-
import readline from 'node:readline';
|
|
4
3
|
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
5
|
import path from 'node:path';
|
|
6
|
-
import {
|
|
7
|
-
import { ASK_TIMEOUT_MS, MAX_SNAPSHOT, attachmentNote, clip, detectVersion, flatten, rel } from './common.js';
|
|
6
|
+
import { ASK_TIMEOUT_MS, MAX_SNAPSHOT, attachmentNote, clip, detectVersion, exitText, flatten, rel, spawnJsonl } from './common.js';
|
|
8
7
|
|
|
9
8
|
const BIN = process.env.WISP_CLAUDE_BIN || 'claude';
|
|
10
9
|
|
|
10
|
+
// Claude in Chrome turns itself on only in interactive sessions; `-p` needs --chrome. Follow the
|
|
11
|
+
// user's own "enabled by default" choice (/chrome in Claude Code) so a wisp run gets the same tools.
|
|
12
|
+
// WISP_CLAUDE_CHROME=1/0 overrides it.
|
|
13
|
+
export function chromeEnabled(env = process.env) {
|
|
14
|
+
if (env.WISP_CLAUDE_CHROME) return env.WISP_CLAUDE_CHROME !== '0';
|
|
15
|
+
const file = env.CLAUDE_CONFIG_DIR ? path.join(env.CLAUDE_CONFIG_DIR, '.claude.json') : path.join(os.homedir(), '.claude.json');
|
|
16
|
+
try {
|
|
17
|
+
return JSON.parse(fs.readFileSync(file, 'utf8')).claudeInChromeDefaultEnabled === true;
|
|
18
|
+
} catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
11
23
|
const PERMISSION_MODES = {
|
|
12
24
|
plan: 'plan',
|
|
13
25
|
edits: 'acceptEdits',
|
|
@@ -44,6 +56,7 @@ export default {
|
|
|
44
56
|
];
|
|
45
57
|
if (model) args.push('--model', model);
|
|
46
58
|
if (effort) args.push('--effort', effort);
|
|
59
|
+
if (chromeEnabled()) args.push('--chrome');
|
|
47
60
|
if (resumeId) args.push('--resume', resumeId);
|
|
48
61
|
if (Object.keys(mcpServers).length) {
|
|
49
62
|
args.push('--mcp-config', JSON.stringify({ mcpServers }));
|
|
@@ -61,12 +74,8 @@ export default {
|
|
|
61
74
|
delete env.CLAUDE_CODE_ENTRYPOINT;
|
|
62
75
|
env.MCP_TOOL_TIMEOUT ??= String(ASK_TIMEOUT_MS);
|
|
63
76
|
|
|
64
|
-
const child = spawn(BIN, args, { cwd, env, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
65
|
-
child.stdin.end(prompt);
|
|
66
|
-
|
|
67
77
|
let sawResult = false;
|
|
68
78
|
let early = null; // an empty result that came before our prompt was answered (see below)
|
|
69
|
-
let stderr = '';
|
|
70
79
|
const pendingTools = new Map(); // tool_use_id -> name, to flag failed tool results
|
|
71
80
|
// Background tasks (run_in_background shells, background subagents) still running. While any
|
|
72
81
|
// are, the CLI stays up after its result and starts another turn when they finish.
|
|
@@ -77,18 +86,7 @@ export default {
|
|
|
77
86
|
if ([...next.keys()].join(' ') !== was) onEvent({ type: 'background', tasks: [...next.values()] });
|
|
78
87
|
};
|
|
79
88
|
|
|
80
|
-
|
|
81
|
-
stderr = (stderr + d).slice(-8000);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
readline.createInterface({ input: child.stdout }).on('line', (line) => {
|
|
85
|
-
if (!line.trim()) return;
|
|
86
|
-
let msg;
|
|
87
|
-
try {
|
|
88
|
-
msg = JSON.parse(line);
|
|
89
|
-
} catch {
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
89
|
+
const onJson = (msg) => {
|
|
92
90
|
if (msg.session_id) onEvent({ type: 'session', id: msg.session_id });
|
|
93
91
|
|
|
94
92
|
if (msg.type === 'assistant') {
|
|
@@ -136,39 +134,22 @@ export default {
|
|
|
136
134
|
sawResult = true;
|
|
137
135
|
onEvent({ ...ev, background: background.size }); // > 0: another turn and result follow when they finish
|
|
138
136
|
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
let stopping = false;
|
|
142
|
-
const done = new Promise((resolve) => {
|
|
143
|
-
child.on('error', (err) => {
|
|
144
|
-
onEvent({ type: 'result', isError: true, text: `Could not start \`${BIN}\`: ${err.message}` });
|
|
145
|
-
sawResult = true;
|
|
146
|
-
resolve();
|
|
147
|
-
});
|
|
148
|
-
child.on('close', (code, signal) => {
|
|
149
|
-
if (!sawResult && early && !stopping) {
|
|
150
|
-
sawResult = true;
|
|
151
|
-
onEvent(early);
|
|
152
|
-
}
|
|
153
|
-
if (!sawResult) {
|
|
154
|
-
onEvent({
|
|
155
|
-
type: 'result',
|
|
156
|
-
isError: true,
|
|
157
|
-
stopped: stopping || signal === 'SIGTERM' || signal === 'SIGINT',
|
|
158
|
-
text: stderr.trim() || `claude exited with ${signal || `code ${code}`}`,
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
resolve();
|
|
162
|
-
});
|
|
163
|
-
});
|
|
137
|
+
};
|
|
164
138
|
|
|
165
|
-
return {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
139
|
+
return spawnJsonl({
|
|
140
|
+
bin: BIN,
|
|
141
|
+
args,
|
|
142
|
+
cwd,
|
|
143
|
+
env,
|
|
144
|
+
input: prompt,
|
|
145
|
+
onEvent,
|
|
146
|
+
onJson,
|
|
147
|
+
onClose: ({ code, signal, stderr, stopped, stopRequested }) => {
|
|
148
|
+
if (sawResult) return;
|
|
149
|
+
if (early && !stopRequested) return onEvent(early);
|
|
150
|
+
onEvent({ type: 'result', isError: true, stopped, text: stderr || exitText('claude', code, signal) });
|
|
170
151
|
},
|
|
171
|
-
};
|
|
152
|
+
});
|
|
172
153
|
},
|
|
173
154
|
};
|
|
174
155
|
|
package/server/adapters/codex.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Codex CLI adapter — drives `codex exec --json` (JSONL thread/turn/item events).
|
|
2
2
|
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import { ASK_TIMEOUT_MS, attachmentNote, clip, detectVersion, fileTracker, rel, spawnJsonl, withProtocol } from './common.js';
|
|
4
|
+
import { ASK_TIMEOUT_MS, attachmentNote, clip, detectVersion, exitText, fileTracker, rel, spawnJsonl, withProtocol } from './common.js';
|
|
5
5
|
|
|
6
6
|
const BIN = process.env.WISP_CODEX_BIN || 'codex';
|
|
7
7
|
const IMAGE = /^image\/(png|jpe?g|gif|webp)$/;
|
|
@@ -157,7 +157,7 @@ export default {
|
|
|
157
157
|
type: 'result',
|
|
158
158
|
isError: !ok,
|
|
159
159
|
stopped,
|
|
160
|
-
text: ok ? lastMessage : failure || lastError || stderr ||
|
|
160
|
+
text: ok ? lastMessage : failure || lastError || stderr || exitText('codex', code, signal),
|
|
161
161
|
stats: {
|
|
162
162
|
durationMs: Date.now() - started,
|
|
163
163
|
inputTokens: usage?.input_tokens,
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
// Helpers shared by the adapters: process plumbing, labels, and diff capture.
|
|
2
2
|
|
|
3
|
-
import { execFileSync } from 'node:child_process';
|
|
4
3
|
import readline from 'node:readline';
|
|
5
4
|
import fs from 'node:fs';
|
|
6
5
|
import path from 'node:path';
|
|
7
6
|
import { spawn, execFile, killTree } from '../proc.js';
|
|
7
|
+
import { gitSync } from '../git.js';
|
|
8
|
+
import { MAX_SNAPSHOT, rel } from '../paths.js';
|
|
9
|
+
import { clip } from '../util.js';
|
|
8
10
|
|
|
9
|
-
export const MAX_SNAPSHOT = 256 * 1024;
|
|
10
11
|
export const ASK_TIMEOUT_MS = 7 * 24 * 3600 * 1000; // ask_user may wait for the human a long time
|
|
11
12
|
|
|
12
13
|
export function detectVersion(bin, args = ['--version']) {
|
|
@@ -25,8 +26,10 @@ export function quietOutput(bin, args, timeout = 15_000) {
|
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
// Spawn a CLI that prints one JSON object per line. onJson gets each parsed line;
|
|
28
|
-
// onClose({ code, signal, stderr }) must emit the final `result` if the
|
|
29
|
-
|
|
29
|
+
// onClose({ code, signal, stderr, stopped, stopRequested }) must emit the final `result` if the
|
|
30
|
+
// stream didn't. stopped: killed by stop() or a signal; stopRequested: by stop() only.
|
|
31
|
+
// onStderr (optional) sees stderr as it comes, for CLIs that only report trouble there.
|
|
32
|
+
export function spawnJsonl({ bin, args, cwd, env, input, onJson, onClose, onEvent, onStderr }) {
|
|
30
33
|
// Some CLIs (opencode) take their working directory from $PWD rather than the real cwd.
|
|
31
34
|
const child = spawn(bin, args, { cwd, env: { ...env, PWD: cwd }, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
32
35
|
child.stdin.on('error', () => {}); // the CLI may exit before reading stdin
|
|
@@ -35,6 +38,7 @@ export function spawnJsonl({ bin, args, cwd, env, input, onJson, onClose, onEven
|
|
|
35
38
|
let stderr = '';
|
|
36
39
|
child.stderr.on('data', (d) => {
|
|
37
40
|
stderr = (stderr + d).slice(-8000);
|
|
41
|
+
onStderr?.(String(d));
|
|
38
42
|
});
|
|
39
43
|
readline.createInterface({ input: child.stdout }).on('line', (line) => {
|
|
40
44
|
if (!line.trim()) return;
|
|
@@ -56,7 +60,7 @@ export function spawnJsonl({ bin, args, cwd, env, input, onJson, onClose, onEven
|
|
|
56
60
|
resolve();
|
|
57
61
|
});
|
|
58
62
|
child.on('close', (code, signal) => {
|
|
59
|
-
if (!failed) onClose({ code, signal, stderr: stderr.trim(), stopped: stopping || signal === 'SIGTERM' || signal === 'SIGINT' });
|
|
63
|
+
if (!failed) onClose({ code, signal, stderr: stderr.trim(), stopped: stopping || signal === 'SIGTERM' || signal === 'SIGINT', stopRequested: stopping });
|
|
60
64
|
resolve();
|
|
61
65
|
});
|
|
62
66
|
});
|
|
@@ -70,6 +74,9 @@ export function spawnJsonl({ bin, args, cwd, env, input, onJson, onClose, onEven
|
|
|
70
74
|
};
|
|
71
75
|
}
|
|
72
76
|
|
|
77
|
+
// The result text for a CLI that ended without saying why.
|
|
78
|
+
export const exitText = (bin, code, signal) => `${bin} exited with ${signal || `code ${code}`}`;
|
|
79
|
+
|
|
73
80
|
export function attachmentNote(files, how = 'Read images and PDFs with your file-reading tool') {
|
|
74
81
|
if (!files.length) return '';
|
|
75
82
|
const lines = files.map((f) => `- ${f.path} (${f.mime}, ${Math.max(1, Math.round(f.size / 1024))} KB)`);
|
|
@@ -82,34 +89,7 @@ export function withProtocol(prompt, systemPrompt, resumeId) {
|
|
|
82
89
|
return resumeId ? prompt : `${systemPrompt.trim()}\n\n---\n\n# Task\n\n${prompt}`;
|
|
83
90
|
}
|
|
84
91
|
|
|
85
|
-
|
|
86
|
-
function realDir(dir) {
|
|
87
|
-
if (!realDirs.has(dir)) {
|
|
88
|
-
try {
|
|
89
|
-
realDirs.set(dir, fs.realpathSync(dir));
|
|
90
|
-
} catch {
|
|
91
|
-
realDirs.set(dir, dir);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
return realDirs.get(dir);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Path relative to cwd, or the path as given if it lies outside. Harnesses may report paths
|
|
98
|
-
// through the resolved cwd (/private/tmp vs /tmp on macOS), so both spellings count.
|
|
99
|
-
export function rel(file, cwd) {
|
|
100
|
-
if (!file) return '';
|
|
101
|
-
const abs = path.resolve(cwd, file);
|
|
102
|
-
for (const base of [cwd, realDir(cwd)]) {
|
|
103
|
-
const r = path.relative(base, abs);
|
|
104
|
-
if (r && !r.startsWith('..') && !path.isAbsolute(r)) return r;
|
|
105
|
-
}
|
|
106
|
-
return file;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export function clip(s, n = 80) {
|
|
110
|
-
s = String(s ?? '').replace(/\s+/g, ' ').trim();
|
|
111
|
-
return s.length > n ? `${s.slice(0, n - 1)}…` : s;
|
|
112
|
-
}
|
|
92
|
+
export { clip, MAX_SNAPSHOT, rel };
|
|
113
93
|
|
|
114
94
|
export function flatten(content) {
|
|
115
95
|
if (typeof content === 'string') return content;
|
|
@@ -136,7 +116,7 @@ export function fileTracker(cwd) {
|
|
|
136
116
|
const known = new Map(); // rel path -> content after our last change() (null = missing)
|
|
137
117
|
const announced = new Map(); // rel path -> content when the harness announced an edit
|
|
138
118
|
let base;
|
|
139
|
-
const git = (args, opts
|
|
119
|
+
const git = (args, opts) => gitSync(cwd, args, opts);
|
|
140
120
|
try {
|
|
141
121
|
// Pinned before the agent starts writing.
|
|
142
122
|
base = git(['stash', 'create']).toString().trim() || git(['rev-parse', '--verify', 'HEAD']).toString().trim();
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// cursor-agent has no flag to inject an MCP server (it only reads .cursor/mcp.json), so wisp's
|
|
4
4
|
// ask_user tool isn't available here; the protocol tells the agent to end with a question block.
|
|
5
5
|
|
|
6
|
-
import { attachmentNote, clip, detectVersion, fileTracker, quietOutput, rel, spawnJsonl, withProtocol } from './common.js';
|
|
6
|
+
import { attachmentNote, clip, detectVersion, exitText, fileTracker, quietOutput, rel, spawnJsonl, withProtocol } from './common.js';
|
|
7
7
|
|
|
8
8
|
const BIN = process.env.WISP_CURSOR_BIN || 'cursor-agent';
|
|
9
9
|
|
|
@@ -91,7 +91,7 @@ export default {
|
|
|
91
91
|
},
|
|
92
92
|
onClose({ code, signal, stderr, stopped }) {
|
|
93
93
|
if (sawResult) return;
|
|
94
|
-
onEvent({ type: 'result', isError: true, stopped, text: stderr ||
|
|
94
|
+
onEvent({ type: 'result', isError: true, stopped, text: stderr || exitText('cursor-agent', code, signal) });
|
|
95
95
|
},
|
|
96
96
|
});
|
|
97
97
|
},
|
package/server/adapters/grok.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import fs from 'node:fs';
|
|
10
10
|
import os from 'node:os';
|
|
11
11
|
import path from 'node:path';
|
|
12
|
-
import { ASK_TIMEOUT_MS, attachmentNote, clip, detectVersion, fileTracker, flatten, quietOutput, rel, spawnJsonl } from './common.js';
|
|
12
|
+
import { ASK_TIMEOUT_MS, attachmentNote, clip, detectVersion, exitText, fileTracker, flatten, quietOutput, rel, spawnJsonl } from './common.js';
|
|
13
13
|
|
|
14
14
|
const BIN = process.env.WISP_GROK_BIN || 'grok';
|
|
15
15
|
|
|
@@ -137,7 +137,7 @@ export default {
|
|
|
137
137
|
},
|
|
138
138
|
onClose({ code, signal, stderr, stopped }) {
|
|
139
139
|
if (sawResult) return;
|
|
140
|
-
onEvent({ type: 'result', isError: true, stopped, text: error || stderr ||
|
|
140
|
+
onEvent({ type: 'result', isError: true, stopped, text: error || stderr || exitText('grok', code, signal) });
|
|
141
141
|
},
|
|
142
142
|
});
|
|
143
143
|
handle.done.finally(() => fs.rmSync(tmp, { recursive: true, force: true }));
|
package/server/adapters/index.js
CHANGED
|
@@ -31,7 +31,22 @@ import cursor from './cursor.js';
|
|
|
31
31
|
import antigravity from './antigravity.js';
|
|
32
32
|
import grok from './grok.js';
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
// The shape above, checked when wisp starts: a missing piece fails loudly here, not mid-run.
|
|
35
|
+
function checked(a) {
|
|
36
|
+
const missing = [
|
|
37
|
+
typeof a.id !== 'string' && 'id',
|
|
38
|
+
typeof a.name !== 'string' && 'name',
|
|
39
|
+
!Array.isArray(a.models) && 'models',
|
|
40
|
+
a.askTool !== null && a.askTool !== undefined && typeof a.askTool !== 'string' && 'askTool',
|
|
41
|
+
typeof a.detect !== 'function' && 'detect()',
|
|
42
|
+
typeof a.run !== 'function' && 'run()',
|
|
43
|
+
a.resumeCommand !== undefined && typeof a.resumeCommand !== 'function' && 'resumeCommand()',
|
|
44
|
+
].filter(Boolean);
|
|
45
|
+
if (missing.length) throw new Error(`adapter ${a.id || '?'}: missing or wrong ${missing.join(', ')}`);
|
|
46
|
+
return a;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const adapters = Object.fromEntries([claude, codex, opencode, cursor, antigravity, grok].map((a) => [a.id, checked(a)]));
|
|
35
50
|
|
|
36
51
|
const detected = new Map();
|
|
37
52
|
|