@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 +95 -29
- package/package.json +2 -2
- package/sandbox-image.json +1 -1
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.
|
|
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
|
|
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:
|
|
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
|
-
|
|
9698
|
-
|
|
9699
|
-
|
|
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
|
-
|
|
21837
|
-
|
|
21838
|
-
|
|
21839
|
-
|
|
21840
|
-
|
|
21841
|
-
|
|
21842
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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": "
|
|
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": {
|
package/sandbox-image.json
CHANGED