@tablation/crew 0.126.7 → 0.126.8

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 (2) hide show
  1. package/dist/cli.js +41 -4
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -15222,13 +15222,30 @@ function isPidAlive(pid) {
15222
15222
  }
15223
15223
  var CONTROL_ONLY_HASH = "control-only";
15224
15224
  var TUNNEL_REJECTED_AFTER = 2;
15225
+ var CONTROL_RETRY_MS = 60 * 60 * 1e3;
15226
+ var RELAY_CONTROL_UNSUPPORTED_KEY = "relay_control_unsupported";
15227
+ function relayControlUnsupportedMessage(workspaceId) {
15228
+ return `the relay refused the control-path forward for workspace ${workspaceId}, so the passengers tunnel runs without the control API (remote ship transport and periscopes will not reach this ship); the relay is too old for \`-path-control\` (crew-relay 0.2.0+) \u2014 deploy it with scripts/deploy-relay.sh`;
15229
+ }
15230
+ function controlRefusedTunnels(stateDir, now = Date.now()) {
15231
+ const out = [];
15232
+ for (const workspaceId of readAllPersistedWorkspaceIds(stateDir)) {
15233
+ const at = readPersistedTunnel(stateDir, workspaceId)?.controlRefusedAt;
15234
+ if (at && now - Date.parse(at) < CONTROL_RETRY_MS) out.push(workspaceId);
15235
+ }
15236
+ return out;
15237
+ }
15225
15238
  function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, stateDir, mcpSecrets = /* @__PURE__ */ new Map(), deps = {}) {
15226
15239
  const alive = deps.isPidAlive ?? isPidAlive;
15227
15240
  const plannedIds = new Set(plans.map((p) => p.workspaceId));
15228
15241
  for (const plan of plans) {
15229
15242
  const existing = readPersistedTunnel(stateDir, plan.workspaceId);
15230
15243
  const mountsHash = plan.mountsHash ?? CONTROL_ONLY_HASH;
15231
- const controlUnchanged = existing?.controlSocket === deps.controlSocket;
15244
+ const refusedAt = existing?.controlRefusedAt;
15245
+ const refusalFresh = refusedAt !== void 0 && Date.now() - Date.parse(refusedAt) < CONTROL_RETRY_MS;
15246
+ let controlSocket = deps.controlSocket && refusalFresh ? void 0 : deps.controlSocket;
15247
+ let controlRefusedAt = refusalFresh ? refusedAt : void 0;
15248
+ const controlUnchanged = existing?.controlSocket === controlSocket;
15232
15249
  const currentSecret = mcpSecrets.get(plan.workspaceId);
15233
15250
  const secretUnchanged = (existing?.mcpSecret ?? void 0) === (currentSecret ?? void 0);
15234
15251
  const slug2 = existing?.slug ?? generateTunnelSlug();
@@ -15253,7 +15270,12 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
15253
15270
  continue;
15254
15271
  }
15255
15272
  const diedQuickly = existing !== void 0 && existing.status === "connecting" && !alive(existing.pid) && controlUnchanged && secretUnchanged && slugUnchanged && existing.mountsHash === mountsHash;
15256
- const quickExits = diedQuickly ? (existing.quickExits ?? 0) + 1 : 0;
15273
+ let quickExits = diedQuickly ? (existing.quickExits ?? 0) + 1 : 0;
15274
+ if (diedQuickly && controlSocket && plan.port !== void 0) {
15275
+ controlSocket = void 0;
15276
+ controlRefusedAt = (/* @__PURE__ */ new Date()).toISOString();
15277
+ quickExits = 0;
15278
+ }
15257
15279
  let settledStatus = "connecting";
15258
15280
  const tunnel = startTunnel({
15259
15281
  relayHost,
@@ -15262,7 +15284,7 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
15262
15284
  slug: slug2,
15263
15285
  apiKey,
15264
15286
  localPort: plan.port,
15265
- controlSocket: deps.controlSocket,
15287
+ controlSocket,
15266
15288
  privateKeyPath,
15267
15289
  mcpSecret: currentSecret,
15268
15290
  spawnFn: deps.spawnFn,
@@ -15275,7 +15297,8 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
15275
15297
  containerName: plan.containerName ?? "",
15276
15298
  workspaceId: plan.workspaceId,
15277
15299
  mountsHash,
15278
- controlSocket: deps.controlSocket,
15300
+ controlSocket,
15301
+ controlRefusedAt,
15279
15302
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
15280
15303
  status: settledStatus,
15281
15304
  mcpSecret: currentSecret,
@@ -25545,6 +25568,15 @@ if (command === "passenger-sync-daemon") {
25545
25568
  since: (/* @__PURE__ */ new Date()).toISOString()
25546
25569
  }).then(() => void 0).catch(() => void 0));
25547
25570
  } else if (status2 === "connected") clearShipAttention(state, rejectedKey);
25571
+ const controlKey = `${RELAY_CONTROL_UNSUPPORTED_KEY}:${workspaceId}`;
25572
+ if (info?.controlRefusedAt) {
25573
+ pending.push(raiseShipAttention({ state, emit, route: owningRoute, ship: cfg.ship }, {
25574
+ kind: RELAY_CONTROL_UNSUPPORTED_KEY,
25575
+ key: controlKey,
25576
+ message: relayControlUnsupportedMessage(workspaceId),
25577
+ since: (/* @__PURE__ */ new Date()).toISOString()
25578
+ }).then(() => void 0).catch(() => void 0));
25579
+ } else if (status2 === "connected") clearShipAttention(state, controlKey);
25548
25580
  }
25549
25581
  });
25550
25582
  for (const plan of containerPlans) pending.push(reconcileEndpoint(plan.workspaceId, reconcile));
@@ -30245,6 +30277,9 @@ watermark: ${state.watermark()}
30245
30277
  for (const t of rejectedTunnels(cfg.ship.stateDir)) {
30246
30278
  problems.push({ severity: "attention", kind: TUNNEL_REJECTED_KEY, message: tunnelRejectedMessage(t.workspaceId, t.quickExits), remedy: "rotate the slug with `crew rotate-passenger-url`" });
30247
30279
  }
30280
+ for (const workspaceId of controlRefusedTunnels(cfg.ship.stateDir)) {
30281
+ problems.push({ severity: "attention", kind: RELAY_CONTROL_UNSUPPORTED_KEY, message: relayControlUnsupportedMessage(workspaceId), remedy: "deploy crew-relay 0.2.0 or later (scripts/deploy-relay.sh in the crew-relay repository)" });
30282
+ }
30248
30283
  const previewProblem2 = await previewHostProblem(cfg);
30249
30284
  if (previewProblem2) problems.push(previewHostFinding(previewProblem2));
30250
30285
  if (cfg.routes.some((x) => x.route === route2.route && x.enabled && x.hostPeriscopes)) {
@@ -30267,6 +30302,8 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
30267
30302
  for (const w of roleCapWarnings(cfg.ship)) process.stdout.write(`role concurrency: WARN ${w}
30268
30303
  `);
30269
30304
  for (const t of rejectedTunnels(cfg.ship.stateDir)) process.stdout.write(`relay tunnel: ATTENTION ${tunnelRejectedMessage(t.workspaceId, t.quickExits)}
30305
+ `);
30306
+ for (const workspaceId of controlRefusedTunnels(cfg.ship.stateDir)) process.stdout.write(`relay tunnel: ATTENTION ${relayControlUnsupportedMessage(workspaceId)}
30270
30307
  `);
30271
30308
  process.stdout.write(`periscopes: ${periscopeSummary(cfg)}
30272
30309
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tablation/crew",
3
- "version": "0.126.7",
3
+ "version": "0.126.8",
4
4
  "description": "A standing team of headless agents that picks work off a Tablation board, does it on a machine you control, and reports back.",
5
5
  "type": "module",
6
6
  "license": "MIT",