@tablation/crew 0.109.0 → 0.110.0

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 +78 -26
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -10767,7 +10767,7 @@ async function reconcileRuns(o) {
10767
10767
  }
10768
10768
 
10769
10769
  // src/live-runs.ts
10770
- import { existsSync as existsSync23, readdirSync as readdirSync10 } from "fs";
10770
+ import { existsSync as existsSync23, readdirSync as readdirSync10, statSync as statSync8 } from "fs";
10771
10771
  import { join as join25 } from "path";
10772
10772
  function liveRuns(stateDir, isAlive = runIsAlive) {
10773
10773
  const dir = join25(stateDir, "streams");
@@ -10783,6 +10783,23 @@ function liveRuns(stateDir, isAlive = runIsAlive) {
10783
10783
  }
10784
10784
  return out.sort((a, b) => a.spawnedAt.localeCompare(b.spawnedAt));
10785
10785
  }
10786
+ function runActivity(stateDir, isAlive = runIsAlive) {
10787
+ const activeRuns = liveRuns(stateDir, isAlive).length;
10788
+ const dir = join25(stateDir, "streams");
10789
+ let newest = 0;
10790
+ if (existsSync23(dir)) {
10791
+ for (const name of readdirSync10(dir)) {
10792
+ if (!name.endsWith(".jsonl") || name.endsWith(".events.jsonl")) continue;
10793
+ const streamPath = join25(dir, name);
10794
+ if (!runFinished(streamPath)) continue;
10795
+ try {
10796
+ newest = Math.max(newest, statSync8(`${streamPath}.meta.json`).mtimeMs);
10797
+ } catch {
10798
+ }
10799
+ }
10800
+ }
10801
+ return { activeRuns, lastRunEndedAt: newest ? new Date(newest).toISOString() : null };
10802
+ }
10786
10803
  function siblingRuns(runs, ownTicketRecordId) {
10787
10804
  return runs.filter((r) => !ownTicketRecordId || r.ticketRecordId !== ownTicketRecordId);
10788
10805
  }
@@ -12572,7 +12589,7 @@ async function signOutSessions(o) {
12572
12589
  }
12573
12590
 
12574
12591
  // src/reset.ts
12575
- import { existsSync as existsSync26, mkdirSync as mkdirSync17, readFileSync as readFileSync24, readdirSync as readdirSync12, rmSync as rmSync16, statSync as statSync8, writeFileSync as writeFileSync18 } from "fs";
12592
+ import { existsSync as existsSync26, mkdirSync as mkdirSync17, readFileSync as readFileSync24, readdirSync as readdirSync12, rmSync as rmSync16, statSync as statSync9, writeFileSync as writeFileSync18 } from "fs";
12576
12593
  import { dirname as dirname12, join as join29 } from "path";
12577
12594
  import { hostFromUrl as hostFromUrl3 } from "@tablation/client";
12578
12595
  function clonedCheckoutsFile(stateDir) {
@@ -12670,7 +12687,7 @@ function removeState(stateDir, dryRun) {
12670
12687
  const targets = [join29(stateDir, "resolved"), join29(stateDir, "keys"), clonedCheckoutsFile(stateDir), join29(stateDir, "connect-session.json"), join29(stateDir, "reload-requested"), join29(stateDir, "daemon.started")];
12671
12688
  try {
12672
12689
  for (const name of readdirSync12(stateDir)) {
12673
- if (name.startsWith(".") && statSync8(join29(stateDir, name)).isFile()) targets.push(join29(stateDir, name));
12690
+ if (name.startsWith(".") && statSync9(join29(stateDir, name)).isFile()) targets.push(join29(stateDir, name));
12674
12691
  }
12675
12692
  } catch {
12676
12693
  }
@@ -12798,6 +12815,7 @@ async function runPoolMember(deps) {
12798
12815
  }
12799
12816
  };
12800
12817
  let reported;
12818
+ let busy = false;
12801
12819
  const report = async (state, o = {}) => {
12802
12820
  const body = {
12803
12821
  state,
@@ -12807,6 +12825,12 @@ async function runPoolMember(deps) {
12807
12825
  reason: o.reason ?? null,
12808
12826
  crewVersion: deps.host.crewVersion
12809
12827
  };
12828
+ const act = state === "running" ? deps.activity?.() : void 0;
12829
+ if (act) {
12830
+ body.activeRuns = act.activeRuns;
12831
+ body.lastRunEndedAt = act.lastRunEndedAt;
12832
+ }
12833
+ if (act) busy = act.activeRuns > 0;
12810
12834
  const r = await settle("status", () => deps.api.status(body));
12811
12835
  if (!r) return "stopped";
12812
12836
  if (r.status === 401) return "credential";
@@ -12902,7 +12926,25 @@ async function runPoolMember(deps) {
12902
12926
  if (!current) await deps.maybeSelfUpgrade?.().catch((e) => {
12903
12927
  deps.log(`self-upgrade: ${e.message}`);
12904
12928
  });
12905
- const ans = await settle("lease", () => deps.api.lease(platform2.pollWaitSeconds || DEFAULT_POLL_WAIT_SECONDS, current ?? void 0));
12929
+ let stopWatch;
12930
+ let flipping = false;
12931
+ if (current && deps.watch && deps.activity) {
12932
+ const held = current;
12933
+ stopWatch = deps.watch(() => {
12934
+ const now = deps.activity().activeRuns > 0;
12935
+ if (flipping || now === busy) return;
12936
+ flipping = true;
12937
+ void report("running", { leaseId: held }).catch(() => void 0).finally(() => {
12938
+ flipping = false;
12939
+ });
12940
+ });
12941
+ }
12942
+ let ans;
12943
+ try {
12944
+ ans = await settle("lease", () => deps.api.lease(platform2.pollWaitSeconds || DEFAULT_POLL_WAIT_SECONDS, current ?? void 0));
12945
+ } finally {
12946
+ stopWatch?.();
12947
+ }
12906
12948
  if (!ans) return POOL_EXIT.ok;
12907
12949
  if (ans.status === 401) {
12908
12950
  deps.log("the pool credential is unknown or revoked (401)");
@@ -12915,10 +12957,10 @@ async function runPoolMember(deps) {
12915
12957
  }
12916
12958
  if (ans.status === 204) {
12917
12959
  const url = current ? deps.relayUrl() : null;
12918
- if (current && url && url !== relayReported) {
12960
+ if (current && (url !== relayReported && url || deps.activity)) {
12919
12961
  const r2 = await report("running", { leaseId: current });
12920
12962
  if (r2 === "credential") return POOL_EXIT.credential;
12921
- relayReported = url;
12963
+ relayReported = url ?? relayReported;
12922
12964
  }
12923
12965
  continue;
12924
12966
  }
@@ -13992,6 +14034,16 @@ function realPoolMemberDeps(o) {
13992
14034
  }
13993
14035
  return null;
13994
14036
  },
14037
+ activity() {
14038
+ const cfg = loadShip(o.crewHome);
14039
+ return cfg ? runActivity(cfg.ship.stateDir) : { activeRuns: 0, lastRunEndedAt: null };
14040
+ },
14041
+ watch(onTick) {
14042
+ const t = setInterval(onTick, 2e3);
14043
+ return () => {
14044
+ clearInterval(t);
14045
+ };
14046
+ },
13995
14047
  sleep: (ms3) => new Promise((r) => setTimeout(r, ms3)),
13996
14048
  log: o.log,
13997
14049
  stopping: o.stopping
@@ -17811,7 +17863,7 @@ function missingReleaserWarning(repo, release, ships, now = Date.now()) {
17811
17863
  }
17812
17864
 
17813
17865
  // src/watch.ts
17814
- import { watch as watchFile, existsSync as existsSync34, statSync as statSync9, openSync as openSync4, readSync as readSync2, closeSync as closeSync4 } from "fs";
17866
+ import { watch as watchFile, existsSync as existsSync34, statSync as statSync10, openSync as openSync4, readSync as readSync2, closeSync as closeSync4 } from "fs";
17815
17867
  var E = "\x1B[";
17816
17868
  var dim = (s) => `${E}2m${s}${E}0m`;
17817
17869
  var bold = (s) => `${E}1m${s}${E}0m`;
@@ -17913,7 +17965,7 @@ function renderHeaders(states, now = Date.now()) {
17913
17965
  }
17914
17966
  function readFrom(file, from) {
17915
17967
  if (!existsSync34(file)) return { events: [], offset: from };
17916
- const size = statSync9(file).size;
17968
+ const size = statSync10(file).size;
17917
17969
  if (size < from) return readFrom(file, 0);
17918
17970
  if (size === from) return { events: [], offset: from };
17919
17971
  const fd = openSync4(file, "r");
@@ -19913,7 +19965,7 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
19913
19965
  }
19914
19966
 
19915
19967
  // src/stream-sweep.ts
19916
- import { existsSync as existsSync38, readdirSync as readdirSync15, rmSync as rmSync18, statSync as statSync10 } from "fs";
19968
+ import { existsSync as existsSync38, readdirSync as readdirSync15, rmSync as rmSync18, statSync as statSync11 } from "fs";
19917
19969
  import { join as join40 } from "path";
19918
19970
  var DAY_MS2 = 24 * 60 * 60 * 1e3;
19919
19971
  function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
@@ -19922,7 +19974,7 @@ function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
19922
19974
  const cutoff = now - retentionDays * DAY_MS2;
19923
19975
  return readdirSync15(dir).map((name) => join40(dir, name)).filter((path) => {
19924
19976
  try {
19925
- return statSync10(path).mtimeMs < cutoff;
19977
+ return statSync11(path).mtimeMs < cutoff;
19926
19978
  } catch {
19927
19979
  return false;
19928
19980
  }
@@ -19950,7 +20002,7 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
19950
20002
  const cutoff = now - retentionDays * DAY_MS2;
19951
20003
  return readdirSync15(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join40(stateDir, name)).filter((path) => {
19952
20004
  try {
19953
- return statSync10(path).mtimeMs < cutoff;
20005
+ return statSync11(path).mtimeMs < cutoff;
19954
20006
  } catch {
19955
20007
  return false;
19956
20008
  }
@@ -19958,7 +20010,7 @@ function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
19958
20010
  }
19959
20011
 
19960
20012
  // src/cli.ts
19961
- import { realpathSync as realpathSync3, readFileSync as readFileSync40, existsSync as existsSync45, mkdirSync as mkdirSync32, writeFileSync as writeFileSync33, rmSync as rmSync23, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync6, statSync as statSync14, readdirSync as readdirSync20 } from "fs";
20013
+ import { realpathSync as realpathSync3, readFileSync as readFileSync40, existsSync as existsSync45, mkdirSync as mkdirSync32, writeFileSync as writeFileSync33, rmSync as rmSync23, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync6, statSync as statSync15, readdirSync as readdirSync20 } from "fs";
19962
20014
  import { createInterface } from "readline/promises";
19963
20015
 
19964
20016
  // src/periscope-cmd.ts
@@ -20386,7 +20438,7 @@ ${tail2(preview.output)}`);
20386
20438
 
20387
20439
  // src/periscope-idle.ts
20388
20440
  import { execFileSync as execFileSync10 } from "child_process";
20389
- import { statSync as statSync11 } from "fs";
20441
+ import { statSync as statSync12 } from "fs";
20390
20442
  import { join as join42 } from "path";
20391
20443
  var MAX_SCANNED_FILES = 2e4;
20392
20444
  function newestWorktreeMtime(root) {
@@ -20407,7 +20459,7 @@ function newestWorktreeMtime(root) {
20407
20459
  if (!rel) continue;
20408
20460
  if (++scanned > MAX_SCANNED_FILES) break;
20409
20461
  try {
20410
- const m = statSync11(join42(root, rel)).mtimeMs;
20462
+ const m = statSync12(join42(root, rel)).mtimeMs;
20411
20463
  if (newest === null || m > newest) newest = m;
20412
20464
  } catch {
20413
20465
  }
@@ -20935,7 +20987,7 @@ function describeCause(err3) {
20935
20987
  }
20936
20988
 
20937
20989
  // src/daemon.ts
20938
- import { existsSync as existsSync42, mkdirSync as mkdirSync30, readFileSync as readFileSync38, readdirSync as readdirSync17, rmSync as rmSync20, statSync as statSync12, writeFileSync as writeFileSync31 } from "fs";
20990
+ import { existsSync as existsSync42, mkdirSync as mkdirSync30, readFileSync as readFileSync38, readdirSync as readdirSync17, rmSync as rmSync20, statSync as statSync13, writeFileSync as writeFileSync31 } from "fs";
20939
20991
  import { join as join45 } from "path";
20940
20992
  import { randomBytes as randomBytes2 } from "crypto";
20941
20993
  var DEFAULT_FLOOR_MS = 5e3;
@@ -21016,7 +21068,7 @@ async function runDaemonLoop(o) {
21016
21068
  function latestMtime(path) {
21017
21069
  let stat2;
21018
21070
  try {
21019
- stat2 = statSync12(path);
21071
+ stat2 = statSync13(path);
21020
21072
  } catch {
21021
21073
  return 0;
21022
21074
  }
@@ -21040,7 +21092,7 @@ function reloadSentinelFor(stateDir) {
21040
21092
  }
21041
21093
  function readReloadRequestedAt(stateDir) {
21042
21094
  try {
21043
- return statSync12(reloadSentinelFor(stateDir)).mtimeMs;
21095
+ return statSync13(reloadSentinelFor(stateDir)).mtimeMs;
21044
21096
  } catch {
21045
21097
  return null;
21046
21098
  }
@@ -21065,7 +21117,7 @@ function readDaemonStarted(stateDir) {
21065
21117
  function configChangedAfter(files, startedAt) {
21066
21118
  for (const f of files) {
21067
21119
  try {
21068
- if (statSync12(f).mtimeMs > startedAt) return f;
21120
+ if (statSync13(f).mtimeMs > startedAt) return f;
21069
21121
  } catch {
21070
21122
  }
21071
21123
  }
@@ -21628,7 +21680,7 @@ import {
21628
21680
  readSync as readSync3,
21629
21681
  readdirSync as readdirSync18,
21630
21682
  rmSync as rmSync21,
21631
- statSync as statSync13,
21683
+ statSync as statSync14,
21632
21684
  watch
21633
21685
  } from "fs";
21634
21686
  import { createConnection } from "net";
@@ -21972,7 +22024,7 @@ function readRange(path, from, max) {
21972
22024
  }
21973
22025
  }
21974
22026
  function lastLines2(path, n) {
21975
- const st = statSync13(path);
22027
+ const st = statSync14(path);
21976
22028
  const cursorAt = (off) => encodeCursor({ dev: st.dev, ino: st.ino, off });
21977
22029
  if (n <= 0 || st.size === 0) return { lines: [], cursor: cursorAt(st.size), reset: false };
21978
22030
  let start = st.size;
@@ -22006,7 +22058,7 @@ function readTail(live, opts) {
22006
22058
  } else {
22007
22059
  const found = candidates(live, opts.rotated ?? false).find((p) => {
22008
22060
  try {
22009
- const s = statSync13(p);
22061
+ const s = statSync14(p);
22010
22062
  return s.ino === cur.ino && s.dev === cur.dev;
22011
22063
  } catch {
22012
22064
  return false;
@@ -22017,7 +22069,7 @@ function readTail(live, opts) {
22017
22069
  off = cur.off;
22018
22070
  } else reset = true;
22019
22071
  }
22020
- let size = statSync13(path).size;
22072
+ let size = statSync14(path).size;
22021
22073
  if (off > size) {
22022
22074
  off = 0;
22023
22075
  reset = true;
@@ -22025,12 +22077,12 @@ function readTail(live, opts) {
22025
22077
  if (off >= size && path !== live) {
22026
22078
  path = live;
22027
22079
  off = 0;
22028
- size = statSync13(path).size;
22080
+ size = statSync14(path).size;
22029
22081
  }
22030
22082
  const { text: text2 } = readRange(path, off, max);
22031
22083
  const cut = text2.lastIndexOf("\n");
22032
22084
  const whole = cut < 0 ? "" : text2.slice(0, cut + 1);
22033
- const st = statSync13(path);
22085
+ const st = statSync14(path);
22034
22086
  return {
22035
22087
  lines: whole.split("\n").filter((l) => l.length),
22036
22088
  cursor: encodeCursor({ dev: st.dev, ino: st.ino, off: off + Buffer.byteLength(whole) }),
@@ -28058,7 +28110,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
28058
28110
  }
28059
28111
  try {
28060
28112
  const evFile = eventFileFor(cfg.ship.stateDir);
28061
- const evSize = existsSync45(evFile) ? statSync14(evFile).size : 0;
28113
+ const evSize = existsSync45(evFile) ? statSync15(evFile).size : 0;
28062
28114
  process.stdout.write(
28063
28115
  evSize > cfg.ship.eventsRotateBytes * 4 ? `events log: WARN ${evFile} is ${Math.round(evSize / 1048576)} MB (> 4x eventsRotateBytes); run \`crew reap\` to rotate it
28064
28116
  ` : "events log: ok\n"
@@ -28092,7 +28144,7 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
28092
28144
  const changed2 = configChangedAfter(daemonConfigPaths(cfg), daemonStartedAt);
28093
28145
  if (changed2) {
28094
28146
  process.stdout.write(
28095
- `daemon config: WARN ${changed2} changed at ${new Date(statSync14(changed2).mtimeMs).toISOString()} but the daemon started at ${new Date(daemonStartedAt).toISOString()} \u2014 run \`crew reload\`
28147
+ `daemon config: WARN ${changed2} changed at ${new Date(statSync15(changed2).mtimeMs).toISOString()} but the daemon started at ${new Date(daemonStartedAt).toISOString()} \u2014 run \`crew reload\`
28096
28148
  `
28097
28149
  );
28098
28150
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tablation/crew",
3
- "version": "0.109.0",
3
+ "version": "0.110.0",
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",