@tokenfactory/acc-runner 0.40.8 → 0.40.10
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/chat-transport.d.ts +8 -0
- package/dist/chat-transport.d.ts.map +1 -1
- package/dist/chat-transport.js +24 -4
- package/dist/chat-transport.js.map +1 -1
- package/dist/companion-identity.d.ts +144 -0
- package/dist/companion-identity.d.ts.map +1 -0
- package/dist/companion-identity.js +189 -0
- package/dist/companion-identity.js.map +1 -0
- package/dist/companion-run.d.ts +6 -1
- package/dist/companion-run.d.ts.map +1 -1
- package/dist/companion-run.js +103 -28
- package/dist/companion-run.js.map +1 -1
- package/dist/companion.d.ts +70 -2
- package/dist/companion.d.ts.map +1 -1
- package/dist/companion.js +150 -11
- package/dist/companion.js.map +1 -1
- package/dist/runtime/auth-lifecycle.d.ts +61 -0
- package/dist/runtime/auth-lifecycle.d.ts.map +1 -0
- package/dist/runtime/auth-lifecycle.js +120 -0
- package/dist/runtime/auth-lifecycle.js.map +1 -0
- package/dist/watch.d.ts +2 -21
- package/dist/watch.d.ts.map +1 -1
- package/dist/watch.js +5 -111
- package/dist/watch.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AUTH-REFRESH: shared runner auth-lifecycle primitives.
|
|
3
|
+
*
|
|
4
|
+
* Extracted verbatim from watch.ts so BOTH the fleet `watch` runner and the
|
|
5
|
+
* `companion` runner refresh, classify auth failures, and back off identically
|
|
6
|
+
* — a single source of truth for "how a runner keeps its session alive". Before
|
|
7
|
+
* this split the companion had NONE of it: its heartbeat logged the same
|
|
8
|
+
* "JWT expired" line every 4s forever (no refresh, no backoff, no exit),
|
|
9
|
+
* leaving a zombie process while the identity went stale server-side.
|
|
10
|
+
*
|
|
11
|
+
* watch.ts re-exports the heartbeat symbols so its existing test imports
|
|
12
|
+
* (`classifyHeartbeatError`, `nextHeartbeatDelayMs`, `HeartbeatErrorKind`) keep
|
|
13
|
+
* resolving from `watch.js`.
|
|
14
|
+
*/
|
|
15
|
+
import type { RunnerConfig } from "../config.js";
|
|
16
|
+
import type { StoredSession } from "../types.js";
|
|
17
|
+
export declare const ONE_HOUR_MS: number;
|
|
18
|
+
/**
|
|
19
|
+
* Thrown when /api/runner/refresh reports refresh-token reuse. There is no safe
|
|
20
|
+
* recovery from a running loop — the keychain has to be reset by
|
|
21
|
+
* `acc-runner login` — so callers surface it loudly and exit.
|
|
22
|
+
*/
|
|
23
|
+
export declare class RefreshReuseDetectedError extends Error {
|
|
24
|
+
constructor(message: string);
|
|
25
|
+
}
|
|
26
|
+
/** True when the session's access token expires within `leadMs` (or is unparseable). */
|
|
27
|
+
export declare function expiresSoon(session: StoredSession, leadMs?: number): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Exchange the stored refresh token for a fresh access token via
|
|
30
|
+
* /api/runner/refresh. Returns a new StoredSession carrying the rotated
|
|
31
|
+
* access + refresh tokens (the server rotates on every refresh; persisting the
|
|
32
|
+
* successor is mandatory or the next call replays the old one and trips the
|
|
33
|
+
* chain-revocation reuse path). Throws RefreshReuseDetectedError on a 401 whose
|
|
34
|
+
* body reports reuse; a generic Error otherwise.
|
|
35
|
+
*/
|
|
36
|
+
export declare function refreshAccessToken(cfg: RunnerConfig, session: StoredSession): Promise<StoredSession>;
|
|
37
|
+
export type HeartbeatErrorKind = "auth" | "dns" | "tls" | "reset" | "timeout" | "network" | "unknown";
|
|
38
|
+
/**
|
|
39
|
+
* Classify a heartbeat RPC error so callers (a) emit a useful line for
|
|
40
|
+
* the operator and (b) only attempt a JWT refresh when the failure shape
|
|
41
|
+
* actually implies an auth problem. Refreshing on a transient network blip just
|
|
42
|
+
* burns the refresh-token rotation chain and surfaces a confusing "refresh
|
|
43
|
+
* failed" line on top of the real underlying network error.
|
|
44
|
+
*/
|
|
45
|
+
export declare function classifyHeartbeatError(err: {
|
|
46
|
+
message?: string | null;
|
|
47
|
+
code?: string | null;
|
|
48
|
+
} | null | undefined): HeartbeatErrorKind;
|
|
49
|
+
/**
|
|
50
|
+
* Exponential backoff ladder between heartbeat attempts when the
|
|
51
|
+
* previous one failed. Bounds the retry cadence so a wedged network / expired
|
|
52
|
+
* token produces spaced attempts (max 60s) rather than a tight per-tick loop.
|
|
53
|
+
*/
|
|
54
|
+
export declare const HEARTBEAT_BACKOFF_MS: readonly [4000, 8000, 16000, 32000, 60000];
|
|
55
|
+
/**
|
|
56
|
+
* Pick the next inter-heartbeat delay from the backoff ladder. `consecutiveFailures`
|
|
57
|
+
* is 1 for the first retry. The return is at least `baseMs` so a tighter
|
|
58
|
+
* operator-configured cadence is never slowed by the backoff scheme.
|
|
59
|
+
*/
|
|
60
|
+
export declare function nextHeartbeatDelayMs(consecutiveFailures: number, baseMs: number): number;
|
|
61
|
+
//# sourceMappingURL=auth-lifecycle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-lifecycle.d.ts","sourceRoot":"","sources":["../../src/runtime/auth-lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,KAAK,EAAkC,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjF,eAAO,MAAM,WAAW,QAAiB,CAAC;AAE1C;;;;GAIG;AACH,qBAAa,yBAA0B,SAAQ,KAAK;gBACtC,OAAO,EAAE,MAAM;CAI5B;AAED,wFAAwF;AACxF,wBAAgB,WAAW,CAAC,OAAO,EAAE,aAAa,EAAE,MAAM,SAAc,GAAG,OAAO,CAIjF;AAED;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,YAAY,EACjB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,aAAa,CAAC,CA6BxB;AAED,MAAM,MAAM,kBAAkB,GAC1B,MAAM,GACN,KAAK,GACL,KAAK,GACL,OAAO,GACP,SAAS,GACT,SAAS,GACT,SAAS,CAAC;AAEd;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,GACxE,kBAAkB,CA2CpB;AAED;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,4CAAkD,CAAC;AAEpF;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,mBAAmB,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAIxF"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { PROTOCOL_VERSION } from "../types.js";
|
|
2
|
+
export const ONE_HOUR_MS = 60 * 60 * 1000;
|
|
3
|
+
/**
|
|
4
|
+
* Thrown when /api/runner/refresh reports refresh-token reuse. There is no safe
|
|
5
|
+
* recovery from a running loop — the keychain has to be reset by
|
|
6
|
+
* `acc-runner login` — so callers surface it loudly and exit.
|
|
7
|
+
*/
|
|
8
|
+
export class RefreshReuseDetectedError extends Error {
|
|
9
|
+
constructor(message) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = "RefreshReuseDetectedError";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/** True when the session's access token expires within `leadMs` (or is unparseable). */
|
|
15
|
+
export function expiresSoon(session, leadMs = ONE_HOUR_MS) {
|
|
16
|
+
const expires = Date.parse(session.access_expires_at);
|
|
17
|
+
if (!Number.isFinite(expires))
|
|
18
|
+
return true;
|
|
19
|
+
return expires < Date.now() + leadMs;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Exchange the stored refresh token for a fresh access token via
|
|
23
|
+
* /api/runner/refresh. Returns a new StoredSession carrying the rotated
|
|
24
|
+
* access + refresh tokens (the server rotates on every refresh; persisting the
|
|
25
|
+
* successor is mandatory or the next call replays the old one and trips the
|
|
26
|
+
* chain-revocation reuse path). Throws RefreshReuseDetectedError on a 401 whose
|
|
27
|
+
* body reports reuse; a generic Error otherwise.
|
|
28
|
+
*/
|
|
29
|
+
export async function refreshAccessToken(cfg, session) {
|
|
30
|
+
const res = await fetch(`${cfg.publicUrl.replace(/\/+$/, "")}/api/runner/refresh`, {
|
|
31
|
+
method: "POST",
|
|
32
|
+
headers: {
|
|
33
|
+
"Content-Type": "application/json",
|
|
34
|
+
"User-Agent": `acc-runner/${PROTOCOL_VERSION}`,
|
|
35
|
+
},
|
|
36
|
+
body: JSON.stringify({ refresh_token: session.refresh_token }),
|
|
37
|
+
});
|
|
38
|
+
if (res.status === 401) {
|
|
39
|
+
const body = (await res.json().catch(() => ({})));
|
|
40
|
+
if (body.error === "refresh_reuse_detected") {
|
|
41
|
+
throw new RefreshReuseDetectedError(body.hint ??
|
|
42
|
+
"refresh token reuse detected; keychain may be compromised. Run `acc-runner login` from a trusted machine.");
|
|
43
|
+
}
|
|
44
|
+
throw new Error(body.hint ?? "refresh token expired; run `acc-runner login`");
|
|
45
|
+
}
|
|
46
|
+
if (!res.ok) {
|
|
47
|
+
throw new Error(`refresh failed: ${res.status} ${await res.text()}`);
|
|
48
|
+
}
|
|
49
|
+
const body = (await res.json());
|
|
50
|
+
return {
|
|
51
|
+
...session,
|
|
52
|
+
access_token: body.access_token,
|
|
53
|
+
access_expires_at: new Date(body.expires_at * 1000).toISOString(),
|
|
54
|
+
refresh_token: body.refresh_token ?? session.refresh_token,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Classify a heartbeat RPC error so callers (a) emit a useful line for
|
|
59
|
+
* the operator and (b) only attempt a JWT refresh when the failure shape
|
|
60
|
+
* actually implies an auth problem. Refreshing on a transient network blip just
|
|
61
|
+
* burns the refresh-token rotation chain and surfaces a confusing "refresh
|
|
62
|
+
* failed" line on top of the real underlying network error.
|
|
63
|
+
*/
|
|
64
|
+
export function classifyHeartbeatError(err) {
|
|
65
|
+
if (!err)
|
|
66
|
+
return "unknown";
|
|
67
|
+
const msg = (err.message ?? "").toLowerCase();
|
|
68
|
+
const code = (err.code ?? "").toLowerCase();
|
|
69
|
+
if (code === "pgrst301" ||
|
|
70
|
+
code === "401" ||
|
|
71
|
+
code === "403" ||
|
|
72
|
+
msg.includes("jwt") ||
|
|
73
|
+
msg.includes("invalid token") ||
|
|
74
|
+
msg.includes("token has expired") ||
|
|
75
|
+
msg.includes("unauthorized") ||
|
|
76
|
+
msg.includes("forbidden")) {
|
|
77
|
+
return "auth";
|
|
78
|
+
}
|
|
79
|
+
if (msg.includes("enotfound") || msg.includes("getaddrinfo") || msg.includes("eai_again")) {
|
|
80
|
+
return "dns";
|
|
81
|
+
}
|
|
82
|
+
if (msg.includes("econnreset") || msg.includes("connection reset")) {
|
|
83
|
+
return "reset";
|
|
84
|
+
}
|
|
85
|
+
if (msg.includes("certificate") ||
|
|
86
|
+
msg.includes("self-signed") ||
|
|
87
|
+
msg.includes("self signed") ||
|
|
88
|
+
msg.includes("tls") ||
|
|
89
|
+
msg.includes("ssl")) {
|
|
90
|
+
return "tls";
|
|
91
|
+
}
|
|
92
|
+
if (msg.includes("etimedout") || msg.includes("timeout")) {
|
|
93
|
+
return "timeout";
|
|
94
|
+
}
|
|
95
|
+
if (msg.includes("fetch failed") ||
|
|
96
|
+
msg.includes("econnrefused") ||
|
|
97
|
+
msg.includes("network") ||
|
|
98
|
+
msg.includes("socket hang up")) {
|
|
99
|
+
return "network";
|
|
100
|
+
}
|
|
101
|
+
return "unknown";
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Exponential backoff ladder between heartbeat attempts when the
|
|
105
|
+
* previous one failed. Bounds the retry cadence so a wedged network / expired
|
|
106
|
+
* token produces spaced attempts (max 60s) rather than a tight per-tick loop.
|
|
107
|
+
*/
|
|
108
|
+
export const HEARTBEAT_BACKOFF_MS = [4_000, 8_000, 16_000, 32_000, 60_000];
|
|
109
|
+
/**
|
|
110
|
+
* Pick the next inter-heartbeat delay from the backoff ladder. `consecutiveFailures`
|
|
111
|
+
* is 1 for the first retry. The return is at least `baseMs` so a tighter
|
|
112
|
+
* operator-configured cadence is never slowed by the backoff scheme.
|
|
113
|
+
*/
|
|
114
|
+
export function nextHeartbeatDelayMs(consecutiveFailures, baseMs) {
|
|
115
|
+
if (consecutiveFailures <= 0)
|
|
116
|
+
return baseMs;
|
|
117
|
+
const idx = Math.min(consecutiveFailures - 1, HEARTBEAT_BACKOFF_MS.length - 1);
|
|
118
|
+
return Math.max(baseMs, HEARTBEAT_BACKOFF_MS[idx]);
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=auth-lifecycle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-lifecycle.js","sourceRoot":"","sources":["../../src/runtime/auth-lifecycle.ts"],"names":[],"mappings":"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"}
|
package/dist/watch.d.ts
CHANGED
|
@@ -8,9 +8,8 @@ import { type ReviewAssignment, type ReviewerOutcome } from "./runtime/reviewer.
|
|
|
8
8
|
import { type SingletonLock } from "./runtime/singleton.js";
|
|
9
9
|
import { type SelfUpgradeResult, type SelfUpdate426Result } from "./runtime/self-upgrade.js";
|
|
10
10
|
import type { StoredSession } from "./types.js";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
11
|
+
import { RefreshReuseDetectedError, classifyHeartbeatError, nextHeartbeatDelayMs, type HeartbeatErrorKind } from "./runtime/auth-lifecycle.js";
|
|
12
|
+
export { RefreshReuseDetectedError, classifyHeartbeatError, nextHeartbeatDelayMs, type HeartbeatErrorKind, };
|
|
14
13
|
/**
|
|
15
14
|
* REVFB (G1) / RVU-2 (FU-RVU2R): TASK slots pump() may fill, leaving `reserved`
|
|
16
15
|
* free for reviews. Normally leaves ≥1 task slot (no-op on a 1-slot runner); the
|
|
@@ -32,24 +31,6 @@ export declare function computeTaskClaimLimit(opts: {
|
|
|
32
31
|
capacityProbe?: boolean;
|
|
33
32
|
claimPaused?: boolean;
|
|
34
33
|
}): number;
|
|
35
|
-
export type HeartbeatErrorKind = "auth" | "dns" | "tls" | "reset" | "timeout" | "network" | "unknown";
|
|
36
|
-
/**
|
|
37
|
-
* v0.6.0: classify the heartbeat RPC error so we (a) emit a useful
|
|
38
|
-
* stderr line for the operator and (b) only attempt a JWT refresh
|
|
39
|
-
* when the failure shape actually implies an auth problem. Refreshing
|
|
40
|
-
* on a transient network blip just burns the refresh-token rotation
|
|
41
|
-
* chain and surfaces a confusing "refresh failed" line on top of the
|
|
42
|
-
* real underlying network error.
|
|
43
|
-
*/
|
|
44
|
-
export declare function classifyHeartbeatError(err: {
|
|
45
|
-
message?: string | null;
|
|
46
|
-
code?: string | null;
|
|
47
|
-
} | null | undefined): HeartbeatErrorKind;
|
|
48
|
-
/** Pick the next inter-heartbeat delay from the backoff ladder. Caller
|
|
49
|
-
* passes the number of consecutive failures (1 → first retry). The
|
|
50
|
-
* return is at least baseMs so a tighter operator-configured cadence
|
|
51
|
-
* is never slowed by the backoff scheme. */
|
|
52
|
-
export declare function nextHeartbeatDelayMs(consecutiveFailures: number, baseMs: number): number;
|
|
53
34
|
export interface WatchOptions {
|
|
54
35
|
/** Override heartbeat interval (ms). Defaults to 4000. */
|
|
55
36
|
heartbeatMs?: number;
|
package/dist/watch.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["../src/watch.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAML,KAAK,iBAAiB,EACtB,KAAK,YAAY,EAGlB,MAAM,aAAa,CAAC;AAIrB,OAAO,EAAE,uBAAuB,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AAClF,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAG3E,OAAO,EAAsB,KAAK,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAC9E,OAAO,EAAE,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACnE,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACrB,MAAM,uBAAuB,CAAC;AAK/B,OAAO,EAIL,KAAK,aAAa,EACnB,MAAM,wBAAwB,CAAC;AAKhC,OAAO,EAIL,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACzB,MAAM,2BAA2B,CAAC;AAInC,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["../src/watch.ts"],"names":[],"mappings":"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"}
|
package/dist/watch.js
CHANGED
|
@@ -43,16 +43,13 @@ import { getClaudeVersion, recordTaskClaudeVersion, } from "./runtime/version-dr
|
|
|
43
43
|
import { autoUpgradeDisabled, maybeSelfUpgrade, maybeSelfUpdateOn426, } from "./runtime/self-upgrade.js";
|
|
44
44
|
import { postRunnerStateMessage } from "./messaging.js";
|
|
45
45
|
import { checkVersion, compareSemver } from "./version-check.js";
|
|
46
|
-
import { PROTOCOL_VERSION } from "./types.js";
|
|
47
46
|
import { PACKAGE_VERSION } from "./pkg-version.js";
|
|
48
47
|
import { resolveAuthMode, describeAuthMode, authPrecedenceHint } from "./provider-auth.js";
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
const ONE_HOUR_MS = 60 * 60 * 1000;
|
|
48
|
+
// AUTH-REFRESH: the refresh + heartbeat-classification
|
|
49
|
+
// primitives now live in a shared module so the companion runner reuses them
|
|
50
|
+
// verbatim. Re-exported below to keep watch.js's existing test-facing surface.
|
|
51
|
+
import { RefreshReuseDetectedError, classifyHeartbeatError, expiresSoon, nextHeartbeatDelayMs, refreshAccessToken, } from "./runtime/auth-lifecycle.js";
|
|
52
|
+
export { RefreshReuseDetectedError, classifyHeartbeatError, nextHeartbeatDelayMs, };
|
|
56
53
|
const REFRESH_CHECK_MS = 30 * 60 * 1000;
|
|
57
54
|
// v0.12.0 (T-52-7): idle-TTL check cadence ceiling. The actual cadence is
|
|
58
55
|
// min(IDLE_CHECK_MS, ttl) so a tiny test TTL still gets checked in time.
|
|
@@ -147,72 +144,6 @@ export function computeTaskClaimLimit(opts) {
|
|
|
147
144
|
const reserved = Math.max(0, Math.floor(opts.reservedReviewSlots));
|
|
148
145
|
return Math.max(1, limit - reserved);
|
|
149
146
|
}
|
|
150
|
-
/**
|
|
151
|
-
* v0.6.0: exponential backoff between heartbeat attempts when the
|
|
152
|
-
* previous one failed. The dogfood loop fired a heartbeat every 4s
|
|
153
|
-
* even when the network was wedged, producing a wall of "fetch failed"
|
|
154
|
-
* stderr noise and reaching the EXIT_AFTER cap in 20s. Spreading
|
|
155
|
-
* attempts out gives flaky links time to recover before we tear down
|
|
156
|
-
* the watch process.
|
|
157
|
-
*/
|
|
158
|
-
const HEARTBEAT_BACKOFF_MS = [4_000, 8_000, 16_000, 32_000, 60_000];
|
|
159
|
-
/**
|
|
160
|
-
* v0.6.0: classify the heartbeat RPC error so we (a) emit a useful
|
|
161
|
-
* stderr line for the operator and (b) only attempt a JWT refresh
|
|
162
|
-
* when the failure shape actually implies an auth problem. Refreshing
|
|
163
|
-
* on a transient network blip just burns the refresh-token rotation
|
|
164
|
-
* chain and surfaces a confusing "refresh failed" line on top of the
|
|
165
|
-
* real underlying network error.
|
|
166
|
-
*/
|
|
167
|
-
export function classifyHeartbeatError(err) {
|
|
168
|
-
if (!err)
|
|
169
|
-
return "unknown";
|
|
170
|
-
const msg = (err.message ?? "").toLowerCase();
|
|
171
|
-
const code = (err.code ?? "").toLowerCase();
|
|
172
|
-
if (code === "pgrst301" ||
|
|
173
|
-
code === "401" ||
|
|
174
|
-
code === "403" ||
|
|
175
|
-
msg.includes("jwt") ||
|
|
176
|
-
msg.includes("invalid token") ||
|
|
177
|
-
msg.includes("token has expired") ||
|
|
178
|
-
msg.includes("unauthorized") ||
|
|
179
|
-
msg.includes("forbidden")) {
|
|
180
|
-
return "auth";
|
|
181
|
-
}
|
|
182
|
-
if (msg.includes("enotfound") || msg.includes("getaddrinfo") || msg.includes("eai_again")) {
|
|
183
|
-
return "dns";
|
|
184
|
-
}
|
|
185
|
-
if (msg.includes("econnreset") || msg.includes("connection reset")) {
|
|
186
|
-
return "reset";
|
|
187
|
-
}
|
|
188
|
-
if (msg.includes("certificate") ||
|
|
189
|
-
msg.includes("self-signed") ||
|
|
190
|
-
msg.includes("self signed") ||
|
|
191
|
-
msg.includes("tls") ||
|
|
192
|
-
msg.includes("ssl")) {
|
|
193
|
-
return "tls";
|
|
194
|
-
}
|
|
195
|
-
if (msg.includes("etimedout") || msg.includes("timeout")) {
|
|
196
|
-
return "timeout";
|
|
197
|
-
}
|
|
198
|
-
if (msg.includes("fetch failed") ||
|
|
199
|
-
msg.includes("econnrefused") ||
|
|
200
|
-
msg.includes("network") ||
|
|
201
|
-
msg.includes("socket hang up")) {
|
|
202
|
-
return "network";
|
|
203
|
-
}
|
|
204
|
-
return "unknown";
|
|
205
|
-
}
|
|
206
|
-
/** Pick the next inter-heartbeat delay from the backoff ladder. Caller
|
|
207
|
-
* passes the number of consecutive failures (1 → first retry). The
|
|
208
|
-
* return is at least baseMs so a tighter operator-configured cadence
|
|
209
|
-
* is never slowed by the backoff scheme. */
|
|
210
|
-
export function nextHeartbeatDelayMs(consecutiveFailures, baseMs) {
|
|
211
|
-
if (consecutiveFailures <= 0)
|
|
212
|
-
return baseMs;
|
|
213
|
-
const idx = Math.min(consecutiveFailures - 1, HEARTBEAT_BACKOFF_MS.length - 1);
|
|
214
|
-
return Math.max(baseMs, HEARTBEAT_BACKOFF_MS[idx]);
|
|
215
|
-
}
|
|
216
147
|
/**
|
|
217
148
|
* v0.12.0 (T-52-7): resolve ACC_RUNNER_IDLE_TTL_MIN into ms. Returns null
|
|
218
149
|
* (TTL disabled) when unset; warns and returns null on junk so a typo'd
|
|
@@ -233,43 +164,6 @@ export function resolveIdleTtlMs(env = process.env) {
|
|
|
233
164
|
function touchActivity(state) {
|
|
234
165
|
state.lastActivityMs = Date.now();
|
|
235
166
|
}
|
|
236
|
-
function expiresSoon(session, leadMs = ONE_HOUR_MS) {
|
|
237
|
-
const expires = Date.parse(session.access_expires_at);
|
|
238
|
-
if (!Number.isFinite(expires))
|
|
239
|
-
return true;
|
|
240
|
-
return expires < Date.now() + leadMs;
|
|
241
|
-
}
|
|
242
|
-
async function refreshAccessToken(cfg, session) {
|
|
243
|
-
const res = await fetch(`${cfg.publicUrl.replace(/\/+$/, "")}/api/runner/refresh`, {
|
|
244
|
-
method: "POST",
|
|
245
|
-
headers: {
|
|
246
|
-
"Content-Type": "application/json",
|
|
247
|
-
"User-Agent": `acc-runner/${PROTOCOL_VERSION}`,
|
|
248
|
-
},
|
|
249
|
-
body: JSON.stringify({ refresh_token: session.refresh_token }),
|
|
250
|
-
});
|
|
251
|
-
if (res.status === 401) {
|
|
252
|
-
const body = (await res.json().catch(() => ({})));
|
|
253
|
-
if (body.error === "refresh_reuse_detected") {
|
|
254
|
-
throw new RefreshReuseDetectedError(body.hint ??
|
|
255
|
-
"refresh token reuse detected; keychain may be compromised. Run `acc-runner login` from a trusted machine.");
|
|
256
|
-
}
|
|
257
|
-
throw new Error(body.hint ?? "refresh token expired; run `acc-runner login`");
|
|
258
|
-
}
|
|
259
|
-
if (!res.ok) {
|
|
260
|
-
throw new Error(`refresh failed: ${res.status} ${await res.text()}`);
|
|
261
|
-
}
|
|
262
|
-
const body = (await res.json());
|
|
263
|
-
return {
|
|
264
|
-
...session,
|
|
265
|
-
access_token: body.access_token,
|
|
266
|
-
access_expires_at: new Date(body.expires_at * 1000).toISOString(),
|
|
267
|
-
// v0.4-E: server rotates on every refresh. Persist the new refresh token
|
|
268
|
-
// so the next call doesn't replay the old one (which would now look like
|
|
269
|
-
// reuse and trip the chain-revocation path).
|
|
270
|
-
refresh_token: body.refresh_token ?? session.refresh_token,
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
167
|
// Centralised handler for the reuse-detected case. Loud red banner so the
|
|
274
168
|
// operator notices immediately, then exit(1) — there's no safe recovery
|
|
275
169
|
// from the watch loop, the keychain has to be reset by `acc-runner login`.
|