@cmmd-center/forge 0.9.4 → 0.9.5
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/bin.cjs +174 -32
- package/dist/bin.mjs +174 -32
- package/dist/runtimeBootstrap.cjs +1 -1
- package/dist/runtimeBootstrap.mjs +1 -1
- package/dist/{runtimeControlAuthority-Dm9z5ziU.mjs → runtimeControlAuthority-L85w5RTn.mjs} +33 -3
- package/dist/{runtimeControlAuthority-Dx6Y5LPI.cjs → runtimeControlAuthority-SNBF5Mjy.cjs} +33 -3
- package/package.json +1 -1
package/dist/bin.cjs
CHANGED
|
@@ -12,7 +12,7 @@ const require_Cache = require("./Cache-XSj5g7uG.cjs");
|
|
|
12
12
|
const require_SqlError = require("./SqlError-C2mi8_tp.cjs");
|
|
13
13
|
const require_esm = require("./esm-Do7HVhLA.cjs");
|
|
14
14
|
const require_Mime = require("./Mime-CyHbecis.cjs");
|
|
15
|
-
const require_runtimeControlAuthority = require("./runtimeControlAuthority-
|
|
15
|
+
const require_runtimeControlAuthority = require("./runtimeControlAuthority-SNBF5Mjy.cjs");
|
|
16
16
|
const require_PTY = require("./PTY-U3JQnLXS.cjs");
|
|
17
17
|
let node_child_process = require("node:child_process");
|
|
18
18
|
node_child_process = require_chunk.__toESM(node_child_process);
|
|
@@ -67803,7 +67803,7 @@ function normalizeNumberish(value) {
|
|
|
67803
67803
|
}
|
|
67804
67804
|
//#endregion
|
|
67805
67805
|
//#region package.json
|
|
67806
|
-
var version$1 = "0.9.
|
|
67806
|
+
var version$1 = "0.9.5";
|
|
67807
67807
|
//#endregion
|
|
67808
67808
|
//#region src/sentry.ts
|
|
67809
67809
|
const SERVER_APP_NAME = "forge-server";
|
|
@@ -99120,14 +99120,9 @@ function mapClaudeRateLimitInfo(info) {
|
|
|
99120
99120
|
}
|
|
99121
99121
|
const cachedSnapshots = /* @__PURE__ */ new Map();
|
|
99122
99122
|
let localClaudeGenerationHash = null;
|
|
99123
|
-
let localClaudeGenerationId = null;
|
|
99124
99123
|
function setLocalClaudeUsageGeneration(generationId) {
|
|
99125
|
-
localClaudeGenerationId = generationId;
|
|
99126
99124
|
localClaudeGenerationHash = (0, node_crypto.createHash)("sha256").update(generationId).digest("hex");
|
|
99127
99125
|
}
|
|
99128
|
-
function currentClaudeUsageGenerationId() {
|
|
99129
|
-
return localClaudeGenerationId;
|
|
99130
|
-
}
|
|
99131
99126
|
function currentClaudeUsageGenerationHash() {
|
|
99132
99127
|
const value = process.env.FORGE_PERSONAL_CLAUDE_GENERATION_HASH?.trim() ?? "";
|
|
99133
99128
|
return /^[a-f0-9]{64}$/.test(value) ? value : localClaudeGenerationHash;
|
|
@@ -99547,6 +99542,16 @@ function extractClaudeProviderAccountId(response) {
|
|
|
99547
99542
|
const providerAccountId = account.providerAccountId.trim();
|
|
99548
99543
|
return providerAccountId.length > 0 ? providerAccountId : null;
|
|
99549
99544
|
}
|
|
99545
|
+
/**
|
|
99546
|
+
* Logs a usage-publish failure, or does nothing when there was none. The null
|
|
99547
|
+
* check lives here rather than in syncClaudeCredentialForSession so that
|
|
99548
|
+
* reporting the failure costs that generator no branches.
|
|
99549
|
+
*/
|
|
99550
|
+
const logUsagePublishFailure = (failure, path) => failure === null ? require_Schema$1.void_ : require_Schema$1.logWarning("cmmd-claude-cred-sync: usage publish failed after deposit", {
|
|
99551
|
+
error: failure.cause instanceof Error ? failure.cause.message : String(failure.cause),
|
|
99552
|
+
stack: failure.cause instanceof Error ? failure.cause.stack : void 0,
|
|
99553
|
+
path
|
|
99554
|
+
});
|
|
99550
99555
|
async function publishClaudeUsageAfterSync(input) {
|
|
99551
99556
|
try {
|
|
99552
99557
|
const usage = await input.loadUsage({
|
|
@@ -99569,13 +99574,16 @@ async function publishClaudeUsageAfterSync(input) {
|
|
|
99569
99574
|
observedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
99570
99575
|
}
|
|
99571
99576
|
});
|
|
99572
|
-
|
|
99577
|
+
return null;
|
|
99578
|
+
} catch (cause) {
|
|
99579
|
+
return { cause };
|
|
99580
|
+
}
|
|
99573
99581
|
}
|
|
99574
99582
|
async function finalizeClaudeCredentialSync(input) {
|
|
99575
99583
|
const providerAccountId = extractClaudeProviderAccountId(input.response);
|
|
99576
|
-
if (providerAccountId === null) return;
|
|
99584
|
+
if (providerAccountId === null) return null;
|
|
99577
99585
|
if (input.auth.path === USER_CLAUDE_CREDENTIAL_PATH) setLocalClaudeUsageGeneration(providerAccountId);
|
|
99578
|
-
|
|
99586
|
+
return publishClaudeUsageAfterSync({
|
|
99579
99587
|
auth: input.auth,
|
|
99580
99588
|
providerAccountId,
|
|
99581
99589
|
accountEmail: input.accountEmail,
|
|
@@ -99630,7 +99638,7 @@ function syncClaudeCredentialForSession(input) {
|
|
|
99630
99638
|
}
|
|
99631
99639
|
})).pipe(require_Schema$1.orElseSucceed(() => null));
|
|
99632
99640
|
if (response === null) return { status: "failed" };
|
|
99633
|
-
yield* require_Schema$1.promise(() => finalizeClaudeCredentialSync({
|
|
99641
|
+
yield* logUsagePublishFailure(yield* require_Schema$1.promise(() => finalizeClaudeCredentialSync({
|
|
99634
99642
|
response,
|
|
99635
99643
|
auth,
|
|
99636
99644
|
accountEmail,
|
|
@@ -99638,7 +99646,7 @@ function syncClaudeCredentialForSession(input) {
|
|
|
99638
99646
|
planType: metadata.planType ?? probedMetadata.planType ?? null,
|
|
99639
99647
|
...input.homePath ? { homePath: input.homePath } : {},
|
|
99640
99648
|
loadUsage: input.loadUsage ?? fetchClaudeUsageViaOfficialClient
|
|
99641
|
-
}));
|
|
99649
|
+
})), auth.path);
|
|
99642
99650
|
return { status: "synced" };
|
|
99643
99651
|
});
|
|
99644
99652
|
}
|
|
@@ -289919,6 +289927,95 @@ const makeRuntimeServerSecretStore = require_Schema$1.gen(function* () {
|
|
|
289919
289927
|
return (yield* ServerConfig).forgeCloudShellMode === true ? yield* makePostgresServerSecretStore : yield* makeLocalServerSecretStore;
|
|
289920
289928
|
});
|
|
289921
289929
|
const ServerSecretStoreRuntimeLive = require_Schema$1.effect(ServerSecretStore, makeRuntimeServerSecretStore);
|
|
289930
|
+
//#endregion
|
|
289931
|
+
//#region src/runtimeAuth/controlAuthorityRetry.ts
|
|
289932
|
+
/**
|
|
289933
|
+
* When to try for control authority again.
|
|
289934
|
+
*
|
|
289935
|
+
* CMMD ships continuously, so a Forge Cloud runtime will outlive restarts of its
|
|
289936
|
+
* own control plane. Obtaining authority is therefore not a startup-time
|
|
289937
|
+
* question: a runtime that asks once, gets an infrastructure page mid-deploy and
|
|
289938
|
+
* gives up stays unauthenticated until something happens to restart it while
|
|
289939
|
+
* CMMD is healthy. Measured: hours, on a Sprite whose guest was serving fine the
|
|
289940
|
+
* whole time.
|
|
289941
|
+
*
|
|
289942
|
+
* Two shapes of failure arrive here and they need opposite answers, which is why
|
|
289943
|
+
* `retryable` is decided at the boundary that saw the response rather than
|
|
289944
|
+
* guessed at here.
|
|
289945
|
+
*/
|
|
289946
|
+
/** First backoff. Short on purpose: a deploy cutover is seconds, not minutes. */
|
|
289947
|
+
const BASE_DELAY_MS = 1e3;
|
|
289948
|
+
/**
|
|
289949
|
+
* Ceiling. Unbounded doubling turns a long outage into a long TAIL: an hour down
|
|
289950
|
+
* would be followed by up to an hour of avoidable downtime after recovery.
|
|
289951
|
+
*/
|
|
289952
|
+
const MAX_DELAY_MS = 6e4;
|
|
289953
|
+
/**
|
|
289954
|
+
* Spread, as a fraction of the delay. A fleet loses authority at the same
|
|
289955
|
+
* instant when the control plane restarts, so without this every runtime
|
|
289956
|
+
* retries in lockstep and the recovering CMMD takes the whole fleet at once.
|
|
289957
|
+
*
|
|
289958
|
+
* Kept below 1 so jitter can only ADD: a later attempt must never be scheduled
|
|
289959
|
+
* sooner than an earlier one, or backoff stops meaning anything.
|
|
289960
|
+
*/
|
|
289961
|
+
const JITTER_FRACTION = .25;
|
|
289962
|
+
function nextControlAuthorityAttempt(input) {
|
|
289963
|
+
if (!input.retryable) return {
|
|
289964
|
+
retry: false,
|
|
289965
|
+
delayMs: 0
|
|
289966
|
+
};
|
|
289967
|
+
const exponent = Math.max(0, input.consecutiveFailures - 1);
|
|
289968
|
+
const cap = MAX_DELAY_MS / (1 + JITTER_FRACTION);
|
|
289969
|
+
const base = Math.min(BASE_DELAY_MS * 2 ** exponent, cap);
|
|
289970
|
+
const random = input.random ?? Math.random;
|
|
289971
|
+
return {
|
|
289972
|
+
retry: true,
|
|
289973
|
+
delayMs: Math.round(base * (1 + JITTER_FRACTION * random()))
|
|
289974
|
+
};
|
|
289975
|
+
}
|
|
289976
|
+
/** Bounded retry budget. The next connect starts a fresh one. */
|
|
289977
|
+
const DEFAULT_MAX_ATTEMPTS = 8;
|
|
289978
|
+
/**
|
|
289979
|
+
* Keep asking for control authority while asking again could plausibly work.
|
|
289980
|
+
*
|
|
289981
|
+
* Bounded rather than infinite: a loop that gives up and says so lets the caller
|
|
289982
|
+
* log something actionable, and a connect or a heartbeat will start a fresh
|
|
289983
|
+
* budget anyway. An unbounded loop would hide the condition in exactly the way
|
|
289984
|
+
* the original `.catch(() => false)` did.
|
|
289985
|
+
*/
|
|
289986
|
+
async function bootstrapWithRetry(input) {
|
|
289987
|
+
const maxAttempts = input.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
|
|
289988
|
+
let lastError;
|
|
289989
|
+
for (let attempts = 1; attempts <= maxAttempts; attempts += 1) {
|
|
289990
|
+
let retryable = true;
|
|
289991
|
+
try {
|
|
289992
|
+
if (await input.attempt()) return {
|
|
289993
|
+
ok: true,
|
|
289994
|
+
attempts
|
|
289995
|
+
};
|
|
289996
|
+
} catch (error) {
|
|
289997
|
+
lastError = error;
|
|
289998
|
+
retryable = typeof error === "object" && error !== null && "retryable" in error && error.retryable === true;
|
|
289999
|
+
}
|
|
290000
|
+
if (!retryable) return {
|
|
290001
|
+
ok: false,
|
|
290002
|
+
attempts,
|
|
290003
|
+
lastError
|
|
290004
|
+
};
|
|
290005
|
+
if (attempts === maxAttempts) break;
|
|
290006
|
+
const next = nextControlAuthorityAttempt({
|
|
290007
|
+
consecutiveFailures: attempts,
|
|
290008
|
+
retryable: true,
|
|
290009
|
+
...input.random ? { random: input.random } : {}
|
|
290010
|
+
});
|
|
290011
|
+
await input.sleep(next.delayMs);
|
|
290012
|
+
}
|
|
290013
|
+
return {
|
|
290014
|
+
ok: false,
|
|
290015
|
+
attempts: maxAttempts,
|
|
290016
|
+
lastError
|
|
290017
|
+
};
|
|
290018
|
+
}
|
|
289922
290019
|
var AuthControlPlaneError = class extends require_Schema$1.TaggedError("AuthControlPlaneError") {};
|
|
289923
290020
|
var AuthControlPlane = class extends require_Schema$1.Service()("forge/AuthControlPlane") {};
|
|
289924
290021
|
//#endregion
|
|
@@ -291118,19 +291215,24 @@ async function bootstrapControlAuthorityFromConnect(input) {
|
|
|
291118
291215
|
const credentialEpoch = input.verified.credentialEpoch;
|
|
291119
291216
|
const hasConnectScope = input.verified.scope.includes("http") || input.verified.scope.includes("ws");
|
|
291120
291217
|
if (!controlPlaneUrl || !provisioningGrant || machineId === null || credentialEpoch === null || !hasConnectScope) return;
|
|
291121
|
-
await
|
|
291122
|
-
|
|
291123
|
-
|
|
291124
|
-
|
|
291125
|
-
|
|
291126
|
-
|
|
291127
|
-
|
|
291128
|
-
|
|
291129
|
-
|
|
291130
|
-
|
|
291131
|
-
|
|
291132
|
-
|
|
291133
|
-
|
|
291218
|
+
await bootstrapWithRetry({
|
|
291219
|
+
attempt: () => require_runtimeControlAuthority.bootstrapRuntimeControlAuthority({
|
|
291220
|
+
identity: {
|
|
291221
|
+
controlPlaneUrl,
|
|
291222
|
+
publicKeyPem: input.runtimeAuthConfig.publicKeyPem,
|
|
291223
|
+
orgId: input.verified.orgId,
|
|
291224
|
+
userId: input.verified.userId,
|
|
291225
|
+
projectId: input.verified.projectId,
|
|
291226
|
+
runtimeId: input.verified.runtimeId,
|
|
291227
|
+
machineId,
|
|
291228
|
+
credentialEpoch
|
|
291229
|
+
},
|
|
291230
|
+
connectToken: input.token,
|
|
291231
|
+
provisioningGrant
|
|
291232
|
+
}),
|
|
291233
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
291234
|
+
}).then((result) => {
|
|
291235
|
+
if (!result.ok && result.lastError !== void 0) throw result.lastError;
|
|
291134
291236
|
}).catch((error) => {
|
|
291135
291237
|
console.error("[bootstrapControlAuthorityFromConnect] failed to establish runtime control authority; every heartbeat will report null until the next successful connect", {
|
|
291136
291238
|
runtimeId: input.verified.runtimeId,
|
|
@@ -296504,6 +296606,32 @@ const readPersistedServerRuntimeState = (path) => require_Schema$1.gen(function*
|
|
|
296504
296606
|
return yield* decodePersistedServerRuntimeState(trimmed).pipe(require_Schema$1.option);
|
|
296505
296607
|
});
|
|
296506
296608
|
//#endregion
|
|
296609
|
+
//#region src/orchestration/turnAdmission.ts
|
|
296610
|
+
/**
|
|
296611
|
+
* Whether a turn can actually run, as opposed to merely being accepted.
|
|
296612
|
+
*
|
|
296613
|
+
* A Forge Cloud runtime reaches its provider through CMMD, so without control
|
|
296614
|
+
* authority it cannot start a turn at all. It could still ACCEPT one: the
|
|
296615
|
+
* command validates, the thread and worktree are created, the user message is
|
|
296616
|
+
* recorded, and the turn then sits at `pending` forever with nothing surfaced.
|
|
296617
|
+
*
|
|
296618
|
+
* Measured on forge-cmmd-r03ad897c5e90 while CMMD was redeploying: HTTP 200 on
|
|
296619
|
+
* dispatch, five user messages, zero assistant messages, every turn stuck with
|
|
296620
|
+
* `started_at` empty. The client had no way to know.
|
|
296621
|
+
*
|
|
296622
|
+
* Kept as a pure function because the interesting part is the RULE, not the
|
|
296623
|
+
* plumbing: it is only cloud runtimes, only turns, and only when authority is
|
|
296624
|
+
* genuinely absent.
|
|
296625
|
+
*/
|
|
296626
|
+
/** Commands that need a provider, and therefore need control authority. */
|
|
296627
|
+
const TURN_COMMANDS = new Set(["thread.turn.start"]);
|
|
296628
|
+
function refuseTurnWithoutControlAuthority(input) {
|
|
296629
|
+
if (!input.cmmdRuntimeMode) return null;
|
|
296630
|
+
if (!TURN_COMMANDS.has(input.commandType)) return null;
|
|
296631
|
+
if (input.hasControlAuthority) return null;
|
|
296632
|
+
return "This runtime has no CMMD control authority, so a turn cannot reach a provider and would queue without ever running. The runtime re-establishes authority on its own; retry shortly.";
|
|
296633
|
+
}
|
|
296634
|
+
//#endregion
|
|
296507
296635
|
//#region ../../packages/shared/src/forgeCommands.ts
|
|
296508
296636
|
/**
|
|
296509
296637
|
* Forge loop commands (`/forge-*`) — the one trigger surface shared by humans
|
|
@@ -297072,6 +297200,16 @@ const orchestrationDispatchRouteLayer = add$1("POST", "/api/orchestration/dispat
|
|
|
297072
297200
|
message: "Invalid orchestration command payload.",
|
|
297073
297201
|
cause
|
|
297074
297202
|
}))));
|
|
297203
|
+
const admissionRefusal = refuseTurnWithoutControlAuthority({
|
|
297204
|
+
commandType: normalizedCommand.type,
|
|
297205
|
+
cmmdRuntimeMode: process.env.FORGE_CMMD_RUNTIME_MODE === "true",
|
|
297206
|
+
hasControlAuthority: require_runtimeControlAuthority.readRuntimeControlAuthority({
|
|
297207
|
+
runtimeId: process.env.FORGE_CMMD_RUNTIME_ID ?? "",
|
|
297208
|
+
machineId: process.env.FORGE_CMMD_RUNTIME_MACHINE_ID ?? "",
|
|
297209
|
+
credentialEpoch: Number(process.env.FORGE_CMMD_RUNTIME_CREDENTIAL_EPOCH ?? "0")
|
|
297210
|
+
}) !== null
|
|
297211
|
+
});
|
|
297212
|
+
if (admissionRefusal !== null) return yield* new OrchestrationDispatchCommandError({ message: admissionRefusal });
|
|
297075
297213
|
const dispatchEffect = normalizedCommand.type === "thread.turn.start" && normalizedCommand.bootstrap ? require_Schema$1.gen(function* () {
|
|
297076
297214
|
const git = yield* require_Schema$1.serviceOption(GitCore);
|
|
297077
297215
|
const gitStatusBroadcaster = yield* require_Schema$1.serviceOption(GitStatusBroadcaster);
|
|
@@ -299394,7 +299532,7 @@ const cmmdSetProviderAccountDefaultRouteLayer = registerProxyRoute({
|
|
|
299394
299532
|
orgIdSource: "none",
|
|
299395
299533
|
responseShape: "unwrapData"
|
|
299396
299534
|
});
|
|
299397
|
-
const
|
|
299535
|
+
const resolveConnectedClaudeGeneration = (session) => require_Schema$1.gen(function* () {
|
|
299398
299536
|
const outcome = yield* runCmmdRestProxy(session, (jwt) => cmmdGateway.request({
|
|
299399
299537
|
method: "GET",
|
|
299400
299538
|
jwt,
|
|
@@ -299408,18 +299546,22 @@ const resolveConnectedClaudeGenerationHash = (session) => require_Schema$1.gen(f
|
|
|
299408
299546
|
};
|
|
299409
299547
|
return {
|
|
299410
299548
|
ok: true,
|
|
299411
|
-
value:
|
|
299549
|
+
value: {
|
|
299550
|
+
generationId: state.generationId,
|
|
299551
|
+
generationHash: (0, node_crypto.createHash)("sha256").update(state.generationId).digest("hex")
|
|
299552
|
+
}
|
|
299412
299553
|
};
|
|
299413
299554
|
});
|
|
299414
299555
|
/**
|
|
299415
299556
|
* The GET path is read-only. The explicit per-account POST refresh invokes
|
|
299416
299557
|
* this helper and propagates any CMMD forward failure to the caller.
|
|
299417
299558
|
*/
|
|
299418
|
-
const forwardClaudeUsageTelemetry = (session,
|
|
299419
|
-
const credentialOutcome = yield*
|
|
299559
|
+
const forwardClaudeUsageTelemetry = (session, requestedProviderAccountId, activelyPoll = false, fetchClaudeUsage = fetchClaudeUsageViaOfficialClient) => require_Schema$1.gen(function* () {
|
|
299560
|
+
const credentialOutcome = yield* resolveConnectedClaudeGeneration(session);
|
|
299420
299561
|
if (!credentialOutcome.ok) return credentialOutcome;
|
|
299421
|
-
|
|
299422
|
-
|
|
299562
|
+
if (credentialOutcome.value === null) return null;
|
|
299563
|
+
const { generationId, generationHash } = credentialOutcome.value;
|
|
299564
|
+
const providerAccountId = requestedProviderAccountId ?? generationId;
|
|
299423
299565
|
const activeSnapshot = activelyPoll && providerAccountId === generationId ? yield* require_Schema$1.tryPromise({
|
|
299424
299566
|
try: () => fetchClaudeUsage({ binaryPath: resolveClaudeBinaryPath("claude") }),
|
|
299425
299567
|
catch: (cause) => new ClaudeUsageReadError({ cause })
|
|
@@ -299427,7 +299569,7 @@ const forwardClaudeUsageTelemetry = (session, providerAccountId = currentClaudeU
|
|
|
299427
299569
|
const snapshot = activeSnapshot ? {
|
|
299428
299570
|
...activeSnapshot,
|
|
299429
299571
|
observedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
299430
|
-
} : getLatestClaudeRateLimitTelemetry(
|
|
299572
|
+
} : getLatestClaudeRateLimitTelemetry(generationHash, providerAccountId);
|
|
299431
299573
|
if (!snapshot) return null;
|
|
299432
299574
|
return yield* runCmmdRestProxy(session, (jwt) => cmmdGateway.request({
|
|
299433
299575
|
method: "POST",
|
package/dist/bin.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import { a as make$48, i as keys, n as getOption, o as makeWith$1, r as invalida
|
|
|
11
11
|
import { S as layer$9, _ as defaultEscape, a as DeadlockError, b as makeCompiler$1, c as SqlError, d as UnknownError, g as custom, h as make$49, i as ConstraintError, l as SqlSyntaxError, m as SqlClient, n as AuthorizationError, o as LockTimeoutError, r as ConnectionError, s as SerializationError, t as AuthenticationError, u as StatementTimeoutError, v as defaultTransforms, y as fragment } from "./SqlError-1c-3v7GY.mjs";
|
|
12
12
|
import { a as diag, c as SpanKind$1, d as TraceFlags, f as ROOT_CONTEXT, i as propagation, l as isSpanContextValid, m as DiagLogLevel, n as init_esm$1, o as context$1, p as createContextKey, r as trace, s as SpanStatusCode, t as esm_exports$1 } from "./esm-Ce1GqbVk.mjs";
|
|
13
13
|
import { t as Mime_default } from "./Mime-BITO-E1U.mjs";
|
|
14
|
-
import { a as verifyCmmdAuthRuntimeTicket, c as verifyCmmdRuntimeTicketRefreshProof, d as createReadOnceFileTokenAccessor, i as sendRuntimeControlHeartbeat, l as runtimeGrantToken, n as bootstrapRuntimeControlAuthorityFromNarrowGrant, o as verifyCmmdClaudeReadinessTicket, r as readRuntimeControlAuthority, s as verifyCmmdRuntimeTicket, t as bootstrapRuntimeControlAuthority, u as setRuntimeGrantToken } from "./runtimeControlAuthority-
|
|
14
|
+
import { a as verifyCmmdAuthRuntimeTicket, c as verifyCmmdRuntimeTicketRefreshProof, d as createReadOnceFileTokenAccessor, i as sendRuntimeControlHeartbeat, l as runtimeGrantToken, n as bootstrapRuntimeControlAuthorityFromNarrowGrant, o as verifyCmmdClaudeReadinessTicket, r as readRuntimeControlAuthority, s as verifyCmmdRuntimeTicket, t as bootstrapRuntimeControlAuthority, u as setRuntimeGrantToken } from "./runtimeControlAuthority-L85w5RTn.mjs";
|
|
15
15
|
import { n as PtySpawnError, t as PtyAdapter } from "./PTY-CBkE5akF.mjs";
|
|
16
16
|
import { createRequire } from "node:module";
|
|
17
17
|
import * as HE from "node:child_process";
|
|
@@ -67570,7 +67570,7 @@ function normalizeNumberish(value) {
|
|
|
67570
67570
|
}
|
|
67571
67571
|
//#endregion
|
|
67572
67572
|
//#region package.json
|
|
67573
|
-
var version$1 = "0.9.
|
|
67573
|
+
var version$1 = "0.9.5";
|
|
67574
67574
|
//#endregion
|
|
67575
67575
|
//#region src/sentry.ts
|
|
67576
67576
|
const SERVER_APP_NAME = "forge-server";
|
|
@@ -98815,14 +98815,9 @@ function mapClaudeRateLimitInfo(info) {
|
|
|
98815
98815
|
}
|
|
98816
98816
|
const cachedSnapshots = /* @__PURE__ */ new Map();
|
|
98817
98817
|
let localClaudeGenerationHash = null;
|
|
98818
|
-
let localClaudeGenerationId = null;
|
|
98819
98818
|
function setLocalClaudeUsageGeneration(generationId) {
|
|
98820
|
-
localClaudeGenerationId = generationId;
|
|
98821
98819
|
localClaudeGenerationHash = createHash("sha256").update(generationId).digest("hex");
|
|
98822
98820
|
}
|
|
98823
|
-
function currentClaudeUsageGenerationId() {
|
|
98824
|
-
return localClaudeGenerationId;
|
|
98825
|
-
}
|
|
98826
98821
|
function currentClaudeUsageGenerationHash() {
|
|
98827
98822
|
const value = process.env.FORGE_PERSONAL_CLAUDE_GENERATION_HASH?.trim() ?? "";
|
|
98828
98823
|
return /^[a-f0-9]{64}$/.test(value) ? value : localClaudeGenerationHash;
|
|
@@ -99242,6 +99237,16 @@ function extractClaudeProviderAccountId(response) {
|
|
|
99242
99237
|
const providerAccountId = account.providerAccountId.trim();
|
|
99243
99238
|
return providerAccountId.length > 0 ? providerAccountId : null;
|
|
99244
99239
|
}
|
|
99240
|
+
/**
|
|
99241
|
+
* Logs a usage-publish failure, or does nothing when there was none. The null
|
|
99242
|
+
* check lives here rather than in syncClaudeCredentialForSession so that
|
|
99243
|
+
* reporting the failure costs that generator no branches.
|
|
99244
|
+
*/
|
|
99245
|
+
const logUsagePublishFailure = (failure, path) => failure === null ? void_$1 : logWarning$1("cmmd-claude-cred-sync: usage publish failed after deposit", {
|
|
99246
|
+
error: failure.cause instanceof Error ? failure.cause.message : String(failure.cause),
|
|
99247
|
+
stack: failure.cause instanceof Error ? failure.cause.stack : void 0,
|
|
99248
|
+
path
|
|
99249
|
+
});
|
|
99245
99250
|
async function publishClaudeUsageAfterSync(input) {
|
|
99246
99251
|
try {
|
|
99247
99252
|
const usage = await input.loadUsage({
|
|
@@ -99264,13 +99269,16 @@ async function publishClaudeUsageAfterSync(input) {
|
|
|
99264
99269
|
observedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
99265
99270
|
}
|
|
99266
99271
|
});
|
|
99267
|
-
|
|
99272
|
+
return null;
|
|
99273
|
+
} catch (cause) {
|
|
99274
|
+
return { cause };
|
|
99275
|
+
}
|
|
99268
99276
|
}
|
|
99269
99277
|
async function finalizeClaudeCredentialSync(input) {
|
|
99270
99278
|
const providerAccountId = extractClaudeProviderAccountId(input.response);
|
|
99271
|
-
if (providerAccountId === null) return;
|
|
99279
|
+
if (providerAccountId === null) return null;
|
|
99272
99280
|
if (input.auth.path === USER_CLAUDE_CREDENTIAL_PATH) setLocalClaudeUsageGeneration(providerAccountId);
|
|
99273
|
-
|
|
99281
|
+
return publishClaudeUsageAfterSync({
|
|
99274
99282
|
auth: input.auth,
|
|
99275
99283
|
providerAccountId,
|
|
99276
99284
|
accountEmail: input.accountEmail,
|
|
@@ -99325,7 +99333,7 @@ function syncClaudeCredentialForSession(input) {
|
|
|
99325
99333
|
}
|
|
99326
99334
|
})).pipe(orElseSucceed(() => null));
|
|
99327
99335
|
if (response === null) return { status: "failed" };
|
|
99328
|
-
yield* promise(() => finalizeClaudeCredentialSync({
|
|
99336
|
+
yield* logUsagePublishFailure(yield* promise(() => finalizeClaudeCredentialSync({
|
|
99329
99337
|
response,
|
|
99330
99338
|
auth,
|
|
99331
99339
|
accountEmail,
|
|
@@ -99333,7 +99341,7 @@ function syncClaudeCredentialForSession(input) {
|
|
|
99333
99341
|
planType: metadata.planType ?? probedMetadata.planType ?? null,
|
|
99334
99342
|
...input.homePath ? { homePath: input.homePath } : {},
|
|
99335
99343
|
loadUsage: input.loadUsage ?? fetchClaudeUsageViaOfficialClient
|
|
99336
|
-
}));
|
|
99344
|
+
})), auth.path);
|
|
99337
99345
|
return { status: "synced" };
|
|
99338
99346
|
});
|
|
99339
99347
|
}
|
|
@@ -289444,6 +289452,95 @@ effect(ServerSecretStore, makeLocalServerSecretStore);
|
|
|
289444
289452
|
const ServerSecretStoreRuntimeLive = effect(ServerSecretStore, gen(function* () {
|
|
289445
289453
|
return (yield* ServerConfig).forgeCloudShellMode === true ? yield* makePostgresServerSecretStore : yield* makeLocalServerSecretStore;
|
|
289446
289454
|
}));
|
|
289455
|
+
//#endregion
|
|
289456
|
+
//#region src/runtimeAuth/controlAuthorityRetry.ts
|
|
289457
|
+
/**
|
|
289458
|
+
* When to try for control authority again.
|
|
289459
|
+
*
|
|
289460
|
+
* CMMD ships continuously, so a Forge Cloud runtime will outlive restarts of its
|
|
289461
|
+
* own control plane. Obtaining authority is therefore not a startup-time
|
|
289462
|
+
* question: a runtime that asks once, gets an infrastructure page mid-deploy and
|
|
289463
|
+
* gives up stays unauthenticated until something happens to restart it while
|
|
289464
|
+
* CMMD is healthy. Measured: hours, on a Sprite whose guest was serving fine the
|
|
289465
|
+
* whole time.
|
|
289466
|
+
*
|
|
289467
|
+
* Two shapes of failure arrive here and they need opposite answers, which is why
|
|
289468
|
+
* `retryable` is decided at the boundary that saw the response rather than
|
|
289469
|
+
* guessed at here.
|
|
289470
|
+
*/
|
|
289471
|
+
/** First backoff. Short on purpose: a deploy cutover is seconds, not minutes. */
|
|
289472
|
+
const BASE_DELAY_MS = 1e3;
|
|
289473
|
+
/**
|
|
289474
|
+
* Ceiling. Unbounded doubling turns a long outage into a long TAIL: an hour down
|
|
289475
|
+
* would be followed by up to an hour of avoidable downtime after recovery.
|
|
289476
|
+
*/
|
|
289477
|
+
const MAX_DELAY_MS = 6e4;
|
|
289478
|
+
/**
|
|
289479
|
+
* Spread, as a fraction of the delay. A fleet loses authority at the same
|
|
289480
|
+
* instant when the control plane restarts, so without this every runtime
|
|
289481
|
+
* retries in lockstep and the recovering CMMD takes the whole fleet at once.
|
|
289482
|
+
*
|
|
289483
|
+
* Kept below 1 so jitter can only ADD: a later attempt must never be scheduled
|
|
289484
|
+
* sooner than an earlier one, or backoff stops meaning anything.
|
|
289485
|
+
*/
|
|
289486
|
+
const JITTER_FRACTION = .25;
|
|
289487
|
+
function nextControlAuthorityAttempt(input) {
|
|
289488
|
+
if (!input.retryable) return {
|
|
289489
|
+
retry: false,
|
|
289490
|
+
delayMs: 0
|
|
289491
|
+
};
|
|
289492
|
+
const exponent = Math.max(0, input.consecutiveFailures - 1);
|
|
289493
|
+
const cap = MAX_DELAY_MS / (1 + JITTER_FRACTION);
|
|
289494
|
+
const base = Math.min(BASE_DELAY_MS * 2 ** exponent, cap);
|
|
289495
|
+
const random = input.random ?? Math.random;
|
|
289496
|
+
return {
|
|
289497
|
+
retry: true,
|
|
289498
|
+
delayMs: Math.round(base * (1 + JITTER_FRACTION * random()))
|
|
289499
|
+
};
|
|
289500
|
+
}
|
|
289501
|
+
/** Bounded retry budget. The next connect starts a fresh one. */
|
|
289502
|
+
const DEFAULT_MAX_ATTEMPTS = 8;
|
|
289503
|
+
/**
|
|
289504
|
+
* Keep asking for control authority while asking again could plausibly work.
|
|
289505
|
+
*
|
|
289506
|
+
* Bounded rather than infinite: a loop that gives up and says so lets the caller
|
|
289507
|
+
* log something actionable, and a connect or a heartbeat will start a fresh
|
|
289508
|
+
* budget anyway. An unbounded loop would hide the condition in exactly the way
|
|
289509
|
+
* the original `.catch(() => false)` did.
|
|
289510
|
+
*/
|
|
289511
|
+
async function bootstrapWithRetry(input) {
|
|
289512
|
+
const maxAttempts = input.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
|
|
289513
|
+
let lastError;
|
|
289514
|
+
for (let attempts = 1; attempts <= maxAttempts; attempts += 1) {
|
|
289515
|
+
let retryable = true;
|
|
289516
|
+
try {
|
|
289517
|
+
if (await input.attempt()) return {
|
|
289518
|
+
ok: true,
|
|
289519
|
+
attempts
|
|
289520
|
+
};
|
|
289521
|
+
} catch (error) {
|
|
289522
|
+
lastError = error;
|
|
289523
|
+
retryable = typeof error === "object" && error !== null && "retryable" in error && error.retryable === true;
|
|
289524
|
+
}
|
|
289525
|
+
if (!retryable) return {
|
|
289526
|
+
ok: false,
|
|
289527
|
+
attempts,
|
|
289528
|
+
lastError
|
|
289529
|
+
};
|
|
289530
|
+
if (attempts === maxAttempts) break;
|
|
289531
|
+
const next = nextControlAuthorityAttempt({
|
|
289532
|
+
consecutiveFailures: attempts,
|
|
289533
|
+
retryable: true,
|
|
289534
|
+
...input.random ? { random: input.random } : {}
|
|
289535
|
+
});
|
|
289536
|
+
await input.sleep(next.delayMs);
|
|
289537
|
+
}
|
|
289538
|
+
return {
|
|
289539
|
+
ok: false,
|
|
289540
|
+
attempts: maxAttempts,
|
|
289541
|
+
lastError
|
|
289542
|
+
};
|
|
289543
|
+
}
|
|
289447
289544
|
var AuthControlPlaneError = class extends TaggedError("AuthControlPlaneError") {};
|
|
289448
289545
|
var AuthControlPlane = class extends Service$1()("forge/AuthControlPlane") {};
|
|
289449
289546
|
//#endregion
|
|
@@ -290636,19 +290733,24 @@ async function bootstrapControlAuthorityFromConnect(input) {
|
|
|
290636
290733
|
const credentialEpoch = input.verified.credentialEpoch;
|
|
290637
290734
|
const hasConnectScope = input.verified.scope.includes("http") || input.verified.scope.includes("ws");
|
|
290638
290735
|
if (!controlPlaneUrl || !provisioningGrant || machineId === null || credentialEpoch === null || !hasConnectScope) return;
|
|
290639
|
-
await
|
|
290640
|
-
|
|
290641
|
-
|
|
290642
|
-
|
|
290643
|
-
|
|
290644
|
-
|
|
290645
|
-
|
|
290646
|
-
|
|
290647
|
-
|
|
290648
|
-
|
|
290649
|
-
|
|
290650
|
-
|
|
290651
|
-
|
|
290736
|
+
await bootstrapWithRetry({
|
|
290737
|
+
attempt: () => bootstrapRuntimeControlAuthority({
|
|
290738
|
+
identity: {
|
|
290739
|
+
controlPlaneUrl,
|
|
290740
|
+
publicKeyPem: input.runtimeAuthConfig.publicKeyPem,
|
|
290741
|
+
orgId: input.verified.orgId,
|
|
290742
|
+
userId: input.verified.userId,
|
|
290743
|
+
projectId: input.verified.projectId,
|
|
290744
|
+
runtimeId: input.verified.runtimeId,
|
|
290745
|
+
machineId,
|
|
290746
|
+
credentialEpoch
|
|
290747
|
+
},
|
|
290748
|
+
connectToken: input.token,
|
|
290749
|
+
provisioningGrant
|
|
290750
|
+
}),
|
|
290751
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
290752
|
+
}).then((result) => {
|
|
290753
|
+
if (!result.ok && result.lastError !== void 0) throw result.lastError;
|
|
290652
290754
|
}).catch((error) => {
|
|
290653
290755
|
console.error("[bootstrapControlAuthorityFromConnect] failed to establish runtime control authority; every heartbeat will report null until the next successful connect", {
|
|
290654
290756
|
runtimeId: input.verified.runtimeId,
|
|
@@ -295985,6 +296087,32 @@ const readPersistedServerRuntimeState = (path) => gen(function* () {
|
|
|
295985
296087
|
return yield* decodePersistedServerRuntimeState(trimmed).pipe(option);
|
|
295986
296088
|
});
|
|
295987
296089
|
//#endregion
|
|
296090
|
+
//#region src/orchestration/turnAdmission.ts
|
|
296091
|
+
/**
|
|
296092
|
+
* Whether a turn can actually run, as opposed to merely being accepted.
|
|
296093
|
+
*
|
|
296094
|
+
* A Forge Cloud runtime reaches its provider through CMMD, so without control
|
|
296095
|
+
* authority it cannot start a turn at all. It could still ACCEPT one: the
|
|
296096
|
+
* command validates, the thread and worktree are created, the user message is
|
|
296097
|
+
* recorded, and the turn then sits at `pending` forever with nothing surfaced.
|
|
296098
|
+
*
|
|
296099
|
+
* Measured on forge-cmmd-r03ad897c5e90 while CMMD was redeploying: HTTP 200 on
|
|
296100
|
+
* dispatch, five user messages, zero assistant messages, every turn stuck with
|
|
296101
|
+
* `started_at` empty. The client had no way to know.
|
|
296102
|
+
*
|
|
296103
|
+
* Kept as a pure function because the interesting part is the RULE, not the
|
|
296104
|
+
* plumbing: it is only cloud runtimes, only turns, and only when authority is
|
|
296105
|
+
* genuinely absent.
|
|
296106
|
+
*/
|
|
296107
|
+
/** Commands that need a provider, and therefore need control authority. */
|
|
296108
|
+
const TURN_COMMANDS = new Set(["thread.turn.start"]);
|
|
296109
|
+
function refuseTurnWithoutControlAuthority(input) {
|
|
296110
|
+
if (!input.cmmdRuntimeMode) return null;
|
|
296111
|
+
if (!TURN_COMMANDS.has(input.commandType)) return null;
|
|
296112
|
+
if (input.hasControlAuthority) return null;
|
|
296113
|
+
return "This runtime has no CMMD control authority, so a turn cannot reach a provider and would queue without ever running. The runtime re-establishes authority on its own; retry shortly.";
|
|
296114
|
+
}
|
|
296115
|
+
//#endregion
|
|
295988
296116
|
//#region ../../packages/shared/src/forgeCommands.ts
|
|
295989
296117
|
/** Root of the repo-local skill contracts. */
|
|
295990
296118
|
const FORGE_SKILLS_DIR = ".agents/skills";
|
|
@@ -296539,6 +296667,16 @@ const orchestrationDispatchRouteLayer = add$1("POST", "/api/orchestration/dispat
|
|
|
296539
296667
|
message: "Invalid orchestration command payload.",
|
|
296540
296668
|
cause
|
|
296541
296669
|
}))));
|
|
296670
|
+
const admissionRefusal = refuseTurnWithoutControlAuthority({
|
|
296671
|
+
commandType: normalizedCommand.type,
|
|
296672
|
+
cmmdRuntimeMode: process.env.FORGE_CMMD_RUNTIME_MODE === "true",
|
|
296673
|
+
hasControlAuthority: readRuntimeControlAuthority({
|
|
296674
|
+
runtimeId: process.env.FORGE_CMMD_RUNTIME_ID ?? "",
|
|
296675
|
+
machineId: process.env.FORGE_CMMD_RUNTIME_MACHINE_ID ?? "",
|
|
296676
|
+
credentialEpoch: Number(process.env.FORGE_CMMD_RUNTIME_CREDENTIAL_EPOCH ?? "0")
|
|
296677
|
+
}) !== null
|
|
296678
|
+
});
|
|
296679
|
+
if (admissionRefusal !== null) return yield* new OrchestrationDispatchCommandError({ message: admissionRefusal });
|
|
296542
296680
|
const dispatchEffect = normalizedCommand.type === "thread.turn.start" && normalizedCommand.bootstrap ? gen(function* () {
|
|
296543
296681
|
const git = yield* serviceOption(GitCore);
|
|
296544
296682
|
const gitStatusBroadcaster = yield* serviceOption(GitStatusBroadcaster);
|
|
@@ -298851,7 +298989,7 @@ const cmmdSetProviderAccountDefaultRouteLayer = registerProxyRoute({
|
|
|
298851
298989
|
orgIdSource: "none",
|
|
298852
298990
|
responseShape: "unwrapData"
|
|
298853
298991
|
});
|
|
298854
|
-
const
|
|
298992
|
+
const resolveConnectedClaudeGeneration = (session) => gen(function* () {
|
|
298855
298993
|
const outcome = yield* runCmmdRestProxy(session, (jwt) => cmmdGateway.request({
|
|
298856
298994
|
method: "GET",
|
|
298857
298995
|
jwt,
|
|
@@ -298865,18 +299003,22 @@ const resolveConnectedClaudeGenerationHash = (session) => gen(function* () {
|
|
|
298865
299003
|
};
|
|
298866
299004
|
return {
|
|
298867
299005
|
ok: true,
|
|
298868
|
-
value:
|
|
299006
|
+
value: {
|
|
299007
|
+
generationId: state.generationId,
|
|
299008
|
+
generationHash: createHash("sha256").update(state.generationId).digest("hex")
|
|
299009
|
+
}
|
|
298869
299010
|
};
|
|
298870
299011
|
});
|
|
298871
299012
|
/**
|
|
298872
299013
|
* The GET path is read-only. The explicit per-account POST refresh invokes
|
|
298873
299014
|
* this helper and propagates any CMMD forward failure to the caller.
|
|
298874
299015
|
*/
|
|
298875
|
-
const forwardClaudeUsageTelemetry = (session,
|
|
298876
|
-
const credentialOutcome = yield*
|
|
299016
|
+
const forwardClaudeUsageTelemetry = (session, requestedProviderAccountId, activelyPoll = false, fetchClaudeUsage = fetchClaudeUsageViaOfficialClient) => gen(function* () {
|
|
299017
|
+
const credentialOutcome = yield* resolveConnectedClaudeGeneration(session);
|
|
298877
299018
|
if (!credentialOutcome.ok) return credentialOutcome;
|
|
298878
|
-
|
|
298879
|
-
|
|
299019
|
+
if (credentialOutcome.value === null) return null;
|
|
299020
|
+
const { generationId, generationHash } = credentialOutcome.value;
|
|
299021
|
+
const providerAccountId = requestedProviderAccountId ?? generationId;
|
|
298880
299022
|
const activeSnapshot = activelyPoll && providerAccountId === generationId ? yield* tryPromise({
|
|
298881
299023
|
try: () => fetchClaudeUsage({ binaryPath: resolveClaudeBinaryPath("claude") }),
|
|
298882
299024
|
catch: (cause) => new ClaudeUsageReadError({ cause })
|
|
@@ -298884,7 +299026,7 @@ const forwardClaudeUsageTelemetry = (session, providerAccountId = currentClaudeU
|
|
|
298884
299026
|
const snapshot = activeSnapshot ? {
|
|
298885
299027
|
...activeSnapshot,
|
|
298886
299028
|
observedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
298887
|
-
} : getLatestClaudeRateLimitTelemetry(
|
|
299029
|
+
} : getLatestClaudeRateLimitTelemetry(generationHash, providerAccountId);
|
|
298888
299030
|
if (!snapshot) return null;
|
|
298889
299031
|
return yield* runCmmdRestProxy(session, (jwt) => cmmdGateway.request({
|
|
298890
299032
|
method: "POST",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
4
4
|
require("./chunk-BhaIgcZK.cjs");
|
|
5
|
-
const require_runtimeControlAuthority = require("./runtimeControlAuthority-
|
|
5
|
+
const require_runtimeControlAuthority = require("./runtimeControlAuthority-SNBF5Mjy.cjs");
|
|
6
6
|
exports.bootstrapRuntimeControlAuthorityFromNarrowGrant = require_runtimeControlAuthority.bootstrapRuntimeControlAuthorityFromNarrowGrant;
|
|
7
7
|
exports.setRuntimeGrantToken = require_runtimeControlAuthority.setRuntimeGrantToken;
|
|
8
8
|
exports.verifyCmmdRuntimeTicket = require_runtimeControlAuthority.verifyCmmdRuntimeTicket;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { n as bootstrapRuntimeControlAuthorityFromNarrowGrant, s as verifyCmmdRuntimeTicket, u as setRuntimeGrantToken } from "./runtimeControlAuthority-
|
|
3
|
+
import { n as bootstrapRuntimeControlAuthorityFromNarrowGrant, s as verifyCmmdRuntimeTicket, u as setRuntimeGrantToken } from "./runtimeControlAuthority-L85w5RTn.mjs";
|
|
4
4
|
export { bootstrapRuntimeControlAuthorityFromNarrowGrant, setRuntimeGrantToken, verifyCmmdRuntimeTicket };
|
|
@@ -249,13 +249,40 @@ const CONTROL_AUTHORITY_SCOPES = new Set(["heartbeat", "replication"]);
|
|
|
249
249
|
var RuntimeControlAuthorityError = class extends Error {
|
|
250
250
|
status;
|
|
251
251
|
code;
|
|
252
|
+
/**
|
|
253
|
+
* Whether trying again could plausibly succeed.
|
|
254
|
+
*
|
|
255
|
+
* A restarting control plane and a wrong credential both fail here and need
|
|
256
|
+
* opposite responses: the first must be retried until the deploy finishes,
|
|
257
|
+
* the second must not be retried at all. Without this the caller cannot tell
|
|
258
|
+
* them apart and picks wrong in one direction or the other.
|
|
259
|
+
*/
|
|
260
|
+
retryable;
|
|
252
261
|
constructor(input) {
|
|
253
262
|
super(input.message);
|
|
254
263
|
this.name = "RuntimeControlAuthorityError";
|
|
255
264
|
this.status = input.status ?? null;
|
|
256
265
|
this.code = input.code;
|
|
266
|
+
this.retryable = input.retryable ?? false;
|
|
257
267
|
}
|
|
258
268
|
};
|
|
269
|
+
/** Longest body prefix quoted into an error message. */
|
|
270
|
+
const CONTROL_AUTHORITY_BODY_PREVIEW = 180;
|
|
271
|
+
/**
|
|
272
|
+
* A short, credential-free description of a body that would not parse.
|
|
273
|
+
*
|
|
274
|
+
* The point is to identify the RESPONDER. A proxy error page says who it is in
|
|
275
|
+
* its first line, and that one line is the whole difference between "CMMD is
|
|
276
|
+
* redeploying" and a multi-hour dig through a guest.
|
|
277
|
+
*
|
|
278
|
+
* Redacted because this lands in logs and the responder is not always the one
|
|
279
|
+
* we think it is: anything resembling a JWT or a long opaque token is stripped
|
|
280
|
+
* rather than trusted to be harmless.
|
|
281
|
+
*/
|
|
282
|
+
function describeUnparsableBody(body) {
|
|
283
|
+
const collapsed = body.replace(/[A-Za-z0-9_-]{12,}\.[A-Za-z0-9_-]{12,}\.[A-Za-z0-9_-]{6,}/g, "<redacted-jwt>").replace(/[A-Za-z0-9_-]{40,}/g, "<redacted>").replace(/\s+/g, " ").trim();
|
|
284
|
+
return collapsed.length > CONTROL_AUTHORITY_BODY_PREVIEW ? `${collapsed.slice(0, CONTROL_AUTHORITY_BODY_PREVIEW)}…` : collapsed;
|
|
285
|
+
}
|
|
259
286
|
let authority = null;
|
|
260
287
|
let bootstrapInFlight = null;
|
|
261
288
|
function normalizedControlPlaneUrl(value) {
|
|
@@ -352,15 +379,18 @@ async function readControlAuthorityResponse(response) {
|
|
|
352
379
|
if (!response.ok) throw new RuntimeControlAuthorityError({
|
|
353
380
|
status: response.status,
|
|
354
381
|
code: "CMMD_RUNTIME_CONTROL_AUTHORITY_REJECTED",
|
|
382
|
+
retryable: response.status >= 500,
|
|
355
383
|
message: `CMMD runtime control authority request failed (${response.status}).`
|
|
356
384
|
});
|
|
385
|
+
const raw = await response.text().catch(() => "");
|
|
357
386
|
try {
|
|
358
|
-
return
|
|
387
|
+
return JSON.parse(raw);
|
|
359
388
|
} catch {
|
|
360
389
|
throw new RuntimeControlAuthorityError({
|
|
361
390
|
status: response.status,
|
|
362
391
|
code: "CMMD_RUNTIME_CONTROL_AUTHORITY_INVALID_RESPONSE",
|
|
363
|
-
|
|
392
|
+
retryable: true,
|
|
393
|
+
message: `CMMD runtime control authority returned ${response.status} with a non-JSON body (content-type: ${response.headers.get("content-type") ?? "none"}): ${describeUnparsableBody(raw) || "<empty>"}`
|
|
364
394
|
});
|
|
365
395
|
}
|
|
366
396
|
}
|
|
@@ -502,4 +532,4 @@ async function sendRuntimeControlHeartbeat(input) {
|
|
|
502
532
|
//#endregion
|
|
503
533
|
export { verifyCmmdAuthRuntimeTicket as a, verifyCmmdRuntimeTicketRefreshProof as c, createReadOnceFileTokenAccessor as d, sendRuntimeControlHeartbeat as i, runtimeGrantToken as l, bootstrapRuntimeControlAuthorityFromNarrowGrant as n, verifyCmmdClaudeReadinessTicket as o, readRuntimeControlAuthority as r, verifyCmmdRuntimeTicket as s, bootstrapRuntimeControlAuthority as t, setRuntimeGrantToken as u };
|
|
504
534
|
|
|
505
|
-
//# sourceMappingURL=runtimeControlAuthority-
|
|
535
|
+
//# sourceMappingURL=runtimeControlAuthority-L85w5RTn.mjs.map
|
|
@@ -250,13 +250,40 @@ const CONTROL_AUTHORITY_SCOPES = new Set(["heartbeat", "replication"]);
|
|
|
250
250
|
var RuntimeControlAuthorityError = class extends Error {
|
|
251
251
|
status;
|
|
252
252
|
code;
|
|
253
|
+
/**
|
|
254
|
+
* Whether trying again could plausibly succeed.
|
|
255
|
+
*
|
|
256
|
+
* A restarting control plane and a wrong credential both fail here and need
|
|
257
|
+
* opposite responses: the first must be retried until the deploy finishes,
|
|
258
|
+
* the second must not be retried at all. Without this the caller cannot tell
|
|
259
|
+
* them apart and picks wrong in one direction or the other.
|
|
260
|
+
*/
|
|
261
|
+
retryable;
|
|
253
262
|
constructor(input) {
|
|
254
263
|
super(input.message);
|
|
255
264
|
this.name = "RuntimeControlAuthorityError";
|
|
256
265
|
this.status = input.status ?? null;
|
|
257
266
|
this.code = input.code;
|
|
267
|
+
this.retryable = input.retryable ?? false;
|
|
258
268
|
}
|
|
259
269
|
};
|
|
270
|
+
/** Longest body prefix quoted into an error message. */
|
|
271
|
+
const CONTROL_AUTHORITY_BODY_PREVIEW = 180;
|
|
272
|
+
/**
|
|
273
|
+
* A short, credential-free description of a body that would not parse.
|
|
274
|
+
*
|
|
275
|
+
* The point is to identify the RESPONDER. A proxy error page says who it is in
|
|
276
|
+
* its first line, and that one line is the whole difference between "CMMD is
|
|
277
|
+
* redeploying" and a multi-hour dig through a guest.
|
|
278
|
+
*
|
|
279
|
+
* Redacted because this lands in logs and the responder is not always the one
|
|
280
|
+
* we think it is: anything resembling a JWT or a long opaque token is stripped
|
|
281
|
+
* rather than trusted to be harmless.
|
|
282
|
+
*/
|
|
283
|
+
function describeUnparsableBody(body) {
|
|
284
|
+
const collapsed = body.replace(/[A-Za-z0-9_-]{12,}\.[A-Za-z0-9_-]{12,}\.[A-Za-z0-9_-]{6,}/g, "<redacted-jwt>").replace(/[A-Za-z0-9_-]{40,}/g, "<redacted>").replace(/\s+/g, " ").trim();
|
|
285
|
+
return collapsed.length > CONTROL_AUTHORITY_BODY_PREVIEW ? `${collapsed.slice(0, CONTROL_AUTHORITY_BODY_PREVIEW)}…` : collapsed;
|
|
286
|
+
}
|
|
260
287
|
let authority = null;
|
|
261
288
|
let bootstrapInFlight = null;
|
|
262
289
|
function normalizedControlPlaneUrl(value) {
|
|
@@ -353,15 +380,18 @@ async function readControlAuthorityResponse(response) {
|
|
|
353
380
|
if (!response.ok) throw new RuntimeControlAuthorityError({
|
|
354
381
|
status: response.status,
|
|
355
382
|
code: "CMMD_RUNTIME_CONTROL_AUTHORITY_REJECTED",
|
|
383
|
+
retryable: response.status >= 500,
|
|
356
384
|
message: `CMMD runtime control authority request failed (${response.status}).`
|
|
357
385
|
});
|
|
386
|
+
const raw = await response.text().catch(() => "");
|
|
358
387
|
try {
|
|
359
|
-
return
|
|
388
|
+
return JSON.parse(raw);
|
|
360
389
|
} catch {
|
|
361
390
|
throw new RuntimeControlAuthorityError({
|
|
362
391
|
status: response.status,
|
|
363
392
|
code: "CMMD_RUNTIME_CONTROL_AUTHORITY_INVALID_RESPONSE",
|
|
364
|
-
|
|
393
|
+
retryable: true,
|
|
394
|
+
message: `CMMD runtime control authority returned ${response.status} with a non-JSON body (content-type: ${response.headers.get("content-type") ?? "none"}): ${describeUnparsableBody(raw) || "<empty>"}`
|
|
365
395
|
});
|
|
366
396
|
}
|
|
367
397
|
}
|
|
@@ -568,4 +598,4 @@ Object.defineProperty(exports, "verifyCmmdRuntimeTicketRefreshProof", {
|
|
|
568
598
|
}
|
|
569
599
|
});
|
|
570
600
|
|
|
571
|
-
//# sourceMappingURL=runtimeControlAuthority-
|
|
601
|
+
//# sourceMappingURL=runtimeControlAuthority-SNBF5Mjy.cjs.map
|