@tablation/crew 0.126.7 → 0.126.9

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 +55 -4
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -14892,6 +14892,20 @@ async function runProvision(o, deps, write, human) {
14892
14892
  emit("route_enabled", `enabled route ${route2}`, { route: route2 });
14893
14893
  }
14894
14894
  }
14895
+ summary.seatKeys = [];
14896
+ for (const route2 of routeNames) {
14897
+ const res = deps.crew(["agents", "sync", route2]);
14898
+ const lines2 = res.stdout.split("\n").map((l) => l.trim());
14899
+ const minted = lines2.flatMap((l) => /^(\w+): agent key minted/.exec(l)?.[1] ?? []);
14900
+ const refused = lines2.filter((l) => /could not mint an agent key|^seat keys: not minted/.test(l));
14901
+ summary.seatKeys.push({ route: route2, minted });
14902
+ if (minted.length > 0) summary.changed = true;
14903
+ emit("seat_keys", `seat keys: ${minted.length} minted${minted.length > 0 ? ` (${minted.join(", ")})` : ""} for ${route2}`, { route: route2, minted });
14904
+ if (res.status >= 2 || refused.length > 0) {
14905
+ const why = refused[0] ?? (res.stderr.trim().split("\n").pop() || `crew agents sync exited ${res.status}`);
14906
+ return fail2(PROVISION_EXIT.failed, `seat keys for ${route2}: ${why}`, { stage: "seat_keys", route: route2 });
14907
+ }
14908
+ }
14895
14909
  const before = deps.installState();
14896
14910
  if (before.unitsStale || !before.running || !before.lingering) {
14897
14911
  const res = deps.crew(["install"]);
@@ -15222,13 +15236,30 @@ function isPidAlive(pid) {
15222
15236
  }
15223
15237
  var CONTROL_ONLY_HASH = "control-only";
15224
15238
  var TUNNEL_REJECTED_AFTER = 2;
15239
+ var CONTROL_RETRY_MS = 60 * 60 * 1e3;
15240
+ var RELAY_CONTROL_UNSUPPORTED_KEY = "relay_control_unsupported";
15241
+ function relayControlUnsupportedMessage(workspaceId) {
15242
+ 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`;
15243
+ }
15244
+ function controlRefusedTunnels(stateDir, now = Date.now()) {
15245
+ const out = [];
15246
+ for (const workspaceId of readAllPersistedWorkspaceIds(stateDir)) {
15247
+ const at = readPersistedTunnel(stateDir, workspaceId)?.controlRefusedAt;
15248
+ if (at && now - Date.parse(at) < CONTROL_RETRY_MS) out.push(workspaceId);
15249
+ }
15250
+ return out;
15251
+ }
15225
15252
  function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, stateDir, mcpSecrets = /* @__PURE__ */ new Map(), deps = {}) {
15226
15253
  const alive = deps.isPidAlive ?? isPidAlive;
15227
15254
  const plannedIds = new Set(plans.map((p) => p.workspaceId));
15228
15255
  for (const plan of plans) {
15229
15256
  const existing = readPersistedTunnel(stateDir, plan.workspaceId);
15230
15257
  const mountsHash = plan.mountsHash ?? CONTROL_ONLY_HASH;
15231
- const controlUnchanged = existing?.controlSocket === deps.controlSocket;
15258
+ const refusedAt = existing?.controlRefusedAt;
15259
+ const refusalFresh = refusedAt !== void 0 && Date.now() - Date.parse(refusedAt) < CONTROL_RETRY_MS;
15260
+ let controlSocket = deps.controlSocket && refusalFresh ? void 0 : deps.controlSocket;
15261
+ let controlRefusedAt = refusalFresh ? refusedAt : void 0;
15262
+ const controlUnchanged = existing?.controlSocket === controlSocket;
15232
15263
  const currentSecret = mcpSecrets.get(plan.workspaceId);
15233
15264
  const secretUnchanged = (existing?.mcpSecret ?? void 0) === (currentSecret ?? void 0);
15234
15265
  const slug2 = existing?.slug ?? generateTunnelSlug();
@@ -15253,7 +15284,12 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
15253
15284
  continue;
15254
15285
  }
15255
15286
  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;
15287
+ let quickExits = diedQuickly ? (existing.quickExits ?? 0) + 1 : 0;
15288
+ if (diedQuickly && controlSocket && plan.port !== void 0) {
15289
+ controlSocket = void 0;
15290
+ controlRefusedAt = (/* @__PURE__ */ new Date()).toISOString();
15291
+ quickExits = 0;
15292
+ }
15257
15293
  let settledStatus = "connecting";
15258
15294
  const tunnel = startTunnel({
15259
15295
  relayHost,
@@ -15262,7 +15298,7 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
15262
15298
  slug: slug2,
15263
15299
  apiKey,
15264
15300
  localPort: plan.port,
15265
- controlSocket: deps.controlSocket,
15301
+ controlSocket,
15266
15302
  privateKeyPath,
15267
15303
  mcpSecret: currentSecret,
15268
15304
  spawnFn: deps.spawnFn,
@@ -15275,7 +15311,8 @@ function syncPassengerTunnels(plans, relayHost, relayPort, privateKeyPath, state
15275
15311
  containerName: plan.containerName ?? "",
15276
15312
  workspaceId: plan.workspaceId,
15277
15313
  mountsHash,
15278
- controlSocket: deps.controlSocket,
15314
+ controlSocket,
15315
+ controlRefusedAt,
15279
15316
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
15280
15317
  status: settledStatus,
15281
15318
  mcpSecret: currentSecret,
@@ -25545,6 +25582,15 @@ if (command === "passenger-sync-daemon") {
25545
25582
  since: (/* @__PURE__ */ new Date()).toISOString()
25546
25583
  }).then(() => void 0).catch(() => void 0));
25547
25584
  } else if (status2 === "connected") clearShipAttention(state, rejectedKey);
25585
+ const controlKey = `${RELAY_CONTROL_UNSUPPORTED_KEY}:${workspaceId}`;
25586
+ if (info?.controlRefusedAt) {
25587
+ pending.push(raiseShipAttention({ state, emit, route: owningRoute, ship: cfg.ship }, {
25588
+ kind: RELAY_CONTROL_UNSUPPORTED_KEY,
25589
+ key: controlKey,
25590
+ message: relayControlUnsupportedMessage(workspaceId),
25591
+ since: (/* @__PURE__ */ new Date()).toISOString()
25592
+ }).then(() => void 0).catch(() => void 0));
25593
+ } else if (status2 === "connected") clearShipAttention(state, controlKey);
25548
25594
  }
25549
25595
  });
25550
25596
  for (const plan of containerPlans) pending.push(reconcileEndpoint(plan.workspaceId, reconcile));
@@ -30245,6 +30291,9 @@ watermark: ${state.watermark()}
30245
30291
  for (const t of rejectedTunnels(cfg.ship.stateDir)) {
30246
30292
  problems.push({ severity: "attention", kind: TUNNEL_REJECTED_KEY, message: tunnelRejectedMessage(t.workspaceId, t.quickExits), remedy: "rotate the slug with `crew rotate-passenger-url`" });
30247
30293
  }
30294
+ for (const workspaceId of controlRefusedTunnels(cfg.ship.stateDir)) {
30295
+ 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)" });
30296
+ }
30248
30297
  const previewProblem2 = await previewHostProblem(cfg);
30249
30298
  if (previewProblem2) problems.push(previewHostFinding(previewProblem2));
30250
30299
  if (cfg.routes.some((x) => x.route === route2.route && x.enabled && x.hostPeriscopes)) {
@@ -30267,6 +30316,8 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
30267
30316
  for (const w of roleCapWarnings(cfg.ship)) process.stdout.write(`role concurrency: WARN ${w}
30268
30317
  `);
30269
30318
  for (const t of rejectedTunnels(cfg.ship.stateDir)) process.stdout.write(`relay tunnel: ATTENTION ${tunnelRejectedMessage(t.workspaceId, t.quickExits)}
30319
+ `);
30320
+ for (const workspaceId of controlRefusedTunnels(cfg.ship.stateDir)) process.stdout.write(`relay tunnel: ATTENTION ${relayControlUnsupportedMessage(workspaceId)}
30270
30321
  `);
30271
30322
  process.stdout.write(`periscopes: ${periscopeSummary(cfg)}
30272
30323
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tablation/crew",
3
- "version": "0.126.7",
3
+ "version": "0.126.9",
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",