@jevitate/cli 0.1.0 → 0.2.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/README.md +23 -0
- package/dist/ai-cli.js +40 -30
- package/dist/ai-cli.js.map +1 -1
- package/dist/author-journey-cli.d.ts +4 -0
- package/dist/author-journey-cli.js +109 -0
- package/dist/author-journey-cli.js.map +1 -0
- package/dist/bin.js +58077 -22454
- package/dist/bin.js.map +1 -1
- package/dist/browser-run-options.d.ts +58 -0
- package/dist/browser-run-options.js +106 -0
- package/dist/browser-run-options.js.map +1 -0
- package/dist/build-info.generated.d.ts +2 -0
- package/dist/build-info.generated.js +10 -0
- package/dist/build-info.generated.js.map +1 -0
- package/dist/check-api.d.ts +5 -0
- package/dist/check-api.js +233 -0
- package/dist/check-api.js.map +1 -0
- package/dist/check-budget.d.ts +26 -0
- package/dist/check-budget.js +89 -0
- package/dist/check-budget.js.map +1 -0
- package/dist/check-cli.d.ts +31 -0
- package/dist/check-cli.js +109 -0
- package/dist/check-cli.js.map +1 -0
- package/dist/check-execute.d.ts +25 -0
- package/dist/check-execute.js +385 -0
- package/dist/check-execute.js.map +1 -0
- package/dist/check-plan.d.ts +113 -0
- package/dist/check-plan.js +448 -0
- package/dist/check-plan.js.map +1 -0
- package/dist/check-suite.d.ts +171 -0
- package/dist/check-suite.js +515 -0
- package/dist/check-suite.js.map +1 -0
- package/dist/check-types.d.ts +181 -0
- package/dist/check-types.js +35 -0
- package/dist/check-types.js.map +1 -0
- package/dist/cli-args.d.ts +26 -0
- package/dist/cli-args.js +50 -0
- package/dist/cli-args.js.map +1 -0
- package/dist/cli-output.d.ts +102 -0
- package/dist/cli-output.js +516 -0
- package/dist/cli-output.js.map +1 -0
- package/dist/cli-reference.d.ts +3 -0
- package/dist/cli-reference.js +81 -0
- package/dist/cli-reference.js.map +1 -0
- package/dist/cli-refusal.d.ts +12 -0
- package/dist/cli-refusal.js +52 -0
- package/dist/cli-refusal.js.map +1 -0
- package/dist/cli-shared.d.ts +330 -0
- package/dist/cli-shared.js +492 -0
- package/dist/cli-shared.js.map +1 -0
- package/dist/cli-surface.d.ts +33 -0
- package/dist/cli-surface.js +48 -0
- package/dist/cli-surface.js.map +1 -0
- package/dist/conversation-options.d.ts +15 -0
- package/dist/conversation-options.js +10 -0
- package/dist/conversation-options.js.map +1 -0
- package/dist/credentials-file.d.ts +15 -0
- package/dist/credentials-file.js +56 -0
- package/dist/credentials-file.js.map +1 -0
- package/dist/data-dir.d.ts +1 -1
- package/dist/data-dir.js +1 -1
- package/dist/defect-evidence.d.ts +141 -0
- package/dist/defect-evidence.js +502 -0
- package/dist/defect-evidence.js.map +1 -0
- package/dist/demo-aspect-api.d.ts +209 -0
- package/dist/demo-aspect-api.js +397 -0
- package/dist/demo-aspect-api.js.map +1 -0
- package/dist/demo-aspect-cli.d.ts +3 -0
- package/dist/demo-aspect-cli.js +241 -0
- package/dist/demo-aspect-cli.js.map +1 -0
- package/dist/demo-capture.d.ts +108 -0
- package/dist/demo-capture.js +438 -0
- package/dist/demo-capture.js.map +1 -0
- package/dist/engine.d.ts +35 -0
- package/dist/engine.js +25 -0
- package/dist/engine.js.map +1 -0
- package/dist/environments.d.ts +155 -0
- package/dist/environments.js +364 -0
- package/dist/environments.js.map +1 -0
- package/dist/exit-codes.d.ts +46 -0
- package/dist/exit-codes.js +91 -0
- package/dist/exit-codes.js.map +1 -0
- package/dist/explore-adversarial.d.ts +137 -0
- package/dist/explore-adversarial.js +218 -0
- package/dist/explore-adversarial.js.map +1 -0
- package/dist/explore-api.d.ts +7 -187
- package/dist/explore-api.js +7 -301
- package/dist/explore-api.js.map +1 -1
- package/dist/explore-author.d.ts +79 -0
- package/dist/explore-author.js +86 -0
- package/dist/explore-author.js.map +1 -0
- package/dist/explore-cli.d.ts +7 -0
- package/dist/explore-cli.js +963 -0
- package/dist/explore-cli.js.map +1 -0
- package/dist/explore-coverage.d.ts +158 -0
- package/dist/explore-coverage.js +240 -0
- package/dist/explore-coverage.js.map +1 -0
- package/dist/explore-feature.d.ts +127 -0
- package/dist/explore-feature.js +220 -0
- package/dist/explore-feature.js.map +1 -0
- package/dist/explore-goal.d.ts +253 -0
- package/dist/explore-goal.js +374 -0
- package/dist/explore-goal.js.map +1 -0
- package/dist/explore-shared.d.ts +152 -0
- package/dist/explore-shared.js +129 -0
- package/dist/explore-shared.js.map +1 -0
- package/dist/explore-specs.d.ts +53 -0
- package/dist/explore-specs.js +333 -0
- package/dist/explore-specs.js.map +1 -0
- package/dist/findings-filing.d.ts +64 -0
- package/dist/findings-filing.js +108 -0
- package/dist/findings-filing.js.map +1 -0
- package/dist/fixture-auth.d.ts +52 -0
- package/dist/fixture-auth.js +82 -0
- package/dist/fixture-auth.js.map +1 -0
- package/dist/fixture-cli.d.ts +70 -0
- package/dist/fixture-cli.js +178 -0
- package/dist/fixture-cli.js.map +1 -0
- package/dist/github-issue-filer.d.ts +62 -0
- package/dist/github-issue-filer.js +188 -0
- package/dist/github-issue-filer.js.map +1 -0
- package/dist/host-health-run.d.ts +24 -0
- package/dist/host-health-run.js +27 -0
- package/dist/host-health-run.js.map +1 -0
- package/dist/http-5xx-outcome.d.ts +6 -0
- package/dist/http-5xx-outcome.js +39 -0
- package/dist/http-5xx-outcome.js.map +1 -0
- package/dist/inbox-cli.d.ts +7 -0
- package/dist/inbox-cli.js +197 -0
- package/dist/inbox-cli.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/init-cli.d.ts +4 -0
- package/dist/init-cli.js +127 -0
- package/dist/init-cli.js.map +1 -0
- package/dist/init-keys.d.ts +19 -2
- package/dist/init-keys.js +15 -4
- package/dist/init-keys.js.map +1 -1
- package/dist/init-next-steps.d.ts +35 -0
- package/dist/init-next-steps.js +79 -0
- package/dist/init-next-steps.js.map +1 -0
- package/dist/invariants-file.d.ts +34 -0
- package/dist/invariants-file.js +68 -0
- package/dist/invariants-file.js.map +1 -0
- package/dist/invariants-validate.d.ts +30 -0
- package/dist/invariants-validate.js +105 -0
- package/dist/invariants-validate.js.map +1 -0
- package/dist/journey-annotate-api.d.ts +76 -0
- package/dist/journey-annotate-api.js +256 -0
- package/dist/journey-annotate-api.js.map +1 -0
- package/dist/journey-annotate-cli.d.ts +9 -0
- package/dist/journey-annotate-cli.js +179 -0
- package/dist/journey-annotate-cli.js.map +1 -0
- package/dist/journey-api.d.ts +79 -2
- package/dist/journey-api.js +136 -19
- package/dist/journey-api.js.map +1 -1
- package/dist/journey-cli.d.ts +4 -0
- package/dist/journey-cli.js +370 -0
- package/dist/journey-cli.js.map +1 -0
- package/dist/journey-demo-api.d.ts +112 -0
- package/dist/journey-demo-api.js +392 -0
- package/dist/journey-demo-api.js.map +1 -0
- package/dist/journey-demo-cli.d.ts +10 -0
- package/dist/journey-demo-cli.js +155 -0
- package/dist/journey-demo-cli.js.map +1 -0
- package/dist/kill-signal.d.ts +193 -0
- package/dist/kill-signal.js +284 -0
- package/dist/kill-signal.js.map +1 -0
- package/dist/launch-armed.d.ts +37 -0
- package/dist/launch-armed.js +31 -0
- package/dist/launch-armed.js.map +1 -0
- package/dist/ledger-api.d.ts +106 -0
- package/dist/ledger-api.js +237 -0
- package/dist/ledger-api.js.map +1 -0
- package/dist/ledger-cli.d.ts +18 -0
- package/dist/ledger-cli.js +107 -0
- package/dist/ledger-cli.js.map +1 -0
- package/dist/load-api.d.ts +29 -11
- package/dist/load-api.js +23 -15
- package/dist/load-api.js.map +1 -1
- package/dist/load-cli.d.ts +4 -0
- package/dist/load-cli.js +97 -0
- package/dist/load-cli.js.map +1 -0
- package/dist/log-correlation.d.ts +147 -0
- package/dist/log-correlation.js +325 -0
- package/dist/log-correlation.js.map +1 -0
- package/dist/log-lines.d.ts +131 -0
- package/dist/log-lines.js +389 -0
- package/dist/log-lines.js.map +1 -0
- package/dist/log-sources.d.ts +71 -0
- package/dist/log-sources.js +235 -0
- package/dist/log-sources.js.map +1 -0
- package/dist/logs-cli.d.ts +3 -0
- package/dist/logs-cli.js +51 -0
- package/dist/logs-cli.js.map +1 -0
- package/dist/logs-retention.d.ts +28 -0
- package/dist/logs-retention.js +84 -0
- package/dist/logs-retention.js.map +1 -0
- package/dist/mcp-api.d.ts +89 -1
- package/dist/mcp-api.js +472 -18
- package/dist/mcp-api.js.map +1 -1
- package/dist/mcp-args.d.ts +38 -0
- package/dist/mcp-args.js +131 -0
- package/dist/mcp-args.js.map +1 -0
- package/dist/mcp-cli-bridge.d.ts +34 -0
- package/dist/mcp-cli-bridge.js +62 -0
- package/dist/mcp-cli-bridge.js.map +1 -0
- package/dist/mcp-cli-runner.d.ts +29 -0
- package/dist/mcp-cli-runner.js +44 -0
- package/dist/mcp-cli-runner.js.map +1 -0
- package/dist/mcp-cli-tools.d.ts +125 -0
- package/dist/mcp-cli-tools.js +680 -0
- package/dist/mcp-cli-tools.js.map +1 -0
- package/dist/mcp-paths.d.ts +28 -0
- package/dist/mcp-paths.js +76 -0
- package/dist/mcp-paths.js.map +1 -0
- package/dist/mission-actors.d.ts +40 -0
- package/dist/mission-actors.js +72 -0
- package/dist/mission-actors.js.map +1 -0
- package/dist/mission-api.d.ts +35 -0
- package/dist/mission-api.js +58 -2
- package/dist/mission-api.js.map +1 -1
- package/dist/mission-cli.d.ts +4 -0
- package/dist/mission-cli.js +247 -0
- package/dist/mission-cli.js.map +1 -0
- package/dist/mission-exit.d.ts +22 -0
- package/dist/mission-exit.js +26 -0
- package/dist/mission-exit.js.map +1 -0
- package/dist/mission-fixtures.d.ts +225 -0
- package/dist/mission-fixtures.js +828 -0
- package/dist/mission-fixtures.js.map +1 -0
- package/dist/mission-journal.d.ts +60 -0
- package/dist/mission-journal.js +115 -0
- package/dist/mission-journal.js.map +1 -0
- package/dist/mission-queue-cli.d.ts +10 -0
- package/dist/mission-queue-cli.js +198 -0
- package/dist/mission-queue-cli.js.map +1 -0
- package/dist/mission-queue-runner.d.ts +126 -0
- package/dist/mission-queue-runner.js +397 -0
- package/dist/mission-queue-runner.js.map +1 -0
- package/dist/multi-run-cli.d.ts +37 -0
- package/dist/multi-run-cli.js +110 -0
- package/dist/multi-run-cli.js.map +1 -0
- package/dist/multi-run.d.ts +339 -0
- package/dist/multi-run.js +619 -0
- package/dist/multi-run.js.map +1 -0
- package/dist/openrouter-call.d.ts +22 -0
- package/dist/openrouter-call.js +63 -0
- package/dist/openrouter-call.js.map +1 -0
- package/dist/profile-cli.d.ts +4 -0
- package/dist/profile-cli.js +57 -0
- package/dist/profile-cli.js.map +1 -0
- package/dist/program.d.ts +3 -75
- package/dist/program.js +119 -1921
- package/dist/program.js.map +1 -1
- package/dist/project-dir.d.ts +96 -0
- package/dist/project-dir.js +220 -0
- package/dist/project-dir.js.map +1 -0
- package/dist/record-api.d.ts +10 -6
- package/dist/record-api.js +39 -28
- package/dist/record-api.js.map +1 -1
- package/dist/record-cli.d.ts +4 -0
- package/dist/record-cli.js +64 -0
- package/dist/record-cli.js.map +1 -0
- package/dist/recording-cli.d.ts +4 -0
- package/dist/recording-cli.js +248 -0
- package/dist/recording-cli.js.map +1 -0
- package/dist/regression-api.d.ts +101 -0
- package/dist/regression-api.js +384 -5
- package/dist/regression-api.js.map +1 -1
- package/dist/regression-cli.d.ts +4 -0
- package/dist/regression-cli.js +232 -0
- package/dist/regression-cli.js.map +1 -0
- package/dist/report-api.d.ts +146 -0
- package/dist/report-api.js +430 -0
- package/dist/report-api.js.map +1 -0
- package/dist/report-cli.d.ts +12 -0
- package/dist/report-cli.js +147 -0
- package/dist/report-cli.js.map +1 -0
- package/dist/result-schema.d.ts +22 -0
- package/dist/result-schema.js +28 -0
- package/dist/result-schema.js.map +1 -0
- package/dist/run-index.d.ts +29 -0
- package/dist/run-index.js +70 -0
- package/dist/run-index.js.map +1 -0
- package/dist/run-screenshots.d.ts +123 -0
- package/dist/run-screenshots.js +229 -0
- package/dist/run-screenshots.js.map +1 -0
- package/dist/runtime-env.d.ts +8 -0
- package/dist/runtime-env.js +22 -0
- package/dist/runtime-env.js.map +1 -0
- package/dist/secret-args.d.ts +23 -0
- package/dist/secret-args.js +45 -0
- package/dist/secret-args.js.map +1 -0
- package/dist/serve-cli.d.ts +4 -0
- package/dist/serve-cli.js +95 -0
- package/dist/serve-cli.js.map +1 -0
- package/dist/server-log-verify.d.ts +34 -0
- package/dist/server-log-verify.js +115 -0
- package/dist/server-log-verify.js.map +1 -0
- package/dist/session-check.d.ts +14 -0
- package/dist/session-check.js +44 -0
- package/dist/session-check.js.map +1 -0
- package/dist/site-cli.d.ts +4 -0
- package/dist/site-cli.js +127 -0
- package/dist/site-cli.js.map +1 -0
- package/dist/site-gate-cli.d.ts +28 -0
- package/dist/site-gate-cli.js +58 -0
- package/dist/site-gate-cli.js.map +1 -0
- package/dist/source-api.js +18 -3
- package/dist/source-api.js.map +1 -1
- package/dist/source-cli.d.ts +4 -0
- package/dist/source-cli.js +284 -0
- package/dist/source-cli.js.map +1 -0
- package/dist/source-run-api.d.ts +27 -1
- package/dist/source-run-api.js +22 -11
- package/dist/source-run-api.js.map +1 -1
- package/dist/storage-state-snapshot.d.ts +50 -0
- package/dist/storage-state-snapshot.js +87 -0
- package/dist/storage-state-snapshot.js.map +1 -0
- package/dist/suite-explore-options.d.ts +289 -0
- package/dist/suite-explore-options.js +140 -0
- package/dist/suite-explore-options.js.map +1 -0
- package/dist/target-config.d.ts +109 -0
- package/dist/target-config.js +216 -0
- package/dist/target-config.js.map +1 -0
- package/dist/transcript-file.d.ts +12 -0
- package/dist/transcript-file.js +23 -0
- package/dist/transcript-file.js.map +1 -0
- package/dist/ui-api.d.ts +3 -0
- package/dist/ui-api.js +7 -1
- package/dist/ui-api.js.map +1 -1
- package/dist/usability-capture.d.ts +43 -0
- package/dist/usability-capture.js +326 -0
- package/dist/usability-capture.js.map +1 -0
- package/dist/usage-config.d.ts +36 -0
- package/dist/usage-config.js +114 -0
- package/dist/usage-config.js.map +1 -0
- package/dist/ux-api.d.ts +3 -75
- package/dist/ux-api.js +3 -220
- package/dist/ux-api.js.map +1 -1
- package/dist/ux-cli.d.ts +4 -0
- package/dist/ux-cli.js +102 -0
- package/dist/ux-cli.js.map +1 -0
- package/dist/ux-config.d.ts +29 -0
- package/dist/ux-config.js +103 -0
- package/dist/ux-config.js.map +1 -0
- package/dist/ux-evidence.d.ts +54 -0
- package/dist/ux-evidence.js +197 -0
- package/dist/ux-evidence.js.map +1 -0
- package/dist/ux-review.d.ts +112 -0
- package/dist/ux-review.js +151 -0
- package/dist/ux-review.js.map +1 -0
- package/dist/ux-usability.d.ts +238 -0
- package/dist/ux-usability.js +494 -0
- package/dist/ux-usability.js.map +1 -0
- package/dist/verify-fix-api.d.ts +173 -0
- package/dist/verify-fix-api.js +454 -0
- package/dist/verify-fix-api.js.map +1 -0
- package/dist/verify-fix-cli.d.ts +4 -0
- package/dist/verify-fix-cli.js +110 -0
- package/dist/verify-fix-cli.js.map +1 -0
- package/package.json +19 -14
- package/skills/jevitate-ci-check/SKILL.md +77 -0
- package/skills/jevitate-demo/SKILL.md +61 -0
- package/skills/jevitate-explore/SKILL.md +88 -87
- package/skills/jevitate-getting-started/SKILL.md +88 -0
- package/skills/jevitate-load-test/SKILL.md +11 -2
- package/skills/jevitate-mission-scope/SKILL.md +19 -11
- package/skills/jevitate-record/SKILL.md +23 -5
- package/skills/jevitate-run-journey/SKILL.md +61 -17
- package/skills/jevitate-sources/SKILL.md +57 -14
- package/skills/jevitate-ux-review/SKILL.md +106 -16
- package/skills/jevitate-verify-fix/SKILL.md +76 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import type { UsageCounts, UsageLedger } from "@jevitate/ai-core";
|
|
2
|
+
import type { TranscriptEntry } from "@jevitate/explore";
|
|
3
|
+
import { type HostHealthSummary, type ResultStrategy } from "@jevitate/domain";
|
|
4
|
+
import { type EngineInfo } from "./engine.js";
|
|
5
|
+
/**
|
|
6
|
+
* Crash-safe termination (#94). `jevitate explore`/`explore --strategy usability` runs are commonly
|
|
7
|
+
* wrapped in an external timeout (`timeout -s TERM 900 jevitate explore ...`, the dogfooding
|
|
8
|
+
* harness's own convention) or killed by an operator. Before this module, SIGTERM/SIGINT killed the
|
|
9
|
+
* process outright — sometimes leaving NOTHING on disk to audit (no `.result.json`, even though the
|
|
10
|
+
* `MissionJournal` had already flushed every step's transcript/Recording as the run progressed).
|
|
11
|
+
*
|
|
12
|
+
* A single process-level handler is installed lazily, once, the first time any mission arms it
|
|
13
|
+
* (`armMissionKillSwitch`). On SIGTERM/SIGINT it writes a partial typed result — `outcome:
|
|
14
|
+
* "inconclusive"`, `reason: "interrupted by SIG… after N steps"` — built from whatever the journal
|
|
15
|
+
* already flushed, starts closing the shared browser pool, then exits with the conventional code
|
|
16
|
+
* (130 SIGINT / 143 SIGTERM) — all SYNCHRONOUSLY, in the same turn the signal arrives, without
|
|
17
|
+
* awaiting the browser close. Playwright installs its OWN SIGTERM/SIGINT handler on the browsers it
|
|
18
|
+
* launched; the instant that fires, any in-flight page operation starts rejecting, and the killed
|
|
19
|
+
* mission's own error handling can turn that into a normal (non-killed) result and call
|
|
20
|
+
* `process.exit` on its own terms — an `await` here, even a fast one, is enough of a window for
|
|
21
|
+
* that race to be won by the mission instead of by this handler. Writing synchronously
|
|
22
|
+
* (`writeFileSync`, already the case) and exiting before yielding the event loop makes this
|
|
23
|
+
* deterministic. Idempotent: a second signal (the wrapper/operator escalating) force-exits
|
|
24
|
+
* immediately.
|
|
25
|
+
*
|
|
26
|
+
* Several missions can be armed at once — one process may run many browser contexts on the shared
|
|
27
|
+
* pool (a daemon, a long-lived MCP server, a watch-mode queue drain). A signal flushes a partial
|
|
28
|
+
* result for EVERY armed mission, each with its own steps/usage, and tells each one's own scoped
|
|
29
|
+
* listener (`runWithMissionKillListener`) — never another mission's. Only then does the process exit,
|
|
30
|
+
* once. A mission's own outcome never exits the process: that is `process.exitCode`, set by the CLI
|
|
31
|
+
* command after the mission returns.
|
|
32
|
+
*/
|
|
33
|
+
export interface KillableMission {
|
|
34
|
+
/**
|
|
35
|
+
* #220: the strategy running (the unified result schema's `strategy`) and the scope it was
|
|
36
|
+
* authorized for (`target`) — so a killed run's partial result carries the same common fields as
|
|
37
|
+
* every other result. Optional only for library/test callers that arm a bare mission.
|
|
38
|
+
*/
|
|
39
|
+
readonly strategy?: ResultStrategy;
|
|
40
|
+
readonly target?: {
|
|
41
|
+
readonly seedUrl: string;
|
|
42
|
+
readonly allowlist: readonly string[];
|
|
43
|
+
readonly storageStatePath?: string;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* The mission's identity path — the same path handed to `MissionJournal` (a Recording path, or a
|
|
47
|
+
* report path for the usability strategy; the journal treats both identically).
|
|
48
|
+
* `transcriptPathFor`/`resultPathFor` are derived from it.
|
|
49
|
+
*/
|
|
50
|
+
readonly recordingPath: string;
|
|
51
|
+
/**
|
|
52
|
+
* Where the journal flushes the transcript, when it is NOT `transcriptPathFor(recordingPath)` — a
|
|
53
|
+
* usability run keys its transcript off its report (`usability-<stamp>.transcript.json`), not off
|
|
54
|
+
* its Recording (`usability-<stamp>.recording.json`) (#120).
|
|
55
|
+
*/
|
|
56
|
+
readonly transcriptPath?: string;
|
|
57
|
+
/**
|
|
58
|
+
* The live step list (the journal's last flushed entries) — read in preference to the file, so a
|
|
59
|
+
* killed run reports every step it took even when the file lags or lives elsewhere (#120).
|
|
60
|
+
*/
|
|
61
|
+
readonly transcript?: () => readonly TranscriptEntry[] | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* #245: the run's `--record-video` folder. A killed run lists the files already there as
|
|
64
|
+
* `videoPaths` — best effort: the signal cannot await a context close, so the last video may be
|
|
65
|
+
* truncated (the shared pool's close is started, not awaited).
|
|
66
|
+
*/
|
|
67
|
+
readonly videoDir?: string;
|
|
68
|
+
/** The run's host-health summary so far (#203): a killed run's result carries it like every other result. */
|
|
69
|
+
readonly hostHealth?: () => HostHealthSummary | undefined;
|
|
70
|
+
/** The run's usage tracker: the tokens already spent are part of the killed run's result (#120). */
|
|
71
|
+
readonly usage?: {
|
|
72
|
+
snapshot(): UsageCounts;
|
|
73
|
+
calls?(): ReturnType<UsageLedger["calls"]>;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Whatever partial report the mission has so far (e.g. a usability run's observed screens and
|
|
77
|
+
* screenshots). Read synchronously on the signal; absent/throwing means no partial report.
|
|
78
|
+
*/
|
|
79
|
+
readonly partialReport?: () => Record<string, unknown> | undefined;
|
|
80
|
+
/**
|
|
81
|
+
* `--save-storage-state` (#159): where to write the storageState JSON on a kill, and the mission's
|
|
82
|
+
* own `StorageStateSnapshotter.snapshot` — the last one captured while the session still looked
|
|
83
|
+
* authenticated (see `storage-state-snapshot.ts`). Read synchronously on the signal, like every
|
|
84
|
+
* other field here; `snapshot()` returning `undefined` (no safe capture yet) means nothing is
|
|
85
|
+
* written — an armed mission with no `--save-storage-state` omits this field entirely.
|
|
86
|
+
*/
|
|
87
|
+
readonly storageState?: {
|
|
88
|
+
readonly path: string;
|
|
89
|
+
readonly snapshot: () => string | undefined;
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* What a killed run prints to stdout before exiting (#120): the `--json` envelope, the human summary
|
|
94
|
+
* (a non-`--json` explore, #210), or nothing (a caller that owns stdout, e.g. the MCP server, or a
|
|
95
|
+
* library use). Set by the CLI command that armed the mission.
|
|
96
|
+
*/
|
|
97
|
+
export type KillSwitchOutput = "envelope" | "human" | "none";
|
|
98
|
+
declare const SIGNAL_EXIT_CODE: {
|
|
99
|
+
readonly SIGINT: 130;
|
|
100
|
+
readonly SIGTERM: 143;
|
|
101
|
+
};
|
|
102
|
+
export type KillSignal = keyof typeof SIGNAL_EXIT_CODE;
|
|
103
|
+
/** The seams a test fakes: nothing here touches the real process/filesystem/browser pool. */
|
|
104
|
+
export interface KillSwitchDeps {
|
|
105
|
+
readonly exit: (code: number) => void;
|
|
106
|
+
readonly closeBrowsers: () => Promise<void>;
|
|
107
|
+
readonly writeResult: (recordingPath: string, missionOutcome: string, exitCode: number, result: unknown, usage?: UsageLedger) => string;
|
|
108
|
+
readonly readTranscript: (transcriptPath: string) => {
|
|
109
|
+
steps: number;
|
|
110
|
+
transcript: readonly TranscriptEntry[];
|
|
111
|
+
};
|
|
112
|
+
readonly onSignal: (signal: KillSignal, handler: () => void) => void;
|
|
113
|
+
/** This build's identity, stamped on the killed run's result like every other result (#112). */
|
|
114
|
+
readonly engine?: () => EngineInfo;
|
|
115
|
+
/** SYNCHRONOUS stdout write — the process exits in the same turn, so nothing may be buffered. */
|
|
116
|
+
readonly writeStdout?: (text: string) => void;
|
|
117
|
+
/**
|
|
118
|
+
* `--save-storage-state` on a kill (#159): writes `json` to `path` SYNCHRONOUSLY, mode 0600.
|
|
119
|
+
* Defaults to `writeKillSnapshot` (a real `writeFileSync`); tests inject a fake here the same way
|
|
120
|
+
* they fake `writeResult`, instead of touching the real filesystem.
|
|
121
|
+
*/
|
|
122
|
+
readonly writeStorageStateSnapshot?: (path: string, json: string) => void;
|
|
123
|
+
}
|
|
124
|
+
type KilledListener = (killed: {
|
|
125
|
+
resultPath: string;
|
|
126
|
+
exitCode: number;
|
|
127
|
+
}) => void;
|
|
128
|
+
/** One mission a signal killed: its partial result and where it was written (when the write succeeded). */
|
|
129
|
+
export interface KilledMission {
|
|
130
|
+
readonly partial: Record<string, unknown>;
|
|
131
|
+
readonly resultPath?: string;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* #220: an orchestrator that owns stdout for the whole command (a `--repeat`/`--persona` multi-run)
|
|
135
|
+
* reports a kill ITSELF: told the signal and every killed mission's partial result, it writes its
|
|
136
|
+
* own partial aggregate SYNCHRONOUSLY and returns what to print — instead of each run's own envelope.
|
|
137
|
+
*/
|
|
138
|
+
export type KillSummary = (killed: {
|
|
139
|
+
signal: KillSignal;
|
|
140
|
+
exitCode: number;
|
|
141
|
+
missions: readonly KilledMission[];
|
|
142
|
+
}) => string | undefined;
|
|
143
|
+
/**
|
|
144
|
+
* Installs the process-level SIGTERM/SIGINT handler with no mission armed yet. The CLI entry point
|
|
145
|
+
* (`bin.ts`) calls this ONCE, before `buildProgram`/`parseAsync` — i.e. before ANY browser can have
|
|
146
|
+
* launched. This matters: Playwright installs its OWN SIGTERM/SIGINT handler on the browser it
|
|
147
|
+
* launches (to avoid orphaning the Chromium subprocess), and Node invokes same-signal listeners in
|
|
148
|
+
* REGISTRATION ORDER — so registering ours first guarantees this module's write-then-exit always
|
|
149
|
+
* runs (and completes: writing is synchronous, and `deps.exit` is called with nothing awaited
|
|
150
|
+
* in between) before Playwright's handler can tear down the browser and let an in-flight mission's
|
|
151
|
+
* own error handling race it to `process.exit` with a different result/code. A command that never
|
|
152
|
+
* arms a mission (`jevitate journey list`, etc.) still benefits: Ctrl-C exits with the conventional
|
|
153
|
+
* code instead of Node's default. Idempotent — a redundant call (or `armMissionKillSwitch` calling
|
|
154
|
+
* it again as a safety net) is a no-op.
|
|
155
|
+
*/
|
|
156
|
+
export declare function installMissionKillSwitch(deps?: KillSwitchDeps): void;
|
|
157
|
+
/**
|
|
158
|
+
* Arms the kill switch around a running mission: installs the process-level handler (as a safety
|
|
159
|
+
* net — the CLI entry point should already have via `installMissionKillSwitch`) and registers
|
|
160
|
+
* `mission` as the one to report on a kill signal. The caller's `finally` MUST call the returned
|
|
161
|
+
* disarm function, synchronously, before any `await` — the mission itself has (or is about to have)
|
|
162
|
+
* written its own typed result by then, so the process-level handler must stop treating a later,
|
|
163
|
+
* unrelated signal as belonging to this mission.
|
|
164
|
+
*/
|
|
165
|
+
export declare function armMissionKillSwitch(mission: KillableMission, deps?: KillSwitchDeps): () => void;
|
|
166
|
+
/**
|
|
167
|
+
* Runs `fn` so that any mission armed inside it (at any await depth) reports a kill to `listener` —
|
|
168
|
+
* and ONLY that mission does. This is how a runner that owns one mission among several running
|
|
169
|
+
* concurrently in the process (e.g. a queue-drain item) learns where ITS result went, never another's.
|
|
170
|
+
*/
|
|
171
|
+
export declare function runWithMissionKillListener<T>(listener: KilledListener, fn: () => Promise<T>): Promise<T>;
|
|
172
|
+
/** How many missions are armed right now (diagnostics and tests). */
|
|
173
|
+
export declare function armedMissionCount(): number;
|
|
174
|
+
/**
|
|
175
|
+
* What a killed run prints to stdout (#120). The CLI command that runs a mission sets it from its
|
|
176
|
+
* own flags (`--json` → the envelope) before arming; the default prints nothing.
|
|
177
|
+
*/
|
|
178
|
+
export declare function setKillSwitchOutput(mode: KillSwitchOutput): void;
|
|
179
|
+
/**
|
|
180
|
+
* #220: installs the kill summary of an orchestrator that owns the command's stdout (a multi-run):
|
|
181
|
+
* on SIGTERM/SIGINT it replaces the per-mission output. Returns the function that removes it.
|
|
182
|
+
*/
|
|
183
|
+
export declare function setKillSummary(fn: KillSummary): () => void;
|
|
184
|
+
/**
|
|
185
|
+
* Registers a process-wide SYNCHRONOUS listener told where EVERY killed mission's partial result was
|
|
186
|
+
* written, just before the process exits. A runner that owns one specific mission should use
|
|
187
|
+
* `runWithMissionKillListener` instead, so it never records another mission's result. Returns the
|
|
188
|
+
* unregister function.
|
|
189
|
+
*/
|
|
190
|
+
export declare function onMissionKilled(listener: KilledListener): () => void;
|
|
191
|
+
/** Test seam: resets all module state (a real run never needs this — one process, one exit). */
|
|
192
|
+
export declare function __resetKillSwitchForTests(): void;
|
|
193
|
+
export {};
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { readFileSync, writeSync } from "node:fs";
|
|
3
|
+
import { MISSION_RESULT_SCHEMA_VERSION } from "@jevitate/domain";
|
|
4
|
+
import { closeSharedBrowserPool } from "@jevitate/playwright";
|
|
5
|
+
import { currentEngineInfo } from "./engine.js";
|
|
6
|
+
import { ok } from "./envelope.js";
|
|
7
|
+
import { formatMissionHuman } from "./cli-output.js";
|
|
8
|
+
import { resultPathFor, writeMissionResult } from "./mission-journal.js";
|
|
9
|
+
import { transcriptPathFor } from "./transcript-file.js";
|
|
10
|
+
import { writeKillSnapshot } from "./storage-state-snapshot.js";
|
|
11
|
+
import { listVideos } from "./browser-run-options.js";
|
|
12
|
+
const SIGNAL_EXIT_CODE = { SIGINT: 130, SIGTERM: 143 };
|
|
13
|
+
/** Reads whatever the journal has already flushed; a run killed before its first step is 0 steps. */
|
|
14
|
+
function readTranscriptFile(transcriptPath) {
|
|
15
|
+
try {
|
|
16
|
+
const parsed = JSON.parse(readFileSync(transcriptPath, "utf8"));
|
|
17
|
+
if (Array.isArray(parsed))
|
|
18
|
+
return { steps: parsed.length, transcript: parsed };
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// Not on disk yet (killed before the first step flushed) — an honest 0-step report, not a crash.
|
|
22
|
+
}
|
|
23
|
+
return { steps: 0, transcript: [] };
|
|
24
|
+
}
|
|
25
|
+
const realDeps = {
|
|
26
|
+
exit: (code) => process.exit(code),
|
|
27
|
+
closeBrowsers: closeSharedBrowserPool,
|
|
28
|
+
writeResult: writeMissionResult,
|
|
29
|
+
readTranscript: readTranscriptFile,
|
|
30
|
+
onSignal: (signal, handler) => {
|
|
31
|
+
process.on(signal, handler);
|
|
32
|
+
},
|
|
33
|
+
engine: currentEngineInfo,
|
|
34
|
+
writeStdout: (text) => {
|
|
35
|
+
writeSync(1, text);
|
|
36
|
+
},
|
|
37
|
+
writeStorageStateSnapshot: writeKillSnapshot,
|
|
38
|
+
};
|
|
39
|
+
let installed = false;
|
|
40
|
+
/** Every armed mission, in arming order, with the kill listener scoped to the context that ran it. */
|
|
41
|
+
const armed = new Map();
|
|
42
|
+
let terminating = false;
|
|
43
|
+
let output = "none";
|
|
44
|
+
let summary;
|
|
45
|
+
/** Process-wide listeners: told about every killed mission. */
|
|
46
|
+
const killedListeners = new Set();
|
|
47
|
+
/** The listener for missions armed inside `runWithMissionKillListener` (e.g. one queue-drain item). */
|
|
48
|
+
const scopedListener = new AsyncLocalStorage();
|
|
49
|
+
/** A getter that throws (or a missing one) yields `undefined` — a flush must never block the exit. */
|
|
50
|
+
function safely(read) {
|
|
51
|
+
if (read === undefined)
|
|
52
|
+
return undefined;
|
|
53
|
+
try {
|
|
54
|
+
return read();
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/** The killed run's partial typed result: every field a finished run's result would carry that exists yet. */
|
|
61
|
+
function partialResult(mission, signal, code, deps) {
|
|
62
|
+
const transcriptPath = mission.transcriptPath ?? transcriptPathFor(mission.recordingPath);
|
|
63
|
+
// The live step list wins over the file (it is never behind it); the file is the fallback for a
|
|
64
|
+
// mission armed without a live reference.
|
|
65
|
+
const live = safely(mission.transcript);
|
|
66
|
+
const flushed = live === undefined ? deps.readTranscript(transcriptPath) : undefined;
|
|
67
|
+
const transcript = live ?? flushed?.transcript ?? [];
|
|
68
|
+
const steps = live === undefined ? (flushed?.steps ?? 0) : live.length;
|
|
69
|
+
const engine = safely(deps.engine);
|
|
70
|
+
const usage = safely(() => mission.usage?.snapshot());
|
|
71
|
+
const report = safely(mission.partialReport);
|
|
72
|
+
const hostHealth = safely(mission.hostHealth);
|
|
73
|
+
const videoDir = mission.videoDir;
|
|
74
|
+
const videoPaths = videoDir === undefined ? undefined : safely(() => listVideos(videoDir));
|
|
75
|
+
return {
|
|
76
|
+
// #220: the unified result schema's common fields (a kill is always the canonical `inconclusive`).
|
|
77
|
+
schemaVersion: MISSION_RESULT_SCHEMA_VERSION,
|
|
78
|
+
...(mission.strategy === undefined ? {} : { strategy: mission.strategy }),
|
|
79
|
+
outcome: "inconclusive",
|
|
80
|
+
missionOutcome: "inconclusive",
|
|
81
|
+
// #217: a goal run's own ending travels as `goalOutcome` (a kill is the shared `inconclusive`).
|
|
82
|
+
...(mission.strategy === "goal" ? { goalOutcome: "inconclusive" } : {}),
|
|
83
|
+
reason: `interrupted by ${signal} after ${steps} step${steps === 1 ? "" : "s"}`,
|
|
84
|
+
stop: "terminated",
|
|
85
|
+
signal,
|
|
86
|
+
steps,
|
|
87
|
+
exitCode: code,
|
|
88
|
+
defects: [],
|
|
89
|
+
hangs: [],
|
|
90
|
+
recordingPaths: [mission.recordingPath],
|
|
91
|
+
recordingPath: mission.recordingPath,
|
|
92
|
+
...(videoPaths === undefined ? {} : { videoPaths }),
|
|
93
|
+
transcriptPath,
|
|
94
|
+
resultPath: resultPathFor(mission.recordingPath),
|
|
95
|
+
...(mission.target === undefined ? {} : { target: { ...mission.target, allowlist: [...mission.target.allowlist] } }),
|
|
96
|
+
transcript,
|
|
97
|
+
...(engine === undefined ? {} : { engine }),
|
|
98
|
+
...(usage === undefined ? {} : { usage }),
|
|
99
|
+
...(report === undefined ? {} : { partialReport: report }),
|
|
100
|
+
...(hostHealth === undefined ? {} : { hostHealth }),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Synchronous by design (never `await`s before `deps.exit`): a killed mission's OWN in-flight
|
|
105
|
+
* `runExploration`/`runUsabilityMission`/etc. call is still running concurrently, and Playwright
|
|
106
|
+
* closes ITS launched browser on the same signal independently — the instant that happens, any
|
|
107
|
+
* pending page operation starts rejecting, and the mission's own error handling can turn that into
|
|
108
|
+
* a normal (non-killed) `crashed` result and call `process.exit` on its OWN terms. Racing that with
|
|
109
|
+
* an `await` here (even a fast one) is enough for the mission's own completion to win — so this
|
|
110
|
+
* writes the partial result (already synchronous: `writeFileSync`) and calls `deps.exit` in the
|
|
111
|
+
* SAME synchronous turn, before anything else gets a chance to run. Browser teardown is started but
|
|
112
|
+
* deliberately not awaited; a close that doesn't finish in time is left to the OS / Playwright's own
|
|
113
|
+
* process-group cleanup on the same signal.
|
|
114
|
+
*/
|
|
115
|
+
function onKillSignal(signal, deps) {
|
|
116
|
+
const code = SIGNAL_EXIT_CODE[signal];
|
|
117
|
+
if (terminating) {
|
|
118
|
+
// Escalation (a second signal, or the mission's own completion racing ahead): stop being
|
|
119
|
+
// graceful and exit right now.
|
|
120
|
+
deps.exit(code);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
terminating = true;
|
|
124
|
+
const killed = [];
|
|
125
|
+
for (const [mission, scoped] of [...armed]) {
|
|
126
|
+
let partial;
|
|
127
|
+
let resultPath;
|
|
128
|
+
try {
|
|
129
|
+
partial = partialResult(mission, signal, code, deps);
|
|
130
|
+
// #163: a killed run's per-call usage sidecar too (the calls it made before the signal).
|
|
131
|
+
const u = mission.usage;
|
|
132
|
+
const ledger = u?.calls === undefined ? undefined : { snapshot: () => u.snapshot(), calls: () => u.calls?.() ?? [] };
|
|
133
|
+
resultPath = deps.writeResult(mission.recordingPath, "inconclusive", code, partial, ledger);
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
// Best-effort: a failed flush must never keep the process from honoring the signal — nor keep
|
|
137
|
+
// the OTHER armed missions from being flushed.
|
|
138
|
+
}
|
|
139
|
+
// #159: `--save-storage-state` on a killed run — synchronous by construction (no `await`
|
|
140
|
+
// possible here, see this function's own doc comment): writes whatever the mission's
|
|
141
|
+
// `StorageStateSnapshotter` already holds (the last snapshot taken while the session still
|
|
142
|
+
// looked authenticated). Nothing is written when the mission has no `--save-storage-state`, or
|
|
143
|
+
// no safe snapshot was captured yet (e.g. killed before the first settled step).
|
|
144
|
+
try {
|
|
145
|
+
const snap = mission.storageState?.snapshot();
|
|
146
|
+
if (snap !== undefined)
|
|
147
|
+
(deps.writeStorageStateSnapshot ?? writeKillSnapshot)(mission.storageState.path, snap);
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
// Best-effort, like the flush above.
|
|
151
|
+
}
|
|
152
|
+
// Whoever ran THIS mission (e.g. its queue-drain item, #117) records where its result went —
|
|
153
|
+
// synchronously; process-wide listeners hear about every mission.
|
|
154
|
+
if (resultPath !== undefined) {
|
|
155
|
+
for (const listener of [...(scoped === undefined ? [] : [scoped]), ...killedListeners]) {
|
|
156
|
+
try {
|
|
157
|
+
listener({ resultPath, exitCode: code });
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
// Best-effort, like the flush.
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (partial !== undefined)
|
|
165
|
+
killed.push({ partial, ...(resultPath === undefined ? {} : { resultPath }) });
|
|
166
|
+
// A `--json` caller gets its envelope even from a killed run (#120) — one line per mission,
|
|
167
|
+
// written synchronously, before the exit below. An orchestrator's summary (#220) replaces them.
|
|
168
|
+
if (summary === undefined && partial !== undefined && output !== "none" && deps.writeStdout !== undefined) {
|
|
169
|
+
try {
|
|
170
|
+
deps.writeStdout(output === "envelope" ? `${JSON.stringify(ok(partial))}\n` : formatMissionHuman(partial));
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
// Best-effort, like the flush: stdout may already be gone (a closed pipe).
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
armed.clear();
|
|
178
|
+
// #220: the orchestrator's own partial aggregate — even when no mission was armed (killed between
|
|
179
|
+
// two runs of a multi-run) — written and printed synchronously, like everything above.
|
|
180
|
+
if (summary !== undefined) {
|
|
181
|
+
try {
|
|
182
|
+
const text = summary({ signal, exitCode: code, missions: killed });
|
|
183
|
+
if (text !== undefined && deps.writeStdout !== undefined)
|
|
184
|
+
deps.writeStdout(text);
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
// Best-effort, like the flush: a failed summary must never keep the process from exiting.
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
deps.closeBrowsers().catch(() => {
|
|
191
|
+
// Best-effort: a failed teardown must never keep the process from having honored the signal.
|
|
192
|
+
});
|
|
193
|
+
deps.exit(code);
|
|
194
|
+
}
|
|
195
|
+
function install(deps) {
|
|
196
|
+
if (installed)
|
|
197
|
+
return;
|
|
198
|
+
installed = true;
|
|
199
|
+
deps.onSignal("SIGTERM", () => onKillSignal("SIGTERM", deps));
|
|
200
|
+
deps.onSignal("SIGINT", () => onKillSignal("SIGINT", deps));
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Installs the process-level SIGTERM/SIGINT handler with no mission armed yet. The CLI entry point
|
|
204
|
+
* (`bin.ts`) calls this ONCE, before `buildProgram`/`parseAsync` — i.e. before ANY browser can have
|
|
205
|
+
* launched. This matters: Playwright installs its OWN SIGTERM/SIGINT handler on the browser it
|
|
206
|
+
* launches (to avoid orphaning the Chromium subprocess), and Node invokes same-signal listeners in
|
|
207
|
+
* REGISTRATION ORDER — so registering ours first guarantees this module's write-then-exit always
|
|
208
|
+
* runs (and completes: writing is synchronous, and `deps.exit` is called with nothing awaited
|
|
209
|
+
* in between) before Playwright's handler can tear down the browser and let an in-flight mission's
|
|
210
|
+
* own error handling race it to `process.exit` with a different result/code. A command that never
|
|
211
|
+
* arms a mission (`jevitate journey list`, etc.) still benefits: Ctrl-C exits with the conventional
|
|
212
|
+
* code instead of Node's default. Idempotent — a redundant call (or `armMissionKillSwitch` calling
|
|
213
|
+
* it again as a safety net) is a no-op.
|
|
214
|
+
*/
|
|
215
|
+
export function installMissionKillSwitch(deps = realDeps) {
|
|
216
|
+
install(deps);
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Arms the kill switch around a running mission: installs the process-level handler (as a safety
|
|
220
|
+
* net — the CLI entry point should already have via `installMissionKillSwitch`) and registers
|
|
221
|
+
* `mission` as the one to report on a kill signal. The caller's `finally` MUST call the returned
|
|
222
|
+
* disarm function, synchronously, before any `await` — the mission itself has (or is about to have)
|
|
223
|
+
* written its own typed result by then, so the process-level handler must stop treating a later,
|
|
224
|
+
* unrelated signal as belonging to this mission.
|
|
225
|
+
*/
|
|
226
|
+
export function armMissionKillSwitch(mission, deps = realDeps) {
|
|
227
|
+
install(deps);
|
|
228
|
+
armed.set(mission, scopedListener.getStore());
|
|
229
|
+
return () => {
|
|
230
|
+
armed.delete(mission);
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Runs `fn` so that any mission armed inside it (at any await depth) reports a kill to `listener` —
|
|
235
|
+
* and ONLY that mission does. This is how a runner that owns one mission among several running
|
|
236
|
+
* concurrently in the process (e.g. a queue-drain item) learns where ITS result went, never another's.
|
|
237
|
+
*/
|
|
238
|
+
export function runWithMissionKillListener(listener, fn) {
|
|
239
|
+
return scopedListener.run(listener, fn);
|
|
240
|
+
}
|
|
241
|
+
/** How many missions are armed right now (diagnostics and tests). */
|
|
242
|
+
export function armedMissionCount() {
|
|
243
|
+
return armed.size;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* What a killed run prints to stdout (#120). The CLI command that runs a mission sets it from its
|
|
247
|
+
* own flags (`--json` → the envelope) before arming; the default prints nothing.
|
|
248
|
+
*/
|
|
249
|
+
export function setKillSwitchOutput(mode) {
|
|
250
|
+
output = mode;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* #220: installs the kill summary of an orchestrator that owns the command's stdout (a multi-run):
|
|
254
|
+
* on SIGTERM/SIGINT it replaces the per-mission output. Returns the function that removes it.
|
|
255
|
+
*/
|
|
256
|
+
export function setKillSummary(fn) {
|
|
257
|
+
summary = fn;
|
|
258
|
+
return () => {
|
|
259
|
+
if (summary === fn)
|
|
260
|
+
summary = undefined;
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Registers a process-wide SYNCHRONOUS listener told where EVERY killed mission's partial result was
|
|
265
|
+
* written, just before the process exits. A runner that owns one specific mission should use
|
|
266
|
+
* `runWithMissionKillListener` instead, so it never records another mission's result. Returns the
|
|
267
|
+
* unregister function.
|
|
268
|
+
*/
|
|
269
|
+
export function onMissionKilled(listener) {
|
|
270
|
+
killedListeners.add(listener);
|
|
271
|
+
return () => {
|
|
272
|
+
killedListeners.delete(listener);
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
/** Test seam: resets all module state (a real run never needs this — one process, one exit). */
|
|
276
|
+
export function __resetKillSwitchForTests() {
|
|
277
|
+
installed = false;
|
|
278
|
+
armed.clear();
|
|
279
|
+
terminating = false;
|
|
280
|
+
output = "none";
|
|
281
|
+
summary = undefined;
|
|
282
|
+
killedListeners.clear();
|
|
283
|
+
}
|
|
284
|
+
//# sourceMappingURL=kill-signal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kill-signal.js","sourceRoot":"","sources":["../src/kill-signal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAGlD,OAAO,EAAE,6BAA6B,EAA+C,MAAM,kBAAkB,CAAC;AAC9G,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAmB,MAAM,aAAa,CAAC;AACjE,OAAO,EAAE,EAAE,EAAE,MAAM,eAAe,CAAC;AACnC,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AACzE,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AA2FtD,MAAM,gBAAgB,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAW,CAAC;AAsBhE,qGAAqG;AACrG,SAAS,kBAAkB,CAAC,cAAsB;IAChD,IAAI,CAAC;QACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC,CAAC;QACzE,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;YAAE,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,MAA2B,EAAE,CAAC;IACtG,CAAC;IAAC,MAAM,CAAC;QACP,iGAAiG;IACnG,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC;AACtC,CAAC;AAED,MAAM,QAAQ,GAAmB;IAC/B,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;IAClC,aAAa,EAAE,sBAAsB;IACrC,WAAW,EAAE,kBAAkB;IAC/B,cAAc,EAAE,kBAAkB;IAClC,QAAQ,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE;QAC5B,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,CAAC;IACD,MAAM,EAAE,iBAAiB;IACzB,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE;QACpB,SAAS,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;IACrB,CAAC;IACD,yBAAyB,EAAE,iBAAiB;CAC7C,CAAC;AAiBF,IAAI,SAAS,GAAG,KAAK,CAAC;AACtB,sGAAsG;AACtG,MAAM,KAAK,GAAG,IAAI,GAAG,EAA+C,CAAC;AACrE,IAAI,WAAW,GAAG,KAAK,CAAC;AACxB,IAAI,MAAM,GAAqB,MAAM,CAAC;AACtC,IAAI,OAAgC,CAAC;AACrC,+DAA+D;AAC/D,MAAM,eAAe,GAAG,IAAI,GAAG,EAAkB,CAAC;AAClD,uGAAuG;AACvG,MAAM,cAAc,GAAG,IAAI,iBAAiB,EAAkB,CAAC;AAE/D,sGAAsG;AACtG,SAAS,MAAM,CAAI,IAA2B;IAC5C,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACzC,IAAI,CAAC;QACH,OAAO,IAAI,EAAE,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,8GAA8G;AAC9G,SAAS,aAAa,CAAC,OAAwB,EAAE,MAAkB,EAAE,IAAY,EAAE,IAAoB;IACrG,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,IAAI,iBAAiB,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IAC1F,gGAAgG;IAChG,0CAA0C;IAC1C,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IACxC,MAAM,OAAO,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACrF,MAAM,UAAU,GAAG,IAAI,IAAI,OAAO,EAAE,UAAU,IAAI,EAAE,CAAC;IACrD,MAAM,KAAK,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC;IACvE,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACnC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;IACtD,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IAC7C,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC9C,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;IAClC,MAAM,UAAU,GAAG,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC3F,OAAO;QACL,mGAAmG;QACnG,aAAa,EAAE,6BAA6B;QAC5C,GAAG,CAAC,OAAO,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC;QACzE,OAAO,EAAE,cAAc;QACvB,cAAc,EAAE,cAAc;QAC9B,gGAAgG;QAChG,GAAG,CAAC,OAAO,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACvE,MAAM,EAAE,kBAAkB,MAAM,UAAU,KAAK,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE;QAC/E,IAAI,EAAE,YAAY;QAClB,MAAM;QACN,KAAK;QACL,QAAQ,EAAE,IAAI;QACd,OAAO,EAAE,EAAE;QACX,KAAK,EAAE,EAAE;QACT,cAAc,EAAE,CAAC,OAAO,CAAC,aAAa,CAAC;QACvC,aAAa,EAAE,OAAO,CAAC,aAAa;QACpC,GAAG,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC;QACnD,cAAc;QACd,UAAU,EAAE,aAAa,CAAC,OAAO,CAAC,aAAa,CAAC;QAChD,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,EAAE,CAAC;QACpH,UAAU;QACV,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC;QAC3C,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC;QACzC,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,MAAM,EAAE,CAAC;QAC1D,GAAG,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC;KACpD,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAS,YAAY,CAAC,MAAkB,EAAE,IAAoB;IAC5D,MAAM,IAAI,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;IACtC,IAAI,WAAW,EAAE,CAAC;QAChB,yFAAyF;QACzF,+BAA+B;QAC/B,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChB,OAAO;IACT,CAAC;IACD,WAAW,GAAG,IAAI,CAAC;IACnB,MAAM,MAAM,GAAoB,EAAE,CAAC;IACnC,KAAK,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,EAAE,CAAC;QAC3C,IAAI,OAA4C,CAAC;QACjD,IAAI,UAA8B,CAAC;QACnC,IAAI,CAAC;YACH,OAAO,GAAG,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;YACrD,yFAAyF;YACzF,MAAM,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC;YACxB,MAAM,MAAM,GAA4B,CAAC,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC;YAC9I,UAAU,GAAG,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,aAAa,EAAE,cAAc,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;QAC9F,CAAC;QAAC,MAAM,CAAC;YACP,8FAA8F;YAC9F,+CAA+C;QACjD,CAAC;QACD,yFAAyF;QACzF,qFAAqF;QACrF,2FAA2F;QAC3F,+FAA+F;QAC/F,iFAAiF;QACjF,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,OAAO,CAAC,YAAY,EAAE,QAAQ,EAAE,CAAC;YAC9C,IAAI,IAAI,KAAK,SAAS;gBAAE,CAAC,IAAI,CAAC,yBAAyB,IAAI,iBAAiB,CAAC,CAAC,OAAO,CAAC,YAAa,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAClH,CAAC;QAAC,MAAM,CAAC;YACP,qCAAqC;QACvC,CAAC;QACD,6FAA6F;QAC7F,kEAAkE;QAClE,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;YAC7B,KAAK,MAAM,QAAQ,IAAI,CAAC,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,eAAe,CAAC,EAAE,CAAC;gBACvF,IAAI,CAAC;oBACH,QAAQ,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;gBAC3C,CAAC;gBAAC,MAAM,CAAC;oBACP,+BAA+B;gBACjC,CAAC;YACH,CAAC;QACH,CAAC;QACD,IAAI,OAAO,KAAK,SAAS;YAAE,MAAM,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC,CAAC;QACzG,4FAA4F;QAC5F,gGAAgG;QAChG,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,SAAS,IAAI,MAAM,KAAK,MAAM,IAAI,IAAI,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;YAC1G,IAAI,CAAC;gBACH,IAAI,CAAC,WAAW,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,CAAC;YAC7G,CAAC;YAAC,MAAM,CAAC;gBACP,2EAA2E;YAC7E,CAAC;QACH,CAAC;IACH,CAAC;IACD,KAAK,CAAC,KAAK,EAAE,CAAC;IACd,kGAAkG;IAClG,uFAAuF;IACvF,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,OAAO,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;YACnE,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,WAAW,KAAK,SAAS;gBAAE,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QACnF,CAAC;QAAC,MAAM,CAAC;YACP,0FAA0F;QAC5F,CAAC;IACH,CAAC;IACD,IAAI,CAAC,aAAa,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE;QAC9B,6FAA6F;IAC/F,CAAC,CAAC,CAAC;IACH,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAClB,CAAC;AAED,SAAS,OAAO,CAAC,IAAoB;IACnC,IAAI,SAAS;QAAE,OAAO;IACtB,SAAS,GAAG,IAAI,CAAC;IACjB,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC;IAC9D,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC;AAC9D,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,wBAAwB,CAAC,OAAuB,QAAQ;IACtE,OAAO,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAwB,EAAE,OAAuB,QAAQ;IAC5F,OAAO,CAAC,IAAI,CAAC,CAAC;IACd,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,cAAc,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC9C,OAAO,GAAG,EAAE;QACV,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACxB,CAAC,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,0BAA0B,CAAI,QAAwB,EAAE,EAAoB;IAC1F,OAAO,cAAc,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;AAC1C,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,iBAAiB;IAC/B,OAAO,KAAK,CAAC,IAAI,CAAC;AACpB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAsB;IACxD,MAAM,GAAG,IAAI,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,EAAe;IAC5C,OAAO,GAAG,EAAE,CAAC;IACb,OAAO,GAAG,EAAE;QACV,IAAI,OAAO,KAAK,EAAE;YAAE,OAAO,GAAG,SAAS,CAAC;IAC1C,CAAC,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,QAAwB;IACtD,eAAe,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC9B,OAAO,GAAG,EAAE;QACV,eAAe,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACnC,CAAC,CAAC;AACJ,CAAC;AAED,gGAAgG;AAChG,MAAM,UAAU,yBAAyB;IACvC,SAAS,GAAG,KAAK,CAAC;IAClB,KAAK,CAAC,KAAK,EAAE,CAAC;IACd,WAAW,GAAG,KAAK,CAAC;IACpB,MAAM,GAAG,MAAM,CAAC;IAChB,OAAO,GAAG,SAAS,CAAC;IACpB,eAAe,CAAC,KAAK,EAAE,CAAC;AAC1B,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { HostHealthSampler } from "@jevitate/explore";
|
|
2
|
+
import type { HostHealthSummary } from "@jevitate/domain";
|
|
3
|
+
import { type KillableMission } from "./kill-signal.js";
|
|
4
|
+
import { StorageStateSnapshotter } from "./storage-state-snapshot.js";
|
|
5
|
+
/**
|
|
6
|
+
* #226: what an armed mission's kill-switch hooks read before the host sampler and the browser exist:
|
|
7
|
+
* `undefined` until the launch finished, the live values after.
|
|
8
|
+
*/
|
|
9
|
+
export interface ArmedHooks {
|
|
10
|
+
readonly hostHealth: () => HostHealthSummary | undefined;
|
|
11
|
+
readonly snapshot: () => string | undefined;
|
|
12
|
+
}
|
|
13
|
+
export interface LaunchArmedOptions<S> {
|
|
14
|
+
/** The mission the kill switch reports on — built from `hooks`, never from the (not yet open) session. */
|
|
15
|
+
readonly mission: (hooks: ArmedHooks) => KillableMission;
|
|
16
|
+
/** Test seam (#203): the run's host-health sampler. Default: this host's. */
|
|
17
|
+
readonly hostHealth?: HostHealthSampler;
|
|
18
|
+
/** `--save-storage-state` was given: the snapshotter captures after settled steps. */
|
|
19
|
+
readonly saveStorageState: boolean;
|
|
20
|
+
/** Opens the run's browser session. */
|
|
21
|
+
readonly open: () => Promise<S>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* THE way a mission runner launches its browser (#226): the kill switch is armed FIRST — before the
|
|
25
|
+
* host sampler and the browser launch (Chromium can take seconds to start on a loaded host) — so a
|
|
26
|
+
* SIGTERM/SIGINT at any point from here writes and prints the partial result instead of exiting
|
|
27
|
+
* 143/130 with nothing. A failed launch stops the sampler and disarms the switch before rethrowing
|
|
28
|
+
* (the command reports that error itself).
|
|
29
|
+
*/
|
|
30
|
+
export declare function launchArmed<S extends {
|
|
31
|
+
captureStorageState?(): Promise<string>;
|
|
32
|
+
}>(opts: LaunchArmedOptions<S>): Promise<{
|
|
33
|
+
disarmKillSwitch: () => void;
|
|
34
|
+
health: HostHealthSampler;
|
|
35
|
+
session: S;
|
|
36
|
+
snapshotter: StorageStateSnapshotter;
|
|
37
|
+
}>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { startHostHealth } from "./host-health-run.js";
|
|
2
|
+
import { armMissionKillSwitch } from "./kill-signal.js";
|
|
3
|
+
import { StorageStateSnapshotter } from "./storage-state-snapshot.js";
|
|
4
|
+
/**
|
|
5
|
+
* THE way a mission runner launches its browser (#226): the kill switch is armed FIRST — before the
|
|
6
|
+
* host sampler and the browser launch (Chromium can take seconds to start on a loaded host) — so a
|
|
7
|
+
* SIGTERM/SIGINT at any point from here writes and prints the partial result instead of exiting
|
|
8
|
+
* 143/130 with nothing. A failed launch stops the sampler and disarms the switch before rethrowing
|
|
9
|
+
* (the command reports that error itself).
|
|
10
|
+
*/
|
|
11
|
+
export async function launchArmed(opts) {
|
|
12
|
+
let health;
|
|
13
|
+
let snapshotter;
|
|
14
|
+
const disarmKillSwitch = armMissionKillSwitch(opts.mission({ hostHealth: () => health?.summary(), snapshot: () => snapshotter?.snapshot() }));
|
|
15
|
+
let started;
|
|
16
|
+
try {
|
|
17
|
+
started = await startHostHealth(opts.hostHealth);
|
|
18
|
+
const session = await opts.open();
|
|
19
|
+
// #159: refreshed after each settled step; the kill switch writes whatever this holds synchronously.
|
|
20
|
+
const snap = new StorageStateSnapshotter(session, opts.saveStorageState);
|
|
21
|
+
health = started;
|
|
22
|
+
snapshotter = snap;
|
|
23
|
+
return { disarmKillSwitch, health: started, session, snapshotter: snap };
|
|
24
|
+
}
|
|
25
|
+
catch (e) {
|
|
26
|
+
started?.stop();
|
|
27
|
+
disarmKillSwitch();
|
|
28
|
+
throw e;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=launch-armed.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"launch-armed.js","sourceRoot":"","sources":["../src/launch-armed.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAwB,MAAM,kBAAkB,CAAC;AAC9E,OAAO,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AAsBtE;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,IAA2B;IAE3B,IAAI,MAAqC,CAAC;IAC1C,IAAI,WAAgD,CAAC;IACrD,MAAM,gBAAgB,GAAG,oBAAoB,CAC3C,IAAI,CAAC,OAAO,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,WAAW,EAAE,QAAQ,EAAE,EAAE,CAAC,CAC/F,CAAC;IACF,IAAI,OAAsC,CAAC;IAC3C,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,eAAe,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACjD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAClC,qGAAqG;QACrG,MAAM,IAAI,GAAG,IAAI,uBAAuB,CAAC,OAAO,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAC;QACzE,MAAM,GAAG,OAAO,CAAC;QACjB,WAAW,GAAG,IAAI,CAAC;QACnB,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;IAC3E,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,EAAE,IAAI,EAAE,CAAC;QAChB,gBAAgB,EAAE,CAAC;QACnB,MAAM,CAAC,CAAC;IACV,CAAC;AACH,CAAC"}
|