verikun 0.30.0 → 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 +18 -20
- package/CHANGELOG.md +39 -0
- package/README.md +22 -1
- package/dist/adb-health.js +3 -7
- package/dist/agent/engine.js +16 -15
- package/dist/agent/grammar.js +4 -1
- package/dist/agent/lease-heartbeat.js +63 -0
- package/dist/agent/remote.js +116 -113
- package/dist/cli.js +221 -279
- 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 +40 -1
- package/dist/exec.js +37 -5
- package/dist/install-timeouts.js +2 -6
- package/dist/rpc.js +9 -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 +1 -10
- 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 +691 -1379
- package/dist/suite.js +155 -449
- 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]`
|
|
@@ -489,7 +491,7 @@ vk suite tests/ --app com.example.app --servers http://a:8391,http://b:8391
|
|
|
489
491
|
`totals.wallClockMs` (how long the gate took) from `totals.durationMs` (device-seconds).
|
|
490
492
|
- `--concurrency N` caps how many run at once — more devices on one host can thrash it.
|
|
491
493
|
`--max-suite-cost-usd N` stops the suite once total model spend crosses it (exit `1`).
|
|
492
|
-
-
|
|
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.
|
|
493
495
|
- Over `--server`, a lane with no free device waits instead of failing tests, and a test whose
|
|
494
496
|
device left the pool re-runs without spending a retry. With no device for any lane the suite
|
|
495
497
|
stops after `VERIKUN_SUITE_DEVICE_WAIT_MIN` (default 10) with exit `3` and a `notRun` list.
|
|
@@ -508,8 +510,9 @@ vk install ./app-debug.apk --server "$VERIKUN_SERVER" # server needs --allow-i
|
|
|
508
510
|
vk suite tests/ --app com.example.app --server "$VERIKUN_SERVER"
|
|
509
511
|
```
|
|
510
512
|
|
|
511
|
-
A wrong URL/key fails fast with exit 3
|
|
512
|
-
|
|
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
|
|
513
516
|
(below). To expose a device from THIS machine: `vk server --allow-install`
|
|
514
517
|
(add `--bind <addr>` to leave loopback; auth key auto-generates if unset).
|
|
515
518
|
|
|
@@ -530,22 +533,17 @@ repairs always land on the same phone. `vk install --server` then installs on ev
|
|
|
530
533
|
`vk devices start|restart|stop --server` is refused (`403`) — a pool has no single device
|
|
531
534
|
to act on.
|
|
532
535
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
column, and a pooled server re-adopts a device that comes back within a minute. A device
|
|
545
|
-
that is still attached keeps its place and is simply dealt last, so its own error keeps
|
|
546
|
-
reaching you rather than a bare "no device attached". A device that is **gone** leaves the
|
|
547
|
-
pool — so `capacity` can drop mid-job, and an install can come back `exit 0` having skipped
|
|
548
|
-
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.
|
|
549
547
|
|
|
550
548
|
## The device is missing or wedged
|
|
551
549
|
|
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,45 @@ 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
|
+
|
|
9
48
|
## [0.30.0] - 2026-09-26
|
|
10
49
|
|
|
11
50
|
### 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
|
@@ -31,17 +31,6 @@ class GuardBlindError extends Error {
|
|
|
31
31
|
this.name = 'GuardBlindError';
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
-
/**
|
|
35
|
-
* A run the server EVICTED cannot continue: its phone left the pool, and every later call is
|
|
36
|
-
* refused the same way. So the catches below that absorb a failed READ — a guard's retry, the
|
|
37
|
-
* empty-tree fallback, a repair that could not be made — must let it through. Absorbed, a lost
|
|
38
|
-
* phone read as an absent guard, a "read found no element" FAIL or a failed repair, and a
|
|
39
|
-
* parallel suite lost the class it needs to re-run the test as a fresh run (#147).
|
|
40
|
-
*/
|
|
41
|
-
const rethrowIfEvicted = (e) => {
|
|
42
|
-
if (e instanceof errors_1.RunEvictedError)
|
|
43
|
-
throw e;
|
|
44
|
-
};
|
|
45
34
|
const describe = (leaf) => [leaf.command, ...leaf.positionals, ...leaf.flags.map((f) => (f.value === 'true' ? `--${f.name}` : `--${f.name} ${f.value}`))]
|
|
46
35
|
.join(' ')
|
|
47
36
|
.trim();
|
|
@@ -202,7 +191,7 @@ async function runPlan(plan, deps) {
|
|
|
202
191
|
return await deps.getElements();
|
|
203
192
|
}
|
|
204
193
|
catch (e) {
|
|
205
|
-
|
|
194
|
+
(0, errors_1.rethrowIfLost)(e);
|
|
206
195
|
return [];
|
|
207
196
|
}
|
|
208
197
|
};
|
|
@@ -259,6 +248,7 @@ async function runPlan(plan, deps) {
|
|
|
259
248
|
// is a bad read of a live screen, not a blind one, and it is already handled below.
|
|
260
249
|
let everRead = false;
|
|
261
250
|
let lastErr;
|
|
251
|
+
let lostSince;
|
|
262
252
|
const minLooks = settleMs > 0 ? 2 : 1;
|
|
263
253
|
for (;;) {
|
|
264
254
|
let els;
|
|
@@ -266,11 +256,18 @@ async function runPlan(plan, deps) {
|
|
|
266
256
|
try {
|
|
267
257
|
els = await deps.getElements();
|
|
268
258
|
everRead = true;
|
|
259
|
+
lastErr = undefined;
|
|
260
|
+
lostSince = undefined;
|
|
269
261
|
}
|
|
270
262
|
catch (e) {
|
|
271
|
-
|
|
263
|
+
if (!(e instanceof errors_1.DeviceGoneError))
|
|
264
|
+
(0, errors_1.rethrowIfLost)(e);
|
|
272
265
|
els = undefined; // transient dump failure — retry once before concluding "absent"
|
|
273
266
|
lastErr = e;
|
|
267
|
+
if (e instanceof errors_1.DeviceGoneError)
|
|
268
|
+
lostSince ??= Date.now();
|
|
269
|
+
else
|
|
270
|
+
lostSince = undefined;
|
|
274
271
|
}
|
|
275
272
|
// An EMPTY tree is not a screen, it is a bad read: a live app always has nodes, and
|
|
276
273
|
// this device routinely returns a partial/blank dump mid-transition (measured: `ui`
|
|
@@ -296,7 +293,7 @@ async function runPlan(plan, deps) {
|
|
|
296
293
|
// One successful read — even an empty tree — and the ordinary semantics resume exactly:
|
|
297
294
|
// settleMs=0 is still a single-shot probe. It must never make a merely ABSENT selector
|
|
298
295
|
// more patient, or every guard silently costs 10s.
|
|
299
|
-
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) {
|
|
300
297
|
await (0, wait_1.sleep)(GUARD_POLL_MS);
|
|
301
298
|
continue;
|
|
302
299
|
}
|
|
@@ -306,6 +303,8 @@ async function runPlan(plan, deps) {
|
|
|
306
303
|
//
|
|
307
304
|
// A no-window that outlives its grace lands here too, and still aborts: at that point
|
|
308
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;
|
|
309
308
|
if (!everRead && (0, errors_1.isEnvError)(lastErr))
|
|
310
309
|
throw new GuardBlindError(selector, lastErr);
|
|
311
310
|
return false;
|
|
@@ -379,7 +378,7 @@ async function runPlan(plan, deps) {
|
|
|
379
378
|
repaired = node;
|
|
380
379
|
}
|
|
381
380
|
catch (e) {
|
|
382
|
-
|
|
381
|
+
(0, errors_1.rethrowIfLost)(e);
|
|
383
382
|
const msg = e instanceof Error ? e.message : String(e);
|
|
384
383
|
return { status: 'fail', where, reason: `repair failed: ${msg}` };
|
|
385
384
|
}
|
|
@@ -404,6 +403,8 @@ async function runPlan(plan, deps) {
|
|
|
404
403
|
// sets status=passed/exitCode=0 and drops the failure evidence) so the report and
|
|
405
404
|
// JUnit stay consistent with the green run, then continue. Scoped to screenshot/
|
|
406
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);
|
|
407
408
|
if (isScreenshotLeaf(current)) {
|
|
408
409
|
const why = outcome.error ? outcome.error.message.split('\n')[0] : `exited ${outcome.code}`;
|
|
409
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('.');
|