verikun 0.29.1 → 0.32.0-rc.1
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/.claude/skills/verikun/SKILL.md +23 -20
- package/CHANGELOG.md +59 -0
- package/README.md +22 -1
- package/dist/adb-health.js +3 -7
- package/dist/agent/engine.js +17 -2
- package/dist/agent/grammar.js +4 -1
- package/dist/agent/lease-heartbeat.js +63 -0
- package/dist/agent/remote.js +159 -105
- package/dist/cli.js +270 -249
- package/dist/commands/auto-wait.js +30 -4
- package/dist/commands/text-entry.js +141 -0
- package/dist/companion/manager.js +26 -10
- package/dist/device/pool.js +9 -6
- package/dist/drivers/adb-runner.js +96 -0
- package/dist/drivers/adb.js +23 -17
- package/dist/drivers/ios.js +42 -28
- package/dist/drivers/lifecycle.js +2 -2
- package/dist/errors.js +78 -1
- package/dist/exec.js +37 -5
- package/dist/install-timeouts.js +2 -6
- package/dist/report.js +2 -1
- package/dist/rpc.js +22 -1
- package/dist/run.js +12 -6
- package/dist/server-devices.js +105 -0
- package/dist/server-host.js +81 -0
- package/dist/server-http.js +0 -5
- package/dist/server-leases.js +168 -0
- package/dist/server-lifecycle.js +20 -2
- package/dist/server-pool.js +124 -157
- package/dist/server-worker.js +30 -42
- package/dist/server.js +701 -1365
- package/dist/suite.js +190 -230
- package/dist/ui/barrier.js +3 -3
- package/dist/ui/field-check.js +204 -0
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/dist/device/failover.js +0 -238
|
@@ -93,7 +93,9 @@ usually don't need a `wait` before an action — `vk tap @next` already polls fo
|
|
|
93
93
|
`@next` to appear.
|
|
94
94
|
|
|
95
95
|
- `vk tap <selector|index>` · `vk tap --at x,y`
|
|
96
|
-
- `vk text <selector> "the text" [--clear] [--enter]` — focus the field, then type
|
|
96
|
+
- `vk text <selector> "the text" [--clear] [--enter]` — focus the field, then type. On
|
|
97
|
+
Android it reads the field back and exits `1` if the value did not land (after one
|
|
98
|
+
retype); a field that reformats or rejects input needs `vk tap` + `vk type` instead
|
|
97
99
|
- `vk type "text" [--enter]` — type into the already-focused field
|
|
98
100
|
- `vk swipe up|down|left|right [--on <selector>] [--distance f] [--duration ms]`
|
|
99
101
|
- `vk swipe --from x,y --to x,y [--duration ms]`
|
|
@@ -437,6 +439,8 @@ vk ai onboarding.md --timeout 5m # tighten the run timeout (default 15m)
|
|
|
437
439
|
- **`3` also means the device toolchain is broken**, checked *before* anything is compiled
|
|
438
440
|
(missing `adb`/`idb`, no device, an ambiguous target) and again if it breaks mid-run. Treat
|
|
439
441
|
it as "fix the machine", never as a failing test — the message carries the install hint.
|
|
442
|
+
Exception: over `--server`, a `--json` result with `evicted: true` means the server's device
|
|
443
|
+
left the pool mid-run — nothing is broken; run the test again.
|
|
440
444
|
|
|
441
445
|
## Run a suite of tests (vk suite)
|
|
442
446
|
|
|
@@ -487,7 +491,10 @@ vk suite tests/ --app com.example.app --servers http://a:8391,http://b:8391
|
|
|
487
491
|
`totals.wallClockMs` (how long the gate took) from `totals.durationMs` (device-seconds).
|
|
488
492
|
- `--concurrency N` caps how many run at once — more devices on one host can thrash it.
|
|
489
493
|
`--max-suite-cost-usd N` stops the suite once total model spend crosses it (exit `1`).
|
|
490
|
-
-
|
|
494
|
+
- Local devices bench and rejoin after 45-second probes; server health owns remote membership. Typed loss reruns free on another device at most twice.
|
|
495
|
+
- Over `--server`, a lane with no free device waits instead of failing tests, and a test whose
|
|
496
|
+
device left the pool re-runs without spending a retry. With no device for any lane the suite
|
|
497
|
+
stops after `VERIKUN_SUITE_DEVICE_WAIT_MIN` (default 10) with exit `3` and a `notRun` list.
|
|
491
498
|
- `--ensure-device` is refused with `--devices` — start the pool with `vk devices start`.
|
|
492
499
|
|
|
493
500
|
## Drive a remote device (--server)
|
|
@@ -503,8 +510,9 @@ vk install ./app-debug.apk --server "$VERIKUN_SERVER" # server needs --allow-i
|
|
|
503
510
|
vk suite tests/ --app com.example.app --server "$VERIKUN_SERVER"
|
|
504
511
|
```
|
|
505
512
|
|
|
506
|
-
A wrong URL/key fails fast with exit 3
|
|
507
|
-
|
|
513
|
+
A wrong URL/key fails fast with exit 3. New clients wait in the server FIFO; a tagged
|
|
514
|
+
`409` means the run lost its device and must restart. `503` after the wait means no device
|
|
515
|
+
became available — boot one
|
|
508
516
|
(below). To expose a device from THIS machine: `vk server --allow-install`
|
|
509
517
|
(add `--bind <addr>` to leave loopback; auth key auto-generates if unset).
|
|
510
518
|
|
|
@@ -525,22 +533,17 @@ repairs always land on the same phone. `vk install --server` then installs on ev
|
|
|
525
533
|
`vk devices start|restart|stop --server` is refused (`403`) — a pool has no single device
|
|
526
534
|
to act on.
|
|
527
535
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
column, and a pooled server re-adopts a device that comes back within a minute. A device
|
|
540
|
-
that is still attached keeps its place and is simply dealt last, so its own error keeps
|
|
541
|
-
reaching you rather than a bare "no device attached". A device that is **gone** leaves the
|
|
542
|
-
pool — so `capacity` can drop mid-job, and an install can come back `exit 0` having skipped
|
|
543
|
-
it. That is a success: nothing can be dealt a device running the previous build.
|
|
536
|
+
Remote peers must support held leases and device supervision; upgrade them together.
|
|
537
|
+
Compilation holds no lease. A heartbeat thread keeps the execution lease alive during
|
|
538
|
+
synchronous model repair. Closing the socket or 30 seconds without bytes ends it and
|
|
539
|
+
restores device settings before the next run. Every server, including capacity one,
|
|
540
|
+
readmits wanted devices with boot/build checks before dealing. `--no-failover` disables
|
|
541
|
+
spare recruitment, not readmission. `VERIKUN_NO_DEVICE_WATCH=1` disables supervision.
|
|
542
|
+
|
|
543
|
+
`vk ai --json` exposes `outcome`; only lost-device gets free reruns. Env failures use normal
|
|
544
|
+
retries. A remote suite checks installedSha between tests and refuses a changed build.
|
|
545
|
+
A single `--server` accepts `--ensure-device`, once in the parent. Install waits for holders,
|
|
546
|
+
returns successful devices plus skipped targets, and keeps stale builds out of dealing.
|
|
544
547
|
|
|
545
548
|
## The device is missing or wedged
|
|
546
549
|
|
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,65 @@ All notable changes to this project are documented here. The format is based on
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.32.0-rc.1] - 2026-10-03
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- **Docs site** links unfurl with a social card image built from the illustration.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- **Server diagrams**: explain device reservations, test reruns and recovery in plain language.
|
|
16
|
+
- **`vk server`**: fork one executor per device; confirmed failures kill its process group, including blocked adb/idb children.
|
|
17
|
+
- **`vk server`**: supervise device health and readmit every pool; typed loss reruns free. Disable supervision with `VERIKUN_NO_DEVICE_WATCH`.
|
|
18
|
+
- **Remote leases**: acquire after compilation; heartbeat holds, FIFO admission, and lease-end setting restoration release abandoned clients.
|
|
19
|
+
- **Remote installs**: gate dealing by build SHA; return after first-success grace while failed targets catch up.
|
|
20
|
+
- **`vk suite`**: use lanes at every remote capacity, bench local failures, and preserve archives with explicit JSON outcomes.
|
|
21
|
+
- **Docs site and README** show the new verikun logo and an animated illustration, with light and
|
|
22
|
+
dark variants.
|
|
23
|
+
- **Docs site** primary color is now black, off-white in dark mode.
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
- **Docs site** favicon no longer 404s; it shows the logo.
|
|
27
|
+
- **Android actions**: surface device-shell failures and retry safe transport loss once; failed liveness confirmation opens a circuit breaker.
|
|
28
|
+
- **Remote deadlines**: use native HTTP timers and keep draining executors out of dealing; any nonempty `VERIKUN_NO_ADB_RECYCLE` disables restart.
|
|
29
|
+
|
|
30
|
+
### Removed
|
|
31
|
+
- **Remote clients and servers**: upgrade both to versions supporting held leases and server-owned device health.
|
|
32
|
+
- **Remote leases**: remove no-hold acquisition, implicit execution leases, five-minute takeover, and support for peers without holds and supervision.
|
|
33
|
+
- **Remote RPC**: remove `evicted` and `deviceChanged` response fields; typed errors identify device loss.
|
|
34
|
+
- **Suite children**: remove verdict inference for JSON without `outcome`; same-build results must supply it.
|
|
35
|
+
|
|
36
|
+
## [0.31.0] - 2026-09-27
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
- **`vk text`** no longer re-taps a field that already has focus, which let SwiftKey drop the whole
|
|
40
|
+
value while `text` exited `0`. ([#151])
|
|
41
|
+
|
|
42
|
+
### Changed
|
|
43
|
+
- **`vk text` on Android** reads the field back, retypes once, and exits `1` if the value is still
|
|
44
|
+
missing; use `tap` + `type` for fields that reformat input. ([#151])
|
|
45
|
+
|
|
46
|
+
[#151]: https://github.com/ddikman/verikun/issues/151
|
|
47
|
+
|
|
48
|
+
## [0.30.0] - 2026-09-26
|
|
49
|
+
|
|
50
|
+
### Fixed
|
|
51
|
+
- **`vk suite --server`** waits for a free device instead of failing tests that never got one, and
|
|
52
|
+
a waiting lane resumes when the pool recovers. ([#147])
|
|
53
|
+
|
|
54
|
+
### Added
|
|
55
|
+
- **`vk suite --server`** re-runs a test whose device left the pool mid-run without spending a
|
|
56
|
+
retry, up to twice; needs the matching `vk server`. ([#147])
|
|
57
|
+
- **`VERIKUN_SUITE_DEVICE_WAIT_MIN`** (default `10`) caps how long a pooled suite waits when no lane
|
|
58
|
+
gets a device, busy pools included, then exits `3`; `0` fails fast. ([#147])
|
|
59
|
+
|
|
60
|
+
### Changed
|
|
61
|
+
- **`--json` errors** name a refused `vk server` lease `NoFreeDeviceError` and an evicted run
|
|
62
|
+
`RunEvictedError` in `errorKind`; exit codes are unchanged. ([#147])
|
|
63
|
+
- **`vk ai --json`** reports a run whose device left the pool as an environment abort with
|
|
64
|
+
`evicted: true` and its report, instead of a bare error. ([#147])
|
|
65
|
+
|
|
66
|
+
[#147]: https://github.com/ddikman/verikun/issues/147
|
|
67
|
+
|
|
9
68
|
## [0.29.1] - 2026-09-26
|
|
10
69
|
|
|
11
70
|
### Fixed
|
package/README.md
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
# verikun
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
<table>
|
|
4
|
+
<tr>
|
|
5
|
+
<td width="260" align="center" valign="middle">
|
|
6
|
+
<picture>
|
|
7
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/readme/illustration-dark.svg">
|
|
8
|
+
<img src="docs/readme/illustration-light.svg" alt="verikun" width="230">
|
|
9
|
+
</picture>
|
|
10
|
+
</td>
|
|
11
|
+
<td valign="middle">
|
|
12
|
+
|
|
13
|
+
**Agent-driven, natural-language mobile tests — during agent development or in CI.** Self-healing and self-improving, with cost caps and test reports.
|
|
4
14
|
|
|
5
15
|
**📚 [Documentation](https://ddikman.github.io/verikun/)** — installation, guides, full command reference, and internals.
|
|
6
16
|
|
|
17
|
+
</td>
|
|
18
|
+
</tr>
|
|
19
|
+
</table>
|
|
20
|
+
|
|
7
21
|
- **Agent CLI** — `vk <command>`: one-shot commands to inspect the screen as a semantic tree (or screenshot) and act on it.
|
|
8
22
|
- **Puppeteer for native mobile** — a thin wrapper over native Android and iOS automation runners with zero runtime dependencies.
|
|
9
23
|
- **Natural-language tests** — `vk ai <file>`: runs plain-English tests, compiled once and replayed model-free (~$0), calling a model only to self-heal a drifted step. Tests share a preamble with `@include`, written once instead of pasted into each. A compile that does not cover its test is rejected rather than cached as a pass. [What that costs](https://ddikman.github.io/verikun/reference/cost/), and how the `--max-cost-usd` ceiling bounds it.
|
|
@@ -22,6 +36,9 @@ $ vk tap @sign_in_btn
|
|
|
22
36
|
tapped [3] Button "Sign in" @sign_in_btn (540,1020) tap
|
|
23
37
|
```
|
|
24
38
|
|
|
39
|
+
**Remote compatibility in 1.0:** upgrade the CLI and server together. Remote execution requires
|
|
40
|
+
a streaming held lease and device supervision; pre-hold peers are no longer supported.
|
|
41
|
+
|
|
25
42
|
## Install
|
|
26
43
|
|
|
27
44
|
Requires Node ≥ 18 and the Android platform-tools (`adb`) on your `PATH`.
|
|
@@ -57,6 +74,10 @@ verikun ships as a skill and plugin, not an MCP server, and that is deliberate.
|
|
|
57
74
|
|
|
58
75
|
There is also no need for an MCP here: verikun runs locally with all its dependencies, and the agent calls it through the plain `vk` CLI — no shared session, data, or authentication to broker.
|
|
59
76
|
|
|
77
|
+
Remote suites acquire after compilation, wait in a server FIFO, and rerun typed device loss
|
|
78
|
+
without spending a retry. Held leases release on client disappearance; recovering devices
|
|
79
|
+
restore settings and verify the retained build before returning to service.
|
|
80
|
+
|
|
60
81
|
## Documentation
|
|
61
82
|
|
|
62
83
|
| | |
|
package/dist/adb-health.js
CHANGED
|
@@ -8,12 +8,8 @@
|
|
|
8
8
|
* of CPU on the error loop. `adb kill-server && adb start-server` took it to zero and every
|
|
9
9
|
* device came back in ~5s.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* candidate sits behind the same host adb — so when the transport is what broke, failover
|
|
14
|
-
* walks the pool retiring healthy phones for a host-side fault. That is the same polarity
|
|
15
|
-
* error `ARTIFACT_RULES` exists to prevent on the install path: enumerate the thing you can
|
|
16
|
-
* actually attribute, and never blame the open-ended side.
|
|
11
|
+
* A host transport failure affects every device behind adb. The server supervises
|
|
12
|
+
* correlated losses together and recycles under a host lock, excluding foreign claims.
|
|
17
13
|
*
|
|
18
14
|
* THE RATE IS A LEAKED-HANDLE COUNTER, which is what makes this measurable rather than
|
|
19
15
|
* guessed. adb scans USB at ~1Hz and each STALE device handle throws one violation per
|
|
@@ -125,7 +121,7 @@ function describeRot(health) {
|
|
|
125
121
|
* exactly, for the rare host running other adb work alongside the server.
|
|
126
122
|
*/
|
|
127
123
|
function adbRecycleEnabled(platform) {
|
|
128
|
-
return platform === 'android' && process.env.VERIKUN_NO_ADB_RECYCLE
|
|
124
|
+
return platform === 'android' && !process.env.VERIKUN_NO_ADB_RECYCLE;
|
|
129
125
|
}
|
|
130
126
|
/** The running adb server's pid. Undefined when there is none, or when `pgrep` is absent. */
|
|
131
127
|
function adbServerPid() {
|
package/dist/agent/engine.js
CHANGED
|
@@ -190,7 +190,8 @@ async function runPlan(plan, deps) {
|
|
|
190
190
|
try {
|
|
191
191
|
return await deps.getElements();
|
|
192
192
|
}
|
|
193
|
-
catch {
|
|
193
|
+
catch (e) {
|
|
194
|
+
(0, errors_1.rethrowIfLost)(e);
|
|
194
195
|
return [];
|
|
195
196
|
}
|
|
196
197
|
};
|
|
@@ -247,6 +248,7 @@ async function runPlan(plan, deps) {
|
|
|
247
248
|
// is a bad read of a live screen, not a blind one, and it is already handled below.
|
|
248
249
|
let everRead = false;
|
|
249
250
|
let lastErr;
|
|
251
|
+
let lostSince;
|
|
250
252
|
const minLooks = settleMs > 0 ? 2 : 1;
|
|
251
253
|
for (;;) {
|
|
252
254
|
let els;
|
|
@@ -254,10 +256,18 @@ async function runPlan(plan, deps) {
|
|
|
254
256
|
try {
|
|
255
257
|
els = await deps.getElements();
|
|
256
258
|
everRead = true;
|
|
259
|
+
lastErr = undefined;
|
|
260
|
+
lostSince = undefined;
|
|
257
261
|
}
|
|
258
262
|
catch (e) {
|
|
263
|
+
if (!(e instanceof errors_1.DeviceGoneError))
|
|
264
|
+
(0, errors_1.rethrowIfLost)(e);
|
|
259
265
|
els = undefined; // transient dump failure — retry once before concluding "absent"
|
|
260
266
|
lastErr = e;
|
|
267
|
+
if (e instanceof errors_1.DeviceGoneError)
|
|
268
|
+
lostSince ??= Date.now();
|
|
269
|
+
else
|
|
270
|
+
lostSince = undefined;
|
|
261
271
|
}
|
|
262
272
|
// An EMPTY tree is not a screen, it is a bad read: a live app always has nodes, and
|
|
263
273
|
// this device routinely returns a partial/blank dump mid-transition (measured: `ui`
|
|
@@ -283,7 +293,7 @@ async function runPlan(plan, deps) {
|
|
|
283
293
|
// One successful read — even an empty tree — and the ordinary semantics resume exactly:
|
|
284
294
|
// settleMs=0 is still a single-shot probe. It must never make a merely ABSENT selector
|
|
285
295
|
// more patient, or every guard silently costs 10s.
|
|
286
|
-
if (!everRead && lastErr instanceof errors_1.TransientReadError && Date.now() < transientDeadline) {
|
|
296
|
+
if (((!everRead && lastErr instanceof errors_1.TransientReadError) || (lastErr instanceof errors_1.DeviceGoneError && Date.now() - (lostSince ?? Date.now()) < 8000)) && Date.now() < transientDeadline) {
|
|
287
297
|
await (0, wait_1.sleep)(GUARD_POLL_MS);
|
|
288
298
|
continue;
|
|
289
299
|
}
|
|
@@ -293,6 +303,8 @@ async function runPlan(plan, deps) {
|
|
|
293
303
|
//
|
|
294
304
|
// A no-window that outlives its grace lands here too, and still aborts: at that point
|
|
295
305
|
// the app really is gone, and reporting "absent" would be the same false green.
|
|
306
|
+
if ((0, errors_1.isDeviceLoss)(lastErr))
|
|
307
|
+
throw lastErr;
|
|
296
308
|
if (!everRead && (0, errors_1.isEnvError)(lastErr))
|
|
297
309
|
throw new GuardBlindError(selector, lastErr);
|
|
298
310
|
return false;
|
|
@@ -366,6 +378,7 @@ async function runPlan(plan, deps) {
|
|
|
366
378
|
repaired = node;
|
|
367
379
|
}
|
|
368
380
|
catch (e) {
|
|
381
|
+
(0, errors_1.rethrowIfLost)(e);
|
|
369
382
|
const msg = e instanceof Error ? e.message : String(e);
|
|
370
383
|
return { status: 'fail', where, reason: `repair failed: ${msg}` };
|
|
371
384
|
}
|
|
@@ -390,6 +403,8 @@ async function runPlan(plan, deps) {
|
|
|
390
403
|
// sets status=passed/exitCode=0 and drops the failure evidence) so the report and
|
|
391
404
|
// JUnit stay consistent with the green run, then continue. Scoped to screenshot/
|
|
392
405
|
// shot — every other command's failure stays terminal.
|
|
406
|
+
if ((0, errors_1.isDeviceLoss)(outcome.error) || outcome.error instanceof errors_1.RunEvictedError)
|
|
407
|
+
(0, errors_1.rethrowIfLost)(outcome.error);
|
|
393
408
|
if (isScreenshotLeaf(current)) {
|
|
394
409
|
const why = outcome.error ? outcome.error.message.split('\n')[0] : `exited ${outcome.code}`;
|
|
395
410
|
deps.log(`[ai] ${where}: screenshot capture failed (${why}) — continuing (best-effort review screenshot)`);
|
package/dist/agent/grammar.js
CHANGED
|
@@ -21,7 +21,10 @@ Each step is one of three node types:
|
|
|
21
21
|
--no-restart skips the force-stop, just bringing it forward)
|
|
22
22
|
stop <package> — force-stop the app
|
|
23
23
|
tap <selector> — tap the element a selector resolves to (scrolls it into view first)
|
|
24
|
-
text <selector> <value...> — focus a field and type value (--clear to clear first, --enter to submit)
|
|
24
|
+
text <selector> <value...> — focus a field and type value (--clear to clear first, --enter to submit);
|
|
25
|
+
FAILS if the field does not then hold the value. For a field that
|
|
26
|
+
reformats or rejects input (masks, currency, invalid-input tests),
|
|
27
|
+
use tap <selector> then type <value...> instead
|
|
25
28
|
type <value...> — type into the already-focused field
|
|
26
29
|
key <name> | back | home | enter
|
|
27
30
|
swipe <up|down|left|right> [--on <selector>] — scroll/swipe (up = scroll down the page)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const node_worker_threads_1 = require("node:worker_threads");
|
|
4
|
+
const node_http_1 = require("node:http");
|
|
5
|
+
const node_https_1 = require("node:https");
|
|
6
|
+
// Own the socket AND the timer: synchronous model repair on the main thread cannot
|
|
7
|
+
// starve client-originated heartbeat bytes. Closing the process closes this hold too.
|
|
8
|
+
const { url, headers, timeoutMs } = node_worker_threads_1.workerData;
|
|
9
|
+
const endpoint = new URL(url);
|
|
10
|
+
const send = endpoint.protocol === 'https:' ? node_https_1.request : node_http_1.request;
|
|
11
|
+
let leased = false;
|
|
12
|
+
let closing = false;
|
|
13
|
+
const req = send(endpoint, { method: 'POST', headers }, res => {
|
|
14
|
+
let data = '';
|
|
15
|
+
res.setEncoding('utf8');
|
|
16
|
+
res.on('data', (chunk) => {
|
|
17
|
+
data += chunk;
|
|
18
|
+
if (leased || !data.includes('\n'))
|
|
19
|
+
return;
|
|
20
|
+
if (res.statusCode === 200) {
|
|
21
|
+
try {
|
|
22
|
+
node_worker_threads_1.parentPort?.postMessage({ kind: 'leased', body: JSON.parse(data.split('\n')[0]) });
|
|
23
|
+
leased = true;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
node_worker_threads_1.parentPort?.postMessage({ kind: 'error', message: 'invalid lease response' });
|
|
27
|
+
req.destroy();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
res.on('end', () => {
|
|
32
|
+
if (!leased) {
|
|
33
|
+
let body;
|
|
34
|
+
try {
|
|
35
|
+
body = JSON.parse(data);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
body = { error: data };
|
|
39
|
+
}
|
|
40
|
+
node_worker_threads_1.parentPort?.postMessage({ kind: 'status', status: res.statusCode, body });
|
|
41
|
+
}
|
|
42
|
+
else if (!closing)
|
|
43
|
+
node_worker_threads_1.parentPort?.postMessage({ kind: 'ended' });
|
|
44
|
+
stop();
|
|
45
|
+
});
|
|
46
|
+
res.on('error', e => { if (!closing)
|
|
47
|
+
node_worker_threads_1.parentPort?.postMessage({ kind: 'error', message: e.message }); stop(); });
|
|
48
|
+
});
|
|
49
|
+
const heartbeat = setInterval(() => { if (!req.destroyed)
|
|
50
|
+
req.write('.'); }, 10_000);
|
|
51
|
+
const timeout = setTimeout(() => {
|
|
52
|
+
if (!leased)
|
|
53
|
+
req.destroy(new Error('lease acquisition timed out'));
|
|
54
|
+
}, timeoutMs);
|
|
55
|
+
function stop() { clearInterval(heartbeat); clearTimeout(timeout); req.destroy(); node_worker_threads_1.parentPort?.close(); }
|
|
56
|
+
req.on('error', e => { if (!closing)
|
|
57
|
+
node_worker_threads_1.parentPort?.postMessage({ kind: 'error', message: e.message }); stop(); });
|
|
58
|
+
node_worker_threads_1.parentPort?.on('message', m => { if (m === 'close') {
|
|
59
|
+
closing = true;
|
|
60
|
+
stop();
|
|
61
|
+
} });
|
|
62
|
+
req.flushHeaders();
|
|
63
|
+
req.write('.');
|