@archme/cli 0.17.0 → 0.17.1
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 +115 -61
- package/package.json +1 -1
- package/sandbox-image.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -8649,11 +8649,11 @@ async function findKey(supabaseUrl, userId) {
|
|
|
8649
8649
|
return (await listKeys(supabaseUrl)).find((k) => k.user_id === userId) ?? null;
|
|
8650
8650
|
}
|
|
8651
8651
|
async function saveKey(supabaseUrl, toSave) {
|
|
8652
|
-
const
|
|
8652
|
+
const saved2 = { ...toSave, saved_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
8653
8653
|
return withRingLock(async () => {
|
|
8654
|
-
const others = (await readEntries()).filter((k) => !isFor(k, supabaseUrl,
|
|
8655
|
-
await writeEntries([...others, { ...
|
|
8656
|
-
return
|
|
8654
|
+
const others = (await readEntries()).filter((k) => !isFor(k, supabaseUrl, saved2.user_id));
|
|
8655
|
+
await writeEntries([...others, { ...saved2, supabase_url: supabaseUrl }]);
|
|
8656
|
+
return saved2;
|
|
8657
8657
|
});
|
|
8658
8658
|
}
|
|
8659
8659
|
async function forgetKey(supabaseUrl, userId) {
|
|
@@ -9316,10 +9316,10 @@ async function mintKeyedAccount(deps) {
|
|
|
9316
9316
|
if (res.status !== 200 || !parsed || !data.user_id) {
|
|
9317
9317
|
throw new LoginError(`Could not create an account (${res.status}): ${redactKey(res.data)}`);
|
|
9318
9318
|
}
|
|
9319
|
-
let
|
|
9319
|
+
let saved2 = false;
|
|
9320
9320
|
try {
|
|
9321
9321
|
await deps.saveKey(deps.supabaseUrl, { user_id: data.user_id, key: data.key, handle: parsed.handle });
|
|
9322
|
-
|
|
9322
|
+
saved2 = true;
|
|
9323
9323
|
} catch (err4) {
|
|
9324
9324
|
deps.log(`! Created ${parsed.handle}, but its key could not be written to ${deps.keyRingPath}: ${err4 instanceof Error ? err4.message : String(err4)}`);
|
|
9325
9325
|
deps.log(" It is the only way back into this account. Save this now (a password manager is a good place):");
|
|
@@ -9331,7 +9331,7 @@ async function mintKeyedAccount(deps) {
|
|
|
9331
9331
|
const grant = await deps.passwordGrant(parsed.email, parsed.secret);
|
|
9332
9332
|
if (!grant.ok) {
|
|
9333
9333
|
throw new LoginError(
|
|
9334
|
-
`Created ${parsed.handle}${
|
|
9334
|
+
`Created ${parsed.handle}${saved2 ? " and saved its key" : ""}, but signing in failed: ${grant.error}. ` + (saved2 ? "Run `archme login --key` to try again." : `Try again with the key printed above: ${KEY_VIA_ENV}.`)
|
|
9335
9335
|
);
|
|
9336
9336
|
}
|
|
9337
9337
|
try {
|
|
@@ -9339,11 +9339,11 @@ async function mintKeyedAccount(deps) {
|
|
|
9339
9339
|
} catch (err4) {
|
|
9340
9340
|
if (!(err4 instanceof CredentialStoreError)) throw err4;
|
|
9341
9341
|
throw new LoginError(
|
|
9342
|
-
`${err4.message} Your account ${parsed.handle} was created${
|
|
9342
|
+
`${err4.message} Your account ${parsed.handle} was created${saved2 ? " and its key saved" : ""}; once that's fixed, sign in with it: ${saved2 ? "`archme login --key`" : KEY_VIA_ENV}.`
|
|
9343
9343
|
);
|
|
9344
9344
|
}
|
|
9345
9345
|
deps.log(`\u2713 Created your ArchMe account ${parsed.handle} \u2014 no email or password, just a key.`);
|
|
9346
|
-
if (
|
|
9346
|
+
if (saved2) {
|
|
9347
9347
|
deps.log(` The key is saved in ${deps.keyRingPath}. Save it in your password manager too: it is the`);
|
|
9348
9348
|
deps.log(" only way back in, including from another computer. `archme key show` prints it.");
|
|
9349
9349
|
}
|
|
@@ -10692,7 +10692,7 @@ __export(install_exports, {
|
|
|
10692
10692
|
import { isatty } from "node:tty";
|
|
10693
10693
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
10694
10694
|
import { dirname as dirname6, join as join5 } from "node:path";
|
|
10695
|
-
import { mkdir as mkdir5 } from "node:fs/promises";
|
|
10695
|
+
import { chmod as chmod3, mkdir as mkdir5 } from "node:fs/promises";
|
|
10696
10696
|
import { existsSync as existsSync2 } from "node:fs";
|
|
10697
10697
|
function bakedSystemdEnv(unit) {
|
|
10698
10698
|
const out = {};
|
|
@@ -10771,12 +10771,16 @@ Starting the installed service anyway; it reads its own sign-in when it boots.`)
|
|
|
10771
10771
|
}
|
|
10772
10772
|
}
|
|
10773
10773
|
}
|
|
10774
|
+
async function ensurePrivateDir(dir) {
|
|
10775
|
+
await mkdir5(dir, { recursive: true, mode: 448 });
|
|
10776
|
+
await chmod3(dir, 448);
|
|
10777
|
+
}
|
|
10774
10778
|
async function runStartFlow(opts = {}) {
|
|
10775
10779
|
const platform3 = opts.platform ?? process.platform;
|
|
10776
10780
|
const env = opts.env ?? process.env;
|
|
10777
10781
|
await ensureStartPrerequisites(opts);
|
|
10778
10782
|
const logPaths = resolveLogPath(platform3, env);
|
|
10779
|
-
await
|
|
10783
|
+
await ensurePrivateDir(resolveLogDir(platform3, env));
|
|
10780
10784
|
const controller = opts.controller ?? pickController(platform3, env);
|
|
10781
10785
|
const installed = await controller.readUnitContent();
|
|
10782
10786
|
const unitEnv2 = installed === null ? {} : platform3 === "darwin" ? bakedUnitEnv(installed) : bakedSystemdEnv(installed);
|
|
@@ -17421,9 +17425,9 @@ var init_sessionAuth = __esm({
|
|
|
17421
17425
|
this.userId = resolveUserId(userInfo2, ctx.fallbackUserId);
|
|
17422
17426
|
this.lastUserInfo = userInfo2;
|
|
17423
17427
|
}
|
|
17424
|
-
let
|
|
17428
|
+
let saved2 = null;
|
|
17425
17429
|
try {
|
|
17426
|
-
|
|
17430
|
+
saved2 = await saveCredentials({
|
|
17427
17431
|
kind: this.kind,
|
|
17428
17432
|
refresh_token: nextRefreshToken,
|
|
17429
17433
|
access_token: this.accessToken,
|
|
@@ -17438,7 +17442,7 @@ var init_sessionAuth = __esm({
|
|
|
17438
17442
|
`auth: ${err4.message} The refreshed sign-in was NOT saved: this process keeps working, but the saved one is now out of date. If this process ends before a later refresh saves it, sign in again then. ${this.reloginHint()}`
|
|
17439
17443
|
);
|
|
17440
17444
|
}
|
|
17441
|
-
return { userInfo: userInfo2, saved, nextRefreshToken };
|
|
17445
|
+
return { userInfo: userInfo2, saved: saved2, nextRefreshToken };
|
|
17442
17446
|
}
|
|
17443
17447
|
// The timer closure reads the LIVE this.refreshToken/deviceId/orgId at fire
|
|
17444
17448
|
// time (via runScheduledRefresh), never a captured value, so an out-of-band
|
|
@@ -17539,13 +17543,13 @@ var init_sessionAuth = __esm({
|
|
|
17539
17543
|
}
|
|
17540
17544
|
throw new LoadStoredAndRefreshError("transient", `${errorPrefix} failed: ${refreshed.error}`);
|
|
17541
17545
|
}
|
|
17542
|
-
const { saved } = await this.applyRefreshedTokens(refreshed, {
|
|
17546
|
+
const { saved: saved2 } = await this.applyRefreshedTokens(refreshed, {
|
|
17543
17547
|
prevRefreshToken: refreshToken,
|
|
17544
17548
|
deviceId,
|
|
17545
17549
|
orgId,
|
|
17546
17550
|
fallbackUserId: this.userId ?? void 0
|
|
17547
17551
|
});
|
|
17548
|
-
if (
|
|
17552
|
+
if (saved2) console.log(`auth: persisted refreshed token to ${describeCredentialSource(saved2)}`);
|
|
17549
17553
|
})();
|
|
17550
17554
|
this.refreshInFlight = p;
|
|
17551
17555
|
return p.finally(() => {
|
|
@@ -17578,13 +17582,13 @@ var init_sessionAuth = __esm({
|
|
|
17578
17582
|
// clearing. Permanent only when there is no saved key or the key is refused;
|
|
17579
17583
|
// the key ring itself is never modified here.
|
|
17580
17584
|
async signInWithSavedKey(userId, refreshError) {
|
|
17581
|
-
let
|
|
17585
|
+
let saved2;
|
|
17582
17586
|
try {
|
|
17583
|
-
|
|
17587
|
+
saved2 = userId ? await findKey(this.cfg.supabaseUrl, userId) : null;
|
|
17584
17588
|
} catch (err4) {
|
|
17585
17589
|
return { ok: false, permanent: false, error: `${refreshError}; ${err4 instanceof Error ? err4.message : String(err4)}` };
|
|
17586
17590
|
}
|
|
17587
|
-
const parsed = parseAccountKey(
|
|
17591
|
+
const parsed = parseAccountKey(saved2?.key);
|
|
17588
17592
|
if (!parsed) return { ok: false, permanent: true, error: `${refreshError}; no saved key for this account` };
|
|
17589
17593
|
console.error(`auth: refresh token rejected (${refreshError}); signing in again with the saved key for ${parsed.handle}`);
|
|
17590
17594
|
const granted = await passwordGrant(parsed.email, parsed.secret, this.cfg);
|
|
@@ -17917,7 +17921,10 @@ function rateLimitPauseUntil(text, nowMs = Date.now(), defaultTimeZone = localTi
|
|
|
17917
17921
|
if (!info) return null;
|
|
17918
17922
|
return {
|
|
17919
17923
|
info,
|
|
17920
|
-
|
|
17924
|
+
// The reset is text the run reported (a box can forge it, and a dated one a
|
|
17925
|
+
// week past rolls a year forward), and the pause stalls the whole account:
|
|
17926
|
+
// never longer than a day. A job still limited then just defers again.
|
|
17927
|
+
pauseUntil: new Date(Math.min(info.resetAt.getTime() + RATE_LIMIT_PAUSE_PADDING_MS, nowMs + RATE_LIMIT_MAX_PAUSE_MS))
|
|
17921
17928
|
};
|
|
17922
17929
|
}
|
|
17923
17930
|
function looksLikeRateLimit(text) {
|
|
@@ -17958,7 +17965,7 @@ function claudeErrorDeferPlan(error, nowMs, defaultTimeZone) {
|
|
|
17958
17965
|
}
|
|
17959
17966
|
return null;
|
|
17960
17967
|
}
|
|
17961
|
-
var RATE_LIMIT_RE, MONTHS, RATE_LIMIT_TENTATIVE_PAUSE_MS, DATED_RESET_ROLLOVER_GRACE_MS, RATE_LIMIT_PAUSE_PADDING_MS, RATE_LIMIT_SHAPED_RE;
|
|
17968
|
+
var RATE_LIMIT_RE, MONTHS, RATE_LIMIT_TENTATIVE_PAUSE_MS, DATED_RESET_ROLLOVER_GRACE_MS, RATE_LIMIT_PAUSE_PADDING_MS, RATE_LIMIT_MAX_PAUSE_MS, RATE_LIMIT_SHAPED_RE;
|
|
17962
17969
|
var init_rateLimit = __esm({
|
|
17963
17970
|
"src/daemon/rateLimit.ts"() {
|
|
17964
17971
|
"use strict";
|
|
@@ -17980,6 +17987,7 @@ var init_rateLimit = __esm({
|
|
|
17980
17987
|
RATE_LIMIT_TENTATIVE_PAUSE_MS = 60 * 60 * 1e3;
|
|
17981
17988
|
DATED_RESET_ROLLOVER_GRACE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
17982
17989
|
RATE_LIMIT_PAUSE_PADDING_MS = 9e4;
|
|
17990
|
+
RATE_LIMIT_MAX_PAUSE_MS = 24 * 60 * 60 * 1e3;
|
|
17983
17991
|
RATE_LIMIT_SHAPED_RE = /(\brate[ -]?limit\b|\blimit\b.{0,40}\bresets?\b|\bresets?\b.{0,40}\blimit\b|\b429\b|\bquota\b|\busage[ -]?limit\b)/i;
|
|
17984
17992
|
}
|
|
17985
17993
|
});
|
|
@@ -18313,7 +18321,7 @@ var init_localPrPoll = __esm({
|
|
|
18313
18321
|
});
|
|
18314
18322
|
|
|
18315
18323
|
// src/daemon/localRepos.ts
|
|
18316
|
-
import { chmod as
|
|
18324
|
+
import { chmod as chmod4, mkdir as mkdir9, readFile as readFile12, writeFile as writeFile9 } from "node:fs/promises";
|
|
18317
18325
|
import { homedir as homedir6 } from "node:os";
|
|
18318
18326
|
import { dirname as dirname12, join as join19 } from "node:path";
|
|
18319
18327
|
function localReposPath(env = process.env) {
|
|
@@ -18416,7 +18424,7 @@ async function saveLocalRepos(repos, env = process.env) {
|
|
|
18416
18424
|
const path7 = localReposPath(env);
|
|
18417
18425
|
await mkdir9(dirname12(path7), { recursive: true, mode: 448 });
|
|
18418
18426
|
await writeFile9(path7, serializeLocalRepos(repos), { mode: 384 });
|
|
18419
|
-
await
|
|
18427
|
+
await chmod4(path7, 384).catch(() => {
|
|
18420
18428
|
});
|
|
18421
18429
|
}
|
|
18422
18430
|
async function registerLocalRepo(entry, env = process.env) {
|
|
@@ -18649,6 +18657,21 @@ var init_defaultBranch = __esm({
|
|
|
18649
18657
|
}
|
|
18650
18658
|
});
|
|
18651
18659
|
|
|
18660
|
+
// src/daemon/sshAgent.ts
|
|
18661
|
+
function detachSshAgent(env = process.env) {
|
|
18662
|
+
if (env.SSH_AUTH_SOCK !== void 0) saved = env.SSH_AUTH_SOCK;
|
|
18663
|
+
delete env.SSH_AUTH_SOCK;
|
|
18664
|
+
}
|
|
18665
|
+
function localGitEnv() {
|
|
18666
|
+
return saved === void 0 ? process.env : { ...process.env, SSH_AUTH_SOCK: saved };
|
|
18667
|
+
}
|
|
18668
|
+
var saved;
|
|
18669
|
+
var init_sshAgent = __esm({
|
|
18670
|
+
"src/daemon/sshAgent.ts"() {
|
|
18671
|
+
"use strict";
|
|
18672
|
+
}
|
|
18673
|
+
});
|
|
18674
|
+
|
|
18652
18675
|
// ../shared/prRefs.ts
|
|
18653
18676
|
function parsePrRefs(message2) {
|
|
18654
18677
|
if (!message2) return [];
|
|
@@ -18701,7 +18724,8 @@ async function git(dir, args, opts = {}) {
|
|
|
18701
18724
|
requireBinary("git");
|
|
18702
18725
|
const res = await execa("git", args, {
|
|
18703
18726
|
cwd: dir ?? void 0,
|
|
18704
|
-
|
|
18727
|
+
// The SSH agent the daemon detached at boot (sshAgent.ts): origin may be ssh.
|
|
18728
|
+
env: { ...localGitEnv(), ...opts.env },
|
|
18705
18729
|
reject: false,
|
|
18706
18730
|
timeout: GIT_TIMEOUT_MS,
|
|
18707
18731
|
// Kill the whole process group: a hung `git fetch` has spawned helper
|
|
@@ -18807,6 +18831,7 @@ var init_localGit = __esm({
|
|
|
18807
18831
|
init_execa();
|
|
18808
18832
|
init_preflight();
|
|
18809
18833
|
init_defaultBranch();
|
|
18834
|
+
init_sshAgent();
|
|
18810
18835
|
init_prRefs();
|
|
18811
18836
|
GIT_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
18812
18837
|
}
|
|
@@ -19892,7 +19917,7 @@ var init_result2 = __esm({
|
|
|
19892
19917
|
});
|
|
19893
19918
|
|
|
19894
19919
|
// src/daemon/sandbox/inputDir.ts
|
|
19895
|
-
import { mkdtemp as mkdtemp3, readdir as readdir2, rm as rm7, writeFile as writeFile10, chmod as
|
|
19920
|
+
import { mkdtemp as mkdtemp3, readdir as readdir2, rm as rm7, writeFile as writeFile10, chmod as chmod5 } from "node:fs/promises";
|
|
19896
19921
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
19897
19922
|
import { join as join22 } from "node:path";
|
|
19898
19923
|
async function writeInputDir(step, input, base = tmpdir3()) {
|
|
@@ -19900,7 +19925,7 @@ async function writeInputDir(step, input, base = tmpdir3()) {
|
|
|
19900
19925
|
const cleanup = () => rm7(dir, { recursive: true, force: true }).catch(() => {
|
|
19901
19926
|
});
|
|
19902
19927
|
try {
|
|
19903
|
-
await
|
|
19928
|
+
await chmod5(dir, 448);
|
|
19904
19929
|
await writeFile10(join22(dir, "input.json"), JSON.stringify(input), { mode: 384, flag: "wx" });
|
|
19905
19930
|
} catch (e) {
|
|
19906
19931
|
await cleanup();
|
|
@@ -20112,7 +20137,7 @@ var init_confinement = __esm({
|
|
|
20112
20137
|
|
|
20113
20138
|
// src/daemon/sandbox/claudeToken.ts
|
|
20114
20139
|
import { constants as fsConstants } from "node:fs";
|
|
20115
|
-
import { chmod as
|
|
20140
|
+
import { chmod as chmod6, mkdir as mkdir11, open as open3 } from "node:fs/promises";
|
|
20116
20141
|
import { dirname as dirname15 } from "node:path";
|
|
20117
20142
|
function assertTokenShape(raw, where) {
|
|
20118
20143
|
const token = raw.trim();
|
|
@@ -20185,7 +20210,7 @@ async function storeSandboxClaudeToken(raw, opts) {
|
|
|
20185
20210
|
} finally {
|
|
20186
20211
|
await fh.close();
|
|
20187
20212
|
}
|
|
20188
|
-
await
|
|
20213
|
+
await chmod6(opts.path, 384);
|
|
20189
20214
|
return { source: "file", note };
|
|
20190
20215
|
}
|
|
20191
20216
|
async function checkClaudeToken(token, fetchFn = fetch) {
|
|
@@ -20611,24 +20636,28 @@ var init_runSandboxedJob = __esm({
|
|
|
20611
20636
|
|
|
20612
20637
|
// src/daemon/sandbox/classifySandboxExit.ts
|
|
20613
20638
|
function classifySandboxExit(input) {
|
|
20614
|
-
const { counters, exitCode,
|
|
20615
|
-
if ((counters?.cap_hits ?? 0) > 0
|
|
20639
|
+
const { counters, exitCode, stepFailure, claudeIsError } = input;
|
|
20640
|
+
if ((counters?.cap_hits ?? 0) > 0) {
|
|
20616
20641
|
return {
|
|
20617
20642
|
kind: "terminal",
|
|
20618
20643
|
reason: SANDBOX_REQUEST_CAP_REASON,
|
|
20619
20644
|
message: `${SANDBOX_REQUEST_CAP_REASON}: the job exceeded the sandbox model-request cap` + (counters ? ` (${counters.model_requests} requests, ${counters.cap_hits} refused)` : "")
|
|
20620
20645
|
};
|
|
20621
20646
|
}
|
|
20622
|
-
if (stepFailure == null && exitCode !== null && exitCode !== 0
|
|
20623
|
-
|
|
20647
|
+
if (stepFailure == null && exitCode !== null && (exitCode !== 0 || claudeIsError === true)) {
|
|
20648
|
+
const status = counters?.last_upstream_status;
|
|
20649
|
+
if (status === 429) return { kind: "upstream_rate_limit" };
|
|
20650
|
+
if (status === 401 || status === 403) return { kind: "upstream_auth_failure" };
|
|
20624
20651
|
}
|
|
20625
20652
|
return { kind: "normal" };
|
|
20626
20653
|
}
|
|
20627
20654
|
function routeSandboxFailure(verdict, planFor, nowMs = Date.now()) {
|
|
20628
|
-
if (verdict
|
|
20629
|
-
|
|
20630
|
-
|
|
20631
|
-
|
|
20655
|
+
if (verdict === null) {
|
|
20656
|
+
const plan = planFor();
|
|
20657
|
+
return plan ? { kind: "defer", plan } : { kind: "fail" };
|
|
20658
|
+
}
|
|
20659
|
+
if (verdict.kind === "terminal") return { kind: "terminal", message: verdict.message };
|
|
20660
|
+
if (verdict.kind === "upstream_rate_limit") {
|
|
20632
20661
|
return {
|
|
20633
20662
|
kind: "defer",
|
|
20634
20663
|
plan: { parsed: false, pauseUntil: new Date(nowMs + RATE_LIMIT_TENTATIVE_PAUSE_MS), info: null }
|
|
@@ -20641,7 +20670,6 @@ var init_classifySandboxExit = __esm({
|
|
|
20641
20670
|
"src/daemon/sandbox/classifySandboxExit.ts"() {
|
|
20642
20671
|
"use strict";
|
|
20643
20672
|
init_rateLimit();
|
|
20644
|
-
init_types();
|
|
20645
20673
|
SANDBOX_REQUEST_CAP_REASON = "sandbox_request_cap";
|
|
20646
20674
|
SandboxExitError = class extends Error {
|
|
20647
20675
|
constructor(message2, verdict) {
|
|
@@ -22495,15 +22523,15 @@ async function runCloneStep(input, deps = {}) {
|
|
|
22495
22523
|
}
|
|
22496
22524
|
}
|
|
22497
22525
|
function applyEnv(env) {
|
|
22498
|
-
const
|
|
22526
|
+
const saved2 = /* @__PURE__ */ new Map();
|
|
22499
22527
|
for (const [k, v] of Object.entries(env)) {
|
|
22500
22528
|
if (process.env[k] === v) continue;
|
|
22501
|
-
|
|
22529
|
+
saved2.set(k, process.env[k]);
|
|
22502
22530
|
if (v === void 0) delete process.env[k];
|
|
22503
22531
|
else process.env[k] = v;
|
|
22504
22532
|
}
|
|
22505
22533
|
return () => {
|
|
22506
|
-
for (const [k, v] of
|
|
22534
|
+
for (const [k, v] of saved2) {
|
|
22507
22535
|
if (v === void 0) delete process.env[k];
|
|
22508
22536
|
else process.env[k] = v;
|
|
22509
22537
|
}
|
|
@@ -22647,7 +22675,9 @@ async function runAgentStep(input, deps = {}) {
|
|
|
22647
22675
|
cwd: repoDir,
|
|
22648
22676
|
timeoutMs: input.timeoutMs,
|
|
22649
22677
|
model: input.model,
|
|
22650
|
-
|
|
22678
|
+
// Diff/PR: no Bash. `-p` mode does not bind built-ins to --allowedTools,
|
|
22679
|
+
// and a build step run through Bash can forge claude's stderr (BUG-143).
|
|
22680
|
+
...firstScan ? { allowedTools: FIRST_SCAN_ALLOWED_TOOLS } : { builtinTools: ["Read", "Glob", "Grep", "Agent"] },
|
|
22651
22681
|
jsonOutput: true,
|
|
22652
22682
|
settingSources: "user",
|
|
22653
22683
|
// A box is Linux: a prompt over 128 KiB cannot be one argv string.
|
|
@@ -22797,7 +22827,7 @@ __export(run_exports, {
|
|
|
22797
22827
|
});
|
|
22798
22828
|
import { createClient as createClient2 } from "@supabase/supabase-js";
|
|
22799
22829
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
22800
|
-
import { mkdtemp as mkdtemp4, mkdir as mkdir16, writeFile as writeFile15, rm as rm8, stat as stat8, chmod as
|
|
22830
|
+
import { mkdtemp as mkdtemp4, mkdir as mkdir16, writeFile as writeFile15, rm as rm8, stat as stat8, chmod as chmod7 } from "node:fs/promises";
|
|
22801
22831
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
22802
22832
|
import { join as join25 } from "node:path";
|
|
22803
22833
|
import { homedir as homedir8, networkInterfaces as networkInterfaces2 } from "node:os";
|
|
@@ -22969,7 +22999,7 @@ async function buildGitEnv(credentials) {
|
|
|
22969
22999
|
].join("\n"),
|
|
22970
23000
|
{ mode: 448 }
|
|
22971
23001
|
);
|
|
22972
|
-
await
|
|
23002
|
+
await chmod7(askpassPath, 448).catch(() => {
|
|
22973
23003
|
});
|
|
22974
23004
|
const gitConfigEntries = [
|
|
22975
23005
|
["credential.helper", ""],
|
|
@@ -23003,24 +23033,24 @@ async function ensureLocalCloneAndFetch(job, entry) {
|
|
|
23003
23033
|
`local mirror ${dir} is missing or not a git repo \u2014 re-run \`archme init ${entry.sourcePath}\``
|
|
23004
23034
|
);
|
|
23005
23035
|
}
|
|
23006
|
-
await execa("git", ["fetch", "--no-tags", "origin", job.repo_branch], { cwd: dir, reject: false });
|
|
23036
|
+
await execa("git", ["fetch", "--no-tags", "origin", job.repo_branch], { cwd: dir, env: localGitEnv(), reject: false });
|
|
23007
23037
|
if (job.source_mode === "pr_update") {
|
|
23008
23038
|
await verifyLocalPrJob(job, async (args) => {
|
|
23009
|
-
const r = await execa("git", args, { cwd: dir, reject: false });
|
|
23039
|
+
const r = await execa("git", args, { cwd: dir, env: localGitEnv(), reject: false });
|
|
23010
23040
|
return { exitCode: r.exitCode ?? 1, stdout: String(r.stdout ?? ""), stderr: String(r.stderr ?? "") };
|
|
23011
23041
|
});
|
|
23012
23042
|
return;
|
|
23013
23043
|
}
|
|
23014
|
-
const afterCheck = await execa("git", ["cat-file", "-e", job.after_sha], { cwd: dir, reject: false });
|
|
23044
|
+
const afterCheck = await execa("git", ["cat-file", "-e", job.after_sha], { cwd: dir, env: localGitEnv(), reject: false });
|
|
23015
23045
|
if (afterCheck.exitCode !== 0) {
|
|
23016
|
-
await execa("git", ["fetch", "origin"], { cwd: dir, reject: false });
|
|
23046
|
+
await execa("git", ["fetch", "origin"], { cwd: dir, env: localGitEnv(), reject: false });
|
|
23017
23047
|
}
|
|
23018
23048
|
}).catch((err4) => {
|
|
23019
23049
|
if (err4 instanceof LocalPrJobRejectedError) throw err4;
|
|
23020
23050
|
const msg = err4 instanceof Error ? err4.message : String(err4);
|
|
23021
23051
|
throw new Error(`local repo ${job.repo_owner}/${job.repo_name} is not usable: ${msg}`);
|
|
23022
23052
|
});
|
|
23023
|
-
return { dir, gitEnv:
|
|
23053
|
+
return { dir, gitEnv: localGitEnv(), cleanupGitEnv: async () => {
|
|
23024
23054
|
} };
|
|
23025
23055
|
}
|
|
23026
23056
|
async function ensureCloneAndFetch(ctx, job, claimDeviceId) {
|
|
@@ -23707,8 +23737,9 @@ async function completeClaudeRun(ctx, job, deviceId, run) {
|
|
|
23707
23737
|
throw new ClaudeRunError(
|
|
23708
23738
|
`claude reported is_error in its JSON result (no successful completion)`,
|
|
23709
23739
|
runResult.summary.trim().slice(-FAILURE_DETAIL_MAX) || null,
|
|
23710
|
-
isClaudeAuthFailure(claudeErrorResult(run.stdout), run.stderr),
|
|
23711
|
-
claudeErrorDetails(run.stdout)
|
|
23740
|
+
run.sandboxVerdict ? run.sandboxVerdict.kind === "upstream_auth_failure" : isClaudeAuthFailure(claudeErrorResult(run.stdout), run.stderr),
|
|
23741
|
+
claudeErrorDetails(run.stdout),
|
|
23742
|
+
run.sandboxVerdict ?? null
|
|
23712
23743
|
);
|
|
23713
23744
|
}
|
|
23714
23745
|
const summary2 = runResult.summary.trim().slice(0, 4e3);
|
|
@@ -23974,7 +24005,12 @@ async function runJob(ctx, job, claimDeviceId) {
|
|
|
23974
24005
|
await rpcRollback(ctx, job.id, deviceId);
|
|
23975
24006
|
return;
|
|
23976
24007
|
}
|
|
23977
|
-
const verdict = classifySandboxExit({
|
|
24008
|
+
const verdict = classifySandboxExit({
|
|
24009
|
+
counters: sandboxMetrics,
|
|
24010
|
+
exitCode: outcome.exitCode,
|
|
24011
|
+
stderr: outcome.stderr,
|
|
24012
|
+
claudeIsError: parseClaudeResult(outcome.claudeStdout).isError
|
|
24013
|
+
});
|
|
23978
24014
|
if (verdict.kind === "terminal") throw new SandboxExitError(verdict.message, verdict);
|
|
23979
24015
|
if (outcome.exitCode !== 0) {
|
|
23980
24016
|
const detail = claudeFailureDetail(outcome.stderr, outcome.claudeStdout);
|
|
@@ -23982,13 +24018,23 @@ async function runJob(ctx, job, claimDeviceId) {
|
|
|
23982
24018
|
throw new ClaudeRunError(
|
|
23983
24019
|
buildClaudeExitMessage(outcome.exitCode, outcome.signal ? ` (${outcome.signal})` : "", outcome.stderr),
|
|
23984
24020
|
detail,
|
|
23985
|
-
|
|
24021
|
+
// The gate's own 401/403, never the box's text (BUG-143).
|
|
24022
|
+
verdict.kind === "upstream_auth_failure",
|
|
23986
24023
|
claudeErrorDetails(outcome.claudeStdout),
|
|
23987
24024
|
verdict
|
|
23988
24025
|
);
|
|
23989
24026
|
}
|
|
23990
24027
|
const jobReads = readsFor(ctx, job, deviceId);
|
|
23991
24028
|
if (firstScan) {
|
|
24029
|
+
if (verdict.kind === "upstream_rate_limit" || verdict.kind === "upstream_auth_failure") {
|
|
24030
|
+
throw new ClaudeRunError(
|
|
24031
|
+
`claude reported is_error in its JSON result (no successful completion)`,
|
|
24032
|
+
null,
|
|
24033
|
+
verdict.kind === "upstream_auth_failure",
|
|
24034
|
+
claudeErrorDetails(outcome.claudeStdout),
|
|
24035
|
+
verdict
|
|
24036
|
+
);
|
|
24037
|
+
}
|
|
23992
24038
|
const gate = firstScanGate(await jobReads.architectureCounts());
|
|
23993
24039
|
if (!gate.ok) throw new Error(gate.reason);
|
|
23994
24040
|
}
|
|
@@ -24001,7 +24047,8 @@ async function runJob(ctx, job, claimDeviceId) {
|
|
|
24001
24047
|
startedAt,
|
|
24002
24048
|
markPhase,
|
|
24003
24049
|
log,
|
|
24004
|
-
reads: jobReads
|
|
24050
|
+
reads: jobReads,
|
|
24051
|
+
sandboxVerdict: verdict
|
|
24005
24052
|
});
|
|
24006
24053
|
return;
|
|
24007
24054
|
}
|
|
@@ -24271,8 +24318,9 @@ async function runJob(ctx, job, claimDeviceId) {
|
|
|
24271
24318
|
const runErr = err4 instanceof ClaudeRunError ? err4 : null;
|
|
24272
24319
|
const haystack = stripPromptEcho(`${msg}
|
|
24273
24320
|
${logs.stderr}`, sentPrompt);
|
|
24321
|
+
const boxed = boxedJobs.has(job.id);
|
|
24274
24322
|
const route2 = routeSandboxFailure(
|
|
24275
|
-
err4 instanceof SandboxExitError ? err4.verdict : runErr?.sandboxVerdict ?? null,
|
|
24323
|
+
err4 instanceof SandboxExitError ? err4.verdict : runErr?.sandboxVerdict ?? (boxed ? { kind: "normal" } : null),
|
|
24276
24324
|
() => rateLimitDeferPlanForExit({
|
|
24277
24325
|
message: msg,
|
|
24278
24326
|
stderr: logs.stderr,
|
|
@@ -24289,7 +24337,7 @@ ${logs.stderr}`, sentPrompt);
|
|
|
24289
24337
|
const plan = route2.kind === "defer" ? route2.plan : null;
|
|
24290
24338
|
if (plan) {
|
|
24291
24339
|
const { parsed, pauseUntil, info } = plan;
|
|
24292
|
-
if (!parsed) {
|
|
24340
|
+
if (!parsed && !boxed) {
|
|
24293
24341
|
await rpcReportEvent(ctx, job, deviceId, "rate_limit_unmatched_exit", {
|
|
24294
24342
|
job_id: job.id,
|
|
24295
24343
|
excerpt: [runErr?.claudeError?.text, haystack].filter(Boolean).join("\n").slice(0, 500)
|
|
@@ -24321,8 +24369,12 @@ ${logs.stderr}`, sentPrompt);
|
|
|
24321
24369
|
const notice = authFailureNotice(boxedJobs.has(job.id), runErr.detail);
|
|
24322
24370
|
console.error(`[${job.id}] ${notice.message} Deferring job + pausing daemon until ${pauseUntil.toISOString()}.`);
|
|
24323
24371
|
const detail = { job_id: job.id, claude_said: runErr.detail };
|
|
24324
|
-
|
|
24325
|
-
|
|
24372
|
+
if (job.job_kind !== "first_scan") {
|
|
24373
|
+
await rpcSetDaemonPause(ctx, pauseUntil, notice.reason, detail);
|
|
24374
|
+
setLocalPause(pauseUntil.getTime());
|
|
24375
|
+
} else {
|
|
24376
|
+
firstScanPauseUntilMs = Math.max(firstScanPauseUntilMs, pauseUntil.getTime());
|
|
24377
|
+
}
|
|
24326
24378
|
const deferred = await rpcDefer(ctx, job.id, deviceId, notice.reason, detail);
|
|
24327
24379
|
if (deferred) return;
|
|
24328
24380
|
console.warn(`[${job.id}] defer no-op \u2014 falling through to fail`);
|
|
@@ -24517,6 +24569,7 @@ function exitDaemon(code) {
|
|
|
24517
24569
|
process.exit(resolveExitCode(code, process.env));
|
|
24518
24570
|
}
|
|
24519
24571
|
async function runDaemon() {
|
|
24572
|
+
detachSshAgent();
|
|
24520
24573
|
let lockHandle = null;
|
|
24521
24574
|
try {
|
|
24522
24575
|
lockHandle = await acquireSingleInstanceLock(resolvePidfilePath(process.env));
|
|
@@ -24891,6 +24944,7 @@ var init_run = __esm({
|
|
|
24891
24944
|
init_host();
|
|
24892
24945
|
init_runSandboxedJob();
|
|
24893
24946
|
init_inputDir();
|
|
24947
|
+
init_sshAgent();
|
|
24894
24948
|
init_classifySandboxExit();
|
|
24895
24949
|
HEARTBEAT_INTERVAL_MS = 6e4;
|
|
24896
24950
|
CLAUDE_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
@@ -30714,7 +30768,7 @@ async function runConciergeToken(opts, deps) {
|
|
|
30714
30768
|
async function reserveStdout(fn) {
|
|
30715
30769
|
const stdout = process.stdout;
|
|
30716
30770
|
const realWrite = stdout.write;
|
|
30717
|
-
const
|
|
30771
|
+
const saved2 = { log: console.log, info: console.info, debug: console.debug };
|
|
30718
30772
|
const toStderr = (...args) => console.error(...args);
|
|
30719
30773
|
console.log = toStderr;
|
|
30720
30774
|
console.info = toStderr;
|
|
@@ -30726,9 +30780,9 @@ async function reserveStdout(fn) {
|
|
|
30726
30780
|
});
|
|
30727
30781
|
} finally {
|
|
30728
30782
|
stdout.write = realWrite;
|
|
30729
|
-
console.log =
|
|
30730
|
-
console.info =
|
|
30731
|
-
console.debug =
|
|
30783
|
+
console.log = saved2.log;
|
|
30784
|
+
console.info = saved2.info;
|
|
30785
|
+
console.debug = saved2.debug;
|
|
30732
30786
|
}
|
|
30733
30787
|
}
|
|
30734
30788
|
async function postConciergeTokenWith(supabaseUrl, accessToken, body) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@archme/cli",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.1",
|
|
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",
|
package/sandbox-image.json
CHANGED