@hellolynk-ai/codex-plugin 0.0.0-stage → 0.2.99

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/.agents/plugins/marketplace.json +20 -0
  2. package/LICENSE +92 -0
  3. package/README.md +86 -2
  4. package/bin/hellolynk-client-setup.mjs +2748 -0
  5. package/bin/hellolynk-mcp-config-shape.mjs +23 -0
  6. package/hellolynk-client-pack-manifest.json +619 -0
  7. package/hellolynk-client-pack-pin.json +4 -0
  8. package/package.json +58 -4
  9. package/plugins/hellolynk-codex/.app.json +3 -0
  10. package/plugins/hellolynk-codex/.codex-plugin/plugin.json +38 -0
  11. package/plugins/hellolynk-codex/.mcp.json +19 -0
  12. package/plugins/hellolynk-codex/assets/hellolynk-mark.png +0 -0
  13. package/plugins/hellolynk-codex/package.json +11 -0
  14. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-client-account.mjs +180 -0
  15. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-client-core.mjs +770 -0
  16. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-client-profile.mjs +191 -0
  17. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-host-auth.mjs +347 -0
  18. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-host-protected-store.mjs +2721 -0
  19. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-local-process-lock.mjs +157 -0
  20. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-neutral-host-installation-store.mjs +765 -0
  21. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-platform-credential-store.mjs +773 -0
  22. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-runtime-safety.cjs +471 -0
  23. package/plugins/hellolynk-codex/runtime/bootstrap/platform/windows/hellolynk-host-protected-store.ps1 +2866 -0
  24. package/plugins/hellolynk-codex/runtime/connect-activate.mjs +837 -0
  25. package/plugins/hellolynk-codex/runtime/core/hellolynk-agent-configurator.mjs +701 -0
  26. package/plugins/hellolynk-codex/runtime/core/hellolynk-client-account.mjs +180 -0
  27. package/plugins/hellolynk-codex/runtime/core/hellolynk-client-core.mjs +770 -0
  28. package/plugins/hellolynk-codex/runtime/core/hellolynk-client-profile.mjs +191 -0
  29. package/plugins/hellolynk-codex/runtime/core/hellolynk-client-update-entry.mjs +31 -0
  30. package/plugins/hellolynk-codex/runtime/core/hellolynk-client-update-runtime.mjs +600 -0
  31. package/plugins/hellolynk-codex/runtime/core/hellolynk-client-update.mjs +453 -0
  32. package/plugins/hellolynk-codex/runtime/core/hellolynk-contact-request-e2ee.mjs +867 -0
  33. package/plugins/hellolynk-codex/runtime/core/hellolynk-device-enrollment.mjs +435 -0
  34. package/plugins/hellolynk-codex/runtime/core/hellolynk-direct-text-e2ee-client.mjs +1964 -0
  35. package/plugins/hellolynk-codex/runtime/core/hellolynk-direct-text-e2ee.mjs +1207 -0
  36. package/plugins/hellolynk-codex/runtime/core/hellolynk-host-auth.mjs +347 -0
  37. package/plugins/hellolynk-codex/runtime/core/hellolynk-host-protected-store.mjs +2721 -0
  38. package/plugins/hellolynk-codex/runtime/core/hellolynk-host-transport-client.mjs +946 -0
  39. package/plugins/hellolynk-codex/runtime/core/hellolynk-host.mjs +2374 -0
  40. package/plugins/hellolynk-codex/runtime/core/hellolynk-human-confirmation-ledger.mjs +256 -0
  41. package/plugins/hellolynk-codex/runtime/core/hellolynk-human-relationship-controller.mjs +1234 -0
  42. package/plugins/hellolynk-codex/runtime/core/hellolynk-inbox-read-state.mjs +11 -0
  43. package/plugins/hellolynk-codex/runtime/core/hellolynk-indexed-message-store.mjs +1558 -0
  44. package/plugins/hellolynk-codex/runtime/core/hellolynk-instance-conversation.mjs +185 -0
  45. package/plugins/hellolynk-codex/runtime/core/hellolynk-local-contact-requests.mjs +1733 -0
  46. package/plugins/hellolynk-codex/runtime/core/hellolynk-local-contacts.mjs +1228 -0
  47. package/plugins/hellolynk-codex/runtime/core/hellolynk-local-data-migration.mjs +766 -0
  48. package/plugins/hellolynk-codex/runtime/core/hellolynk-local-data-protection.mjs +747 -0
  49. package/plugins/hellolynk-codex/runtime/core/hellolynk-local-identity-trust.mjs +274 -0
  50. package/plugins/hellolynk-codex/runtime/core/hellolynk-local-messaging.mjs +3043 -0
  51. package/plugins/hellolynk-codex/runtime/core/hellolynk-local-peer-transport.mjs +103 -0
  52. package/plugins/hellolynk-codex/runtime/core/hellolynk-local-process-lock.mjs +157 -0
  53. package/plugins/hellolynk-codex/runtime/core/hellolynk-model-inbox.mjs +230 -0
  54. package/plugins/hellolynk-codex/runtime/core/hellolynk-model-notices.cjs +60 -0
  55. package/plugins/hellolynk-codex/runtime/core/hellolynk-native-addressing-tools.cjs +231 -0
  56. package/plugins/hellolynk-codex/runtime/core/hellolynk-native-conversations.mjs +280 -0
  57. package/plugins/hellolynk-codex/runtime/core/hellolynk-native-human-confirmation-broker.mjs +1036 -0
  58. package/plugins/hellolynk-codex/runtime/core/hellolynk-native-inbox-runtime.mjs +553 -0
  59. package/plugins/hellolynk-codex/runtime/core/hellolynk-native-notice.mjs +50 -0
  60. package/plugins/hellolynk-codex/runtime/core/hellolynk-native-reply-routes.mjs +96 -0
  61. package/plugins/hellolynk-codex/runtime/core/hellolynk-native-worker-lease.mjs +84 -0
  62. package/plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-assembly.mjs +955 -0
  63. package/plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-installation-store.mjs +765 -0
  64. package/plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-launcher.mjs +665 -0
  65. package/plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-process.mjs +4374 -0
  66. package/plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-store-adapters.mjs +548 -0
  67. package/plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-workbench-read-model.mjs +1365 -0
  68. package/plugins/hellolynk-codex/runtime/core/hellolynk-platform-credential-store.mjs +773 -0
  69. package/plugins/hellolynk-codex/runtime/core/hellolynk-relay-client-cli.mjs +232 -0
  70. package/plugins/hellolynk-codex/runtime/core/hellolynk-release-retention.mjs +160 -0
  71. package/plugins/hellolynk-codex/runtime/core/hellolynk-runtime-safety.cjs +471 -0
  72. package/plugins/hellolynk-codex/runtime/core/hellolynk-workbench-action-grants.mjs +457 -0
  73. package/plugins/hellolynk-codex/runtime/core/hellolynk-workbench-connection.mjs +234 -0
  74. package/plugins/hellolynk-codex/runtime/neutral-host-bootstrap.mjs +64 -0
  75. package/plugins/hellolynk-codex/runtime/neutral-host-release-entry.mjs +70 -0
  76. package/plugins/hellolynk-codex/runtime/neutral-host-release-loader.mjs +156 -0
  77. package/plugins/hellolynk-codex/runtime/neutral-host-release-manifest.json +358 -0
  78. package/plugins/hellolynk-codex/runtime/neutral-host-release-pin.json +4 -0
  79. package/plugins/hellolynk-codex/runtime/platform/windows/configure-agent.ps1 +281 -0
  80. package/plugins/hellolynk-codex/runtime/platform/windows/credential-store.ps1 +844 -0
  81. package/plugins/hellolynk-codex/runtime/platform/windows/hellolynk-host-protected-store.ps1 +2866 -0
  82. package/plugins/hellolynk-codex/runtime/platform/windows/hellolynk-human-confirmation-ledger.ps1 +155 -0
  83. package/plugins/hellolynk-codex/runtime/platform/windows/hellolynk-local-data-key.ps1 +300 -0
  84. package/plugins/hellolynk-codex/runtime/platform/windows/hellolynk-native-human-confirmation-broker.ps1 +216 -0
  85. package/plugins/hellolynk-codex/runtime/workbench/candidate-bootstrap.mjs +54 -0
  86. package/plugins/hellolynk-codex/runtime/workbench/contract.mjs +1314 -0
  87. package/plugins/hellolynk-codex/runtime/workbench/hellolynk-workbench-build.json +215 -0
  88. package/plugins/hellolynk-codex/runtime/workbench/host-capabilities.mjs +80 -0
  89. package/plugins/hellolynk-codex/runtime/workbench/i18n.mjs +1127 -0
  90. package/plugins/hellolynk-codex/runtime/workbench/loopback-surface.mjs +740 -0
  91. package/plugins/hellolynk-codex/runtime/workbench/mcp-app-bundle.mjs +97 -0
  92. package/plugins/hellolynk-codex/runtime/workbench/service.mjs +485 -0
  93. package/plugins/hellolynk-codex/runtime/workbench/state.mjs +421 -0
  94. package/plugins/hellolynk-codex/runtime/workbench/transports.mjs +171 -0
  95. package/plugins/hellolynk-codex/runtime/workbench/ui/app.mjs +6120 -0
  96. package/plugins/hellolynk-codex/runtime/workbench/ui/connection-form.mjs +155 -0
  97. package/plugins/hellolynk-codex/runtime/workbench/ui/hellolynk-mark.png +0 -0
  98. package/plugins/hellolynk-codex/runtime/workbench/ui/http-transport.mjs +247 -0
  99. package/plugins/hellolynk-codex/runtime/workbench/ui/icons/arrow-left.svg +3 -0
  100. package/plugins/hellolynk-codex/runtime/workbench/ui/icons/close.svg +3 -0
  101. package/plugins/hellolynk-codex/runtime/workbench/ui/icons/contacts.svg +3 -0
  102. package/plugins/hellolynk-codex/runtime/workbench/ui/icons/inbox.svg +3 -0
  103. package/plugins/hellolynk-codex/runtime/workbench/ui/icons/refresh.svg +3 -0
  104. package/plugins/hellolynk-codex/runtime/workbench/ui/icons/search.svg +3 -0
  105. package/plugins/hellolynk-codex/runtime/workbench/ui/icons/security.svg +3 -0
  106. package/plugins/hellolynk-codex/runtime/workbench/ui/icons/settings.svg +3 -0
  107. package/plugins/hellolynk-codex/runtime/workbench/ui/index.html +139 -0
  108. package/plugins/hellolynk-codex/runtime/workbench/ui/loopback-entry.mjs +26 -0
  109. package/plugins/hellolynk-codex/runtime/workbench/ui/mcp-app-client.mjs +151 -0
  110. package/plugins/hellolynk-codex/runtime/workbench/ui/mcp-app-entry.mjs +452 -0
  111. package/plugins/hellolynk-codex/runtime/workbench/ui/styles.css +686 -0
  112. package/plugins/hellolynk-codex/server/codex-neutral-production.mjs +520 -0
  113. package/plugins/hellolynk-codex/server/mcp-proxy.mjs +306 -0
  114. package/plugins/hellolynk-codex/server/mcp-server.mjs +1193 -0
  115. package/plugins/hellolynk-codex/server/native-conversation-directory.mjs +33 -0
  116. package/plugins/hellolynk-codex/server/native-reply-routes.mjs +32 -0
  117. package/plugins/hellolynk-codex/server/native-task-client.mjs +382 -0
  118. package/plugins/hellolynk-codex/server/native-task-runtime.mjs +8 -0
  119. package/plugins/hellolynk-codex/server/neutral-host-adapter.mjs +649 -0
  120. package/plugins/hellolynk-codex/server/workbench-runtime.mjs +180 -0
  121. package/plugins/hellolynk-codex/skills/hellolynk-open/SKILL.md +11 -0
  122. package/plugins/hellolynk-codex/skills/hellolynk-open/agents/openai.yaml +4 -0
  123. package/tools/hellolynk-activate-neutral-host-clients.mjs +837 -0
@@ -0,0 +1,665 @@
1
+ import crypto from "node:crypto";
2
+ import { spawn } from "node:child_process";
3
+ import fs from "node:fs/promises";
4
+ import path from "node:path";
5
+ import runtimeSafety from "./hellolynk-runtime-safety.cjs";
6
+
7
+ const { standaloneHelloLynkNodeEnvironment, resolveHelloLynkStandaloneNode, assertHelloLynkClientNodeVersion, assertHelloLynkPowerShell7 } = runtimeSafety;
8
+
9
+ export const HELLOLYNK_NEUTRAL_HOST_LAUNCH_FAILURE_CONTRACT = "hellolynk-neutral-host-launch-failure-v1";
10
+ const HELLOLYNK_NEUTRAL_HOST_LAUNCH_ATTEMPT_CONTRACT = "hellolynk-neutral-host-launch-attempt-v1";
11
+
12
+ const RUNNER_RELATIVE = "runtime/neutral-host-bootstrap.mjs";
13
+ const BOOTSTRAP_STDERR_MAX_BYTES = 4 * 1024;
14
+ const DEFAULT_BOOTSTRAP_FAILURE_OBSERVATION_MS = 75_000;
15
+ const MAX_BOOTSTRAP_FAILURE_OBSERVATION_MS = 120_000;
16
+ const LAUNCH_FAILURE_MAX_BYTES = 2 * 1024;
17
+ // Pre-spawn timings a launcher may hand over for the next cold-start investigation.
18
+ const LAUNCH_STAGE_MS_FIELDS = Object.freeze(["bindingResolvedMs", "discoverProbedMs", "lockAcquiredMs"]);
19
+ const LAUNCH_STAGE_MAX_MS = 86_400_000;
20
+ const SAFE_BOOTSTRAP_FAILURE_CODES = new Set([
21
+ "hellolynk_node_22_16_required",
22
+ "neutral_codex_binding_configuration_required",
23
+ "neutral_codex_binding_invalid",
24
+ "neutral_codex_launch_failed",
25
+ "neutral_codex_runner_integrity_invalid",
26
+ "neutral_codex_runner_unavailable",
27
+ "neutral_codex_uncaught_exception",
28
+ "neutral_host_already_running",
29
+ "neutral_host_bootstrap_exited",
30
+ "neutral_host_bootstrap_failed",
31
+ "neutral_host_drain_timeout",
32
+ "neutral_host_launch_configuration_required",
33
+ "neutral_host_launch_failed",
34
+ "neutral_host_launch_failure_store_failed",
35
+ "neutral_host_launch_failure_store_invalid",
36
+ // The Host refused to start below the release an installer recorded; the caller
37
+ // waits briefly for a Host of that release, then reports this client out of date.
38
+ "neutral_host_release_superseded",
39
+ "neutral_host_runner_integrity_invalid",
40
+ "neutral_host_runner_unavailable",
41
+ "neutral_host_singleton_claim_failed",
42
+ "neutral_host_singleton_lock_invalid",
43
+ "neutral_host_singleton_owner_mismatch",
44
+ "neutral_host_singleton_owner_unknown",
45
+ "neutral_host_singleton_stale_quarantine_failed",
46
+ "neutral_host_stop_failed",
47
+ "neutral_runtime_uncaught_exception",
48
+ "neutral_trae_work_uncaught_exception",
49
+ "powershell7_missing",
50
+ ]);
51
+ const SAFE_RELATIVE = /^(?:[A-Za-z0-9][A-Za-z0-9._-]*\/)*[A-Za-z0-9][A-Za-z0-9._-]*\.(?:cjs|cs|css|html|js|json|md|mjs|ps1|vbs)$/;
52
+ const REQUIRED_RUNTIME_RECORDS = Object.freeze([
53
+ "neutral-host-bootstrap.mjs",
54
+ "neutral-host-release-loader.mjs",
55
+ "neutral-host-release-entry.mjs",
56
+ "core/hellolynk-host-protected-store.mjs",
57
+ "core/hellolynk-runtime-safety.cjs",
58
+ "core/hellolynk-local-data-migration.mjs",
59
+ "core/hellolynk-local-data-protection.mjs",
60
+ "core/hellolynk-indexed-message-store.mjs",
61
+ "core/hellolynk-neutral-host-installation-store.mjs",
62
+ "core/hellolynk-neutral-host-assembly.mjs",
63
+ "core/hellolynk-neutral-host-process.mjs",
64
+ "core/hellolynk-neutral-host-workbench-read-model.mjs",
65
+ "core/hellolynk-workbench-action-grants.mjs",
66
+ "platform/windows/hellolynk-host-protected-store.ps1",
67
+ "platform/windows/hellolynk-local-data-key.ps1",
68
+ "workbench/contract.mjs",
69
+ ]);
70
+
71
+ export async function launchPinnedNeutralHost(binding, options = {}) {
72
+ assertHelloLynkClientNodeVersion();
73
+ assertHelloLynkPowerShell7();
74
+ const launch = normalize(binding, options);
75
+ await verifyPinnedNeutralHostRuntime(launch);
76
+ const lockPath = path.join(
77
+ launch.runtimeRoot,
78
+ "neutral-host",
79
+ `${sha256(`${launch.region}\0${launch.principalId}\0${launch.hostInstallationId}`)}.${launch.adapterId}-launch.lock`,
80
+ );
81
+ await safeDirectory(launch.runtimeRoot, true);
82
+ await safeDirectory(path.dirname(lockPath), true);
83
+ const wait = options.wait || delay;
84
+ const deadline = Date.now() + bounded(options.lockTimeoutMs, 15_000, 500, 60_000);
85
+ let ownsLock = false;
86
+ while (Date.now() < deadline) {
87
+ try {
88
+ await fs.mkdir(lockPath, { mode: 0o700 });
89
+ await fs.writeFile(path.join(lockPath, "owner.json"), `${JSON.stringify({
90
+ contract: "hellolynk-neutral-host-launch-lock-v1",
91
+ adapterId: launch.adapterId,
92
+ pid: process.pid,
93
+ createdAtMs: Date.now(),
94
+ })}\n`, { encoding: "utf8", flag: "wx", mode: 0o600 });
95
+ ownsLock = true;
96
+ break;
97
+ } catch (error) {
98
+ if (error?.code !== "EEXIST") throw codeError("neutral_host_launch_failed");
99
+ await safeDirectory(lockPath, false);
100
+ await reclaimDeadLock(lockPath).catch(() => false);
101
+ await wait(50);
102
+ }
103
+ }
104
+ if (!ownsLock) throw codeError("neutral_host_launch_lock_timeout");
105
+ try {
106
+ await launchPinnedNeutralHostBootstrap(launch, options);
107
+ return Object.freeze({ started: true, owner: `${launch.adapterId}_process` });
108
+ } finally {
109
+ await fs.rm(path.join(lockPath, "owner.json"), { force: true }).catch(() => {});
110
+ await fs.rmdir(lockPath).catch(() => {});
111
+ }
112
+ }
113
+
114
+ export async function launchPinnedNeutralHostBootstrap(binding, options = {}) {
115
+ assertHelloLynkClientNodeVersion();
116
+ assertHelloLynkPowerShell7();
117
+ const launch = normalize(binding, options);
118
+ const failureBasePath = launchFailurePath(launch);
119
+ await safeDirectory(launch.runtimeRoot, true);
120
+ await safeDirectory(path.dirname(failureBasePath), true);
121
+ const failureAttemptId = await beginLaunchFailureAttempt(failureBasePath, launchStages(options.stages));
122
+ const observation = bootstrapObservationWindows(options);
123
+ const spawnRunner = options.spawnRunner || spawnBootstrap;
124
+ try {
125
+ await spawnRunner({
126
+ bootstrapPath: launch.bootstrapPath,
127
+ storeRoot: launch.storeRoot,
128
+ expected: owner(launch),
129
+ failurePath: launchFailureRecordPath(failureBasePath, failureAttemptId),
130
+ failureBasePath,
131
+ failureAttemptId,
132
+ observationMs: observation.observationMs,
133
+ failureObservationMs: observation.failureObservationMs,
134
+ env: options.env || process.env,
135
+ });
136
+ } catch (error) {
137
+ const code = stableBootstrapFailureCode(error?.code);
138
+ if (error?.launchFailureRecorded !== true) {
139
+ await writeLaunchFailure(failureBasePath, failureAttemptId, code);
140
+ }
141
+ const reported = codeError(code);
142
+ reported.launchFailure = await readLaunchFailure(failureBasePath, failureAttemptId).catch(() => null);
143
+ throw reported;
144
+ }
145
+ return Object.freeze({ started: true, owner: `${launch.adapterId}_process` });
146
+ }
147
+
148
+ export async function readPinnedNeutralHostLaunchFailure(binding, options = {}) {
149
+ const launch = normalize(binding, options);
150
+ return readLaunchFailure(launchFailurePath(launch));
151
+ }
152
+
153
+ export async function verifyPinnedNeutralHostRuntime(binding, options = {}) {
154
+ const launch = normalize(binding, options);
155
+ const pinPath = path.join(launch.releaseRoot, "runtime", "neutral-host-release-pin.json");
156
+ const manifestPath = path.join(launch.releaseRoot, "runtime", "neutral-host-release-manifest.json");
157
+ const [pinBytes, manifestBytes] = await Promise.all([
158
+ readSafeFile(pinPath, 16 * 1024),
159
+ readSafeFile(manifestPath, 256 * 1024),
160
+ ]);
161
+ const pin = parseJson(pinBytes);
162
+ const manifest = parseJson(manifestBytes);
163
+ if (!plain(pin) || !exact(pin, ["contract", "manifestSha256"])
164
+ || pin.contract !== "hellolynk-neutral-host-release-pin-v1"
165
+ || pin.manifestSha256 !== launch.releaseManifestSha256
166
+ || sha256(manifestBytes) !== launch.releaseManifestSha256
167
+ || !plain(manifest) || !exact(manifest, ["contract", "entry", "files"])
168
+ || manifest.contract !== "hellolynk-neutral-host-release-manifest-v1"
169
+ || manifest.entry !== "neutral-host-release-entry.mjs"
170
+ || !Array.isArray(manifest.files) || manifest.files.length < 3 || manifest.files.length > 256) fail();
171
+ const records = new Map();
172
+ const paths = new Map();
173
+ for (const item of manifest.files) {
174
+ if (!plain(item) || !exact(item, ["path", "sha256"])
175
+ || !/^[a-f0-9]{64}$/.test(String(item.sha256 || ""))) fail();
176
+ const recordPath = String(item.path || "");
177
+ const file = resolveManifestFile(launch, recordPath);
178
+ const canonical = canonicalPath(file);
179
+ if (records.has(recordPath) || paths.has(canonical)) fail();
180
+ const bytes = await readSafeFile(file, 2 * 1024 * 1024).catch(() => fail());
181
+ if (sha256(bytes) !== item.sha256) fail();
182
+ const record = Object.freeze({ recordPath, file, bytes });
183
+ records.set(recordPath, record);
184
+ paths.set(canonical, record);
185
+ }
186
+ for (const required of REQUIRED_RUNTIME_RECORDS) if (!records.has(required)) fail();
187
+ if (!samePath(records.get("neutral-host-bootstrap.mjs").file, launch.bootstrapPath)
188
+ || !records.has(manifest.entry)) fail();
189
+ verifyStaticImports(records, paths, launch.releaseRoot);
190
+ return Object.freeze({ ok: true, bootstrapPath: launch.bootstrapPath, verifiedFileCount: records.size });
191
+ }
192
+
193
+ function normalize(value, options) {
194
+ if (!plain(value)) throw codeError("neutral_host_launch_configuration_required");
195
+ const runtimeRoot = absolute(value.runtimeRoot);
196
+ const releaseRoot = absolute(value.releaseRoot);
197
+ const profileManifestPath = absolute(value.profileManifestPath);
198
+ const releaseManifestSha256 = String(value.releaseManifestSha256 || "");
199
+ if (value.runnerRelativePath !== RUNNER_RELATIVE || !/^[a-f0-9]{64}$/.test(releaseManifestSha256)) {
200
+ throw codeError("neutral_host_launch_configuration_required");
201
+ }
202
+ const storeRoot = options.storeRoot ? absolute(options.storeRoot) : path.dirname(runtimeRoot);
203
+ if (path.basename(runtimeRoot).toLowerCase() !== "runtime"
204
+ || !samePath(storeRoot, path.dirname(runtimeRoot))
205
+ || !samePath(profileManifestPath, path.join(storeRoot, "profile-manifest-v1.json"))) {
206
+ throw codeError("neutral_host_launch_configuration_required");
207
+ }
208
+ return Object.freeze({
209
+ runtimeRoot, releaseRoot, profileManifestPath, releaseManifestSha256, storeRoot,
210
+ runnerRelativePath: RUNNER_RELATIVE,
211
+ bootstrapPath: path.join(releaseRoot, ...RUNNER_RELATIVE.split("/")),
212
+ region: safeId(value.region), principalId: safeId(value.principalId),
213
+ delegateId: safeId(value.delegateId), hostInstallationId: safeId(value.hostInstallationId),
214
+ profileId: safeId(value.profileId), adapterId: safeId(value.adapterId),
215
+ version: Number(value.version), scopes: Object.freeze([...(value.scopes || [])].map(String)),
216
+ });
217
+ }
218
+
219
+ function resolveManifestFile(launch, recordPath) {
220
+ if (!SAFE_RELATIVE.test(recordPath) || recordPath.split("/").includes("..")) fail();
221
+ const external = recordPath.startsWith("release/");
222
+ const base = external ? launch.releaseRoot : path.join(launch.releaseRoot, "runtime");
223
+ const relative = external ? recordPath.slice("release/".length) : recordPath;
224
+ const result = path.resolve(base, ...relative.split("/"));
225
+ if (!inside(base, result)) fail();
226
+ return result;
227
+ }
228
+
229
+ function verifyStaticImports(records, paths, releaseRoot) {
230
+ const imports = /(?:\bfrom\s*|\bimport\s*\(\s*|\bimport\s+)["'](\.{1,2}\/[^"']+)["']/g;
231
+ for (const record of records.values()) {
232
+ if (!record.file.endsWith(".mjs")) continue;
233
+ const source = record.bytes.toString("utf8");
234
+ imports.lastIndex = 0;
235
+ for (let match = imports.exec(source); match; match = imports.exec(source)) {
236
+ const imported = path.resolve(path.dirname(record.file), ...match[1].split("/"));
237
+ if (!inside(releaseRoot, imported) || !paths.has(canonicalPath(imported))) fail();
238
+ }
239
+ }
240
+ }
241
+
242
+ async function spawnBootstrap(input) {
243
+ assertHelloLynkClientNodeVersion();
244
+ const executable = await resolveHelloLynkStandaloneNode({ env: input.env });
245
+ const child = spawn(executable, [input.bootstrapPath, "run", "--root", input.storeRoot], {
246
+ cwd: path.dirname(input.bootstrapPath),
247
+ detached: true,
248
+ stdio: ["ignore", "ignore", "pipe"],
249
+ windowsHide: true,
250
+ env: standaloneHelloLynkNodeEnvironment(input.env || process.env),
251
+ });
252
+ const observation = observeBootstrap(child, input.observationMs, input.failureObservationMs);
253
+ const outcome = await observation.initial;
254
+ if (outcome.running) {
255
+ child.stderr?.unref?.();
256
+ child.unref();
257
+ void observation.terminal.then(async (lateOutcome) => {
258
+ if (!lateOutcome) return;
259
+ const code = bootstrapFailureCode(lateOutcome);
260
+ await writeLaunchFailure(input.failureBasePath, input.failureAttemptId, code);
261
+ }).catch(() => {});
262
+ return;
263
+ }
264
+ const code = bootstrapFailureCode(outcome);
265
+ const recorded = await writeLaunchFailure(input.failureBasePath, input.failureAttemptId, code);
266
+ const error = codeError(code);
267
+ error.launchFailureRecorded = Boolean(recorded);
268
+ error.launchFailure = await readLaunchFailure(input.failureBasePath, input.failureAttemptId).catch(() => null);
269
+ throw error;
270
+ }
271
+
272
+ function observeBootstrap(child, observationMs, failureObservationMs) {
273
+ let stderr = Buffer.alloc(0);
274
+ const failureDeadlineAtMs = Date.now() + failureObservationMs;
275
+ const capture = (chunk) => {
276
+ if (stderr.length >= BOOTSTRAP_STDERR_MAX_BYTES) return;
277
+ const bytes = Buffer.from(chunk);
278
+ stderr = Buffer.concat([
279
+ stderr,
280
+ bytes.subarray(0, BOOTSTRAP_STDERR_MAX_BYTES - stderr.length),
281
+ ]);
282
+ };
283
+ child.stderr?.on("data", capture);
284
+ let initialSettled = false;
285
+ let terminalSettled = false;
286
+ let initialResolve;
287
+ let terminalResolve;
288
+ const initial = new Promise((resolve) => { initialResolve = resolve; });
289
+ const terminal = new Promise((resolve) => { terminalResolve = resolve; });
290
+ let initialTimer = null;
291
+ let failureTimer = null;
292
+ const settleInitial = (value) => {
293
+ if (initialSettled) return;
294
+ initialSettled = true;
295
+ clearTimeout(initialTimer);
296
+ initialResolve(value);
297
+ };
298
+ const cleanup = () => {
299
+ clearTimeout(initialTimer);
300
+ clearTimeout(failureTimer);
301
+ child.off?.("error", onError);
302
+ child.off?.("close", onClose);
303
+ child.stderr?.off?.("data", capture);
304
+ // Keep the detached child's pipe drained without retaining our evidence
305
+ // closure after the bounded discovery window.
306
+ child.stderr?.resume?.();
307
+ child.stderr?.unref?.();
308
+ };
309
+ const settleTerminal = (value) => {
310
+ if (terminalSettled) return;
311
+ if (Date.now() >= failureDeadlineAtMs) {
312
+ expireObservation();
313
+ return;
314
+ }
315
+ terminalSettled = true;
316
+ const outcome = Object.freeze({ ...value, stderr });
317
+ settleInitial(outcome);
318
+ cleanup();
319
+ terminalResolve(outcome);
320
+ };
321
+ const onError = () => settleTerminal({ running: false, spawnError: true, exitCode: null });
322
+ const onClose = (exitCode) => settleTerminal({ running: false, spawnError: false, exitCode });
323
+ const expireObservation = () => {
324
+ if (terminalSettled) return;
325
+ terminalSettled = true;
326
+ settleInitial(Object.freeze({ running: true }));
327
+ stderr.fill(0);
328
+ stderr = Buffer.alloc(0);
329
+ cleanup();
330
+ terminalResolve(null);
331
+ };
332
+ child.once("error", onError);
333
+ child.once("close", onClose);
334
+ initialTimer = setTimeout(() => settleInitial(Object.freeze({ running: true })), observationMs);
335
+ failureTimer = setTimeout(expireObservation, failureObservationMs);
336
+ failureTimer.unref?.();
337
+ return Object.freeze({ initial, terminal });
338
+ }
339
+
340
+ function bootstrapFailureCode(outcome) {
341
+ let parsedCode = null;
342
+ try { parsedCode = parseBootstrapFailureCode(outcome.stderr); }
343
+ finally { outcome.stderr?.fill?.(0); }
344
+ return outcome.spawnError
345
+ ? "neutral_host_launch_failed"
346
+ : parsedCode || (outcome.exitCode === 0 ? "neutral_host_bootstrap_exited" : "neutral_host_bootstrap_failed");
347
+ }
348
+
349
+ function parseBootstrapFailureCode(stderr) {
350
+ const text = Buffer.isBuffer(stderr) ? stderr.toString("utf8") : "";
351
+ for (const line of text.split(/\r?\n/).slice(-8).reverse()) {
352
+ let record;
353
+ try { record = JSON.parse(line); } catch { continue; }
354
+ if (plain(record) && record.ok === false && typeof record.code === "string") {
355
+ return stableBootstrapFailureCode(record.code);
356
+ }
357
+ }
358
+ return null;
359
+ }
360
+
361
+ function stableBootstrapFailureCode(value) {
362
+ const code = String(value || "");
363
+ return SAFE_BOOTSTRAP_FAILURE_CODES.has(code) ? code : "neutral_host_bootstrap_failed";
364
+ }
365
+
366
+ function bootstrapObservationWindows(options) {
367
+ const requestedObservationMs = bounded(options.bootstrapObservationMs, 3_000, 100, 10_000);
368
+ const explicitDeadline = Number(options.bootstrapFailureDeadlineMs);
369
+ let failureObservationMs;
370
+ if (Number.isSafeInteger(explicitDeadline)) {
371
+ failureObservationMs = Math.max(0, Math.min(
372
+ explicitDeadline - Date.now(),
373
+ MAX_BOOTSTRAP_FAILURE_OBSERVATION_MS,
374
+ ));
375
+ } else {
376
+ failureObservationMs = bounded(
377
+ options.bootstrapFailureObservationMs,
378
+ DEFAULT_BOOTSTRAP_FAILURE_OBSERVATION_MS,
379
+ 0,
380
+ MAX_BOOTSTRAP_FAILURE_OBSERVATION_MS,
381
+ );
382
+ }
383
+ return Object.freeze({
384
+ observationMs: Math.min(requestedObservationMs, failureObservationMs),
385
+ failureObservationMs,
386
+ });
387
+ }
388
+
389
+ function launchFailurePath(launch) {
390
+ return path.join(
391
+ launch.runtimeRoot,
392
+ "neutral-host",
393
+ `${sha256(`${launch.region}\0${launch.principalId}\0${launch.hostInstallationId}`)}.${launch.adapterId}-launch-failure`,
394
+ );
395
+ }
396
+
397
+ function launchAttemptPath(base) { return `${base}-attempt.json`; }
398
+ function launchFailureRecordPath(base, attemptId) { return `${base}.${attemptId}.json`; }
399
+
400
+ async function beginLaunchFailureAttempt(base, stages = null) {
401
+ const prior = await readLaunchAttempt(base);
402
+ await removeLaunchFile(launchAttemptPath(base));
403
+ if (prior) await removeLaunchFile(launchFailureRecordPath(base, prior.attemptId));
404
+ const attemptId = crypto.randomBytes(16).toString("hex");
405
+ // Stages ride on this one write; no second file or write is added for them.
406
+ await writeAtomicLaunchFile(launchAttemptPath(base), Object.freeze({
407
+ contract: HELLOLYNK_NEUTRAL_HOST_LAUNCH_ATTEMPT_CONTRACT,
408
+ attemptId,
409
+ startedAt: new Date().toISOString(),
410
+ ...(stages ? { stages } : {}),
411
+ }));
412
+ return attemptId;
413
+ }
414
+
415
+ async function writeLaunchFailure(base, attemptId, code) {
416
+ const current = await readLaunchAttempt(base);
417
+ if (current?.attemptId !== attemptId) return null;
418
+ const record = Object.freeze({
419
+ contract: HELLOLYNK_NEUTRAL_HOST_LAUNCH_FAILURE_CONTRACT,
420
+ code: stableBootstrapFailureCode(code),
421
+ observedAt: new Date().toISOString(),
422
+ });
423
+ const target = launchFailureRecordPath(base, attemptId);
424
+ await writeAtomicLaunchFile(target, record);
425
+ const after = await readLaunchAttempt(base);
426
+ if (after?.attemptId !== attemptId) {
427
+ await removeLaunchFile(target);
428
+ return null;
429
+ }
430
+ return record;
431
+ }
432
+
433
+ async function readLaunchFailure(base, expectedAttemptId = null) {
434
+ const before = await readLaunchAttempt(base);
435
+ if (!before || (expectedAttemptId && before.attemptId !== expectedAttemptId)) return null;
436
+ const bytes = await readLaunchFile(launchFailureRecordPath(base, before.attemptId));
437
+ if (!bytes) return null;
438
+ let record;
439
+ try { record = JSON.parse(bytes.toString("utf8")); }
440
+ catch { throw codeError("neutral_host_launch_failure_store_invalid"); }
441
+ if (!plain(record) || !exact(record, ["code", "contract", "observedAt"])
442
+ || record.contract !== HELLOLYNK_NEUTRAL_HOST_LAUNCH_FAILURE_CONTRACT
443
+ || stableBootstrapFailureCode(record.code) !== record.code
444
+ || !Number.isFinite(Date.parse(String(record.observedAt || "")))) {
445
+ throw codeError("neutral_host_launch_failure_store_invalid");
446
+ }
447
+ const after = await readLaunchAttempt(base);
448
+ if (after?.attemptId !== before.attemptId) return null;
449
+ return Object.freeze(structuredClone(record));
450
+ }
451
+
452
+ async function readLaunchAttempt(base) {
453
+ const bytes = await readLaunchFile(launchAttemptPath(base));
454
+ if (!bytes) return null;
455
+ let record;
456
+ try { record = JSON.parse(bytes.toString("utf8")); }
457
+ catch { throw codeError("neutral_host_launch_failure_store_invalid"); }
458
+ if (!plain(record)
459
+ || !(exact(record, ["attemptId", "contract", "startedAt"])
460
+ || (exact(record, ["attemptId", "contract", "stages", "startedAt"]) && validLaunchStages(record.stages)))
461
+ || record.contract !== HELLOLYNK_NEUTRAL_HOST_LAUNCH_ATTEMPT_CONTRACT
462
+ || !/^[a-f0-9]{32}$/.test(String(record.attemptId || ""))
463
+ || !Number.isFinite(Date.parse(String(record.startedAt || "")))) {
464
+ throw codeError("neutral_host_launch_failure_store_invalid");
465
+ }
466
+ return Object.freeze(record);
467
+ }
468
+
469
+ async function readLaunchFile(target) {
470
+ await assertLaunchParent(target);
471
+ let pathMetadata;
472
+ try { pathMetadata = await fs.lstat(target, { bigint: true }); }
473
+ catch (error) {
474
+ if (error?.code === "ENOENT") return null;
475
+ throw codeError("neutral_host_launch_failure_store_failed");
476
+ }
477
+ if (!pathMetadata.isFile() || pathMetadata.isSymbolicLink()
478
+ || pathMetadata.nlink !== 1n || pathMetadata.size > BigInt(LAUNCH_FAILURE_MAX_BYTES)) {
479
+ throw codeError("neutral_host_launch_failure_store_invalid");
480
+ }
481
+ let handle;
482
+ try { handle = await fs.open(target, "r"); }
483
+ catch { throw codeError("neutral_host_launch_failure_store_failed"); }
484
+ try {
485
+ const before = await handle.stat({ bigint: true });
486
+ if (!before.isFile() || before.nlink !== 1n || before.size > BigInt(LAUNCH_FAILURE_MAX_BYTES)
487
+ || !sameIdentity(pathMetadata, before)) {
488
+ throw codeError("neutral_host_launch_failure_store_invalid");
489
+ }
490
+ const bytes = await handle.readFile();
491
+ const after = await handle.stat({ bigint: true });
492
+ if (bytes.length > LAUNCH_FAILURE_MAX_BYTES || !sameIdentity(before, after)) {
493
+ throw codeError("neutral_host_launch_failure_store_invalid");
494
+ }
495
+ return bytes;
496
+ } finally { await handle.close().catch(() => {}); }
497
+ }
498
+
499
+ async function writeAtomicLaunchFile(target, record) {
500
+ await assertLaunchParent(target);
501
+ await assertLaunchFailureTarget(target);
502
+ const bytes = Buffer.from(`${JSON.stringify(record)}\n`, "utf8");
503
+ if (bytes.length > LAUNCH_FAILURE_MAX_BYTES) throw codeError("neutral_host_launch_failure_store_failed");
504
+ const temporary = `${target}.${process.pid}.${crypto.randomBytes(8).toString("hex")}.tmp`;
505
+ let handle;
506
+ try {
507
+ handle = await fs.open(temporary, "wx", 0o600);
508
+ await handle.writeFile(bytes);
509
+ await handle.sync();
510
+ await handle.close();
511
+ handle = null;
512
+ await fs.rename(temporary, target);
513
+ } catch {
514
+ await handle?.close().catch(() => {});
515
+ await fs.unlink(temporary).catch(() => {});
516
+ throw codeError("neutral_host_launch_failure_store_failed");
517
+ }
518
+ }
519
+
520
+ async function removeLaunchFile(target) {
521
+ await assertLaunchParent(target);
522
+ let metadata;
523
+ try { metadata = await fs.lstat(target, { bigint: true }); }
524
+ catch (error) {
525
+ if (error?.code === "ENOENT") return;
526
+ throw codeError("neutral_host_launch_failure_store_failed");
527
+ }
528
+ if (!metadata.isFile() || metadata.isSymbolicLink()
529
+ || metadata.nlink !== 1n || metadata.size > BigInt(LAUNCH_FAILURE_MAX_BYTES)) {
530
+ throw codeError("neutral_host_launch_failure_store_invalid");
531
+ }
532
+ try { await fs.unlink(target); }
533
+ catch { throw codeError("neutral_host_launch_failure_store_failed"); }
534
+ }
535
+
536
+ async function assertLaunchParent(target) {
537
+ try { await noReparseAncestors(path.dirname(target)); }
538
+ catch { throw codeError("neutral_host_launch_failure_store_invalid"); }
539
+ }
540
+
541
+ async function assertLaunchFailureTarget(target) {
542
+ let metadata;
543
+ try { metadata = await fs.lstat(target, { bigint: true }); }
544
+ catch (error) {
545
+ if (error?.code === "ENOENT") return;
546
+ throw codeError("neutral_host_launch_failure_store_failed");
547
+ }
548
+ if (!metadata.isFile() || metadata.isSymbolicLink()
549
+ || metadata.nlink !== 1n || metadata.size > BigInt(LAUNCH_FAILURE_MAX_BYTES)) {
550
+ throw codeError("neutral_host_launch_failure_store_invalid");
551
+ }
552
+ }
553
+
554
+ async function readSafeFile(file, maximum) {
555
+ await noReparseAncestors(file);
556
+ let handle;
557
+ try { handle = await fs.open(file, "r"); }
558
+ catch { throw codeError("neutral_host_runner_unavailable"); }
559
+ try {
560
+ const before = await handle.stat({ bigint: true });
561
+ if (!before.isFile() || before.nlink !== 1n || before.size > BigInt(maximum)) fail();
562
+ const bytes = await handle.readFile();
563
+ const after = await handle.stat({ bigint: true });
564
+ if (!sameIdentity(before, after)) fail();
565
+ return bytes;
566
+ } catch (error) {
567
+ if (/^neutral_host_/.test(String(error?.code || ""))) throw error;
568
+ throw codeError("neutral_host_runner_integrity_invalid");
569
+ } finally { await handle?.close(); }
570
+ }
571
+
572
+ async function noReparseAncestors(target) {
573
+ const resolved = path.resolve(target);
574
+ const parsed = path.parse(resolved);
575
+ let cursor = parsed.root;
576
+ for (const segment of path.relative(parsed.root, resolved).split(path.sep).filter(Boolean)) {
577
+ cursor = path.join(cursor, segment);
578
+ let metadata;
579
+ try { metadata = await fs.lstat(cursor); }
580
+ catch { throw codeError("neutral_host_runner_unavailable"); }
581
+ if (metadata.isSymbolicLink()) fail();
582
+ }
583
+ }
584
+
585
+ async function safeDirectory(target, create) {
586
+ if (create) await fs.mkdir(target, { recursive: true, mode: 0o700 });
587
+ const metadata = await fs.lstat(target).catch(() => null);
588
+ if (!metadata?.isDirectory() || metadata.isSymbolicLink()) {
589
+ throw codeError("neutral_host_launch_configuration_required");
590
+ }
591
+ }
592
+
593
+ async function reclaimDeadLock(lockPath) {
594
+ const metadata = await fs.lstat(lockPath);
595
+ let ownerRecord;
596
+ try { ownerRecord = JSON.parse(await fs.readFile(path.join(lockPath, "owner.json"), "utf8")); }
597
+ catch {
598
+ if (Date.now() - metadata.mtimeMs <= 10_000) return false;
599
+ return quarantineStaleLock(lockPath);
600
+ }
601
+ const pid = Number(ownerRecord?.pid);
602
+ if (Date.now() - metadata.mtimeMs <= 10_000
603
+ || (Number.isSafeInteger(pid) && pid > 0 && processAlive(pid))) return false;
604
+ return quarantineStaleLock(lockPath);
605
+ }
606
+
607
+ async function quarantineStaleLock(lockPath) {
608
+ const quarantine = `${lockPath}.stale.${Date.now()}.${crypto.randomBytes(8).toString("hex")}`;
609
+ try { await fs.rename(lockPath, quarantine); }
610
+ catch (error) { if (error?.code === "ENOENT") return false; throw error; }
611
+ await fs.rm(quarantine, { recursive: true, force: true });
612
+ return true;
613
+ }
614
+
615
+ function owner(value) {
616
+ return Object.freeze({
617
+ region: value.region, principalId: value.principalId, delegateId: value.delegateId,
618
+ hostInstallationId: value.hostInstallationId, profileId: value.profileId,
619
+ adapterId: value.adapterId, version: value.version, scopes: value.scopes,
620
+ });
621
+ }
622
+ function safeId(value) { const text = String(value || ""); if (!/^[A-Za-z0-9][A-Za-z0-9_.:-]{2,198}$/.test(text)) fail(); return text; }
623
+ function absolute(value) { const text = String(value || ""); if (!path.isAbsolute(text) || path.resolve(text) !== text) fail(); return text; }
624
+ function inside(root, target) { const relative = path.relative(path.resolve(root), path.resolve(target)); return Boolean(relative) && relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative); }
625
+ function canonicalPath(value) { const result = path.resolve(value); return process.platform === "win32" ? result.toLowerCase() : result; }
626
+ function samePath(left, right) { return canonicalPath(left) === canonicalPath(right); }
627
+ function sameIdentity(left, right) {
628
+ return left.ino === right.ino
629
+ && (left.dev === right.dev
630
+ || (process.platform === "win32" && (left.dev === 0n || right.dev === 0n)))
631
+ && left.size === right.size && left.mtimeNs === right.mtimeNs && left.nlink === right.nlink;
632
+ }
633
+ function processAlive(pid) { try { process.kill(pid, 0); return true; } catch (error) { return error?.code === "EPERM"; } }
634
+ function parseJson(value) { try { return JSON.parse(value); } catch { fail(); } }
635
+ // Stages are diagnostics only. On the way in, unknown or unusable values are dropped so
636
+ // they can never block a launch; on disk they are validated as strictly as the other fields.
637
+ function launchStages(value) {
638
+ if (!plain(value)) return null;
639
+ const stages = {};
640
+ if (isoTimestamp(value.enteredAt)) stages.enteredAt = value.enteredAt;
641
+ for (const field of LAUNCH_STAGE_MS_FIELDS) {
642
+ if (stageMs(value[field])) stages[field] = value[field];
643
+ }
644
+ return Object.keys(stages).length ? Object.freeze(stages) : null;
645
+ }
646
+ function validLaunchStages(value) {
647
+ if (!plain(value)) return false;
648
+ const keys = Object.keys(value);
649
+ return keys.length > 0
650
+ && keys.every((key) => key === "enteredAt" || LAUNCH_STAGE_MS_FIELDS.includes(key))
651
+ && (!("enteredAt" in value) || isoTimestamp(value.enteredAt))
652
+ && LAUNCH_STAGE_MS_FIELDS.every((field) => !(field in value) || stageMs(value[field]));
653
+ }
654
+ function isoTimestamp(value) {
655
+ if (typeof value !== "string" || !Number.isFinite(Date.parse(value))) return false;
656
+ return new Date(value).toISOString() === value;
657
+ }
658
+ function stageMs(value) { return Number.isSafeInteger(value) && value >= 0 && value <= LAUNCH_STAGE_MAX_MS; }
659
+ function exact(value, expected) { const actual = Object.keys(value).sort(); const wanted = [...expected].sort(); return actual.length === wanted.length && actual.every((item, index) => item === wanted[index]); }
660
+ function plain(value) { return Boolean(value) && typeof value === "object" && !Array.isArray(value); }
661
+ function bounded(value, fallback, minimum, maximum) { const result = Number(value ?? fallback); return Number.isSafeInteger(result) && result >= minimum && result <= maximum ? result : fallback; }
662
+ function delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); }
663
+ function sha256(value) { return crypto.createHash("sha256").update(value).digest("hex"); }
664
+ function fail() { throw codeError("neutral_host_runner_integrity_invalid"); }
665
+ function codeError(code) { const error = new Error(code); error.code = code; return error; }