@cmmd-center/forge 0.10.14 → 0.11.0
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 +1114 -227
- package/dist/bin.mjs +1134 -247
- package/dist/client/assets/{DiffPanel-Bnkifxef.js → DiffPanel-B1yCvMgl.js} +3 -3
- package/dist/client/assets/{DiffPanel.logic-Bk4d9mRT.js → DiffPanel.logic-DS-ZTg-9.js} +2 -2
- package/dist/client/assets/DiffPanelShell-BFklbpRe.js +2531 -0
- package/dist/client/assets/{DiffWorkerPoolProvider-BO-u17g1.js → DiffWorkerPoolProvider-Q7qqna90.js} +8 -8
- package/dist/client/assets/{PullRequestCodePanel-Bbuxcu5g.js → PullRequestCodePanel-CO19fx8r.js} +3 -3
- package/dist/client/assets/{Virtualizer-BxZHsCOD.js → Virtualizer-nnC3ZYoF.js} +2 -2
- package/dist/client/assets/{index-BeMh8T3m.js → index-ChfmpOIo.js} +59 -59
- package/dist/client/index.html +4 -4
- package/dist/forge-build.json +1 -1
- package/dist/{open-FIgTDosf.mjs → open-D8mGyWmt.mjs} +3 -3
- package/package.json +1 -1
- package/dist/client/assets/DiffPanelShell-D_nSDbjF.js +0 -2531
package/dist/bin.cjs
CHANGED
|
@@ -16544,7 +16544,7 @@ function makeNetServiceShape(createServer = node_net.createServer) {
|
|
|
16544
16544
|
}
|
|
16545
16545
|
//#endregion
|
|
16546
16546
|
//#region ../../packages/shared/src/releaseInfo.generated.ts
|
|
16547
|
-
const FORGE_RELEASE_VERSION = "0.
|
|
16547
|
+
const FORGE_RELEASE_VERSION = "0.11.0";
|
|
16548
16548
|
//#endregion
|
|
16549
16549
|
//#region ../../packages/shared/src/hostProcess.ts
|
|
16550
16550
|
/**
|
|
@@ -40972,7 +40972,7 @@ function splitPath(filename) {
|
|
|
40972
40972
|
const parts = splitPathRe.exec(truncated);
|
|
40973
40973
|
return parts ? parts.slice(1) : [];
|
|
40974
40974
|
}
|
|
40975
|
-
function dirname$
|
|
40975
|
+
function dirname$7(path) {
|
|
40976
40976
|
const result = splitPath(path);
|
|
40977
40977
|
const root = result[0] || "";
|
|
40978
40978
|
let dir = result[1];
|
|
@@ -58287,7 +58287,7 @@ function getSpotlightConfig(optionsSpotlight) {
|
|
|
58287
58287
|
function normalizeWindowsPath(path) {
|
|
58288
58288
|
return path.replace(/^[A-Z]:/, "").replace(/\\/g, "/");
|
|
58289
58289
|
}
|
|
58290
|
-
function createGetModuleFromFilename(basePath = process.argv[1] ? dirname$
|
|
58290
|
+
function createGetModuleFromFilename(basePath = process.argv[1] ? dirname$7(process.argv[1]) : process.cwd(), isWindows = node_path.sep === "\\") {
|
|
58291
58291
|
const normalizedBase = isWindows ? normalizeWindowsPath(basePath) : basePath;
|
|
58292
58292
|
return (filename) => {
|
|
58293
58293
|
if (!filename) return;
|
|
@@ -65775,7 +65775,7 @@ function normalizeNumberish(value) {
|
|
|
65775
65775
|
}
|
|
65776
65776
|
//#endregion
|
|
65777
65777
|
//#region package.json
|
|
65778
|
-
var version$1 = "0.
|
|
65778
|
+
var version$1 = "0.11.0";
|
|
65779
65779
|
//#endregion
|
|
65780
65780
|
//#region src/sentry.ts
|
|
65781
65781
|
const SERVER_APP_NAME = "forge-server";
|
|
@@ -66248,7 +66248,7 @@ function readCmmdAvatarUrl(raw) {
|
|
|
66248
66248
|
function readTrimmedString$2(value) {
|
|
66249
66249
|
return typeof value === "string" ? value.trim() || null : null;
|
|
66250
66250
|
}
|
|
66251
|
-
function asRecord$
|
|
66251
|
+
function asRecord$14(value) {
|
|
66252
66252
|
return value && typeof value === "object" ? value : null;
|
|
66253
66253
|
}
|
|
66254
66254
|
function readDirectPlanLabelFromRecord(obj) {
|
|
@@ -66260,26 +66260,26 @@ function readNestedPlanLabelFromRecord(obj) {
|
|
|
66260
66260
|
function readCmmdPlanLabel(obj) {
|
|
66261
66261
|
const direct = readDirectPlanLabelFromRecord(obj);
|
|
66262
66262
|
if (direct) return direct;
|
|
66263
|
-
const plan = asRecord$
|
|
66263
|
+
const plan = asRecord$14(obj["plan"]);
|
|
66264
66264
|
if (plan) {
|
|
66265
66265
|
const label = readNestedPlanLabelFromRecord(plan);
|
|
66266
66266
|
if (label) return label;
|
|
66267
66267
|
}
|
|
66268
|
-
const subscription = asRecord$
|
|
66268
|
+
const subscription = asRecord$14(obj["subscription"]);
|
|
66269
66269
|
if (subscription) {
|
|
66270
66270
|
const label = readNestedPlanLabelFromRecord(subscription);
|
|
66271
66271
|
if (label) return label;
|
|
66272
|
-
const subscriptionPlan = asRecord$
|
|
66272
|
+
const subscriptionPlan = asRecord$14(subscription["plan"]);
|
|
66273
66273
|
if (subscriptionPlan) {
|
|
66274
66274
|
const nestedLabel = readNestedPlanLabelFromRecord(subscriptionPlan);
|
|
66275
66275
|
if (nestedLabel) return nestedLabel;
|
|
66276
66276
|
}
|
|
66277
66277
|
}
|
|
66278
|
-
const billing = asRecord$
|
|
66278
|
+
const billing = asRecord$14(obj["billing"]);
|
|
66279
66279
|
if (billing) {
|
|
66280
66280
|
const label = readNestedPlanLabelFromRecord(billing);
|
|
66281
66281
|
if (label) return label;
|
|
66282
|
-
const billingPlan = asRecord$
|
|
66282
|
+
const billingPlan = asRecord$14(billing["plan"]);
|
|
66283
66283
|
if (billingPlan) return readNestedPlanLabelFromRecord(billingPlan);
|
|
66284
66284
|
}
|
|
66285
66285
|
return null;
|
|
@@ -70094,22 +70094,22 @@ const EMPTY_PARSED_ERROR = {
|
|
|
70094
70094
|
message: null,
|
|
70095
70095
|
raw: null
|
|
70096
70096
|
};
|
|
70097
|
-
function asRecord$
|
|
70097
|
+
function asRecord$13(value) {
|
|
70098
70098
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
70099
70099
|
}
|
|
70100
70100
|
function readTrimmedString$1(value) {
|
|
70101
70101
|
return (typeof value === "string" ? value.trim() : "") || null;
|
|
70102
70102
|
}
|
|
70103
70103
|
function readErrorEnvelope(record) {
|
|
70104
|
-
return asRecord$
|
|
70104
|
+
return asRecord$13(record["error"]);
|
|
70105
70105
|
}
|
|
70106
70106
|
function readUpstreamErrorCode(body) {
|
|
70107
|
-
const record = asRecord$
|
|
70107
|
+
const record = asRecord$13(body);
|
|
70108
70108
|
if (record === null) return null;
|
|
70109
70109
|
return readTrimmedString$1(record["code"]) ?? readTrimmedString$1(readErrorEnvelope(record)?.["code"]);
|
|
70110
70110
|
}
|
|
70111
70111
|
function readErrorMessage$2(raw) {
|
|
70112
|
-
const record = asRecord$
|
|
70112
|
+
const record = asRecord$13(raw);
|
|
70113
70113
|
if (record === null) return null;
|
|
70114
70114
|
const error = record["error"];
|
|
70115
70115
|
const nestedMessage = readErrorEnvelope(record)?.["message"];
|
|
@@ -72197,7 +72197,7 @@ function readStringJS(length) {
|
|
|
72197
72197
|
if (units.length > 0) result += fromCharCode.apply(String, units);
|
|
72198
72198
|
return result;
|
|
72199
72199
|
}
|
|
72200
|
-
function readString$
|
|
72200
|
+
function readString$5(source, start, length) {
|
|
72201
72201
|
let existingSrc = src;
|
|
72202
72202
|
src = source;
|
|
72203
72203
|
position$1 = start;
|
|
@@ -74062,7 +74062,7 @@ function readStruct(src, position, srcEnd, unpackr) {
|
|
|
74062
74062
|
}
|
|
74063
74063
|
if (end == null) end = source.bytesEnd - refStart;
|
|
74064
74064
|
if (source.srcString) return source.srcString.slice(ref, end);
|
|
74065
|
-
return readString$
|
|
74065
|
+
return readString$5(src, ref + refStart, end - ref);
|
|
74066
74066
|
};
|
|
74067
74067
|
break;
|
|
74068
74068
|
case UTF8:
|
|
@@ -77414,7 +77414,7 @@ require_Schema$1.succeed$1(TraceDiagnostics, TraceDiagnostics.of({ read: (option
|
|
|
77414
77414
|
* through the server is what lets it ask at all.
|
|
77415
77415
|
*/
|
|
77416
77416
|
const RUNTIMES_PATH = "/api/forge/context/runtimes";
|
|
77417
|
-
function asRecord$
|
|
77417
|
+
function asRecord$12(value) {
|
|
77418
77418
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
77419
77419
|
}
|
|
77420
77420
|
/** A trimmed string, or null. Empty and whitespace are the same as absent. */
|
|
@@ -77429,7 +77429,7 @@ function text$1(value) {
|
|
|
77429
77429
|
* something that cannot work.
|
|
77430
77430
|
*/
|
|
77431
77431
|
function toSummary(value) {
|
|
77432
|
-
const row = asRecord$
|
|
77432
|
+
const row = asRecord$12(value);
|
|
77433
77433
|
if (!row) return null;
|
|
77434
77434
|
const runtimeId = text$1(row["runtimeId"]);
|
|
77435
77435
|
const organizationId = row["organizationId"];
|
|
@@ -77453,7 +77453,7 @@ function listCloudRuntimes(input) {
|
|
|
77453
77453
|
return require_Schema$1.gen(function* () {
|
|
77454
77454
|
const jwt = input.jwt;
|
|
77455
77455
|
if (jwt === null || jwt.trim().length === 0) return yield* require_Schema$1.fail(new CloudRuntimeListError({ failure: "not_signed_in" }));
|
|
77456
|
-
const runtimes = asRecord$
|
|
77456
|
+
const runtimes = asRecord$12(asRecord$12(yield* require_Schema$1.tryPromise({
|
|
77457
77457
|
try: () => input.request({
|
|
77458
77458
|
method: "GET",
|
|
77459
77459
|
jwt,
|
|
@@ -92992,7 +92992,7 @@ var AnalyticsService = class AnalyticsService extends require_Schema$1.Service()
|
|
|
92992
92992
|
var RuntimeReplication = class extends require_Schema$1.Service()("forge/runtimeReplication/Services/RuntimeReplication") {};
|
|
92993
92993
|
//#endregion
|
|
92994
92994
|
//#region src/runtimeDrain/forgeGitCommit.ts
|
|
92995
|
-
const execFileAsync$
|
|
92995
|
+
const execFileAsync$7 = (0, node_util.promisify)(node_child_process.execFile);
|
|
92996
92996
|
const FORGE_GIT_COMMIT_PATTERN = /^[0-9a-f]{7,40}$/i;
|
|
92997
92997
|
/**
|
|
92998
92998
|
* Resolve the commit this forge checkout actually booted from, so the
|
|
@@ -93016,7 +93016,7 @@ const FORGE_GIT_COMMIT_PATTERN = /^[0-9a-f]{7,40}$/i;
|
|
|
93016
93016
|
* it yet.
|
|
93017
93017
|
*/
|
|
93018
93018
|
let cached$1 = null;
|
|
93019
|
-
async function resolveForgeGitCommit(execImpl = (command, args) => execFileAsync$
|
|
93019
|
+
async function resolveForgeGitCommit(execImpl = (command, args) => execFileAsync$7(command, [...args], { timeout: 5e3 })) {
|
|
93020
93020
|
if (cached$1 !== null) return cached$1;
|
|
93021
93021
|
try {
|
|
93022
93022
|
const { stdout } = await execImpl("git", ["rev-parse", "HEAD"]);
|
|
@@ -94979,7 +94979,7 @@ function mightCarryUsage(line, provider) {
|
|
|
94979
94979
|
* a line at a time, so the usual cause is a tail the scan opened mid-write,
|
|
94980
94980
|
* and a count that keeps growing is a file the scan can no longer read.
|
|
94981
94981
|
*/
|
|
94982
|
-
function parseJsonObject$
|
|
94982
|
+
function parseJsonObject$2(line, diagnostics) {
|
|
94983
94983
|
let parsed;
|
|
94984
94984
|
try {
|
|
94985
94985
|
parsed = JSON.parse(line);
|
|
@@ -95009,7 +95009,7 @@ function claudeDedupeKey(messageId, requestId) {
|
|
|
95009
95009
|
return `${messageId ?? ""}:${requestId ?? ""}`;
|
|
95010
95010
|
}
|
|
95011
95011
|
function parseClaudeLine(line, diagnostics) {
|
|
95012
|
-
const record = parseJsonObject$
|
|
95012
|
+
const record = parseJsonObject$2(line, diagnostics);
|
|
95013
95013
|
if (record === null) return null;
|
|
95014
95014
|
if (record["type"] !== "assistant") return null;
|
|
95015
95015
|
const messageRecord = objectAt(record, "message");
|
|
@@ -95156,7 +95156,7 @@ function parseCodexTokenCount(record, payloadRecord, state) {
|
|
|
95156
95156
|
};
|
|
95157
95157
|
}
|
|
95158
95158
|
function parseCodexLine(line, state, diagnostics) {
|
|
95159
|
-
const record = parseJsonObject$
|
|
95159
|
+
const record = parseJsonObject$2(line, diagnostics);
|
|
95160
95160
|
if (record === null) return null;
|
|
95161
95161
|
const payloadRecord = objectAt(record, "payload");
|
|
95162
95162
|
if (payloadRecord === null) return null;
|
|
@@ -99537,7 +99537,7 @@ function buildDiscoveryResult(probes) {
|
|
|
99537
99537
|
}
|
|
99538
99538
|
//#endregion
|
|
99539
99539
|
//#region src/sourceControl/probe.ts
|
|
99540
|
-
const execFileAsync$
|
|
99540
|
+
const execFileAsync$6 = (0, node_util.promisify)(node_child_process.execFile);
|
|
99541
99541
|
/**
|
|
99542
99542
|
* How long a machine's toolchain answer stays good for.
|
|
99543
99543
|
*
|
|
@@ -99548,7 +99548,7 @@ const execFileAsync$5 = (0, node_util.promisify)(node_child_process.execFile);
|
|
|
99548
99548
|
*/
|
|
99549
99549
|
const DISCOVERY_TTL = "5 minutes";
|
|
99550
99550
|
/** Probes time out because `gh auth status` reaches the network on a cold keyring. */
|
|
99551
|
-
const PROBE_TIMEOUT_MS = 5e3;
|
|
99551
|
+
const PROBE_TIMEOUT_MS$1 = 5e3;
|
|
99552
99552
|
/**
|
|
99553
99553
|
* Runs one probe. A tool that is absent, fails, or hangs is reported the same
|
|
99554
99554
|
* way — `null`, meaning "no answer" — because the discovery result only
|
|
@@ -99560,7 +99560,7 @@ const PROBE_TIMEOUT_MS = 5e3;
|
|
|
99560
99560
|
*/
|
|
99561
99561
|
const probe = (command, args) => require_Schema$1.promise(async () => {
|
|
99562
99562
|
try {
|
|
99563
|
-
const { stdout, stderr } = await execFileAsync$
|
|
99563
|
+
const { stdout, stderr } = await execFileAsync$6(command, [...args], { timeout: PROBE_TIMEOUT_MS$1 });
|
|
99564
99564
|
const output = `${stdout}\n${stderr}`.trim();
|
|
99565
99565
|
return { output: output.length === 0 ? null : output };
|
|
99566
99566
|
} catch (cause) {
|
|
@@ -100880,7 +100880,7 @@ const EMPTY_PLAN_VALUES = new Set([
|
|
|
100880
100880
|
"none",
|
|
100881
100881
|
"null"
|
|
100882
100882
|
]);
|
|
100883
|
-
function asRecord$
|
|
100883
|
+
function asRecord$11(value) {
|
|
100884
100884
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
100885
100885
|
return value;
|
|
100886
100886
|
}
|
|
@@ -100897,7 +100897,7 @@ function decodeJwtClaims(token) {
|
|
|
100897
100897
|
try {
|
|
100898
100898
|
const decoded = Buffer.from(payload, "base64url");
|
|
100899
100899
|
if (decoded.byteLength > MAX_CLAIMS_BYTES) return void 0;
|
|
100900
|
-
return asRecord$
|
|
100900
|
+
return asRecord$11(JSON.parse(decoded.toString("utf8")));
|
|
100901
100901
|
} catch {
|
|
100902
100902
|
return;
|
|
100903
100903
|
}
|
|
@@ -100919,7 +100919,7 @@ function findPlanType(claims) {
|
|
|
100919
100919
|
const direct = fromRecord(claims);
|
|
100920
100920
|
if (direct) return direct;
|
|
100921
100921
|
for (const value of Object.values(claims)) {
|
|
100922
|
-
const nested = asRecord$
|
|
100922
|
+
const nested = asRecord$11(value);
|
|
100923
100923
|
if (!nested) continue;
|
|
100924
100924
|
const found = fromRecord(nested);
|
|
100925
100925
|
if (found) return found;
|
|
@@ -100936,7 +100936,7 @@ function readCodexAuthIdentity(authJson) {
|
|
|
100936
100936
|
} catch {
|
|
100937
100937
|
return {};
|
|
100938
100938
|
}
|
|
100939
|
-
const claims = decodeJwtClaims(asRecord$
|
|
100939
|
+
const claims = decodeJwtClaims(asRecord$11(asRecord$11(parsed)?.tokens)?.id_token);
|
|
100940
100940
|
if (!claims) return {};
|
|
100941
100941
|
const email = findEmail(claims);
|
|
100942
100942
|
const planType = findPlanType(claims);
|
|
@@ -101099,6 +101099,7 @@ const CLAUDE_IDENTITY_KEYS = new Set([
|
|
|
101099
101099
|
"provider",
|
|
101100
101100
|
"credentialGenerationId"
|
|
101101
101101
|
]);
|
|
101102
|
+
const CLAUDE_IDENTITY_OPTIONAL_KEYS = new Set(["providerAccountIdentity"]);
|
|
101102
101103
|
function isRecord$13(value) {
|
|
101103
101104
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
101104
101105
|
}
|
|
@@ -101123,6 +101124,10 @@ function daysInMonth$1(year, month) {
|
|
|
101123
101124
|
function hasExactKeys$2(value, keys) {
|
|
101124
101125
|
return Object.keys(value).every((key) => keys.has(key)) && Object.keys(value).length === keys.size;
|
|
101125
101126
|
}
|
|
101127
|
+
function hasAllowedKeys(value, required, optional) {
|
|
101128
|
+
const keys = Object.keys(value);
|
|
101129
|
+
return keys.every((key) => required.has(key) || optional.has(key)) && [...required].every((key) => keys.includes(key));
|
|
101130
|
+
}
|
|
101126
101131
|
function parseDeliveryIdentity(value, provider) {
|
|
101127
101132
|
if (!isRecord$13(value)) return null;
|
|
101128
101133
|
if (provider === "openai") return parseCodexDeliveryIdentity(value);
|
|
@@ -101137,12 +101142,25 @@ function parseCodexDeliveryIdentity(value) {
|
|
|
101137
101142
|
providerAccountIdentity: value.providerAccountIdentity
|
|
101138
101143
|
};
|
|
101139
101144
|
}
|
|
101145
|
+
/**
|
|
101146
|
+
* Absent means the writer does not name the Provider Account yet, and the
|
|
101147
|
+
* identity keeps the shape it has always had. Present but malformed means the
|
|
101148
|
+
* identity is not the one CMMD signed, so it is refused the same way a
|
|
101149
|
+
* malformed Codex account identity is.
|
|
101150
|
+
*/
|
|
101151
|
+
function claudeAccountFields(value) {
|
|
101152
|
+
if (value === void 0) return {};
|
|
101153
|
+
if (typeof value !== "string" || !PROVIDER_ACCOUNT_IDENTITY$2.test(value)) return null;
|
|
101154
|
+
return { providerAccountIdentity: value };
|
|
101155
|
+
}
|
|
101140
101156
|
function parseClaudeDeliveryIdentity(value) {
|
|
101141
|
-
|
|
101157
|
+
const account = claudeAccountFields(value.providerAccountIdentity);
|
|
101158
|
+
if (!account || !hasAllowedKeys(value, CLAUDE_IDENTITY_KEYS, CLAUDE_IDENTITY_OPTIONAL_KEYS) || value.kind !== "credential_generation" || value.provider !== "claude" || typeof value.credentialGenerationId !== "string" || !UUID.test(value.credentialGenerationId)) return null;
|
|
101142
101159
|
return {
|
|
101143
101160
|
kind: "credential_generation",
|
|
101144
101161
|
provider: "claude",
|
|
101145
|
-
credentialGenerationId: value.credentialGenerationId
|
|
101162
|
+
credentialGenerationId: value.credentialGenerationId,
|
|
101163
|
+
...account
|
|
101146
101164
|
};
|
|
101147
101165
|
}
|
|
101148
101166
|
function parseProviderRuntimeDeliveryReceipt(value, expected) {
|
|
@@ -101181,10 +101199,49 @@ function readProviderDeliveryBinding(value) {
|
|
|
101181
101199
|
identity
|
|
101182
101200
|
};
|
|
101183
101201
|
}
|
|
101202
|
+
/** The Provider Account a delivery identity names, or null when it names none. */
|
|
101203
|
+
function providerDeliveryAccount(identity) {
|
|
101204
|
+
return identity.kind === "provider_account" ? identity.providerAccountIdentity : identity.providerAccountIdentity ?? null;
|
|
101205
|
+
}
|
|
101206
|
+
function sameDeliveredCredential(left, right) {
|
|
101207
|
+
if (left.kind === "provider_account" && right.kind === "provider_account") return left.providerAccountIdentity === right.providerAccountIdentity;
|
|
101208
|
+
return left.kind === "credential_generation" && right.kind === "credential_generation" && left.credentialGenerationId === right.credentialGenerationId;
|
|
101209
|
+
}
|
|
101210
|
+
/**
|
|
101211
|
+
* Names how one thread's delivery binding moved, which is the question the
|
|
101212
|
+
* delivery layer could not answer while Claude's identity carried no account.
|
|
101213
|
+
*
|
|
101214
|
+
* `account_switch` needs both sides to name an account, the accounts to differ,
|
|
101215
|
+
* the delivered credential to change with them, and the runtime, machine and
|
|
101216
|
+
* epoch to hold. A switch changes the account and the generation together, so
|
|
101217
|
+
* two accounts on one generation would be two accounts sharing a credential,
|
|
101218
|
+
* and a switch that also moved the epoch changed more than the identity. Both
|
|
101219
|
+
* are `unknown`.
|
|
101220
|
+
*
|
|
101221
|
+
* The same delivered credential is `unchanged` whether or not either side names
|
|
101222
|
+
* an account, so a binding recorded before CMMD named the account still resumes
|
|
101223
|
+
* against the credential it ran on. A changed credential where either side
|
|
101224
|
+
* names no account is `unknown` rather than guessed at, which keeps an
|
|
101225
|
+
* account-less binding compared on exactly the fields it was compared on before.
|
|
101226
|
+
*
|
|
101227
|
+
* `providerDeliveryBindingsEqual` asks only whether the answer is `unchanged`.
|
|
101228
|
+
* `permitsProviderAccountTransition` in `provider/providerAccountTransition.ts`
|
|
101229
|
+
* asks whether it is `account_switch`, and is the Codex resume gate's rule
|
|
101230
|
+
* today. #3055 wires the Claude resume gate onto it.
|
|
101231
|
+
*/
|
|
101232
|
+
function classifyProviderDeliveryBindingChange(prior, current) {
|
|
101233
|
+
if (prior.runtimeId !== current.runtimeId || prior.machineId !== current.machineId || prior.identity.kind !== current.identity.kind || prior.identity.provider !== current.identity.provider) return "unknown";
|
|
101234
|
+
const sameEpoch = prior.credentialEpoch === current.credentialEpoch;
|
|
101235
|
+
const sameCredential = sameDeliveredCredential(prior.identity, current.identity);
|
|
101236
|
+
const priorAccount = providerDeliveryAccount(prior.identity);
|
|
101237
|
+
const currentAccount = providerDeliveryAccount(current.identity);
|
|
101238
|
+
if (priorAccount !== null && currentAccount !== null && priorAccount !== currentAccount) return sameEpoch && !sameCredential ? "account_switch" : "unknown";
|
|
101239
|
+
if (sameEpoch && sameCredential) return "unchanged";
|
|
101240
|
+
if (priorAccount === null || currentAccount === null) return "unknown";
|
|
101241
|
+
return "credential_refresh";
|
|
101242
|
+
}
|
|
101184
101243
|
function providerDeliveryBindingsEqual(left, right) {
|
|
101185
|
-
|
|
101186
|
-
if (left.identity.kind === "provider_account" && right.identity.kind === "provider_account") return left.identity.providerAccountIdentity === right.identity.providerAccountIdentity;
|
|
101187
|
-
return left.identity.kind === "credential_generation" && right.identity.kind === "credential_generation" && left.identity.credentialGenerationId === right.identity.credentialGenerationId;
|
|
101244
|
+
return classifyProviderDeliveryBindingChange(left, right) === "unchanged";
|
|
101188
101245
|
}
|
|
101189
101246
|
//#endregion
|
|
101190
101247
|
//#region src/provider/claudeReadinessReason.ts
|
|
@@ -101463,8 +101520,128 @@ const runClaudeLogout = (runtime) => new Promise((resolve, reject) => {
|
|
|
101463
101520
|
});
|
|
101464
101521
|
});
|
|
101465
101522
|
//#endregion
|
|
101523
|
+
//#region src/usage/providerRateLimits.ts
|
|
101524
|
+
/**
|
|
101525
|
+
* `new Date(ms).toISOString()` throws past +-8.64e15 ms, so a `resetsAt` past
|
|
101526
|
+
* the same bound in seconds is refused rather than allowed to throw inside a
|
|
101527
|
+
* decoder whose whole job is to fail closed.
|
|
101528
|
+
*/
|
|
101529
|
+
const MAX_RESETS_AT_SECONDS = 864e10;
|
|
101530
|
+
/**
|
|
101531
|
+
* One reset time in unix seconds as an ISO string, or null.
|
|
101532
|
+
*
|
|
101533
|
+
* Both mappers read reset times through this, so a malformed `resetsAt`
|
|
101534
|
+
* yields the null that means "this observation said nothing" instead of a
|
|
101535
|
+
* RangeError thrown out of a decoder the ingestion layer runs on every Claude
|
|
101536
|
+
* rate-limit event.
|
|
101537
|
+
*/
|
|
101538
|
+
function resetsAtIso(resetsAt) {
|
|
101539
|
+
if (!Number.isFinite(resetsAt) || resetsAt <= 0 || resetsAt > MAX_RESETS_AT_SECONDS) return null;
|
|
101540
|
+
return (/* @__PURE__ */ new Date(resetsAt * 1e3)).toISOString();
|
|
101541
|
+
}
|
|
101542
|
+
/**
|
|
101543
|
+
* The SDK only ever reports ONE window per event. Only `five_hour` and
|
|
101544
|
+
* `seven_day` map to the two windows the Usage page shows; the
|
|
101545
|
+
* per-model/overage variants (seven_day_opus, overage, ...) are ignored here
|
|
101546
|
+
* on purpose rather than silently overwriting the plain 7-day figure.
|
|
101547
|
+
*/
|
|
101548
|
+
function mapClaudeRateLimitInfo(info) {
|
|
101549
|
+
if (typeof info.utilization !== "number" || typeof info.resetsAt !== "number" || !info.status) return null;
|
|
101550
|
+
const kind = info.rateLimitType === "five_hour" ? "fiveHour" : info.rateLimitType === "seven_day" ? "sevenDay" : null;
|
|
101551
|
+
if (!kind) return null;
|
|
101552
|
+
const resetsAt = resetsAtIso(info.resetsAt);
|
|
101553
|
+
if (resetsAt === null) return null;
|
|
101554
|
+
return {
|
|
101555
|
+
kind,
|
|
101556
|
+
window: {
|
|
101557
|
+
usedPercent: Math.round(info.utilization * 100),
|
|
101558
|
+
resetsAt,
|
|
101559
|
+
status: info.status
|
|
101560
|
+
}
|
|
101561
|
+
};
|
|
101562
|
+
}
|
|
101563
|
+
/**
|
|
101564
|
+
* The one place a used-percent becomes a status. `unifiedWindows` carries no
|
|
101565
|
+
* per-window status of its own, and the `/usage` parser derives one the same
|
|
101566
|
+
* way, so both Claude usage sources agree on where "warning" starts.
|
|
101567
|
+
*/
|
|
101568
|
+
function usageStatus(usedPercent) {
|
|
101569
|
+
if (usedPercent >= 100) return "rejected";
|
|
101570
|
+
return usedPercent >= 80 ? "allowed_warning" : "allowed";
|
|
101571
|
+
}
|
|
101572
|
+
/**
|
|
101573
|
+
* One untyped `utilization` as a whole percent, or nothing. The [0, 100] bound
|
|
101574
|
+
* matches `claudeUsageCli.ts`'s own bound on the `/usage` text, which in turn
|
|
101575
|
+
* matches CMMD's parser.
|
|
101576
|
+
*/
|
|
101577
|
+
function readUsedPercent(utilization) {
|
|
101578
|
+
if (typeof utilization !== "number" || !Number.isFinite(utilization)) return null;
|
|
101579
|
+
const usedPercent = Math.round(utilization * 100);
|
|
101580
|
+
return usedPercent < 0 || usedPercent > 100 ? null : usedPercent;
|
|
101581
|
+
}
|
|
101582
|
+
/**
|
|
101583
|
+
* Reads one `unifiedWindows` entry, or nothing. Every field is checked because
|
|
101584
|
+
* none of them are typed: a non-object entry, a utilization that is not a
|
|
101585
|
+
* finite number in [0, 1], or a reset time that is not a positive finite
|
|
101586
|
+
* number of seconds all mean "this observation said nothing about this
|
|
101587
|
+
* window".
|
|
101588
|
+
*/
|
|
101589
|
+
function readUnifiedWindow(raw) {
|
|
101590
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
101591
|
+
const { utilization, resetsAt } = raw;
|
|
101592
|
+
const usedPercent = readUsedPercent(utilization);
|
|
101593
|
+
if (usedPercent === null) return null;
|
|
101594
|
+
if (typeof resetsAt !== "number") return null;
|
|
101595
|
+
const resetsAtIsoValue = resetsAtIso(resetsAt);
|
|
101596
|
+
if (resetsAtIsoValue === null) return null;
|
|
101597
|
+
return {
|
|
101598
|
+
usedPercent,
|
|
101599
|
+
resetsAt: resetsAtIsoValue,
|
|
101600
|
+
status: usageStatus(usedPercent)
|
|
101601
|
+
};
|
|
101602
|
+
}
|
|
101603
|
+
/**
|
|
101604
|
+
* Reads the `unifiedWindows` sub-object of `rate_limit_info` into the two
|
|
101605
|
+
* rolling windows. The SDK rebuilds that sub-object on every turn from the
|
|
101606
|
+
* `anthropic-ratelimit-unified-{5h,7d}-utilization` and `-reset` response
|
|
101607
|
+
* headers.
|
|
101608
|
+
*
|
|
101609
|
+
* The top-level `rateLimitType` names one window per event and carries a
|
|
101610
|
+
* utilization only for `five_hour` or `seven_day`. This sub-object reports
|
|
101611
|
+
* every window it knows on every observation, which makes it the only
|
|
101612
|
+
* SDK-side source that can feed the Usage card without waking a Sprite and
|
|
101613
|
+
* delivering a credential.
|
|
101614
|
+
*
|
|
101615
|
+
* Its `seven_day_overage_included` window is skipped. `/usage` names each model
|
|
101616
|
+
* row after the display name Anthropic's usage API gives that model, one row
|
|
101617
|
+
* per allowlisted model, and the `7d_oi` header behind this window is one
|
|
101618
|
+
* aggregate that carries no name. Any name given to it here would be a guess
|
|
101619
|
+
* that could disagree with the rows the probe writes, so model rows come from
|
|
101620
|
+
* the probe alone.
|
|
101621
|
+
*
|
|
101622
|
+
* It is also undocumented internal surface stripped from the SDK's exported
|
|
101623
|
+
* types, so this decoder fails closed everywhere. It returns null when the
|
|
101624
|
+
* sub-object is absent or is not a plain object, and null when neither window
|
|
101625
|
+
* in it survives validation. A window that fails validation is reported as
|
|
101626
|
+
* null, and an unrecognized key is skipped, never fatal. A caller that gets
|
|
101627
|
+
* null has learned nothing and must not publish, because a partial write could
|
|
101628
|
+
* overwrite a good `/usage` reading with a guess.
|
|
101629
|
+
*/
|
|
101630
|
+
function mapClaudeUnifiedWindows(info) {
|
|
101631
|
+
const raw = info.unifiedWindows;
|
|
101632
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
101633
|
+
const windows = raw;
|
|
101634
|
+
const fiveHour = readUnifiedWindow(windows.five_hour);
|
|
101635
|
+
const sevenDay = readUnifiedWindow(windows.seven_day);
|
|
101636
|
+
if (!fiveHour && !sevenDay) return null;
|
|
101637
|
+
return {
|
|
101638
|
+
fiveHour,
|
|
101639
|
+
sevenDay
|
|
101640
|
+
};
|
|
101641
|
+
}
|
|
101642
|
+
//#endregion
|
|
101466
101643
|
//#region src/usage/claudeUsageCli.ts
|
|
101467
|
-
const execFileAsync$
|
|
101644
|
+
const execFileAsync$5 = (0, node_util.promisify)(node_child_process.execFile);
|
|
101468
101645
|
const USAGE_ARGS = [
|
|
101469
101646
|
"--safe-mode",
|
|
101470
101647
|
"--no-session-persistence",
|
|
@@ -101488,7 +101665,7 @@ const MONTH_BY_NAME = {
|
|
|
101488
101665
|
Dec: 11
|
|
101489
101666
|
};
|
|
101490
101667
|
async function runClaudeUsageCommand(binaryPath, args, claudeConfigDir) {
|
|
101491
|
-
const { stdout } = await execFileAsync$
|
|
101668
|
+
const { stdout } = await execFileAsync$5(binaryPath, [...args], {
|
|
101492
101669
|
env: {
|
|
101493
101670
|
...process.env,
|
|
101494
101671
|
TZ: "UTC",
|
|
@@ -101563,10 +101740,6 @@ function parseResetAt(value, now) {
|
|
|
101563
101740
|
});
|
|
101564
101741
|
return resetAt === null || Number.isNaN(resetAt.getTime()) ? null : resetAt.toISOString();
|
|
101565
101742
|
}
|
|
101566
|
-
function usageStatus(usedPercent) {
|
|
101567
|
-
if (usedPercent >= 100) return "rejected";
|
|
101568
|
-
return usedPercent >= 80 ? "allowed_warning" : "allowed";
|
|
101569
|
-
}
|
|
101570
101743
|
function parseUsageLine(output, label, now) {
|
|
101571
101744
|
const escapedLabel = label.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
101572
101745
|
const match = new RegExp(`^${escapedLabel}: (\\d+(?:\\.\\d+)?)% used(?: · resets (.+))?$`, "m").exec(output);
|
|
@@ -101635,6 +101808,29 @@ async function fetchClaudeUsageViaOfficialClient(input) {
|
|
|
101635
101808
|
};
|
|
101636
101809
|
}
|
|
101637
101810
|
//#endregion
|
|
101811
|
+
//#region src/usage/claudeUsageTelemetryRequest.ts
|
|
101812
|
+
/** The owner-session route for a Claude usage reading. */
|
|
101813
|
+
const CLAUDE_USAGE_TELEMETRY_PATH = "/api/forge/context/providers/usage/claude/telemetry";
|
|
101814
|
+
/**
|
|
101815
|
+
* Builds the body for either lane.
|
|
101816
|
+
*
|
|
101817
|
+
* `modelWindows` is sent only when the reading carries it. CMMD keeps the
|
|
101818
|
+
* stored model rows when the key is absent, so a reading that cannot name
|
|
101819
|
+
* model rows the way `/usage` does leaves the key out.
|
|
101820
|
+
*/
|
|
101821
|
+
function claudeUsageTelemetryBody(input) {
|
|
101822
|
+
return {
|
|
101823
|
+
providerAccountId: input.attribution.providerAccountId,
|
|
101824
|
+
credentialGenerationId: input.attribution.credentialGenerationId,
|
|
101825
|
+
...input.account,
|
|
101826
|
+
source: input.source,
|
|
101827
|
+
fiveHour: input.windows.fiveHour,
|
|
101828
|
+
sevenDay: input.windows.sevenDay,
|
|
101829
|
+
...input.windows.modelWindows !== void 0 ? { modelWindows: input.windows.modelWindows } : {},
|
|
101830
|
+
observedAt: input.observedAt
|
|
101831
|
+
};
|
|
101832
|
+
}
|
|
101833
|
+
//#endregion
|
|
101638
101834
|
//#region src/cmmd/syncClaudeCredential.ts
|
|
101639
101835
|
/**
|
|
101640
101836
|
* Card 1359 (harvest path A, Claude) — publish a personal Claude subscription
|
|
@@ -101650,7 +101846,6 @@ async function fetchClaudeUsageViaOfficialClient(input) {
|
|
|
101650
101846
|
const USER_CLAUDE_CREDENTIAL_PATH = "/api/forge/context/providers/claude/credential";
|
|
101651
101847
|
const USER_CLAUDE_LINKED_ACCOUNT_PATH = "/api/forge/context/providers/claude/accounts";
|
|
101652
101848
|
const AUTH_RUNTIME_CLAUDE_CREDENTIAL_PATH = "/api/forge/context/auth-runtime-claude-credential";
|
|
101653
|
-
const USER_CLAUDE_USAGE_PATH = "/api/forge/context/providers/usage/claude/telemetry";
|
|
101654
101849
|
const AUTH_RUNTIME_CLAUDE_USAGE_PATH = "/api/forge/context/auth-runtime-claude-credential/usage";
|
|
101655
101850
|
/** Path to the local Claude credentials.json, mirroring Claude Code's resolution. */
|
|
101656
101851
|
function resolveLocalClaudeCredentialPath(homePath) {
|
|
@@ -101679,7 +101874,7 @@ function resolveLocalClaudeCredentialPath(homePath) {
|
|
|
101679
101874
|
* it reaches — this is the first gate and the only one near the user, who is
|
|
101680
101875
|
* standing in front of the sign-in and can simply try again.
|
|
101681
101876
|
*
|
|
101682
|
-
* The floor is ONE token, not both,
|
|
101877
|
+
* The floor is ONE token, not both, because an
|
|
101683
101878
|
* expired or blank access token beside a live refresh token is the normal
|
|
101684
101879
|
* resting state of a rotating login, which Claude Code refreshes silently on
|
|
101685
101880
|
* use. Demanding both would reject a perfectly recoverable credential and break
|
|
@@ -101757,10 +101952,10 @@ function extractClaudeAccountMetadata(credentialJson) {
|
|
|
101757
101952
|
return {};
|
|
101758
101953
|
}
|
|
101759
101954
|
}
|
|
101760
|
-
const execFileAsync$
|
|
101955
|
+
const execFileAsync$4 = (0, node_util.promisify)(node_child_process.execFile);
|
|
101761
101956
|
async function loadClaudeAccountMetadataFromCli(homePath) {
|
|
101762
101957
|
try {
|
|
101763
|
-
const { stdout } = await execFileAsync$
|
|
101958
|
+
const { stdout } = await execFileAsync$4(resolveClaudeBinaryPath("claude"), ["auth", "status"], {
|
|
101764
101959
|
timeout: 2e4,
|
|
101765
101960
|
maxBuffer: 1024 * 1024,
|
|
101766
101961
|
env: {
|
|
@@ -101856,18 +102051,20 @@ async function publishClaudeUsageAfterSync(input) {
|
|
|
101856
102051
|
binaryPath: resolveClaudeBinaryPath("claude"),
|
|
101857
102052
|
...input.homePath ? { claudeConfigDir: input.homePath } : {}
|
|
101858
102053
|
});
|
|
101859
|
-
await publishUsageBody(input.auth.jwt, input.auth.path === AUTH_RUNTIME_CLAUDE_CREDENTIAL_PATH ? AUTH_RUNTIME_CLAUDE_USAGE_PATH :
|
|
101860
|
-
|
|
101861
|
-
|
|
101862
|
-
|
|
101863
|
-
|
|
101864
|
-
|
|
102054
|
+
await publishUsageBody(input.auth.jwt, input.auth.path === AUTH_RUNTIME_CLAUDE_CREDENTIAL_PATH ? AUTH_RUNTIME_CLAUDE_USAGE_PATH : CLAUDE_USAGE_TELEMETRY_PATH, claudeUsageTelemetryBody({
|
|
102055
|
+
attribution: {
|
|
102056
|
+
providerAccountId: input.providerAccountId,
|
|
102057
|
+
credentialGenerationId: input.credentialGenerationId
|
|
102058
|
+
},
|
|
102059
|
+
account: {
|
|
102060
|
+
accountEmail: input.accountEmail,
|
|
102061
|
+
accountDisplayName: input.accountDisplayName,
|
|
102062
|
+
planType: input.planType
|
|
102063
|
+
},
|
|
101865
102064
|
source: "active_poll",
|
|
101866
|
-
|
|
101867
|
-
sevenDay: usage.sevenDay,
|
|
101868
|
-
modelWindows: usage.modelWindows,
|
|
102065
|
+
windows: usage,
|
|
101869
102066
|
observedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
101870
|
-
});
|
|
102067
|
+
}));
|
|
101871
102068
|
return null;
|
|
101872
102069
|
} catch (cause) {
|
|
101873
102070
|
return { cause };
|
|
@@ -149409,11 +149606,11 @@ function readObject(value) {
|
|
|
149409
149606
|
function readArray(value) {
|
|
149410
149607
|
return Array.isArray(value) ? value : void 0;
|
|
149411
149608
|
}
|
|
149412
|
-
function readString$
|
|
149609
|
+
function readString$4(value) {
|
|
149413
149610
|
return typeof value === "string" ? value : void 0;
|
|
149414
149611
|
}
|
|
149415
149612
|
function nonEmptyTrimmed$1(value) {
|
|
149416
|
-
const candidate = readString$
|
|
149613
|
+
const candidate = readString$4(value)?.trim();
|
|
149417
149614
|
return candidate ? candidate : void 0;
|
|
149418
149615
|
}
|
|
149419
149616
|
function readBoolean$2(value) {
|
|
@@ -150195,7 +150392,7 @@ function issueCmmdLocalUserProviderEgressCredential(input) {
|
|
|
150195
150392
|
});
|
|
150196
150393
|
}
|
|
150197
150394
|
//#endregion
|
|
150198
|
-
//#region src/provider/
|
|
150395
|
+
//#region src/provider/providerAccountTransition.ts
|
|
150199
150396
|
function explicitProviderAccountTransition(fromAccountId, toAccountId) {
|
|
150200
150397
|
if (!fromAccountId || !toAccountId || fromAccountId === toAccountId) return void 0;
|
|
150201
150398
|
return {
|
|
@@ -150203,13 +150400,20 @@ function explicitProviderAccountTransition(fromAccountId, toAccountId) {
|
|
|
150203
150400
|
toAccountId
|
|
150204
150401
|
};
|
|
150205
150402
|
}
|
|
150206
|
-
|
|
150403
|
+
/**
|
|
150404
|
+
* Whether a thread may move from `prior` to `current` because the person asked
|
|
150405
|
+
* for another Provider Account. The credential broker authorizes the selected
|
|
150406
|
+
* account, so only the identity may change, which is what `account_switch`
|
|
150407
|
+
* means.
|
|
150408
|
+
*
|
|
150409
|
+
* Nothing in the rule is specific to a provider, but the Codex resume gate in
|
|
150410
|
+
* `codexAppServerManager.ts` is its only caller. The Claude half is parked
|
|
150411
|
+
* until #3055 wires the Claude resume gate onto it.
|
|
150412
|
+
*/
|
|
150413
|
+
function permitsProviderAccountTransition(input) {
|
|
150207
150414
|
const { transition, requestedAccountId, prior, current } = input;
|
|
150208
|
-
if (!transition || transition.fromAccountId === transition.toAccountId || transition.toAccountId !== requestedAccountId
|
|
150209
|
-
return
|
|
150210
|
-
...prior,
|
|
150211
|
-
identity: current.identity
|
|
150212
|
-
}, current);
|
|
150415
|
+
if (!transition || transition.fromAccountId === transition.toAccountId || transition.toAccountId !== requestedAccountId) return false;
|
|
150416
|
+
return classifyProviderDeliveryBindingChange(prior, current) === "account_switch";
|
|
150213
150417
|
}
|
|
150214
150418
|
//#endregion
|
|
150215
150419
|
//#region src/providerAuth/codexLocalAccountIdentity.ts
|
|
@@ -150709,7 +150913,7 @@ var CodexAppServerManager = class extends node_events.EventEmitter {
|
|
|
150709
150913
|
const rawPriorDeliveryBinding = input.resumeCursor.providerDeliveryBinding;
|
|
150710
150914
|
const priorDeliveryBinding = readProviderDeliveryBinding(rawPriorDeliveryBinding);
|
|
150711
150915
|
if (rawPriorDeliveryBinding !== void 0 && rawPriorDeliveryBinding !== null && !priorDeliveryBinding) throw new Error("Existing Codex history cannot resume because its persisted delivered provider account binding is unreadable.");
|
|
150712
|
-
if (priorDeliveryBinding && !providerDeliveryBindingsEqual(priorDeliveryBinding, currentDeliveryBinding) && !
|
|
150916
|
+
if (priorDeliveryBinding && !providerDeliveryBindingsEqual(priorDeliveryBinding, currentDeliveryBinding) && !permitsProviderAccountTransition({
|
|
150713
150917
|
transition: input.accountTransition,
|
|
150714
150918
|
requestedAccountId: input.providerAccountId,
|
|
150715
150919
|
prior: priorDeliveryBinding,
|
|
@@ -176783,7 +176987,7 @@ function em(e) {
|
|
|
176783
176987
|
BO ??= new Int32Array(new SharedArrayBuffer(4)), Atomics.wait(BO, 0, 0, SY);
|
|
176784
176988
|
}
|
|
176785
176989
|
}
|
|
176786
|
-
function rm$
|
|
176990
|
+
function rm$6(e, t) {
|
|
176787
176991
|
let n = (0, path.resolve)(t).toLowerCase(), r = (0, path.dirname)((0, path.resolve)(e)).toLowerCase();
|
|
176788
176992
|
if (r === n || KO(r, n) || qO(r, n, "lexical")) return !0;
|
|
176789
176993
|
let o = em(t)?.toLowerCase();
|
|
@@ -189633,7 +189837,7 @@ function Wo() {
|
|
|
189633
189837
|
}
|
|
189634
189838
|
function sle(e) {
|
|
189635
189839
|
let t = process.cwd();
|
|
189636
|
-
return e.filter((n) => !rm$
|
|
189840
|
+
return e.filter((n) => !rm$6(n, t));
|
|
189637
189841
|
}
|
|
189638
189842
|
function ZU(e) {
|
|
189639
189843
|
let t = Bun.which(e);
|
|
@@ -206928,7 +207132,7 @@ function createCodexBrowserDynamicTools() {
|
|
|
206928
207132
|
function readArgs$3(value) {
|
|
206929
207133
|
return value && typeof value === "object" ? value : {};
|
|
206930
207134
|
}
|
|
206931
|
-
function readString$
|
|
207135
|
+
function readString$3(args, key) {
|
|
206932
207136
|
const value = args[key];
|
|
206933
207137
|
return typeof value === "string" && value.trim().length > 0 ? value : void 0;
|
|
206934
207138
|
}
|
|
@@ -206957,27 +207161,27 @@ function createCodexBrowserDynamicToolHandler(browserManager, options = {}) {
|
|
|
206957
207161
|
const toolScope = options.environmentId === void 0 ? {} : { environmentId: options.environmentId };
|
|
206958
207162
|
const handlers = makeBrowserHandlers(browserManager, ThreadId.make(input.threadId), toolScope);
|
|
206959
207163
|
const args = readArgs$3(input.arguments);
|
|
206960
|
-
const tabId = readString$
|
|
207164
|
+
const tabId = readString$3(args, "tabId");
|
|
206961
207165
|
switch (input.tool) {
|
|
206962
207166
|
case "forge_browser_list_sessions": return toCodexDynamicToolResult$2(await handlers.listSessions());
|
|
206963
207167
|
case "forge_browser_name_session": {
|
|
206964
|
-
const name = readString$
|
|
207168
|
+
const name = readString$3(args, "name");
|
|
206965
207169
|
if (!name) throw new Error("forge_browser_name_session requires a name.");
|
|
206966
207170
|
return toCodexDynamicToolResult$2(await handlers.nameSession(name));
|
|
206967
207171
|
}
|
|
206968
207172
|
case "forge_browser_set_visibility": {
|
|
206969
|
-
const visibility = readString$
|
|
207173
|
+
const visibility = readString$3(args, "visibility");
|
|
206970
207174
|
if (visibility !== "hidden" && visibility !== "visible") throw new Error("forge_browser_set_visibility requires visibility hidden or visible.");
|
|
206971
207175
|
return toCodexDynamicToolResult$2(await handlers.setVisibility(visibility));
|
|
206972
207176
|
}
|
|
206973
207177
|
case "forge_browser_close_session":
|
|
206974
207178
|
case "forge_browser_reset_session": return toCodexDynamicToolResult$2(await handlers.closeSession());
|
|
206975
207179
|
case "forge_browser_navigate": {
|
|
206976
|
-
const url = readString$
|
|
207180
|
+
const url = readString$3(args, "url");
|
|
206977
207181
|
if (!url) throw new Error("forge_browser_navigate requires a URL.");
|
|
206978
207182
|
return toCodexDynamicToolResult$2(await handlers.navigate(url, tabId));
|
|
206979
207183
|
}
|
|
206980
|
-
case "forge_browser_new_tab": return toCodexDynamicToolResult$2(await handlers.newTab(readString$
|
|
207184
|
+
case "forge_browser_new_tab": return toCodexDynamicToolResult$2(await handlers.newTab(readString$3(args, "url")));
|
|
206981
207185
|
case "forge_browser_close_tab":
|
|
206982
207186
|
if (!tabId) throw new Error("forge_browser_close_tab requires a tabId.");
|
|
206983
207187
|
return toCodexDynamicToolResult$2(await handlers.closeTab(tabId));
|
|
@@ -206988,33 +207192,33 @@ function createCodexBrowserDynamicToolHandler(browserManager, options = {}) {
|
|
|
206988
207192
|
case "forge_browser_go_forward": return toCodexDynamicToolResult$2(await handlers.goForward(tabId));
|
|
206989
207193
|
case "forge_browser_reload": return toCodexDynamicToolResult$2(await handlers.reload(tabId));
|
|
206990
207194
|
case "forge_browser_click": {
|
|
206991
|
-
const selector = readString$
|
|
207195
|
+
const selector = readString$3(args, "selector");
|
|
206992
207196
|
if (!selector) throw new Error("forge_browser_click requires a selector.");
|
|
206993
207197
|
return toCodexDynamicToolResult$2(await handlers.click(selector, tabId));
|
|
206994
207198
|
}
|
|
206995
207199
|
case "forge_browser_click_point": {
|
|
206996
207200
|
const x = readNumber$2(args, "x");
|
|
206997
207201
|
const y = readNumber$2(args, "y");
|
|
206998
|
-
const button = readString$
|
|
207202
|
+
const button = readString$3(args, "button");
|
|
206999
207203
|
const clickCount = readNumber$2(args, "clickCount");
|
|
207000
207204
|
if (x === void 0 || y === void 0) throw new Error("forge_browser_click_point requires x and y.");
|
|
207001
207205
|
if (button !== void 0 && button !== "left" && button !== "middle" && button !== "right") throw new Error("forge_browser_click_point requires button left, middle, or right.");
|
|
207002
207206
|
return toCodexDynamicToolResult$2(await handlers.clickPoint(x, y, button, clickCount, tabId));
|
|
207003
207207
|
}
|
|
207004
207208
|
case "forge_browser_type": {
|
|
207005
|
-
const selector = readString$
|
|
207209
|
+
const selector = readString$3(args, "selector");
|
|
207006
207210
|
if (!selector) throw new Error("forge_browser_type requires a selector.");
|
|
207007
|
-
const text = readString$
|
|
207211
|
+
const text = readString$3(args, "text");
|
|
207008
207212
|
if (text === void 0) throw new Error("forge_browser_type requires text.");
|
|
207009
207213
|
return toCodexDynamicToolResult$2(await handlers.type(selector, text, readBoolean$1(args, "append"), tabId));
|
|
207010
207214
|
}
|
|
207011
207215
|
case "forge_browser_type_text": {
|
|
207012
|
-
const text = readString$
|
|
207216
|
+
const text = readString$3(args, "text");
|
|
207013
207217
|
if (text === void 0) throw new Error("forge_browser_type_text requires text.");
|
|
207014
207218
|
return toCodexDynamicToolResult$2(await handlers.typeText(text, readBoolean$1(args, "pressEnter"), tabId));
|
|
207015
207219
|
}
|
|
207016
207220
|
case "forge_browser_press_key": {
|
|
207017
|
-
const key = readString$
|
|
207221
|
+
const key = readString$3(args, "key");
|
|
207018
207222
|
if (!key) throw new Error("forge_browser_press_key requires a key.");
|
|
207019
207223
|
return toCodexDynamicToolResult$2(await handlers.pressKey(key, tabId));
|
|
207020
207224
|
}
|
|
@@ -207328,7 +207532,7 @@ function createCodexComputerDynamicToolHandler(computerManager) {
|
|
|
207328
207532
|
function readArgs$1(value) {
|
|
207329
207533
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
207330
207534
|
}
|
|
207331
|
-
function readString$
|
|
207535
|
+
function readString$2(args, key) {
|
|
207332
207536
|
const value = args[key];
|
|
207333
207537
|
return typeof value === "string" ? value : void 0;
|
|
207334
207538
|
}
|
|
@@ -207348,7 +207552,7 @@ function readStringArray(args, key) {
|
|
|
207348
207552
|
return value.filter((entry) => typeof entry === "string");
|
|
207349
207553
|
}
|
|
207350
207554
|
function readRequiredString(args, key, toolName) {
|
|
207351
|
-
const value = readString$
|
|
207555
|
+
const value = readString$2(args, key)?.trim();
|
|
207352
207556
|
if (!value) throw new Error(`${toolName} requires ${key}.`);
|
|
207353
207557
|
return value;
|
|
207354
207558
|
}
|
|
@@ -207383,7 +207587,7 @@ async function runGitHubLifecycleTool(toolName, context) {
|
|
|
207383
207587
|
action,
|
|
207384
207588
|
args: lifecycleArgs,
|
|
207385
207589
|
mode
|
|
207386
|
-
}), mode === "write" ? readString$
|
|
207590
|
+
}), mode === "write" ? readString$2(args, "confirmationId") : void 0);
|
|
207387
207591
|
}
|
|
207388
207592
|
//#endregion
|
|
207389
207593
|
//#region src/github/GitHubCodexPullRequestTools.ts
|
|
@@ -207393,8 +207597,8 @@ async function runGitHubPullRequestTool(toolName, context) {
|
|
|
207393
207597
|
case "forge_github_create_pull_request": {
|
|
207394
207598
|
const cwd = readRequiredString(args, "cwd", toolName);
|
|
207395
207599
|
const title = readRequiredString(args, "title", toolName);
|
|
207396
|
-
const body = readString$
|
|
207397
|
-
const headBranch = readString$
|
|
207600
|
+
const body = readString$2(args, "body");
|
|
207601
|
+
const headBranch = readString$2(args, "headBranch")?.trim();
|
|
207398
207602
|
if (body === void 0) throw new Error("forge_github_create_pull_request requires body.");
|
|
207399
207603
|
const request = {
|
|
207400
207604
|
cwd,
|
|
@@ -207403,7 +207607,7 @@ async function runGitHubPullRequestTool(toolName, context) {
|
|
|
207403
207607
|
baseBranch: readRequiredString(args, "baseBranch", toolName),
|
|
207404
207608
|
...headBranch ? { headBranch } : {}
|
|
207405
207609
|
};
|
|
207406
|
-
return runTool(request, () => broker.createPullRequest(auth, request), readString$
|
|
207610
|
+
return runTool(request, () => broker.createPullRequest(auth, request), readString$2(args, "confirmationId"));
|
|
207407
207611
|
}
|
|
207408
207612
|
case "forge_github_get_pull_request": {
|
|
207409
207613
|
const cwd = readRequiredString(args, "cwd", toolName);
|
|
@@ -207425,29 +207629,29 @@ function runStatus$1({ auth, broker, runTool }) {
|
|
|
207425
207629
|
return runTool({}, () => broker.status(auth));
|
|
207426
207630
|
}
|
|
207427
207631
|
function runListRepositories({ args, auth, broker, runTool }) {
|
|
207428
|
-
const search = readString$
|
|
207632
|
+
const search = readString$2(args, "search");
|
|
207429
207633
|
return runTool({ search: search ?? null }, () => broker.listRepositories(auth, { search }));
|
|
207430
207634
|
}
|
|
207431
207635
|
function runCloneRepository({ args, auth, broker, runTool }) {
|
|
207432
207636
|
const toolName = "forge_github_clone_repository";
|
|
207433
207637
|
const repoFullName = readRequiredString(args, "repoFullName", toolName);
|
|
207434
207638
|
const destination = readRequiredString(args, "destination", toolName);
|
|
207435
|
-
const branch = readString$
|
|
207639
|
+
const branch = readString$2(args, "branch");
|
|
207436
207640
|
const request = {
|
|
207437
207641
|
repoFullName,
|
|
207438
207642
|
destination,
|
|
207439
207643
|
...branch ? { branch } : {}
|
|
207440
207644
|
};
|
|
207441
|
-
return runTool(request, () => broker.cloneRepository(auth, request), readString$
|
|
207645
|
+
return runTool(request, () => broker.cloneRepository(auth, request), readString$2(args, "confirmationId"));
|
|
207442
207646
|
}
|
|
207443
207647
|
function runCreateRepository({ args, auth, broker, runTool }) {
|
|
207444
207648
|
const toolName = "forge_github_create_repository";
|
|
207445
207649
|
const owner = readRequiredString(args, "owner", toolName);
|
|
207446
207650
|
const name = readRequiredString(args, "name", toolName);
|
|
207447
|
-
const visibility = readString$
|
|
207651
|
+
const visibility = readString$2(args, "visibility");
|
|
207448
207652
|
if (visibility !== void 0 && visibility !== "private" && visibility !== "public") throw new Error("forge_github_create_repository visibility must be private or public.");
|
|
207449
207653
|
const repositoryVisibility = visibility;
|
|
207450
|
-
const description = readString$
|
|
207654
|
+
const description = readString$2(args, "description");
|
|
207451
207655
|
const request = {
|
|
207452
207656
|
owner,
|
|
207453
207657
|
name,
|
|
@@ -207455,32 +207659,32 @@ function runCreateRepository({ args, auth, broker, runTool }) {
|
|
|
207455
207659
|
...repositoryVisibility ? { visibility: repositoryVisibility } : {},
|
|
207456
207660
|
...description !== void 0 ? { description } : {}
|
|
207457
207661
|
};
|
|
207458
|
-
return runTool(request, () => broker.createRepository(auth, request), readString$
|
|
207662
|
+
return runTool(request, () => broker.createRepository(auth, request), readString$2(args, "confirmationId"));
|
|
207459
207663
|
}
|
|
207460
207664
|
function runLinkProjectRepository({ args, auth, broker, runTool }) {
|
|
207461
207665
|
const toolName = "forge_github_link_project_repository";
|
|
207462
207666
|
const cwd = readRequiredString(args, "cwd", toolName);
|
|
207463
207667
|
const repoFullName = readRequiredString(args, "repoFullName", toolName);
|
|
207464
|
-
const branch = readString$
|
|
207668
|
+
const branch = readString$2(args, "branch");
|
|
207465
207669
|
const request = {
|
|
207466
207670
|
cwd,
|
|
207467
207671
|
repoFullName,
|
|
207468
207672
|
...branch ? { branch } : {}
|
|
207469
207673
|
};
|
|
207470
|
-
return runTool(request, () => broker.linkProjectRepository(auth, request), readString$
|
|
207674
|
+
return runTool(request, () => broker.linkProjectRepository(auth, request), readString$2(args, "confirmationId"));
|
|
207471
207675
|
}
|
|
207472
207676
|
function runPushBranch({ args, auth, broker, runTool }) {
|
|
207473
207677
|
const cwd = readRequiredString(args, "cwd", "forge_github_push_branch");
|
|
207474
|
-
const branch = readString$
|
|
207678
|
+
const branch = readString$2(args, "branch");
|
|
207475
207679
|
const request = {
|
|
207476
207680
|
cwd,
|
|
207477
207681
|
...branch ? { branch } : {}
|
|
207478
207682
|
};
|
|
207479
|
-
return runTool(request, () => broker.pushProjectBranch(auth, request), readString$
|
|
207683
|
+
return runTool(request, () => broker.pushProjectBranch(auth, request), readString$2(args, "confirmationId"));
|
|
207480
207684
|
}
|
|
207481
207685
|
function runRefreshCredentials({ args, auth, broker, runTool }) {
|
|
207482
207686
|
const request = { cwd: readRequiredString(args, "cwd", "forge_github_refresh_credentials") };
|
|
207483
|
-
return runTool(request, () => broker.refreshProjectCredentials(auth, request), readString$
|
|
207687
|
+
return runTool(request, () => broker.refreshProjectCredentials(auth, request), readString$2(args, "confirmationId"));
|
|
207484
207688
|
}
|
|
207485
207689
|
const repositoryToolHandlers = {
|
|
207486
207690
|
forge_github_status: runStatus$1,
|
|
@@ -207518,7 +207722,7 @@ async function runGitHubWorkflowTool(toolName, context) {
|
|
|
207518
207722
|
}
|
|
207519
207723
|
function listWorkflowRuns({ args, auth, broker, runTool }) {
|
|
207520
207724
|
const cwd = readRequiredString(args, "cwd", "forge_github_list_workflow_runs");
|
|
207521
|
-
const branch = readString$
|
|
207725
|
+
const branch = readString$2(args, "branch")?.trim();
|
|
207522
207726
|
const limit = readNumber$1(args, "limit");
|
|
207523
207727
|
const request = {
|
|
207524
207728
|
cwd,
|
|
@@ -207537,7 +207741,7 @@ function watchWorkflowRun({ args, auth, broker, runTool }) {
|
|
|
207537
207741
|
}
|
|
207538
207742
|
function getWorkflowRunLogs({ args, auth, broker, runTool }) {
|
|
207539
207743
|
const { cwd, runId } = readWorkflowRunRequest(args, "forge_github_get_workflow_run_logs");
|
|
207540
|
-
const jobId = readString$
|
|
207744
|
+
const jobId = readString$2(args, "jobId")?.trim();
|
|
207541
207745
|
const request = {
|
|
207542
207746
|
cwd,
|
|
207543
207747
|
runId,
|
|
@@ -207547,16 +207751,16 @@ function getWorkflowRunLogs({ args, auth, broker, runTool }) {
|
|
|
207547
207751
|
}
|
|
207548
207752
|
function downloadWorkflowArtifacts({ args, auth, broker, runTool }) {
|
|
207549
207753
|
const cwd = readRequiredString(args, "cwd", "forge_github_download_workflow_artifacts");
|
|
207550
|
-
const runId = readString$
|
|
207551
|
-
const artifactName = readString$
|
|
207552
|
-
const destinationDir = readString$
|
|
207754
|
+
const runId = readString$2(args, "runId")?.trim();
|
|
207755
|
+
const artifactName = readString$2(args, "artifactName")?.trim();
|
|
207756
|
+
const destinationDir = readString$2(args, "destinationDir")?.trim();
|
|
207553
207757
|
const request = {
|
|
207554
207758
|
cwd,
|
|
207555
207759
|
...runId ? { runId } : {},
|
|
207556
207760
|
...artifactName ? { artifactName } : {},
|
|
207557
207761
|
...destinationDir ? { destinationDir } : {}
|
|
207558
207762
|
};
|
|
207559
|
-
return runTool(request, () => broker.downloadWorkflowArtifacts(auth, request), readString$
|
|
207763
|
+
return runTool(request, () => broker.downloadWorkflowArtifacts(auth, request), readString$2(args, "confirmationId"));
|
|
207560
207764
|
}
|
|
207561
207765
|
function listWorkflows({ args, auth, broker, runTool }) {
|
|
207562
207766
|
const cwd = readRequiredString(args, "cwd", "forge_github_list_workflows");
|
|
@@ -207575,7 +207779,7 @@ function getWorkflow({ args, auth, broker, runTool }) {
|
|
|
207575
207779
|
}
|
|
207576
207780
|
function runWorkflow({ args, auth, broker, runTool }) {
|
|
207577
207781
|
const { cwd, workflow } = readWorkflowRequest(args, "forge_github_run_workflow");
|
|
207578
|
-
const ref = readString$
|
|
207782
|
+
const ref = readString$2(args, "ref")?.trim();
|
|
207579
207783
|
const fields = readStringRecord(args, "fields");
|
|
207580
207784
|
const request = {
|
|
207581
207785
|
cwd,
|
|
@@ -207583,7 +207787,7 @@ function runWorkflow({ args, auth, broker, runTool }) {
|
|
|
207583
207787
|
...ref ? { ref } : {},
|
|
207584
207788
|
...fields && Object.keys(fields).length > 0 ? { fields } : {}
|
|
207585
207789
|
};
|
|
207586
|
-
return runTool(request, () => broker.runWorkflow(auth, request), readString$
|
|
207790
|
+
return runTool(request, () => broker.runWorkflow(auth, request), readString$2(args, "confirmationId"));
|
|
207587
207791
|
}
|
|
207588
207792
|
function enableWorkflow(context) {
|
|
207589
207793
|
return runWorkflowToggle("forge_github_enable_workflow", context, "enableWorkflow");
|
|
@@ -207606,12 +207810,12 @@ function readWorkflowRequest(args, toolName) {
|
|
|
207606
207810
|
async function runWorkflowRunMutation(toolName, context, brokerMethod) {
|
|
207607
207811
|
const { args, auth, broker, runTool } = context;
|
|
207608
207812
|
const request = readWorkflowRunRequest(args, toolName);
|
|
207609
|
-
return runTool(request, () => broker[brokerMethod](auth, request), readString$
|
|
207813
|
+
return runTool(request, () => broker[brokerMethod](auth, request), readString$2(args, "confirmationId"));
|
|
207610
207814
|
}
|
|
207611
207815
|
function runWorkflowToggle(toolName, context, brokerMethod) {
|
|
207612
207816
|
const { args, auth, broker, runTool } = context;
|
|
207613
207817
|
const request = readWorkflowRequest(args, toolName);
|
|
207614
|
-
return runTool(request, () => broker[brokerMethod](auth, request), readString$
|
|
207818
|
+
return runTool(request, () => broker[brokerMethod](auth, request), readString$2(args, "confirmationId"));
|
|
207615
207819
|
}
|
|
207616
207820
|
//#endregion
|
|
207617
207821
|
//#region src/github/GitHubCodexWorkspaceGitTools.ts
|
|
@@ -207628,7 +207832,7 @@ async function runWorkspaceGitTool(toolName, context) {
|
|
|
207628
207832
|
case "forge_git_commit": return runCommit("forge_git_commit", context);
|
|
207629
207833
|
case "forge_git_reset_hard": return runResetHard("forge_git_reset_hard", context);
|
|
207630
207834
|
case "forge_git": {
|
|
207631
|
-
const operation = readString$
|
|
207835
|
+
const operation = readString$2(args, "operation")?.trim();
|
|
207632
207836
|
switch (operation) {
|
|
207633
207837
|
case "status": return runStatus(toolName, context, "status");
|
|
207634
207838
|
case "log": return runLog(toolName, context);
|
|
@@ -207657,7 +207861,7 @@ function runLog(toolName, { args, broker, runTool }) {
|
|
|
207657
207861
|
}
|
|
207658
207862
|
function runStash(toolName, { args, broker, runTool }, operation) {
|
|
207659
207863
|
const cwd = readRequiredString(args, "cwd", toolName);
|
|
207660
|
-
const message = readString$
|
|
207864
|
+
const message = readString$2(args, "message")?.trim();
|
|
207661
207865
|
return runTool({
|
|
207662
207866
|
cwd,
|
|
207663
207867
|
...operation ? { operation } : {},
|
|
@@ -207665,12 +207869,12 @@ function runStash(toolName, { args, broker, runTool }, operation) {
|
|
|
207665
207869
|
}, () => broker.stashWorkspace({
|
|
207666
207870
|
cwd,
|
|
207667
207871
|
...message ? { message } : {}
|
|
207668
|
-
}), readString$
|
|
207872
|
+
}), readString$2(args, "confirmationId"));
|
|
207669
207873
|
}
|
|
207670
207874
|
function runCommit(toolName, { args, broker, runTool }, operation) {
|
|
207671
207875
|
const cwd = readRequiredString(args, "cwd", toolName);
|
|
207672
207876
|
const message = readRequiredString(args, "message", operation ? `${toolName} operation=commit` : toolName);
|
|
207673
|
-
const branch = readString$
|
|
207877
|
+
const branch = readString$2(args, "branch")?.trim();
|
|
207674
207878
|
return runTool({
|
|
207675
207879
|
cwd,
|
|
207676
207880
|
...operation ? { operation } : {},
|
|
@@ -207680,11 +207884,11 @@ function runCommit(toolName, { args, broker, runTool }, operation) {
|
|
|
207680
207884
|
cwd,
|
|
207681
207885
|
message,
|
|
207682
207886
|
...branch ? { branch } : {}
|
|
207683
|
-
}), readString$
|
|
207887
|
+
}), readString$2(args, "confirmationId"));
|
|
207684
207888
|
}
|
|
207685
207889
|
function runResetHard(toolName, { args, broker, runTool }, operation) {
|
|
207686
207890
|
const cwd = readRequiredString(args, "cwd", toolName);
|
|
207687
|
-
const ref = readString$
|
|
207891
|
+
const ref = readString$2(args, "ref")?.trim();
|
|
207688
207892
|
return runTool({
|
|
207689
207893
|
cwd,
|
|
207690
207894
|
...operation ? { operation } : {},
|
|
@@ -207692,7 +207896,7 @@ function runResetHard(toolName, { args, broker, runTool }, operation) {
|
|
|
207692
207896
|
}, () => broker.resetWorkspaceHard({
|
|
207693
207897
|
cwd,
|
|
207694
207898
|
...ref ? { ref } : {}
|
|
207695
|
-
}), readString$
|
|
207899
|
+
}), readString$2(args, "confirmationId"));
|
|
207696
207900
|
}
|
|
207697
207901
|
//#endregion
|
|
207698
207902
|
//#region src/github/GitHubCodexToolRunner.ts
|
|
@@ -208326,7 +208530,7 @@ function createCodexGitHubDynamicToolHandler(options = {}) {
|
|
|
208326
208530
|
error: "Forge could not resolve the authenticated user session for this thread. Send a new turn from the Forge UI and retry."
|
|
208327
208531
|
}, false);
|
|
208328
208532
|
const args = readArgs$1(input.arguments);
|
|
208329
|
-
const dispatchOperation = input.tool === "forge_git" ? readString$
|
|
208533
|
+
const dispatchOperation = input.tool === "forge_git" ? readString$2(args, "operation")?.trim() : void 0;
|
|
208330
208534
|
const operation = dispatchOperation ?? operationForTool(input.tool);
|
|
208331
208535
|
const requiresConfirmation = !bypassWriteConfirmation && (isGitHubWriteTool(input.tool) || dispatchOperation !== void 0 && FORGE_GIT_WRITE_OPERATIONS.has(dispatchOperation));
|
|
208332
208536
|
const runTool = async (request, execute, confirmationId) => {
|
|
@@ -208848,7 +209052,7 @@ function createCodexDevServerDynamicTools() {
|
|
|
208848
209052
|
function readArgs(value) {
|
|
208849
209053
|
return value && typeof value === "object" ? value : {};
|
|
208850
209054
|
}
|
|
208851
|
-
function readString(args, key) {
|
|
209055
|
+
function readString$1(args, key) {
|
|
208852
209056
|
const value = args[key];
|
|
208853
209057
|
return typeof value === "string" && value.trim().length > 0 ? value : void 0;
|
|
208854
209058
|
}
|
|
@@ -208861,8 +209065,8 @@ function readBoolean(args, key) {
|
|
|
208861
209065
|
return typeof value === "boolean" ? value : void 0;
|
|
208862
209066
|
}
|
|
208863
209067
|
function runDevServerStart(handlers, args) {
|
|
208864
|
-
const command = readString(args, "command");
|
|
208865
|
-
const cwd = readString(args, "cwd");
|
|
209068
|
+
const command = readString$1(args, "command");
|
|
209069
|
+
const cwd = readString$1(args, "cwd");
|
|
208866
209070
|
const port = readNumber(args, "port");
|
|
208867
209071
|
const readyTimeoutMs = readNumber(args, "readyTimeoutMs");
|
|
208868
209072
|
const openPreview = readBoolean(args, "openPreview");
|
|
@@ -209018,7 +209222,7 @@ function readConnectorParams(tool, args) {
|
|
|
209018
209222
|
switch (tool.tool) {
|
|
209019
209223
|
case "forge_gmail_search": return {
|
|
209020
209224
|
...params,
|
|
209021
|
-
query: readString$
|
|
209225
|
+
query: readString$2(args, "query") ?? ""
|
|
209022
209226
|
};
|
|
209023
209227
|
case "forge_gmail_list_recent": return {
|
|
209024
209228
|
...params,
|
|
@@ -209030,7 +209234,7 @@ function readConnectorParams(tool, args) {
|
|
|
209030
209234
|
}
|
|
209031
209235
|
}
|
|
209032
209236
|
function appendString(target, args, key) {
|
|
209033
|
-
const value = readString$
|
|
209237
|
+
const value = readString$2(args, key);
|
|
209034
209238
|
if (value !== void 0) target[key] = value;
|
|
209035
209239
|
}
|
|
209036
209240
|
function appendNumber(target, args, key) {
|
|
@@ -209619,7 +209823,7 @@ const collectStreamAsString = (stream) => stream.pipe(require_Path.decodeText(),
|
|
|
209619
209823
|
//#region src/orchestration/WorkerPlanCodexTools.ts
|
|
209620
209824
|
const FORGE_PROPOSE_WORKER_TASKS_TOOL = "forge_propose_worker_tasks";
|
|
209621
209825
|
const MAX_WORKER_PLAN_ITEMS = 5;
|
|
209622
|
-
function asRecord$
|
|
209826
|
+
function asRecord$10(value) {
|
|
209623
209827
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
209624
209828
|
}
|
|
209625
209829
|
function readTrimmedString(value) {
|
|
@@ -209635,7 +209839,7 @@ function isValidWorkerSummary(value) {
|
|
|
209635
209839
|
return value !== void 0 && value.length > 0 && value.length <= 4e3;
|
|
209636
209840
|
}
|
|
209637
209841
|
function decodeWorkerPlanItem(value) {
|
|
209638
|
-
const item = asRecord$
|
|
209842
|
+
const item = asRecord$10(value);
|
|
209639
209843
|
const slug = readTrimmedString(item?.slug);
|
|
209640
209844
|
const title = readTrimmedString(item?.title);
|
|
209641
209845
|
const summary = readTrimmedString(item?.summary);
|
|
@@ -209699,7 +209903,7 @@ function createWorkerPlanDynamicTools() {
|
|
|
209699
209903
|
function createWorkerPlanDynamicToolHandler() {
|
|
209700
209904
|
return async (input) => {
|
|
209701
209905
|
if (input.tool !== "forge_propose_worker_tasks") throw new Error(`Unsupported worker-plan tool: ${input.tool}`);
|
|
209702
|
-
if (!decodeWorkerPlanItems(asRecord$
|
|
209906
|
+
if (!decodeWorkerPlanItems(asRecord$10(input.arguments)?.items)) throw new Error("Worker plans require one to five unique, fully described items.");
|
|
209703
209907
|
return {
|
|
209704
209908
|
success: true,
|
|
209705
209909
|
contentItems: [{
|
|
@@ -211192,6 +211396,374 @@ function withoutInheritedAnthropicCredentials(env, options) {
|
|
|
211192
211396
|
return contained;
|
|
211193
211397
|
}
|
|
211194
211398
|
//#endregion
|
|
211399
|
+
//#region src/provider/personalClaudeInVm.ts
|
|
211400
|
+
/**
|
|
211401
|
+
* CMMD writes this marker when it activates a personal Claude subscription
|
|
211402
|
+
* inside a running sprite. Activation always happens after boot, so a
|
|
211403
|
+
* boot-time env var alone cannot reflect it (FOR-36249).
|
|
211404
|
+
*
|
|
211405
|
+
* CMMD names the file in the Service env (FORGE_PERSONAL_CLAUDE_MARKER_PATH).
|
|
211406
|
+
* Without that, the marker sits directly under FORGE_HOME, which is the same
|
|
211407
|
+
* guest home CMMD derives every sprite path from. A server with neither has no
|
|
211408
|
+
* sprite home to look in, and a desktop never has a marker, so it reads none.
|
|
211409
|
+
*/
|
|
211410
|
+
const PERSONAL_CLAUDE_IN_VM_MARKER_NAME = ".forge-personal-claude-in-vm";
|
|
211411
|
+
const PERSONAL_CLAUDE_GENERATION_FILE_NAME = ".forge-personal-claude-generation";
|
|
211412
|
+
function markerPath(env) {
|
|
211413
|
+
if (env.FORGE_PERSONAL_CLAUDE_MARKER_PATH) return env.FORGE_PERSONAL_CLAUDE_MARKER_PATH;
|
|
211414
|
+
if (env.FORGE_HOME) return (0, node_path.join)(env.FORGE_HOME, PERSONAL_CLAUDE_IN_VM_MARKER_NAME);
|
|
211415
|
+
return null;
|
|
211416
|
+
}
|
|
211417
|
+
/**
|
|
211418
|
+
* True when this runtime should offer the personal Claude subscription lane.
|
|
211419
|
+
*
|
|
211420
|
+
* Accepts either signal: the boot-time env var (backward compatible) or the
|
|
211421
|
+
* activation marker on disk. Reading the marker at call time lets the provider
|
|
211422
|
+
* catalog and the per-turn readiness guard self-heal once CMMD delivers,
|
|
211423
|
+
* without a sprite reboot. Callers still verify the credential is non-empty
|
|
211424
|
+
* before treating it as usable; this only decides whether the lane is active.
|
|
211425
|
+
*/
|
|
211426
|
+
function personalClaudeInVmEnabled(env = process.env) {
|
|
211427
|
+
if (env.FORGE_PERSONAL_CLAUDE_IN_VM === "true") return true;
|
|
211428
|
+
const marker = markerPath(env);
|
|
211429
|
+
if (!marker) return false;
|
|
211430
|
+
try {
|
|
211431
|
+
return (0, node_fs.existsSync)(marker);
|
|
211432
|
+
} catch {
|
|
211433
|
+
return false;
|
|
211434
|
+
}
|
|
211435
|
+
}
|
|
211436
|
+
/** Exported at boot. This is the restart proof CMMD waits on, so it must never read the file. */
|
|
211437
|
+
function bootExportedPersonalClaudeGenerationHash(env = process.env) {
|
|
211438
|
+
return env.FORGE_PERSONAL_CLAUDE_GENERATION_HASH?.trim() ?? "";
|
|
211439
|
+
}
|
|
211440
|
+
/**
|
|
211441
|
+
* The sha256 of the Claude credential generation this runtime holds, or "" when
|
|
211442
|
+
* it holds none. The readiness guard compares it with the generation CMMD
|
|
211443
|
+
* applied. The restart proof must not use it; it uses the boot-exported hash.
|
|
211444
|
+
*
|
|
211445
|
+
* The boot preload exports FORGE_PERSONAL_CLAUDE_GENERATION_HASH, but only when
|
|
211446
|
+
* the marker already exists at boot. Activation in place writes the generation
|
|
211447
|
+
* file and touches the marker without setting any env, so an empty variable
|
|
211448
|
+
* falls back to that file, hashed the way the preload hashes it. CMMD puts the
|
|
211449
|
+
* file beside the marker in the guest home and names no path for it. A marker
|
|
211450
|
+
* with no generation file is a legacy runtime, and gets no hash here either.
|
|
211451
|
+
*/
|
|
211452
|
+
function personalClaudeGenerationHash(env = process.env) {
|
|
211453
|
+
const exported = bootExportedPersonalClaudeGenerationHash(env);
|
|
211454
|
+
if (exported) return exported;
|
|
211455
|
+
const marker = markerPath(env);
|
|
211456
|
+
if (!marker) return "";
|
|
211457
|
+
try {
|
|
211458
|
+
if (!(0, node_fs.existsSync)(marker)) return "";
|
|
211459
|
+
const generation = (0, node_fs.readFileSync)((0, node_path.join)((0, node_path.dirname)(marker), PERSONAL_CLAUDE_GENERATION_FILE_NAME), "utf8").trim();
|
|
211460
|
+
return generation ? (0, node_crypto.createHash)("sha256").update(generation).digest("hex") : "";
|
|
211461
|
+
} catch {
|
|
211462
|
+
return "";
|
|
211463
|
+
}
|
|
211464
|
+
}
|
|
211465
|
+
//#endregion
|
|
211466
|
+
//#region src/provider/claudeCredentialDirectory.ts
|
|
211467
|
+
/**
|
|
211468
|
+
* Per-account credential directories for Claude turns.
|
|
211469
|
+
*
|
|
211470
|
+
* Claude Code keeps conversation transcripts under
|
|
211471
|
+
* `${CLAUDE_CONFIG_DIR}/projects/<cwd-slug>/<sessionId>.jsonl` and its login
|
|
211472
|
+
* under `.credentials.json`. `CLAUDE_SECURESTORAGE_CONFIG_DIR` moves only the
|
|
211473
|
+
* credential file, so each Provider Account can hold its own login while every
|
|
211474
|
+
* session file stays in one place and `--resume` keeps finding its target. See
|
|
211475
|
+
* `docs/adr/claude-accounts-split-credentials-not-sessions.md`.
|
|
211476
|
+
*
|
|
211477
|
+
* That variable is not documented. Under
|
|
211478
|
+
* `docs/adr/unpublished-provider-surface-needs-a-fallback.md` the dependency is
|
|
211479
|
+
* only allowed because absence degrades to a named behaviour that already
|
|
211480
|
+
* works: one account per Environment, exactly what runs today. This module is
|
|
211481
|
+
* the gate that keeps the degrade honest. It sets the variable only when a
|
|
211482
|
+
* readable `.credentials.json` is already sitting in the resolved directory,
|
|
211483
|
+
* because a missing or empty directory makes the CLI report logged out and a
|
|
211484
|
+
* live turn would fail. Nothing writes these directories yet, so every runtime
|
|
211485
|
+
* today takes the skip path.
|
|
211486
|
+
*
|
|
211487
|
+
* Pure and dependency-free on purpose, for the same reason as
|
|
211488
|
+
* `claudeCredentialContainment.ts`: ClaudeAdapter drags in Effect platform
|
|
211489
|
+
* services, the browser manager and the server config, so a unit test that
|
|
211490
|
+
* imports it cannot run in a worktree. This rule decides which account pays for
|
|
211491
|
+
* a turn, so it has to stay directly testable.
|
|
211492
|
+
*/
|
|
211493
|
+
/** Repoints `.credentials.json` without moving the transcripts. */
|
|
211494
|
+
const CLAUDE_SECURESTORAGE_CONFIG_DIR_ENV = "CLAUDE_SECURESTORAGE_CONFIG_DIR";
|
|
211495
|
+
/** The one file Claude Code writes into the credential directory. */
|
|
211496
|
+
const CLAUDE_CREDENTIAL_FILE_NAME = ".credentials.json";
|
|
211497
|
+
/**
|
|
211498
|
+
* Runtime env var naming the Provider Account this runtime's Claude turns
|
|
211499
|
+
* belong to.
|
|
211500
|
+
*
|
|
211501
|
+
* NOTHING SETS THIS. It has no writer anywhere in Forge or CMMD, so as shipped
|
|
211502
|
+
* the per-account lane can never turn on and every runtime keeps today's single
|
|
211503
|
+
* account. The writer waits on issue 3073, which decides which identity keys
|
|
211504
|
+
* the directory. That has to be settled first: the credential generation
|
|
211505
|
+
* rotates on every re-delivery, so keying on it would give a new directory
|
|
211506
|
+
* each time and defeat the point.
|
|
211507
|
+
*
|
|
211508
|
+
* It is also not the seam. The seam is the `credentialDirectory` parameter on
|
|
211509
|
+
* `ClaudeAdapter.buildClaudeQueryEnv`, which is what tests and callers drive.
|
|
211510
|
+
* This variable is one bridge into that parameter, and the delivery step is
|
|
211511
|
+
* free to replace it with something better rather than adopt it.
|
|
211512
|
+
*/
|
|
211513
|
+
const CLAUDE_PROVIDER_ACCOUNT_IDENTITY_ENV = "FORGE_CLAUDE_PROVIDER_ACCOUNT_IDENTITY";
|
|
211514
|
+
/** Operator escape hatch for the directory root, mostly for local debugging. */
|
|
211515
|
+
const CLAUDE_CREDENTIAL_ROOT_ENV = "FORGE_CLAUDE_CREDENTIAL_ROOT";
|
|
211516
|
+
const CREDENTIAL_ROOT_DIRECTORY_NAME = "claude-credentials";
|
|
211517
|
+
const FALLBACK_ROOT_DIRECTORY_NAME = ".forge-claude-credentials";
|
|
211518
|
+
const DEFAULT_CLAUDE_CONFIG_DIRECTORY_NAME = ".claude";
|
|
211519
|
+
const MAX_ACCOUNT_IDENTITY_LENGTH = 256;
|
|
211520
|
+
const DIRECTORY_NAME_HASH_LENGTH = 32;
|
|
211521
|
+
/**
|
|
211522
|
+
* Directory name for an account. A hash, so no email or display name reaches a
|
|
211523
|
+
* filesystem path and no path-illegal character can appear in one either.
|
|
211524
|
+
*/
|
|
211525
|
+
function claudeCredentialDirectoryName(accountIdentity) {
|
|
211526
|
+
return `account-${(0, node_crypto.createHash)("sha256").update(accountIdentity, "utf8").digest("hex").slice(0, DIRECTORY_NAME_HASH_LENGTH)}`;
|
|
211527
|
+
}
|
|
211528
|
+
/** The account this runtime's Claude turns belong to, or null on every runtime today. */
|
|
211529
|
+
function resolveClaudeProviderAccountIdentity(env) {
|
|
211530
|
+
const identity = env[CLAUDE_PROVIDER_ACCOUNT_IDENTITY_ENV]?.trim();
|
|
211531
|
+
if (!identity || identity.length > MAX_ACCOUNT_IDENTITY_LENGTH) return null;
|
|
211532
|
+
return identity;
|
|
211533
|
+
}
|
|
211534
|
+
/**
|
|
211535
|
+
* Where per-account credential directories live. Always outside
|
|
211536
|
+
* `CLAUDE_CONFIG_DIR`, so one of them can never be mistaken for the main
|
|
211537
|
+
* credential store, and so a stray read of the config directory cannot walk
|
|
211538
|
+
* into another account's login.
|
|
211539
|
+
*/
|
|
211540
|
+
function claudeCredentialDirectoryRoot(env) {
|
|
211541
|
+
const root = credentialRootCandidate(env);
|
|
211542
|
+
if (!root || !(0, node_path.isAbsolute)(root)) return null;
|
|
211543
|
+
return isInside(root, claudeConfigDirectory(env)) ? null : (0, node_path.resolve)(root);
|
|
211544
|
+
}
|
|
211545
|
+
/** Absolute credential directory for one account, or null when either half is missing. */
|
|
211546
|
+
function resolveClaudeCredentialDirectory(input) {
|
|
211547
|
+
if (!input.accountIdentity) return null;
|
|
211548
|
+
const root = claudeCredentialDirectoryRoot(input.env);
|
|
211549
|
+
if (!root) return null;
|
|
211550
|
+
return (0, node_path.join)(root, claudeCredentialDirectoryName(input.accountIdentity));
|
|
211551
|
+
}
|
|
211552
|
+
/**
|
|
211553
|
+
* Decide whether this turn may point at a per-account credential directory.
|
|
211554
|
+
*
|
|
211555
|
+
* The cheap checks run first and the capability probe runs last, so a runtime
|
|
211556
|
+
* with no delivered credential never spawns the CLI. Any failure is a skip, so
|
|
211557
|
+
* the worst outcome is today's behaviour.
|
|
211558
|
+
*/
|
|
211559
|
+
async function decideClaudeCredentialDirectory(input) {
|
|
211560
|
+
if (!input.accountIdentity) return skip("no-account");
|
|
211561
|
+
if (input.env["CLAUDE_SECURESTORAGE_CONFIG_DIR"]?.trim()) return skip("operator-override");
|
|
211562
|
+
const directory = resolveClaudeCredentialDirectory(input);
|
|
211563
|
+
if (!directory) return skip("no-root");
|
|
211564
|
+
if (!(input.credentialFilePresent ?? hasReadableCredentialFile)(directory)) return skip("no-credential-file");
|
|
211565
|
+
return await probeSupportSafely(input.probeSupport) === "supported" ? {
|
|
211566
|
+
outcome: "set",
|
|
211567
|
+
directory
|
|
211568
|
+
} : skip("capability-not-supported");
|
|
211569
|
+
}
|
|
211570
|
+
/** True when `.credentials.json` is in the directory and readable right now. */
|
|
211571
|
+
function hasReadableCredentialFile(directory) {
|
|
211572
|
+
const file = (0, node_path.join)(directory, CLAUDE_CREDENTIAL_FILE_NAME);
|
|
211573
|
+
try {
|
|
211574
|
+
if (!(0, node_fs.statSync)(file).isFile()) return false;
|
|
211575
|
+
(0, node_fs.accessSync)(file, node_fs.constants.R_OK);
|
|
211576
|
+
return true;
|
|
211577
|
+
} catch {
|
|
211578
|
+
return false;
|
|
211579
|
+
}
|
|
211580
|
+
}
|
|
211581
|
+
function skip(reason) {
|
|
211582
|
+
return {
|
|
211583
|
+
outcome: "skipped",
|
|
211584
|
+
reason
|
|
211585
|
+
};
|
|
211586
|
+
}
|
|
211587
|
+
/** `inconclusive` is treated as `unsupported`, and so is a probe that throws. */
|
|
211588
|
+
async function probeSupportSafely(probeSupport) {
|
|
211589
|
+
try {
|
|
211590
|
+
return await probeSupport();
|
|
211591
|
+
} catch {
|
|
211592
|
+
return "inconclusive";
|
|
211593
|
+
}
|
|
211594
|
+
}
|
|
211595
|
+
function credentialRootCandidate(env) {
|
|
211596
|
+
const explicit = env[CLAUDE_CREDENTIAL_ROOT_ENV]?.trim();
|
|
211597
|
+
if (explicit) return explicit;
|
|
211598
|
+
const forgeHome = env.FORGE_HOME?.trim();
|
|
211599
|
+
if (forgeHome) return (0, node_path.join)(forgeHome, CREDENTIAL_ROOT_DIRECTORY_NAME);
|
|
211600
|
+
try {
|
|
211601
|
+
return (0, node_path.join)((0, node_os.homedir)(), FALLBACK_ROOT_DIRECTORY_NAME);
|
|
211602
|
+
} catch {
|
|
211603
|
+
return null;
|
|
211604
|
+
}
|
|
211605
|
+
}
|
|
211606
|
+
function claudeConfigDirectory(env) {
|
|
211607
|
+
const configured = env.CLAUDE_CONFIG_DIR?.trim();
|
|
211608
|
+
if (configured) return configured;
|
|
211609
|
+
try {
|
|
211610
|
+
return (0, node_path.join)((0, node_os.homedir)(), DEFAULT_CLAUDE_CONFIG_DIRECTORY_NAME);
|
|
211611
|
+
} catch {
|
|
211612
|
+
return null;
|
|
211613
|
+
}
|
|
211614
|
+
}
|
|
211615
|
+
function isInside(candidate, parent) {
|
|
211616
|
+
if (!parent) return false;
|
|
211617
|
+
const step = (0, node_path.relative)((0, node_path.resolve)(parent), (0, node_path.resolve)(candidate));
|
|
211618
|
+
return step === "" || !step.startsWith("..") && !(0, node_path.isAbsolute)(step);
|
|
211619
|
+
}
|
|
211620
|
+
//#endregion
|
|
211621
|
+
//#region src/provider/claudeCredentialDirectoryProbe.ts
|
|
211622
|
+
/**
|
|
211623
|
+
* Capability probe for `CLAUDE_SECURESTORAGE_CONFIG_DIR`.
|
|
211624
|
+
*
|
|
211625
|
+
* The variable is unpublished, so a runtime is asked rather than assumed to
|
|
211626
|
+
* honour it (`docs/adr/unpublished-provider-surface-needs-a-fallback.md`). The
|
|
211627
|
+
* question the probe can actually answer: point the variable at an empty
|
|
211628
|
+
* directory and a runtime that honours it flips from logged in to logged out.
|
|
211629
|
+
* A runtime with no credential at all is logged out either way and cannot
|
|
211630
|
+
* discriminate, which is `inconclusive`, and callers read that as unsupported.
|
|
211631
|
+
*
|
|
211632
|
+
* `claude auth status` is a pure read. It sends no prompt and leaves
|
|
211633
|
+
* `.credentials.json` byte-identical, so probing spends no subscription usage.
|
|
211634
|
+
* It prints JSON on stdout and exits 1 when logged out, so the exit code is not
|
|
211635
|
+
* a failure signal and the `loggedIn` field is what gets read.
|
|
211636
|
+
*/
|
|
211637
|
+
const execFileAsync$3 = (0, node_util.promisify)(node_child_process.execFile);
|
|
211638
|
+
const AUTH_STATUS_ARGS = ["auth", "status"];
|
|
211639
|
+
const VERSION_ARGS = ["--version"];
|
|
211640
|
+
/**
|
|
211641
|
+
* The probe sits in front of a turn, so a provider process that never answers
|
|
211642
|
+
* must not hold that turn open. `execFile` kills the child at this bound, and
|
|
211643
|
+
* `runBounded` gives up on it at the same bound whatever the deps do, so a
|
|
211644
|
+
* hung or unkillable process costs a few seconds and an `inconclusive`.
|
|
211645
|
+
*/
|
|
211646
|
+
const PROBE_TIMEOUT_MS = 5e3;
|
|
211647
|
+
const PROBE_DIRECTORY_PREFIX = "forge-claude-capability-";
|
|
211648
|
+
const VERSION_PATTERN = /\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?/;
|
|
211649
|
+
function makeClaudeCredentialDirectoryProbe(deps) {
|
|
211650
|
+
const answers = /* @__PURE__ */ new Map();
|
|
211651
|
+
return { support: async () => {
|
|
211652
|
+
const version = await readVersionSafely(deps);
|
|
211653
|
+
if (version === null) return runProbe(deps);
|
|
211654
|
+
const cached = answers.get(version);
|
|
211655
|
+
if (cached) return cached;
|
|
211656
|
+
const pending = runProbe(deps);
|
|
211657
|
+
answers.set(version, pending);
|
|
211658
|
+
return pending;
|
|
211659
|
+
} };
|
|
211660
|
+
}
|
|
211661
|
+
/**
|
|
211662
|
+
* One probe per binary path for the life of the process. The probe spawns the
|
|
211663
|
+
* CLI twice, and every Claude session start would otherwise pay for it.
|
|
211664
|
+
*/
|
|
211665
|
+
const probesByBinaryPath = /* @__PURE__ */ new Map();
|
|
211666
|
+
function claudeCredentialDirectoryProbeFor(binaryPath) {
|
|
211667
|
+
const binary = binaryPath?.trim() || "claude";
|
|
211668
|
+
const existing = probesByBinaryPath.get(binary);
|
|
211669
|
+
if (existing) return existing;
|
|
211670
|
+
const probe = makeClaudeCredentialDirectoryProbe(claudeCredentialDirectoryProbeDeps(binary));
|
|
211671
|
+
probesByBinaryPath.set(binary, probe);
|
|
211672
|
+
return probe;
|
|
211673
|
+
}
|
|
211674
|
+
function claudeCredentialDirectoryProbeDeps(binaryPath) {
|
|
211675
|
+
return {
|
|
211676
|
+
runAuthStatus: ({ credentialDirectory }) => runClaudeCommand$1(binaryPath, AUTH_STATUS_ARGS, credentialDirectory),
|
|
211677
|
+
readBinaryVersion: async () => {
|
|
211678
|
+
const { stdout } = await runClaudeCommand$1(binaryPath, VERSION_ARGS, null);
|
|
211679
|
+
return VERSION_PATTERN.exec(stdout)?.[0] ?? null;
|
|
211680
|
+
},
|
|
211681
|
+
makeProbeDirectory: () => (0, node_fs_promises.mkdtemp)((0, node_path.join)((0, node_os.tmpdir)(), PROBE_DIRECTORY_PREFIX)),
|
|
211682
|
+
removeProbeDirectory: (directory) => (0, node_fs_promises.rm)(directory, {
|
|
211683
|
+
recursive: true,
|
|
211684
|
+
force: true
|
|
211685
|
+
})
|
|
211686
|
+
};
|
|
211687
|
+
}
|
|
211688
|
+
async function runProbe(deps) {
|
|
211689
|
+
let probeDirectory = null;
|
|
211690
|
+
try {
|
|
211691
|
+
if (parseLoggedIn(await runBounded(deps, () => deps.runAuthStatus({ credentialDirectory: null }))) !== true) return "inconclusive";
|
|
211692
|
+
probeDirectory = await deps.makeProbeDirectory();
|
|
211693
|
+
const directory = probeDirectory;
|
|
211694
|
+
const redirected = parseLoggedIn(await runBounded(deps, () => deps.runAuthStatus({ credentialDirectory: directory })));
|
|
211695
|
+
if (redirected === false) return "supported";
|
|
211696
|
+
return redirected === true ? "unsupported" : "inconclusive";
|
|
211697
|
+
} catch {
|
|
211698
|
+
return "inconclusive";
|
|
211699
|
+
} finally {
|
|
211700
|
+
if (probeDirectory !== null) await deps.removeProbeDirectory(probeDirectory).catch(() => void 0);
|
|
211701
|
+
}
|
|
211702
|
+
}
|
|
211703
|
+
async function readVersionSafely(deps) {
|
|
211704
|
+
try {
|
|
211705
|
+
return await runBounded(deps, () => deps.readBinaryVersion());
|
|
211706
|
+
} catch {
|
|
211707
|
+
return null;
|
|
211708
|
+
}
|
|
211709
|
+
}
|
|
211710
|
+
/**
|
|
211711
|
+
* Give up on a call that outlives the bound. The underlying promise is left to
|
|
211712
|
+
* settle on its own; the real deps hand `execFile` the same bound, so its child
|
|
211713
|
+
* is already being killed by the time this rejects.
|
|
211714
|
+
*/
|
|
211715
|
+
async function runBounded(deps, operation) {
|
|
211716
|
+
const timeoutMs = deps.timeoutMs ?? PROBE_TIMEOUT_MS;
|
|
211717
|
+
let timer;
|
|
211718
|
+
try {
|
|
211719
|
+
return await Promise.race([operation(), new Promise((_resolve, reject) => {
|
|
211720
|
+
timer = setTimeout(() => reject(/* @__PURE__ */ new Error("claude auth status timed out")), timeoutMs);
|
|
211721
|
+
})]);
|
|
211722
|
+
} finally {
|
|
211723
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
211724
|
+
}
|
|
211725
|
+
}
|
|
211726
|
+
/** The `loggedIn` field, or null when the output does not carry one. */
|
|
211727
|
+
function parseLoggedIn(run) {
|
|
211728
|
+
const parsed = parseJsonObject$1(run.stdout);
|
|
211729
|
+
return typeof parsed?.loggedIn === "boolean" ? parsed.loggedIn : null;
|
|
211730
|
+
}
|
|
211731
|
+
function parseJsonObject$1(output) {
|
|
211732
|
+
const trimmed = output.trim();
|
|
211733
|
+
const start = trimmed.indexOf("{");
|
|
211734
|
+
const end = trimmed.lastIndexOf("}");
|
|
211735
|
+
if (start === -1 || end <= start) return null;
|
|
211736
|
+
try {
|
|
211737
|
+
const parsed = JSON.parse(trimmed.slice(start, end + 1));
|
|
211738
|
+
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : null;
|
|
211739
|
+
} catch {
|
|
211740
|
+
return null;
|
|
211741
|
+
}
|
|
211742
|
+
}
|
|
211743
|
+
async function runClaudeCommand$1(binaryPath, args, credentialDirectory) {
|
|
211744
|
+
try {
|
|
211745
|
+
const { stdout } = await execFileAsync$3(binaryPath, [...args], {
|
|
211746
|
+
env: {
|
|
211747
|
+
...process.env,
|
|
211748
|
+
...credentialDirectory ? { [CLAUDE_SECURESTORAGE_CONFIG_DIR_ENV]: credentialDirectory } : {}
|
|
211749
|
+
},
|
|
211750
|
+
maxBuffer: 1024 * 1024,
|
|
211751
|
+
timeout: PROBE_TIMEOUT_MS
|
|
211752
|
+
});
|
|
211753
|
+
return {
|
|
211754
|
+
stdout,
|
|
211755
|
+
exitCode: 0
|
|
211756
|
+
};
|
|
211757
|
+
} catch (cause) {
|
|
211758
|
+
const failure = cause;
|
|
211759
|
+
if (typeof failure.stdout !== "string") throw cause;
|
|
211760
|
+
return {
|
|
211761
|
+
stdout: failure.stdout,
|
|
211762
|
+
exitCode: typeof failure.code === "number" ? failure.code : null
|
|
211763
|
+
};
|
|
211764
|
+
}
|
|
211765
|
+
}
|
|
211766
|
+
//#endregion
|
|
211195
211767
|
//#region src/provider/makeManagedServerProvider.ts
|
|
211196
211768
|
const makeManagedServerProvider = require_Schema$1.fn("makeManagedServerProvider")(function* (input) {
|
|
211197
211769
|
const refreshSemaphore = yield* require_Path.make$5(1);
|
|
@@ -211283,43 +211855,6 @@ const makeProviderAuthProvenance = (input) => {
|
|
|
211283
211855
|
};
|
|
211284
211856
|
};
|
|
211285
211857
|
//#endregion
|
|
211286
|
-
//#region src/provider/personalClaudeInVm.ts
|
|
211287
|
-
/**
|
|
211288
|
-
* CMMD writes this marker when it activates a personal Claude subscription
|
|
211289
|
-
* inside a running sprite. Activation always happens after boot, so a
|
|
211290
|
-
* boot-time env var alone cannot reflect it (FOR-36249).
|
|
211291
|
-
*
|
|
211292
|
-
* CMMD names the file in the Service env (FORGE_PERSONAL_CLAUDE_MARKER_PATH).
|
|
211293
|
-
* Without that, the marker sits directly under FORGE_HOME, which is the same
|
|
211294
|
-
* guest home CMMD derives every sprite path from. A server with neither has no
|
|
211295
|
-
* sprite home to look in, and a desktop never has a marker, so it reads none.
|
|
211296
|
-
*/
|
|
211297
|
-
const PERSONAL_CLAUDE_IN_VM_MARKER_NAME = ".forge-personal-claude-in-vm";
|
|
211298
|
-
function markerPath(env) {
|
|
211299
|
-
if (env.FORGE_PERSONAL_CLAUDE_MARKER_PATH) return env.FORGE_PERSONAL_CLAUDE_MARKER_PATH;
|
|
211300
|
-
if (env.FORGE_HOME) return (0, node_path.join)(env.FORGE_HOME, PERSONAL_CLAUDE_IN_VM_MARKER_NAME);
|
|
211301
|
-
return null;
|
|
211302
|
-
}
|
|
211303
|
-
/**
|
|
211304
|
-
* True when this runtime should offer the personal Claude subscription lane.
|
|
211305
|
-
*
|
|
211306
|
-
* Accepts either signal: the boot-time env var (backward compatible) or the
|
|
211307
|
-
* activation marker on disk. Reading the marker at call time lets the provider
|
|
211308
|
-
* catalog and the per-turn readiness guard self-heal once CMMD delivers,
|
|
211309
|
-
* without a sprite reboot. Callers still verify the credential is non-empty
|
|
211310
|
-
* before treating it as usable; this only decides whether the lane is active.
|
|
211311
|
-
*/
|
|
211312
|
-
function personalClaudeInVmEnabled(env = process.env) {
|
|
211313
|
-
if (env.FORGE_PERSONAL_CLAUDE_IN_VM === "true") return true;
|
|
211314
|
-
const marker = markerPath(env);
|
|
211315
|
-
if (!marker) return false;
|
|
211316
|
-
try {
|
|
211317
|
-
return (0, node_fs.existsSync)(marker);
|
|
211318
|
-
} catch {
|
|
211319
|
-
return false;
|
|
211320
|
-
}
|
|
211321
|
-
}
|
|
211322
|
-
//#endregion
|
|
211323
211858
|
//#region src/provider/Layers/ClaudeProvider.ts
|
|
211324
211859
|
function endpointProfileDisplayNames(settings) {
|
|
211325
211860
|
return Object.fromEntries(settings.endpointProfiles.filter((p) => p.displayName).map((p) => [p.slug, p.displayName]));
|
|
@@ -211520,25 +212055,25 @@ const AUTH_METHOD_CONTAINER_KEYS = [
|
|
|
211520
212055
|
/** Lift an unknown value into `Option<string>` if it is a non-empty string. */
|
|
211521
212056
|
const asNonEmptyString = (v) => typeof v === "string" && v.length > 0 ? require_Schema$1.some(v) : require_Schema$1.none();
|
|
211522
212057
|
/** Lift an unknown value into `Option<Record>` if it is a plain object. */
|
|
211523
|
-
const asRecord$
|
|
212058
|
+
const asRecord$9 = (v) => typeof v === "object" && v !== null && !globalThis.Array.isArray(v) ? require_Schema$1.some(v) : require_Schema$1.none();
|
|
211524
212059
|
/**
|
|
211525
212060
|
* Walk an unknown parsed JSON value looking for a subscription/plan
|
|
211526
212061
|
* identifier, returning the first match as an `Option`.
|
|
211527
212062
|
*/
|
|
211528
212063
|
function findSubscriptionType(value) {
|
|
211529
212064
|
if (globalThis.Array.isArray(value)) return require_Schema$1.firstSomeOf(value.map(findSubscriptionType));
|
|
211530
|
-
return asRecord$
|
|
212065
|
+
return asRecord$9(value).pipe(require_Schema$1.flatMap$2((record) => {
|
|
211531
212066
|
const direct = require_Schema$1.firstSomeOf(SUBSCRIPTION_TYPE_KEYS.map((key) => asNonEmptyString(record[key])));
|
|
211532
212067
|
if (require_Schema$1.isSome(direct)) return direct;
|
|
211533
|
-
return require_Schema$1.firstSomeOf(SUBSCRIPTION_CONTAINER_KEYS.map((key) => asRecord$
|
|
212068
|
+
return require_Schema$1.firstSomeOf(SUBSCRIPTION_CONTAINER_KEYS.map((key) => asRecord$9(record[key]).pipe(require_Schema$1.flatMap$2(findSubscriptionType))));
|
|
211534
212069
|
}));
|
|
211535
212070
|
}
|
|
211536
212071
|
function findAuthMethod(value) {
|
|
211537
212072
|
if (globalThis.Array.isArray(value)) return require_Schema$1.firstSomeOf(value.map(findAuthMethod));
|
|
211538
|
-
return asRecord$
|
|
212073
|
+
return asRecord$9(value).pipe(require_Schema$1.flatMap$2((record) => {
|
|
211539
212074
|
const direct = require_Schema$1.firstSomeOf(AUTH_METHOD_KEYS.map((key) => asNonEmptyString(record[key])));
|
|
211540
212075
|
if (require_Schema$1.isSome(direct)) return direct;
|
|
211541
|
-
return require_Schema$1.firstSomeOf(AUTH_METHOD_CONTAINER_KEYS.map((key) => asRecord$
|
|
212076
|
+
return require_Schema$1.firstSomeOf(AUTH_METHOD_CONTAINER_KEYS.map((key) => asRecord$9(record[key]).pipe(require_Schema$1.flatMap$2(findAuthMethod))));
|
|
211542
212077
|
}));
|
|
211543
212078
|
}
|
|
211544
212079
|
/**
|
|
@@ -212738,10 +213273,27 @@ function claudeForgeAttributionPrompt() {
|
|
|
212738
213273
|
const attribution = resolveAgentAttribution();
|
|
212739
213274
|
return `When creating git commits, do NOT add any "Claude" or "Anthropic" co-author trailers. Use exactly:\n ${attribution.coauthorTrailer}\n\nWhen creating GitHub pull request descriptions, do NOT add any "🤖 Generated with [Claude Code]" footer. Use exactly:\n ${attribution.prFooter}`;
|
|
212740
213275
|
}
|
|
212741
|
-
|
|
213276
|
+
/**
|
|
213277
|
+
* `credentialDirectory` points this turn at one Provider Account's own
|
|
213278
|
+
* `.credentials.json` without moving the transcripts, which is what lets an
|
|
213279
|
+
* account switch keep `--resume` working
|
|
213280
|
+
* (`docs/adr/claude-accounts-split-credentials-not-sessions.md`). It is null on
|
|
213281
|
+
* every runtime today: `decideClaudeCredentialDirectory` only hands back a
|
|
213282
|
+
* directory that already holds a readable credential file, and nothing writes
|
|
213283
|
+
* one yet, so the environment stays byte-for-byte what it was.
|
|
213284
|
+
*
|
|
213285
|
+
* It applies to the personal-subscription branch below and to nothing else. On
|
|
213286
|
+
* the profile / proxy branch the credential file is not what authenticates the
|
|
213287
|
+
* turn, an API key is, so pointing the variable there would add a second source
|
|
213288
|
+
* of truth that a later reader has to rule out.
|
|
213289
|
+
*/
|
|
213290
|
+
function buildClaudeQueryEnv(baseProcessEnv, endpointProfile, claudeSettings, providerProxyCredential, credentialDirectory) {
|
|
212742
213291
|
const baseEnv = scrubForgeProviderChildEnvSecrets(baseProcessEnv);
|
|
212743
213292
|
const effectiveBaseUrl = resolveClaudeQueryBaseUrl(endpointProfile, claudeSettings, providerProxyCredential);
|
|
212744
|
-
if (!effectiveBaseUrl) return
|
|
213293
|
+
if (!effectiveBaseUrl) return {
|
|
213294
|
+
...withoutInheritedAnthropicCredentials(baseEnv, { stripEndpointOverride: personalClaudeInVmEnabled(baseProcessEnv) }),
|
|
213295
|
+
...credentialDirectory ? { [CLAUDE_SECURESTORAGE_CONFIG_DIR_ENV]: credentialDirectory } : {}
|
|
213296
|
+
};
|
|
212745
213297
|
const effectiveApiKey = resolveClaudeQueryApiKey(endpointProfile, claudeSettings, providerProxyCredential?.token ?? null);
|
|
212746
213298
|
return {
|
|
212747
213299
|
...baseEnv,
|
|
@@ -212784,7 +213336,7 @@ function userInputQuestionsFromToolInput(toolInput) {
|
|
|
212784
213336
|
return (Array.isArray(toolInput.questions) ? toolInput.questions : []).map((question, idx) => userInputQuestionFromUnknown(question, idx));
|
|
212785
213337
|
}
|
|
212786
213338
|
function userInputQuestionFromUnknown(question, idx) {
|
|
212787
|
-
const value = asRecord$
|
|
213339
|
+
const value = asRecord$8(question) ?? {};
|
|
212788
213340
|
return {
|
|
212789
213341
|
id: typeof value.header === "string" ? value.header : `q-${idx}`,
|
|
212790
213342
|
header: typeof value.header === "string" ? value.header : `Question ${idx + 1}`,
|
|
@@ -212794,13 +213346,13 @@ function userInputQuestionFromUnknown(question, idx) {
|
|
|
212794
213346
|
};
|
|
212795
213347
|
}
|
|
212796
213348
|
function userInputOptionFromUnknown(option) {
|
|
212797
|
-
const value = asRecord$
|
|
213349
|
+
const value = asRecord$8(option) ?? {};
|
|
212798
213350
|
return {
|
|
212799
213351
|
label: typeof value.label === "string" ? value.label : "",
|
|
212800
213352
|
description: typeof value.description === "string" ? value.description : ""
|
|
212801
213353
|
};
|
|
212802
213354
|
}
|
|
212803
|
-
function asRecord$
|
|
213355
|
+
function asRecord$8(value) {
|
|
212804
213356
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
212805
213357
|
}
|
|
212806
213358
|
function permissionResultFromDecision(decision, toolInput, suggestions) {
|
|
@@ -213705,7 +214257,7 @@ const makeClaudeAdapter = require_Schema$1.fn("makeClaudeAdapter")(function* (op
|
|
|
213705
214257
|
const updateResumeCursor = require_Schema$1.fn("updateResumeCursor")(function* (context) {
|
|
213706
214258
|
const threadId = context.session.threadId;
|
|
213707
214259
|
if (!threadId) return;
|
|
213708
|
-
const providerDeliveryBinding = readProviderDeliveryBinding(asRecord$
|
|
214260
|
+
const providerDeliveryBinding = readProviderDeliveryBinding(asRecord$8(context.session.resumeCursor)?.providerDeliveryBinding);
|
|
213709
214261
|
const resumeCursor = {
|
|
213710
214262
|
threadId,
|
|
213711
214263
|
...context.resumeSessionId ? { resume: context.resumeSessionId } : {},
|
|
@@ -215010,6 +215562,11 @@ const makeClaudeAdapter = require_Schema$1.fn("makeClaudeAdapter")(function* (op
|
|
|
215010
215562
|
})));
|
|
215011
215563
|
const claudeSettings = serverSettings.providers.claudeAgent;
|
|
215012
215564
|
const claudeBinaryPath = claudeSettings.binaryPath;
|
|
215565
|
+
const claudeCredentialDirectory = yield* require_Schema$1.tryPromise(() => decideClaudeCredentialDirectory({
|
|
215566
|
+
env: process.env,
|
|
215567
|
+
accountIdentity: resolveClaudeProviderAccountIdentity(process.env),
|
|
215568
|
+
probeSupport: () => claudeCredentialDirectoryProbeFor(claudeBinaryPath).support()
|
|
215569
|
+
})).pipe(require_Schema$1.map((decision) => decision.outcome === "set" ? decision.directory : null), require_Schema$1.orElseSucceed(() => null));
|
|
215013
215570
|
const modelConfig = resolveClaudeSessionModelConfig(input.modelSelection, claudeSettings.endpointProfiles);
|
|
215014
215571
|
const providerProxyCredential = yield* issueClaudeProviderProxyCredential(modelConfig.endpointProfile, modelConfig.modelSlug ?? modelConfig.effectiveApiModelId ?? "unknown", threadId);
|
|
215015
215572
|
const permissionMode = permissionModeForRuntimeMode(input.runtimeMode);
|
|
@@ -215034,7 +215591,7 @@ const makeClaudeAdapter = require_Schema$1.fn("makeClaudeAdapter")(function* (op
|
|
|
215034
215591
|
existingResumeSessionId,
|
|
215035
215592
|
newSessionId,
|
|
215036
215593
|
canUseTool,
|
|
215037
|
-
env: buildClaudeQueryEnv(process.env, modelConfig.endpointProfile, claudeSettings, providerProxyCredential),
|
|
215594
|
+
env: buildClaudeQueryEnv(process.env, modelConfig.endpointProfile, claudeSettings, providerProxyCredential, claudeCredentialDirectory),
|
|
215038
215595
|
browserManager,
|
|
215039
215596
|
threadId,
|
|
215040
215597
|
browserToolEnvironmentId,
|
|
@@ -218783,7 +219340,7 @@ function makeAcpContentDeltaEvent(input) {
|
|
|
218783
219340
|
}
|
|
218784
219341
|
//#endregion
|
|
218785
219342
|
//#region ../../packages/shared/src/toolActivity.ts
|
|
218786
|
-
function asRecord$
|
|
219343
|
+
function asRecord$7(value) {
|
|
218787
219344
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
218788
219345
|
}
|
|
218789
219346
|
function asTrimmedString(value) {
|
|
@@ -218809,10 +219366,10 @@ function extractCommandFromTitle$1(title) {
|
|
|
218809
219366
|
return /`([^`]+)`/u.exec(title)?.[1]?.trim() || void 0;
|
|
218810
219367
|
}
|
|
218811
219368
|
function extractToolCommand(data, title) {
|
|
218812
|
-
const item = asRecord$
|
|
218813
|
-
const itemInput = asRecord$
|
|
218814
|
-
const itemResult = asRecord$
|
|
218815
|
-
const rawInput = asRecord$
|
|
219369
|
+
const item = asRecord$7(data?.item);
|
|
219370
|
+
const itemInput = asRecord$7(item?.input);
|
|
219371
|
+
const itemResult = asRecord$7(item?.result);
|
|
219372
|
+
const rawInput = asRecord$7(data?.rawInput);
|
|
218816
219373
|
const direct = [
|
|
218817
219374
|
normalizeCommandValue$1(item?.command),
|
|
218818
219375
|
normalizeCommandValue$1(itemInput?.command),
|
|
@@ -218840,7 +219397,7 @@ function collectPaths(value, paths, seen, depth) {
|
|
|
218840
219397
|
}
|
|
218841
219398
|
return;
|
|
218842
219399
|
}
|
|
218843
|
-
const record = asRecord$
|
|
219400
|
+
const record = asRecord$7(value);
|
|
218844
219401
|
if (!record) return;
|
|
218845
219402
|
for (const key of [
|
|
218846
219403
|
"path",
|
|
@@ -218899,7 +219456,7 @@ function deriveToolActivityPresentation(input) {
|
|
|
218899
219456
|
const title = asTrimmedString(input.title);
|
|
218900
219457
|
const detail = stripTrailingExitCode(asTrimmedString(input.detail));
|
|
218901
219458
|
const fallbackSummary = asTrimmedString(input.fallbackSummary) ?? "Tool";
|
|
218902
|
-
const data = asRecord$
|
|
219459
|
+
const data = asRecord$7(input.data);
|
|
218903
219460
|
const command = extractToolCommand(data, title);
|
|
218904
219461
|
const primaryPath = extractPrimaryPath(data);
|
|
218905
219462
|
const action = classifyToolAction({
|
|
@@ -218923,7 +219480,7 @@ function deriveToolActivityPresentation(input) {
|
|
|
218923
219480
|
...primaryPath ? { detail: primaryPath } : {}
|
|
218924
219481
|
};
|
|
218925
219482
|
if (action === "search") {
|
|
218926
|
-
const query = asTrimmedString(asRecord$
|
|
219483
|
+
const query = asTrimmedString(asRecord$7(data?.rawInput)?.query) ?? asTrimmedString(asRecord$7(data?.rawInput)?.pattern) ?? asTrimmedString(asRecord$7(data?.rawInput)?.searchTerm);
|
|
218927
219484
|
return {
|
|
218928
219485
|
summary: "Searched files",
|
|
218929
219486
|
...query ? { detail: query } : {}
|
|
@@ -230138,7 +230695,7 @@ function claudeReadinessGuardEffect(config, dependencies, resolved, input) {
|
|
|
230138
230695
|
});
|
|
230139
230696
|
}
|
|
230140
230697
|
function requiresNativeClaudeReadiness(config, provider) {
|
|
230141
|
-
return provider === "claudeAgent" && config.forgeCmmdRuntimeMode === true &&
|
|
230698
|
+
return provider === "claudeAgent" && config.forgeCmmdRuntimeMode === true && personalClaudeInVmEnabled();
|
|
230142
230699
|
}
|
|
230143
230700
|
function resolveGuardAuthority(config, readSessionId, input) {
|
|
230144
230701
|
const runtimeId = config.forgeCmmdRuntimeId?.trim();
|
|
@@ -230150,7 +230707,7 @@ function resolveGuardAuthority(config, readSessionId, input) {
|
|
|
230150
230707
|
runtimeId,
|
|
230151
230708
|
machineId,
|
|
230152
230709
|
credentialEpoch,
|
|
230153
|
-
localGenerationHash:
|
|
230710
|
+
localGenerationHash: personalClaudeGenerationHash(),
|
|
230154
230711
|
sessionId
|
|
230155
230712
|
};
|
|
230156
230713
|
}
|
|
@@ -285783,7 +286340,7 @@ function makeApprovedWorkerPlanEventState() {
|
|
|
285783
286340
|
function promptKey(masterThreadId, promptId) {
|
|
285784
286341
|
return `${masterThreadId}\n${promptId}`;
|
|
285785
286342
|
}
|
|
285786
|
-
function asRecord$
|
|
286343
|
+
function asRecord$6(value) {
|
|
285787
286344
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
285788
286345
|
}
|
|
285789
286346
|
function exactMaterializedTaskIds(value, expectedCount) {
|
|
@@ -285793,7 +286350,7 @@ function exactMaterializedTaskIds(value, expectedCount) {
|
|
|
285793
286350
|
}
|
|
285794
286351
|
function readyBatchFromActivity(state, event) {
|
|
285795
286352
|
if (event.payload.activity.kind !== "plan.task-creation.batch-succeeded") return null;
|
|
285796
|
-
const payload = asRecord$
|
|
286353
|
+
const payload = asRecord$6(event.payload.activity.payload);
|
|
285797
286354
|
const promptId = payload?.["promptId"];
|
|
285798
286355
|
if (typeof promptId !== "string") return null;
|
|
285799
286356
|
const key = promptKey(event.payload.threadId, promptId);
|
|
@@ -286993,7 +287550,7 @@ function runtimeGrantNeedsRefresh(token, identity, nowSeconds = Math.floor(Date.
|
|
|
286993
287550
|
return claims.expiresAtSeconds - nowSeconds <= RUNTIME_GRANT_REFRESH_SKEW_SECONDS;
|
|
286994
287551
|
}
|
|
286995
287552
|
/** Narrow an unknown to a plain object, or null. */
|
|
286996
|
-
function asRecord$
|
|
287553
|
+
function asRecord$5(value) {
|
|
286997
287554
|
return value !== null && typeof value === "object" ? value : null;
|
|
286998
287555
|
}
|
|
286999
287556
|
/** The raw (untrimmed) string when it is present and not blank, else null. */
|
|
@@ -287005,7 +287562,7 @@ function asTimestamp(value) {
|
|
|
287005
287562
|
return typeof value === "string" && Number.isFinite(Date.parse(value)) ? value : null;
|
|
287006
287563
|
}
|
|
287007
287564
|
function parseRuntimeGrantRefreshResponse(runtimeId, value) {
|
|
287008
|
-
const data = asRecord$
|
|
287565
|
+
const data = asRecord$5(asRecord$5(value)?.data);
|
|
287009
287566
|
if (!data || data.runtimeId !== runtimeId) return null;
|
|
287010
287567
|
const grantToken = asNonBlankString(data.grantToken);
|
|
287011
287568
|
const expiresAt = asTimestamp(data.expiresAt);
|
|
@@ -287066,7 +287623,6 @@ async function refreshRuntimeProvisioningGrant(input) {
|
|
|
287066
287623
|
async function maybeRefreshRuntimeProvisioningGrant(input) {
|
|
287067
287624
|
const current = require_runtimeControlAuthority.runtimeGrantToken();
|
|
287068
287625
|
if (!current) return "skipped";
|
|
287069
|
-
if (!runtimeGrantNeedsRefresh(current, input)) return "skipped";
|
|
287070
287626
|
const exactIdentity = exactRuntimeControlIdentity(input);
|
|
287071
287627
|
const currentClaims = currentGrantClaimsForRefresh({
|
|
287072
287628
|
currentGrant: current,
|
|
@@ -287075,6 +287631,11 @@ async function maybeRefreshRuntimeProvisioningGrant(input) {
|
|
|
287075
287631
|
});
|
|
287076
287632
|
if (!currentClaims) throw invalidRefreshResponse("Current runtime provisioning grant is unverifiable.");
|
|
287077
287633
|
const currentAuthorityClass = currentGrantAuthorityClass(currentClaims, exactIdentity);
|
|
287634
|
+
const controlAuthorityMissing = currentAuthorityClass === "exact" && exactIdentity !== null && require_runtimeControlAuthority.readRuntimeControlAuthority({
|
|
287635
|
+
runtimeId: input.runtimeId,
|
|
287636
|
+
...exactIdentity
|
|
287637
|
+
}) === null;
|
|
287638
|
+
if (!runtimeGrantNeedsRefresh(current, input) && !controlAuthorityMissing) return "skipped";
|
|
287078
287639
|
const refreshed = await refreshRuntimeProvisioningGrant({
|
|
287079
287640
|
runtimeId: input.runtimeId,
|
|
287080
287641
|
currentGrant: current,
|
|
@@ -287361,25 +287922,124 @@ const makeRuntimeDrainReporter = require_Schema$1.gen(function* () {
|
|
|
287361
287922
|
var RuntimeDrainReporter = class extends require_Schema$1.Service()("forge/runtimeDrain/RuntimeDrainReporter") {};
|
|
287362
287923
|
const RuntimeDrainReporterLive = require_Schema$1.effect(RuntimeDrainReporter, makeRuntimeDrainReporter);
|
|
287363
287924
|
//#endregion
|
|
287364
|
-
//#region src/usage/
|
|
287365
|
-
/**
|
|
287366
|
-
*
|
|
287367
|
-
*
|
|
287368
|
-
*
|
|
287369
|
-
*
|
|
287370
|
-
|
|
287371
|
-
|
|
287372
|
-
|
|
287373
|
-
|
|
287374
|
-
|
|
287375
|
-
|
|
287376
|
-
|
|
287377
|
-
|
|
287378
|
-
|
|
287379
|
-
|
|
287380
|
-
|
|
287381
|
-
|
|
287382
|
-
|
|
287925
|
+
//#region src/usage/Services/ClaudeUsageTelemetry.ts
|
|
287926
|
+
/**
|
|
287927
|
+
* Claude usage the SDK reports on every turn, published as passive telemetry.
|
|
287928
|
+
*
|
|
287929
|
+
* Every Claude turn carries a `rate_limit_event` whose `unifiedWindows`
|
|
287930
|
+
* sub-object holds the two rolling windows the Usage card renders. Reading it
|
|
287931
|
+
* costs nothing: no `claude` process, no credential delivery, no woken sprite.
|
|
287932
|
+
*
|
|
287933
|
+
* This module is the whole publish lane behind one call. `observe` takes what
|
|
287934
|
+
* an observation knows and returns void. Whether the floor admits it, which
|
|
287935
|
+
* credential generation it ran on, whether the value is new, and whether CMMD
|
|
287936
|
+
* accepted it are all decided here, so the ingestion layer stays a decoder and
|
|
287937
|
+
* a hand-off. Nothing decided here can fail a turn, and no admitted
|
|
287938
|
+
* observation outlives `CLAUDE_USAGE_TELEMETRY_PUBLISH_TIMEOUT_MS`.
|
|
287939
|
+
*
|
|
287940
|
+
* The `/usage` probe in `syncClaudeCredential.ts` is a separate lane that
|
|
287941
|
+
* publishes `source: "active_poll"`. This one publishes
|
|
287942
|
+
* `source: "passive_sdk_telemetry"`, and both lanes build their body with
|
|
287943
|
+
* `claudeUsageTelemetryBody`.
|
|
287944
|
+
*
|
|
287945
|
+
* @module ClaudeUsageTelemetry
|
|
287946
|
+
*/
|
|
287947
|
+
var ClaudeUsageTelemetry = class extends require_Schema$1.Service()("forge/usage/Services/ClaudeUsageTelemetry") {};
|
|
287948
|
+
/**
|
|
287949
|
+
* The longest one admitted observation may take, from the credential
|
|
287950
|
+
* generation lookup through the CMMD credential and the POST, before it is
|
|
287951
|
+
* abandoned and counted as failed. Ingestion does not wait on it, so this
|
|
287952
|
+
* bounds how long a slow lookup or a slow CMMD keeps a fiber alive, not how
|
|
287953
|
+
* long a turn waits. Together with the floor it caps how many observations for
|
|
287954
|
+
* one account can be in flight at once.
|
|
287955
|
+
*/
|
|
287956
|
+
const CLAUDE_USAGE_TELEMETRY_PUBLISH_TIMEOUT_MS = 1e4;
|
|
287957
|
+
/** How many accounts, and how many pairs, the observer remembers before evicting the oldest. */
|
|
287958
|
+
const MAX_TRACKED_KEYS = 32;
|
|
287959
|
+
/**
|
|
287960
|
+
* Why one publish did not land. Every reason is the same to `observe`, which
|
|
287961
|
+
* logs it and moves on, so the type exists to name the lane rather than to be
|
|
287962
|
+
* matched on.
|
|
287963
|
+
*/
|
|
287964
|
+
var ClaudeUsageTelemetryPublishError = class extends require_Schema$1.TaggedError("ClaudeUsageTelemetryPublishError") {};
|
|
287965
|
+
function pairKey(attribution) {
|
|
287966
|
+
return `${attribution.providerAccountId}\u0000${attribution.credentialGenerationId}`;
|
|
287967
|
+
}
|
|
287968
|
+
function windowFingerprint(value) {
|
|
287969
|
+
return value === null ? "-" : `${value.usedPercent}|${value.resetsAt}|${value.status}`;
|
|
287970
|
+
}
|
|
287971
|
+
/**
|
|
287972
|
+
* A value fingerprint built field by field rather than by `JSON.stringify` on
|
|
287973
|
+
* the whole object, so the comparison does not depend on key insertion order
|
|
287974
|
+
* and cannot silently start ignoring a field a future mapper adds without a
|
|
287975
|
+
* matching change here.
|
|
287976
|
+
*/
|
|
287977
|
+
function windowsFingerprint(windows) {
|
|
287978
|
+
return `${windowFingerprint(windows.fiveHour)}/${windowFingerprint(windows.sevenDay)}`;
|
|
287979
|
+
}
|
|
287980
|
+
/**
|
|
287981
|
+
* Stores `value` as the newest entry for `key` and evicts the oldest entry past
|
|
287982
|
+
* the cap. Map iteration is insertion-ordered, so the first key is the one
|
|
287983
|
+
* touched longest ago. An idle account or a rotated credential generation
|
|
287984
|
+
* rarely comes back, so evicting it costs at most one early lookup or one
|
|
287985
|
+
* redundant publish.
|
|
287986
|
+
*/
|
|
287987
|
+
function remember(map, key, value) {
|
|
287988
|
+
map.delete(key);
|
|
287989
|
+
map.set(key, value);
|
|
287990
|
+
if (map.size > MAX_TRACKED_KEYS) {
|
|
287991
|
+
const oldest = map.keys().next();
|
|
287992
|
+
if (!oldest.done) map.delete(oldest.value);
|
|
287993
|
+
}
|
|
287994
|
+
}
|
|
287995
|
+
/**
|
|
287996
|
+
* Builds the observer over injected dependencies. The HTTP call and the clock
|
|
287997
|
+
* both arrive from outside, so a test drives the whole policy without a network
|
|
287998
|
+
* or a timer.
|
|
287999
|
+
*/
|
|
288000
|
+
function makeClaudeUsageTelemetry(dependencies) {
|
|
288001
|
+
const now = dependencies.now ?? (() => Date.now());
|
|
288002
|
+
/** When each account last had an observation admitted, whatever came of it. */
|
|
288003
|
+
const admittedAt = /* @__PURE__ */ new Map();
|
|
288004
|
+
/** The fingerprint of the last value CMMD accepted, per pair. */
|
|
288005
|
+
const publishedFingerprints = /* @__PURE__ */ new Map();
|
|
288006
|
+
const publishIfNew = (windows, providerAccountId, lookupCredentialGenerationId, at) => require_Schema$1.gen(function* () {
|
|
288007
|
+
const credentialGenerationId = yield* lookupCredentialGenerationId;
|
|
288008
|
+
if (!credentialGenerationId) return;
|
|
288009
|
+
const attribution = {
|
|
288010
|
+
providerAccountId,
|
|
288011
|
+
credentialGenerationId
|
|
288012
|
+
};
|
|
288013
|
+
const key = pairKey(attribution);
|
|
288014
|
+
const fingerprint = windowsFingerprint(windows);
|
|
288015
|
+
if (publishedFingerprints.get(key) === fingerprint) return;
|
|
288016
|
+
yield* dependencies.publish(claudeUsageTelemetryBody({
|
|
288017
|
+
attribution,
|
|
288018
|
+
source: "passive_sdk_telemetry",
|
|
288019
|
+
windows,
|
|
288020
|
+
observedAt: new Date(at).toISOString()
|
|
288021
|
+
}));
|
|
288022
|
+
remember(publishedFingerprints, key, fingerprint);
|
|
288023
|
+
});
|
|
288024
|
+
/**
|
|
288025
|
+
* The floor is read and claimed inside the returned effect, never while
|
|
288026
|
+
* building it, and before anything suspends. Two turns on one account can be
|
|
288027
|
+
* in flight at once, and the floor only holds them apart if the second one
|
|
288028
|
+
* reads what the first one wrote.
|
|
288029
|
+
*/
|
|
288030
|
+
const observe = (observation) => require_Schema$1.suspend$1(() => {
|
|
288031
|
+
const { windows, providerAccountId } = observation;
|
|
288032
|
+
if (!windows || !providerAccountId) return require_Schema$1.void_;
|
|
288033
|
+
const at = now();
|
|
288034
|
+
const lastAdmittedAt = admittedAt.get(providerAccountId);
|
|
288035
|
+
if (lastAdmittedAt !== void 0 && at - lastAdmittedAt < 5e3) return require_Schema$1.void_;
|
|
288036
|
+
remember(admittedAt, providerAccountId, at);
|
|
288037
|
+
return publishIfNew(windows, providerAccountId, observation.credentialGenerationId, at).pipe(require_Schema$1.timeout(CLAUDE_USAGE_TELEMETRY_PUBLISH_TIMEOUT_MS), require_Schema$1.catchCause((cause) => require_Schema$1.logWarning("claude usage passive telemetry publish failed", {
|
|
288038
|
+
providerAccountId,
|
|
288039
|
+
cause
|
|
288040
|
+
})), require_Schema$1.asVoid);
|
|
288041
|
+
});
|
|
288042
|
+
return { observe };
|
|
287383
288043
|
}
|
|
287384
288044
|
//#endregion
|
|
287385
288045
|
//#region src/orchestration/Layers/ProviderRuntimeIngestion.ts
|
|
@@ -287394,20 +288054,37 @@ const BUFFERED_PROPOSED_PLAN_BY_ID_TTL = require_Schema$1.minutes(120);
|
|
|
287394
288054
|
const MAX_BUFFERED_ASSISTANT_CHARS = 24e3;
|
|
287395
288055
|
const PROVIDER_INGESTION_CONCURRENCY = 8;
|
|
287396
288056
|
const STRICT_PROVIDER_LIFECYCLE_GUARD = process.env.FORGE_STRICT_PROVIDER_LIFECYCLE_GUARD !== "0";
|
|
287397
|
-
|
|
288057
|
+
/**
|
|
288058
|
+
* The SDK's own `rate_limit_info` off one ClaudeAdapter rate-limit event, or
|
|
288059
|
+
* nothing. Both readers below start here: the banner takes the single window
|
|
288060
|
+
* `rateLimitType` names, and passive usage telemetry takes the `unifiedWindows`
|
|
288061
|
+
* sub-object riding along on the same object.
|
|
288062
|
+
*/
|
|
288063
|
+
function readClaudeRateLimitInfo(raw) {
|
|
287398
288064
|
const rateLimitInfo = raw.rate_limit_info;
|
|
287399
|
-
if (rateLimitInfo === null || typeof rateLimitInfo !== "object") return
|
|
288065
|
+
if (rateLimitInfo === null || typeof rateLimitInfo !== "object") return null;
|
|
288066
|
+
return rateLimitInfo;
|
|
288067
|
+
}
|
|
288068
|
+
function decodeClaudeRateLimitWindows(raw) {
|
|
288069
|
+
const rateLimitInfo = readClaudeRateLimitInfo(raw);
|
|
288070
|
+
if (!rateLimitInfo) return [];
|
|
287400
288071
|
const mapped = mapClaudeRateLimitInfo(rateLimitInfo);
|
|
287401
288072
|
return mapped ? [{
|
|
287402
288073
|
window: mapped.kind === "fiveHour" ? "five_hour" : "seven_day",
|
|
287403
288074
|
...mapped.window
|
|
287404
288075
|
}] : [];
|
|
287405
288076
|
}
|
|
288077
|
+
/** The raw telemetry object ClaudeAdapter/CodexAdapter wrap as `payload.rateLimits`. */
|
|
288078
|
+
function readRateLimitPayload(event) {
|
|
288079
|
+
if (event.type !== "account.rate-limits.updated") return null;
|
|
288080
|
+
const raw = event.payload.rateLimits;
|
|
288081
|
+
return raw !== null && typeof raw === "object" ? raw : null;
|
|
288082
|
+
}
|
|
287406
288083
|
/**
|
|
287407
288084
|
* Codex's app-server protocol shape isn't type-published anywhere this
|
|
287408
|
-
* codebase has access to, so it's read defensively
|
|
287409
|
-
* is skipped rather than thrown, since this is a best-effort in-chat notice
|
|
287410
|
-
*
|
|
288085
|
+
* codebase has access to, so it's read defensively. An unrecognized shape
|
|
288086
|
+
* is skipped rather than thrown, since this is a best-effort in-chat notice
|
|
288087
|
+
* that the turn itself never depends on.
|
|
287411
288088
|
*/
|
|
287412
288089
|
/**
|
|
287413
288090
|
* Decodes ONE window of the codex app-server `account/rateLimits/updated`
|
|
@@ -287720,6 +288397,9 @@ function shouldApplyProviderTurnCompleted(activeTurnId, eventTurnId) {
|
|
|
287720
288397
|
}
|
|
287721
288398
|
const make$9 = require_Schema$1.fn("make")(function* () {
|
|
287722
288399
|
const orchestrationEngine = yield* OrchestrationEngineService;
|
|
288400
|
+
const claudeUsageTelemetry = yield* ClaudeUsageTelemetry;
|
|
288401
|
+
const claudeUsageScope = yield* require_Schema$1.make$3("parallel");
|
|
288402
|
+
yield* require_Schema$1.addFinalizer(() => require_Schema$1.close(claudeUsageScope, require_Schema$1.void_$1));
|
|
287723
288403
|
const providerService = yield* ProviderService;
|
|
287724
288404
|
const projectionTurnRepository = yield* ProjectionTurnRepository;
|
|
287725
288405
|
const serverSettingsService = yield* ServerSettingsService;
|
|
@@ -288128,18 +288808,53 @@ const make$9 = require_Schema$1.fn("make")(function* () {
|
|
|
288128
288808
|
* SDKRateLimitInfo, reused from apps/server/src/usage/providerRateLimits.ts
|
|
288129
288809
|
* (same decoder the Usage page cache uses). Codex's app-server protocol
|
|
288130
288810
|
* shape isn't type-published anywhere this codebase has access to, so it's
|
|
288131
|
-
* read defensively
|
|
288132
|
-
* thrown error, since this is a best-effort in-chat notice
|
|
288133
|
-
*
|
|
288811
|
+
* read defensively. An unrecognized shape means no window update, not a
|
|
288812
|
+
* thrown error, since this is a best-effort in-chat notice that the turn
|
|
288813
|
+
* itself never depends on.
|
|
288134
288814
|
*/
|
|
288135
288815
|
function decodeRateLimitWindows(event) {
|
|
288136
|
-
|
|
288137
|
-
|
|
288138
|
-
if (raw === null || typeof raw !== "object") return [];
|
|
288816
|
+
const raw = readRateLimitPayload(event);
|
|
288817
|
+
if (!raw) return [];
|
|
288139
288818
|
if (event.provider === "claudeAgent") return decodeClaudeRateLimitWindows(raw);
|
|
288140
288819
|
if (event.provider === "codex") return decodeCodexRateLimitWindows(raw);
|
|
288141
288820
|
return [];
|
|
288142
288821
|
}
|
|
288822
|
+
/**
|
|
288823
|
+
* The credential generation a Claude thread's live session runs on, or null.
|
|
288824
|
+
*
|
|
288825
|
+
* It is the delivery identity the readiness guard verified before the
|
|
288826
|
+
* session started, which ProviderService writes into the provider resume
|
|
288827
|
+
* cursor and `listSessions` hands back on the live session. Claude's
|
|
288828
|
+
* delivery identity is `{kind: "credential_generation"}` and carries NO
|
|
288829
|
+
* account field, so it cannot stand in for the account the thread's model
|
|
288830
|
+
* selection names, and neither id is derivable from the other. The usage
|
|
288831
|
+
* observer drops an observation missing either one, because publishing under
|
|
288832
|
+
* a guessed id would write one account's usage onto another account's card.
|
|
288833
|
+
*/
|
|
288834
|
+
const claudeCredentialGenerationId = require_Schema$1.fn("claudeCredentialGenerationId")(function* (threadId) {
|
|
288835
|
+
const cursor = (yield* providerService.listSessions()).find((entry) => entry.threadId === threadId && entry.provider === "claudeAgent")?.resumeCursor;
|
|
288836
|
+
const binding = cursor !== null && typeof cursor === "object" ? readProviderDeliveryBinding(cursor.providerDeliveryBinding) : null;
|
|
288837
|
+
return binding?.identity.kind === "credential_generation" ? binding.identity.credentialGenerationId : null;
|
|
288838
|
+
});
|
|
288839
|
+
/**
|
|
288840
|
+
* Publishes the unified windows the same event already carried for the
|
|
288841
|
+
* banner. Passive: no probe, no credential delivery, no woken sprite. The SDK
|
|
288842
|
+
* reports these figures on every turn whether anything reads them or not.
|
|
288843
|
+
* The observer owns the floor, the credential generation lookup, dedupe, the
|
|
288844
|
+
* timeout and the HTTP call, and never fails.
|
|
288845
|
+
*/
|
|
288846
|
+
const observeClaudeUsage = require_Schema$1.fn("observeClaudeUsage")(function* (event, thread) {
|
|
288847
|
+
if (event.provider !== "claudeAgent") return;
|
|
288848
|
+
const raw = readRateLimitPayload(event);
|
|
288849
|
+
const rateLimitInfo = raw ? readClaudeRateLimitInfo(raw) : null;
|
|
288850
|
+
const windows = rateLimitInfo ? mapClaudeUnifiedWindows(rateLimitInfo) : null;
|
|
288851
|
+
if (!windows) return;
|
|
288852
|
+
yield* claudeUsageTelemetry.observe({
|
|
288853
|
+
windows,
|
|
288854
|
+
providerAccountId: readProviderAccountId(thread.modelSelection) ?? null,
|
|
288855
|
+
credentialGenerationId: claudeCredentialGenerationId(thread.id)
|
|
288856
|
+
});
|
|
288857
|
+
});
|
|
288143
288858
|
const handleRateLimitEvent = require_Schema$1.fn("handleRateLimitEvent")(function* (event, thread) {
|
|
288144
288859
|
const provider = event.provider === "claudeAgent" ? "claude" : event.provider === "codex" ? "codex" : null;
|
|
288145
288860
|
if (!provider) return;
|
|
@@ -288156,6 +288871,10 @@ const make$9 = require_Schema$1.fn("make")(function* () {
|
|
|
288156
288871
|
resetsAt: win.resetsAt,
|
|
288157
288872
|
createdAt: event.createdAt
|
|
288158
288873
|
}), { discard: true });
|
|
288874
|
+
yield* observeClaudeUsage(event, thread).pipe(require_Schema$1.catchCause((cause) => require_Schema$1.hasInterruptsOnly(cause) ? require_Schema$1.void_ : require_Schema$1.logWarning("claude usage observation failed", {
|
|
288875
|
+
eventId: event.eventId,
|
|
288876
|
+
cause
|
|
288877
|
+
})), require_Schema$1.forkIn(claudeUsageScope));
|
|
288159
288878
|
});
|
|
288160
288879
|
const processRuntimeEvent = require_Schema$1.fn("processRuntimeEvent")(function* (event) {
|
|
288161
288880
|
const thread = (yield* orchestrationEngine.getReadModel()).threads.find((entry) => entry.id === event.threadId);
|
|
@@ -288271,6 +288990,44 @@ const make$9 = require_Schema$1.fn("make")(function* () {
|
|
|
288271
288990
|
});
|
|
288272
288991
|
const ProviderRuntimeIngestionLive = require_Schema$1.effect(ProviderRuntimeIngestionService, make$9()).pipe(require_Schema$1.provide$1(ProjectionTurnRepositoryLive));
|
|
288273
288992
|
//#endregion
|
|
288993
|
+
//#region src/usage/Layers/ClaudeUsageTelemetry.ts
|
|
288994
|
+
/**
|
|
288995
|
+
* The real publish behind `ClaudeUsageTelemetry`: the owner's CMMD credential
|
|
288996
|
+
* and one POST to `CLAUDE_USAGE_TELEMETRY_PATH`.
|
|
288997
|
+
*
|
|
288998
|
+
* The call always goes out on the owner session credential, never on a
|
|
288999
|
+
* runtime grant.
|
|
289000
|
+
*
|
|
289001
|
+
* A machine with nobody signed in, or one whose CMMD credential cannot be
|
|
289002
|
+
* minted, fails the publish rather than reporting success. The observer only
|
|
289003
|
+
* remembers a value it managed to send, so a quiet success here would suppress
|
|
289004
|
+
* the publish for as long as that reading stayed the same, which on a stable
|
|
289005
|
+
* account is hours.
|
|
289006
|
+
*
|
|
289007
|
+
* @module ClaudeUsageTelemetryLive
|
|
289008
|
+
*/
|
|
289009
|
+
const LOG_PREFIX = "claude-usage-passive-telemetry";
|
|
289010
|
+
const ClaudeUsageTelemetryLive = require_Schema$1.effect(ClaudeUsageTelemetry, require_Schema$1.gen(function* () {
|
|
289011
|
+
const sql = yield* require_SqlError.SqlClient;
|
|
289012
|
+
const publish = (body) => require_Schema$1.gen(function* () {
|
|
289013
|
+
const credential = yield* resolveCmmdOwnerSessionCredential(sql, { logPrefix: LOG_PREFIX });
|
|
289014
|
+
if (credential.kind !== "credential") return yield* new ClaudeUsageTelemetryPublishError({ reason: credential.kind });
|
|
289015
|
+
yield* require_Schema$1.tryPromise({
|
|
289016
|
+
try: () => cmmdGateway.request({
|
|
289017
|
+
method: "POST",
|
|
289018
|
+
jwt: credential.jwt,
|
|
289019
|
+
path: CLAUDE_USAGE_TELEMETRY_PATH,
|
|
289020
|
+
body
|
|
289021
|
+
}),
|
|
289022
|
+
catch: (cause) => new ClaudeUsageTelemetryPublishError({
|
|
289023
|
+
reason: "request-failed",
|
|
289024
|
+
cause
|
|
289025
|
+
})
|
|
289026
|
+
});
|
|
289027
|
+
});
|
|
289028
|
+
return makeClaudeUsageTelemetry({ publish });
|
|
289029
|
+
}));
|
|
289030
|
+
//#endregion
|
|
288274
289031
|
//#region ../../packages/shared/src/DrainableWorker.ts
|
|
288275
289032
|
/**
|
|
288276
289033
|
* Create a drainable worker that processes items from an unbounded queue.
|
|
@@ -294068,7 +294825,7 @@ function resolveBuildCommitFromEnv(env) {
|
|
|
294068
294825
|
* environment descriptor down instead of reporting an honest "unknown".
|
|
294069
294826
|
*/
|
|
294070
294827
|
function readBakedBuildCommit() {
|
|
294071
|
-
return "
|
|
294828
|
+
return "8cafbb87b6204b98e93c1c69253c1c590af5f8a1";
|
|
294072
294829
|
}
|
|
294073
294830
|
async function resolveServerBuildCommit(input) {
|
|
294074
294831
|
if (isFullCommitSha(input.baked)) return input.baked;
|
|
@@ -303171,7 +303928,7 @@ const LocalAutomationSchedulerLive = require_Schema$1.effectDiscard(require_Sche
|
|
|
303171
303928
|
}));
|
|
303172
303929
|
//#endregion
|
|
303173
303930
|
//#region src/command/providerUsageEnvironment.ts
|
|
303174
|
-
function asRecord$
|
|
303931
|
+
function asRecord$4(value) {
|
|
303175
303932
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
303176
303933
|
}
|
|
303177
303934
|
function positiveInteger(value) {
|
|
@@ -303191,7 +303948,7 @@ function readRuntimeId(row) {
|
|
|
303191
303948
|
return typeof runtimeId === "string" && runtimeId.trim() !== "" ? runtimeId : null;
|
|
303192
303949
|
}
|
|
303193
303950
|
function readCandidate(runtime, ownerUserId) {
|
|
303194
|
-
const row = asRecord$
|
|
303951
|
+
const row = asRecord$4(runtime);
|
|
303195
303952
|
if (row === null || !isOwnedRuntime(row, ownerUserId) || row["desiredState"] !== "running") return null;
|
|
303196
303953
|
const organizationId = positiveInteger(row["organizationId"]);
|
|
303197
303954
|
const environmentId = readEnvironmentId(row["environmentId"]);
|
|
@@ -303226,15 +303983,17 @@ function deduplicateCandidates(candidates) {
|
|
|
303226
303983
|
* organization from browser input.
|
|
303227
303984
|
*/
|
|
303228
303985
|
function selectProviderUsageEnvironment(rawCatalog, ownerUserId) {
|
|
303229
|
-
const runtimes = asRecord$
|
|
303986
|
+
const runtimes = asRecord$4(asRecord$4(rawCatalog)?.["data"])?.["runtimes"];
|
|
303230
303987
|
if (!Array.isArray(runtimes)) return { kind: "unavailable" };
|
|
303231
303988
|
const candidates = deduplicateCandidates(runtimes.map((runtime) => readCandidate(runtime, ownerUserId)).filter((candidate) => candidate !== null));
|
|
303232
303989
|
const bestRank = Math.min(...candidates.map((candidate) => statusRank(candidate.status)));
|
|
303233
303990
|
const bestCandidates = candidates.filter((candidate) => statusRank(candidate.status) === bestRank);
|
|
303234
303991
|
if (bestCandidates.length !== 1) return { kind: "unavailable" };
|
|
303992
|
+
const selected = bestCandidates[0];
|
|
303235
303993
|
return {
|
|
303236
303994
|
kind: "ready",
|
|
303237
|
-
organizationId:
|
|
303995
|
+
organizationId: selected.organizationId,
|
|
303996
|
+
runtimeId: selected.runtimeId
|
|
303238
303997
|
};
|
|
303239
303998
|
}
|
|
303240
303999
|
//#endregion
|
|
@@ -303255,13 +304014,22 @@ function selectProviderUsageEnvironment(rawCatalog, ownerUserId) {
|
|
|
303255
304014
|
const FAILURE_TEXT = {
|
|
303256
304015
|
CLAUDE_USAGE_ENVIRONMENT_UNAVAILABLE: "Refresh needs your Environment running.",
|
|
303257
304016
|
CLAUDE_USAGE_CREDENTIAL_NOT_READY: "The Claude credential is not applied to your Environment yet.",
|
|
304017
|
+
CLAUDE_USAGE_CREDENTIAL_APPLY_FAILED: "Your Environment rejected the delivered Claude credential.",
|
|
304018
|
+
CLAUDE_USAGE_CREDENTIAL_REFRESHING: "The delivered Claude credential expired. CMMD is sending a replacement.",
|
|
303258
304019
|
CLAUDE_USAGE_REFRESH_IN_PROGRESS: "A refresh for this account is already running.",
|
|
303259
304020
|
CLAUDE_USAGE_PROBE_FAILED: "Your Environment could not read Claude usage.",
|
|
303260
304021
|
CLAUDE_USAGE_RECONNECT_REQUIRED: "Reconnect this Claude account to refresh usage.",
|
|
303261
304022
|
CLAUDE_USAGE_ACCOUNT_NOT_FOUND: "This Claude account is no longer connected.",
|
|
303262
304023
|
CLAUDE_USAGE_REFRESH_UNAVAILABLE: "Claude usage refresh is unavailable."
|
|
303263
304024
|
};
|
|
303264
|
-
/**
|
|
304025
|
+
/**
|
|
304026
|
+
* Verbatim from CMMD `personal-claude-usage.ts` and `provider-usage-service.ts`.
|
|
304027
|
+
*
|
|
304028
|
+
* Three reasons land on `CLAUDE_USAGE_CREDENTIAL_NOT_READY` because the refresh
|
|
304029
|
+
* response cannot tell them apart. `claudeUsageCredentialReadiness.ts` splits
|
|
304030
|
+
* them afterwards by reading the runtime's readiness, which is the only place
|
|
304031
|
+
* that knows whether a delivery is on its way or has already failed.
|
|
304032
|
+
*/
|
|
303265
304033
|
const CODE_BY_UPSTREAM_REASON = {
|
|
303266
304034
|
runtime_not_active: "CLAUDE_USAGE_ENVIRONMENT_UNAVAILABLE",
|
|
303267
304035
|
claude_native_delivery_disabled: "CLAUDE_USAGE_ENVIRONMENT_UNAVAILABLE",
|
|
@@ -303274,7 +304042,12 @@ const CODE_BY_UPSTREAM_REASON = {
|
|
|
303274
304042
|
usage_receipt_invalid: "CLAUDE_USAGE_PROBE_FAILED",
|
|
303275
304043
|
usage_output_invalid: "CLAUDE_USAGE_PROBE_FAILED"
|
|
303276
304044
|
};
|
|
303277
|
-
|
|
304045
|
+
/**
|
|
304046
|
+
* The one place a failure body is built, so every sentence a browser reads is
|
|
304047
|
+
* written in `FAILURE_TEXT` above. `claudeUsageCredentialReadiness.ts` calls it
|
|
304048
|
+
* to reach the two codes no upstream reason maps to.
|
|
304049
|
+
*/
|
|
304050
|
+
function describeClaudeUsageRefreshFailureCode(code, status) {
|
|
303278
304051
|
return {
|
|
303279
304052
|
status,
|
|
303280
304053
|
body: {
|
|
@@ -303283,6 +304056,9 @@ function failure(code, status) {
|
|
|
303283
304056
|
}
|
|
303284
304057
|
};
|
|
303285
304058
|
}
|
|
304059
|
+
function failure(code, status) {
|
|
304060
|
+
return describeClaudeUsageRefreshFailureCode(code, status);
|
|
304061
|
+
}
|
|
303286
304062
|
/**
|
|
303287
304063
|
* CMMD sends the reason as `{ "error": "<reason>" }`. Anything else, including
|
|
303288
304064
|
* a longer sentence that happens to start with an allowlisted word, is treated
|
|
@@ -303303,6 +304079,77 @@ function describeClaudeUsageRefreshFailure(error) {
|
|
|
303303
304079
|
return failure("CLAUDE_USAGE_REFRESH_UNAVAILABLE", error.status === 503 ? 503 : 502);
|
|
303304
304080
|
}
|
|
303305
304081
|
//#endregion
|
|
304082
|
+
//#region src/command/claudeUsageCredentialReadiness.ts
|
|
304083
|
+
/**
|
|
304084
|
+
* Which "credential not ready" a Claude usage refresh actually hit.
|
|
304085
|
+
*
|
|
304086
|
+
* CMMD answers a blocked refresh with one reason for three different worlds:
|
|
304087
|
+
* a delivery still travelling, a delivery CMMD cannot produce, and a delivery
|
|
304088
|
+
* the Environment already ran and rejected. `claudeUsageRefreshFailure.ts` has
|
|
304089
|
+
* to collapse all three into `CLAUDE_USAGE_CREDENTIAL_NOT_READY`, because the
|
|
304090
|
+
* refresh response carries nothing that separates them. The card then says
|
|
304091
|
+
* "not applied yet", which asks the user to wait even when waiting can never
|
|
304092
|
+
* work: on 2026-09-08 CMMD's reconciler failed native credential validation on
|
|
304093
|
+
* runtime 523cd771 forty-three times in seventy-two minutes, and the card said
|
|
304094
|
+
* "yet" through all of it.
|
|
304095
|
+
*
|
|
304096
|
+
* The runtime readiness read is the one source that separates the worlds, so
|
|
304097
|
+
* the handler makes that read only for the collapsed code and hands the answer
|
|
304098
|
+
* here. Every sentence is written in `claudeUsageRefreshFailure.ts`; nothing
|
|
304099
|
+
* from CMMD's `lastErrorCode` or `lastErrorMessage` is repeated to a browser.
|
|
304100
|
+
*/
|
|
304101
|
+
const CMMD_READINESS_PATH_PREFIX = "/api/forge/context/providers/claude/runtime-readiness/";
|
|
304102
|
+
/** Typed as the gateway's absolute-path shape so the call site needs no cast. */
|
|
304103
|
+
function claudeUsageReadinessPath(runtimeId) {
|
|
304104
|
+
return `${CMMD_READINESS_PATH_PREFIX}${encodeURIComponent(runtimeId)}`;
|
|
304105
|
+
}
|
|
304106
|
+
/**
|
|
304107
|
+
* Whether a readiness read would tell the caller anything it does not know.
|
|
304108
|
+
*
|
|
304109
|
+
* Only the collapsed code is ambiguous. Every other failure already names its
|
|
304110
|
+
* own cause, so asking CMMD again would cost a round trip on the error path
|
|
304111
|
+
* and could only produce a less specific answer.
|
|
304112
|
+
*/
|
|
304113
|
+
function claudeUsageFailureWantsReadiness(failure) {
|
|
304114
|
+
return failure.body.code === "CLAUDE_USAGE_CREDENTIAL_NOT_READY";
|
|
304115
|
+
}
|
|
304116
|
+
function asRecord$3(value) {
|
|
304117
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
304118
|
+
}
|
|
304119
|
+
function readString(value) {
|
|
304120
|
+
return typeof value === "string" && value !== "" ? value : null;
|
|
304121
|
+
}
|
|
304122
|
+
/**
|
|
304123
|
+
* Refines the collapsed credential failure using the runtime's readiness.
|
|
304124
|
+
*
|
|
304125
|
+
* The ranking is by what the user can act on, not by which field looks most
|
|
304126
|
+
* severe:
|
|
304127
|
+
*
|
|
304128
|
+
* 1. A reason of `credential_reconnect_required` or `credential_not_connected`
|
|
304129
|
+
* means CMMD holds no usable grant. That is upstream of delivery, so it wins
|
|
304130
|
+
* even when the delivery it caused also failed. No re-delivery helps; only a
|
|
304131
|
+
* reconnect does.
|
|
304132
|
+
* 2. `deliveryStatus: "failed"` means CMMD had a credential, sent it, and the
|
|
304133
|
+
* Environment rejected it. Waiting cannot fix that, which is exactly what
|
|
304134
|
+
* the collapsed sentence tells the user to do.
|
|
304135
|
+
* 3. `credential_expired_refreshing` means a replacement is already in flight,
|
|
304136
|
+
* so the honest instruction is to retry shortly.
|
|
304137
|
+
*
|
|
304138
|
+
* Anything else, including a readiness read that failed or answered a shape
|
|
304139
|
+
* this module does not recognise, keeps the collapsed failure. A refinement
|
|
304140
|
+
* that guessed would be worse than the vague sentence it replaced.
|
|
304141
|
+
*/
|
|
304142
|
+
function refineClaudeUsageRefreshFailure(failure, rawReadiness) {
|
|
304143
|
+
if (!claudeUsageFailureWantsReadiness(failure)) return failure;
|
|
304144
|
+
const readiness = asRecord$3(asRecord$3(rawReadiness)?.["data"]);
|
|
304145
|
+
if (readiness === null) return failure;
|
|
304146
|
+
const reasonCode = claudeReadinessReasonCode(readString(readiness["reason"]));
|
|
304147
|
+
if (reasonCode === "CREDENTIAL_RECONNECT_REQUIRED") return describeClaudeUsageRefreshFailureCode("CLAUDE_USAGE_RECONNECT_REQUIRED", 409);
|
|
304148
|
+
if (readiness["deliveryStatus"] === "failed") return describeClaudeUsageRefreshFailureCode("CLAUDE_USAGE_CREDENTIAL_APPLY_FAILED", 503);
|
|
304149
|
+
if (reasonCode === "CREDENTIAL_EXPIRED_REFRESHING") return describeClaudeUsageRefreshFailureCode("CLAUDE_USAGE_CREDENTIAL_REFRESHING", 503);
|
|
304150
|
+
return failure;
|
|
304151
|
+
}
|
|
304152
|
+
//#endregion
|
|
303306
304153
|
//#region src/command/wsToken.ts
|
|
303307
304154
|
/**
|
|
303308
304155
|
* Build the WS token response given an optional PAT and the configured CMMD
|
|
@@ -305168,6 +306015,25 @@ function buildClaudeUsageRefreshErrorResponse(error) {
|
|
|
305168
306015
|
const failure = describeClaudeUsageRefreshFailure(error);
|
|
305169
306016
|
return require_HttpServer.jsonUnsafe(failure.body, { status: failure.status });
|
|
305170
306017
|
}
|
|
306018
|
+
/**
|
|
306019
|
+
* Answers a refresh CMMD refused, asking the selected runtime what state its
|
|
306020
|
+
* Claude credential is in when the refusal alone cannot say. Only the collapsed
|
|
306021
|
+
* credential code costs the extra read, and a readiness read that itself fails
|
|
306022
|
+
* leaves the original answer untouched.
|
|
306023
|
+
*/
|
|
306024
|
+
function answerBlockedClaudeUsageRefresh(input) {
|
|
306025
|
+
return require_Schema$1.gen(function* () {
|
|
306026
|
+
const failure = describeClaudeUsageRefreshFailure(input.error);
|
|
306027
|
+
if (!claudeUsageFailureWantsReadiness(failure)) return require_HttpServer.jsonUnsafe(failure.body, { status: failure.status });
|
|
306028
|
+
const readinessOutcome = yield* runCmmdRestProxy(input.session, (jwt) => cmmdGateway.request({
|
|
306029
|
+
method: "GET",
|
|
306030
|
+
jwt,
|
|
306031
|
+
path: claudeUsageReadinessPath(input.runtimeId)
|
|
306032
|
+
}), { jwt: input.requestJwt });
|
|
306033
|
+
const refined = refineClaudeUsageRefreshFailure(failure, readinessOutcome.ok ? readinessOutcome.value : null);
|
|
306034
|
+
return require_HttpServer.jsonUnsafe(refined.body, { status: refined.status });
|
|
306035
|
+
});
|
|
306036
|
+
}
|
|
305171
306037
|
function makeRefreshProviderUsageHandler(dependencies = {}) {
|
|
305172
306038
|
return require_Schema$1.gen(function* () {
|
|
305173
306039
|
const request = yield* require_HttpServer.HttpServerRequest;
|
|
@@ -305204,7 +306070,12 @@ function makeRefreshProviderUsageHandler(dependencies = {}) {
|
|
|
305204
306070
|
body: { providerAccountId: input.providerAccountId },
|
|
305205
306071
|
orgId: String(target.organizationId)
|
|
305206
306072
|
}), { jwt: requestJwt });
|
|
305207
|
-
if (!refreshOutcome.ok) return
|
|
306073
|
+
if (!refreshOutcome.ok) return yield* answerBlockedClaudeUsageRefresh({
|
|
306074
|
+
session,
|
|
306075
|
+
requestJwt,
|
|
306076
|
+
runtimeId: target.runtimeId,
|
|
306077
|
+
error: refreshOutcome.error
|
|
306078
|
+
});
|
|
305208
306079
|
const snapshotOutcome = yield* runCmmdRestProxy(session, (jwt) => cmmdGateway.request({
|
|
305209
306080
|
method: "GET",
|
|
305210
306081
|
jwt,
|
|
@@ -305303,6 +306174,22 @@ registerProxyRoute({
|
|
|
305303
306174
|
responseShape: "unwrapData",
|
|
305304
306175
|
successStatus: 200
|
|
305305
306176
|
});
|
|
306177
|
+
registerProxyRoute({
|
|
306178
|
+
forgePath: "/api/cmmd/forge-runtimes/:runtimeId/execution-intents",
|
|
306179
|
+
method: "POST",
|
|
306180
|
+
cmmdPath: "/api/forge/context/runtimes/:runtimeId/execution-intents",
|
|
306181
|
+
orgIdSource: "none",
|
|
306182
|
+
responseShape: "unwrapData",
|
|
306183
|
+
successStatus: 200
|
|
306184
|
+
});
|
|
306185
|
+
registerProxyRoute({
|
|
306186
|
+
forgePath: "/api/cmmd/forge-runtimes/:runtimeId/execution-intents/:intentId/dispatched",
|
|
306187
|
+
method: "POST",
|
|
306188
|
+
cmmdPath: "/api/forge/context/runtimes/:runtimeId/execution-intents/:intentId/dispatched",
|
|
306189
|
+
orgIdSource: "none",
|
|
306190
|
+
responseShape: "unwrapData",
|
|
306191
|
+
successStatus: 200
|
|
306192
|
+
});
|
|
305306
306193
|
registerProxyRoute({
|
|
305307
306194
|
forgePath: "/api/cmmd/devices/:deviceId/revoke",
|
|
305308
306195
|
method: "POST",
|
|
@@ -316543,7 +317430,7 @@ function claimClaudeReadinessJti(input) {
|
|
|
316543
317430
|
return "claimed";
|
|
316544
317431
|
}
|
|
316545
317432
|
const readUsableClaudeGenerationHash = (config) => {
|
|
316546
|
-
const configuredGenerationHash =
|
|
317433
|
+
const configuredGenerationHash = bootExportedPersonalClaudeGenerationHash();
|
|
316547
317434
|
if (!/^[a-f0-9]{64}$/.test(configuredGenerationHash)) return require_Schema$1.succeed(null);
|
|
316548
317435
|
return require_Schema$1.tryPromise(() => (0, node_fs_promises.readFile)(resolveLocalClaudeCredentialPath(config.forgeClaudeConfigPath), "utf8")).pipe(require_Schema$1.map((credentialJson) => isClaudeSubscriptionCredential(credentialJson) ? configuredGenerationHash : null), require_Schema$1.orElseSucceed(() => null));
|
|
316549
317436
|
};
|
|
@@ -317061,7 +317948,7 @@ const PlatformServicesLive = require_Schema$1.unwrap(require_Schema$1.gen(functi
|
|
|
317061
317948
|
}));
|
|
317062
317949
|
const RuntimeReplicationLayerLive = RuntimeReplicationLive.pipe(require_Schema$1.provideMerge(RuntimeReplicationClientLive), require_Schema$1.provideMerge(RuntimeReplicationOutboxLive), require_Schema$1.provideMerge(ActiveHourMeterLive.pipe(require_Schema$1.provide$1(RuntimeReplicationOutboxLive))));
|
|
317063
317950
|
const RuntimeDrainLayerLive = RuntimeDrainReporterLive.pipe(require_Schema$1.provideMerge(RuntimeDrainStateLive));
|
|
317064
|
-
const RuntimeAwareProviderReactorsLive = require_Schema$1.mergeAll(ProviderRuntimeIngestionLive, ProviderCommandReactorLive).pipe(require_Schema$1.provide$1(RuntimeDrainLayerLive));
|
|
317951
|
+
const RuntimeAwareProviderReactorsLive = require_Schema$1.mergeAll(ProviderRuntimeIngestionLive, ProviderCommandReactorLive).pipe(require_Schema$1.provide$1(RuntimeDrainLayerLive), require_Schema$1.provide$1(ClaudeUsageTelemetryLive));
|
|
317065
317952
|
const PipelineWorkerCoordinatorLayerLive = PipelineWorkerCoordinatorLive.pipe(require_Schema$1.provideMerge(PipelineRunClientLive));
|
|
317066
317953
|
const AgentProtocolReactorLayerLive = AgentProtocolReactorLive.pipe(require_Schema$1.provideMerge(PipelineRunStoreLive));
|
|
317067
317954
|
const PipelineRunCoordinatorLayerLive = PipelineRunCoordinatorLive.pipe(require_Schema$1.provideMerge(PipelineProviderAuthorityInstallerLive), require_Schema$1.provideMerge(PipelineChangeInspectorLive), require_Schema$1.provideMerge(PipelineContextArtifactStoreLive), require_Schema$1.provideMerge(PipelineModelPolicyLive), require_Schema$1.provideMerge(PipelineRunClientLive), require_Schema$1.provideMerge(PipelineRunStoreLive), require_Schema$1.provideMerge(PipelineRunWorkspaceAllocatorLive), require_Schema$1.provideMerge(PipelineStagePolicyLive), require_Schema$1.provideMerge(PipelineWorkerCoordinatorLayerLive));
|