@evident-ai/cli 3.5.0 → 3.5.1-dev.18543a6

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/dist/index.js CHANGED
@@ -1158,7 +1158,7 @@ import ora4 from "ora";
1158
1158
  import { select as select4 } from "@inquirer/prompts";
1159
1159
 
1160
1160
  // src/lib/telemetry.ts
1161
- var CLI_VERSION = (true ? "3.5.0" : void 0) ?? process.env.npm_package_version ?? "unknown";
1161
+ var CLI_VERSION = (true ? "3.5.1-dev.18543a6" : void 0) ?? process.env.npm_package_version ?? "unknown";
1162
1162
  function getCliVersion() {
1163
1163
  return CLI_VERSION;
1164
1164
  }
@@ -1630,12 +1630,18 @@ async function checkOpenCodeHealth(port) {
1630
1630
  return { healthy: false, error: message };
1631
1631
  }
1632
1632
  }
1633
+ function bodyExcerpt(body) {
1634
+ const excerpt = body.replace(/\s+/g, " ").trim().slice(0, 160);
1635
+ return excerpt ? ` (body="${excerpt}")` : " (empty body)";
1636
+ }
1633
1637
  async function checkOpenCode2Health(port, password) {
1634
1638
  try {
1635
- const response = await fetch(`http://127.0.0.1:${port}/global/health`, {
1636
- headers: {
1637
- Authorization: buildOpenCodeBasicAuthHeader(password)
1638
- },
1639
+ const headers = {};
1640
+ if (password !== void 0) {
1641
+ headers.Authorization = buildOpenCodeBasicAuthHeader(password);
1642
+ }
1643
+ const response = await fetch(`http://127.0.0.1:${port}/api/health`, {
1644
+ headers,
1639
1645
  signal: AbortSignal.timeout(2e3)
1640
1646
  });
1641
1647
  if (response.status === 401) {
@@ -1644,8 +1650,31 @@ async function checkOpenCode2Health(port, password) {
1644
1650
  if (!response.ok) {
1645
1651
  return { healthy: false, error: `HTTP ${response.status}` };
1646
1652
  }
1647
- const data = await response.json().catch(() => ({}));
1648
- return { healthy: true, version: data.version };
1653
+ const body = await response.text();
1654
+ const excerpt = bodyExcerpt(body);
1655
+ let data;
1656
+ try {
1657
+ data = JSON.parse(body);
1658
+ } catch (error2) {
1659
+ return {
1660
+ healthy: false,
1661
+ error: `Invalid JSON health response${excerpt}: ${error2 instanceof Error ? error2.message : String(error2)}`
1662
+ };
1663
+ }
1664
+ if (typeof data !== "object" || data === null || Array.isArray(data)) {
1665
+ return {
1666
+ healthy: false,
1667
+ error: `Invalid health response${excerpt}: expected healthy=true`
1668
+ };
1669
+ }
1670
+ const health = data;
1671
+ if (health.healthy !== true) {
1672
+ return {
1673
+ healthy: false,
1674
+ error: `Invalid health response${excerpt}: expected healthy=true`
1675
+ };
1676
+ }
1677
+ return typeof health.version === "string" ? { healthy: true, version: health.version } : { healthy: true };
1649
1678
  } catch (error2) {
1650
1679
  const message = error2 instanceof Error ? error2.message : "Unknown error";
1651
1680
  return { healthy: false, error: message };
@@ -4282,6 +4311,9 @@ function isSessionDbProvenanceRecord(value) {
4282
4311
  return typeof record.opencodeVersion === "string" && Array.isArray(record.migrationIds) && record.migrationIds.every((id) => typeof id === "string") && typeof record.updatedAt === "string";
4283
4312
  }
4284
4313
 
4314
+ // src/lib/opencode/opencode2-package.ts
4315
+ var OPENCODE2_PACKAGE_SPEC = "@opencode-ai/cli@0.0.0-beta-17823";
4316
+
4285
4317
  // src/lib/opencode/opencode-version-gate.ts
4286
4318
  var QUEUE_VALIDATED_OPENCODE_VERSIONS = ["1.17.11", "1.18.3"];
4287
4319
  function isQueueValidatedVersion(version2) {
@@ -4588,7 +4620,7 @@ async function startOpenCode2(port, options = {}) {
4588
4620
  args = [
4589
4621
  "-y",
4590
4622
  "-p",
4591
- "@opencode-ai/cli@beta",
4623
+ OPENCODE2_PACKAGE_SPEC,
4592
4624
  "--",
4593
4625
  "opencode2",
4594
4626
  "serve",
@@ -4665,7 +4697,7 @@ async function promptOpenCodeInstall(interactive) {
4665
4697
  npm: "npm install -g opencode-ai",
4666
4698
  curl: "curl -fsSL https://opencode.ai/install.sh | sh",
4667
4699
  v2: {
4668
- npm: "npm install -g @opencode-ai/cli@beta",
4700
+ npm: `npm install -g ${OPENCODE2_PACKAGE_SPEC}`,
4669
4701
  curl: "curl -fsSL https://opencode.ai/v2/install | bash"
4670
4702
  }
4671
4703
  }
@@ -6399,6 +6431,7 @@ var DEFAULT_PAUSED_POLL_INTERVAL_MS = 2e3;
6399
6431
  var DEFAULT_PAUSED_MAX_WAIT_MS = 10 * 60 * 1e3;
6400
6432
  var DEFAULT_STUCK_QUEUED_MS = 6e4;
6401
6433
  var HEARTBEAT_MS = 6e4;
6434
+ var ALIVE_WITHHELD_THRESHOLD_MS = 3 * HEARTBEAT_MS;
6402
6435
  var ABSOLUTE_MAX_PROCESSING_MS = 6 * 60 * 60 * 1e3;
6403
6436
  var B2_ABANDONMENT_MIN_PINNED_MS = 3 * 6e4;
6404
6437
  var AMBIGUOUS_FINISH_MAX_PINNED_MS = 3 * 6e4;
@@ -7603,7 +7636,7 @@ var ChannelDriver = class _ChannelDriver {
7603
7636
  return "dispatch";
7604
7637
  }
7605
7638
  }
7606
- return this.reattachRedrive(conv, sessionId, message, ocId);
7639
+ return this.reattachRedrive(conv, sessionId, message, ocId, state);
7607
7640
  }
7608
7641
  if (ongoing === false) {
7609
7642
  if (state === "running" && isAmbiguousFinishPinnedRunning(messages, ocId ?? "")) {
@@ -7620,11 +7653,12 @@ var ChannelDriver = class _ChannelDriver {
7620
7653
  return "dispatch";
7621
7654
  }
7622
7655
  /**
7623
- * The `reattached` outcome (Task 3.3): the prior turn is STILL ONGOING per
7624
- * opencode's own status map — undo the false reclaim instead of starting a
7625
- * second turn.
7656
+ * The `reattached` outcome (Task 3.3): opencode's own status map says the session
7657
+ * is ongoing, so undo the false reclaim instead of starting a second turn. `state`
7658
+ * carries the correlated message's observed state so the outcome does not claim
7659
+ * that a queued message's own turn was already running.
7626
7660
  */
7627
- async reattachRedrive(conv, sessionId, message, ocId) {
7661
+ async reattachRedrive(conv, sessionId, message, ocId, state) {
7628
7662
  let anchorMs;
7629
7663
  const parsed = message.processing_started_at ? Date.parse(message.processing_started_at) : NaN;
7630
7664
  if (!Number.isNaN(parsed)) {
@@ -7633,7 +7667,7 @@ var ChannelDriver = class _ChannelDriver {
7633
7667
  anchorMs = this.now();
7634
7668
  this.log({
7635
7669
  level: "warn",
7636
- message: `Re-drive: message ${message.id.slice(0, 8)} has null/unparseable processing_started_at (${String(message.processing_started_at)}) \u2014 this is expected when OpenCode accepted the turn but never started it; anchoring the watcher's absolute-age ceiling to now`,
7670
+ message: `Re-drive: message ${message.id.slice(0, 8)} has no usable processing_started_at (${String(message.processing_started_at)}) \u2014 anchoring the watcher's absolute-age ceiling to now`,
7637
7671
  conversation_id: conv.id,
7638
7672
  message_id: message.id
7639
7673
  });
@@ -7662,17 +7696,19 @@ var ChannelDriver = class _ChannelDriver {
7662
7696
  return bound === "abandoned" ? "abandoned" : "unresolved";
7663
7697
  }
7664
7698
  this.clearRedriveUnresolved(message.id);
7665
- this.registerReadopted(conv, sessionId, message, ocId ?? "", anchorMs);
7699
+ this.registerReadopted(conv, sessionId, message, ocId ?? "", anchorMs, this.now());
7666
7700
  this.dispatched.add(message.id);
7667
7701
  this.readopted.add(message.id);
7668
- this.ensureWatcherRunning(sessionId);
7702
+ const watcherState = this.ensureWatcherRunning(sessionId);
7669
7703
  const watchedForMs = this.now() - anchorMs;
7670
7704
  void this.postSignal(conv.id, message.id, "redrive_reattached", {
7671
- watched_for_ms: watchedForMs
7705
+ watched_for_ms: watchedForMs,
7706
+ run_state: state,
7707
+ watcher_state: watcherState
7672
7708
  });
7673
7709
  this.log({
7674
7710
  level: "warn",
7675
- message: `Re-drive: message ${message.id.slice(0, 8)} (session ${sessionId.slice(0, 8)}) was wrongly reclaimed to pending while its turn was still running (watched ${watchedForMs}ms) \u2014 restored to processing instead of re-dispatching`,
7711
+ message: state === "running" ? `Re-drive: message ${message.id.slice(0, 8)} (session ${sessionId.slice(0, 8)}) was wrongly reclaimed to pending while its turn was still running (watched ${watchedForMs}ms) \u2014 restored to processing instead of re-dispatching` : `Re-drive: message ${message.id.slice(0, 8)} (session ${sessionId.slice(0, 8)}) was reclaimed to pending while its own turn had not started and the session was busy (watched ${watchedForMs}ms) \u2014 restored to processing instead of re-dispatching to avoid a duplicate`,
7676
7712
  conversation_id: conv.id,
7677
7713
  message_id: message.id
7678
7714
  });
@@ -8279,6 +8315,7 @@ var ChannelDriver = class _ChannelDriver {
8279
8315
  stuckReported: false,
8280
8316
  lastAliveAt: 0,
8281
8317
  aliveInFlight: false,
8318
+ aliveWithheldSignalled: false,
8282
8319
  titleSynced: false,
8283
8320
  titleSyncInFlight: false,
8284
8321
  awaitingHumanLatched: false,
@@ -8338,9 +8375,12 @@ var ChannelDriver = class _ChannelDriver {
8338
8375
  /**
8339
8376
  * Register a RE-ADOPTED `processing` message with its session watcher
8340
8377
  * (ADR-0046, WI-4). Mirrors `registerInFlight` but anchors the give-up
8341
- * `deadline` to the row's SERVER-SIDE `processed_at` (Invariant 1), NEVER to
8342
- * `now`, so the paused/queued/unreachable cases settle on the same wall-clock a
8343
- * fresh dispatch would (10 min after `processed_at`, not 10 min from now).
8378
+ * `deadline` to the attempt's SERVER-SIDE `processed_at` (Invariant 1), while
8379
+ * `processingAnchorMs` keeps the absolute-age ceiling tied to the original turn.
8380
+ * The deadline tracks THIS attempt; the ceiling tracks THE TURN. For a fresh
8381
+ * dispatch both anchors are `now`; a restart re-adopt passes the server's
8382
+ * `processed_at` for both, while a re-drive passes the newly stamped attempt time
8383
+ * only for the deadline.
8344
8384
  *
8345
8385
  * This re-attaches into the SAME watcher, so the ADR-0047 progressing-vs-paused
8346
8386
  * give-up (`serviceInFlightMessage`) applies unchanged: a re-adopted turn
@@ -8361,7 +8401,7 @@ var ChannelDriver = class _ChannelDriver {
8361
8401
  * server already flipped to `processing`; the running/done transitions still
8362
8402
  * fire from the watcher's normal branches.
8363
8403
  */
8364
- registerReadopted(conv, sessionId, message, opencodeMessageId, processedAtMs) {
8404
+ registerReadopted(conv, sessionId, message, opencodeMessageId, processedAtMs, watchDeadlineAnchorMs) {
8365
8405
  let watcher = this.watchers.get(sessionId);
8366
8406
  if (!watcher) {
8367
8407
  watcher = this.newSessionWatcher(conv);
@@ -8372,11 +8412,11 @@ var ChannelDriver = class _ChannelDriver {
8372
8412
  opencodeMessageId,
8373
8413
  message,
8374
8414
  dispatchedAt: this.now(),
8375
- // Anchor the absolute-age ceiling to the SERVER-SIDE `processed_at` (the same
8376
- // value seeding `deadline`), NOT `dispatchedAt` — so a re-adopted zombie's age
8377
- // reflects the real turn duration and the ceiling fires on the ORIGINAL turn.
8415
+ // Anchor the absolute-age ceiling to the SERVER-SIDE `processed_at`, NOT
8416
+ // `dispatchedAt` — so a re-adopted zombie's age reflects the real turn duration
8417
+ // and the ceiling fires on the ORIGINAL turn.
8378
8418
  processingAnchorMs: processedAtMs,
8379
- deadline: processedAtMs + this.pausedMaxWaitMs,
8419
+ deadline: watchDeadlineAnchorMs + this.pausedMaxWaitMs,
8380
8420
  // The server row is ALREADY `processing`; do not re-fire markProcessing.
8381
8421
  started: true,
8382
8422
  done: false,
@@ -8393,6 +8433,7 @@ var ChannelDriver = class _ChannelDriver {
8393
8433
  // with no extra `re_adopted` signal needed (folds old WI-6).
8394
8434
  lastAliveAt: 0,
8395
8435
  aliveInFlight: false,
8436
+ aliveWithheldSignalled: false,
8396
8437
  titleSynced: false,
8397
8438
  titleSyncInFlight: false,
8398
8439
  awaitingHumanLatched: false,
@@ -8509,6 +8550,8 @@ var ChannelDriver = class _ChannelDriver {
8509
8550
  * tracked on the watcher and cleared when it settles; it never rejects (fully
8510
8551
  * guarded), so a failed poll/callback can never crash the run loop — the cron
8511
8552
  * stays as the safety net.
8553
+ * Returns the branch taken so re-attach telemetry can show whether it left a
8554
+ * ticking loop behind.
8512
8555
  *
8513
8556
  * The generation started here (#1618) is captured in the `.finally` closure
8514
8557
  * so a RETIRED loop settling late — after `reconcileWatchers` has already
@@ -8522,7 +8565,7 @@ var ChannelDriver = class _ChannelDriver {
8522
8565
  level: "debug",
8523
8566
  message: `No watcher entry exists for session ${sessionId} \u2014 nothing to do`
8524
8567
  });
8525
- return;
8568
+ return "no_watcher";
8526
8569
  }
8527
8570
  this.ensureSessionErrorStream();
8528
8571
  if (watcher.loop) {
@@ -8531,7 +8574,7 @@ var ChannelDriver = class _ChannelDriver {
8531
8574
  message: `Watcher loop already running for session ${sessionId}; no-op with ${watcher.inFlight.size} message(s) in flight`,
8532
8575
  conversation_id: watcher.conv.id
8533
8576
  });
8534
- return;
8577
+ return "already_running";
8535
8578
  }
8536
8579
  if (watcher.inFlight.size === 0) {
8537
8580
  this.log({
@@ -8540,7 +8583,7 @@ var ChannelDriver = class _ChannelDriver {
8540
8583
  conversation_id: watcher.conv.id
8541
8584
  });
8542
8585
  this.watchers.delete(sessionId);
8543
- return;
8586
+ return "no_in_flight";
8544
8587
  }
8545
8588
  const generation = watcher.generation;
8546
8589
  this.log({
@@ -8556,6 +8599,7 @@ var ChannelDriver = class _ChannelDriver {
8556
8599
  }
8557
8600
  });
8558
8601
  watcher.loop = loop;
8602
+ return "started";
8559
8603
  }
8560
8604
  ensureSessionErrorStream() {
8561
8605
  if (this.sessionErrorStream || this.stopped) return;
@@ -9081,7 +9125,8 @@ var ChannelDriver = class _ChannelDriver {
9081
9125
  this.removeInFlight(watcher, inFlight.evidentMessageId);
9082
9126
  return;
9083
9127
  }
9084
- if (activelyRunning && !inFlight.awaitingHumanLatched && !inFlight.aliveInFlight && this.now() - inFlight.lastAliveAt >= HEARTBEAT_MS) {
9128
+ const heartbeatDue = activelyRunning && !inFlight.awaitingHumanLatched && !inFlight.aliveInFlight && this.now() - inFlight.lastAliveAt >= HEARTBEAT_MS;
9129
+ if (heartbeatDue) {
9085
9130
  inFlight.aliveInFlight = true;
9086
9131
  this.log({
9087
9132
  level: "debug",
@@ -9091,7 +9136,10 @@ var ChannelDriver = class _ChannelDriver {
9091
9136
  });
9092
9137
  void this.postSignal(conv.id, inFlight.evidentMessageId, "alive").then((ok) => {
9093
9138
  inFlight.aliveInFlight = false;
9094
- if (ok) inFlight.lastAliveAt = this.now();
9139
+ if (ok) {
9140
+ inFlight.lastAliveAt = this.now();
9141
+ inFlight.aliveWithheldSignalled = false;
9142
+ }
9095
9143
  this.log({
9096
9144
  level: "debug",
9097
9145
  message: `Heartbeat POST for message ${inFlight.evidentMessageId} in session ${sessionId} completed with ok=${ok}`,
@@ -9143,6 +9191,16 @@ var ChannelDriver = class _ChannelDriver {
9143
9191
  inFlight.pausedOnPermission = false;
9144
9192
  inFlight.pausedClearConfirmed = false;
9145
9193
  }
9194
+ const watchedForMs = this.now() - Math.max(inFlight.lastAliveAt, inFlight.dispatchedAt);
9195
+ if (!heartbeatDue && !inFlight.aliveWithheldSignalled && watchedForMs >= ALIVE_WITHHELD_THRESHOLD_MS) {
9196
+ inFlight.aliveWithheldSignalled = true;
9197
+ const reason = inFlight.awaitingHumanLatched ? "awaiting_human" : inFlight.aliveInFlight ? "alive_in_flight" : "not_running";
9198
+ void this.postSignal(conv.id, inFlight.evidentMessageId, "alive_withheld", {
9199
+ reason,
9200
+ run_state: state,
9201
+ watched_for_ms: watchedForMs
9202
+ });
9203
+ }
9146
9204
  const siblingPaused = (sib) => openQuestions.has(sib.evidentMessageId) || openPermissions.has(sib.evidentMessageId) || sib.awaitingHumanLatched || sib.pausedOnQuestion || sib.pausedOnPermission;
9147
9205
  const hasActivelyRunningSibling = [...watcher.inFlight.values()].some(
9148
9206
  (sib) => sib.evidentMessageId !== inFlight.evidentMessageId && messageRunState(messages, sib.opencodeMessageId) === "running" && !siblingPaused(sib)
@@ -9153,15 +9211,15 @@ var ChannelDriver = class _ChannelDriver {
9153
9211
  const attempts = this.recordNeverStartedAttempt(id, sessionId);
9154
9212
  if (attempts >= MAX_NEVER_STARTED_ATTEMPTS) {
9155
9213
  inFlight.neverStartedResolved = true;
9156
- const watchedForMs = this.now() - inFlight.dispatchedAt;
9214
+ const watchedForMs2 = this.now() - inFlight.dispatchedAt;
9157
9215
  this.log({
9158
9216
  level: "error",
9159
- message: `Message ${id.slice(0, 8)} never started after ${attempts} attempts (${watchedForMs}ms watched) \u2014 reporting the run failed; the accepted OpenCode copy was not cancelled`,
9217
+ message: `Message ${id.slice(0, 8)} never started after ${attempts} attempts (${watchedForMs2}ms watched) \u2014 reporting the run failed; the accepted OpenCode copy was not cancelled`,
9160
9218
  conversation_id: conv.id,
9161
9219
  message_id: id
9162
9220
  });
9163
9221
  void this.postSignal(conv.id, id, "never_started_failed", {
9164
- watched_for_ms: watchedForMs,
9222
+ watched_for_ms: watchedForMs2,
9165
9223
  attempts
9166
9224
  });
9167
9225
  await this.settleMessageNeverStarted(sessionId, watcher, inFlight, attempts);
@@ -9175,7 +9233,8 @@ var ChannelDriver = class _ChannelDriver {
9175
9233
  message_id: inFlight.evidentMessageId
9176
9234
  });
9177
9235
  void this.postSignal(conv.id, inFlight.evidentMessageId, "gave_up", {
9178
- watched_for_ms: this.now() - inFlight.dispatchedAt
9236
+ watched_for_ms: this.now() - inFlight.dispatchedAt,
9237
+ run_state: state
9179
9238
  });
9180
9239
  this.recordReleasedOpencodeId(
9181
9240
  inFlight.evidentMessageId,
@@ -9632,7 +9691,8 @@ var ChannelDriver = class _ChannelDriver {
9632
9691
  if ((state === "running" || state === "queued") && ocId) {
9633
9692
  const conv = this.convForRow(sessionId, row);
9634
9693
  const message = this.queuedMessageForRow(row);
9635
- this.registerReadopted(conv, sessionId, message, ocId, this.processedAtMs(row));
9694
+ const processedAtMs = this.processedAtMs(row);
9695
+ this.registerReadopted(conv, sessionId, message, ocId, processedAtMs, processedAtMs);
9636
9696
  this.dispatched.add(row.id);
9637
9697
  this.readopted.add(row.id);
9638
9698
  this.ensureWatcherRunning(sessionId);
@@ -9927,7 +9987,15 @@ var ChannelDriver = class _ChannelDriver {
9927
9987
  return;
9928
9988
  }
9929
9989
  this.unconfirmedDispatchFailures.delete(row.id);
9930
- this.registerReadopted(readoptConv, sessionId, readoptMessage, ocId, this.processedAtMs(row));
9990
+ const processedAtMs = this.processedAtMs(row);
9991
+ this.registerReadopted(
9992
+ readoptConv,
9993
+ sessionId,
9994
+ readoptMessage,
9995
+ ocId,
9996
+ processedAtMs,
9997
+ processedAtMs
9998
+ );
9931
9999
  this.dispatched.add(row.id);
9932
10000
  this.readopted.add(row.id);
9933
10001
  this.awaitingReadopt.delete(row.id);
@@ -11347,25 +11415,6 @@ Port ${port} is already in use.`));
11347
11415
  import chalk6 from "chalk";
11348
11416
  import ora3 from "ora";
11349
11417
  import { select as select3 } from "@inquirer/prompts";
11350
- async function probeOpenCode2WithoutPassword(port) {
11351
- try {
11352
- const response = await fetch(`http://127.0.0.1:${port}/global/health`, {
11353
- signal: AbortSignal.timeout(2e3)
11354
- });
11355
- if (response.status === 401) {
11356
- return { healthy: false, authFailed: true, error: "HTTP 401" };
11357
- }
11358
- if (!response.ok) {
11359
- return { healthy: false, error: `HTTP ${response.status}` };
11360
- }
11361
- return { healthy: true };
11362
- } catch (error2) {
11363
- return {
11364
- healthy: false,
11365
- error: error2 instanceof Error ? error2.message : "Unknown error"
11366
- };
11367
- }
11368
- }
11369
11418
  function unknownPasswordError(port) {
11370
11419
  return new Error(
11371
11420
  `OpenCode V2 (opencode2) is already running on port ${port} with a password this runner doesn't know. Free the port or pass --port.`
@@ -11373,7 +11422,7 @@ function unknownPasswordError(port) {
11373
11422
  }
11374
11423
  var INTERACTIVE_START_TIMEOUT_MS2 = 3e4;
11375
11424
  async function ensureOpenCode2Running(ctx) {
11376
- const initialHealth = await probeOpenCode2WithoutPassword(ctx.port);
11425
+ const initialHealth = await checkOpenCode2Health(ctx.port);
11377
11426
  if (initialHealth.authFailed) {
11378
11427
  throw unknownPasswordError(ctx.port);
11379
11428
  }
@@ -11388,7 +11437,7 @@ async function ensureOpenCode2Running(ctx) {
11388
11437
  }
11389
11438
  if (!isOpenCode2Installed()) {
11390
11439
  throw new Error(
11391
- "OpenCode V2 (opencode2) is not installed. Install it with: npm install -g @opencode-ai/cli@beta"
11440
+ `OpenCode V2 (opencode2) is not installed. Install it with: npm install -g ${OPENCODE2_PACKAGE_SPEC}`
11392
11441
  );
11393
11442
  }
11394
11443
  let port = ctx.port;