@stringsync/splates 0.0.0-stage → 0.0.45

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 (251) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +308 -2
  3. package/acp-extensions.d.ts +82 -0
  4. package/add.d.ts +53 -0
  5. package/adherence.d.ts +49 -0
  6. package/age.d.ts +2 -0
  7. package/agent-connection.d.ts +171 -0
  8. package/agent-errors.d.ts +2 -0
  9. package/agent-process.d.ts +65 -0
  10. package/agent-stream.d.ts +91 -0
  11. package/agents.d.ts +15 -0
  12. package/artifact-room.d.ts +47 -0
  13. package/artifact-viewers.d.ts +6 -0
  14. package/artifact.d.ts +30 -0
  15. package/artifacts.d.ts +161 -0
  16. package/asked.d.ts +146 -0
  17. package/asked.js +7 -0
  18. package/assumed.d.ts +34 -0
  19. package/attach.d.ts +30 -0
  20. package/attachments.d.ts +43 -0
  21. package/baselines.d.ts +55 -0
  22. package/claim.d.ts +17 -0
  23. package/cloudflare-api.d.ts +25 -0
  24. package/cloudflare-error.d.ts +5 -0
  25. package/cloudflared-tunnel-provider.d.ts +27 -0
  26. package/codex-limits.d.ts +19 -0
  27. package/compaction.d.ts +17 -0
  28. package/config-source.d.ts +13 -0
  29. package/config.d.ts +98 -0
  30. package/config.js +7 -0
  31. package/conversation-archive.d.ts +6 -0
  32. package/conversation-background.d.ts +21 -0
  33. package/conversation-choice.d.ts +4 -0
  34. package/conversation-files.d.ts +51 -0
  35. package/conversation-given.d.ts +8 -0
  36. package/conversation-host.d.ts +102 -0
  37. package/conversation-listing.d.ts +15 -0
  38. package/conversation-log.d.ts +301 -0
  39. package/conversation-messages.d.ts +18 -0
  40. package/conversation-name.d.ts +28 -0
  41. package/conversation-options.d.ts +73 -0
  42. package/conversation-reference.d.ts +2 -0
  43. package/conversation-state.d.ts +472 -0
  44. package/conversation-turns.d.ts +115 -0
  45. package/conversation.d.ts +209 -0
  46. package/conversations.d.ts +140 -0
  47. package/coordinator-state.d.ts +28 -0
  48. package/creds.d.ts +123 -0
  49. package/darwin-temperature.d.ts +7 -0
  50. package/decided.d.ts +18 -0
  51. package/demo.d.ts +29 -0
  52. package/dev/api.d.ts +209 -0
  53. package/dev/assets.d.ts +24 -0
  54. package/dev/conversation-address.d.ts +47 -0
  55. package/dev/conversation-feed.d.ts +166 -0
  56. package/dev/live-stream.d.ts +29 -0
  57. package/dev/worker.d.ts +33 -0
  58. package/dev-access.d.ts +61 -0
  59. package/dev-artifacts.d.ts +22 -0
  60. package/dev-budgets.d.ts +18 -0
  61. package/dev-conversation-events.d.ts +39 -0
  62. package/dev-conversation-routes.d.ts +90 -0
  63. package/dev-creds.d.ts +17 -0
  64. package/dev-dictation-connection.d.ts +18 -0
  65. package/dev-dictation.d.ts +17 -0
  66. package/dev-http.d.ts +76 -0
  67. package/dev-open.d.ts +15 -0
  68. package/dev-options.d.ts +133 -0
  69. package/dev-outcomes.d.ts +21 -0
  70. package/dev-pages.d.ts +16 -0
  71. package/dev-picker.d.ts +57 -0
  72. package/dev-port-socket.d.ts +26 -0
  73. package/dev-ports.d.ts +11 -0
  74. package/dev-project-watch.d.ts +44 -0
  75. package/dev-project.d.ts +168 -0
  76. package/dev-push.d.ts +59 -0
  77. package/dev-restart.d.ts +141 -0
  78. package/dev-servers.d.ts +30 -0
  79. package/dev-setup-native.d.ts +23 -0
  80. package/dev-setup-opencode.d.ts +29 -0
  81. package/dev-setup.d.ts +16 -0
  82. package/dev-shell.d.ts +70 -0
  83. package/dev-slot.d.ts +28 -0
  84. package/dev-sockets.d.ts +13 -0
  85. package/dev-task-ports.d.ts +73 -0
  86. package/dev-tasks.d.ts +20 -0
  87. package/dev-terminal-connection.d.ts +19 -0
  88. package/dev-terminal.d.ts +42 -0
  89. package/dev-todos.d.ts +35 -0
  90. package/dev-transcriber.d.ts +15 -0
  91. package/dev-tunnel.d.ts +38 -0
  92. package/dev-versions.d.ts +53 -0
  93. package/dev-workers.d.ts +55 -0
  94. package/dev.d.ts +24 -0
  95. package/diff.d.ts +29 -0
  96. package/dispatched.d.ts +16 -0
  97. package/editors.d.ts +22 -0
  98. package/escalate.d.ts +27 -0
  99. package/events.d.ts +311 -0
  100. package/everywhere.d.ts +41 -0
  101. package/exhibit.d.ts +21 -0
  102. package/exit.d.ts +54 -0
  103. package/fact-todos.d.ts +18 -0
  104. package/fake-agent.d.ts +107 -0
  105. package/fake-agent.js +284 -0
  106. package/finding-facts.d.ts +38 -0
  107. package/findings.d.ts +84 -0
  108. package/fold.d.ts +43 -0
  109. package/follow.d.ts +41 -0
  110. package/format.d.ts +68 -0
  111. package/git.d.ts +112 -0
  112. package/handed-out.d.ts +79 -0
  113. package/handoff.d.ts +23 -0
  114. package/heavy-run.d.ts +12 -0
  115. package/heavy.d.ts +28 -0
  116. package/heavy.js +16 -0
  117. package/held.d.ts +22 -0
  118. package/index-0tfvy823.js +272 -0
  119. package/index-43pq33n4.js +157 -0
  120. package/index-4776671w.js +698 -0
  121. package/index-d4hjrzaq.js +464 -0
  122. package/index-gcfq7awr.js +740 -0
  123. package/index-k73tsw5g.js +101 -0
  124. package/index-kaa44k7q.js +59 -0
  125. package/index-vcv9m93e.js +1265 -0
  126. package/index-xmfmy729.js +249 -0
  127. package/index.d.ts +2 -0
  128. package/index.js +26928 -0
  129. package/init.d.ts +28 -0
  130. package/journal.d.ts +57 -0
  131. package/landed.d.ts +14 -0
  132. package/lane-todos.d.ts +44 -0
  133. package/lane.d.ts +55 -0
  134. package/lanes-from-tags.d.ts +17 -0
  135. package/lanes.d.ts +96 -0
  136. package/legacy-names.d.ts +70 -0
  137. package/legacy-ps.d.ts +31 -0
  138. package/limits.d.ts +32 -0
  139. package/list.d.ts +18 -0
  140. package/load-config.d.ts +44 -0
  141. package/log.d.ts +14 -0
  142. package/machine.d.ts +42 -0
  143. package/merge.d.ts +35 -0
  144. package/migrate-config.d.ts +31 -0
  145. package/models.d.ts +24 -0
  146. package/needs.d.ts +115 -0
  147. package/notices.d.ts +39 -0
  148. package/offered.d.ts +39 -0
  149. package/opencode-usage.d.ts +37 -0
  150. package/outcomes.d.ts +114 -0
  151. package/package.json +74 -5
  152. package/path.d.ts +13 -0
  153. package/plan.d.ts +236 -0
  154. package/port-forward.d.ts +22 -0
  155. package/port-pipe.d.ts +36 -0
  156. package/ports-list.d.ts +19 -0
  157. package/probe-runtime.d.ts +5 -0
  158. package/probe-runtime.js +13 -0
  159. package/project-reaches.d.ts +23 -0
  160. package/projects.d.ts +136 -0
  161. package/prompts.d.ts +128 -0
  162. package/pty.d.ts +39 -0
  163. package/pulse.d.ts +8 -0
  164. package/recommend.d.ts +31 -0
  165. package/release.d.ts +41 -0
  166. package/remote-process.d.ts +26 -0
  167. package/remote-pty.d.ts +29 -0
  168. package/remote-worker.d.ts +77 -0
  169. package/remove.d.ts +31 -0
  170. package/repository.d.ts +91 -0
  171. package/retry.d.ts +34 -0
  172. package/roster.d.ts +63 -0
  173. package/saved-ports-host.d.ts +25 -0
  174. package/saved-tunnel.d.ts +44 -0
  175. package/server-worker-socket.d.ts +21 -0
  176. package/setup-areas.d.ts +7 -0
  177. package/setup-opencode-model-error.d.ts +3 -0
  178. package/setup-opencode-models.d.ts +54 -0
  179. package/setup-opencode-recipe.d.ts +26 -0
  180. package/setup-opencode.d.ts +46 -0
  181. package/setup.d.ts +102 -0
  182. package/shared-browser.d.ts +50 -0
  183. package/shell.d.ts +17 -0
  184. package/show.d.ts +106 -0
  185. package/skill-checks.d.ts +73 -0
  186. package/skill-checks.js +10 -0
  187. package/skills.d.ts +16 -0
  188. package/slot-tunnel.d.ts +27 -0
  189. package/slots.d.ts +135 -0
  190. package/snapshot.d.ts +75 -0
  191. package/splates-artifact.d.ts +3 -0
  192. package/splates-creds.d.ts +3 -0
  193. package/splates-dev.d.ts +22 -0
  194. package/splates-flags.d.ts +34 -0
  195. package/splates-lane.d.ts +3 -0
  196. package/splates-projects.d.ts +3 -0
  197. package/splates-setup.d.ts +17 -0
  198. package/splates-slots.d.ts +3 -0
  199. package/splates-todo.d.ts +3 -0
  200. package/splates-tunnel.d.ts +3 -0
  201. package/splates-worker.d.ts +3 -0
  202. package/splates.d.ts +28 -0
  203. package/stats.d.ts +186 -0
  204. package/supervise.d.ts +50 -0
  205. package/system-changes.d.ts +67 -0
  206. package/system.d.ts +28 -0
  207. package/systems.d.ts +119 -0
  208. package/systems.js +21 -0
  209. package/table.d.ts +5 -0
  210. package/task-log.d.ts +35 -0
  211. package/task-ports.d.ts +25 -0
  212. package/tell.d.ts +47 -0
  213. package/todo-edit.d.ts +60 -0
  214. package/todo-file.d.ts +6 -0
  215. package/todo-lanes.d.ts +82 -0
  216. package/todo-list.d.ts +26 -0
  217. package/todo-take.d.ts +27 -0
  218. package/todo.d.ts +146 -0
  219. package/todos.d.ts +157 -0
  220. package/tool-calls.d.ts +18 -0
  221. package/tunnel-provider.d.ts +37 -0
  222. package/tunnel-provider.js +0 -0
  223. package/tunnel-remove.d.ts +28 -0
  224. package/tunnel-setup.d.ts +104 -0
  225. package/unix-worker-listener.d.ts +23 -0
  226. package/unix-worker-socket.d.ts +30 -0
  227. package/update.d.ts +29 -0
  228. package/update.js +10 -0
  229. package/vocabulary.d.ts +9 -0
  230. package/wait.d.ts +36 -0
  231. package/watch.d.ts +46 -0
  232. package/watched.d.ts +43 -0
  233. package/websocket-worker-socket.d.ts +18 -0
  234. package/worker-call-host.d.ts +51 -0
  235. package/worker-channel.d.ts +83 -0
  236. package/worker-connection.d.ts +24 -0
  237. package/worker-daemon.d.ts +64 -0
  238. package/worker-git.d.ts +18 -0
  239. package/worker-list.d.ts +49 -0
  240. package/worker-machine.d.ts +34 -0
  241. package/worker-port-host.d.ts +21 -0
  242. package/worker-process-host.d.ts +29 -0
  243. package/worker-protocol.d.ts +104 -0
  244. package/worker-pty-host.d.ts +34 -0
  245. package/worker-registry.d.ts +68 -0
  246. package/worker-replay.d.ts +25 -0
  247. package/worker-run.d.ts +20 -0
  248. package/worker-session.d.ts +189 -0
  249. package/worker-socket.d.ts +15 -0
  250. package/worktree-service.d.ts +113 -0
  251. package/worktree.d.ts +232 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 stringsync LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,309 @@
1
- # Temporary Holding Version
1
+ # Spinning Plates
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [spinningplates.tech](https://spinningplates.tech) · command: `splates`
4
+
5
+ Run many coding agents on one repository at once. Each works in its own git
6
+ worktree and lands its change on `main` itself, without pull requests.
7
+
8
+ ```bash
9
+ bunx @stringsync/splates init
10
+ ```
11
+
12
+ Run it in your repository and commit what it writes: the splates skill, which it
13
+ installs with [`skills`](https://skills.sh) from this release's tag into
14
+ `.agents/skills` for Codex and most agents, linked from `.claude/skills` for
15
+ Claude Code, and `skills-lock.json`. `splates update` moves it to a newer release.
16
+ Without splates, `bunx skills add stringsync/splates#v<release> --skill splates`
17
+ installs the same skill. From then on, your agents start each change with
18
+ `splates add` and land it with `splates merge`.
19
+
20
+ Spinning Plates was arbor (`@webappwiz/arbor`). In a project that used it, run
21
+ `bunx @stringsync/splates update`: it installs the splates skill in place of arbor's and
22
+ renames `arbor.config.ts` and `arbor.systems.ts`. The next `splates server` moves
23
+ `~/.arbor` and each repository's `.git/arbor` to their new names, leaving links
24
+ behind. For one release, `arbor` still runs splates, and the `ARBOR_*`
25
+ variables, `ARBOR.md` and the `[ARBOR …]` directives are still read.
26
+
27
+ Contributions are welcome: see [Contributing](CONTRIBUTING.md).
28
+
29
+ ## How it works
30
+
31
+ At its core, splates is a consistent way to coordinate worktrees. Each change is
32
+ a task with its own branch and worktree, and every agent moves tasks through
33
+ the same few commands, so agents never edit the same checkout and always know
34
+ what the others are doing. There is no daemon and no orchestrator: splates is a
35
+ CLI over git, and any agent in any terminal can use it. `splates --help` lists
36
+ every command. A bare `splates` prints help too; start the web page with
37
+ `splates server`. The `splates dev` namespace belongs only to this repository
38
+ and is absent from the published CLI.
39
+
40
+ **Merging.** `splates merge` rebases the task onto `main`, runs your checks
41
+ (`preMerge` in `splates.config.ts`) on the result, fast-forwards `main`, and
42
+ removes the worktree. A conflict or a failing check goes back to the agent that
43
+ made the change, which fixes it and merges again.
44
+
45
+ **Heavy jobs.** Hooks (`postCheckout`, `postRewrite`, `preMerge`) run in one
46
+ of the machine's heavy-job slots, two by default (`SPLATES_HEAVY_JOBS` sets
47
+ how many), so several agents testing at once queue rather than run the
48
+ machine out of memory. `splates heavy -- <command>` runs anything else as
49
+ heavy, such as a test suite an agent runs mid-task, in a slot too.
50
+
51
+ **Escalating.** When an agent needs you (a decision, access, a review, or
52
+ merges that keep failing), it runs `splates escalate`: it stops, leaves its
53
+ worktree as it is, and writes down its questions. You answer, and it picks up
54
+ where it left off.
55
+
56
+ ## The harness
57
+
58
+ `splates server` is an optional page for running agents and answering them, on
59
+ http://localhost:4269.
60
+
61
+ ```bash
62
+ bunx @stringsync/splates server
63
+ ```
64
+
65
+ Run it from anywhere: it serves every splates project on this machine. Its
66
+ project picker switches between them, and adds a repository by its path,
67
+ running `splates init` there and asking before it commits what that wrote.
68
+
69
+ ### Parallel agents
70
+
71
+ Start as many conversations as you like, each in a task of its own.
72
+
73
+ ![Several agents working at once](https://raw.githubusercontent.com/stringsync/splates/main/packages/splates/screenshots/parallel.png)
74
+
75
+ ### Questions and demos
76
+
77
+ Agents ask as they go instead of guessing. Each question comes with its
78
+ choices, the one the agent would pick, and a screenshot of each when there is
79
+ something to see. Answer in a click.
80
+
81
+ ![Questions with a screenshot per choice](https://raw.githubusercontent.com/stringsync/splates/main/packages/splates/screenshots/questions.png)
82
+
83
+ When work is ready, the agent hands you a demo: steps to try, what you should
84
+ see, and a checklist to tick off before you merge or ask for changes.
85
+
86
+ ![A demo to try before merging](https://raw.githubusercontent.com/stringsync/splates/main/packages/splates/screenshots/demo.png)
87
+
88
+ ### Annotate any image
89
+
90
+ Mark up any image, one you upload or one an agent shows you. Draw a box or an
91
+ arrow, pin a note to each, and send it back.
92
+
93
+ ![An agent's screenshot, marked up](https://raw.githubusercontent.com/stringsync/splates/main/packages/splates/screenshots/annotate.png)
94
+
95
+ ### Remote access
96
+
97
+ To open the page from another machine, put it behind a Cloudflare tunnel and
98
+ Cloudflare Access, which decides who may enter. Set it up once per machine:
99
+
100
+ ```bash
101
+ bunx @stringsync/splates tunnel setup splates.example.com --email me@example.com
102
+ bunx @stringsync/splates creds wait SPLATES_CLOUDFLARE_API_TOKEN
103
+ bunx @stringsync/splates tunnel setup splates.example.com --email me@example.com
104
+ bunx @stringsync/splates server
105
+ ```
106
+
107
+ `tunnel setup` makes, through Cloudflare's API, a tunnel, the DNS record
108
+ routing the host to it, and an Access application letting in only those
109
+ emails, and saves them: the host, team and AUD tag in
110
+ `~/.splates/tunnel.json`, the tunnel's token in the system's secret store (the
111
+ Keychain on macOS). From then on a bare `splates server` runs the tunnel with
112
+ `cloudflared`, no `cloudflared login` needed. Run again, it changes only what
113
+ differs, such as the emails. The first run has no API token yet, so it
114
+ requests one on the Creds page of `splates server`, with how to make it; paste it
115
+ there, and run it again. An account with no Zero Trust organization gets one
116
+ with `--team <name>`, for `<name>.cloudflareaccess.com`. `splates tunnel status`
117
+ shows what is saved, and `splates tunnel remove` deletes it all again: the tunnels
118
+ of the machine and of every slot, their DNS records, the Access applications
119
+ and their policy, and the saved files and tunnel tokens. The API token stays.
120
+
121
+ To run several at once, as when trying changes to splates from a few worktrees,
122
+ give each its own slot:
123
+
124
+ ```bash
125
+ bunx @stringsync/splates server --slot
126
+ ```
127
+
128
+ `--slot` claims a short animal word for the checkout it runs in, as `fox`, and
129
+ serves it at `https://fox.<zone>`, on the zone `tunnel setup` used, behind the
130
+ same Access policy, so a phone opens each by name. A task's worktree keeps its
131
+ word until the task merges or is removed, which stops its server and gives the
132
+ word back; run again, `--slot` stops the tree's last server first. It refuses
133
+ a splates repository's main tree, which every agent shares. `splates slots list`
134
+ shows who holds each word, and `splates slots prune` frees those no tree owns any
135
+ more, stopping their servers. A word's tunnel and DNS record are made on its first
136
+ claim and kept, and one wildcard Access application covers every slot. A slot
137
+ keeps its own projects, roster and tunnel in `~/.splates/slots/<word>`, apart
138
+ from yours, and starts in a throwaway project there, unless that home
139
+ registers the project it is run in (`SPLATES_HOME=~/.splates/slots/fox splates
140
+ init`). The page shows the slot's emoji beside the project's name.
141
+
142
+ Or give the tunnel and the Access application by hand, over what is saved:
143
+
144
+ ```bash
145
+ bunx @stringsync/splates server --tunnel splates.example.com \
146
+ --access-team my-team --access-aud <the application's AUD tag>
147
+ ```
148
+
149
+ `--tunnel` runs `cloudflared tunnel run` against the page for as long as the
150
+ server lives (`--tunnel-name` picks the named tunnel, else cloudflared's own
151
+ config does), and is refused without both Access flags. Requests to that host
152
+ must carry a verified Access token; with one, conversations and terminals
153
+ work too.
154
+
155
+ To use another tunnel, give `splates.config.ts` a `tunnel`, any object that
156
+ implements `TunnelProvider`:
157
+
158
+ ```ts
159
+ import type { ConfigFile } from "@stringsync/splates/config";
160
+ import type { TunnelProvider } from "@stringsync/splates/tunnel-provider";
161
+
162
+ const myTunnel: TunnelProvider = {
163
+ // throw, saying what to do, unless the tunnel can run
164
+ async ready() {},
165
+ // carry route.host to route.url until the returned resource is disposed
166
+ run: ({ host, url, log }) => {
167
+ const child = Bun.spawn(["my-tunnel", "--host", host, "--to", url]);
168
+ return { disposeAsync: async () => void child.kill() };
169
+ },
170
+ };
171
+
172
+ export default { tunnel: myTunnel } satisfies ConfigFile;
173
+ ```
174
+
175
+ `--tunnel <host>` then runs yours instead of cloudflared. Access is still
176
+ required, since splates verifies Cloudflare Access tokens whatever carries the
177
+ traffic, and `--tunnel-name` is refused beside a provider of your own. One
178
+ that is not ready, or fails to start, is logged and skipped: the page keeps
179
+ serving on this machine.
180
+
181
+ ### Task ports
182
+
183
+ Any port a task's processes listen on, in its worktree or under its agent, is
184
+ found and forwarded as VS Code forwards ports, through the worker that runs
185
+ it. Save a wildcard base host for this machine to open each one anywhere,
186
+ behind the same Access application:
187
+
188
+ ```bash
189
+ bunx @stringsync/splates server --ports-host ports.example.com
190
+ ```
191
+
192
+ `https://<task>-<port>.ports.example.com` then reaches that task's server,
193
+ seen there as `localhost:<port>`, and only with a verified Access token;
194
+ routing `*.ports.example.com` to `splates server`, its DNS and TLS, are yours.
195
+ `--ports-host none` forgets the base. `splates ports [task]` lists each port
196
+ with its remote and local links, as the task's dialog on the page does.
197
+
198
+ ### Stats
199
+
200
+ As each task merges or is removed, splates writes down how it went in
201
+ `~/.splates/outcomes.jsonl`: how many times it came to you with questions or for
202
+ a review, how many merges failed and why, how many of the agent's picks you
203
+ overturned or took with words added, how long it waited on you, the systems it touched, why it was
204
+ removed, and the agent and model behind it. The Stats page sums it over the
205
+ last 6 hours, day, 3 days or 7 days, cut into bars of half an hour to six
206
+ hours, or over all time a bar a week, by model, project and system, so you can tell whether tasks are landing
207
+ with fewer questions as your prompts and setup change. Its Act on cards
208
+ compare each figure with the as long a span before, saying whether it got
209
+ better or worse, and say what to change when one stands out. Fixed rules over
210
+ the counts make those calls, not a model, so the same outcomes always read
211
+ the same.
212
+
213
+ ### Optional OpenCode ACP agent
214
+
215
+ Open **Settings → Setup** and visit the optional OpenCode steps. Check this
216
+ machine, install if needed, and connect an existing Go/Go Plus account through
217
+ the native masked key prompt. The supported CLI exposes the V2 standalone login,
218
+ auth listing and metadata APIs; unsupported installations show update guidance.
219
+ No key belongs in chat, and splates never reads or exports native credential files.
220
+
221
+ Refresh the Go catalog, choose exact main and auxiliary models, review the
222
+ configuration, and confirm **Use balance** is off in Console before applying.
223
+ The save updates the running server's roster immediately. Claude/Codex entries,
224
+ custom fields, model descriptions and defaults are preserved.
225
+
226
+ The final **Check configuration** step rereads the saved recipe, including after
227
+ a server restart, and checks it for the current project's main directory. It
228
+ checks installation, stored authentication, ACP initialize, both model IDs and
229
+ effective agent configuration. It sends no prompt. Project OpenCode configuration
230
+ is disabled only for this launch recipe; global files remain untouched. New
231
+ custom or managed configuration that conflicts with the recipe requires repair.
232
+
233
+ **New OpenCode conversation** repeats that check, explicitly selects the saved
234
+ main model and opens an empty conversation in the displayed directory. Its first
235
+ message is your decision. Configuration readiness does not establish successful
236
+ inference, entitlement, quota or billing. Console confirmation is your assertion;
237
+ splates cannot verify or change that setting. Revisit/repair returns to the existing
238
+ steps. Disable removes only the wizard-owned roster entry for new conversations;
239
+ existing conversations and native authentication remain.
240
+
241
+ #### Advanced manual reference
242
+
243
+ Manual roster editing remains available for custom launches; use the supported
244
+ V2 recipe and inspection commands in [the setup contracts](design/opencode-acp.md#374-readiness-and-completion).
245
+ A plain `opencode acp` entry or custom command does not establish wizard readiness.
246
+ Custom commands are never replaced or removed automatically. Avoid the older V1
247
+ `small_model`/provider recipe: it does not pin every V2 auxiliary/custom agent.
248
+
249
+ Splates requires the full, case-sensitive model ID returned in ACP settings for
250
+ OpenCode. Missing choices or a different confirmed provider/model stop the turn;
251
+ it never substitutes another provider. Refresh your editable catalog from
252
+ OpenCode's offered models when it changes. In Choosing agents and models,
253
+ choose OpenCode from New, then select the model in the composer (Settings on a
254
+ phone). Go choices show “OpenCode Go” and their full `opencode-go/...` ID; models
255
+ with the same display name remain distinct across providers. ACP config updates
256
+ replace the offered options. A removed selection is shown as unavailable until
257
+ you explicitly choose a replacement; it is never silently changed to the current
258
+ agent default. OpenCode choices do not change Claude/Codex saved settings.
259
+ Usage provenance and comparisons are tracked in #354.
260
+
261
+ OpenCode owns its native login and tools. Splates neither imports its credentials
262
+ nor exports OS-store secrets to it. Recovery uses advertised resume support, or
263
+ advertised load with history replay suppressed. An imported CLI session still
264
+ replays once. OpenCode recovery uses its original absolute directory; if that
265
+ directory is gone, restore it or explicitly start a new conversation. Unsupported
266
+ recovery reports an error. Without steering support, messages queue for the next
267
+ turn. Client file and terminal capabilities are not advertised.
268
+
269
+ Compatibility as of 2026-10-17: this integration is verified with synthetic ACP
270
+ fixtures, not a recorded OpenCode executable. `opencode` was absent from the
271
+ implementation environment's PATH, so no executable version or live login was
272
+ verified and no minimum supported version is claimed. Production acceptance
273
+ requires recording a version and sanitized handshake separately; no paid smoke
274
+ test was run. See [the experiment plan](design/opencode-acp.md) for opt-in setup,
275
+ provider restrictions, and the separate paid-overflow control. Fixture checks
276
+ never establish account entitlement or remaining quota.
277
+
278
+ ### OpenCode experiment usage
279
+
280
+ OpenCode's Usage popover shows the last dispatched requested/confirmed full model
281
+ IDs, measured first-text and elapsed latency, and parent-only per-turn ACP token
282
+ counts. Missing fields read **Unknown**, including optional cache counts. Reported
283
+ USD cost is a cumulative session observation, separate from unknown additional
284
+ charges; it is not a subscription invoice. Quota and mixed-model remaining
285
+ allowance stay unknown: consult OpenCode Console and keep **Use balance** disabled.
286
+ Legacy task cost/token aggregates exclude these differently scoped observations.
287
+
288
+ See [the manual comparison recipe](design/opencode-acp.md#354-implementation-and-manual-comparison-record)
289
+ for identical baseline files, a results worksheet, provenance and the authorization
290
+ gate. Fixtures need no production login. Live executable compatibility remains
291
+ unverified and no real comparison was run. `splates dev fix --code-only` disables
292
+ both Scry model roles and avoids opening credential stores; model-dependent rules
293
+ are reported as unchecked. `splates dev test` runs the local fixture suites.
294
+
295
+ ### OpenCode setup wizard (optional)
296
+
297
+ Open Settings → Set up splates → OpenCode to check installation, explicitly install
298
+ the official CLI with npm, and connect an existing Go/Go Plus account through
299
+ OpenCode's masked native key prompt. Return and choose Check again to inspect
300
+ native credential metadata and negotiate ACP without sending a model prompt.
301
+ Older native login interfaces show recovery instructions. No keys belong in an
302
+ agent conversation. The native controls currently require macOS or Linux.
303
+
304
+ Installation/connection is the first part of this wizard. Model selection and
305
+ configuration (#373), then final conversation readiness (#374), remain separate
306
+ lane steps. Until those land, this step does not add an agent roster entry. A
307
+ stored credential and ACP handshake do not verify subscription access, billing,
308
+ model availability or inference. See the [setup contract](design/opencode-acp.md#372-setup-wizard-contract-lane-53-step-1)
309
+ for exact files and supported interfaces.
@@ -0,0 +1,82 @@
1
+ import type * as acp from "@agentclientprotocol/sdk";
2
+ import type { ConversationState } from "./conversation-state.js";
3
+ /**
4
+ * What every session is opened with. Claude's AskUserQuestion is off, so its
5
+ * questions go under `## Blocked` and into its reply, as the splates skill asks,
6
+ * rather than into a form `SPLATES.md` never sees. Other agents ignore it.
7
+ */
8
+ export declare const SESSION_META: {
9
+ claudeCode: {
10
+ options: {
11
+ disallowedTools: string[];
12
+ };
13
+ };
14
+ };
15
+ /** The ACP extension that says a prompt into the turn the agent is running. */
16
+ export declare const STEER = "_session/steering";
17
+ /**
18
+ * The updates and request, an ACP draft Claude's adapter speaks ahead of the
19
+ * SDK, that say when a background command starts and ends, and stop one.
20
+ */
21
+ export declare const ASYNC_TASK_SPAWNED = "async_task_spawned";
22
+ export declare const ASYNC_TASK_STATE = "async_task_state_update";
23
+ export declare const ASYNC_TASK_PROGRESS = "async_task_progress";
24
+ export declare const ASYNC_TASK_STOP = "_session/async_task/stop";
25
+ /** The notification the client hears each of those updates as (see `asyncTasks`). */
26
+ export declare const ASYNC_TASK_UPDATE = "_splates/async_task";
27
+ /**
28
+ * The messages from an agent, each update on a background command moved to
29
+ * `ASYNC_TASK_UPDATE`, ahead of the SDK's checks, which would refuse it as a
30
+ * session update they do not know.
31
+ */
32
+ export declare function asyncTasks(messages: ReadableStream<acp.AnyMessage>): ReadableStream<acp.AnyMessage>;
33
+ export interface AsyncTaskSpawned {
34
+ sessionUpdate: typeof ASYNC_TASK_SPAWNED;
35
+ asyncTaskId: string;
36
+ name: string;
37
+ /** `shell` for a command, else a workflow or the like. */
38
+ taskType: string;
39
+ description: string;
40
+ toolCallId?: string;
41
+ outputFilePath?: string;
42
+ }
43
+ export interface AsyncTaskState {
44
+ sessionUpdate: typeof ASYNC_TASK_STATE;
45
+ asyncTaskId: string;
46
+ state: "running" | "paused" | "completed" | "failed" | "stopped";
47
+ outputFilePath?: string;
48
+ }
49
+ /** What the agent says as a command goes on: here, only where its output is. */
50
+ export interface AsyncTaskProgress {
51
+ sessionUpdate: typeof ASYNC_TASK_PROGRESS;
52
+ asyncTaskId: string;
53
+ outputFilePath?: string;
54
+ }
55
+ export type AsyncTaskUpdate = AsyncTaskSpawned | AsyncTaskState | AsyncTaskProgress;
56
+ /** Whether an agent says, as it starts, that it takes `STEER`. */
57
+ export declare function steers(initialized: acp.InitializeResponse): boolean;
58
+ /**
59
+ * The AIR session list: subscribed for a working directory, the agent says
60
+ * as each of its sessions there starts or ends a turn. Codex says so of a
61
+ * turn it starts on its own nowhere else.
62
+ */
63
+ export declare const SESSION_LIST_SUBSCRIBE = "_session/list/subscribe";
64
+ export declare const SESSION_LIST_CHANGES = "_session/list/changes";
65
+ /** Whether an agent says, as it starts, that it takes `SESSION_LIST_SUBSCRIBE`. */
66
+ export declare function listsSessions(initialized: acp.InitializeResponse): boolean;
67
+ /** What a `SESSION_LIST_CHANGES` row says of a session's turn. */
68
+ export interface ListedTurn {
69
+ running: boolean;
70
+ /** When its last turn ended, in ms; null before one has. */
71
+ endedAt: number | null;
72
+ }
73
+ /**
74
+ * What `params` of `SESSION_LIST_CHANGES` say of `session`'s turn; null when
75
+ * they hold no row for it, or one that says nothing of its turns, as for a
76
+ * session no process has open.
77
+ */
78
+ export declare function listedTurn(params: Record<string, unknown>, session: string): ListedTurn | null;
79
+ /** Whether `update` is the agent's final answer, after which its turn ends. */
80
+ export declare function finalAnswer(update: acp.SessionUpdate): boolean;
81
+ /** Folds an update on a background command into those the agent left. */
82
+ export declare function foldBackground(state: ConversationState, update: AsyncTaskUpdate): void;
package/add.d.ts ADDED
@@ -0,0 +1,53 @@
1
+ import { type Logger } from "webappwiz/log";
2
+ import type { Fs, HeavyCap } from "webappwiz/system";
3
+ import type { Config } from "./config.js";
4
+ import { type RosterAgent } from "./roster.js";
5
+ import type { Shell } from "./shell.js";
6
+ import type { Todos } from "./todos.js";
7
+ import type { Dispatched } from "./worktree.js";
8
+ import type { WorktreeService } from "./worktree-service.js";
9
+ export interface AddOptions {
10
+ /** Branch the task starts from and merges onto. Defaults to the trunk. */
11
+ base?: string;
12
+ /**
13
+ * The todos this task takes up: their words seed the plan's Goal, and
14
+ * nobody else can take them while the task lives.
15
+ */
16
+ todos?: number[];
17
+ /**
18
+ * One or two lines on what done means, written under `## Goal` ahead of
19
+ * whatever the todos seed it with.
20
+ */
21
+ goal?: string;
22
+ /**
23
+ * Runs once the worktree exists and before its record makes the task live,
24
+ * so whatever it tells of the task is in place before anyone can see it.
25
+ */
26
+ onTree?: () => Promise<void>;
27
+ /**
28
+ * The agent to hand the task to, which `splates server` starts in its tree once
29
+ * the tree is ready; its id must be one of `agents`.
30
+ */
31
+ dispatch?: Dispatched;
32
+ /** The roster's agents, which `dispatch` must name one of, and its model. */
33
+ agents?: RosterAgent[];
34
+ /**
35
+ * Added from another project, as `splates --project` does: the task goes
36
+ * to an agent of its own, so `dispatch` is a must.
37
+ */
38
+ away?: boolean;
39
+ /**
40
+ * Hands the task out though the agent adding it has as many out to
41
+ * agents as `config.agentsPerAgent` allows, as only the person may ask.
42
+ */
43
+ overCapacity?: boolean;
44
+ }
45
+ export declare function add({ service, shell, config, log, fs, todos, heavy, }: {
46
+ service: WorktreeService;
47
+ shell: Shell;
48
+ config: Config;
49
+ log: Logger;
50
+ fs: Fs;
51
+ todos: Todos;
52
+ heavy: HeavyCap;
53
+ }, task: string, { base, todos: ids, goal: asked, onTree, dispatch, agents, away, overCapacity, }?: AddOptions): Promise<void>;
package/adherence.d.ts ADDED
@@ -0,0 +1,49 @@
1
+ import type { Fs } from "webappwiz/system";
2
+ import type { ConversationState } from "./conversation-state.js";
3
+ import type { Outcomes } from "./outcomes.js";
4
+ import { type Transcript } from "./skill-checks.js";
5
+ /**
6
+ * How closely `splates server`'s conversations follow the splates skill, across
7
+ * projects, in `~/.splates/adherence.jsonl`: a line each time a turn ends and
8
+ * changes how a conversation's checks went, the last one standing for it. It
9
+ * outlives the conversations' logs, which go as archived ones are pruned.
10
+ */
11
+ export declare class Adherence {
12
+ /** `~/.splates/adherence.jsonl`, or a test's own. */
13
+ readonly path: string;
14
+ private readonly fs;
15
+ /** Where the commits a conversation merged are kept; null to judge without them. */
16
+ private readonly outcomes;
17
+ /** The scores last written for each conversation, so a turn that changes none writes nothing. */
18
+ private readonly written;
19
+ constructor(
20
+ /** `~/.splates/adherence.jsonl`, or a test's own. */
21
+ path: string, fs: Fs,
22
+ /** Where the commits a conversation merged are kept; null to judge without them. */
23
+ outcomes: Outcomes | null);
24
+ /** Judges `state` as it stands, and keeps the result when it changed. */
25
+ judge(state: ConversationState, project: string, at: string): Promise<void>;
26
+ /** Each conversation's checks as last judged. */
27
+ list(): Promise<Judged[]>;
28
+ /** What `state` merged, read from the outcomes only once it has merged anything. */
29
+ private endOf;
30
+ }
31
+ /** How each check went over one conversation: the places it applied, passed and failed. */
32
+ export type Scores = Record<string, {
33
+ passed: number;
34
+ failed: number;
35
+ }>;
36
+ /** A conversation's checks as last judged, a line of `~/.splates/adherence.jsonl`. */
37
+ export interface Judged {
38
+ conversation: string;
39
+ /** The main tree of the repository it ran in. */
40
+ project: string;
41
+ agent: string;
42
+ /** The splates skill version it started on; null when unknown. */
43
+ skill: string | null;
44
+ /** When it was judged, as an ISO timestamp. */
45
+ at: string;
46
+ scores: Scores;
47
+ }
48
+ /** A conversation as `skill-checks` reads it: what it said and ran, in order. */
49
+ export declare function condense(state: ConversationState): Transcript;
package/age.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ /** How long ago an ISO timestamp was, at the coarsest unit that still reads. */
2
+ export declare function age(since: string): string;