@archme/cli 0.14.6 → 0.14.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.
package/dist/cli.js CHANGED
@@ -9653,8 +9653,7 @@ var init_launchctl = __esm({
9653
9653
  let r = await bootstrap();
9654
9654
  if (r.exitCode === 0 || r.exitCode === 17) return;
9655
9655
  if (r.exitCode === 5) {
9656
- await this.opts.spawner.run("launchctl", ["bootout", SERVICE_TARGET(this.opts.uid)]);
9657
- await new Promise((resolve7) => setTimeout(resolve7, 500));
9656
+ await this.stop();
9658
9657
  r = await bootstrap();
9659
9658
  if (r.exitCode === 0 || r.exitCode === 17) return;
9660
9659
  }
@@ -9665,9 +9664,22 @@ var init_launchctl = __esm({
9665
9664
  "bootout",
9666
9665
  SERVICE_TARGET(this.opts.uid)
9667
9666
  ]);
9668
- if (r.exitCode !== 0 && r.exitCode !== 113) {
9667
+ if (r.exitCode === 113) return;
9668
+ if (r.exitCode !== 0 && r.exitCode !== 3) {
9669
9669
  throw new Error(`launchctl bootout failed (exit ${r.exitCode}): ${r.stderr || r.stdout}`);
9670
9670
  }
9671
+ const pollMs = this.opts.stopPollMs ?? 250;
9672
+ const deadline = Date.now() + (this.opts.stopTimeoutMs ?? 25e3);
9673
+ const registered = async () => {
9674
+ const p = await this.opts.spawner.run("launchctl", ["print", SERVICE_TARGET(this.opts.uid)]);
9675
+ return !(p.exitCode === 113 || /Could not find/i.test(p.stderr));
9676
+ };
9677
+ while (await registered()) {
9678
+ if (Date.now() >= deadline) {
9679
+ throw new Error(`launchctl bootout: ${LABEL} is still registered with launchd after ${this.opts.stopTimeoutMs ?? 25e3}ms`);
9680
+ }
9681
+ await new Promise((resolve7) => setTimeout(resolve7, pollMs));
9682
+ }
9671
9683
  }
9672
9684
  async enable() {
9673
9685
  const r = await this.opts.spawner.run("launchctl", ["enable", SERVICE_TARGET(this.opts.uid)]);
@@ -9684,7 +9696,7 @@ var init_launchctl = __esm({
9684
9696
  async inspect() {
9685
9697
  const printRes = await this.opts.spawner.run("launchctl", ["print", SERVICE_TARGET(this.opts.uid)]);
9686
9698
  if (printRes.exitCode === 113 || /Could not find/i.test(printRes.stderr)) {
9687
- return { loaded: false, running: false, pid: null, enabled: false };
9699
+ return { loaded: false, running: false, pid: null, enabled: await this.enabled() };
9688
9700
  }
9689
9701
  if (printRes.exitCode !== 0) {
9690
9702
  throw new Error(`launchctl print failed (exit ${printRes.exitCode}): ${printRes.stderr || printRes.stdout}`);
@@ -9693,13 +9705,13 @@ var init_launchctl = __esm({
9693
9705
  const stateMatch = printRes.stdout.match(/^\s*state\s*=\s*(\S+)/m);
9694
9706
  const pid = pidMatch ? Number(pidMatch[1]) : null;
9695
9707
  const running = pid != null && stateMatch?.[1] === "running";
9708
+ return { loaded: true, running, pid, enabled: await this.enabled() };
9709
+ }
9710
+ async enabled() {
9696
9711
  const disRes = await this.opts.spawner.run("launchctl", ["print-disabled", DOMAIN(this.opts.uid)]);
9697
- let enabled = true;
9698
- if (disRes.exitCode === 0) {
9699
- const re = new RegExp(`"${LABEL.replace(/\./g, "\\.")}"\\s*=>\\s*true`);
9700
- if (re.test(disRes.stdout)) enabled = false;
9701
- }
9702
- return { loaded: true, running, pid, enabled };
9712
+ if (disRes.exitCode !== 0) return true;
9713
+ const re = new RegExp(`"${LABEL.replace(/\./g, "\\.")}"\\s*=>\\s*true`);
9714
+ return !re.test(disRes.stdout);
9703
9715
  }
9704
9716
  };
9705
9717
  }
@@ -10653,7 +10665,7 @@ function bakedSystemdEnv(unit) {
10653
10665
  function mergeUnitEnv(shell, unit) {
10654
10666
  const out = {};
10655
10667
  for (const k of ENV_KEYS_TO_BAKE) {
10656
- const v = shell[k] !== void 0 ? shell[k] : unit[k];
10668
+ const v = k === "PATH" ? [...new Set(`${shell.PATH ?? ""}:${unit.PATH ?? ""}`.split(":").filter(Boolean))].join(":") : shell[k] !== void 0 ? shell[k] : unit[k];
10657
10669
  if (v) out[k] = v;
10658
10670
  }
10659
10671
  return out;
@@ -19354,6 +19366,31 @@ var init_imageManifest = __esm({
19354
19366
  }
19355
19367
  });
19356
19368
 
19369
+ // src/daemon/claimStance.ts
19370
+ function claimStance(configured, gate) {
19371
+ if (!gate || gate.allowsClaims()) return { kind: "claim", mode: configured };
19372
+ if (gate.isFailing?.()) return { kind: "presence_only", mode: "required_unavailable" };
19373
+ return { kind: "hold" };
19374
+ }
19375
+ function claimRpcParams(a2) {
19376
+ return {
19377
+ p_device_id: a2.deviceId,
19378
+ p_worker_id: a2.workerId,
19379
+ p_lease_seconds: a2.leaseSeconds,
19380
+ // Stamps presence + gates against daemon_min_version (migration 067);
19381
+ // NULL (read failure) is below any floor server-side.
19382
+ p_daemon_version: a2.daemonVersion,
19383
+ // Recorded on presence only; never gates (R3 §3.4).
19384
+ p_claude_version: a2.claudeVersion,
19385
+ p_sandbox_mode: a2.sandboxMode
19386
+ };
19387
+ }
19388
+ var init_claimStance = __esm({
19389
+ "src/daemon/claimStance.ts"() {
19390
+ "use strict";
19391
+ }
19392
+ });
19393
+
19357
19394
  // src/daemon/sandboxJob/result.ts
19358
19395
  import { readFile as readFile13 } from "node:fs/promises";
19359
19396
  function isValidNonce(nonce) {
@@ -19654,6 +19691,10 @@ var init_confinement = __esm({
19654
19691
  allowsClaims() {
19655
19692
  return this.opts.mode !== "required" || !this.failing && !this.probing;
19656
19693
  }
19694
+ /** A probe has failed and no re-probe has passed since (re-probes included). */
19695
+ isFailing() {
19696
+ return this.failing;
19697
+ }
19657
19698
  schedule(ms) {
19658
19699
  if (this.stopped) return;
19659
19700
  if (this.handle !== null) this.timers.clearTimeout(this.handle);
@@ -21829,18 +21870,18 @@ The sub-agent returns its findings; **you then apply** the fixes yourself \u2014
21829
21870
  ${patchSection}
21830
21871
  ${DIFF_RULES}`;
21831
21872
  }
21832
- async function rpcClaim(ctx, deviceId) {
21873
+ async function rpcClaim(ctx, deviceId, mode) {
21874
+ const claudeVersion = await claudeCodeVersion();
21833
21875
  const { data, error } = await withAuthRetry(
21834
21876
  "claim",
21835
- () => ctx.supabase.rpc("claim_archme_job", {
21836
- p_device_id: deviceId,
21837
- p_worker_id: ctx.workerId,
21838
- p_lease_seconds: ctx.leaseSeconds,
21839
- // Stamps daemon_presence.daemon_version + gates against
21840
- // daemon_min_version (migration 067). NULL — read failure — is
21841
- // treated as below any floor server-side.
21842
- p_daemon_version: daemonVersion
21843
- }),
21877
+ () => ctx.supabase.rpc("claim_archme_job", claimRpcParams({
21878
+ deviceId,
21879
+ workerId: ctx.workerId,
21880
+ leaseSeconds: ctx.leaseSeconds,
21881
+ daemonVersion,
21882
+ claudeVersion,
21883
+ sandboxMode: mode
21884
+ })),
21844
21885
  ctx.reLogin
21845
21886
  );
21846
21887
  if (error) {
@@ -21972,11 +22013,14 @@ async function rpcDefer(ctx, jobId, deviceId, reason, detail) {
21972
22013
  }
21973
22014
  return data != null;
21974
22015
  }
21975
- async function rpcFail(ctx, jobId, deviceId, message2, metrics, logs) {
22016
+ async function rpcFail(ctx, jobId, deviceId, message2, metrics, logs, opts = {}) {
21976
22017
  const result = await callJobCompletionEndpoint(ctx, "fail", jobId, deviceId, {
21977
22018
  error: message2.slice(0, 4e3),
21978
22019
  metrics,
21979
- logs
22020
+ logs,
22021
+ // retry:false fails the job for good now (migration 141); absent, it
22022
+ // requeues while attempts remain.
22023
+ ...opts
21980
22024
  });
21981
22025
  if (!result.ok) console.error(`[${jobId}] fail error:`, result.error);
21982
22026
  }
@@ -22785,7 +22829,7 @@ ${logs.stderr}`, sentPrompt);
22785
22829
  );
22786
22830
  if (route2.kind === "terminal") {
22787
22831
  console.log(`[${job.id}] failed (sandbox request cap, not deferred): ${route2.message}`);
22788
- await rpcFail(ctx, job.id, deviceId, route2.message, metrics, logs);
22832
+ await rpcFail(ctx, job.id, deviceId, route2.message, metrics, logs, { retry: false });
22789
22833
  return;
22790
22834
  }
22791
22835
  const plan = route2.kind === "defer" ? route2.plan : null;
@@ -22853,7 +22897,18 @@ async function dispatch(sched) {
22853
22897
  }
22854
22898
  pauseUntilMs = null;
22855
22899
  }
22856
- if (confinementGate && !confinementGate.allowsClaims()) return;
22900
+ const stance = claimStance(sandboxMode, confinementGate);
22901
+ if (stance.kind === "hold") return;
22902
+ if (stance.kind === "presence_only") {
22903
+ sched.dispatching = true;
22904
+ try {
22905
+ for (const { ctx, deviceId } of claimUnits(sched)) await sched.claim(ctx, deviceId, stance.mode);
22906
+ } finally {
22907
+ sched.dispatching = false;
22908
+ }
22909
+ return;
22910
+ }
22911
+ const mode = stance.mode;
22857
22912
  sched.dispatching = true;
22858
22913
  try {
22859
22914
  let atCap = false;
@@ -22865,7 +22920,7 @@ async function dispatch(sched) {
22865
22920
  atCap = true;
22866
22921
  break;
22867
22922
  }
22868
- const claimed = await claimRoundRobin(sched);
22923
+ const claimed = await claimRoundRobin(sched, mode);
22869
22924
  if (!claimed) {
22870
22925
  slot();
22871
22926
  break;
@@ -22886,16 +22941,20 @@ async function dispatch(sched) {
22886
22941
  sched.dispatching = false;
22887
22942
  }
22888
22943
  }
22889
- async function claimRoundRobin(sched) {
22944
+ function claimUnits(sched) {
22890
22945
  const units = [];
22891
22946
  for (const ctx of sched.sessions) {
22892
22947
  if (ctx.auth && ctx.auth.getRefreshToken() === null && ctx.auth.getAccessToken() === null) continue;
22893
22948
  for (const d of ctx.devices) units.push({ ctx, deviceId: d.deviceId });
22894
22949
  }
22950
+ return units;
22951
+ }
22952
+ async function claimRoundRobin(sched, mode) {
22953
+ const units = claimUnits(sched);
22895
22954
  const n2 = units.length;
22896
22955
  for (let i2 = 0; i2 < n2; i2++) {
22897
22956
  const u2 = units[(sched.rrIndex + i2) % n2];
22898
- const job = await sched.claim(u2.ctx, u2.deviceId);
22957
+ const job = await sched.claim(u2.ctx, u2.deviceId, mode);
22899
22958
  if (job && job.id) {
22900
22959
  sched.rrIndex = (sched.rrIndex + i2 + 1) % n2;
22901
22960
  return { job, ctx: u2.ctx, deviceId: u2.deviceId };
@@ -23044,6 +23103,7 @@ async function runDaemon() {
23044
23103
  }
23045
23104
  exitDaemon(1);
23046
23105
  }
23106
+ sandboxMode = sandboxSettings.mode;
23047
23107
  if (process.env.ARCHME_SANDBOX_SLOTS !== void 0) {
23048
23108
  console.log(" ARCHME_SANDBOX_SLOTS is no longer read; sandbox slots follow MAX_CONCURRENT_JOBS");
23049
23109
  }
@@ -23310,7 +23370,7 @@ ${signal} received, shutting down`);
23310
23370
  process.on("SIGINT", () => shutdown("SIGINT"));
23311
23371
  process.on("SIGTERM", () => shutdown("SIGTERM"));
23312
23372
  }
23313
- var HEARTBEAT_INTERVAL_MS, CLAUDE_TIMEOUT_MS, CLAUDE_LARGE_DIFF_TIMEOUT_MS, SAFETY_SWEEP_MS, LOG_STDOUT_CAP_BYTES, LOG_STDERR_CAP_BYTES, FAILURE_DETAIL_MAX, AUTH_PAUSE_MS, ClaudeRunError, PRE_SPAWN_TOKEN_MIN_LIFETIME_MS, SUPABASE_URL, SUPABASE_ANON_KEY, ARCHME_DAEMON_CLIENT_ID, ARCHME_MCP_BASE_URL, CLONE_BASE_DIR, DAEMON_ID, LEASE_SECONDS, inFlight, cancelled, killReasons, activeCleanups, cloneMutex, shuttingDown, daemonVersion, pauseUntilMs, resumeTimer, currentSched, MAX_CONCURRENT_JOBS, ctxByJob, deviceByJob, sandboxHost, confinementGate, boxedJobs, leaseLost, prefetchOverride, DEFAULT_CLONE_BASE_DIR, JOB_TOKEN_PREFIX, INLINE_PATCH_BYTE_CAP, GIT_SHA_RE, CURATOR_PREAMBLE, CORE_PRINCIPLES, STORY_COVERAGE_GATE, NEW_STORY_GUIDANCE, STORY_DESCRIPTION_EDIT_RULE, PHASE_GUIDANCE, BASE_RULES, DIFF_RULES, finite, channelSeq, MultipleAccountsError, __testDispatch;
23373
+ var HEARTBEAT_INTERVAL_MS, CLAUDE_TIMEOUT_MS, CLAUDE_LARGE_DIFF_TIMEOUT_MS, SAFETY_SWEEP_MS, LOG_STDOUT_CAP_BYTES, LOG_STDERR_CAP_BYTES, FAILURE_DETAIL_MAX, AUTH_PAUSE_MS, ClaudeRunError, PRE_SPAWN_TOKEN_MIN_LIFETIME_MS, SUPABASE_URL, SUPABASE_ANON_KEY, ARCHME_DAEMON_CLIENT_ID, ARCHME_MCP_BASE_URL, CLONE_BASE_DIR, DAEMON_ID, LEASE_SECONDS, inFlight, cancelled, killReasons, activeCleanups, cloneMutex, shuttingDown, daemonVersion, pauseUntilMs, resumeTimer, currentSched, MAX_CONCURRENT_JOBS, ctxByJob, deviceByJob, sandboxHost, confinementGate, sandboxMode, boxedJobs, leaseLost, prefetchOverride, DEFAULT_CLONE_BASE_DIR, JOB_TOKEN_PREFIX, INLINE_PATCH_BYTE_CAP, GIT_SHA_RE, CURATOR_PREAMBLE, CORE_PRINCIPLES, STORY_COVERAGE_GATE, NEW_STORY_GUIDANCE, STORY_DESCRIPTION_EDIT_RULE, PHASE_GUIDANCE, BASE_RULES, DIFF_RULES, finite, channelSeq, MultipleAccountsError, __testDispatch;
23314
23374
  var init_run = __esm({
23315
23375
  "src/daemon/run.ts"() {
23316
23376
  "use strict";
@@ -23354,6 +23414,8 @@ var init_run = __esm({
23354
23414
  init_localPoller();
23355
23415
  init_setting();
23356
23416
  init_imageManifest();
23417
+ init_claimStance();
23418
+ init_claudeVersion();
23357
23419
  init_host();
23358
23420
  init_runSandboxedJob();
23359
23421
  init_inputDir();
@@ -23391,6 +23453,7 @@ var init_run = __esm({
23391
23453
  deviceByJob = /* @__PURE__ */ new Map();
23392
23454
  sandboxHost = null;
23393
23455
  confinementGate = null;
23456
+ sandboxMode = "off";
23394
23457
  boxedJobs = /* @__PURE__ */ new Set();
23395
23458
  leaseLost = /* @__PURE__ */ new Set();
23396
23459
  prefetchOverride = null;
@@ -23574,6 +23637,9 @@ ${ANCHOR_VERIFICATION}`;
23574
23637
  setConfinementGate(gate) {
23575
23638
  confinementGate = gate;
23576
23639
  },
23640
+ setSandboxMode(mode) {
23641
+ sandboxMode = mode;
23642
+ },
23577
23643
  // Daemon-version hooks for tests / introspection. The real value is
23578
23644
  // populated at runDaemon boot from package.json; tests can stub.
23579
23645
  setDaemonVersion(v) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archme/cli",
3
- "version": "0.14.6",
3
+ "version": "0.14.8",
4
4
  "description": "Map your codebase into a living architecture diagram: run `archme init` in a repo to scan it, `archme daemon start` to keep it current on every commit.",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -38,7 +38,7 @@
38
38
  "build": "node scripts/build.mjs",
39
39
  "build:check": "node scripts/smoke-build.mjs",
40
40
  "prepublishOnly": "npm run build:check",
41
- "test": "npx tsx --test --experimental-test-module-mocks 'src/**/*.test.ts'",
41
+ "test": "node --liftoff-only --import tsx --test --experimental-test-module-mocks 'src/**/*.test.ts'",
42
42
  "typecheck": "tsc --noEmit"
43
43
  },
44
44
  "dependencies": {
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "image": "ghcr.io/architectme/archme-job",
3
- "digest": "sha256:5fb1cc6d6fc5199820c05599db9ebe085b180950357686c87cfae4b9440f564b"
3
+ "digest": "sha256:9e4d5cd98c8d72bd14fadcf5063b3cd8c45dc2f65e2fe2ad44e141f5bfe0edbf"
4
4
  }