@tablation/crew 0.126.5 → 0.126.7

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 +214 -125
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -690,9 +690,9 @@ function urlPort(url) {
690
690
  return void 0;
691
691
  }
692
692
  }
693
- function portListening(port, timeoutMs = 500) {
693
+ function hostListening(port, host, timeoutMs) {
694
694
  return new Promise((resolve8) => {
695
- const s = connect({ port, host: "127.0.0.1" });
695
+ const s = connect({ port, host });
696
696
  const done = (ok) => {
697
697
  s.destroy();
698
698
  resolve8(ok);
@@ -702,6 +702,10 @@ function portListening(port, timeoutMs = 500) {
702
702
  s.once("error", () => done(false));
703
703
  });
704
704
  }
705
+ async function portListening(port, timeoutMs = 500) {
706
+ if (await hostListening(port, "127.0.0.1", timeoutMs)) return true;
707
+ return hostListening(port, "::1", timeoutMs);
708
+ }
705
709
  async function periscopesForStatus(stateDir, probe = portListening) {
706
710
  return Promise.all(readPeriscopeSidecars(stateDir).map(async (sc) => {
707
711
  const port = urlPort(sc.url);
@@ -12000,6 +12004,21 @@ function slugFor(route2, agentId) {
12000
12004
  const [workspaceSlug] = splitRoute(route2.route);
12001
12005
  return seatKeySlug(workspaceSlug, agentId);
12002
12006
  }
12007
+ function withFoundSeats(route2, found, previousReleaseAgentId) {
12008
+ const resolved = route2.resolved;
12009
+ if (!resolved) return route2;
12010
+ const releaseSeat2 = found.releaseSeat ?? resolved.releaseSeat;
12011
+ const releaseAgentId = resolved.releaseAgentId ?? previousReleaseAgentId;
12012
+ return {
12013
+ ...route2,
12014
+ resolved: {
12015
+ ...resolved,
12016
+ seats: found.seats,
12017
+ ...releaseSeat2 ? { releaseSeat: releaseSeat2 } : {},
12018
+ ...releaseAgentId ? { releaseAgentId } : {}
12019
+ }
12020
+ };
12021
+ }
12003
12022
  function seatAgentId(route2, role) {
12004
12023
  return route2.resolved?.agentPersonas?.[role]?.agentId;
12005
12024
  }
@@ -12492,6 +12511,15 @@ printf '%s
12492
12511
  ' "$tip" > "$out/$name.tip"
12493
12512
  echo "updated $tip"
12494
12513
  `;
12514
+ var SHOW_SCRIPT = `
12515
+ set -e
12516
+ export GIT_TERMINAL_PROMPT=0
12517
+ m=${VOLUME_ROOT}/"$1"; base="$2"; path="$3"
12518
+ git config --global --add safe.directory '*'
12519
+ git -C "$m" rev-parse --verify --quiet "refs/heads/$base^{commit}" >/dev/null || { echo "the mirror has no branch $base" >&2; exit 3; }
12520
+ git -C "$m" cat-file -e "refs/heads/$base:$path" 2>/dev/null || exit 4
12521
+ git -C "$m" show "refs/heads/$base:$path"
12522
+ `;
12495
12523
  var SandboxCache = class {
12496
12524
  s;
12497
12525
  exec;
@@ -12578,6 +12606,20 @@ var SandboxCache = class {
12578
12606
  if (!m) throw new Error(`unexpected output materializing ${repo.host}/${repo.path}: ${stdout.trim().slice(0, 200)}`);
12579
12607
  return { tip: m[2], changed: m[1] === "updated" };
12580
12608
  }
12609
+ /**
12610
+ * The text of `path` at the tip of `base` in the repository's mirror, or
12611
+ * null when the mirror has no such file. Throws when the mirror or the
12612
+ * branch is missing, so the caller can tell "no contract" from "no mirror".
12613
+ */
12614
+ async showFile(repo, base, path) {
12615
+ try {
12616
+ const { stdout } = await this.helper(this.s.helperImage, SHOW_SCRIPT, [mirrorRelPath(repo), base, path]);
12617
+ return stdout;
12618
+ } catch (e) {
12619
+ if (e.code === 4) return null;
12620
+ throw e;
12621
+ }
12622
+ }
12581
12623
  /** Mirrors in the volume with their last refresh; empty when the volume does not exist. */
12582
12624
  async list() {
12583
12625
  try {
@@ -14051,7 +14093,7 @@ async function runPoolMember(deps) {
14051
14093
  await deps.sleep(backoffMs(failures++));
14052
14094
  }
14053
14095
  };
14054
- let reported;
14096
+ let reported2;
14055
14097
  let busy = false;
14056
14098
  const report = async (state, o = {}) => {
14057
14099
  const body = {
@@ -14074,7 +14116,7 @@ async function runPoolMember(deps) {
14074
14116
  if (r.status === 401) return "credential";
14075
14117
  if (r.status === 409) deps.log(`status ${state}: the platform refused the transition (409); not retrying`);
14076
14118
  else if (r.status >= 400) deps.log(`status ${state}: platform answered ${r.status}; not retrying`);
14077
- reported = body;
14119
+ reported2 = body;
14078
14120
  deps.store.write({ ...deps.store.read(), lastState: state });
14079
14121
  return "ok";
14080
14122
  };
@@ -14157,7 +14199,7 @@ async function runPoolMember(deps) {
14157
14199
  if (r === "stopped") return POOL_EXIT.ok;
14158
14200
  }
14159
14201
  }
14160
- let relayReported = reported?.state === "running" ? reported.relayUrl : null;
14202
+ let relayReported = reported2?.state === "running" ? reported2.relayUrl : null;
14161
14203
  let reregister = false;
14162
14204
  while (!reregister) {
14163
14205
  if (deps.stopping()) return POOL_EXIT.ok;
@@ -19072,6 +19114,73 @@ function runForge(a, io) {
19072
19114
  `, err: "" };
19073
19115
  }
19074
19116
 
19117
+ // src/mirror-contract.ts
19118
+ import { existsSync as existsSync40 } from "fs";
19119
+
19120
+ // src/passenger-mirror.ts
19121
+ import { mkdirSync as mkdirSync29 } from "fs";
19122
+ import { dirname as dirname25 } from "path";
19123
+ function mirrorRepoOf(spec) {
19124
+ const fromOrigin = spec.originUrl ? parseRemote(spec.originUrl) : null;
19125
+ if (fromOrigin) return fromOrigin;
19126
+ if (!spec.remote) return null;
19127
+ return { host: normalizeForgeHost(spec.forgeHost) || DEFAULT_FORGE_HOST, path: spec.remote };
19128
+ }
19129
+ async function materializeMirrorCheckouts(specs, cacheFor = (s) => new SandboxCache(s.mirror.cache)) {
19130
+ const out = [];
19131
+ for (const spec of specs) {
19132
+ if (!spec.mirror) continue;
19133
+ const base = { name: spec.name, workspaceId: spec.workspaceId };
19134
+ const repo = mirrorRepoOf(spec);
19135
+ if (!repo) {
19136
+ out.push({ ...base, action: "failed", detail: `no remote known for ${spec.name}, so its mirror cannot be found` });
19137
+ continue;
19138
+ }
19139
+ try {
19140
+ const parent = dirname25(spec.dir);
19141
+ mkdirSync29(parent, { recursive: true });
19142
+ mkdirSync29(spec.dir, { recursive: true });
19143
+ const r = await cacheFor(spec).materialize(repo, spec.base, parent, spec.name);
19144
+ out.push({ ...base, action: r.changed ? "updated" : "unchanged", detail: r.tip });
19145
+ } catch (e) {
19146
+ const err3 = e;
19147
+ out.push({ ...base, action: "failed", detail: (err3.stderr?.trim() || err3.message || String(e)).split("\n").slice(-3).join(" / ") });
19148
+ }
19149
+ }
19150
+ return out;
19151
+ }
19152
+ function mirrorOutcome(m) {
19153
+ if (m.action === "failed") return { action: "clone-failed", detail: m.detail, repeat: false };
19154
+ return { action: "cloned", detail: `${m.action} ${m.detail}` };
19155
+ }
19156
+
19157
+ // src/mirror-contract.ts
19158
+ function lacksCheckout(ship, dir) {
19159
+ return ship.runner === "sandbox" && !existsSync40(dir);
19160
+ }
19161
+ function mirrorRepoOfTarget(t) {
19162
+ return mirrorRepoOf({ remote: t.remote, forgeHost: t.forgeHost });
19163
+ }
19164
+ async function readMirrorContract(cache, repo, base) {
19165
+ for (const name of REPO_CONFIG_FILENAMES) {
19166
+ const text2 = await cache.showFile(repo, base, name);
19167
+ if (text2 !== null) return parseRepoConfig(text2, `${repo.host}/${repo.path}:${base}:${name}`);
19168
+ }
19169
+ return null;
19170
+ }
19171
+ function noCheckoutReleaseNote(repo, mode) {
19172
+ if (mode === "local" || mode === null) {
19173
+ return `${repo}: no checkout on this ship; release.mode "local" repositories are released by the host that owns the deploy hook, which takes a ticket at verified`;
19174
+ }
19175
+ return `${repo}: no checkout on this ship; release.mode "${mode}" needs the sandboxed release over a mirror-fed clone, which is not built (CREW-2940)`;
19176
+ }
19177
+ var reported = /* @__PURE__ */ new Set();
19178
+ function firstReport(key) {
19179
+ if (reported.has(key)) return false;
19180
+ reported.add(key);
19181
+ return true;
19182
+ }
19183
+
19075
19184
  // src/whoami.ts
19076
19185
  async function buildWhoami(routes, deps = {}) {
19077
19186
  const doFetch = deps.fetch ?? fetch;
@@ -19243,7 +19352,7 @@ function missingReleaserWarning(repo, release, ships, now = Date.now()) {
19243
19352
  }
19244
19353
 
19245
19354
  // src/watch.ts
19246
- import { watch as watchFile, existsSync as existsSync40, statSync as statSync11, openSync as openSync5, readSync as readSync2, closeSync as closeSync5 } from "fs";
19355
+ import { watch as watchFile, existsSync as existsSync41, statSync as statSync11, openSync as openSync5, readSync as readSync2, closeSync as closeSync5 } from "fs";
19247
19356
  var E = "\x1B[";
19248
19357
  var dim = (s) => `${E}2m${s}${E}0m`;
19249
19358
  var bold = (s) => `${E}1m${s}${E}0m`;
@@ -19344,7 +19453,7 @@ function renderHeaders(states, now = Date.now()) {
19344
19453
  return states.map((s) => renderHeader(s, now)).join("");
19345
19454
  }
19346
19455
  function readFrom(file, from) {
19347
- if (!existsSync40(file)) return { events: [], offset: from };
19456
+ if (!existsSync41(file)) return { events: [], offset: from };
19348
19457
  const size = statSync11(file).size;
19349
19458
  if (size < from) return readFrom(file, 0);
19350
19459
  if (size === from) return { events: [], offset: from };
@@ -19587,8 +19696,8 @@ function nextRoles(decision, ran) {
19587
19696
  }
19588
19697
 
19589
19698
  // src/ship-name.ts
19590
- import { existsSync as existsSync41, mkdirSync as mkdirSync29, readFileSync as readFileSync37, renameSync as renameSync5, writeFileSync as writeFileSync32 } from "fs";
19591
- import { dirname as dirname25 } from "path";
19699
+ import { existsSync as existsSync42, mkdirSync as mkdirSync30, readFileSync as readFileSync37, renameSync as renameSync5, writeFileSync as writeFileSync32 } from "fs";
19700
+ import { dirname as dirname26 } from "path";
19592
19701
  import { parseDocument as parseDocument5 } from "yaml";
19593
19702
  var ShipNameError = class extends Error {
19594
19703
  };
@@ -19649,7 +19758,7 @@ async function syncRouteName(i) {
19649
19758
  return { status: "ok", ...base, action, name: i.local };
19650
19759
  }
19651
19760
  function readSyncedName(resolvedFile) {
19652
- if (!existsSync41(resolvedFile)) return void 0;
19761
+ if (!existsSync42(resolvedFile)) return void 0;
19653
19762
  try {
19654
19763
  const v = JSON.parse(readFileSync37(resolvedFile, "utf8")).shipName;
19655
19764
  return typeof v === "string" ? v : void 0;
@@ -19658,7 +19767,7 @@ function readSyncedName(resolvedFile) {
19658
19767
  }
19659
19768
  }
19660
19769
  function writeSyncedName(resolvedFile, name) {
19661
- if (!existsSync41(resolvedFile)) return;
19770
+ if (!existsSync42(resolvedFile)) return;
19662
19771
  let raw;
19663
19772
  try {
19664
19773
  raw = JSON.parse(readFileSync37(resolvedFile, "utf8"));
@@ -19667,7 +19776,7 @@ function writeSyncedName(resolvedFile, name) {
19667
19776
  }
19668
19777
  if (raw.shipName === name) return;
19669
19778
  raw.shipName = name;
19670
- mkdirSync29(dirname25(resolvedFile), { recursive: true });
19779
+ mkdirSync30(dirname26(resolvedFile), { recursive: true });
19671
19780
  const tmp = `${resolvedFile}.tmp${process.pid}`;
19672
19781
  writeFileSync32(tmp, `${JSON.stringify(raw, null, 2)}
19673
19782
  `);
@@ -20360,8 +20469,8 @@ function describeCapability(i) {
20360
20469
  }
20361
20470
 
20362
20471
  // src/agents-sync-run.ts
20363
- import { existsSync as existsSync42, mkdirSync as mkdirSync30, readFileSync as readFileSync38, writeFileSync as writeFileSync33 } from "fs";
20364
- import { dirname as dirname26 } from "path";
20472
+ import { existsSync as existsSync43, mkdirSync as mkdirSync31, readFileSync as readFileSync38, writeFileSync as writeFileSync33 } from "fs";
20473
+ import { dirname as dirname27 } from "path";
20365
20474
  async function runAgentsSync(target, o) {
20366
20475
  const dryRun = o.dryRun ?? false;
20367
20476
  const force = o.force ?? false;
@@ -20394,11 +20503,11 @@ async function runAgentsSync(target, o) {
20394
20503
  for (const s of skillResult?.outcomes ?? []) o.say(describeSkillSyncOutcome(s));
20395
20504
  if (!dryRun) {
20396
20505
  const resolvedPath = resolvedPathFor(o.stateDir, target.route);
20397
- const raw = existsSync42(resolvedPath) ? JSON.parse(readFileSync38(resolvedPath, "utf8")) : {};
20506
+ const raw = existsSync43(resolvedPath) ? JSON.parse(readFileSync38(resolvedPath, "utf8")) : {};
20398
20507
  raw.agentPersonas = personaResult.agentPersonas;
20399
20508
  if (skillResult) raw.agentSkills = skillResult.agentSkills;
20400
20509
  if (personaResult.releaseAgentId) raw.releaseAgentId = personaResult.releaseAgentId;
20401
- mkdirSync30(dirname26(resolvedPath), { recursive: true });
20510
+ mkdirSync31(dirname27(resolvedPath), { recursive: true });
20402
20511
  writeFileSync33(resolvedPath, `${JSON.stringify(raw, null, 2)}
20403
20512
  `);
20404
20513
  if (target.resolved) {
@@ -21021,7 +21130,7 @@ ${run3.err}` });
21021
21130
  }
21022
21131
 
21023
21132
  // src/sample-ticket.ts
21024
- import { basename as basename12, dirname as dirname27, join as join44 } from "path";
21133
+ import { basename as basename12, dirname as dirname28, join as join44 } from "path";
21025
21134
  var SAMPLE_REPO = "crew-sample";
21026
21135
  var SAMPLE_REMOTE = `local/${SAMPLE_REPO}`;
21027
21136
  var SAMPLE_MARKER = "sample: true";
@@ -21044,7 +21153,7 @@ has something safe to work on: its test and build hooks always pass, it has no
21044
21153
  remote, and \`crew sample-ticket --remove\` deletes it.
21045
21154
  `;
21046
21155
  function sampleDir(existing, reposBase, workspace) {
21047
- if (existing.length > 0) return join44(dirname27(existing[0]), SAMPLE_REPO);
21156
+ if (existing.length > 0) return join44(dirname28(existing[0]), SAMPLE_REPO);
21048
21157
  return join44(reposBase, workspace, SAMPLE_REPO);
21049
21158
  }
21050
21159
  var SAMPLE_TITLE = "Sample: add a documented `greet` function with a test";
@@ -21089,8 +21198,8 @@ function planSampleTicket(i) {
21089
21198
  }
21090
21199
 
21091
21200
  // src/worktree-sweep.ts
21092
- import { existsSync as existsSync43, readFileSync as readFileSync40, readdirSync as readdirSync14 } from "fs";
21093
- import { basename as basename13, dirname as dirname28, join as join45, resolve as resolve5 } from "path";
21201
+ import { existsSync as existsSync44, readFileSync as readFileSync40, readdirSync as readdirSync14 } from "fs";
21202
+ import { basename as basename13, dirname as dirname29, join as join45, resolve as resolve5 } from "path";
21094
21203
  function planWorktreeSweep(target, tickets, contract) {
21095
21204
  const terminal = new Set(contract.statuses.resolved.filter((s) => s !== contract.statuses.verified));
21096
21205
  const verified = contract.statuses.verified;
@@ -21154,7 +21263,7 @@ function worktreeTicketNumbers(target) {
21154
21263
  return [...nums];
21155
21264
  }
21156
21265
  function siblingWorktreeDirs(target) {
21157
- const parent = dirname28(target.dir.replace(/[/\\]+$/, ""));
21266
+ const parent = dirname29(target.dir.replace(/[/\\]+$/, ""));
21158
21267
  const stem = `${basename13(target.dir.replace(/[/\\]+$/, ""))}-`;
21159
21268
  const registered = new Set(worktrees(target.dir).map((w) => resolve5(w.path)));
21160
21269
  let names;
@@ -21202,7 +21311,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
21202
21311
  let removed = 0;
21203
21312
  let keptBranches = 0;
21204
21313
  for (const a of actions) {
21205
- if (!dryRun && base && !a.keepBranch && existsSync43(a.path) && isHandBackMerge(a.path, base)) {
21314
+ if (!dryRun && base && !a.keepBranch && existsSync44(a.path) && isHandBackMerge(a.path, base)) {
21206
21315
  if (gitOk(a.path, ["merge", "--abort"]) !== null) {
21207
21316
  log.emit(
21208
21317
  `abandoned the hand-back merge of ${base} in ${a.path}: ${a.ticket.issue_id} deployed without it`,
@@ -21210,7 +21319,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
21210
21319
  );
21211
21320
  }
21212
21321
  }
21213
- if (!dryRun && existsSync43(a.path)) {
21322
+ if (!dryRun && existsSync44(a.path)) {
21214
21323
  let dirty = false;
21215
21324
  try {
21216
21325
  dirty = status(a.path).length > 0;
@@ -21221,7 +21330,7 @@ async function applyWorktreeSweep(cwd, actions, dryRun, log, remote = "origin",
21221
21330
  continue;
21222
21331
  }
21223
21332
  }
21224
- if (!dryRun && a.requirePushed && a.branch && existsSync43(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
21333
+ if (!dryRun && a.requirePushed && a.branch && existsSync44(a.path) && remoteConfigured(cwd, remote) && worktreeHoldsUnpushed(a.path, remote)) {
21225
21334
  refuse2(a, "unpushed commits", `${a.branch} has commits that are not on ${remote}`);
21226
21335
  continue;
21227
21336
  }
@@ -21375,12 +21484,12 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
21375
21484
  }
21376
21485
 
21377
21486
  // src/stream-sweep.ts
21378
- import { existsSync as existsSync44, readdirSync as readdirSync15, rmSync as rmSync20, statSync as statSync12 } from "fs";
21487
+ import { existsSync as existsSync45, readdirSync as readdirSync15, rmSync as rmSync20, statSync as statSync12 } from "fs";
21379
21488
  import { join as join46 } from "path";
21380
21489
  var DAY_MS2 = 24 * 60 * 60 * 1e3;
21381
21490
  function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
21382
21491
  const dir = join46(stateDir, "streams");
21383
- if (!existsSync44(dir)) return [];
21492
+ if (!existsSync45(dir)) return [];
21384
21493
  const cutoff = now - retentionDays * DAY_MS2;
21385
21494
  return readdirSync15(dir).map((name) => join46(dir, name)).filter((path) => {
21386
21495
  try {
@@ -21408,7 +21517,7 @@ function applyStreamSweep(paths, dryRun, log) {
21408
21517
  return removed;
21409
21518
  }
21410
21519
  function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
21411
- if (!existsSync44(stateDir)) return [];
21520
+ if (!existsSync45(stateDir)) return [];
21412
21521
  const cutoff = now - retentionDays * DAY_MS2;
21413
21522
  return readdirSync15(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join46(stateDir, name)).filter((path) => {
21414
21523
  try {
@@ -21420,11 +21529,11 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
21420
21529
  }
21421
21530
 
21422
21531
  // src/cli.ts
21423
- import { realpathSync as realpathSync3, readFileSync as readFileSync46, existsSync as existsSync51, mkdirSync as mkdirSync38, writeFileSync as writeFileSync38, rmSync as rmSync25, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync6, statSync as statSync16, readdirSync as readdirSync20 } from "fs";
21532
+ import { realpathSync as realpathSync3, readFileSync as readFileSync46, existsSync as existsSync52, mkdirSync as mkdirSync38, writeFileSync as writeFileSync38, rmSync as rmSync25, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync6, statSync as statSync16, readdirSync as readdirSync20 } from "fs";
21424
21533
  import { createInterface } from "readline/promises";
21425
21534
 
21426
21535
  // src/periscope-cmd.ts
21427
- import { existsSync as existsSync45, mkdirSync as mkdirSync31, readFileSync as readFileSync41, rmSync as rmSync21, writeFileSync as writeFileSync34 } from "fs";
21536
+ import { existsSync as existsSync46, mkdirSync as mkdirSync32, readFileSync as readFileSync41, rmSync as rmSync21, writeFileSync as writeFileSync34 } from "fs";
21428
21537
  import { join as join47 } from "path";
21429
21538
  var PeriscopeError = class extends Error {
21430
21539
  };
@@ -21442,7 +21551,7 @@ function removePeriscopePrior(stateDir, ticketRecordId) {
21442
21551
  }
21443
21552
  function writePeriscopePrior(stateDir, ticketRecordId, prior) {
21444
21553
  try {
21445
- mkdirSync31(priorDir(stateDir), { recursive: true });
21554
+ mkdirSync32(priorDir(stateDir), { recursive: true });
21446
21555
  writeFileSync34(priorPath(stateDir, ticketRecordId), JSON.stringify(prior));
21447
21556
  } catch {
21448
21557
  }
@@ -21450,7 +21559,7 @@ function writePeriscopePrior(stateDir, ticketRecordId, prior) {
21450
21559
  function readPeriscopePrior(stateDir, ticketRecordId) {
21451
21560
  try {
21452
21561
  const p = priorPath(stateDir, ticketRecordId);
21453
- return existsSync45(p) ? JSON.parse(readFileSync41(p, "utf8")) : void 0;
21562
+ return existsSync46(p) ? JSON.parse(readFileSync41(p, "utf8")) : void 0;
21454
21563
  } catch {
21455
21564
  return void 0;
21456
21565
  }
@@ -22005,8 +22114,8 @@ import { dirname as dirOf2, resolve as resolvePath } from "path";
22005
22114
 
22006
22115
  // src/ssh-keys.ts
22007
22116
  import { execFileSync as execFileSync13 } from "child_process";
22008
- import { existsSync as existsSync46, mkdirSync as mkdirSync32, readFileSync as readFileSync42, chmodSync as chmodSync3 } from "fs";
22009
- import { dirname as dirname29, join as join49 } from "path";
22117
+ import { existsSync as existsSync47, mkdirSync as mkdirSync33, readFileSync as readFileSync42, chmodSync as chmodSync3 } from "fs";
22118
+ import { dirname as dirname30, join as join49 } from "path";
22010
22119
  var SshKeyError = class extends Error {
22011
22120
  };
22012
22121
  function shipSshKeyPathFor(stateDir) {
@@ -22015,8 +22124,8 @@ function shipSshKeyPathFor(stateDir) {
22015
22124
  function ensureShipSshKeypair(stateDir) {
22016
22125
  const privateKeyPath = shipSshKeyPathFor(stateDir);
22017
22126
  const publicKeyPath = `${privateKeyPath}.pub`;
22018
- if (!existsSync46(privateKeyPath)) {
22019
- mkdirSync32(dirname29(privateKeyPath), { recursive: true });
22127
+ if (!existsSync47(privateKeyPath)) {
22128
+ mkdirSync33(dirname30(privateKeyPath), { recursive: true });
22020
22129
  try {
22021
22130
  execFileSync13("ssh-keygen", [
22022
22131
  "-t",
@@ -22036,7 +22145,7 @@ function ensureShipSshKeypair(stateDir) {
22036
22145
  }
22037
22146
  chmodSync3(privateKeyPath, 384);
22038
22147
  }
22039
- if (!existsSync46(publicKeyPath)) {
22148
+ if (!existsSync47(publicKeyPath)) {
22040
22149
  throw new SshKeyError(`${privateKeyPath} exists but ${publicKeyPath} does not \u2014 regenerate or restore it`);
22041
22150
  }
22042
22151
  const publicKey = readFileSync42(publicKeyPath, "utf8").trim();
@@ -22203,46 +22312,9 @@ async function unregisterEndpoint(workspaceId, info, d) {
22203
22312
  import { createServer as createHttpServer } from "http";
22204
22313
  import { spawn as spawn9 } from "child_process";
22205
22314
  import { randomBytes } from "crypto";
22206
- import { existsSync as existsSync47, mkdirSync as mkdirSync34, readFileSync as readFileSync43, writeFileSync as writeFileSync35, unlinkSync as unlinkSync4, readdirSync as readdirSync16 } from "fs";
22315
+ import { existsSync as existsSync48, mkdirSync as mkdirSync34, readFileSync as readFileSync43, writeFileSync as writeFileSync35, unlinkSync as unlinkSync4, readdirSync as readdirSync16 } from "fs";
22207
22316
  import { join as join50 } from "path";
22208
22317
 
22209
- // src/passenger-mirror.ts
22210
- import { mkdirSync as mkdirSync33 } from "fs";
22211
- import { dirname as dirname30 } from "path";
22212
- function mirrorRepoOf(spec) {
22213
- const fromOrigin = spec.originUrl ? parseRemote(spec.originUrl) : null;
22214
- if (fromOrigin) return fromOrigin;
22215
- if (!spec.remote) return null;
22216
- return { host: normalizeForgeHost(spec.forgeHost) || DEFAULT_FORGE_HOST, path: spec.remote };
22217
- }
22218
- async function materializeMirrorCheckouts(specs, cacheFor = (s) => new SandboxCache(s.mirror.cache)) {
22219
- const out = [];
22220
- for (const spec of specs) {
22221
- if (!spec.mirror) continue;
22222
- const base = { name: spec.name, workspaceId: spec.workspaceId };
22223
- const repo = mirrorRepoOf(spec);
22224
- if (!repo) {
22225
- out.push({ ...base, action: "failed", detail: `no remote known for ${spec.name}, so its mirror cannot be found` });
22226
- continue;
22227
- }
22228
- try {
22229
- const parent = dirname30(spec.dir);
22230
- mkdirSync33(parent, { recursive: true });
22231
- mkdirSync33(spec.dir, { recursive: true });
22232
- const r = await cacheFor(spec).materialize(repo, spec.base, parent, spec.name);
22233
- out.push({ ...base, action: r.changed ? "updated" : "unchanged", detail: r.tip });
22234
- } catch (e) {
22235
- const err3 = e;
22236
- out.push({ ...base, action: "failed", detail: (err3.stderr?.trim() || err3.message || String(e)).split("\n").slice(-3).join(" / ") });
22237
- }
22238
- }
22239
- return out;
22240
- }
22241
- function mirrorOutcome(m) {
22242
- if (m.action === "failed") return { action: "clone-failed", detail: m.detail, repeat: false };
22243
- return { action: "cloned", detail: `${m.action} ${m.detail}` };
22244
- }
22245
-
22246
22318
  // src/secret-compare.ts
22247
22319
  import { timingSafeEqual } from "crypto";
22248
22320
  function secretsMatch(a, b) {
@@ -22338,7 +22410,7 @@ function syncDaemonStatePath(stateDir, workspaceId) {
22338
22410
  }
22339
22411
  function readPersistedSyncDaemon(stateDir, workspaceId) {
22340
22412
  const path = syncDaemonStatePath(stateDir, workspaceId);
22341
- if (!existsSync47(path)) return void 0;
22413
+ if (!existsSync48(path)) return void 0;
22342
22414
  try {
22343
22415
  return JSON.parse(readFileSync43(path, "utf8"));
22344
22416
  } catch {
@@ -22369,7 +22441,7 @@ function waitForExit(pid, alive, timeoutMs = 5e3) {
22369
22441
  }
22370
22442
  function readAllPersistedSyncWorkspaceIds(stateDir) {
22371
22443
  const dir = join50(stateDir, "passengers");
22372
- if (!existsSync47(dir)) return [];
22444
+ if (!existsSync48(dir)) return [];
22373
22445
  const suffix = "-sync.json";
22374
22446
  return readdirSync16(dir).filter((f) => f.endsWith(suffix)).map((f) => f.slice(0, -suffix.length));
22375
22447
  }
@@ -22889,7 +22961,7 @@ function contractFixers(dir, baseRef, tickets, locate) {
22889
22961
  }
22890
22962
 
22891
22963
  // src/daemon.ts
22892
- import { existsSync as existsSync48, mkdirSync as mkdirSync36, readFileSync as readFileSync44, readdirSync as readdirSync17, rmSync as rmSync22, statSync as statSync14, writeFileSync as writeFileSync36 } from "fs";
22964
+ import { existsSync as existsSync49, mkdirSync as mkdirSync36, readFileSync as readFileSync44, readdirSync as readdirSync17, rmSync as rmSync22, statSync as statSync14, writeFileSync as writeFileSync36 } from "fs";
22893
22965
  import { join as join52 } from "path";
22894
22966
  import { randomBytes as randomBytes2 } from "crypto";
22895
22967
  var DEFAULT_FLOOR_MS = 5e3;
@@ -22987,7 +23059,7 @@ function computeTreeSignature(paths) {
22987
23059
  }
22988
23060
  function makeStaleChecker(paths, opts = {}) {
22989
23061
  const initial = computeTreeSignature(paths);
22990
- return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync48(opts.reloadSentinel);
23062
+ return () => computeTreeSignature(paths) !== initial || opts.reloadSentinel !== void 0 && existsSync49(opts.reloadSentinel);
22991
23063
  }
22992
23064
  function reloadSentinelFor(stateDir) {
22993
23065
  return join52(stateDir, "reload-requested");
@@ -23238,8 +23310,8 @@ async function runOnePassUnguarded(o) {
23238
23310
  set.add(run3);
23239
23311
  void run3.finally(() => set.delete(run3));
23240
23312
  }
23241
- const reported = TICKET_ROLES.has(role) ? await settled : true;
23242
- const tookTicket = reported !== false;
23313
+ const reported2 = TICKET_ROLES.has(role) ? await settled : true;
23314
+ const tookTicket = reported2 !== false;
23243
23315
  if (tookTicket) started.push(role);
23244
23316
  if (roleCap(role) <= 1) {
23245
23317
  live.delete(role);
@@ -23576,7 +23648,7 @@ import { createHash as createHash10 } from "crypto";
23576
23648
  import {
23577
23649
  chmodSync as chmodSync4,
23578
23650
  closeSync as closeSync6,
23579
- existsSync as existsSync49,
23651
+ existsSync as existsSync50,
23580
23652
  fstatSync,
23581
23653
  openSync as openSync6,
23582
23654
  readSync as readSync3,
@@ -23947,7 +24019,7 @@ function lastLines2(path, n) {
23947
24019
  }
23948
24020
  function readTail(live, opts) {
23949
24021
  const max = opts.maxBytes ?? MAX_TAIL_BYTES;
23950
- if (!existsSync49(live)) {
24022
+ if (!existsSync50(live)) {
23951
24023
  return { lines: [], cursor: opts.after ?? encodeCursor({ dev: 0, ino: 0, off: 0 }), reset: false };
23952
24024
  }
23953
24025
  const cur = opts.after ? decodeCursor(opts.after) : null;
@@ -24104,7 +24176,7 @@ async function route(deps, method, url, req, abort) {
24104
24176
  const run3 = decodeURIComponent(p.slice("/streams/".length));
24105
24177
  if (!RUN_NAME_RE.test(run3) || run3.includes("..") || /\.(pid|meta\.json|events\.jsonl)$/.test(run3)) return err2(400, "not a run name");
24106
24178
  const file = join53(deps.stateDir, "streams", run3.endsWith(".jsonl") ? run3 : `${run3}.jsonl`);
24107
- if (!existsSync49(file)) return err2(404, "no such run");
24179
+ if (!existsSync50(file)) return err2(404, "no such run");
24108
24180
  return tailReply(file, url.searchParams, false, abort);
24109
24181
  }
24110
24182
  if ((p === "/pause" || p === "/resume") && post3) {
@@ -24148,7 +24220,7 @@ function send(res, r) {
24148
24220
  res.end(body);
24149
24221
  }
24150
24222
  async function socketIsStale(path) {
24151
- if (!existsSync49(path)) return false;
24223
+ if (!existsSync50(path)) return false;
24152
24224
  return new Promise((resolve8) => {
24153
24225
  const c = createConnection(path);
24154
24226
  c.once("connect", () => {
@@ -24167,7 +24239,7 @@ async function startControlServer(deps) {
24167
24239
  return null;
24168
24240
  }
24169
24241
  if (await socketIsStale(path)) rmSync23(path, { force: true });
24170
- else if (existsSync49(path)) {
24242
+ else if (existsSync50(path)) {
24171
24243
  log(`control API not started: ${path} is in use by another process`);
24172
24244
  return null;
24173
24245
  }
@@ -24260,7 +24332,7 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
24260
24332
 
24261
24333
  // src/periscope-endpoints.ts
24262
24334
  import { randomBytes as randomBytes3 } from "crypto";
24263
- import { chmodSync as chmodSync5, existsSync as existsSync50, mkdirSync as mkdirSync37, readdirSync as readdirSync19, readFileSync as readFileSync45, rmSync as rmSync24, writeFileSync as writeFileSync37 } from "fs";
24335
+ import { chmodSync as chmodSync5, existsSync as existsSync51, mkdirSync as mkdirSync37, readdirSync as readdirSync19, readFileSync as readFileSync45, rmSync as rmSync24, writeFileSync as writeFileSync37 } from "fs";
24264
24336
  import { join as join54 } from "path";
24265
24337
  var secretDir = (stateDir) => join54(stateDir, "periscope-mcp");
24266
24338
  var secretFile = (stateDir, id) => join54(secretDir(stateDir), `${id}.json`);
@@ -24303,7 +24375,7 @@ function removePeriscopeEndpointRecord(stateDir, ticketRecordId) {
24303
24375
  }
24304
24376
  function listPeriscopeEndpoints(stateDir) {
24305
24377
  const dir = endpointDir(stateDir);
24306
- if (!existsSync50(dir)) return [];
24378
+ if (!existsSync51(dir)) return [];
24307
24379
  const out = [];
24308
24380
  for (const name of readdirSync19(dir)) {
24309
24381
  if (!name.endsWith(".json")) continue;
@@ -25286,7 +25358,7 @@ if (command === "passenger-sync-daemon") {
25286
25358
  } else {
25287
25359
  const toleratesNoRoutes = command === "credential" || command === "forge" || command === "repos" || command === "ship" || command === "disconnect" && flag("reset") || command === "deprovision" || command === "connect" && flag("sign-out");
25288
25360
  const configless = command === "connect" && flag("sign-out") || command === "disconnect" && flag("reset") || command === "deprovision";
25289
- if (configless && !existsSync51(findConfigFile(CREW_HOME))) {
25361
+ if (configless && !existsSync52(findConfigFile(CREW_HOME))) {
25290
25362
  cfg = loadConfig(CREW_HOME, firstRunConfigPath(), { text: "ship: {}\n", allowNoRoutes: true });
25291
25363
  } else {
25292
25364
  cfg = loadConfig(CREW_HOME, void 0, toleratesNoRoutes ? { allowNoRoutes: true } : {});
@@ -25630,7 +25702,7 @@ if (command === "passenger-sync-daemon") {
25630
25702
  if (host === "linux" && hasSystemd) {
25631
25703
  const label = labelFor(CREW_HOME, "run");
25632
25704
  const staleTimer = join56(homedir11(), ".config", "systemd", "user", `${label}.timer`);
25633
- if (existsSync51(staleTimer)) {
25705
+ if (existsSync52(staleTimer)) {
25634
25706
  process.stdout.write(
25635
25707
  `removing stale run timer from before the persistent-service cutover: ${staleTimer}${dryRun ? " (dry run)" : ""}
25636
25708
  `
@@ -25659,7 +25731,7 @@ if (command === "passenger-sync-daemon") {
25659
25731
  const { settled: doctorRepos, errors: refusedRepos } = await settleRepos(reposOf(c), (t) => t.name, (t) => resolveOneRepo(c, t));
25660
25732
  for (const e of refusedRepos) problems.push(`repo ${e.repo}: ${e.detail}`);
25661
25733
  for (const r of doctorRepos) {
25662
- if (!existsSync51(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
25734
+ if (!existsSync52(r.dir)) problems.push(`repo ${r.name}: ${r.dir} is missing on disk`);
25663
25735
  if (!satisfies(host, r.config.platform)) problems.push(`repo ${r.name}: requires ${r.config.platform}, this host is ${host}`);
25664
25736
  readiness.push(...validateEffective(r.config).map((p) => `repo ${r.name}: ${p}`));
25665
25737
  }
@@ -25686,7 +25758,7 @@ if (command === "passenger-sync-daemon") {
25686
25758
  }
25687
25759
  async function doctorFix(o) {
25688
25760
  const routes = await Promise.all(routesInScope(cfg.routes, named).map((c) => gatherHealth({ route: c.route, enabled: c.enabled, hasRepos: reposOf(c).length > 0 }, () => routeProblems(c))));
25689
- const unitExists = (p) => existsSync51(p);
25761
+ const unitExists = (p) => existsSync52(p);
25690
25762
  const crontab = o.host === "linux" ? spawnSync11("crontab", ["-l"], { encoding: "utf8" }).stdout ?? "" : "";
25691
25763
  const schedulerAbsent = o.host !== "windows" ? !schedulerInstalled(planInstall(cfg.ship, CREW_HOME, o.host, detectSystemd(), "run", installContext()), unitExists, crontab) : true;
25692
25764
  const plan = planFix({ routes, schedulerAbsent, host: o.host });
@@ -25838,9 +25910,25 @@ ${changed2.map((c) => ` - ${c}
25838
25910
  }
25839
25911
  }
25840
25912
  const precheckTickets = /* @__PURE__ */ new Map();
25913
+ const scope0 = (c, t) => releaseLockScope(c.route, t.name);
25841
25914
  async function releasePhase(c, target, opts = {}) {
25842
25915
  const scope = releaseLockScope(c.route, target.name);
25843
25916
  const remit = emit.forRoute(c.route);
25917
+ if (lacksCheckout(cfg.ship, target.dir)) {
25918
+ let mode = null;
25919
+ const settings = cacheSettingsOf(cfg.ship.sandbox);
25920
+ const mirror = mirrorRepoOfTarget(target);
25921
+ if (settings && mirror) {
25922
+ try {
25923
+ mode = (await readMirrorContract(new SandboxCache(settings), mirror, c.branch?.base ?? "main"))?.release.mode ?? null;
25924
+ } catch (e) {
25925
+ if (firstReport(`${scope0(c, target)}:contract`)) remit.warn(`${target.name}: could not read the contract from the mirror: ${e.message}`, { step: "release" });
25926
+ }
25927
+ }
25928
+ const note = noCheckoutReleaseNote(target.name, mode);
25929
+ if (firstReport(`${scope0(c, target)}:release`)) remit.emit(note, { step: "release" });
25930
+ return { scope: releaseLockScope(c.route, target.name), tests: "skipped", outcome: "skipped", detail: note };
25931
+ }
25844
25932
  const relLock = dryRun ? void 0 : state.acquire(`release-${scope}`);
25845
25933
  if (relLock && !relLock.ok) {
25846
25934
  remit.emit(`a previous release (pid ${relLock.heldBy}) is still running \u2014 skipping`, { step: "release" });
@@ -26100,10 +26188,10 @@ ${changed2.map((c) => ` - ${c}
26100
26188
  if (outcome.gateRed && !dryRun) {
26101
26189
  try {
26102
26190
  const g = outcome.gateRed;
26103
- const reported = await tracker.events("gate_red");
26191
+ const reported2 = await tracker.events("gate_red");
26104
26192
  const seat = releaseSeat(c);
26105
26193
  for (const t of gateRedTickets(outcome.merged, outcome.decision.merges)) {
26106
- if (alreadyReported(t, g, reported)) continue;
26194
+ if (alreadyReported(t, g, reported2)) continue;
26107
26195
  await tracker.postEvent(t.id, gateRedComment(g), seat, "gate_red");
26108
26196
  remit.warn(`test gate red for ${g.count} cycles \u2014 noted on the ticket`, { ticket: t.issue_id, step: "merge" });
26109
26197
  }
@@ -26114,10 +26202,10 @@ ${changed2.map((c) => ` - ${c}
26114
26202
  }
26115
26203
  if (outcome.localRefSkips?.length && !dryRun) {
26116
26204
  try {
26117
- const reported = await tracker.events("local_ref_skip");
26205
+ const reported2 = await tracker.events("local_ref_skip");
26118
26206
  const seat = releaseSeat(c);
26119
26207
  for (const s of outcome.localRefSkips) {
26120
- if (localRefSkipReported(s.ticket, s.problem, reported)) continue;
26208
+ if (localRefSkipReported(s.ticket, s.problem, reported2)) continue;
26121
26209
  await tracker.postEvent(s.ticket.id, localRefSkipComment(cfg.ship.name, s.problem), seat, "local_ref_skip");
26122
26210
  }
26123
26211
  } catch (e) {
@@ -27187,7 +27275,7 @@ ${cu.tail.trimEnd()}
27187
27275
  `);
27188
27276
  if (!dryRun) {
27189
27277
  const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
27190
- const raw = existsSync51(resolvedPath) ? JSON.parse(readFileSync46(resolvedPath, "utf8")) : {};
27278
+ const raw = existsSync52(resolvedPath) ? JSON.parse(readFileSync46(resolvedPath, "utf8")) : {};
27191
27279
  raw.agentSkills = result.agentSkills;
27192
27280
  mkdirSync38(dirOf2(resolvedPath), { recursive: true });
27193
27281
  writeFileSync38(resolvedPath, `${JSON.stringify(raw, null, 2)}
@@ -27292,8 +27380,8 @@ ${cu.tail.trimEnd()}
27292
27380
  try {
27293
27381
  const a = parseHooksArgs(positional.slice(2), routeNamesOf(configText));
27294
27382
  const dir = repoDirOf(configText, dirname34(cfg.configFile), a.route, a.name);
27295
- if (!existsSync51(dir)) throw new ReposError(`${dir} does not exist on disk`);
27296
- const files = { exists: (r) => existsSync51(resolvePath(dir, r)), list: (r) => {
27383
+ if (!existsSync52(dir)) throw new ReposError(`${dir} does not exist on disk`);
27384
+ const files = { exists: (r) => existsSync52(resolvePath(dir, r)), list: (r) => {
27297
27385
  try {
27298
27386
  return readdirSync20(resolvePath(dir, r));
27299
27387
  } catch {
@@ -27411,7 +27499,7 @@ ${cu.tail.trimEnd()}
27411
27499
  if (sub === "list" && !routeName) usage2();
27412
27500
  if (sub === "add") {
27413
27501
  try {
27414
- const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync51(resolvePath(p.replace(/^~(?=\/|$)/, homedir11()))));
27502
+ const a = parseAddArgs(positional.slice(2), routeNamesOf(configText), (p) => existsSync52(resolvePath(p.replace(/^~(?=\/|$)/, homedir11()))));
27415
27503
  routeName = a.route;
27416
27504
  addPath = a.path;
27417
27505
  addName = a.name;
@@ -27441,7 +27529,7 @@ ${cu.tail.trimEnd()}
27441
27529
  const resolvedFile = resolvedPathFor(cfg.ship.stateDir, routeName);
27442
27530
  if (sub === "list" || sub === "add") {
27443
27531
  const liveRoute = cfg.routes.find((r) => r.route === routeName);
27444
- if (liveRoute?.resolved?.reposModelId && existsSync51(resolvedFile) && !dryRun) {
27532
+ if (liveRoute?.resolved?.reposModelId && existsSync52(resolvedFile) && !dryRun) {
27445
27533
  try {
27446
27534
  await hydrateApiKeys([liveRoute]);
27447
27535
  persistRepoRows(cfg.ship.stateDir, liveRoute.route, await new Tracker(liveRoute, cfg.ship).repoRowsForMap());
@@ -27450,7 +27538,7 @@ ${cu.tail.trimEnd()}
27450
27538
  }
27451
27539
  }
27452
27540
  let resolvedRemotes;
27453
- if (existsSync51(resolvedFile)) {
27541
+ if (existsSync52(resolvedFile)) {
27454
27542
  try {
27455
27543
  resolvedRemotes = JSON.parse(readFileSync46(resolvedFile, "utf8"));
27456
27544
  } catch {
@@ -27459,7 +27547,7 @@ ${cu.tail.trimEnd()}
27459
27547
  const originOf = (dir) => gitOk(dir, ["remote", "get-url", "origin"]) ?? void 0;
27460
27548
  const text2 = configText;
27461
27549
  const base = dirname34(cfg.configFile);
27462
- const isCheckout = (d) => existsSync51(`${d}/.git`);
27550
+ const isCheckout = (d) => existsSync52(`${d}/.git`);
27463
27551
  try {
27464
27552
  if (sub === "list") {
27465
27553
  if (flag("json")) {
@@ -27469,7 +27557,7 @@ ${cu.tail.trimEnd()}
27469
27557
  base,
27470
27558
  resolved: resolvedRemotes,
27471
27559
  originOf,
27472
- exists: (d) => existsSync51(d),
27560
+ exists: (d) => existsSync52(d),
27473
27561
  isCheckout,
27474
27562
  repoHasHooks: (d) => REPO_CONFIG_FILENAMES.some((f) => {
27475
27563
  try {
@@ -27491,7 +27579,7 @@ ${cu.tail.trimEnd()}
27491
27579
  for (const r of repos) {
27492
27580
  const origin2 = originOf(r.dir);
27493
27581
  const row = matchRepoName(origin2, resolvedRemotes);
27494
- const note = !existsSync51(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
27582
+ const note = !existsSync52(r.dir) ? "missing on disk" : row ? `Repos row: ${row}` : `no Repos row matches ${normalizeRemote(origin2) ?? "this checkout (no origin remote)"}`;
27495
27583
  process.stdout.write(`${r.name} ${r.dir} ${note}
27496
27584
  `);
27497
27585
  }
@@ -27503,9 +27591,9 @@ ${cu.tail.trimEnd()}
27503
27591
  ` : `${human}
27504
27592
  `);
27505
27593
  const plan2 = planRepoClone({ text: text2, base, route: routeName, name: addName, resolved: resolvedRemotes, dir: value("dir"), isCheckout });
27506
- if (existsSync51(plan2.target) && (existsSync51(`${plan2.target}/.git`) || readdirSync20(plan2.target).length > 0)) {
27594
+ if (existsSync52(plan2.target) && (existsSync52(`${plan2.target}/.git`) || readdirSync20(plan2.target).length > 0)) {
27507
27595
  throw new ReposError(
27508
- existsSync51(`${plan2.target}/.git`) ? `${plan2.target} is already a checkout \u2014 attach it with \`crew repos add ${routeName} ${plan2.name} ${plan2.target}\`` : `${plan2.target} exists and is not empty \u2014 pass --dir to clone somewhere else`
27596
+ existsSync52(`${plan2.target}/.git`) ? `${plan2.target} is already a checkout \u2014 attach it with \`crew repos add ${routeName} ${plan2.name} ${plan2.target}\`` : `${plan2.target} exists and is not empty \u2014 pass --dir to clone somewhere else`
27509
27597
  );
27510
27598
  }
27511
27599
  if (dryRun) {
@@ -27530,7 +27618,7 @@ ${cu.tail.trimEnd()}
27530
27618
  break;
27531
27619
  }
27532
27620
  const target = resolvePath(addPath.replace(/^~(?=\/|$)/, homedir11()));
27533
- const top = existsSync51(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
27621
+ const top = existsSync52(target) ? gitOk(target, ["rev-parse", "--show-toplevel"]) : null;
27534
27622
  if (!top) throw new ReposError(`${target} is not a git checkout`);
27535
27623
  const origin = originOf(top);
27536
27624
  const plan = planRepoAdd({
@@ -27582,7 +27670,7 @@ ${cu.tail.trimEnd()}
27582
27670
  const created = await rowTracker.createRepoRow(rowPayload);
27583
27671
  process.stdout.write(`created Repos row ${String(rowPayload.name)} (${String(rowPayload.remote)}) ${created.id}
27584
27672
  `);
27585
- if (existsSync51(resolvedFile)) {
27673
+ if (existsSync52(resolvedFile)) {
27586
27674
  const raw = JSON.parse(readFileSync46(resolvedFile, "utf8"));
27587
27675
  raw.repoNames = { ...raw.repoNames, [created.id]: String(rowPayload.name) };
27588
27676
  raw.repoRemotes = { ...raw.repoRemotes, [created.id]: String(rowPayload.remote) };
@@ -28004,7 +28092,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
28004
28092
  }
28005
28093
  }
28006
28094
  let previous;
28007
- const hadPreviousFile = existsSync51(resolvedPath);
28095
+ const hadPreviousFile = existsSync52(resolvedPath);
28008
28096
  if (hadPreviousFile) {
28009
28097
  try {
28010
28098
  previous = JSON.parse(readFileSync46(resolvedPath, "utf8"));
@@ -28149,7 +28237,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
28149
28237
  let mintOutcomes = [];
28150
28238
  if (!dryRun && previous?.agentPersonas && existingRoute?.resolved) {
28151
28239
  try {
28152
- mintOutcomes = await mintMissingSeatKeys({ ...existingRoute, resolved: { ...existingRoute.resolved, seats: found.seats } }, { userAgent: cfg.ship.userAgent, shipName: cfg.ship.name });
28240
+ mintOutcomes = await mintMissingSeatKeys(withFoundSeats(existingRoute, found, previous.releaseAgentId), { userAgent: cfg.ship.userAgent, shipName: cfg.ship.name });
28153
28241
  for (const o of mintOutcomes) process.stderr.write(`${describeMintOutcome(o)}
28154
28242
  `);
28155
28243
  for (const p of await probeSeatReads({ ...existingRoute, resolved: { ...existingRoute.resolved, seats: found.seats } }, { userAgent: cfg.ship.userAgent })) {
@@ -28884,7 +28972,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
28884
28972
  prepare: async (path) => {
28885
28973
  for (const f of repoWt.config.worktrees.copy) {
28886
28974
  const src = join56(repoWt.dir, f);
28887
- if (!existsSync51(src) || existsSync51(join56(path, f))) continue;
28975
+ if (!existsSync52(src) || existsSync52(join56(path, f))) continue;
28888
28976
  mkdirSync38(dirOf2(join56(path, f)), { recursive: true });
28889
28977
  copyFileSync2(src, join56(path, f));
28890
28978
  }
@@ -28951,11 +29039,12 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
28951
29039
  });
28952
29040
  };
28953
29041
  if (subP === "list") {
28954
- const entries = periscopeListEntries(
28955
- (await Promise.all(routesP.map(gather))).flat().map((r) => ({ ...r, pointedAt: readPointedAt(r.worktree) })),
28956
- /* @__PURE__ */ new Date(),
28957
- (r) => pidsInWorktree(r.worktree, Number(r.number)).length > 0
28958
- );
29042
+ const listRows = (await Promise.all(routesP.map(gather))).flat().map((r) => ({ ...r, pointedAt: readPointedAt(r.worktree) }));
29043
+ const serving = new Map(await Promise.all(listRows.map(async (r) => {
29044
+ const port = urlPort(r.url);
29045
+ return [r.id, port === void 0 ? false : await portListening(port)];
29046
+ })));
29047
+ const entries = periscopeListEntries(listRows, /* @__PURE__ */ new Date(), (r) => serving.get(r.id) ?? false);
28959
29048
  process.stdout.write(jsonP ? `${JSON.stringify(entries)}
28960
29049
  ` : `${formatPeriscopeList(entries)}
28961
29050
  `);
@@ -29106,7 +29195,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
29106
29195
  const shellR = hit && resolveShell(hit.repo.config.shell ?? cfg.ship.shell);
29107
29196
  const resumed = await runPeriscopeResume({
29108
29197
  find: async () => hit && { row: hit.row, tag: hit.tag },
29109
- worktreeExists: (p) => existsSync51(p),
29198
+ worktreeExists: (p) => existsSync52(p),
29110
29199
  prepareDropFile,
29111
29200
  startProxy: (wt, url, n) => launchProxy(crewInvocation(CREW_HOME), wt, url, Number(n), { host: hit.row.host || "localhost", inject: hit.repo.config.periscope?.inspector !== "repo" }),
29112
29201
  caps: periscopeCaps(cfg.ship),
@@ -29203,7 +29292,7 @@ ${where.join("\n")}`);
29203
29292
  prepare: async (path) => {
29204
29293
  for (const f of repoP.config.worktrees.copy) {
29205
29294
  const src = join56(repoP.dir, f);
29206
- if (!existsSync51(src)) continue;
29295
+ if (!existsSync52(src)) continue;
29207
29296
  mkdirSync38(dirOf2(join56(path, f)), { recursive: true });
29208
29297
  copyFileSync2(src, join56(path, f));
29209
29298
  }
@@ -29695,7 +29784,7 @@ ${where.join("\n")}`);
29695
29784
  }
29696
29785
  if (row) {
29697
29786
  await tracker.deleteRepoRow(row.id);
29698
- if (existsSync51(resolvedFile)) {
29787
+ if (existsSync52(resolvedFile)) {
29699
29788
  const raw = JSON.parse(readFileSync46(resolvedFile, "utf8"));
29700
29789
  if (raw.repoNames) delete raw.repoNames[row.id];
29701
29790
  if (raw.repoRemotes) delete raw.repoRemotes[row.id];
@@ -29703,13 +29792,13 @@ ${where.join("\n")}`);
29703
29792
  `);
29704
29793
  }
29705
29794
  }
29706
- if (existsSync51(join56(dir, ".git")) && basename16(dir) === SAMPLE_REPO) rmSync25(dir, { recursive: true, force: true });
29795
+ if (existsSync52(join56(dir, ".git")) && basename16(dir) === SAMPLE_REPO) rmSync25(dir, { recursive: true, force: true });
29707
29796
  say("removed", { dir, closed }, `removed the sample repo${closed.length ? `, closed ${closed.join(", ")}` : ""}`);
29708
29797
  break;
29709
29798
  }
29710
29799
  const projectId = route2.resolved.areaId ?? resolveRepoProject(await tracker.projectRows(), value("project"));
29711
- const fresh = !existsSync51(join56(dir, ".git"));
29712
- if (existsSync51(dir) && fresh && readdirSync20(dir).length > 0) fail2(`${dir} exists and is not a git checkout \u2014 move it aside`);
29800
+ const fresh = !existsSync52(join56(dir, ".git"));
29801
+ if (existsSync52(dir) && fresh && readdirSync20(dir).length > 0) fail2(`${dir} exists and is not a git checkout \u2014 move it aside`);
29713
29802
  if (dryRun) {
29714
29803
  say("dry-run", { dir, newCheckout: fresh, repoRow: row?.id ?? null }, `would ${fresh ? "create" : "reuse"} ${dir}${row ? "" : ", create the Repos row"}, and file one ${tracker.contract.statuses.approved} ticket`);
29715
29804
  break;
@@ -29735,7 +29824,7 @@ ${where.join("\n")}`);
29735
29824
  spec: { platform: "unix", releaseMode: "integrate", ciProvider: "none" }
29736
29825
  });
29737
29826
  repoId = (await tracker.createRepoRow(payload)).id;
29738
- if (existsSync51(resolvedFile)) {
29827
+ if (existsSync52(resolvedFile)) {
29739
29828
  const raw = JSON.parse(readFileSync46(resolvedFile, "utf8"));
29740
29829
  raw.repoNames = { ...raw.repoNames, [repoId]: SAMPLE_REPO };
29741
29830
  raw.repoRemotes = { ...raw.repoRemotes, [repoId]: SAMPLE_REMOTE };
@@ -30091,7 +30180,7 @@ watermark: ${state.watermark()}
30091
30180
  }
30092
30181
  for (const r of repos) {
30093
30182
  const remote = r.config.branch.remote;
30094
- if (existsSync51(r.dir)) {
30183
+ if (existsSync52(r.dir)) {
30095
30184
  pushAuth.push({
30096
30185
  route: c.route,
30097
30186
  repo: r.name,
@@ -30192,7 +30281,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
30192
30281
  }
30193
30282
  try {
30194
30283
  const evFile = eventFileFor(cfg.ship.stateDir);
30195
- const evSize = existsSync51(evFile) ? statSync16(evFile).size : 0;
30284
+ const evSize = existsSync52(evFile) ? statSync16(evFile).size : 0;
30196
30285
  process.stdout.write(
30197
30286
  evSize > cfg.ship.eventsRotateBytes * 4 ? `events log: WARN ${evFile} is ${Math.round(evSize / 1048576)} MB (> 4x eventsRotateBytes); run \`crew reap\` to rotate it
30198
30287
  ` : "events log: ok\n"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tablation/crew",
3
- "version": "0.126.5",
3
+ "version": "0.126.7",
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",