@unotest/web 0.42.0 → 0.43.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 +423 -3
- package/README.md +37 -5
- package/dist/config/schema.d.ts +98 -63
- package/dist/config/schema.js +1 -1
- package/dist/driver/index.js +1 -1
- package/dist/dsl/index.d.ts +66 -2
- package/dist/dsl/index.js +1 -1
- package/dist/dsl/web-dsl-language-service.d.ts +1 -1
- package/dist/dsl/web-dsl-language-service.js +1 -1
- package/dist/{linter-Yf4gPe5J.d.ts → linter-BWSAYBQy.d.ts} +24 -0
- package/dist/mcp/server.js +1 -1
- package/dist/runner/cli.d.ts +1 -1
- package/dist/runner/cli.js +1 -1
- package/dist/runner/init/chrome-probe.js +1 -1
- package/dist/runner/init.js +1 -1
- package/dist/runner/install-chromium.js +1 -1
- package/dist/runner/prepare-fix.js +1 -1
- package/dist/runner/scaffold-workspace.js +1 -1
- package/dist/runner/serve-fixture.js +1 -1
- package/dist/runner/web-runner-adapter.js +1 -1
- package/guides/agent-integration.md +105 -6
- package/guides/dsl-reference.md +19 -0
- package/package.json +8 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,425 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.43.0] - 2026-09-27
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 6fc8140: A run on an environment no longer inherits a host from the base `.env`
|
|
8
|
+
|
|
9
|
+
Environment files layer: `unotest/.env` first, then the `unotest/.env.<name>`
|
|
10
|
+
overlay an active environment selects, the later file winning. That is the
|
|
11
|
+
right rule for a password or a user name, and the wrong one for a host. A
|
|
12
|
+
project whose base file points `API_BASE_CHAT` at production, and whose
|
|
13
|
+
`dev` overlay does not mention it, ran `--env dev` and sent its API calls to
|
|
14
|
+
production. Nothing in the output said so: the reply reported the `dev`
|
|
15
|
+
environment, and the requests went elsewhere.
|
|
16
|
+
|
|
17
|
+
From this release a variable that becomes a HOST has to come from the active
|
|
18
|
+
environment's overlay, or from the process environment. Three variables count
|
|
19
|
+
as hosts: `APP_BASE_URL`, which every relative `goto` resolves against; the
|
|
20
|
+
name passed as `apiCall(..., {base: "NAME"})`; and a variable used as the URL
|
|
21
|
+
in `goto(NAME)`. When the value still comes from the base file, the run is
|
|
22
|
+
refused before the browser starts, and `lint`, `run_test` and `explore_start`
|
|
23
|
+
refuse identically — with a message that names the variable, both files, and
|
|
24
|
+
the two ways forward. Every other variable layers exactly as before.
|
|
25
|
+
|
|
26
|
+
A base value that really is meant for every environment is declared once, in
|
|
27
|
+
`unotest.config.{js,mjs,ts}`:
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
export default { env: { shared: ["API_BASE_CHAT"] } };
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
A value the process environment supplies — a shell variable, or the values a
|
|
34
|
+
box injects into a run — is never affected by this rule: it is a value
|
|
35
|
+
somebody stated for this run, not one silently inherited from a file the
|
|
36
|
+
overlay was supposed to replace.
|
|
37
|
+
|
|
38
|
+
Migration: a suite that runs with `--env <name>` and keeps its host in the
|
|
39
|
+
base file will now refuse, naming the variable. Either add the variable to
|
|
40
|
+
the overlay, which is what most projects meant, or list its name under
|
|
41
|
+
`env: { shared: [...] }`. A suite that never uses `--env` is unaffected.
|
|
42
|
+
|
|
43
|
+
- 703185f: A run held at its failure has a screenshot, and the tools answer while it stands
|
|
44
|
+
|
|
45
|
+
A run paused on a failure left nothing on disk: no failure bundle, no
|
|
46
|
+
screenshot, and `get_failure_screenshot` replied "failure bundle not
|
|
47
|
+
found". The frame only appeared once the run was over — which is the one
|
|
48
|
+
moment it is least useful, because the browser has closed and the page
|
|
49
|
+
that broke is gone. Meanwhile the run was sitting right there, still
|
|
50
|
+
showing it.
|
|
51
|
+
|
|
52
|
+
The bundle is now written at the pause, from that page, so
|
|
53
|
+
`list_failures`, `get_failure_screenshot`, `get_failure_a11y` and the
|
|
54
|
+
rest answer on the first call. Each record carries `runState`: `paused`
|
|
55
|
+
while the run is still held — the browser shows the failure, and a
|
|
56
|
+
resumed run that fails again elsewhere re-takes the frame at the new
|
|
57
|
+
failure — then `finished` or `aborted` once the run is over. The frame
|
|
58
|
+
of a finished run stays the one taken at the failure: resuming continues
|
|
59
|
+
with the next statement, so the page moves on while the verdict does
|
|
60
|
+
not, and re-taking it at the end would show a page the failure never
|
|
61
|
+
happened on.
|
|
62
|
+
|
|
63
|
+
Also fixed: aborting a run while it was paused left its process alive
|
|
64
|
+
until the 30-minute inspection TTL expired. The run now ends when it is
|
|
65
|
+
aborted.
|
|
66
|
+
|
|
67
|
+
- 7006895: Spending and deleting wait for a person
|
|
68
|
+
|
|
69
|
+
A key of a customer account — an agent's key, a signed-in machine's, and a CI
|
|
70
|
+
key issued before this release — can no longer rent a box, delete one or
|
|
71
|
+
switch the Picker on by itself. The cloud records what was asked for and answers with a
|
|
72
|
+
request instead; somebody approves or declines it in the cabinet, on the
|
|
73
|
+
Approvals tab, and only a signed-in browser can — not the key that asked, and
|
|
74
|
+
not the link in the email about it. Nothing is created and nothing is charged
|
|
75
|
+
until that happens.
|
|
76
|
+
|
|
77
|
+
For the CLI this is a wait, not a failure. `box create`, `box destroy` and
|
|
78
|
+
`box picker on` print where to answer and how long the request lives, then
|
|
79
|
+
carry on with what the cloud finally did and print exactly what they would
|
|
80
|
+
have printed anyway. Ten minutes with no answer is exit code 75 — nothing was
|
|
81
|
+
created and nothing was charged, run the command again. A decline is exit code 2. `--yes` does not skip it: the flag says the terminal need not ask, not that
|
|
82
|
+
the account need not.
|
|
83
|
+
|
|
84
|
+
Only the CI key of an operator account, one that unotest runs itself, acts
|
|
85
|
+
with nobody watching. A pipeline that rents or deletes boxes with a
|
|
86
|
+
customer's key now waits for somebody to press Approve, or has to rent its
|
|
87
|
+
box ahead of time.
|
|
88
|
+
|
|
89
|
+
- 4582091: `box create` with a short balance points to the dashboard instead of starting a top-up
|
|
90
|
+
|
|
91
|
+
The cloud's API for agents has no top-up: credit is added by a person, by
|
|
92
|
+
card, under "Add credit" on the dashboard's home page. `box create` used to
|
|
93
|
+
start a payment from the terminal when the balance did not cover the first
|
|
94
|
+
hour, against an endpoint the service does not have, so on the real cloud it
|
|
95
|
+
failed with a 404.
|
|
96
|
+
|
|
97
|
+
Now it stops before renting anything and says what is short: the first hour's
|
|
98
|
+
price, the balance and the amount missing, and the dashboard page to add it
|
|
99
|
+
on. It exits 75 — nothing created, nothing charged — and is run again once the
|
|
100
|
+
credit is there. A key that cannot read the balance (the one `login` issues
|
|
101
|
+
cannot) gets the price and the page, and no invented shortfall. `--json`
|
|
102
|
+
prints `{ "needs": "top-up", "firstHourCents", "balanceCents",
|
|
103
|
+
"missingCents", "currency", "url" }`, with `null` for a figure the key may not
|
|
104
|
+
read.
|
|
105
|
+
|
|
106
|
+
The `cloud_box_create` MCP tool answers the same case as
|
|
107
|
+
`{ state: "top-up-needed" }` with the same figures, instead of an error.
|
|
108
|
+
|
|
109
|
+
Migration: the `--topup <eur>` flag of `box create` is removed — top-up moved
|
|
110
|
+
to the dashboard. A script that passes it is refused with the usage and exit
|
|
111
|
+
2; drop the flag, and add credit on the dashboard's home page before renting.
|
|
112
|
+
|
|
113
|
+
- dae29a2: The cloud account as commands and as MCP tools: `box list`, `box logs`, `approvals`, and nine `cloud_*` tools
|
|
114
|
+
|
|
115
|
+
An agent could read a box it had been pointed at and nothing more: which
|
|
116
|
+
boxes the account had, renting one and deleting one lived in a terminal, and
|
|
117
|
+
the list of boxes appeared only inside an error message. The account is now
|
|
118
|
+
reachable from both sides through one implementation, so a box reads the same
|
|
119
|
+
whether a person or an agent asked.
|
|
120
|
+
|
|
121
|
+
New commands:
|
|
122
|
+
|
|
123
|
+
- `box list` — every box on the account with its state, version and projects.
|
|
124
|
+
`--json` prints the same objects the `cloud_boxes_list` tool returns.
|
|
125
|
+
- `box logs [name] [--bootstrap|--updater]` — the end of a box's own log,
|
|
126
|
+
without logging in to it. A box sends it on its next five-minute check, so
|
|
127
|
+
the first call says it has been asked and when to run again, with exit 0.
|
|
128
|
+
- `approvals [id]` — what is waiting for a person on this account, or what
|
|
129
|
+
became of one request. Answering stays in the cabinet: a terminal holding
|
|
130
|
+
the key cannot approve its own request.
|
|
131
|
+
|
|
132
|
+
New MCP tools, outside the `core` toolset: `cloud_boxes_list`,
|
|
133
|
+
`cloud_box_status`, `cloud_box_create`, `cloud_box_destroy`,
|
|
134
|
+
`cloud_box_access`, `cloud_box_update`, `cloud_box_logs`, `cloud_balance`,
|
|
135
|
+
`cloud_approval_wait`. `cloud_box_access` writes the fresh pair into the
|
|
136
|
+
project the way `box access` does and answers with the names it wrote, never
|
|
137
|
+
the values. They answer states an agent can act on instead of
|
|
138
|
+
errors: `not-logged-in` with the command that fixes it, `approval-pending`
|
|
139
|
+
with the request to wait on, `insufficient-scope`, and `declined`,
|
|
140
|
+
`expired` or `still-waiting` from the wait.
|
|
141
|
+
|
|
142
|
+
While a rent or a delete waits for approval, the terminal now names it in the
|
|
143
|
+
words the cabinet and the email use — `Rent a box "acme", size s (€0.09 per
|
|
144
|
+
hour)` — and prints a line when it is approved, not only when it is declined.
|
|
145
|
+
The sentences live in `@unotest/protocol` (`approvalActionText`,
|
|
146
|
+
`approvalPrice`), shared by every surface that shows a request.
|
|
147
|
+
|
|
148
|
+
`unotest-web --help` and `box --help` list the new commands, say which
|
|
149
|
+
commands wait for approval, and give one table of exit codes. `box <command>
|
|
150
|
+
--help` now prints that command's own flags wherever `--help` sits on the
|
|
151
|
+
line, and `approvals` refuses an option it does not know with exit 2.
|
|
152
|
+
|
|
153
|
+
Compatibility: every call to the cloud now carries an `X-Unotest-Client`
|
|
154
|
+
header. The cloud refuses a call that would wait for approval when the header
|
|
155
|
+
is missing — an older CLI could not read the request it would be handed — with
|
|
156
|
+
`426 client-too-old` and a message to update. Update `@unotest/web` before
|
|
157
|
+
renting or deleting boxes with any key of a customer account, a CI key
|
|
158
|
+
included.
|
|
159
|
+
|
|
160
|
+
- ba32d73: A config key the package does not know is refused, with the key you probably meant
|
|
161
|
+
|
|
162
|
+
`chanel: "chrome"` used to be dropped in silence: the run went ahead on
|
|
163
|
+
the default channel, printed `1 passed` and exited 0, so the one setting
|
|
164
|
+
you changed was the one setting that did not apply. Every section of
|
|
165
|
+
`unotest/unotest.config.*` is now checked by name, before the browser
|
|
166
|
+
starts:
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
e2e: config validation failed:
|
|
170
|
+
unknown key "chanel" — did you mean "channel"?
|
|
171
|
+
retry: unknown key "cuont" — did you mean "count"?
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
A key nothing is close to gets the other answer, because it usually
|
|
175
|
+
comes from a config written for a newer release than the installed
|
|
176
|
+
package:
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
unknown key "telemetryEndpoint" — not a config key; check your
|
|
180
|
+
@unotest/web version if it comes from a newer release
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
The config `unotest-web init` writes no longer offers a `viewer` section:
|
|
184
|
+
the viewer's port has always come from `UNOTEST_VIEWER_PORT`, and the
|
|
185
|
+
section was a suggestion the schema never had. A config generated by an
|
|
186
|
+
earlier version needs that block removed.
|
|
187
|
+
|
|
188
|
+
**Migration.** A config that carries a key this version does not know
|
|
189
|
+
now fails instead of running: fix the spelling, remove the key, or
|
|
190
|
+
install the release the key belongs to. Nothing else about the config
|
|
191
|
+
changes — every known field keeps its name, its default and its merge
|
|
192
|
+
behaviour, and a partially written section is still merged with the
|
|
193
|
+
defaults key by key.
|
|
194
|
+
|
|
195
|
+
- fb12602: A step that takes minutes no longer disappears into the client's background
|
|
196
|
+
|
|
197
|
+
Waiting on a slow page — a chat answer that takes four minutes to come
|
|
198
|
+
back, a report that builds itself — used to be a call your agent never got
|
|
199
|
+
an answer to: MCP clients stop waiting at about two minutes and carry on,
|
|
200
|
+
and whatever the step replied after that was lost. The way round it was to
|
|
201
|
+
cut every wait to under a hundred seconds and repeat the step.
|
|
202
|
+
|
|
203
|
+
Now a step still running after 90 seconds answers `{ status: "accepted",
|
|
204
|
+
taskId }` and keeps going: same timeout, same recording into the session.
|
|
205
|
+
Collect it with the new `explore_step_result({ taskId })` — `running`,
|
|
206
|
+
`done` with the reply the step would have given, or `failed`. A batch of
|
|
207
|
+
steps goes to the background as one task, for one id. The `durationMs` of
|
|
208
|
+
a collected step is the whole step, from its start — there is no threshold
|
|
209
|
+
to add back by hand.
|
|
210
|
+
|
|
211
|
+
While a step is out there, the browser context says so instead of being
|
|
212
|
+
damaged: another step, `explore_stop` and `close_context` answer `busy`
|
|
213
|
+
and name the task to wait for, and `explore_state` lists what is pending.
|
|
214
|
+
|
|
215
|
+
- 82d5920: Signing in to a box with one environment opens its viewer straight away
|
|
216
|
+
|
|
217
|
+
A box configured with a single environment used to answer a sign-in with a
|
|
218
|
+
screen holding one option and a button. There was nothing to choose, so the
|
|
219
|
+
screen only stood between you and the viewer behind it.
|
|
220
|
+
|
|
221
|
+
Now that sign-in lands in the viewer. The **Environments** screen still
|
|
222
|
+
appears whenever it has something to say — more than one environment to pick
|
|
223
|
+
from, a box that has not been sent a test suite yet, or a licence in its
|
|
224
|
+
grace period, whose warning lives there.
|
|
225
|
+
|
|
226
|
+
So that skipping the screen does not hide what it carried, the viewer's
|
|
227
|
+
session menu now offers **box pages**: one link back to the box's own
|
|
228
|
+
pages — environments, read tokens, and administration for those who have
|
|
229
|
+
it. It appears when the box tells the viewer where they are, so a viewer
|
|
230
|
+
and a box on different versions simply do without it.
|
|
231
|
+
|
|
232
|
+
- 3aee366: take_wheel now points the session at the run it takes the wheel of
|
|
233
|
+
|
|
234
|
+
An agent debugging a paused run would call `take_wheel`, take a
|
|
235
|
+
screenshot, and get a picture of a completely different stand — whatever
|
|
236
|
+
page the session had been recording on before. Nothing was wrong with the
|
|
237
|
+
screenshot: the wheel only ever took the control token, while the browser
|
|
238
|
+
stayed where it was, and the reply said `ok` either way.
|
|
239
|
+
|
|
240
|
+
`take_wheel` now also attaches this session to the run's browser, so the
|
|
241
|
+
next screenshot, snapshot or step reads the page being debugged. Every
|
|
242
|
+
`explore_step` reply carries `context` — `{kind: "run", runtimeId}` or
|
|
243
|
+
`{kind: "own"}` — so the source of a frame is stated rather than guessed.
|
|
244
|
+
A session has exactly one browser: `take_wheel` and
|
|
245
|
+
`attach_debug_session` point it at a run, `explore_start` and
|
|
246
|
+
`new_context` at your own, and `explorationId` chooses recording and
|
|
247
|
+
where a file is filed, never which browser a step runs on.
|
|
248
|
+
`release_wheel` hands the token back and deliberately leaves the browser
|
|
249
|
+
on the run.
|
|
250
|
+
|
|
251
|
+
Behaviour change for anything parsing the reply: `take_wheel` no longer
|
|
252
|
+
always answers `ok`. When it cannot join the browser it answers
|
|
253
|
+
`recording_open` (a live recording would be dropped — nothing is changed,
|
|
254
|
+
not even the wheel), `no_browser` (the run has not published one yet) or
|
|
255
|
+
`no_page`, each naming what to do next.
|
|
256
|
+
|
|
257
|
+
- 9034092: Opening a box's viewer while a suite is being installed no longer looks like a failure
|
|
258
|
+
|
|
259
|
+
Sending a new suite to a box replaces that environment's viewer, and for the
|
|
260
|
+
second or two that takes, anyone holding the page open got a bare `502` — the
|
|
261
|
+
status that tells a browser nothing and tells a script the box is broken.
|
|
262
|
+
|
|
263
|
+
The box now says, while it is happening, that it is replacing that viewer. A
|
|
264
|
+
browser gets a short page that comes back by itself once the new viewer
|
|
265
|
+
answers; an agent reading the box with a token gets `503` with `Retry-After`
|
|
266
|
+
and `unavailable`, which is the box's existing word for "wait and ask again".
|
|
267
|
+
|
|
268
|
+
A viewer that stopped for any other reason answers the same way but promises
|
|
269
|
+
nothing, and the page does not reload itself: nobody is bringing it back, and
|
|
270
|
+
saying otherwise would be worse than saying nothing.
|
|
271
|
+
|
|
272
|
+
### Patch Changes
|
|
273
|
+
|
|
274
|
+
- b41b02e: A cloud that will not show the price list is quoted as such, not as free
|
|
275
|
+
|
|
276
|
+
`box create` and `box picker on` quote the hourly price inside the
|
|
277
|
+
question they ask. When the cloud refused the price list, the question was
|
|
278
|
+
asked with no price in it at all — and silence there reads as "this is
|
|
279
|
+
free", with no way for the reader to learn that a price exists and they
|
|
280
|
+
were not shown it.
|
|
281
|
+
|
|
282
|
+
A refusal for want of a permission now says so: `price not visible to this
|
|
283
|
+
key: it needs billing:read`. On a current cloud the list is under `read`,
|
|
284
|
+
which every key from `login` carries — so this is what an OLDER cloud says
|
|
285
|
+
to a newer client, which is exactly when a client must not go quiet about
|
|
286
|
+
money. Every other reason stays silent, because a cloud older still has no
|
|
287
|
+
price list at all and a command must not fail for want of a courtesy.
|
|
288
|
+
|
|
289
|
+
- 3e7e3fc: API keys carry permissions
|
|
290
|
+
|
|
291
|
+
A key used to be all or nothing: any live key could do everything the API
|
|
292
|
+
offers. Now every key carries a list of permissions — see the boxes, manage
|
|
293
|
+
them, read the balance — and every route states the one it needs. A key
|
|
294
|
+
without it gets `insufficient-scope`, which is a 403 and not a 401: the
|
|
295
|
+
credential is good, so signing in again would change nothing.
|
|
296
|
+
|
|
297
|
+
Where the list comes from depends on the key. In the cabinet you tick the
|
|
298
|
+
permissions when issuing a key for an agent. Signing a machine in asks for
|
|
299
|
+
enough to see your boxes and work with them, never your money, and the
|
|
300
|
+
approval page in the browser lets you narrow that before you approve — the
|
|
301
|
+
key then has exactly what was left ticked. `login` and `whoami` print what
|
|
302
|
+
the key may do.
|
|
303
|
+
|
|
304
|
+
Keys that already exist keep the permissions they had, with one exception:
|
|
305
|
+
spending money and deleting with nobody watching. Only the CI key of an
|
|
306
|
+
operator account, one that unotest runs itself, may do that. A CI key a
|
|
307
|
+
customer holds from before this release works like an agent's key: renting,
|
|
308
|
+
deleting and switching the Picker on wait for a person to approve.
|
|
309
|
+
|
|
310
|
+
One visible change for a key that cannot read the balance: `whoami` says so
|
|
311
|
+
on the balance line instead of failing, and signing in with such a key is
|
|
312
|
+
still recognised as being signed in.
|
|
313
|
+
|
|
314
|
+
- 8353232: A rental the account cannot pay is refused before anybody is asked to approve it
|
|
315
|
+
|
|
316
|
+
A key that may not spend by itself used to get an approval request for a box
|
|
317
|
+
even when the balance could not cover the first hour: a person was asked to
|
|
318
|
+
approve it, and the rental then failed at their yes. The money is now checked
|
|
319
|
+
first, and such a create is answered `402 insufficient-balance` with nothing
|
|
320
|
+
recorded and nobody written to. The check at approval stays — a balance can
|
|
321
|
+
move while a request waits — and a request that fails there says so in words:
|
|
322
|
+
the price of the hour and where to add credit.
|
|
323
|
+
|
|
324
|
+
Every `insufficient-balance` carries `topupUrl`, the dashboard page where a
|
|
325
|
+
person adds credit. A key that may read the account's money also gets
|
|
326
|
+
`firstHourCents`, `balanceCents`, `missingCents` and `currency`; any other
|
|
327
|
+
key gets no figures.
|
|
328
|
+
|
|
329
|
+
`box create` prints the cloud's own figures and link when the refusal carries
|
|
330
|
+
them, instead of counting them again.
|
|
331
|
+
|
|
332
|
+
- 2006bf9: A refused config says which field is wrong, and `channel` takes the same words everywhere
|
|
333
|
+
|
|
334
|
+
Any bad value in `unotest/unotest.config.mjs` used to come back as one
|
|
335
|
+
line — `config validation failed` — with no field, no value and no list of
|
|
336
|
+
what would have been accepted, though all three were known. Now the
|
|
337
|
+
refusal names them:
|
|
338
|
+
|
|
339
|
+
```
|
|
340
|
+
config validation failed:
|
|
341
|
+
channel: expected one of "chrome" / "msedge" / "chrome-beta" / "chromium" / "bundled" / null, got "bundeld"
|
|
342
|
+
defaultTimeoutMs: expected number, got string
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
And `channel: "bundled"` is accepted. `UNOTEST_BROWSER_CHANNEL` has always
|
|
346
|
+
taken that word — the `.env` template says the variable merely overrides
|
|
347
|
+
`channel` — while the config refused it. Both inputs now share one
|
|
348
|
+
vocabulary, and `bundled` means what it has always meant: Playwright's own
|
|
349
|
+
Chromium, the default.
|
|
350
|
+
|
|
351
|
+
- e7cdab7: An empty id is refused instead of quietly changing what a tool does
|
|
352
|
+
|
|
353
|
+
`explore_step` and `explore_steps` treated `explorationId: ""` as "run
|
|
354
|
+
this without recording": the step went through, the reply said it had
|
|
355
|
+
succeeded, and nothing reached the session log. The agent found out at
|
|
356
|
+
`explore_stop`, with an empty recording and the work already done. The
|
|
357
|
+
same held for `explore_record`, and for `runtimeId: ""` in the debugger
|
|
358
|
+
tools, where an empty value silently meant "the session you attached to".
|
|
359
|
+
|
|
360
|
+
An empty id is now an input error naming the field, exactly as the
|
|
361
|
+
neighbouring tools have always answered. Leaving the key out is still how
|
|
362
|
+
you ask for the ad-hoc mode — absence is the mode, emptiness is a typo.
|
|
363
|
+
|
|
364
|
+
- a3f89ee: The MCP server reports the version it actually is
|
|
365
|
+
|
|
366
|
+
`initialize` answered `serverInfo` with a hard-coded `0.1.0` whatever
|
|
367
|
+
version of the package was running, while `--version` printed the real
|
|
368
|
+
number. An editor showed the wrong version in its server list, and nobody
|
|
369
|
+
checking a session could tell a server started from npm apart from one
|
|
370
|
+
started out of a working copy.
|
|
371
|
+
|
|
372
|
+
Both surfaces now read the package's own manifest through one helper, so
|
|
373
|
+
they cannot drift apart again. A manifest that cannot be read is an error
|
|
374
|
+
naming the fault, not a silent `0.0.0` — an invented version reads as a
|
|
375
|
+
real answer in a protocol reply and in a support transcript.
|
|
376
|
+
|
|
377
|
+
- f1f3fb1: Existing boxes install the five-minute update check themselves, and report a finished update in the tick that applied it
|
|
378
|
+
|
|
379
|
+
A box rented before the five-minute check existed only had the hourly timer:
|
|
380
|
+
pressing **Update** in the cabinet could take up to an hour and a half to
|
|
381
|
+
reach it. The updater now installs that pair of systemd units itself when the
|
|
382
|
+
box does not have them — exactly those two, only when the file is absent, and
|
|
383
|
+
never replacing a unit an operator edited.
|
|
384
|
+
|
|
385
|
+
A finished update is also reported in the tick that applied it, instead of in
|
|
386
|
+
the next one, so the cabinet card and `box status` stop showing "updating" for
|
|
387
|
+
a box that is already done.
|
|
388
|
+
|
|
389
|
+
**Both start working with the update after this one.** The move onto this
|
|
390
|
+
version is still carried out by the updater a box runs today: it arrives on
|
|
391
|
+
the hourly timer — up to an hour and a half — and that first "applied" still
|
|
392
|
+
comes with the next hourly tick. From there on the new updater is in charge,
|
|
393
|
+
a pressed **Update** lands within minutes, and the cabinet shows it as done as
|
|
394
|
+
soon as it is.
|
|
395
|
+
|
|
396
|
+
- 74a1f9d: The viewer finishes writing its run index before it exits
|
|
397
|
+
|
|
398
|
+
On boot, and whenever a run finishes or the environment switches, the viewer
|
|
399
|
+
rebuilds the derived index it serves history from. Those writes are
|
|
400
|
+
deliberately not waited for while the server runs — a history months deep
|
|
401
|
+
must not hold the UI back — but they were not waited for on the way down
|
|
402
|
+
either. Stopping the viewer returned while files were still landing under the
|
|
403
|
+
run directory: a directory the caller believed had been released kept growing
|
|
404
|
+
for a fraction of a second, and anything that removed or archived it right
|
|
405
|
+
after the viewer stopped could fail or capture a half-written index.
|
|
406
|
+
|
|
407
|
+
Shutdown now waits for the index writes that are still in flight, so a
|
|
408
|
+
stopped viewer is a viewer that has finished writing. A rebuild that takes
|
|
409
|
+
longer makes the stop take longer, which is the honest cost of not leaving
|
|
410
|
+
half a file behind.
|
|
411
|
+
|
|
412
|
+
- Updated dependencies [6fc8140]
|
|
413
|
+
- Updated dependencies [4582091]
|
|
414
|
+
- Updated dependencies [dae29a2]
|
|
415
|
+
- Updated dependencies [82d5920]
|
|
416
|
+
- Updated dependencies [74a1f9d]
|
|
417
|
+
- @unotest/core@0.43.0
|
|
418
|
+
- @unotest/protocol@0.43.0
|
|
419
|
+
- @unotest/viewer@0.43.0
|
|
420
|
+
- @unotest/dsl@0.43.0
|
|
421
|
+
- @unotest/grounder-client@0.43.0
|
|
422
|
+
|
|
3
423
|
## [0.42.0] - 2026-09-24
|
|
4
424
|
|
|
5
425
|
### Minor Changes
|
|
@@ -818,7 +1238,7 @@ picker on` fetches this project's own access and writes the three grounder
|
|
|
818
1238
|
|
|
819
1239
|
That screen named one command:
|
|
820
1240
|
|
|
821
|
-
|
|
1241
|
+
npx @unotest/web bundle push --run --env ENVIRONMENT
|
|
822
1242
|
|
|
823
1243
|
In a project that has just been created, the command refuses with `no box
|
|
824
1244
|
address` before it does anything: the project holds neither the box's address
|
|
@@ -827,8 +1247,8 @@ address` before it does anything: the project holds neither the box's address
|
|
|
827
1247
|
|
|
828
1248
|
The screen now names both steps, in the order they have to happen:
|
|
829
1249
|
|
|
830
|
-
|
|
831
|
-
|
|
1250
|
+
npx @unotest/web box access larch
|
|
1251
|
+
npx @unotest/web bundle push --run --env ENVIRONMENT
|
|
832
1252
|
|
|
833
1253
|
The box's own name is filled in when its licence carries one. `box access`
|
|
834
1254
|
with no name refuses, and its refusal offers to create a box — to somebody who
|
package/README.md
CHANGED
|
@@ -152,15 +152,47 @@ npx @unotest/web box create # rents a box named after the project, size S
|
|
|
152
152
|
boxes — and keeps the key in `~/.config/unotest/credentials.json`
|
|
153
153
|
(mode 0600). `box create` creates the box with the environments the
|
|
154
154
|
suite already runs locally (each `unotest/.env.<name>` and its
|
|
155
|
-
`APP_BASE_URL`),
|
|
156
|
-
the Checkout link and waits for the payment), waits for the box, writes
|
|
155
|
+
`APP_BASE_URL`), waits for the box, writes
|
|
157
156
|
`UNOTEST_BOX_URL` to `unotest/.env` and the push and read tokens to
|
|
158
157
|
`unotest/.secrets`, pushes the suite and prints the viewer's address.
|
|
159
158
|
The tokens live thirty days; `bundle push` and `box …` re-mint them
|
|
160
159
|
through the cloud when a box says they have expired, and `box access`
|
|
161
|
-
does it on demand. `box
|
|
162
|
-
|
|
163
|
-
`
|
|
160
|
+
does it on demand. `box list` shows every box on the account, `box status`
|
|
161
|
+
one of them, and `box logs` the end of a box's own log without logging in to
|
|
162
|
+
it; `box destroy` does what it says, and `whoami` and `logout` complete the
|
|
163
|
+
set. In CI, export `UNOTEST_CLOUD_TOKEN` instead of signing in.
|
|
164
|
+
|
|
165
|
+
Credit is added in the cabinet, by card, under "Add credit" on its home page
|
|
166
|
+
— never from the terminal. When the balance does not cover a new box's first
|
|
167
|
+
hour, `box create` says how much is missing (as far as the key may read the
|
|
168
|
+
balance) and where to add it, and exits 75 with nothing created and nothing
|
|
169
|
+
charged; run it again once the credit is there.
|
|
170
|
+
|
|
171
|
+
Anything that spends money or cannot be undone — creating a box, deleting
|
|
172
|
+
one, switching the Picker on — **waits for a person** unless the key is a CI
|
|
173
|
+
key. The CLI prints where to approve it and how long the request lives, waits
|
|
174
|
+
there, and then carries on exactly as if the call had gone straight through.
|
|
175
|
+
Nobody answers in ten minutes: exit code 75, nothing created and nothing
|
|
176
|
+
charged, run the command again. Somebody declines: exit code 2. `--yes` does
|
|
177
|
+
not change any of this — the flag says the terminal need not ask, not that
|
|
178
|
+
the account need not.
|
|
179
|
+
`approvals` lists what is waiting, and `approvals <id>` says what became of
|
|
180
|
+
one.
|
|
181
|
+
|
|
182
|
+
An agent does the same through the MCP server's `cloud_*` tools — listing,
|
|
183
|
+
renting, deleting, logs, the balance — with the same key and the same rules:
|
|
184
|
+
a rent or a delete comes back as a request to wait on, and
|
|
185
|
+
`cloud_approval_wait` waits for the person's answer.
|
|
186
|
+
|
|
187
|
+
A key carries **permissions**. `login` asks for enough to see the
|
|
188
|
+
account's boxes and work with them, never its money, and the approval
|
|
189
|
+
page in the browser is where you can narrow that further; `login` and
|
|
190
|
+
`whoami` print what the key ended up with. A key that may not read the
|
|
191
|
+
balance is still a working key — `whoami` says the balance is not
|
|
192
|
+
visible to it instead of failing — and a call it may not make comes
|
|
193
|
+
back as `insufficient-scope`, which no amount of signing in again will
|
|
194
|
+
change. Keys for an agent are issued in the cabinet, with the
|
|
195
|
+
permissions ticked there.
|
|
164
196
|
|
|
165
197
|
The **Picker** — grounding intent locators on the box's grounder — comes
|
|
166
198
|
with the box today: it lists at €0.05–€0.13 an hour depending on size,
|